import numpy as np
import gurobipy
from gurobipy import *

import matplotlib.pyplot as plt

###########NOTES###############
”’
The setup is as follows:

demand_node_demands—-> Vector of demands being sent to transhipment nodes.
supply_node_limit——> Supply nodes that receive from transshipment vertices
num_transhipment_nodes—> number of transhipment nodes receiving from demand nodes and sending to the supply node
load_limit—————> upper limit of load that can be put on any one transhipment node

demand——>Transshipment——>Supply

EXAMPLE of trivial setup where no nodes are overloaded:
demand_node_demands = [10,10,10] ——-> three demand nodes, sending 10 units each to transhipment nodes
supply_node_limit=[30]           ——-> a single supply node that needs to receive 30 units total from transhipment nodes
num_transhipment_nodes = 3       ——-> three transhipment nodes that receive from demand nodes and send to supply nodes
load_limit = 20                  ——-> if a transhipment node is sending+recieving more than 20 units it is ‘overloaded’
”’
def overloaded_trans_vertices(supply_node_limit, demand_node_demands, num_transhipment_nodes, load_limit, costs_S, costs_D):

m = Model(‘Min Buffered Number’)

T = num_transhipment_nodes
L = load_limit
num_supply = len(supply_node_limit)
num_demand = len(demand_node_demands)

# Variables
z = [] for i in range(T):
z.append(
m.addVar(lb=0, ub=GRB.INFINITY, obj=0, vtype=GRB.CONTINUOUS, name=”z” + str(i)))

S = [] # S[i,j]==number of units that supply node ‘j’ receives from transhipment node ‘i’
for j in range(T):

S.append([m.addVar(lb=0, ub=GRB.INFINITY, obj=0, vtype=GRB.CONTINUOUS)
for i in range(num_supply)])

D = [] # D[i,j]==number of units that demand node ‘i’ sends to transhipment node ‘j’
for i in range(num_demand):
D.append(
[m.addVar(lb=0, ub=GRB.INFINITY, obj=0, vtype=GRB.CONTINUOUS) for j in range(T)])
D=np.array(D)
S=np.array(S)
a = m.addVar(lb=0, ub=GRB.INFINITY, obj=0, vtype=GRB.CONTINUOUS, name=”a”)
# Objective
m.update()
m.setObjective(quicksum(z), GRB.MINIMIZE)

# Constraints
for i in range(T):
m.addConstr(z[i] >= quicksum(costs_S[i,j]*S[i,j] for j in range(num_supply)) + quicksum(costs_D[j,i]*D[j,i] for j in range(num_demand)) – a * L + 1)

for i in range(num_demand):
m.addConstr(quicksum(D[i,j] for j in range(T)) ==  a*demand_node_demands[i])

for j in range(num_supply):
m.addConstr(quicksum(S[i,j] for i in range(T)) ==  a*supply_node_limit[j])

for i in range(T):
m.addConstr( -quicksum(S[i,j] for j in range(num_supply)) + quicksum(D[j,i] for j in range(num_demand)) == 0 )
m.optimize()

for i in range(D.shape[0]):
for j in range(D.shape[1]):
D[i,j]=D[i,j].X
for i in range(S.shape[0]):
for j in range(S.shape[1]):
S[i,j]=S[i,j].X
for i in range(T):
z[i]=z[i].X
a=a.X

return S,D,z,a

###############################################################################################

demand_node_demands = [10 for i in range(3)] supply_node_limit=[10*10000] num_transhipment_nodes = 3
load_limit = .7*supply_node_limit[0]/float(num_transhipment_nodes)

T = num_transhipment_nodes
num_demand = len(demand_node_demands)
num_supply = len(supply_node_limit)

costs_S=np.uniform( (T,num_supply)  )
costs_D=np.uniform( (num_demand,T)  )

S, D, z, a = overloaded_trans_vertices(
supply_node_limit, demand_node_demands, num_transhipment_nodes, load_limit, costs_S, costs_D)