From cc288161a45f3621bdaa1aeb35f669d6c418525c Mon Sep 17 00:00:00 2001 From: Arthur O'Dwyer Date: Mon, 17 Apr 2017 01:29:16 -0700 Subject: [PATCH] Major overhaul of temperature logic. Behavioral change. I think the reason the temperature logic was so confused in the old code is because the Java code has a class `Temperature` that is used for graphical display *and* two variables in `formulas` that are used for most of the actual math. But somewhere along the line, some of the code in `formulas.java` started reading from `Temperature.value` as well. So the Python code was just faithfully copying that confusion. The actual abstraction here is a very simple "temperature" object with a stored value. It can be "clamped" to 100.0 for a given period. The only complication is that one of the codelets (the rule-transformer codelet) wants to get access to the "actual value" of the temperature even when it is clamped. The Python rule-transformer codelet also had a bug: it was accidentally setting `temperature.value` on the `temperature` module instead of on the `temperature.temperature` object! This turned some of its behavior into a no-op, for whatever that's worth. Lastly, the calculation of `finalTemperature` in the main program can now report 100.0 if the answer is found while the temperature is clamped. I don't fully understand why this didn't happen in the old code. I've hacked around it with `temperature.last_unclamped_value` for now, but I should TODO FIXME. --- copycat/codeletMethods.py | 16 +++++++--------- copycat/coderack.py | 6 +++--- copycat/copycat.py | 4 ++-- copycat/formulas.py | 17 +++++------------ copycat/temperature.py | 20 +++++++++++++++----- copycat/workspace.py | 10 ++-------- copycat/workspaceFormulas.py | 1 - 7 files changed, 34 insertions(+), 40 deletions(-) diff --git a/copycat/codeletMethods.py b/copycat/codeletMethods.py index 7a8dea5..f06845d 100644 --- a/copycat/codeletMethods.py +++ b/copycat/codeletMethods.py @@ -3,7 +3,7 @@ import logging import random from slipnet import slipnet -import temperature +from temperature import temperature import formulas from workspaceFormulas import chooseDirectedNeighbor from workspaceFormulas import chooseNeighbor @@ -130,7 +130,7 @@ def __slippability(conceptMappings): @codelet('breaker') def breaker(coderack, codelet): - probabilityOfFizzle = (100.0 - formulas.Temperature) / 100.0 + probabilityOfFizzle = (100.0 - temperature.value()) / 100.0 if formulas.coinFlip(probabilityOfFizzle): return # choose a structure at random @@ -804,13 +804,11 @@ def rule_translator(coderack, codelet): if bondDensity > 1.0: bondDensity = 1.0 cutoff = __getCutOff(bondDensity) * 10.0 - assert cutoff >= formulas.actualTemperature - if workspace.rule.buildTranslatedRule(): - workspace.foundAnswer = True - else: - temperature.clampTime = coderack.codeletsRun + 100 - temperature.clamped = True - formulas.Temperature = 100.0 + if cutoff >= temperature.actual_value: + if workspace.rule.buildTranslatedRule(): + workspace.foundAnswer = True + else: + temperature.clampUntil(coderack.codeletsRun + 100) @codelet('bottom-up-correspondence-scout') diff --git a/copycat/coderack.py b/copycat/coderack.py index ae98017..3f0a781 100644 --- a/copycat/coderack.py +++ b/copycat/coderack.py @@ -23,7 +23,7 @@ def probabilityOfPosting(workspace, codeletName): if codeletName == 'breaker': return 1.0 if 'description' in codeletName: - result = (formulas.Temperature / 100.0) ** 2 + result = (temperature.value() / 100.0) ** 2 else: result = workspace.intraStringUnhappiness / 100.0 if 'correspondence' in codeletName: @@ -165,7 +165,7 @@ class Coderack(object): urgency = 3 if codeletName == 'breaker': urgency = 1 - if formulas.Temperature < 25.0 and 'translator' in codeletName: + if temperature.value() < 25.0 and 'translator' in codeletName: urgency = 5 for _ in xrange(howMany): if formulas.coinFlip(probability): @@ -303,7 +303,7 @@ class Coderack(object): def chooseCodeletToRun(self): assert self.codelets - scale = (100.0 - formulas.Temperature + 10.0) / 15.0 + scale = (100.0 - temperature.value() + 10.0) / 15.0 urgsum = sum(codelet.urgency ** scale for codelet in self.codelets) threshold = random.random() * urgsum chosen = self.codelets[0] diff --git a/copycat/copycat.py b/copycat/copycat.py index ad8091b..c5ce81a 100644 --- a/copycat/copycat.py +++ b/copycat/copycat.py @@ -17,7 +17,7 @@ def mainLoop(lastUpdate): workspace.updateEverything() coderack.updateCodelets() slipnet.update() - workspace.updateTemperature() + temperature.update(workspace.getUpdatedTemperature()) lastUpdate = currentTime logging.debug('Number of codelets: %d', len(coderack.codelets)) coderack.chooseAndRunCodelet() @@ -36,7 +36,7 @@ def runTrial(answers): answer = workspace.rule.finalAnswer else: answer = None - finalTemperature = temperature.value + finalTemperature = temperature.last_unclamped_value finalTime = coderack.codeletsRun print 'Answered %s (time %d, final temperature %.1f)' % (answer, finalTime, finalTemperature) answers[answer] = answers.get(answer, {'count': 0, 'tempsum': 0, 'timesum': 0}) diff --git a/copycat/formulas.py b/copycat/formulas.py index c1a4bc1..5bbe894 100644 --- a/copycat/formulas.py +++ b/copycat/formulas.py @@ -4,8 +4,6 @@ import random from temperature import temperature -actualTemperature = Temperature = 100.0 - def selectListPosition(probabilities): total = sum(probabilities) @@ -35,23 +33,18 @@ def weightedAverage(values): def temperatureAdjustedValue(value): - #logging.info('Temperature: %s' % Temperature) - #logging.info('actualTemperature: %s' % actualTemperature) - return value ** (((100.0 - Temperature) / 30.0) + 0.5) + return value ** (((100.0 - temperature.value()) / 30.0) + 0.5) def temperatureAdjustedProbability(value): - if not value or value == 0.5 or not temperature.value: + if value == 0 or value == 0.5 or temperature.value() == 0: return value if value < 0.5: return 1.0 - temperatureAdjustedProbability(1.0 - value) - coldness = 100.0 - temperature.value + coldness = 100.0 - temperature.value() a = math.sqrt(coldness) - b = 10.0 - a - c = b / 100 - d = c * (1.0 - (1.0 - value)) # as said the java - e = (1.0 - value) + d - f = 1.0 - e + c = (10 - a) / 100 + f = (c + 1) * value return max(f, 0.5) diff --git a/copycat/temperature.py b/copycat/temperature.py index afc33f3..8c9d749 100644 --- a/copycat/temperature.py +++ b/copycat/temperature.py @@ -1,19 +1,29 @@ -import logging - class Temperature(object): def __init__(self): - self.value = 100.0 + self.actual_value = 100.0 + self.last_unclamped_value = 100.0 self.clamped = True self.clampTime = 30 def update(self, value): - self.value = value + self.last_unclamped_value = value + if self.clamped: + self.actual_value = 100.0 + else: + self.actual_value = value + + def clampUntil(self, when): + self.clamped = True + self.clampTime = when + # but do not modify self.actual_value until someone calls update() def tryUnclamp(self, currentTime): if self.clamped and currentTime >= self.clampTime: - logging.info('unclamp temperature at %d', currentTime) self.clamped = False + def value(self): + return 100.0 if self.clamped else self.actual_value + temperature = Temperature() diff --git a/copycat/workspace.py b/copycat/workspace.py index bd77adb..58d4e40 100644 --- a/copycat/workspace.py +++ b/copycat/workspace.py @@ -1,7 +1,6 @@ import logging import formulas -from temperature import temperature from workspaceString import WorkspaceString @@ -85,19 +84,14 @@ class Workspace(object): self.initial.updateIntraStringUnhappiness() self.target.updateIntraStringUnhappiness() - def updateTemperature(self): + def getUpdatedTemperature(self): self.assessTemperature() ruleWeakness = 100.0 if self.rule: self.rule.updateStrength() ruleWeakness = 100.0 - self.rule.totalStrength values = ((self.totalUnhappiness, 0.8), (ruleWeakness, 0.2)) - above_actual_temperature = formulas.actualTemperature + 0.001 - formulas.actualTemperature = formulas.weightedAverage(values) - if temperature.clamped: - formulas.actualTemperature = 100.0 - formulas.Temperature = formulas.actualTemperature - temperature.update(formulas.Temperature) + return formulas.weightedAverage(values) def numberOfUnrelatedObjects(self): """A list of all objects in the workspace with >= 1 open bond slots""" diff --git a/copycat/workspaceFormulas.py b/copycat/workspaceFormulas.py index 64067f0..f40910d 100644 --- a/copycat/workspaceFormulas.py +++ b/copycat/workspaceFormulas.py @@ -1,7 +1,6 @@ import logging from workspace import workspace -from temperature import temperature from slipnet import slipnet import formulas