Created
February 4, 2022 19:50
-
-
Save thepaul/4822082547a7f1f45eb85975bb7d52c0 to your computer and use it in GitHub Desktop.
Simulating chance of disqualification for a given % of data loss
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
#!/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