diff tools/myTools/bin/FVS_python3/FVS_localsearch_10_python.py @ 1:7e5c71b2e71f draft default tip

Uploaded
author laurenmarazzi
date Wed, 22 Dec 2021 16:00:34 +0000
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tools/myTools/bin/FVS_python3/FVS_localsearch_10_python.py	Wed Dec 22 16:00:34 2021 +0000
@@ -0,0 +1,193 @@
+'''
+This file contains functions used to calculate feedback vertex set for directed graph
+
+The major function is the FVS_local_search(), which calculate maximum sub topological ordering of a graph
+The algorithm is given by "Applying local search to feedback vertex set problem"
+Author of the paper Philippe Galinier, Eunice Lemamou, Mohamed Wassim Bouzidi
+The code mimic the pseudocode given in Page 805 in that paper
+The code is written by Gang Yang, Penn State University
+'''
+
+
+import networkx as nx
+import random
+import math
+import array
+
+
+
+#the following two function calculate the position for the candidate given the existing topological ordering S
+def get_position_minus(candidate_incoming_neighbour, S):
+  '''
+  get_position_minus return position as just after its numbered in-coming neighbours
+  As in the paper, the function return i_minus(v)
+  '''
+  position = 1
+  for x in range(len(S)-1,-1,-1):             #we loop through index from the end as we try to find the largest index of numbered incoming neighbour
+    if S[x] in candidate_incoming_neighbour:
+      position = x+2                          #2 comes from we need to put candidate after the incoming neighbour and also the position count from 1 instead of 0
+      return position
+  return position                             #put the candidate in the first position if there is no numbered incoming neighbour
+
+
+
+def get_position_plus(candidate_outgoing_neighbour, S):
+  '''
+  get_position_plus return position as just before its numbered out-going neighbours
+  As in the paper, the function return i_plus(v)
+  '''
+  position = 1+len(S)
+  for x in range(len(S)):                     #we loop through index from the beginning as we try to find the smallest index of numbered outgoing neighbour
+    if S[x] in candidate_outgoing_neighbour:
+      position = x+1                          #1 comes from the fact position count from 1 instead of 0
+      return position
+  return position                             #put the candidate in the first position if there is no numbered outgoing neighbour
+
+
+
+def FVS_local_search(G_input, T_0, alpha, maxMvt, maxFail, randomseed=None):
+  '''
+  Returns an maximum sub topological ordering of a DiGraph G.
+  FVS is G_input \ the topological ordering
+  A topological ordering of a graph is an ordering of vertices such that
+  the starting point of every arc occurs earlier in the ordering than
+  the endpoint of the art.
+
+  This algorithm is a fast approximation based on simulating annealing(SA) of a noval local search strategy [1]_.
+  The code follows the pseudocode given in Page 805 in that paper.
+
+
+  Parameters
+  ----------
+  G : NetworkX Graph/DiGraph, result for MultiGraph is not tested
+  T_0 : the initial temperature  in SA
+  alpha : the cooling multiplier for temperatue in the geometric cooling regime
+  maxMvt_factor : maxMvt_factor times network size is the number of iterations for the inner loop given a fixed temperatue
+  maxFail : FVS_local_search stops when maxFail number of outloops (temperatue) fail to improve the result
+  randomseed: random seed for generating random numbers
+
+  Returns
+  -------
+  An approximation of the maximum ordering of the given graph as a list.
+
+
+  Notes
+  -----
+  The code is written by Gang Yang, Department of Physics, Penn State University
+
+
+  References
+  ----------
+  ..[1] Galinier, P., Lemamou, E. & Bouzidi, M.W. J Heuristics (2013) 19: 797. doi:10.1007/s10732-013-9224-z
+
+  '''
+  #set a random seed
+  random.seed(randomseed)
+  G=G_input.copy()
+  N= len(G.nodes())           #number of nodes in the graph
+  edges=G.edges()
+
+
+  #Initialization
+  T = T_0                     #set initial temperatue
+  nbFail = 0                  #Outer loop counter to record failure times
+  S = []                      #list to record the ascending ordering
+  S_optimal = []              #list to record the optimal ordering
+
+
+  #calculate parent and child node for each node
+  parent = [{} for i in range(N)]
+  child = [{} for i in range(N)]
+
+  for edge in edges:
+    child[int(edge[0])][int(edge[1])]=None
+    parent[int(edge[1])][int(edge[0])]=None
+
+  #all the nodes that are self_loops
+  self_loops = [edge[0] for edge in edges if edge[0]==edge[1]]
+  print(self_loops)
+  #all the nodes that is not in S
+  unnumbered=[x for x in range(N) if x not in self_loops]
+  N_unnumbered = len(unnumbered)
+  while nbFail< maxFail:
+    nbMvt = 0       #Inner loop counter to record movement times
+    failure = True  #if cardinal of S increase after one inner loop, failure will be set to false
+    while nbMvt < maxMvt:
+      candidate_index = random.randint(0, N_unnumbered-1)
+      candidate = unnumbered[candidate_index]         #random pick a node from all unnumbered node
+      position_type = random.randint(0,1)             #random choose a position type
+      #calculate incoming/outgoing neighbour for the candidate node, store as keys in the dict
+      candidate_incoming_neighbour = parent[candidate]
+      candidate_outgoing_neighbour = child[candidate]
+      #see how to find the position on Page 803 of the paper
+      #position_type=1 means just after incoming neighbours
+      if position_type==1:
+        position = get_position_minus(candidate_incoming_neighbour,S)
+      #position_type=0 means just before outgoint neighbours
+      elif position_type==0:
+        position = get_position_plus(candidate_outgoing_neighbour,S)
+
+      #first, insert the candidate to the given position
+      S_trail = S[:]  #copy the list
+      S_trail.insert(position-1,candidate)
+
+      #second remove all the conflict
+      #break the sequence into two parts: before and after the candidate node and
+      S_trail_head= S_trail[:position-1]
+      S_trail_tail= S_trail[position:]
+      #determine conflict node See page 801
+      if position_type==1:
+        CV_pos=[]    #conflict before the newly inserted node in the topological ordering
+        for x in range(len(S_trail_head)):
+          nodetemp=S_trail_head[x]
+          #print nodetemp,candidate_outgoing_neighbour,nodetemp in candidate_outgoing_neighbour
+          if nodetemp in candidate_outgoing_neighbour:
+            CV_pos.append(nodetemp)
+        conflict=CV_pos   #there won't be conflict after the inserted node as the node inserted after its incoming neighbour
+      elif position_type==0:
+        CV_neg=[]    #conflict after the newly inserted node in the topological ordering
+        for x in range(len(S_trail_tail)):
+          nodetemp=S_trail_tail[x]
+          #print nodetemp,candidate_incoming_neighbour,nodetemp in candidate_incoming_neighbour
+          if nodetemp in candidate_incoming_neighbour:
+            CV_neg.append(nodetemp)
+        conflict=CV_neg   #there won't be conflict before the inserted node as the node inserted before its outgoing neighbour
+      #finally remove the conflict node
+      N_conflict=len(conflict)
+      if N_conflict>0:
+        for i in range(N_conflict):
+          S_trail.remove(conflict[i])
+
+      #third, evaluate the move
+      #delta_move=-len(S_trail)+len(S)
+      delta_move=N_conflict-1
+      #accept all the move that is not harmful, otherwise use metrospolis algorithm
+      if delta_move<=0 or math.exp(-delta_move/float(T))>random.random():
+        S = S_trail[:]
+        #update unnumbered nodes
+        unnumbered.remove(candidate)   #remove the node just inserted
+        if N_conflict>0:               #add all the conflict node just removed
+          for i in range(N_conflict):
+            unnumbered.append(conflict[i])
+        N_unnumbered+=delta_move
+        nbMvt = nbMvt+1
+        #update S_optimal only when there is increase in cardinal of the sequence
+        if len(S)>len(S_optimal):
+          S_optimal = S[:]
+          failure = False
+        if N_unnumbered==0:
+          return S_optimal
+
+    #Increment the failure times if no progress in size of the sequence
+    if failure==True:
+      nbFail+=1
+    else:    #otherwise reset the num of failure times
+      nbFail=0
+    #shrink the temperatue by factor alpha
+    T=T*alpha
+    #print T
+    #print nbFail
+  return S_optimal
+
+
+