implement basic functionality
This commit is contained in:
parent
d6b89f4a2b
commit
021d8724cd
|
@ -1,2 +1,98 @@
|
||||||
|
from typing import Dict, Optional, Tuple
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
# import numpy.typing as npt
|
||||||
|
import numpy.random as npr
|
||||||
|
from scipy.stats import f_oneway
|
||||||
|
|
||||||
|
|
||||||
|
class ClocktowerManager:
|
||||||
|
__slots__ = ['bounds', 'data', 'rng', 'significance', 'best_guess', 'min_correct_tries']
|
||||||
|
bounds: Tuple[int, int]
|
||||||
|
data: Dict[int, np.ndarray]
|
||||||
|
rng: npr.Generator
|
||||||
|
significance: float
|
||||||
|
min_correct_tries: int
|
||||||
|
best_guess: Optional[int]
|
||||||
|
|
||||||
|
def __init__(self, bounds: Tuple[int, int] = (0, 256),
|
||||||
|
significance: float = 0.01,
|
||||||
|
min_correct_tries: int = 5,
|
||||||
|
rng: Optional[npr.Generator] = None) -> None:
|
||||||
|
self.bounds = bounds
|
||||||
|
self.data = {k: np.array([], dtype=np.float64) for k in range(bounds[0], bounds[1])}
|
||||||
|
self.significance = significance
|
||||||
|
if rng is None:
|
||||||
|
rng = npr.default_rng()
|
||||||
|
self.rng = rng
|
||||||
|
self.min_correct_tries = min_correct_tries
|
||||||
|
self.best_guess = None
|
||||||
|
|
||||||
|
def next_guess(self) -> Optional[int]:
|
||||||
|
if (self.best_guess is not None and
|
||||||
|
len(self.data[self.best_guess]) >= self.min_correct_tries):
|
||||||
|
return None
|
||||||
|
|
||||||
|
min_count = min([len(x) for x in self.data.values()])
|
||||||
|
if min_count < 3 or self.rng.uniform(0, 1) > 0.5:
|
||||||
|
min_keys = [k for k in self.data.keys() if len(self.data[k]) == min_count]
|
||||||
|
return self.rng.choice(min_keys)
|
||||||
|
elif self.best_guess is not None:
|
||||||
|
return self.best_guess
|
||||||
|
else:
|
||||||
|
means = {k: v.mean() for k, v in self.data.items()}
|
||||||
|
return max(means.items(), key=lambda x: x[1])[0]
|
||||||
|
|
||||||
|
def update(self, guess: int, value: float) -> None:
|
||||||
|
if guess not in self.data.keys():
|
||||||
|
return
|
||||||
|
|
||||||
|
self.data[guess] = np.append(self.data[guess], np.array([value], dtype=np.float64))
|
||||||
|
inputs = [x for x in self.data.values() if len(x) > 0]
|
||||||
|
if len(inputs) < 2:
|
||||||
|
return
|
||||||
|
if max([len(v) for v in inputs]) < 2:
|
||||||
|
return
|
||||||
|
res = f_oneway(*inputs)
|
||||||
|
print("results", res)
|
||||||
|
if res.pvalue <= self.significance:
|
||||||
|
print("significant!")
|
||||||
|
self.best_guess = max(self.data.items(), key=lambda v: v[1].mean())[0]
|
||||||
|
else:
|
||||||
|
self.best_guess = None
|
||||||
|
|
||||||
|
def get_best_guess(self) -> int:
|
||||||
|
if self.best_guess is None:
|
||||||
|
raise Exception("guess not available")
|
||||||
|
return self.best_guess
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
pass
|
gen = npr.default_rng()
|
||||||
|
stdev = 7059
|
||||||
|
u0 = 500000
|
||||||
|
u1 = 506046
|
||||||
|
# stdev = 1000
|
||||||
|
# u0 = 500000
|
||||||
|
# u1 = 506046
|
||||||
|
correct_guess = 0x42
|
||||||
|
|
||||||
|
def sample(guess):
|
||||||
|
return gen.normal(u1 if guess == correct_guess else u0, stdev)
|
||||||
|
|
||||||
|
mgr = ClocktowerManager()
|
||||||
|
num_guesses = 0
|
||||||
|
while True:
|
||||||
|
num_guesses += 1
|
||||||
|
guess = mgr.next_guess()
|
||||||
|
if guess is None:
|
||||||
|
print("answer", hex(mgr.get_best_guess()))
|
||||||
|
break
|
||||||
|
print("guessing", hex(guess), "(guess", num_guesses, ")")
|
||||||
|
value = sample(guess)
|
||||||
|
mgr.update(guess, value)
|
||||||
|
if len(mgr.data[correct_guess]) > 0:
|
||||||
|
print("means", mgr.data[correct_guess].mean(),
|
||||||
|
np.hstack([v for k, v in mgr.data.items() if k != correct_guess]).mean())
|
||||||
|
|
||||||
|
print("took", num_guesses, "guesses")
|
||||||
|
|
6
setup.py
6
setup.py
|
@ -14,7 +14,11 @@ setup(
|
||||||
author_email='haskal@awoo.systems',
|
author_email='haskal@awoo.systems',
|
||||||
license='AGPLv3',
|
license='AGPLv3',
|
||||||
packages=['clocktower'],
|
packages=['clocktower'],
|
||||||
install_requires=[],
|
install_requires=[
|
||||||
|
"numpy",
|
||||||
|
"scipy",
|
||||||
|
# "matplotlib"
|
||||||
|
],
|
||||||
python_requires=">=3.9",
|
python_requires=">=3.9",
|
||||||
classifiers=[
|
classifiers=[
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
|
|
Loading…
Reference in New Issue