"""
Provide placement heuristics and all placement-related methods (check capacity, change assignment
...).
"""
import math
import random
from itertools import combinations
import numpy as np
from . import init as it
#####################################################################
# Functions definitions
# TODO factorize many functions (assign indiv, change node during assign indiv..)
[docs]def assign_container_node(node_id, container_id, instance, remove=True):
"""Assign container_id to node_id, and remove it from old node.
:param node_id: _description_
:type node_id: str
:param container_id: _description_
:type container_id: str
:param instance: _description_
:type instance: Instance
:param remove: _description_, defaults to True
:type remove: bool, optional
"""
old_id = instance.df_indiv.loc[
instance.df_indiv[it.indiv_field] == container_id
][it.host_field].to_numpy()[0]
instance.df_host.loc[
instance.df_host[it.host_field] == node_id, it.metrics
] = instance.df_host.loc[
instance.df_host[it.host_field] == node_id, it.metrics
].to_numpy() + instance.df_indiv.loc[
instance.df_indiv[it.indiv_field] == container_id, it.metrics
].to_numpy()
if remove:
remove_container_node(old_id, container_id, instance)
instance.df_indiv.loc[
instance.df_indiv[it.indiv_field] == container_id, [it.host_field]
] = node_id
[docs]def remove_container_node(node_id, container_id, instance):
"""Remove container from node.
:param node_id: _description_
:type node_id: str
:param container_id: _description_
:type container_id: str
:param instance: _description_
:type instance: Instance
"""
instance.df_host.loc[
instance.df_host[it.host_field] == node_id, it.metrics
] = instance.df_host.loc[
instance.df_host[it.host_field] == node_id, it.metrics
].to_numpy() - instance.df_indiv.loc[
instance.df_indiv[it.indiv_field] == container_id, it.metrics
].to_numpy()
[docs]def free_full_nodes(instance, full_nodes, tick):
"""Change the solution in order to satisfy node capacities.
:param instance: _description_
:type instance: Instance
:param full_nodes: _description_
:type full_nodes: List
:param tick: _description_
:type tick: int
"""
for host in full_nodes:
stop = False
while not stop:
df_indiv_host = instance.df_indiv.loc[
(instance.df_indiv[it.host_field] == host) & (
instance.df_indiv[it.tick_field] == tick
)
]
if df_indiv_host[it.metrics[0]].sum() > instance.df_host_meta.loc[
instance.df_host_meta[it.host_field] == host
][it.metrics[0]].to_numpy()[0]:
moving_indiv = random.choice(
df_indiv_host[it.indiv_field].unique())
assign_indiv_available_host(
instance, moving_indiv, tick, tick,
instance.df_indiv[it.host_field].nunique())
else:
stop = True
[docs]def assign_indiv_available_host(instance, indiv_id, tmin, tmax, nb_open_nodes):
"""Assign the individual to first available host.
:param instance: _description_
:type instance: Instance
:param indiv_id: _description_
:type indiv_id: str
:param tmin: _description_
:type tmin: int
:param tmax: _description_
:type tmax: int
:param nb_open_nodes: _description_
:type nb_open_nodes: int
:raises RuntimeError: _description_
"""
print('Moving individual %s' % indiv_id)
cons_c = instance.df_indiv.loc[
(instance.df_indiv[it.indiv_field] == indiv_id) & (
instance.df_indiv[it.tick_field] >= tmin) & (
instance.df_indiv[it.tick_field] <= tmax
)
][it.metrics[0]].to_numpy()
n = 0
checked_nodes = 1
done = False
while not done:
# TODO check n <= min_nodes or infeasibility
cap_node = instance.df_host_meta.loc[
instance.
df_host_meta[it.host_field] == instance.dict_id_n[n]
][it.metrics[0]].to_numpy()[0]
conso_node = instance.df_indiv.loc[
(instance.df_indiv[it.host_field] == instance.dict_id_n[n]) & (
instance.df_indiv[it.tick_field] >= tmin) & (
instance.df_indiv[it.tick_field] <= tmax
)
][it.metrics[0]].sum()
if np.all(np.less((conso_node + cons_c), cap_node)):
conso_node += cons_c
done = True
assign_container_node(
instance.dict_id_n[n], indiv_id, instance)
else:
checked_nodes += 1
n = (n + 1) % nb_open_nodes
if checked_nodes > nb_open_nodes:
print('Impossible to move %s on another existing node.' % indiv_id)
if (instance.dict_id_n[checked_nodes] in
instance.df_host_meta[it.host_field].unique()) & (
np.all(np.less(cons_c, cap_node))):
print('We can open node %s' % (instance.dict_id_n[checked_nodes]))
assign_container_node(
instance.dict_id_n[checked_nodes], indiv_id, instance)
done = True
else:
raise RuntimeError('No node to welcome %s' % indiv_id)
# find_substitution(instance, indiv_id, tmin, tmax)
[docs]def assign_indiv_initial_placement(
instance, indiv_id, tmin, tmax, conso_nodes, min_nodes, n=0
):
"""Assign indiv in node during first heuristic.
:param instance: _description_
:type instance: Instance
:param indiv_id: _description_
:type indiv_id: str
:param tmin: _description_
:type tmin: int
:param tmax: _description_
:type tmax: int
:param conso_nodes: _description_
:type conso_nodes: List
:param min_nodes: _description_
:type min_nodes: int
:param n: _description_, defaults to 0
:type n: int, optional
:return: _description_
:rtype: bool
"""
cons_c = instance.df_indiv.loc[
(instance.df_indiv[it.indiv_field] == indiv_id) & (
instance.df_indiv[it.tick_field] >= tmin) & (
instance.df_indiv[it.tick_field] <= tmax
)
][it.metrics[0]].to_numpy()
checked_nodes = 1
done = False
while not done:
cap_node = instance.df_host_meta.loc[
instance.
df_host_meta[it.host_field] == instance.dict_id_n[n]
][it.metrics[0]].to_numpy()[0]
if np.all(np.less(
(conso_nodes[n] + cons_c), cap_node)):
conso_nodes[n] += cons_c
assign_container_node(
instance.dict_id_n[n],
indiv_id,
instance)
done = True
else:
checked_nodes += 1
if checked_nodes >= min_nodes:
print('Impossible to move %s on another existing node.' % indiv_id)
if (instance.dict_id_n[checked_nodes] in
instance.df_host_meta[it.host_field].unique()) & (
np.all(np.less(cons_c, cap_node))):
print('We can open node %s' % (instance.dict_id_n[checked_nodes]))
min_nodes += 1
assign_container_node(
instance.dict_id_n[checked_nodes],
indiv_id,
instance)
done = True
else:
n = (n + 1) % min_nodes
cap_node = instance.df_host_meta.loc[
instance.
df_host_meta[it.host_field] == instance.dict_id_n[n]
][it.metrics[0]].to_numpy()[0]
return done
[docs]def find_substitution(instance, indiv_id, tmin, tmax):
"""Find a node in which we can place indiv_id by moving another indiv.
:param instance: _description_
:type instance: Instance
:param indiv_id: _description_
:type indiv_id: str
:param tmin: _description_
:type tmin: int
:param tmax: _description_
:type tmax: int
:raises RuntimeError: _description_
"""
# TODO not fully functionnal
cons_c = instance.df_indiv.loc[
(instance.df_indiv[it.indiv_field] == indiv_id) & (
instance.df_indiv[it.tick_field] >= tmin) & (
instance.df_indiv[it.tick_field] <= tmax
)
][it.metrics[0]].to_numpy()
n = 0
checked_nodes = 1
done = False
while not done:
conso_node = instance.df_indiv.loc[
(instance.df_indiv[it.host_field] == instance.dict_id_n[n]) & (
instance.df_indiv[it.tick_field] >= tmin) & (
instance.df_indiv[it.tick_field] <= tmax
)
][it.metrics[0]].sum()
cap_node = instance.df_host_meta.loc[
instance.
df_host_meta[it.host_field] == instance.dict_id_n[n]
][it.metrics[0]].to_numpy()[0]
moving_indiv = random.choice(
instance.df_indiv.loc[
(instance.df_indiv[it.host_field] == instance.dict_id_n[n])
][it.indiv_field].unique())
conso_indiv = instance.df_indiv.loc[
(instance.df_indiv[it.indiv_field] == moving_indiv) & (
instance.df_indiv[it.tick_field] >= tmin) & (
instance.df_indiv[it.tick_field] <= tmax)
][it.metrics[0]].to_numpy()
if np.all(np.less((conso_node - conso_indiv + cons_c), cap_node)):
done = True
assign_container_node(
instance.dict_id_n[n], indiv_id, instance)
assign_indiv_available_host(instance, moving_indiv, tmin, tmax)
else:
checked_nodes += 1
n = (n + 1) % instance.nb_nodes
if checked_nodes > instance.nb_nodes:
raise RuntimeError('No node to welcome %s' % indiv_id)
[docs]def spread_containers(
list_containers, instance, conso_nodes, total_time, min_nodes
):
"""Spread containers from list_containers into nodes.
:param list_containers: _description_
:type list_containers: List
:param instance: _description_
:type instance: Instance
:param conso_nodes: _description_
:type conso_nodes: np.array
:param total_time: _description_
:type total_time: int
:param min_nodes: _description_
:type min_nodes: int
"""
n = 0
for c in list_containers:
cons_c = instance.df_indiv.loc[
instance.df_indiv[it.indiv_field] == c
][it.metrics[0]].to_numpy()[:total_time]
cap_node = instance.df_host_meta.loc[
instance.
df_host_meta[it.host_field] == instance.dict_id_n[n]
][it.metrics[0]].to_numpy()[0]
checked_nodes = 1
done = False
while not done:
# TODO check n <= min_nodes or infeasibility
if np.all(np.less((conso_nodes[n] + cons_c), cap_node)):
conso_nodes[n] += cons_c
done = True
assign_container_node(
instance.dict_id_n[n], c, instance)
else:
checked_nodes += 1
if checked_nodes > min_nodes:
min_nodes += 1
checked_nodes = 1
n = (n + 1) % min_nodes
cap_node = instance.df_host_meta.loc[
instance.
df_host_meta[it.host_field] == instance.dict_id_n[n]
][it.metrics[0]].to_numpy()[0]
n = (n + 1) % min_nodes
[docs]def colocalize_clusters(
list_containers_i, list_containers_j, containers_grouped, instance, total_time, min_nodes,
conso_nodes, n=0
):
"""Allocate containers of 2 clusters grouping by pairs.
:param list_containers_i: _description_
:type list_containers_i: List
:param list_containers_j: _description_
:type list_containers_j: List
:param containers_grouped: _description_
:type containers_grouped: List
:param instance: _description_
:type instance: Instance
:param total_time: _description_
:type total_time: int
:param min_nodes: _description_
:type min_nodes: int
:param conso_nodes: _description_
:type conso_nodes: List
:param n: _description_, defaults to 0
:type n: int, optional
:raises RuntimeError: _description_
:raises RuntimeError: _description_
:return: _description_
:rtype: int
"""
for c in range(min(len(list_containers_i),
len(list_containers_j))):
# allocate 2 containers !! TODO
cons_i = instance.df_indiv.loc[
instance.
df_indiv[it.indiv_field] == list_containers_i[c]
][it.metrics[0]].to_numpy()[:total_time]
cons_j = instance.df_indiv.loc[
instance.
df_indiv[it.indiv_field] == list_containers_j[c]
][it.metrics[0]].to_numpy()[:total_time]
cap_node = instance.df_host_meta.loc[
instance.
df_host_meta[it.host_field] == instance.dict_id_n[n]
][it.metrics[0]].to_numpy()[0]
i_n = 0
if not np.all(np.less(
cons_i + cons_j, cap_node)):
# If indivs i & j can't fit together : split them
if not assign_indiv_initial_placement(
instance, list_containers_i[c],
instance.df_indiv[it.tick_field].min(), total_time - 1,
conso_nodes, min_nodes, n):
raise RuntimeError('No node to welcome %s' % list_containers_i[c])
n = (n + 1) % min_nodes
if not assign_indiv_initial_placement(
instance, list_containers_j[c],
instance.df_indiv[it.tick_field].min(), total_time - 1,
conso_nodes, min_nodes, n):
raise RuntimeError('No node to welcome %s' % list_containers_j[c])
else:
# Indivs i & j could fit together
done = False
while not done:
if np.all(np.less(
(conso_nodes[n] + cons_i + cons_j), cap_node)):
conso_nodes[n] += cons_i + cons_j
assign_container_node(
instance.dict_id_n[n],
list_containers_i[c],
instance)
assign_container_node(
instance.dict_id_n[n],
list_containers_j[c],
instance)
containers_grouped.append([
list_containers_i[c], list_containers_j[c]])
done = True
else:
i_n += 1
if i_n >= min_nodes:
min_nodes += 1
n = i_n
else:
n = (n + 1) % min_nodes
cap_node = instance.df_host_meta.loc[
instance.
df_host_meta[it.host_field] == instance.dict_id_n[n]
][it.metrics[0]].to_numpy()[0]
n = (n + 1) % min_nodes
return c
[docs]def allocation_distant_pairwise(
instance, cluster_var_matrix, labels_, nb_nodes=None, lb=0.0
):
"""First placement heuristic implemented.
Idea : take two most distant clusters
(from their mean profile), and assign by pair (one container from each
cluster) on the first available node, and so on. Take the following node if
current one has not enough resources.
Return a list of containers forced to be grouped together.
:param instance: _description_
:type instance: Instance
:param cluster_var_matrix: _description_
:type cluster_var_matrix: np.array
:param labels_: _description_
:type labels_: List
:param nb_nodes: _description_, defaults to None
:type nb_nodes: int, optional
:param lb: _description_, defaults to 0.0
:type lb: float, optional
"""
print('Beginning of allocation ...')
stop = False
total_time = instance.sep_time
min_nodes = nb_nodes or nb_min_nodes(instance, total_time)
conso_nodes = np.zeros((instance.nb_nodes, total_time))
n = 0
cluster_var_matrix_copy = np.copy(cluster_var_matrix)
clusters_done_ = np.zeros(instance.nb_clusters, dtype=int)
c_it = instance.nb_clusters
containers_grouped = []
while not stop:
# no cluster remaining -> stop allocation
if (c_it == 0):
stop = True
break
# 1 cluster remaining -> spread it TODO
elif (c_it == 1):
c = np.where(clusters_done_ == 0)[0][0]
list_containers = [instance.dict_id_c[u] for u, value in enumerate(
labels_) if value == c]
spread_containers(list_containers, instance, conso_nodes,
total_time, min_nodes)
stop = True
# > 1 cluster remaining -> co-localize 2 more distant
else:
valid_idx = np.where(cluster_var_matrix_copy.flatten() >= lb)[0]
min_idx = valid_idx[cluster_var_matrix_copy.flatten()[
valid_idx].argmin()]
i, j = np.unravel_index(
min_idx, cluster_var_matrix_copy.shape)
list_containers_i = [
instance.dict_id_c[u] for u, value in
enumerate(labels_) if value == i]
list_containers_j = [
instance.dict_id_c[u] for u, value in
enumerate(labels_) if value == j]
it_cont = colocalize_clusters(list_containers_i, list_containers_j,
containers_grouped, instance, total_time,
min_nodes, conso_nodes, n)
# TODO factorization of container allocation
if not (len(list_containers_i) == len(list_containers_j)):
# we have to place remaining containers
it_cont += 1
if it_cont < len(list_containers_j):
list_containers = list_containers_j[it_cont:]
spread_containers(list_containers, instance, conso_nodes,
total_time, min_nodes)
elif it_cont < len(list_containers_i):
list_containers = list_containers_i[it_cont:]
spread_containers(list_containers, instance, conso_nodes,
total_time, min_nodes)
cluster_var_matrix_copy[i, :] = -1.0
cluster_var_matrix_copy[:, i] = -1.0
cluster_var_matrix_copy[j, :] = -1.0
cluster_var_matrix_copy[:, j] = -1.0
clusters_done_[i] = 1
clusters_done_[j] = 1
c_it = c_it - 2
[docs]def allocation_ffd(
instance, cluster_vars, cluster_var_matrix, labels_, bound_new_node=50
):
"""Second placement heuristic.
Idea (based on "first-fit decreasing" bin-packing
heuristic) : order clusters by decreasing variance, place all containers
belonging to the clusters in this order.
For each container, try to place it on a node decreasing the node's
variance. If not possible, place it on the node whose variance increases
the least.
:param instance: _description_
:type instance: Instance
:param cluster_vars: _description_
:type cluster_vars: np.array
:param cluster_var_matrix: _description_
:type cluster_var_matrix: np.array
:param labels_: _description_
:type labels_: List
:param bound_new_node: _description_, defaults to 50
:type bound_new_node: float, optional
"""
# TODO add open-nodes system (to run through open nodes only, and open a
# new one when needed / with criterion)
total_time = instance.sep_time
# find minimum number of nodes needed
min_nodes = nb_min_nodes(instance, total_time)
idx_cluster_vars = np.argsort(cluster_vars)[::-1]
conso_nodes = np.zeros((min_nodes, total_time))
# try to place "opposite clusters" first
conso_nodes, cluster_done = place_opposite_clusters(
instance, cluster_vars,
cluster_var_matrix, labels_,
min_nodes, conso_nodes)
nodes_vars = conso_nodes.var(axis=1)
idx_nodes_vars = np.argsort(nodes_vars)[::-1]
# We browse the clusters by variance decreasing
for i in range(instance.nb_clusters):
# check if cluster i has not be placed in init
if not cluster_done[idx_cluster_vars[i]]:
list_containers = [instance.dict_id_c[j] for j, value in enumerate(
labels_) if value == idx_cluster_vars[i]]
# We browse containers in cluster i
for container in list_containers:
consu_cont = instance.df_indiv.loc[
instance.
df_indiv[it.indiv_field] == container
][it.metrics[0]].to_numpy()[:total_time]
idx_node = 0
min_var = math.inf
idx_min_var = -1
assign_container = 0
# we run through already open nodes
for idx_node in idx_nodes_vars:
cap_node = instance.df_host_meta.loc[
instance.
df_host_meta[it.host_field] == instance.dict_id_n[idx_node]
][it.metrics[0]].to_numpy()[0]
new_var = (
conso_nodes[idx_node] + consu_cont).var()
# here we drop the node variance => ok
if (new_var <= nodes_vars[idx_node]) and np.all(
np.less(
(conso_nodes[idx_node] + consu_cont), cap_node)):
assign_container = idx_node + 1
break
# looking for the node variance grows the least
else:
if (new_var < min_var) and np.all(
np.less(
(conso_nodes[idx_node] + consu_cont),
cap_node)):
min_var = new_var
idx_min_var = idx_node
if not assign_container:
if min_var < math.inf:
assign_container = idx_min_var + 1
# TODO here criterion for opening new node
# if criterion : open node
# we open a new node
else:
print('Critic open new node !')
idx_node += 1
# TODO test nb_open nodes for feasability (raise error)
# Finally we assign the container
assign_container = assign_container - 1
conso_nodes[assign_container] += consu_cont
nodes_vars[assign_container
] = conso_nodes[assign_container].var()
idx_nodes_vars = np.argsort(nodes_vars)[::-1]
assign_container_node(
instance.dict_id_n[assign_container], container, instance)
[docs]def allocation_spread(instance, min_nodes=None):
"""Spread technique for placement.
:param instance: _description_
:type instance: Instance
:param min_nodes: _description_, defaults to None
:type min_nodes: int, optional
"""
total_time = instance.sep_time
min_nodes = min_nodes or nb_min_nodes(instance, total_time)
conso_nodes = np.zeros((instance.nb_nodes, total_time))
spread_containers(
instance.df_indiv[it.indiv_field].unique(),
instance, conso_nodes, total_time, min_nodes
)
# TODO consider 85% usage now ? maybe add parameter
# TODO make it generic with metrics
[docs]def nb_min_nodes(instance, total_time):
"""Compute the minimum number of nodes needed to support the load.
:param instance: _description_
:type instance: Instance
:param total_time: _description_
:type total_time: int
:return: _description_
:rtype: float
"""
# max_cpu = 0.0
# max_mem = 0.0
# for t in range(total_time):
# max_t_cpu = instance.df_indiv[
# instance.df_indiv[it.tick_field] == t][it.metrics[0]].sum()
# max_t_mem = instance.df_indiv[
# instance.df_indiv[it.tick_field] == t]['mem'].sum()
# if max_t_cpu > max_cpu:
# max_cpu = max_t_cpu
# if max_t_mem > max_mem:
# max_mem = max_t_mem
# # TODO consider nodes with different capacities
# cap_cpu = instance.df_host_meta[it.metrics[0]].to_numpy()[0]
# cap_mem = instance.df_host_meta['mem'].to_numpy()[0]
# min_nodes_cpu = math.ceil(max_cpu / cap_cpu)
# min_nodes_mem = math.ceil(max_mem / cap_mem)
# return max(min_nodes_cpu, min_nodes_mem)
max_metric = 0.0
for t in range(total_time):
max_t_metric = instance.df_indiv[
instance.df_indiv[it.tick_field] == t][it.metrics[0]].sum()
if max_t_metric > max_metric:
max_metric = max_t_metric
cap_metric = instance.df_host_meta[it.metrics[0]].to_numpy()[0]
return (math.ceil(max_metric / cap_metric))
# TODO integrate upper bound for considering all clusters sum variance < ub
[docs]def place_opposite_clusters(
instance, cluster_vars, cluster_var_matrix, labels_, min_nodes, conso_nodes
):
"""Initialize allocation heuristic by co-localizing distant clusters.
:param instance: _description_
:type instance: Instance
:param cluster_vars: _description_
:type cluster_vars: np.array
:param cluster_var_matrix: _description_
:type cluster_var_matrix: np.array
:param labels_: _description_
:type labels_: List
:param min_nodes: _description_
:type min_nodes: int
:param conso_nodes: _description_
:type conso_nodes: np.array
:return: _description_
:rtype: Tuple[np.array, np.array]
"""
total_time = instance.sep_time
lb = 0.0
valid_idx = np.where(cluster_var_matrix.flatten() > lb)[0]
min_idx = valid_idx[cluster_var_matrix.flatten()[valid_idx].argmin()]
i, j = np.unravel_index(
min_idx, cluster_var_matrix.shape)
cluster_done = np.zeros(instance.nb_clusters)
list_containers_i = [instance.dict_id_c[u] for u, value in enumerate(
labels_) if value == i]
list_containers_j = [instance.dict_id_c[u] for u, value in enumerate(
labels_) if value == j]
it_cont = colocalize_clusters(list_containers_i, list_containers_j,
instance, total_time, min_nodes,
conso_nodes)
if not (len(list_containers_i) == len(list_containers_j)):
# we have to place remaining containers
if it_cont < len(list_containers_j):
list_containers = list_containers_j[it_cont:]
spread_containers(list_containers, instance, conso_nodes,
total_time, min_nodes)
elif it_cont < len(list_containers_i):
list_containers = list_containers_i[it_cont:]
spread_containers(list_containers, instance, conso_nodes,
total_time, min_nodes)
cluster_done[i] = 1
cluster_done[j] = 1
return conso_nodes, cluster_done
[docs]def move_list_containers(mvg_conts, instance, tmin, tmax, order='max'):
"""Move the list of containers to move.
:param mvg_conts: _description_
:type mvg_conts: List
:param instance: _description_
:type instance: Instance
:param tmin: _description_
:type tmin: int
:param tmax: _description_
:type tmax: int
:param order: _description_, defaults to 'max'
:type order: str, optional
"""
# Remove all moving containers from nodes first
old_ids = {}
for mvg_cont in mvg_conts:
old_ids[mvg_cont] = instance.df_indiv.loc[
instance.df_indiv[it.indiv_field] == instance.dict_id_c[mvg_cont]
][it.host_field].to_numpy()[0]
remove_container_node(old_ids[mvg_cont], instance.dict_id_c[mvg_cont], instance)
# TODO developp smart method for replace containers (based on clustering)
# Assign them to a new node
mvg_conts_cons = {}
for mvg_cont in mvg_conts:
mvg_conts_cons[mvg_cont] = instance.df_indiv.loc[
instance.df_indiv[it.indiv_field] == instance.dict_id_c[mvg_cont]
][it.metrics[0]].to_numpy()
order_indivs = ()
if order == 'max':
order_indivs = ((max(cons), c) for c, cons in mvg_conts_cons.items())
elif order == 'mean':
order_indivs = ((sum(cons) / len(cons), c) for c, cons in mvg_conts_cons.items())
for val, c in sorted(order_indivs, reverse=True):
move_container(c, instance, tmin, tmax, old_ids[mvg_cont])
# TODO what to do if can't open another node
[docs]def move_container(mvg_cont, instance, tmin, tmax, old_id):
"""Move `mvg_cont` to another node.
:param mvg_cont: _description_
:type mvg_cont: int
:param instance: _description_
:type instance: Instance
:param tmin: _description_
:type tmin: int
:param tmax: _description_
:type tmax: int
:param old_id: _description_
:type old_id: str
"""
print('Moving container :', instance.dict_id_c[mvg_cont])
working_df_indiv = instance.df_indiv[
(instance.
df_indiv[it.tick_field] >= tmin) & (
instance.df_indiv[it.tick_field] <= tmax)]
nb_open_nodes = working_df_indiv[it.host_field].nunique()
cons_c = working_df_indiv.loc[
working_df_indiv[
it.indiv_field] == instance.dict_id_c[mvg_cont]
][it.metrics[0]].to_numpy()
n = working_df_indiv.loc[
working_df_indiv[
it.indiv_field] == instance.dict_id_c[mvg_cont]
][it.host_field].to_numpy()[0]
cap_node = instance.df_host_meta.loc[
instance.df_host_meta[it.host_field] == n
][it.metrics[0]].to_numpy()[0]
# conso_nodes = np.zeros((working_df_indiv[it.host_field].nunique(), duration))
nodes = working_df_indiv[it.host_field].unique()
n_int = 0
new_n = None
min_var = float('inf')
for node in nodes:
node_data = instance.df_host.loc[
(instance.df_host[it.tick_field] >= tmin) & (
instance.df_host[it.tick_field] <= tmax) & (
instance.df_host[it.host_field] == node
)
].groupby(
instance.df_host[it.tick_field]
)[it.metrics[0]].sum().to_numpy()
if (np.all(np.less((node_data + cons_c), cap_node))) and (
(node_data + cons_c).var() < min_var):
new_n = node
min_var = (node_data + cons_c).var()
n_int += 1
if new_n is None:
print('Impossible to move %s on another existing node.' % instance.dict_id_c[mvg_cont])
print('We need to open a new node')
nb_open_nodes += 1
n_int += 1
new_n = instance.dict_id_n[n_int]
assign_container_node(
new_n,
instance.dict_id_c[mvg_cont],
instance,
remove=False)
# n = working_df_indiv.loc[
# working_df_indiv[
# it.indiv_field] == instance.dict_id_c[mvg_cont]
# ][it.host_field].to_numpy()[0]
# print(working_df_indiv.loc[
# working_df_indiv[
# it.indiv_field] == instance.dict_id_c[mvg_cont]
# ][it.host_field].to_numpy()[0])
# input()
# n_int = ([k for k, v in instance.
# dict_id_n.items() if v == n][0] + 1) % nb_open_nodes
# print('He was on %s' % n)
# done = False
# n_count = 1
# while not done:
# # TODO check n <= min_nodes or infeasibility
# if np.all(np.less((conso_nodes[n_int] + cons_c), cap_node)):
# assign_container_node(
# instance.dict_id_n[n_int],
# instance.dict_id_c[mvg_cont],
# instance)
# done = True
# else:
# n_count += 1
# if n_count > nb_open_nodes:
# print('We need to open a new node')
# nb_open_nodes += 1
# conso_nodes = np.append(
# conso_nodes, [np.zeros(duration)], axis=0)
# n_int = nb_open_nodes - 1
# else:
# n_int = (n_int + 1) % nb_open_nodes
# cap_node = instance.df_host_meta.loc[
# instance.
# df_host_meta[it.host_field] == instance.dict_id_n[n_int]
# ][it.metrics[0]].to_numpy()[0]
print('He can go on %s' % new_n)
[docs]def build_placement_adj_matrix(df_indiv, dict_id_c):
"""Build the adjacency matrix of placement.
:param df_indiv: _description_
:type df_indiv: pd.DataFrame
:param dict_id_c: _description_
:type dict_id_c: Dict
:return: _description_
:rtype: np.array
"""
nodes_ = [None] * df_indiv[it.indiv_field].nunique()
for c in range(len(nodes_)):
nodes_[c] = df_indiv.loc[
df_indiv[it.indiv_field] == dict_id_c[c]][
it.host_field].to_numpy()[0]
v = np.zeros((len(nodes_), len(nodes_)))
for (i, j) in combinations(range(len(nodes_)), 2):
if nodes_[i] == nodes_[j]:
v[i, j] = 1
v[j, i] = 1
return v