0
votes

Problem Statement: I am trying to model and optimize a placement problem of placing the nodes of an undirected graph in a grid of cells such that the weighted Euclidean length is minimized subject to the constraint each grid cell can contain only a certain number of nodes based on its weighted-capacity. I am trying to use CVXPY to frame the model as a convex problem of weighted edge length minimization and optimize. The grid is organized as a 2X3 matrix. When I try to convert the node locations to a grid number with an expression and try to use the elements of the expression vector as an array index ( so that I can sum up all node weights in that grid cell), CVXPY does not seem to work.

Here is some code I tried. Sample graph of 10 nodes with weighted edge matrix. Line 97 (gn = gridNum[i]) and 99 seem to have an issue( highlighted by PROBLEM comment. The last print line output is expected to show all the nodes are in only grid cell 0.


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@author: rajkumar
"""

import numpy as np
import cvxpy as cvxp
import matplotlib.pyplot as plt


# Problem data creation
# Given a set of nodes and weighted edges,
# place them on a grid of cells which have a link capacity,
# such that the total euclidean distance is minimized
# subject to the constraint that number of nodes in a grid cell
# dont exceed a certain capacity

# Will deal with maximizing grid cell link capacity objective function later
# once I get the grid cell number issue fixed
# E.g graph - 10 nodes with connectivity matrix
# 6 grid cells arranged as 2x3
num_nodes = cvxp.Parameter(nonneg=True,value=10)
num_grid_cells = cvxp.Parameter(nonneg=True,value=6)
# Max X and Y grid coordinate values
max_X = cvxp.Parameter(nonneg=True, value=(num_grid_cells.value/2))
max_Y = cvxp.Parameter(nonneg=True,value=(num_grid_cells.value/3))

#Weight of each node
nodeWts = np.array([10,20,15,12,19,11,14,9,12,8])
#Capacity of each grid cell
gridCellCapacities = np.array([30,30,30,30,30,30])
# created a Variable intialized with above
gridCapacity = cvxp.Parameter(shape=num_grid_cells.value, value=gridCellCapacities, nonneg=True)

# Node adjacency matrix
cellConnectivity = np.matrix([[0.,5.,2.,3.,4.,5.,0.,0.,0.,0.],
                              [5.,0.,0.,2.,3.,4.,5.,6.,7.,8.],
                              [2.,0.,0.,2.,3.,4.,5.,6.,7.,8.],
                              [3.,2.,2.,0.,2.,3.,4.,5.,6.,7.],
                              [4.,3.,3.,2.,0.,2.,3.,4.,5.,6.],
                              [5.,4.,4.,3.,2.,0.,2.,3.,4.,5.],
                              [0.,5.,5.,4.,3.,2.,0.,2.,3.,4.],
                              [0.,6.,6.,5.,4.,3.,2.,0.,2.,3.],
                              [0.,7.,7.,6.,5.,4.,3.,2.,0.,8.],
                              [0.,8.,8.,7.,6.,5.,4.,3.,8.,0.]])



cellWeightedDeg = np.matrix([[19,0,0,0,0,0,0,0,0,0],
                     [0,40,0,0,0,0,0,0,0,0],
                     [0,0,37,0,0,0,0,0,0,0],
                     [0,0,0,34,0,0,0,0,0,0],
                     [0,0,0,0,32,0,0,0,0,0],
                     [0,0,0,0,0,32,0,0,0,0],
                     [0,0,0,0,0,0,28,0,0,0],
                     [0,0,0,0,0,0,0,31,0,0],
                     [0,0,0,0,0,0,0,0,42,0],
                     [0,0,0,0,0,0,0,0,0,49]])
# Positive semi definite laplacian
cellLP = cellWeightedDeg - cellConnectivity

locX = cvxp.Variable(num_nodes.value,pos=True)
locY = cvxp.Variable(num_nodes.value,pos=True)

# Variable to store sum of node weights in a grid cell
gridWt = cvxp.Variable(num_grid_cells.value, pos=True)

#Number of nodes in each grid cell - num_nodes x num_grid_cells matrix
nodesInGridCells = cvxp.Variable(shape=(num_nodes.value,num_grid_cells.value))

#### Some workaround if not positive semi definite
cellLP = 0.5*(cellLP+cellLP.T)           # make Q symmetric
w, v = np.linalg.eig(cellLP)   # eigen decomposition
print("Eigenvalues\n",w)
w1 = min(w)               # first eigen value
print("Smallest eigenvalue",w1)
tol = 1.0e-10          # tolerance 
f = 0                  # factor for diagonal perturbation
if w1<tol:                 
  f = -w1 + tol
cellLP += f*np.eye(10)

print('cellLP - sh', cellLP)

# Get grid number from X,Y locations of nodes
# Grid arrangement
# 3,4,5
# 0,1,2

gridW = np.array([0,0,0,0,0,0])
gridNum = cvxp.ceil(locX) + cvxp.abs(cvxp.square(cvxp.ceil(locY))) - 2

# The following loop is to get the gridNum from the above expression vector
# so that it can be used to index into gridW - weight array
# Elementwise extraction from gridNum expression is not working
# PROBLEM IN THIS LOOP
for i in range(num_nodes.value):
    gn = gridNum[i]
    print('ISGV', gn.is_vector())
    gridW[gn.value] += nodeWts[i]
print("GN_IS_VEC: ", gridNum.is_vector())

constraints = [locX >= 0.1, locY >= 0.1, locX <= max_X.value, locY <= max_Y.value]

objectiveX = (1/2)*cvxp.quad_form(locX,cellLP)
objectiveY = (1/2)*cvxp.quad_form(locY,cellLP)

# PSD based objective
#objectiveX = (1/2)*cvxp.quad_form(locX, cvxp.Parameter(shape=cellLP.shape, value=cellLP, PSD=True))
#objectiveY = (1/2)*cvxp.quad_form(locY, cvxp.Parameter(shape=cellLP.shape, value=cellLP, PSD=True))

prob = cvxp.Problem(cvxp.Minimize(objectiveX+objectiveY), constraints)
prob.solve();
print('MINWL',prob.value)
print('X: ', locX.value)
print('Y: ', locY.value)
print('GN: ', gridNum.value)
# Wrong values printed - Only grid number 0 should have all the nodes
print('GW: ', gridW)

OUTPUT FROM CVXPY

Eigenvalues
 [-7.10542736e-15  5.82050153e+01  1.91148884e+01  5.18253316e+01
  3.09023706e+01  4.05651162e+01  3.85097439e+01  3.38040401e+01
  3.48251251e+01  3.62483688e+01]

Smallest eigenvalue -7.105427357601002e-15

cellLP - sh [[19. -5. -2. -3. -4. -5.  0.  0.  0.  0.]
 [-5. 40.  0. -2. -3. -4. -5. -6. -7. -8.]
 [-2.  0. 37. -2. -3. -4. -5. -6. -7. -8.]
 [-3. -2. -2. 34. -2. -3. -4. -5. -6. -7.]
 [-4. -3. -3. -2. 32. -2. -3. -4. -5. -6.]
 [-5. -4. -4. -3. -2. 32. -2. -3. -4. -5.]
 [ 0. -5. -5. -4. -3. -2. 28. -2. -3. -4.]
 [ 0. -6. -6. -5. -4. -3. -2. 31. -2. -3.]
 [ 0. -7. -7. -6. -5. -4. -3. -2. 42. -8.]
 [ 0. -8. -8. -7. -6. -5. -4. -3. -8. 49.]]

ISGV True
ISGV True
ISGV True
ISGV True
ISGV True
ISGV True
ISGV True
ISGV True
ISGV True
ISGV True
GN_IS_VEC:  True
MINWL 2.872635462836115e-11
X:  [0.16948144 0.16948144 0.16948144 0.16948144 0.16948144 0.16948144
 0.16948144 0.16948144 0.16948144 0.16948144]
Y:  [0.16948144 0.16948144 0.16948144 0.16948144 0.16948144 0.16948144
 0.16948144 0.16948144 0.16948144 0.16948144]
GN:  [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
GW: [130 130 130 130 130 130]   <---- Problem here
1

1 Answers

0
votes

Ok. I found a way to make CVXPY compare values and then generate an index.

for i in range(num_nodes.value):
    idx = 0
    for j in range(num_grid_cells.value):
        if(gridNum[i].value.__eq__(j)):
            idx = j
            break
    gridW[idx] += nodeWts[i]

Though this gives the correct grid cell weights - gridW, the optimizer is not able to Minimize ( minimized distance is inf ) when the weight constraint is added like:

constraints = [locX >= 0.1, locY >= 0.1, locX <= max_X.value, locY <= max_Y.value, gridW <= gridCapacity]

OUTPUT from CVXPY:

Eigenvalues
 [-7.10542736e-15  5.82050153e+01  1.91148884e+01  5.18253316e+01
  3.09023706e+01  4.05651162e+01  3.85097439e+01  3.38040401e+01
  3.48251251e+01  3.62483688e+01]
Smallest eigenvalue -7.105427357601002e-15
cellLP - sh [[19. -5. -2. -3. -4. -5.  0.  0.  0.  0.]
 [-5. 40.  0. -2. -3. -4. -5. -6. -7. -8.]
 [-2.  0. 37. -2. -3. -4. -5. -6. -7. -8.]
 [-3. -2. -2. 34. -2. -3. -4. -5. -6. -7.]
 [-4. -3. -3. -2. 32. -2. -3. -4. -5. -6.]
 [-5. -4. -4. -3. -2. 32. -2. -3. -4. -5.]
 [ 0. -5. -5. -4. -3. -2. 28. -2. -3. -4.]
 [ 0. -6. -6. -5. -4. -3. -2. 31. -2. -3.]
 [ 0. -7. -7. -6. -5. -4. -3. -2. 42. -8.]
 [ 0. -8. -8. -7. -6. -5. -4. -3. -8. 49.]]
MINWL inf
X:  None
Y:  None
GN:  None
GW: [130   0   0   0   0   0]