Created
March 8, 2022 14:19
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Paste to file and run with
python3 scriptname.py