From 31e875f95785840319a5ddc3c2471310b363a93d Mon Sep 17 00:00:00 2001 From: Michael Whittaker Date: Fri, 29 Jan 2021 00:01:47 -0800 Subject: [PATCH] Tidied up load dist code. --- plot_load_distribution.py | 50 +++++++++++++++++++++++++++++++++ quorums/__init__.py | 2 ++ quorums/strategy.py | 51 --------------------------------- quorums/viz.py | 59 ++++++++++++++++++++++++++++++++++++++- 4 files changed, 110 insertions(+), 52 deletions(-) create mode 100644 plot_load_distribution.py diff --git a/plot_load_distribution.py b/plot_load_distribution.py new file mode 100644 index 0000000..218f90d --- /dev/null +++ b/plot_load_distribution.py @@ -0,0 +1,50 @@ +from quorums import * +import matplotlib +import matplotlib.pyplot as plt +import numpy as np + + +def main(): + a = Node('a', capacity=100) + b = Node('b', capacity=200) + c = Node('c', capacity=100) + d = Node('d', capacity=200) + e = Node('e', capacity=100) + nodes = [a, b, c, d, e] + + quorum_systems = { + 'majority': QuorumSystem(reads=majority([a, b, c, d, e])), + 'crumbling_walls': QuorumSystem(reads=a*b + c*d*e), + 'paths': QuorumSystem(reads=a*b + a*c*e + d*e + d*c*b), + } + + for name, qs in quorum_systems.items(): + d = {0.0: 1, 0.1: 1, 0.2: 1, 0.3: 1, 0.4: 1, 0.5: 1, + 0.6: 1, 0.7: 1, 0.8: 1, 0.9: 1, 1.0: 1} + fig, axes = plt.subplots(3, 4, figsize=(6 * 4, 4 * 3), sharey='all') + axes_iter = (axes[row][col] for row in range(3) for col in range(4)) + + for fr in d.keys(): + sigma = qs.strategy(read_fraction=fr) + ax = next(axes_iter) + plot_load_distribution_on(ax, sigma, nodes) + ax.set_title(f'Optimized For Read Fraction = {fr}') + ax.set_xlabel('Read Fraction') + ax.legend() + + sigma = qs.strategy(read_fraction=d) + ax = next(axes_iter) + plot_load_distribution_on(ax, sigma, nodes) + ax.set_title('Optimized For Uniform Read Fraction') + ax.set_xlabel('Read Fraction') + ax.legend() + + axes[0][0].set_ylabel('Load') + axes[1][0].set_ylabel('Load') + axes[2][0].set_ylabel('Load') + fig.tight_layout() + fig.savefig(f'{name}.pdf') + + +if __name__ == '__main__': + main() diff --git a/quorums/__init__.py b/quorums/__init__.py index b509c36..3c10e52 100644 --- a/quorums/__init__.py +++ b/quorums/__init__.py @@ -7,4 +7,6 @@ from .viz import ( plot_node_utilization_on, plot_node_throughput, plot_node_throughput_on, + plot_load_distribution, + plot_load_distribution_on, ) diff --git a/quorums/strategy.py b/quorums/strategy.py index 6889905..fba7251 100644 --- a/quorums/strategy.py +++ b/quorums/strategy.py @@ -6,9 +6,6 @@ from .geometry import Point, Segment from typing import Dict, Generic, List, Optional, Set, Tuple, TypeVar import collections import itertools -import math -import matplotlib -import matplotlib.pyplot as plt import numpy as np @@ -87,54 +84,6 @@ class Strategy(Generic[T]): -> float: return 1 / self.load(read_fraction, write_fraction) - def _group(self, segments: List[Tuple[Segment, T]]) -> Dict[Segment, List[T]]: - groups: Dict[Segment, List[T]] = collections.defaultdict(list) - for segment, x in segments: - match_found = False - for other, xs in groups.items(): - if segment.approximately_equal(other): - xs.append(x) - match_found = True - break - - if not match_found: - groups[segment].append(x) - - return groups - - def plot_load_distribution_on(self, - ax: plt.Axes, - nodes: Optional[List[Node[T]]] = None) \ - -> None: - nodes = nodes or list(self.nodes) - - # We want to plot every node's load distribution. Multiple nodes might - # have the same load distribution, so we group the nodes by their - # distribution. The grouping is a little annoying because two floats - # might not be exactly equal but pretty close. - groups = self._group([ - (Segment(Point(0, self.node_load(node, read_fraction=0)), - Point(1, self.node_load(node, read_fraction=1))), node.x) - for node in nodes - ]) - - # Compute and plot the max of all segments. We increase the line - # slightly so it doesn't overlap with the other lines. - path = geometry.max_of_segments(list(groups.keys())) - ax.plot([p[0] for p in path], - [p[1] for p in path], - label='load', - linewidth=4) - - for segment, xs in groups.items(): - ax.plot([segment.l.x, segment.r.x], - [segment.l.y, segment.r.y], - '--', - label=','.join(str(x) for x in xs), - linewidth=2, - alpha=0.75) - - def _node_load(self, x: T, fr: float) -> float: """ _node_load returns the load on x given a fixed read fraction fr. diff --git a/quorums/viz.py b/quorums/viz.py index 5b74ae1..153617a 100644 --- a/quorums/viz.py +++ b/quorums/viz.py @@ -1,8 +1,11 @@ from . import distribution +from . import geometry from .distribution import Distribution from .expr import Node +from .geometry import Point, Segment from .strategy import Strategy -from typing import List, Optional, Set, TypeVar +from typing import Dict, List, Optional, Set, Tuple, TypeVar +import collections import matplotlib import matplotlib.pyplot as plt import numpy as np @@ -146,3 +149,57 @@ def _plot_node_load_on(ax: plt.Axes, write_capacities, matplotlib.cm.get_cmap('Blues')) ax.set_xticks(x_ticks) ax.set_xticklabels(str(x) for x in x_list) + + +def plot_load_distribution(filename: str, + strategy: Strategy[T], + nodes: Optional[List[Node[T]]] = None): + fig, ax = plt.subplots() + plot_load_distribution_on(ax, strategy, nodes) + ax.set_xlabel('Read Fraction') + ax.set_ylabel('Load') + fig.tight_layout() + fig.savefig(filename) + + +def _group(segments: Dict[T, Segment]) -> Dict[Segment, List[T]]: + groups: Dict[Segment, List[T]] = collections.defaultdict(list) + for x, segment in segments.items(): + matches = (s for s in groups if segment.approximately_equal(s)) + groups[next(matches, segment)].append(x) + return groups + + +def plot_load_distribution_on(ax: plt.Axes, + strategy: Strategy[T], + nodes: Optional[List[Node[T]]] = None): + nodes = nodes or list(strategy.nodes) + + # We want to plot every node's load distribution. Multiple nodes might + # have the same load distribution, so we group the nodes by their + # distribution. The grouping is a little annoying because two floats + # might not be exactly equal but pretty close. + groups = _group({ + node.x: Segment( + Point(0, strategy.node_load(node, read_fraction=0)), + Point(1, strategy.node_load(node, read_fraction=1)) + ) + for node in nodes + }) + + # Compute and plot the max of all segments. We plot the load first so that + # it lies underneath the node loads. + path = geometry.max_of_segments(list(groups.keys())) + ax.plot([p[0] for p in path], + [p[1] for p in path], + label='load', + linewidth=4) + + # We plot the node loads second so that they appear above the load. + for segment, xs in groups.items(): + ax.plot([segment.l.x, segment.r.x], + [segment.l.y, segment.r.y], + '--', + label=','.join(str(x) for x in xs), + linewidth=2, + alpha=0.75)