I am trying to solve an optimization problem, using the CP-Sat solver of Ortools, that is very similar to a standard Pickup and Delivery example but with a twist that I cannot figure out how to solve. A simplified version of my problem would be:
I have some containers that needs to be picked up, by a single vehicle, and moved to another area with pre-allocated "parking-lots". The task is to find the minimal travel distance of the vehicle. Each pickup can be dropped off on any of the parking lots in the other area.
I have so far managed to solve this by utilizing AddCircuit, on a graph with a start node with edges going out to all pickup nodes, and with each pickup node having edges going out to all parking nodes and modelling the possible edges as booleans.
Example: start node (0), two pickups (1,2) and two parking lots (3,4). See figure.
A twist to the problem that I cannot figure out how solve, is that there should be an ordering of the parkings. Imagine the parking lot is a tunnel with one open end. I.e. I cannot park at the bottom of the tunnel if a container has already been parked in beginning of the tunnel. For the example attached, I cannot park a container on node 4 if node 3 has already had a container parked. In this case the attached minimal path solution (0->1->3->2->4) is not valid but should rather be 0->2->4->1->3->0 (also being the only feasible path for this simple example).
I would appreciate any help I could get on this.
A solution to the example without the ordering of the parkings are shown below:
import numpy as np
from ortools.sat.python import cp_model
model = cp_model.CpModel()
solver = cp_model.CpSolver()
start_node = (0, 0)
pickup_nodes = [(-1, 1), (1, 3)]
drop_off_nodes = [(-1, 2), (1, 4)]
all_nodes = [start_node] + pickup_nodes + drop_off_nodes
pickup_indices = [1, 2]
drop_off_indices = [3, 4]
scale = 100 # scale to solve rounding problem
# using euclidean distance
distances = [
[int(scale * np.sqrt((n1[0] - n2[0]) ** 2 + (n1[1] - n2[1]) ** 2)) for n2 in all_nodes]
for n1 in all_nodes
]
literals = {}
all_arcs = []
# start-> pickup
for i in pickup_indices:
literals[0, i] = model.NewBoolVar(f"{0} -> {i}") # start arc
all_arcs.append((0, i, literals[0, i]))
# pickup -> drop off
for i in pickup_indices:
for j in drop_off_indices:
literals[i, j] = model.NewBoolVar(f"{i} -> {j}")
all_arcs.append((i, j, literals[i, j]))
# drop off -> pickup
for i in drop_off_indices:
for j in pickup_indices:
literals[i, j] = model.NewBoolVar(f"{i} -> {j}")
all_arcs.append((i, j, literals[i, j]))
# drop off -> start
for i in drop_off_indices:
literals[i, 0] = model.NewBoolVar(f"{i} -> {0}")
all_arcs.append((i, 0, literals[i, 0]))
model.AddCircuit(all_arcs)
model.Minimize(sum(literals[i, j] * distances[i][j] for i, j in literals))
solver.Solve(model)
print(f"Travel distance: {solver.ObjectiveValue()}")
# print path
start_node = 0
node_idx = 0
print(node_idx, end="")
full_circuit = False
while not full_circuit:
for i, pos in enumerate(all_nodes):
if (node_idx, i) in literals and solver.Value(literals[(node_idx, i)]):
print(f" -> {i}", end="")
node_idx = i
break
if node_idx == start_node:
full_circuit = True
N.B: This question has also been posted on https://groups.google.com/g/or-tools-discuss/c/xazcgayBUok
I would like to rotate the following k-partite graph vertically or at 45 degrees. I want to show the following plot in a hierarchical way where red nodes are at the top and green node-set are at the bottom
Documentation of networks only have a rotate option for shell_layout and for edges labels networkx.drawing.nx_pylab.draw_networkx_edge_labels
Here is the program code:
G = nx.Graph()
G.add_nodes_from(emc["entity"], bipartite=0)
G.add_nodes_from(set(EMM_unique["keys"]).symmetric_difference(set(emc["entity"])), bipartite=1)
G.add_nodes_from(EMM["id"], bipartite=2)
G.add_edges_from(list(emc.itertuples(index=False)))
G.add_edges_from(list(EMM.itertuples(index=False)))
nodes = G.nodes()
# for each of the parts create a set
nodes_0 = set([n for n in nodes if G.nodes[n]['bipartite']==0])
nodes_1 = set([n for n in nodes if G.nodes[n]['bipartite']==1])
nodes_2 = set([n for n in nodes if G.nodes[n]['bipartite']==2])
# set the location of the nodes for each set
pos = dict()
pos.update( (n, (1, i)) for i, n in enumerate(nodes_0) ) # put nodes from X at x=1
pos.update( (n, (2, i)) for i, n in enumerate(nodes_1) ) # put nodes from Y at x=2
pos.update( (n, (3, i)) for i, n in enumerate(nodes_2) ) # put nodes from X at x=1
color_map = []
for node in G:
if node in emc["entity"].values:
color_map.append("red")
elif node in EMM["id"].values:
color_map.append("green")
else:
color_map.append("blue")
nx.draw(G, pos, node_color=color_map, width= 2, with_labels=True, with_arrows=True)
This solution is only good to flip the position and is not useful for rotation. As I am not adding the nodes one by one therefore, this solution is not very helpful as well.
Instead of flipping the graph, have you looked into using pygraphviz or the hierarchical_pos solution as in the answer below?
Can one get hierarchical graphs from networkx with python 3?
the hierarchy_pos solution worked well to some extent for me:
## Converting the graph to an oriented tree through depth first search or breadth first search
tree_g = nx.dfs_tree(g, <starting_node>)
## attempt to draw it like a tree
pos = hierarchy_pos(tree_g)
nx.draw(tree_g, pos=pos,....)
https://networkx.github.io/documentation/networkx-1.10/reference/generated/networkx.algorithms.traversal.depth_first_search.dfs_tree.html
I tried to change the coordinates at the position(pos) variable and it worked. The part of the code that is different from what I posted above and has a solution is here
# set the location of the nodes for each set
pos = dict()
pos.update( (n, (i, -1)) for i, n in enumerate(nodes_0) )
pos.update( (n, (i, -2) for i, n in enumerate(nodes_1) )
pos.update( (n, (i, -3)) for i, n in enumerate(nodes_2) )
The Following is a code snippet of a game called Ink Spill from the book 'Make Games with Python & Pygame' by Al Sweigart. (Full Code here: http://invpy.com/inkspill.py)
This recursive function is responsible for changing
the old color ( in this case '2', which is mainBoard[0][0]) to the new color (in this case '3', which is mainBoard[1][0])
that the player clicks on .
My question is: In this example why even when the base condition is
met and the return is executed, the execution still jumps to the next block inside the function.
I have tested this with print statements all over the function many times and with different parameters and also on Visualize Code website ...I still don't understand it!
Here, I have deleted many of my print statements and have left just two at the beginning. If you run this code you will see on your console that on the third line the condition is met (3 !=2), but the execution continues!
I really appreciate any help. Thank you very much.
And by the way unfortunately, the other question that was asked by someone else: Why does this recursive function continue even after its base case has been satisfied didn't answer my question although very similar!
boardWidth = 3
boardHeight = 3
mainBoard = [[2, 0, 0], [3, 0, 0], [4, 0, 0]]
# (blue, red, red),(yellow, red, red),(orange, red, red)
def floodFill(board, oldColor, newColor, x, y):
print(board[x][y], oldColor)
print(mainBoard)
if board[x][y] != oldColor:
return
board[x][y] = newColor # change the color of the current box
# Make the recursive call for any neighboring boxes:
if x > 0:
floodFill(board, oldColor, newColor, x - 1, y)
if x < boardWidth - 1:
floodFill(board, oldColor, newColor, x + 1, y)
if y > 0:
floodFill(board, oldColor, newColor, x, y - 1)
if y < boardHeight - 1:
floodFill(board, oldColor, newColor, x, y + 1)
floodFill(mainBoard, 2, 3, 0, 0)
I created a HSV mask from the image. The result like following:
My goal is draw muliple rectangles that fit mask height or width, like following:
I encouter 2 problem.
I don't know how to locate the starting and ending point in mask for creating rectangle. If I use for loop to scan though mask row by row, it may split mask into 2 part.
Sometime, there also contain 2 different mask in one image. How can I draw rectangles?
Anyone can give me some suggestion?
You can search for your biggest contour (cross-like shape) with cv2.findContour(). It returns an array of coordinates of the contour. Then you can search your contour for point that has the highest X coordinate (that being your most right point), lowest X coordinat (most left point), highest Y coordinate (being most bottom point) and lowest Y coordinate (being your highest point). After you have all 4 values you can search the contour again for all points that have that values and append them in 4 different lists which you sort them later so you can get these points as drown on the bottom picture:
cv2.circle(img,(top_vertical[0]), 4, (0,0,255), -1)
cv2.circle(img,(top_vertical[-1]), 4, (0,0,255), -1)
cv2.circle(img,(bottom_vertical[0]), 4, (0,0,255), -1)
cv2.circle(img,(bottom_vertical[-1]), 4, (0,0,255), -1)
cv2.circle(img,(left_horizontal[0]), 4, (0,0,255), -1)
cv2.circle(img,(left_horizontal[-1]), 4, (0,0,255), -1)
cv2.circle(img,(right_horizontal[0]), 4, (0,0,255), -1)
cv2.circle(img,(right_horizontal[-1]), 4, (0,0,255), -1)
From this point forward I have transformed the lists into numpy arrays as it is easier for me. You can do it any other way.
Then it is just a matter of how many rectangles you want and how do you want to display them. In my example code you have to input how many same size rectangles you want and the last one is the size of what is left. I have first displayed rectangles on Y coordinate (green color) and then on X coordinate, which is divided on two segments (left and right) because they slightly vary in distance and I did not want to draw over the Y coordinate rectangles as they are not drawn on your example image. You can change the logic of writting the rectangles as you wish. Hope it helps a bit or give an idea on how to proceede. Cheers!
Example code:
import cv2
import numpy as np
# Read image and search for contours.
img = cv2.imread('cross.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, threshold = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY)
_, contours, hierarchy = cv2.findContours(threshold,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
# Select the biggest contour (if you wish to segmentize only the cross-like contour).
cnt = max(contours, key=cv2.contourArea)
# Create empty lists for appending key points.
top_vertical = []
bottom_vertical = []
left_horizontal = []
right_horizontal = []
# Setting the starter values for N, S, E, W.
top = 10000
bottom = 0
left = 10000
right = 0
# Loop to get highest key values of N, S, E, W.
for i in cnt:
y = int(i[:,1])
x = int(i[:, 0])
if x < left:
left = int(x)
if x > right:
right = int(x)
if y < top:
top = int(y)
if y > bottom:
bottom = int(y)
# Loop for appending all points containing key values of N, S, E, W.
for i in cnt:
if int(i[:,1]) == top:
up = (int(i[:,0]), int(i[:,1]))
top_vertical.append(up)
if int(i[:,1]) == bottom:
down = (int(i[:,0]), int(i[:,1]))
bottom_vertical.append(down)
if int(i[:,0]) == left:
l = (int(i[:,0]), int(i[:,1]))
left_horizontal.append(l)
if int(i[:,0]) == right:
r = (int(i[:,0]), int(i[:,1]))
right_horizontal.append(r)
# Sorting the lists.
top_vertical.sort(key=lambda tup: tup[0])
bottom_vertical.sort(key=lambda tup: tup[0])
left_horizontal.sort(key=lambda tup: tup[1])
right_horizontal.sort(key=lambda tup: tup[1])
# Optional drawing of key points.
'''cv2.circle(img,(top_vertical[0]), 4, (0,0,255), -1)
cv2.circle(img,(top_vertical[-1]), 4, (0,0,255), -1)
cv2.circle(img,(bottom_vertical[0]), 4, (0,0,255), -1)
cv2.circle(img,(bottom_vertical[-1]), 4, (0,0,255), -1)
cv2.circle(img,(left_horizontal[0]), 4, (0,0,255), -1)
cv2.circle(img,(left_horizontal[-1]), 4, (0,0,255), -1)
cv2.circle(img,(right_horizontal[0]), 4, (0,0,255), -1)
cv2.circle(img,(right_horizontal[-1]), 4, (0,0,255), -1)'''
# Transforming lists to arrays.
top_vertical = np.array(top_vertical)
bottom_vertical = np.array(bottom_vertical)
left_horizontal = np.array(left_horizontal)
right_horizontal = np.array(right_horizontal)
# Calculating height and weight of the contour.
distance_y = bottom - top
distance_x = right - left
# Inputs for the number of same size segments.
a = input('Input the number of same size segments in Y coordinate: ')
b = input('Input the number of same size segments in left X coordinate: ')
c = input('Input the number of same size segments in right X coordinate: ')
# Calculation of area per segment and limit for the lenght of combined segments (height and weight) .
segment_y = distance_y/int(a)
segment_x_reference = int(top_vertical[0,0]) - int(left_horizontal[0,0])
segment_x = segment_x_reference/int(b)
segment_x_right_reference = int(right_horizontal[0,0]) - int(top_vertical[-1,0])
segment_x_right = segment_x_right_reference/int(c)
# Drawing rectangles on the Y axis.
for i in range(1,20):
sq = int(segment_y)*i
if sq < distance_y:
cv2.rectangle(img,(top_vertical[0,0], top_vertical[0,1]),((top_vertical[-1,0]),top_vertical[0,1] + sq),(0,255,0),1)
else:
sq = distance_y
cv2.rectangle(img,(top_vertical[0,0], top_vertical[0,1]),((top_vertical[-1,0]),sq),(0,255,0),1)
break
# Drawing rectangles on the left side of X axis.
for i in range(1,20):
sq = int(segment_x)*i
if sq < segment_x_reference:
cv2.rectangle(img,(left_horizontal[0,0], left_horizontal[0,1]),((left_horizontal[0,0])+sq, left_horizontal[-1,1]),(255,0,0),1)
else:
sq = segment_x_reference
cv2.rectangle(img,(left_horizontal[0,0], left_horizontal[0,1]),((left_horizontal[0,0])+sq, left_horizontal[-1,1]),(255,0,0),1)
break
# Drawing rectangles on the right side of X axis.
for i in range(1,20):
sq = int(segment_x_right)*i
if sq < segment_x_right_reference:
cv2.rectangle(img,(right_horizontal[0,0], right_horizontal[0,1]),((right_horizontal[0,0])-sq, right_horizontal[-1,1]),(255,0,0),1)
else:
sq = segment_x_right_reference
cv2.rectangle(img,(right_horizontal[0,0], right_horizontal[0,1]),((right_horizontal[0,0])-sq, right_horizontal[-1,1]),(255,0,0),1)
break
# Displaying result.
cv2.imshow('img', img)
Result:
Input the number of same size segments in Y coordinate: 5
Input the number of same size segments in left X coordinate: 2
Input the number of same size segments in right X coordinate: 2