Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save GastonMazzei/ca880ca93c78554845075fd2b4536a5b to your computer and use it in GitHub Desktop.
Save GastonMazzei/ca880ca93c78554845075fd2b4536a5b to your computer and use it in GitHub Desktop.
Each processor (node of a ring network) applies a set of local rules and is able to count the number of nodes in the network. It is self-stabilizing as it supports random initialization.
import numpy as np
import matplotlib.pyplot as plt
from uuid import uuid4
"""
Distributed Self-stabilizing Algorithms:
Compute locally the ring's size.
assuming all variables are initialized randomly.
Precondition: no local set of observed IDs is initialized containing IDs
that are part of the ring network. Most probably valid if
IDs are generated using UUID4 and ring size is much smaller.
Pseudo-code follows.
statesi:
S a set full of seen names // self-stabilizing means this could start empty or full of nonesense
//
// OBS: ALGORITHM DEPENDS on S initially NOT HAVING EXISTENT CHILD NAMES.
// we can assure this will occur with a very high probability if
// number_possible_names >> number_nodes
// e.g. using UUID4 to generate names and using number_nodes ~ 10k.
myname the child’s name // in our self-stabilizing analysis we assume this is CORRECT
// self-stabilizing means this could be any number >= 0
L the length of the anneau
// self-stabilizing means this could start being any array, e.g. empty array {}, or array full of noise {1,100,2,…}
Ssize the size of S
RecvSizesRight an array with the sizes received from right neighbors
RecvSizesLeft an array with the sizes received from left neighbors
msgsi to the left:
if Ssize[-2] < Ssize[-1] then // if the received name was new, forward it
send left (“name received from the right”, SsizePost)
else
send left (myname, SsizePost) // else send ours
msgsi to the right:
if Ssize[-2] < Ssize[-1] then // if the received name was new, forward it
send right (“name received from the left”, SsizePost)
else
send right (myname, SsizePost) // else send ours
transitioni:
if Ssize[-1] != sizeof(S) // useful to align the sizes array and the set in the first randomly-initialized iteration
Ssize.push_back( sizeof(S) )
S := S U {“name received from the left”} U {“name received from the right”} U {myname} // recompute set
Ssize.push_back( sizeof(S) ) // update size
if ( Ssize[-1] == Ssize[-2] == Ssize[-3] ) // if there have been N=3 rounds without updates, compute L
L := (b – a) / 1.5f // where Ssize[a] to Ssize[b] define a straight line with slope 1 and standard deviation exactly 0
// OBS: the computation can also be done using RecvSizesRight or RecvSizesLeft
"""
def NameGenerator():
"""
Generate a random name
"""
# Option 1: home-made randomness
NameLen = 30
letters = "abcdefghijklmnopqrstuvwxyz"
N = len(letters)
name = ''.join([letters[x] for x in np.random.randint(0,N,NameLen)])
#return name
#
# Option 2: using standard uuid4
return uuid4().__str__()
def SetGenerator(N):
return {NameGenerator() for i in range(N)}
class RingNode:
def __init__(self, name: str, S: set, c: int, d: int):
self.name = name
self.set = set(S)
self.message_left = (NameGenerator(), np.random.randint(c,d))
self.message_right = (NameGenerator(), np.random.randint(c,d))
self.name_received_from_right = None
self.name_received_from_left = None
self.SsizePrev = 0
self.SsizePost = 0
# Extra: add random history for the received sizes container
e,f,g = np.random.randint(c,d,3)
if e>f:
temp = e
e=f
f=temp
elif e==f:
f += 1
self.recvSizes = [np.random.randint(e,f,g).tolist(),
np.random.randint(e,f,g).tolist()]
class Ring:
def __init__(self, L = 10, a=0, b=100, c=0, d=100):
self.ring = [RingNode(
NameGenerator(),
SetGenerator(np.random.randint(a,b)),
c,d,
) for _ in range(L)]
self.L = L
def C(self):
for i,node in enumerate(self.ring):
# Collect messages 'received from neighbors'
node.name_received_from_right = self.ring[(i+1) % self.L].message_left
node.name_received_from_left = self.ring[(i-1) % self.L].message_right
# set size before update
node.SsizePrev = len(node.set)
# update set
node.set = node.set.union({node.name})
node.set = node.set.union({node.name_received_from_right[0]})
node.set = node.set.union({node.name_received_from_left[0]})
# set size after update
node.SsizePost = len(node.set)
# append received vals to the list that keeps track of history
node.recvSizes[0].append(node.name_received_from_left[1])
node.recvSizes[1].append(node.name_received_from_right[1])
def M(self):
for i,node in enumerate(self.ring):
if node.SsizePrev < node.SsizePost:
node.message_left = (node.name_received_from_right[0], node.SsizePost)
node.message_right = (node.name_received_from_left[0], node.SsizePost)
else:
node.message_left = (node.name, node.SsizePost)
node.message_right = (node.name, node.SsizePost)
def iterate(self):
self.C()
self.M()
def simulate(self, EPOCHS = 100):
for _ in range(EPOCHS):
self.iterate()
def extract_results(self):
results = []
for node in self.ring:
results.append(np.asarray(node.recvSizes))
return (self.L, results)
class Displayer():
def __init__(self, results: np.ndarray):
self.data = results
def show(self):
f, ax = plt.subplots(1,2, figsize=(20,7))
for i in range(2):
maxL = 0
for j in range(len(self.data[1])):
if len(self.data[1][j][i,:]) > maxL:
maxL = len(self.data[1][j][i,:])
for j in range(len(self.data[1])):
llen = len(self.data[1][j][i,:])
offset = maxL - llen
ax[i].plot(range(offset, maxL), self.data[1][j][i,:], c=['k','r'][i], alpha=0.3)
ax[i].set_title(f'Ring Length is {self.data[0]}\n(mssgs received from {["right","left"][i]})')
ax[i].set_ylabel('Recv size value')
ax[i].set_xlabel('right-aligned array index')
plt.show()
def main(RingLength,
RandomInitialSetUniformBounds,
RandomInitialMessageUniformBounds,
SimulationEpochs):
# Start a bidirectional ring instance
BidirectionalRing = Ring(
RingLength,
*RandomInitialSetUniformBounds,
*RandomInitialMessageUniformBounds,
)
# Apply the algorithm
BidirectionalRing.simulate(
SimulationEpochs
)
# Start a results displayer instance
ResultsDisplayer = Displayer( BidirectionalRing.extract_results() )
# Show results
ResultsDisplayer.show()
if __name__=='__main__':
SimulationEpochs = 500
for L in [200, 20,50,100]:
for a_b in [(0,100), (10,500), (0,2)]:
for c_d in [(0,100), (10,500), (0,2)]:
# Configure simulation
RingLength = L
RandomInitialSetUniformBounds = a_b
RandomInitialMessageUniformBounds = c_d
# Run main function
main(RingLength,
RandomInitialSetUniformBounds,
RandomInitialMessageUniformBounds,
SimulationEpochs)
@GastonMazzei
Copy link
Author

Paste to file and run with python3 scriptname.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment