OR-Tools VRP: Constrain locations to be served by same vehicle - modeling

I would like to constrain locations to be served by the same vehicle.
I used capacity-constraints for achieving this. Say we have l = [[1,2], [3,4]] which means that location 1, 2 must be served by the same vehicle and 3, 4 as well. So 1, 2 ends up on route_1 and 3, 4 on route_2
My code for achieving this is:
for idx, route_constraint in enumerate(l):
vehicle_capacities = [0] * NUM_VEHICLES
vehicle_capacities[idx] = len(route_constraint)
route_dimension_name = 'Same_Route_' + str(idx)
def callback(from_index):
from_node = manager.IndexToNode(from_index)
return 1 if from_node in route_constraint else 0
same_routes_callback_index = routing.RegisterUnaryTransitCallback(callback)
routing.AddDimensionWithVehicleCapacity(
same_routes_callback_index,
0, # null capacity slack
vehicle_capacities, # vehicle maximum capacities
True, # start cumul to zero
route_dimension_name)
The idea is that 1,2 have a capacity demand of each 1 unit (all others have zero). As only vehicle 1 has a capacity of 2 it is the only one able to serve 1,2.
This seems to work fine if len(l) == 1. If greater the solver is not able to find a solution if though I put into l pairs of locations which were on the same route without the above code (hence without the above capacity constraints.
Is there a more elegant way to model my requirement?
Why does the solver fail to find a solution?
I have also considered the possibility of dropping visits (at a high cost) to give the solver the possibility to start from a solution which drops visits such that it will find his way fro this point to a solution without any drops. I had no luck.
Thanks in advance.

Each stop has a vehicle var whose values determine what vehicle is allowed to visit the stop. If you want to have stops 1 and 2 serviced by vehicle 0 use a member constraint on the vehicle var of each stop and set it to [0]. Since you might have other constraints that make stops optional add the value -1 to the list. It is a special value that indicates that the stop is not serviced by a vehicle.
In Python:
n2x = index_manager.NodeToIndex
cpsolver = routing_model.solver()
for stop in [1,2]:
vehicle_var = routing_model.VehicleVar(n2x(stop))
values = [-1, 0]
cpsolver.Add(cpsolver.MemberCt(vehicle_var, values))

Related

New to python and need help getting started with this function

I am very new to python and I have done research but all I could find on this problem was on outdated versions of python. I hear this community is able to help me with this problem.
I am attempting on making a function called makeChange with amount as a parameter.
The function is supposed to take user input as a decimal and convert what the user inputted into bills and coins. (for example .05$, .10$, .25$, .50$ 1$ and so on.)
Is it possible that I can get a base to build off of? (Not the entire function maybe a few errors just so I can learn.)
(Thanks for taking the time to read what I have to say!)
Problem
So, in makeChange problem we find try to find the minimum number of coins that add up to a give amount of money.
Code
def makeChange(amount):
# You can add more or remove the coins from this list
coins = [.05, .1, .25, .5, 1]
# LEARN: Find out how sort function works on a list, and what does the reverse parameter means. Why do we need to sort it?
# Sort the coins list (incase it's not sorted)
coins.sort(reverse=True)
change = []
for coin in coins:
# LEARN: Find out why we type cast the result of totalCoin to integer,
# LEARN: Find out what do we get by using integer division and modulo to the amount variable
# Notes: // means integer division, / means float division, % means modulo or remainder
totalCoin = int(float(amount) // coin)
amount = amount % coin
# Round the amount to 2 precision point (0.25, 1.00) since there is no $0.001 right
amount = round(amount, 2)
# Append the coin we use for change
for i in range(totalCoin):
change.append(coin)
if amount == 0:
return change
# If we ever reach here, that means, the given coin in coins list
# is not able to return the change
return 'Not changeable'
print('22.76: ', makeChange(22.76)) # 22.76: Not changeable
print('13.8: ', makeChange(13.8)) # 13.8: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.5, 0.25, 0.05]
print('2.4', makeChange(2.4)) # 2.4 [1, 1, 0.25, 0.1, 0.05]
Conclusion
The code above is working properly, I did not any error to it, BUT I added lots of comment for you to find out yourself, how and why this code works the way it is. Good luck on learning Python!
Here is a start for you:
def makeChange(amount):
pass
You can compute how many bills and coins are needed. You can work on float type. int function converts float to int and truncate decimal part. For example
def makeChange(amount):
amt = float(amount)
n100 = int(amt/100.)
amt = amt - n100*100.
n50 = int(amt/50.)
amt = amt - n50*50.
# and so on... you can add more
n100, n50 # return how many bills

Creating random, non recurrent groups of undetermined size from a list

I want to create a program where a user can input what the maximum size of the groups is that they want. These groups are formed from a list of names from a submission form. The idea is that there are multiple rounds in which the names are paired in the requested maximum group size and each round does not create previously formed groups. Also, no one should be left out, so no groups of 1 person.
I have two problems: first off: if I have a list of 10 names and I input that I want max size groups of 3 persons, I get 3 groups of 3 persons and 1 of 1, but it should be 3, 3, 2, 2. I used two different functions I found on here, but both have the same problem.
Secondly, I have no idea how to make sure that in a new round there won't be any groups from previous round.
I am pretty new to programming, so any tips are welcome.
This is the first function I have:
members = group_size()
def teams(amount, size):
for i in range(0, len(amount), size):
yield amount[i:i + size]
participants = Row_list_names
random.shuffle(participants)
print("These are your groups:")
print(list(teams(participants, members)))
And this is the second:
members = group_size()
participants = Row_list_names
random.shuffle(participants)
for i in range(len(participants) // members + 1):
print('Group {} consists of:'.format(i+1))
group = participants[i*members:i*members + members]
for participant in group:
print(participant)
group_size() returns an integer number for how many people should be in the group.
For the second problem, shuffling as you do should do the trick nicely.
For the first problem, the functions are doing what you tell them to: you skip ahead and slice the list in chunks that contain exactly member participants. You do not notice that the last slice is out of bound because python is lenient on that:
>>> l = [0,1,2,3,4]
>>> l[:40]
[0, 1, 2, 3, 4]
The point is that not all groups should be of the same size:
from math import ceil
from math import floor
def split_groups(group_size, part):
# first compute the number of groups given the requested size
group_num = ceil(len(part) / group_size)
print(f"group_num {group_num}")
# compute a fractional length of the groups
group_size_frac = len(part) / group_num
print(f"group_size_frac {group_size_frac}")
total_assigned = 0
for i in range(group_num):
# get the start and end indexes using the fractional length
start = floor(i * group_size_frac)
end = floor((i + 1) * group_size_frac)
group = part[start:end]
print(f"start {start} end {end} -> {group}")
print(f"number of participants in this group {len(group)}")
total_assigned += len(group)
# check that we assigned all of the participants
assert total_assigned == len(part)
I have not tested any edge case, but a quick check by running
for group_size in range(1, 5):
for num_participants in range(10, 50):
part = list(range(num_participants))
split_groups(group_size, part)
shows that every participant was assigned to a group.
Plug in the shuffling you did before and you have random groups.
Cheers!

"unique" crossover for genetic algorithm - TSP

I am creating a Genetic Algorithm to solve the Traveling Salesman Problem.
Currently, two 2D lists represent the two parents that need to be crossed:
path_1 = np.shuffle(np.arange(12).reshape(6, 2))
path_2 = np.arange(12).reshape(6,2)
Suppose each element in the list represents an (x, y) coordinate on a cartesian plane, and the 2D list represents the path that the "traveling salesman" must take (from index 0 to index -1).
Since the TSP requires that all points are included in a path, the resulting child of this crossover must have no duplicate points.
I have little idea as to how I can make such crossover and have the resulting child representative of both parents.
You need to use an ordered crossover operator, like OX1.
OX1 is a fairly simple permutation crossover.
Basically, a swath of consecutive alleles from parent 1 drops down,
and remaining values are placed in the child in the order which they
appear in parent 2.
I used to run TSP with these operators:
Crossover: Ordered Crossver (OX1).
Mutation: Reverse Sequence Mutation (RSM)
Selection: Roulette Wheel Selection
You can do something like this,
Choose half (or any random number between 0 to (length - 1)) coordinates from one parent using any approach, lets say where i % 2 == 0.
These can be positioned into the child using multiple approaches: either randomly, or all in the starting (or ending), or alternate position.
Now the remaining coordinated need to come from the 2nd parent for which you can traverse in the 2nd parent and if the coordinate is not chosen add it in the empty spaces.
For example,
I am choosing even positioned coordinated from parent 1 and putting it in even position indices in the child and then traversing in parent 2 to put the remaining coordinated in the odd position indices in the child.
def crossover(p1, p2, number_of_cities):
chk = {}
for i in range(number_of_cities):
chk[i] = 0
child = [-1] * number_of_cities
for x in range(len(p1)):
if x % 2 == 0:
child[x] = p1[x]
chk[p1[x]] = 1
y = 1
for x in range(len(p2)):
if chk[p2[x]] == 0:
child[y] = p2[x]
y += 2
return child
This approach preserves the order of cities visited from both parents.
Also since it is not symmetric p1 and p2 can be switched to give 2 children and the better (or both) can be chosen.

Order constraints in optimisation

I have a set of many (10000+) items, from which have I have to choose exactly 20 items. I can only choose each item once. My items have profits, and costs, as well as several boolean properties (such as colour). I need to output the results in a specific order: in particular I need the first and third items to be blue, and the second and fourth items to be red.
Each item is represented as a tuple:
item = ('item name', cost, profit, is_blue, is_red)
as an example
vase = ['Ming Vase', 1000, 10000, 0, 1]
plate = ['China Plate', 10, 5, 1, 0]
and the total set of items is a list of lists:
items = [item1, item2, ..., itemN].
My profits and costs are also lists:
profits = [x[2] for x in items]
costs = [x[1] for x in items]
For each item chosen, it needs to have a minimum value, and a minimum of 5 items must have the property (is_blue) flag set to 1.
I want to choose the 20 cheapest items with the highest value, such that 5 of them have the is_blue flag set to 1, and the first and third items are blue (etc).
I'm having trouble formulating this using google OR tools.
from ortools.linear_solver import pywraplp
solver = pywraplp.Solver('SolveAssignmentProblemMIP',
pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING)
x = {}
for i in range(MAX_ITEMS):
x[i] = solver.BoolVar('x[%s]' % (i))
#Define the constraints
total_chosen = 20
solver.Add(solver.Sum([x[i] for i in range(MAX_ITEMS)]) == total_chosen)
blues = [x[3] for x in items]
solver.Add(solver.Sum([blues[i] * x[i] for i in .
range(MAX_ITEMS)]) >= 5)
max_cost = 5.0
for i in range(MAX_ITEMS):
solver.Add(x[i] * cost[i] <= max_cost)
solver.Maximize(solver.Sum([profits[i] * x[i] for i in range(total_chosen)]))
sol = solver.Solve()
I can get the set of items I've chosen by:
for i in range(MAX_ITEMS):
if x[i].solution_value() > 0:
print(item[i].item_name)
This works fine - it chooses the set of 20 items which maximise the profits subject to the cost constraint, but I'm stuck on how to extend this to choosing items in way that guarantees that the first is blue etc.
Any help in formulating the constraints and objective would be really helpful. Thanks!
Instead of expressing chosen items with BoolVar, consider making a list of 20 IntVar with domain of 0..MAX_ITEMS. From there it should be fairly easy to do something like this:
solver.Add(chosens[0].IndexOf(all_items)[3] == 1)
solver.Add(chosens[2].IndexOf(all_items)[3] == 1)
chosens[i].IndexOf(all_items) simply means all_items[IndexOfChosen], I.E: whichever item is chosen for the Ith place. If you go with this approach, do not forget to MakeAllDifferent!

Analyzing the time complexity of Coin changing

We're doing the classic problem of determining the number of ways that we can make change that amounts to Z given a set of coins.
For example, Amount=5 and Coins={1, 2, 3}. One way we can make 5 is {2, 3}.
The naive recursive solution has a time complexity of factorial time.
f(n) = n * f(n-1) = n!
My professor argued that it actually has a time complexity of O(2^n), because we only choose to use a coin or not. That intuitively makes sense. However how come my recurence doesn't work out to be O(2^n)?
EDIT:
My recurrence is as follows:
f(5, {1, 2, 3})
/ \ .....
f(4, {2, 3}) f(3, {1, 3}) .....
Notice how the branching factor decreases by 1 at every step.
Formally.
T(n) = n*F(n-1) = n!
The recurrence doesn't work out to what you expect it to work out to because it doesn't reflect the number of operations made by the algorithm.
If the algorithm decides for each coin whether to output it or not, then you can model its time complexity with the recurrence T(n) = 2*T(n-1) + O(1) with T(1)=O(1); the intuition is that for each coin you have two options---output the coin or not; this obviously solves to T(n)=O(2^n).
I too was trying to analyze the time complexity for the brute force which performs depth first search:
def countCombinations(coins, n, amount, k=0):
if amount == 0:
return 1
res = 0
for i in range(k, n):
if coins[k] <= amount:
remaining_amount = amount - coins[i] # considering this coin, try for remaining sum
# in next round include this coin too
res += countCombinations(coins, n, remaining_amount, i)
return res
but we can see that the coins which are used in one round is used again in the next round, so at least for 1st coin we have n items at each stage which is equivalent to permutation with repetition n^r for n items available to arrange into r positions at each stage.
ex: [1, 1, 1, 1]; sum = 4
This will generate a recursive tree where for first path we literally have solutions at each diverged subpath until we have the sum=0. so the time complexity is O(sum^n) ie for each stage in the path towards sum we have n different subpaths.
Note however there is another algorithm which uses take/not-take approach and at most there is 2 branch at a node in recursion tree. Hence the time complexity for this algorithm is O(2^(n*m))
ex: say coins = [1, 1] sum = 2 there are 11 nodes/points to visit in the recursion tree for 6 paths(leaves) then complexity is at most 2^(2*2) => 2^4 => 16 (Hence 11 nodes visiting for a max of 16 possibility is correct but little loose on upper bound).
def get_count(coins, n, sum):
if(n == 0): # no coins left, to try a combination that matches the sum
return 0
if(sum == 0): # no more sum left to match, means that we have completely co-incided with our trial
return 1 # (return success)
# don't-include the last coin in the sum calc so, leave it and try rest
excluded = get_count(coins, n-1, sum)
included = 0
if(coins[n-1] <= sum):
# include the last coin in the sum calc, so reduce by its quantity in the sum
# we assume here that n is constant ie, it is supplied in unlimited(we can choose same coin again and again),
included = get_count(coins, n, sum-coins[n-1])
return included+excluded

Resources