From f85cdb401bf3682c916812d79c35f10bb6738406 Mon Sep 17 00:00:00 2001 From: Csaba Kiraly Date: Wed, 29 Mar 2023 15:49:52 +0200 Subject: [PATCH 1/5] fix line allocation when evenLineDistribution=True vector should have chi elements for each validator Signed-off-by: Csaba Kiraly --- DAS/simulator.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/DAS/simulator.py b/DAS/simulator.py index a6788b8..c58b20f 100644 --- a/DAS/simulator.py +++ b/DAS/simulator.py @@ -46,8 +46,9 @@ class Simulator: lightVal = int(self.shape.numberNodes * self.shape.class1ratio * self.shape.vpn1) heavyVal = int(self.shape.numberNodes * (1-self.shape.class1ratio) * self.shape.vpn2) totalValidators = lightVal + heavyVal - rows = list(range(self.shape.blockSize)) * (int(totalValidators/self.shape.blockSize)+1) - columns = list(range(self.shape.blockSize)) * (int(totalValidators/self.shape.blockSize)+1) + totalRows = totalValidators * self.shape.chi + rows = list(range(self.shape.blockSize)) * (int(totalRows/self.shape.blockSize)+1) + columns = list(range(self.shape.blockSize)) * (int(totalRows/self.shape.blockSize)+1) offset = heavyVal*self.shape.chi random.shuffle(rows) random.shuffle(columns) From 9f3089c232d941b897a8393a5671c0bb557ff5b1 Mon Sep 17 00:00:00 2001 From: Csaba Kiraly Date: Wed, 29 Mar 2023 15:54:44 +0200 Subject: [PATCH 2/5] rowIDs and columnIDs are sets Fixes issue 29, where multiple instances of an ID in rowIDs created a topology with nodes with a huge degree. This huge degree then created lots of duplicates, eating up available bandwidth. Signed-off-by: Csaba Kiraly --- DAS/validator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DAS/validator.py b/DAS/validator.py index 49165a0..32ef149 100644 --- a/DAS/validator.py +++ b/DAS/validator.py @@ -69,8 +69,8 @@ class Validator: # random.seed(self.ID) self.nodeClass = 1 if (self.ID <= shape.numberNodes * shape.class1ratio) else 2 self.vpn = self.shape.vpn1 if (self.nodeClass == 1) else self.shape.vpn2 - self.rowIDs = rows if rows else unionOfSamples(range(self.shape.blockSize), self.shape.chi, self.vpn) - self.columnIDs = columns if columns else unionOfSamples(range(self.shape.blockSize), self.shape.chi, self.vpn) + self.rowIDs = set(rows) if rows else unionOfSamples(range(self.shape.blockSize), self.shape.chi, self.vpn) + self.columnIDs = set(columns) if columns else unionOfSamples(range(self.shape.blockSize), self.shape.chi, self.vpn) self.rowNeighbors = collections.defaultdict(dict) self.columnNeighbors = collections.defaultdict(dict) From 795bb1d10d189315c310014ba857f959df8d2c8b Mon Sep 17 00:00:00 2001 From: Leonardo Bautista-Gomez Date: Thu, 30 Mar 2023 13:15:42 +0200 Subject: [PATCH 3/5] Move set to simulator for future diagnostic purposes --- DAS/simulator.py | 6 ++++-- DAS/validator.py | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/DAS/simulator.py b/DAS/simulator.py index c58b20f..5aac495 100644 --- a/DAS/simulator.py +++ b/DAS/simulator.py @@ -61,8 +61,10 @@ class Simulator: j = i - int(heavyVal/self.shape.vpn2) start = offset+( j *self.shape.chi) end = offset+((j+1)*self.shape.chi) - r = rows[start:end] - c = columns[start:end] + r = list(set(rows[start:end])) + c = list(set(columns[start:end])) + r.sort() + c.sort() val = Validator(i, int(not i!=0), self.logger, self.shape, r, c) else: val = Validator(i, int(not i!=0), self.logger, self.shape) diff --git a/DAS/validator.py b/DAS/validator.py index 32ef149..49165a0 100644 --- a/DAS/validator.py +++ b/DAS/validator.py @@ -69,8 +69,8 @@ class Validator: # random.seed(self.ID) self.nodeClass = 1 if (self.ID <= shape.numberNodes * shape.class1ratio) else 2 self.vpn = self.shape.vpn1 if (self.nodeClass == 1) else self.shape.vpn2 - self.rowIDs = set(rows) if rows else unionOfSamples(range(self.shape.blockSize), self.shape.chi, self.vpn) - self.columnIDs = set(columns) if columns else unionOfSamples(range(self.shape.blockSize), self.shape.chi, self.vpn) + self.rowIDs = rows if rows else unionOfSamples(range(self.shape.blockSize), self.shape.chi, self.vpn) + self.columnIDs = columns if columns else unionOfSamples(range(self.shape.blockSize), self.shape.chi, self.vpn) self.rowNeighbors = collections.defaultdict(dict) self.columnNeighbors = collections.defaultdict(dict) From 296c4fb762be2b8409c3521ae561533454ab1a97 Mon Sep 17 00:00:00 2001 From: Leonardo Bautista-Gomez Date: Thu, 30 Mar 2023 13:36:48 +0200 Subject: [PATCH 4/5] Rows and columns to sets --- DAS/simulator.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/DAS/simulator.py b/DAS/simulator.py index 5aac495..99cb5a5 100644 --- a/DAS/simulator.py +++ b/DAS/simulator.py @@ -61,10 +61,8 @@ class Simulator: j = i - int(heavyVal/self.shape.vpn2) start = offset+( j *self.shape.chi) end = offset+((j+1)*self.shape.chi) - r = list(set(rows[start:end])) - c = list(set(columns[start:end])) - r.sort() - c.sort() + r = set(rows[start:end]) + c = set(columns[start:end]) val = Validator(i, int(not i!=0), self.logger, self.shape, r, c) else: val = Validator(i, int(not i!=0), self.logger, self.shape) From 7719f84a10a7f5d867aa608876cb29c03fa6b535 Mon Sep 17 00:00:00 2001 From: Leo Date: Thu, 30 Mar 2023 13:41:50 +0200 Subject: [PATCH 5/5] Switch from time steps to miliseconds (#30) Switch from time steps to miliseconds --- DAS/results.py | 13 +++++++------ DAS/simulator.py | 13 +++++++------ DAS/visualizer.py | 6 ++++-- config_example.py | 15 +++++++++------ study.py | 2 +- 5 files changed, 28 insertions(+), 21 deletions(-) diff --git a/DAS/results.py b/DAS/results.py index c5687a5..a11da24 100644 --- a/DAS/results.py +++ b/DAS/results.py @@ -7,22 +7,23 @@ from dicttoxml import dicttoxml class Result: """This class stores and process/store the results of a simulation.""" - def __init__(self, shape): + def __init__(self, shape, execID): """It initializes the instance with a specific shape.""" self.shape = shape + self.execID = execID self.blockAvailable = -1 self.tta = -1 self.missingVector = [] self.metrics = {} - def populate(self, shape, missingVector): + def populate(self, shape, config, missingVector): """It populates part of the result data inside a vector.""" self.shape = shape self.missingVector = missingVector missingSamples = missingVector[-1] if missingSamples == 0: self.blockAvailable = 1 - self.tta = len(missingVector) + self.tta = len(missingVector) * (config.stepDuration) else: self.blockAvailable = 0 self.tta = -1 @@ -35,8 +36,8 @@ class Result: """It dumps the results of the simulation in an XML file.""" if not os.path.exists("results"): os.makedirs("results") - if not os.path.exists("results/"+execID): - os.makedirs("results/"+execID) + if not os.path.exists("results/"+self.execID): + os.makedirs("results/"+self.execID) resd1 = self.shape.__dict__ resd2 = self.__dict__.copy() resd2.pop("shape") @@ -44,6 +45,6 @@ class Result: resXml = dicttoxml(resd1) xmlstr = minidom.parseString(resXml) xmlPretty = xmlstr.toprettyxml() - filePath = "results/"+execID+"/"+str(self.shape)+".xml" + filePath = "results/"+self.execID+"/"+str(self.shape)+".xml" with open(filePath, "w") as f: f.write(xmlPretty) diff --git a/DAS/simulator.py b/DAS/simulator.py index 677a260..4acf3f5 100644 --- a/DAS/simulator.py +++ b/DAS/simulator.py @@ -13,12 +13,13 @@ from DAS.validator import * class Simulator: """This class implements the main DAS simulator.""" - def __init__(self, shape, config): + def __init__(self, shape, config, execID): """It initializes the simulation with a set of parameters (shape).""" self.shape = shape self.config = config self.format = {"entity": "Simulator"} - self.result = Result(self.shape) + self.execID = execID + self.result = Result(self.shape, self.execID) self.validators = [] self.logger = [] self.logLevel = config.logLevel @@ -192,14 +193,14 @@ class Simulator: # log TX and RX statistics trafficStats = self.glob.getTrafficStats(self.validators) - self.logger.debug("step %d: %s" % + self.logger.debug("step %d: %s" % (steps, trafficStats), extra=self.format) for i in range(0,self.shape.numberNodes): self.validators[i].updateStats() trafficStatsVector.append(trafficStats) missingSamples, sampleProgress, nodeProgress, validatorProgress = self.glob.getProgress(self.validators) - self.logger.debug("step %d, arrived %0.02f %%, ready %0.02f %%, validated %0.02f %%" + self.logger.debug("step %d, arrived %0.02f %%, ready %0.02f %%, validated %0.02f %%" % (steps, sampleProgress*100, nodeProgress*100, validatorProgress*100), extra=self.format) cnS = "samples received" @@ -231,7 +232,7 @@ class Simulator: missingVector.append(missingSamples) break elif missingSamples == 0: - #self.logger.info("The entire block is available at step %d, with failure rate %d !" % (steps, self.shape.failureRate), extra=self.format) + self.logger.debug("The entire block is available at step %d, with failure rate %d !" % (steps, self.shape.failureRate), extra=self.format) missingVector.append(missingSamples) break else: @@ -240,6 +241,6 @@ class Simulator: progress = pd.DataFrame(progressVector) if self.config.saveProgress: self.result.addMetric("progress", progress.to_dict(orient='list')) - self.result.populate(self.shape, missingVector) + self.result.populate(self.shape, self.config, missingVector) return self.result diff --git a/DAS/visualizer.py b/DAS/visualizer.py index 8afd006..db4b2d5 100644 --- a/DAS/visualizer.py +++ b/DAS/visualizer.py @@ -36,7 +36,7 @@ class Visualizer: bwUplinkProd = int(root.find('bwUplinkProd').text) bwUplink1 = int(root.find('bwUplink1').text) bwUplink2 = int(root.find('bwUplink2').text) - tta = int(root.find('tta').text) + tta = float(root.find('tta').text) # Loop over all possible combinations of of the parameters minus two for combination in combinations(self.parameters, len(self.parameters)-2): @@ -120,7 +120,7 @@ class Visualizer: hist, xedges, yedges = np.histogram2d(data[key][labels[0]], data[key][labels[1]], bins=(len(xlabels), len(ylabels)), weights=data[key]['ttas']) hist = hist.T fig, ax = plt.subplots(figsize=(10, 6)) - sns.heatmap(hist, xticklabels=xlabels, yticklabels=ylabels, cmap='Purples', cbar_kws={'label': 'Time to block availability'}, linecolor='black', linewidths=0.3, annot=True, fmt=".2f", ax=ax) + sns.heatmap(hist, xticklabels=xlabels, yticklabels=ylabels, cmap='Purples', cbar_kws={'label': 'Time to block availability (ms)'}, linecolor='black', linewidths=0.3, annot=True, fmt=".2f", ax=ax) plt.xlabel(self.formatLabel(labels[0])) plt.ylabel(self.formatLabel(labels[1])) filename = "" @@ -131,6 +131,8 @@ class Visualizer: filename += f"{key[paramValueCnt]}" formattedTitle = self.formatTitle(key[paramValueCnt]) title += formattedTitle + if (paramValueCnt+1) % 5 == 0: + title += "\n" paramValueCnt += 1 title_obj = plt.title(title) font_size = 16 * fig.get_size_inches()[0] / 10 diff --git a/config_example.py b/config_example.py index 5b1a396..3ff5ae8 100644 --- a/config_example.py +++ b/config_example.py @@ -27,14 +27,14 @@ logLevel = logging.INFO # number of parallel workers. -1: all cores; 1: sequential # for more details, see joblib.Parallel -numJobs = 3 +numJobs = -1 # distribute rows/columns evenly between validators (True) # or generate it using local randomness (False) evenLineDistribution = True # Number of simulation runs with the same parameters for statistical relevance -runs = range(10) +runs = range(2) # Number of validators numberNodes = range(256, 513, 128) @@ -49,14 +49,14 @@ blockSizes = range(32,65,16) netDegrees = range(6, 9, 2) # number of rows and columns a validator is interested in -chis = range(1, 5, 2) +chis = range(2, 5, 2) # ratio of class1 nodes (see below for parameters per class) -class1ratios = np.arange(0, 1, .2) +class1ratios = [0.8, 0.9] # Number of validators per beacon node validatorsPerNode1 = [1] -validatorsPerNode2 = [2, 4, 8, 16, 32] +validatorsPerNode2 = [500] # Set uplink bandwidth. In segments (~560 bytes) per timestep (50ms?) # 1 Mbps ~= 1e6 / 20 / 8 / 560 ~= 11 @@ -64,8 +64,11 @@ bwUplinksProd = [2200] bwUplinks1 = [110] bwUplinks2 = [2200] +# Step duration in miliseconds (Classic RTT is about 100ms) +stepDuration = 50 + # Set to True if you want your run to be deterministic, False if not -deterministic = False +deterministic = True # If your run is deterministic you can decide the random seed. This is ignore otherwise. randomSeed = "DAS" diff --git a/study.py b/study.py index fde8099..aa27a5f 100644 --- a/study.py +++ b/study.py @@ -28,7 +28,7 @@ def runOnce(config, shape, execID): shape.setSeed(config.randomSeed+"-"+str(shape)) random.seed(shape.randomSeed) - sim = Simulator(shape, config) + sim = Simulator(shape, config, execID) sim.initLogger() sim.initValidators() sim.initNetwork()