Skip to content

Instantly share code, notes, and snippets.

@thepaul
Created February 4, 2022 19:50
Show Gist options
  • Save thepaul/4822082547a7f1f45eb85975bb7d52c0 to your computer and use it in GitHub Desktop.
Save thepaul/4822082547a7f1f45eb85975bb7d52c0 to your computer and use it in GitHub Desktop.
Simulating chance of disqualification for a given % of data loss
#!/usr/bin/env python3
#
# This is meant to act as a double-check of math used elsewhere to calculate the chance
# of disqualification given a level of data loss on a storage node. Instead of calculating
# the chance algorithmically, this performs a simulation using the specified parameters
# and reports how many nodes under the given circumstances would actually be disqualified.
import argparse
import csv
import math
import random
import sys
parser = argparse.ArgumentParser()
parser.add_argument(
'-d', '--data_loss', type=float, default=0.03,
help='Chance of a failed audit (default: %(default).2f)')
parser.add_argument(
'-r', '--rounds', type=int, default=1000,
help='Number of rounds per simulation (default: %(default)s)')
parser.add_argument(
'-x', '--runs', type=int, default=100,
help='Number of simulations to run (default: %(default)s)')
parser.add_argument(
'-l', '--lambda', type=float, default=0.95, dest='lambda_',
help='Value of the lambda parameter in the beta distribution for reputation calculations (default: %(default).2f)')
parser.add_argument(
'-w', '--weight', type=float, default=1,
help='Value of the weight parameter in the reputation calculations (default: %(default)g)')
parser.add_argument(
'--v_succ', type=float, default=1,
help='Value of the v_succ parameter in the reputation calculations (default: %(default)g)')
parser.add_argument(
'--v_fail', type=float, default=-1,
help='Value of the v_fail parameter in the reputation calculations (default: %(default)g)')
parser.add_argument(
'-q', '--dq_thresh', type=float, default=0.60,
help='The disqualification threshold (default: %(default)g)')
parser.add_argument(
'-a', '--init_alpha', type=float, default=1,
help='Initial value of the alpha parameter in the beta distribution for reputation calculations (default: %(default)g)')
parser.add_argument(
'-b', '--init_beta', type=float, default=0,
help='Initial value of the beta parameter in the beta distribution for reputation calculations (default: %(default)g)')
parser.add_argument(
'-g', '--grace', type=int, default=0,
help='Number of rounds to be safe from disqualification (default: %(default)s)')
parser.add_argument(
'--csv', action='store_true', default=False,
help='Enable CSV output')
def update_reputation(is_success, alpha, beta, lamb, w, v_succ, v_fail):
v = v_fail
if is_success:
v = v_succ
new_alpha = lamb*alpha + w*(1+v)/2
new_beta = lamb*beta + w*(1-v)/2
return new_alpha, new_beta
def rep(alpha, beta):
return alpha / (alpha+beta)
def sim(rounds, data_loss, lamb, weight, v_succ, v_fail, dq_thresh, init_alpha, init_beta, grace):
alpha = init_alpha
beta = init_beta
failures = []
reputation = []
rounds_to_dq = None
for r in range(rounds):
is_succ = random.random() > data_loss
alpha, beta = update_reputation(is_succ, alpha, beta, lamb, weight, v_succ, v_fail)
if not is_succ:
failures.append(r)
new_rep = rep(alpha, beta)
reputation.append(new_rep)
if new_rep < dq_thresh and r >= grace:
rounds_to_dq = r
break
return reputation, failures, rounds_to_dq
def run(data_loss, rounds=1000, runs=100, **args):
runs_dqd = 0
rounds_to_dqs = []
for _ in range(runs):
r, f, rounds_to_dq = sim(rounds, data_loss, **args)
if rounds_to_dq is not None:
runs_dqd += 1
rounds_to_dqs.append(rounds_to_dq)
mean_rounds_to_dq = 0
if len(rounds_to_dqs) > 0:
mean_rounds_to_dq = sum(rounds_to_dqs) / len(rounds_to_dqs)
return runs_dqd / runs, mean_rounds_to_dq
if __name__ == "__main__":
opts = parser.parse_args()
xchance, mean_rounds_to_dq = run(
opts.data_loss,
rounds=opts.rounds,
runs=opts.runs,
lamb=opts.lambda_,
weight=opts.weight,
v_succ=opts.v_succ,
v_fail=opts.v_fail,
dq_thresh=opts.dq_thresh,
init_alpha=opts.init_alpha,
init_beta=opts.init_beta,
grace=opts.grace,
)
if opts.csv:
w = csv.writer(sys.stdout)
w.writerow((opts.data_loss, opts.lambda_, opts.dq_thresh, opts.grace, xchance, mean_rounds_to_dq))
else:
print(f"with {opts.data_loss * 100:.2f}% data loss, {xchance * 100:.2f}% of runs hit dq (after {mean_rounds_to_dq:.2f} rounds on avg)")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment