Added duplicate free resilience computation.
This commit is contained in:
parent
2d4bcc1a09
commit
630effa405
1 changed files with 68 additions and 28 deletions
|
@ -12,6 +12,23 @@ import pulp
|
||||||
T = TypeVar('T')
|
T = TypeVar('T')
|
||||||
|
|
||||||
|
|
||||||
|
def _min_hitting_set(sets: Iterator[Set[T]]) -> int:
|
||||||
|
x_vars: Dict[T, pulp.LpVariable] = dict()
|
||||||
|
next_id = itertools.count()
|
||||||
|
|
||||||
|
problem = pulp.LpProblem("min_hitting_set", pulp.LpMinimize)
|
||||||
|
for (i, xs) in enumerate(sets):
|
||||||
|
for x in xs:
|
||||||
|
if x not in x_vars:
|
||||||
|
id = next(next_id)
|
||||||
|
x_vars[x] = pulp.LpVariable(f'x{id}', cat=pulp.LpBinary)
|
||||||
|
problem += sum(x_vars[x] for x in xs) >= 1
|
||||||
|
|
||||||
|
problem += sum(x_vars.values())
|
||||||
|
problem.solve(pulp.apis.PULP_CBC_CMD(msg=False))
|
||||||
|
return int(sum(v.varValue for v in x_vars.values()))
|
||||||
|
|
||||||
|
|
||||||
class Expr(Generic[T]):
|
class Expr(Generic[T]):
|
||||||
def __add__(self, rhs: 'Expr[T]') -> 'Expr[T]':
|
def __add__(self, rhs: 'Expr[T]') -> 'Expr[T]':
|
||||||
return _or(self, rhs)
|
return _or(self, rhs)
|
||||||
|
@ -31,9 +48,24 @@ class Expr(Generic[T]):
|
||||||
def nodes(self) -> Set['Node[T]']:
|
def nodes(self) -> Set['Node[T]']:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def resilience(self) -> int:
|
||||||
|
if self.dup_free():
|
||||||
|
return self._dup_free_min_failures() - 1
|
||||||
|
else:
|
||||||
|
return _min_hitting_set(self.quorums()) - 1
|
||||||
|
|
||||||
def dual(self) -> 'Expr[T]':
|
def dual(self) -> 'Expr[T]':
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def dup_free(self) -> bool:
|
||||||
|
return len(self.nodes()) == self._num_leaves()
|
||||||
|
|
||||||
|
def _num_leaves(self) -> int:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _dup_free_min_failures(self) -> int:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
class Node(Expr[T]):
|
class Node(Expr[T]):
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
|
@ -82,6 +114,12 @@ class Node(Expr[T]):
|
||||||
def dual(self) -> Expr:
|
def dual(self) -> Expr:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
def _num_leaves(self) -> int:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def _dup_free_min_failures(self) -> int:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
class Or(Expr[T]):
|
class Or(Expr[T]):
|
||||||
def __init__(self, es: List[Expr[T]]) -> None:
|
def __init__(self, es: List[Expr[T]]) -> None:
|
||||||
|
@ -109,6 +147,12 @@ class Or(Expr[T]):
|
||||||
def dual(self) -> Expr:
|
def dual(self) -> Expr:
|
||||||
return And([e.dual() for e in self.es])
|
return And([e.dual() for e in self.es])
|
||||||
|
|
||||||
|
def _num_leaves(self) -> int:
|
||||||
|
return sum(e._num_leaves() for e in self.es)
|
||||||
|
|
||||||
|
def _dup_free_min_failures(self) -> int:
|
||||||
|
return sum(e._dup_free_min_failures() for e in self.es)
|
||||||
|
|
||||||
|
|
||||||
class And(Expr[T]):
|
class And(Expr[T]):
|
||||||
def __init__(self, es: List[Expr[T]]) -> None:
|
def __init__(self, es: List[Expr[T]]) -> None:
|
||||||
|
@ -136,6 +180,11 @@ class And(Expr[T]):
|
||||||
def dual(self) -> Expr:
|
def dual(self) -> Expr:
|
||||||
return Or([e.dual() for e in self.es])
|
return Or([e.dual() for e in self.es])
|
||||||
|
|
||||||
|
def _num_leaves(self) -> int:
|
||||||
|
return sum(e._num_leaves() for e in self.es)
|
||||||
|
|
||||||
|
def _dup_free_min_failures(self) -> int:
|
||||||
|
return min(e._dup_free_min_failures() for e in self.es)
|
||||||
|
|
||||||
class Choose(Expr[T]):
|
class Choose(Expr[T]):
|
||||||
def __init__(self, k: int, es: List[Expr[T]]) -> None:
|
def __init__(self, k: int, es: List[Expr[T]]) -> None:
|
||||||
|
@ -166,6 +215,12 @@ class Choose(Expr[T]):
|
||||||
# TODO(mwhittaker): Prove that this is in fact the dual.
|
# TODO(mwhittaker): Prove that this is in fact the dual.
|
||||||
return Choose(len(self.es) - self.k + 1, [e.dual() for e in self.es])
|
return Choose(len(self.es) - self.k + 1, [e.dual() for e in self.es])
|
||||||
|
|
||||||
|
def _num_leaves(self) -> int:
|
||||||
|
return sum(e._num_leaves() for e in self.es)
|
||||||
|
|
||||||
|
def _dup_free_min_failures(self) -> int:
|
||||||
|
return sum(sorted(e._dup_free_min_failures() for e in self.es)[:self.k])
|
||||||
|
|
||||||
|
|
||||||
def _and(lhs: Expr[T], rhs: Expr[T]) -> 'And[T]':
|
def _and(lhs: Expr[T], rhs: Expr[T]) -> 'And[T]':
|
||||||
if isinstance(lhs, And) and isinstance(rhs, And):
|
if isinstance(lhs, And) and isinstance(rhs, And):
|
||||||
|
@ -305,10 +360,10 @@ class QuorumSystem(Generic[T]):
|
||||||
return min(self.read_resilience(), self.write_resilience())
|
return min(self.read_resilience(), self.write_resilience())
|
||||||
|
|
||||||
def read_resilience(self) -> int:
|
def read_resilience(self) -> int:
|
||||||
return self._min_hitting_set(self.read_quorums()) - 1
|
return self.reads.resilience()
|
||||||
|
|
||||||
def write_resilience(self) -> int:
|
def write_resilience(self) -> int:
|
||||||
return self._min_hitting_set(self.write_quorums()) - 1
|
return self.writes.resilience()
|
||||||
|
|
||||||
def strategy(self,
|
def strategy(self,
|
||||||
read_fraction: Optional[Distribution] = None,
|
read_fraction: Optional[Distribution] = None,
|
||||||
|
@ -334,6 +389,9 @@ class QuorumSystem(Generic[T]):
|
||||||
raise ValueError(f'There are no {f}-resilient write quorums')
|
raise ValueError(f'There are no {f}-resilient write quorums')
|
||||||
return self._load_optimal_strategy(read_quorums, write_quorums, d)
|
return self._load_optimal_strategy(read_quorums, write_quorums, d)
|
||||||
|
|
||||||
|
def dup_free(self) -> bool:
|
||||||
|
return self.reads.dup_free() and self.writes.dup_free()
|
||||||
|
|
||||||
def _f_resilient_quorums(self,
|
def _f_resilient_quorums(self,
|
||||||
f: int,
|
f: int,
|
||||||
xs: List[T],
|
xs: List[T],
|
||||||
|
@ -361,22 +419,6 @@ class QuorumSystem(Generic[T]):
|
||||||
sigma = self.strategy(read_fraction, write_fraction, f)
|
sigma = self.strategy(read_fraction, write_fraction, f)
|
||||||
return sigma.load(read_fraction, write_fraction)
|
return sigma.load(read_fraction, write_fraction)
|
||||||
|
|
||||||
def _min_hitting_set(self, sets: Iterator[Set[T]]) -> int:
|
|
||||||
x_vars: Dict[T, pulp.LpVariable] = dict()
|
|
||||||
next_id = itertools.count()
|
|
||||||
|
|
||||||
problem = pulp.LpProblem("min_hitting_set", pulp.LpMinimize)
|
|
||||||
for (i, xs) in enumerate(sets):
|
|
||||||
for x in xs:
|
|
||||||
if x not in x_vars:
|
|
||||||
id = next(next_id)
|
|
||||||
x_vars[x] = pulp.LpVariable(f'x{id}', cat=pulp.LpBinary)
|
|
||||||
problem += sum(x_vars[x] for x in xs) >= 1
|
|
||||||
|
|
||||||
problem += sum(x_vars.values())
|
|
||||||
problem.solve(pulp.apis.PULP_CBC_CMD(msg=False))
|
|
||||||
return int(sum(v.varValue for v in x_vars.values()))
|
|
||||||
|
|
||||||
def _load_optimal_strategy(self,
|
def _load_optimal_strategy(self,
|
||||||
read_quorums: List[Set[T]],
|
read_quorums: List[Set[T]],
|
||||||
write_quorums: List[Set[T]],
|
write_quorums: List[Set[T]],
|
||||||
|
@ -428,17 +470,12 @@ class QuorumSystem(Generic[T]):
|
||||||
write_capacity[x])
|
write_capacity[x])
|
||||||
problem += (x_load <= l, x)
|
problem += (x_load <= l, x)
|
||||||
|
|
||||||
# print(problem)
|
|
||||||
problem.solve(pulp.apis.PULP_CBC_CMD(msg=False))
|
problem.solve(pulp.apis.PULP_CBC_CMD(msg=False))
|
||||||
return ExplicitStrategy(nodes,
|
return ExplicitStrategy(nodes,
|
||||||
read_quorums,
|
read_quorums,
|
||||||
[v.varValue for v in read_quorum_vars],
|
[v.varValue for v in read_quorum_vars],
|
||||||
write_quorums,
|
write_quorums,
|
||||||
[v.varValue for v in write_quorum_vars])
|
[v.varValue for v in write_quorum_vars])
|
||||||
# for v in read_weights + write_weights:
|
|
||||||
# print(f'{v.name} = {v.varValue}')
|
|
||||||
# return l.varValue
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Strategy(Generic[T]):
|
class Strategy(Generic[T]):
|
||||||
|
@ -536,12 +573,15 @@ class ExplicitStrategy(Strategy[T]):
|
||||||
# g = Node('g')
|
# g = Node('g')
|
||||||
# h = Node('h')
|
# h = Node('h')
|
||||||
# i = Node('i')
|
# i = Node('i')
|
||||||
|
#
|
||||||
# walls = QuorumSystem(reads=a*b + c*d*e)
|
# walls = QuorumSystem(reads=a*b + c*d*e)
|
||||||
# paths = QuorumSystem(reads=a*b + a*c*e + d*e + d*c*b)
|
# paths = QuorumSystem(reads=a*b + a*c*e + d*e + d*c*b)
|
||||||
# maj = QuorumSystem(reads=majority([a, b, c, d, e]))
|
# maj = QuorumSystem(reads=majority([a, b, c, d, e]))
|
||||||
#
|
#
|
||||||
# for qs in [walls, paths, maj]:
|
# for qs in [walls, paths, maj]:
|
||||||
|
# print(qs.dup_free())
|
||||||
|
# print(qs.resilience())
|
||||||
|
|
||||||
# sigma_0 = qs.strategy(read_fraction=0.5)
|
# sigma_0 = qs.strategy(read_fraction=0.5)
|
||||||
# sigma_1 = qs.strategy(read_fraction=0.5, f=1)
|
# sigma_1 = qs.strategy(read_fraction=0.5, f=1)
|
||||||
# print(sigma_0.load(read_fraction=0.5), sigma_1.load(read_fraction=0.5))
|
# print(sigma_0.load(read_fraction=0.5), sigma_1.load(read_fraction=0.5))
|
||||||
|
|
Loading…
Reference in a new issue