From 313ef6b5bf67f816dee9032edc7667878b6dec9f Mon Sep 17 00:00:00 2001
From: Adam Sitabkhan <adam.sitabkhan@gmail.com>
Date: Fri, 10 Jan 2025 19:32:46 -0600
Subject: [PATCH] Basic grid placement with optimization

---
 guided_mrmp/controllers/place_grid.py | 111 ++++++++++++++++++++++++++
 1 file changed, 111 insertions(+)
 create mode 100644 guided_mrmp/controllers/place_grid.py

diff --git a/guided_mrmp/controllers/place_grid.py b/guided_mrmp/controllers/place_grid.py
new file mode 100644
index 0000000..e66f048
--- /dev/null
+++ b/guided_mrmp/controllers/place_grid.py
@@ -0,0 +1,111 @@
+import cvxpy as cp
+import numpy as np
+
+def place_grid(robot_locations, cell_size=1, grid_shape=(5, 5)):
+    """
+        Place a grid to cover robot locations with alignment to centers.
+
+        inputs:
+            - robot_locations (list): locations of robots involved in conflict [[x,y], [x,y], ...]
+            - cell_size (float): the width of each grid cell in continuous space
+            - grid_shape (tuple): (# of rows, # of columns) of the grid
+        outputs:
+            - origin (tuple): bottom-left corner of the grid in continuous space
+    """
+    robot_locations = np.array(robot_locations)
+    N = len(robot_locations)
+    
+    # Decision variable: Bottom-left corner of the grid in continuous space
+    origin = cp.Variable(2, name='origin')
+    
+    # Decision variable: Integer grid indices for each robot
+    grid_indices = cp.Variable((N, 2), integer=True, name='grid_indices')
+    
+    # Calculate cell centers for each robot based on grid indices
+    # Reshape origin to (1, 2) for broadcasting
+    cell_centers = cp.reshape(origin, (1, 2), order='C') + grid_indices * cell_size + cell_size / 2
+    
+    # Objective: Minimize the sum of squared distances
+    cost = cp.sum_squares(robot_locations - cell_centers)
+    
+    # Constraints
+    constraints = []
+    
+    # Grid indices must be non-negative
+    constraints.append(grid_indices >= 0)
+    
+    # Grid indices must fit within grid bounds
+    if grid_shape[0] == grid_shape[1]: # Square grid
+        constraints.append(grid_indices <= grid_shape[0] - 1)
+    else: # Rectangular grid
+        constraints.append(grid_indices[:,0] <= grid_shape[1] - 1)
+        constraints.append(grid_indices[:,1] <= grid_shape[0] - 1)
+    
+    # No two robots can share a cell
+    # Reformulation of the constraints
+    #   abs(grid_indices[i, 0] - grid_indices[j, 0]) >= 1 and
+    #   abs(grid_indices[i, 1] - grid_indices[j, 1]) >= 1
+    # to be compatible with solver
+    for i in range(N):
+        for j in range(i+1, N):
+            # Auxiliary variable for the distance between cell centers i and j in the x direction
+            xdist = cp.Variable(name=f"xdist_{i}_{j}")  
+            constraints.append(xdist >= grid_indices[i, 0] - grid_indices[j, 0])
+            constraints.append(xdist >= -(grid_indices[i, 0] - grid_indices[j, 0]))
+            
+            # Auxiliary variable for the distance between cell centers i and j in the y direction
+            ydist = cp.Variable(name=f"ydist_{i}_{j}")  
+            constraints.append(ydist >= grid_indices[i, 1] - grid_indices[j, 1])
+            constraints.append(ydist >= -(grid_indices[i, 1] - grid_indices[j, 1]))
+            
+            # Enforce that robots must be at least one cell apart
+            constraints.append(xdist + ydist >= cell_size) 
+    
+    # Solve the optimization problem
+    prob = cp.Problem(cp.Minimize(cost), constraints)
+    prob.solve(solver=cp.SCIP, scip_params={
+        "numerics/feastol": 1e-6, 
+        "numerics/dualfeastol": 1e-6,
+    })
+
+    if prob.status not in ["optimal", "optimal_inaccurate"]:
+        print("problem could not be solved to optimality")
+        return None
+    print(prob.status)
+    return origin.value, cell_centers.value
+        
+def main():
+    robot_locations = [(1.2, 1.6), (1.6, 1.2)]
+    cell_size = 1
+    grid_shape = (5, 5)
+    
+    origin, cell_centers = place_grid(robot_locations, cell_size, grid_shape)
+    print("Grid Origin (Bottom-Left Corner):", origin)
+    print(cell_centers)
+
+    import matplotlib.pyplot as plt
+    
+    plt.figure(figsize=(4, 4))
+    # Draw the grid
+    for i in range(grid_shape[1] + 1):
+        # Draw vertical lines
+        plt.plot([origin[0] + i * cell_size, origin[0] + i * cell_size], 
+                    [origin[1], origin[1] + grid_shape[0] * cell_size], 'k-')
+    for i in range(grid_shape[0] + 1):
+        # Draw horizontal lines
+        plt.plot([origin[0], origin[0] + grid_shape[1] * cell_size], 
+                    [origin[1] + i * cell_size, origin[1] + i * cell_size], 'k-')
+
+    # Plot robot locations
+    robot_locations = np.array(robot_locations)
+    plt.scatter(robot_locations[:, 0], robot_locations[:, 1], c='r', label='Robot Locations')
+
+    # Plot cell centers
+    cell_centers = np.array(cell_centers)
+    plt.scatter(cell_centers[:, 0], cell_centers[:, 1], c='b', label='Cell Centers')
+    plt.legend()
+
+    plt.show()
+
+if __name__ == "__main__":
+    main()
\ No newline at end of file
-- 
GitLab