From b90bae25847220088a0af9b2b5bfd742ddaa22dd Mon Sep 17 00:00:00 2001 From: LSaldyt Date: Wed, 4 Oct 2017 15:20:59 -0600 Subject: [PATCH] Adds automatic running, formula tests --- copycat/copycat.py | 45 ++++++++++++++++---------- copycat/temperature.py | 73 +++++++++++++++++++++++++++++++++++++++--- multi-run.py | 33 +++++++++++++++++++ 3 files changed, 129 insertions(+), 22 deletions(-) create mode 100755 multi-run.py diff --git a/copycat/copycat.py b/copycat/copycat.py index 8602fc7..64adbef 100644 --- a/copycat/copycat.py +++ b/copycat/copycat.py @@ -4,7 +4,6 @@ from .slipnet import Slipnet from .temperature import Temperature from .workspace import Workspace - class Reporter(object): """Do-nothing base class for defining new reporter types""" def report_answer(self, answer): @@ -66,24 +65,36 @@ class Copycat(object): self.reporter.report_answer(answer) return answer - def run(self, initial, modified, target, iterations): + def run(self, initial, modified, target, iterations, testAdjFormulas=False): self.workspace.resetWithStrings(initial, modified, target) - answers = {} - for i in range(iterations): - answer = self.runTrial() - d = answers.setdefault(answer['answer'], { - 'count': 0, - 'sumtemp': 0, # TODO: use entropy - 'sumtime': 0 - }) - d['count'] += 1 - d['sumtemp'] += answer['temp'] # TODO: use entropy - d['sumtime'] += answer['time'] + if testAdjFormulas: + formulas = self.temperature.adj_formulas() + else: + formulas = ['original'] - for answer, d in answers.items(): - d['avgtemp'] = d.pop('sumtemp') / d['count'] - d['avgtime'] = d.pop('sumtime') / d['count'] - return answers + formulaList = [] + for formula in formulas: + self.temperature.useAdj(formula) + answers = {} + for i in range(iterations): + answer = self.runTrial() + d = answers.setdefault(answer['answer'], { + 'count': 0, + 'sumtemp': 0, # TODO: use entropy + 'sumtime': 0 + }) + d['count'] += 1 + d['sumtemp'] += answer['temp'] # TODO: use entropy + d['sumtime'] += answer['time'] + + for answer, d in answers.items(): + d['avgtemp'] = d.pop('sumtemp') / d['count'] + d['avgtime'] = d.pop('sumtime') / d['count'] + formulaList.append(answers) + if not testAdjFormulas: + return formulaList[0] + else: + return formulaList def run_forever(self, initial, modified, target): self.workspace.resetWithStrings(initial, modified, target) diff --git a/copycat/temperature.py b/copycat/temperature.py index 485b9cb..8538a3e 100644 --- a/copycat/temperature.py +++ b/copycat/temperature.py @@ -1,8 +1,44 @@ import math +# Alternate formulas for getAdjustedProbability + +def _original(temp, prob): + if prob == 0 or prob == 0.5 or temp == 0: + return prob + if prob < 0.5: + return 1.0 - _original(temp, 1.0 - prob) + coldness = 100.0 - temp + a = math.sqrt(coldness) + c = (10 - a) / 100 + f = (c + 1) * prob + return max(f, 0.5) + +def _entropy(temp, prob): + if prob == 0 or prob == 0.5 or temp == 0: + return prob + if prob < 0.5: + return 1.0 - _original(temp, 1.0 - prob) + coldness = 100.0 - temp + a = math.sqrt(coldness) + c = (10 - a) / 100 + f = (c + 1) * prob + return -f * math.log2(f) + +def _inverse_prob(temp, prob): + # Temperature, potentially clamped + iprob = 1 - prob + return (temp / 100) * iprob + ((100 - temp) / 100) * prob + + class Temperature(object): def __init__(self): self.reset() + self.adjustmentType = 'original' + self._adjustmentFormulas = { + 'original' : _original, + 'entropy' : _entropy, + 'inverse' : _inverse_prob + } def reset(self): self.actual_value = 100.0 @@ -32,7 +68,19 @@ class Temperature(object): def getAdjustedValue(self, value): return value ** (((100.0 - self.value()) / 30.0) + 0.5) - """ + def getAdjustedProbability(self, value): + temp = self.value() + prob = value + return self._adjustmentFormulas[self.adjustmentType](temp, prob) + + def useAdj(self, adj): + print('Changing to adjustment formula {}'.format(adj)) + self.adjustmentType = adj + + def adj_formulas(self): + return self._adjustmentFormulas.keys() + + ''' def getAdjustedProbability(self, value): if value == 0 or value == 0.5 or self.value() == 0: return value @@ -43,9 +91,20 @@ class Temperature(object): c = (10 - a) / 100 f = (c + 1) * value return max(f, 0.5) - """ def getAdjustedProbability(self, value): + if value == 0 or value == 0.5 or self.value() == 0: + return value + if value < 0.5: + return 1.0 - self.getAdjustedProbability(1.0 - value) + coldness = 100.0 - self.value() + a = math.sqrt(coldness) + c = (10 - a) / 100 + f = (c + 1) * value + # return max(f, 0.5) + # return max(f, 0.0) + # return (0 + (-f * math.log2(f))) + return -f * math.log2(f) # TODO: use entropy """ @@ -291,9 +350,13 @@ class Temperature(object): # This function does precisely what I think the original lisp comment describes, but provides crappy results # return (temp / 200) * iprob + ((200 - temp) / 200) * prob # However, this version preforms much better: + # Essentially, it weights probabilities towards their inverses when temperature is higher, and leaves them unaffected when it is lower. # Some statistical analysis is needed of course return (temp / 100) * iprob + ((100 - temp) / 100) * prob - ''' + + # This will give only xyd answers: + #return 1 - (temp / 100) * iprob + ((100 - temp) / 100) * prob + """ lucas@infinity:~/projects/personal/copycat$ ./main.py abc abd xyz --iterations 10 --plot Answered wyz (time 3865, final temperature 13.9) Answered wyz (time 8462, final temperature 10.8) @@ -324,7 +387,7 @@ class Temperature(object): Answered ijjkkl (time 1064, final temperature 50.6) ijjlll: 3 (avg time 3051.0, avg temp 16.1) ijjkkl: 7 (avg time 1540.4, avg temp 42.6) - ''' + """ # unparameterized version #curvedProb = (temp / 100) * iprob + (cold / 100) * prob @@ -340,7 +403,7 @@ class Temperature(object): # beta = 1.0 # return ((alpha + temp / 100) * iprob + (beta + cold / 100) * prob) / (alpha + beta) - ''' + """ # A scaling factor (between 0 and infinity), based on temperature (i.e. 100/coldness) if temp == 100: # Avoid dividing by zero factor = float('inf') diff --git a/multi-run.py b/multi-run.py new file mode 100755 index 0000000..fc3cbfc --- /dev/null +++ b/multi-run.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +import argparse, logging +from copycat import Copycat, Reporter, plot_answers, save_answers + +class SimpleReporter(Reporter): + """Reports results from a single run.""" + def report_answer(self, answer): + """Self-explanatory code.""" + print('Answered %s (time %d, final temperature %.1f)' % ( + answer['answer'], answer['time'], answer['temp'], + )) + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--iterations', type=int, default=1, help='Run the given case this many times.') + options = parser.parse_args() + + copycat = Copycat(reporter=SimpleReporter()) + + with open('input/reduced_problems.csv', 'r') as infile: + for line in infile: + line = line.replace('\n', '') + a, b, c = line.split(',') + answerList = copycat.run(a, b, c, options.iterations, True) + for answers in answerList: + for answer, d in sorted(iter(answers.items()), key=lambda kv: kv[1]['avgtemp']): + print('%s: %d (avg time %.1f, avg temp %.1f)' % (answer, d['count'], d['avgtime'], d['avgtemp'])) + + #filename = 'output/{}-{}-{}.csv'.format(a, b, c) + #save_answers(answers, filename) + +if __name__ == '__main__': + main()