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.
This commit is contained in:
Arthur O'Dwyer
2017-04-17 01:29:16 -07:00
parent 6a56fdd898
commit cc288161a4
7 changed files with 34 additions and 40 deletions

View File

@ -3,7 +3,7 @@ import logging
import random import random
from slipnet import slipnet from slipnet import slipnet
import temperature from temperature import temperature
import formulas import formulas
from workspaceFormulas import chooseDirectedNeighbor from workspaceFormulas import chooseDirectedNeighbor
from workspaceFormulas import chooseNeighbor from workspaceFormulas import chooseNeighbor
@ -130,7 +130,7 @@ def __slippability(conceptMappings):
@codelet('breaker') @codelet('breaker')
def breaker(coderack, codelet): def breaker(coderack, codelet):
probabilityOfFizzle = (100.0 - formulas.Temperature) / 100.0 probabilityOfFizzle = (100.0 - temperature.value()) / 100.0
if formulas.coinFlip(probabilityOfFizzle): if formulas.coinFlip(probabilityOfFizzle):
return return
# choose a structure at random # choose a structure at random
@ -804,13 +804,11 @@ def rule_translator(coderack, codelet):
if bondDensity > 1.0: if bondDensity > 1.0:
bondDensity = 1.0 bondDensity = 1.0
cutoff = __getCutOff(bondDensity) * 10.0 cutoff = __getCutOff(bondDensity) * 10.0
assert cutoff >= formulas.actualTemperature if cutoff >= temperature.actual_value:
if workspace.rule.buildTranslatedRule(): if workspace.rule.buildTranslatedRule():
workspace.foundAnswer = True workspace.foundAnswer = True
else: else:
temperature.clampTime = coderack.codeletsRun + 100 temperature.clampUntil(coderack.codeletsRun + 100)
temperature.clamped = True
formulas.Temperature = 100.0
@codelet('bottom-up-correspondence-scout') @codelet('bottom-up-correspondence-scout')

View File

@ -23,7 +23,7 @@ def probabilityOfPosting(workspace, codeletName):
if codeletName == 'breaker': if codeletName == 'breaker':
return 1.0 return 1.0
if 'description' in codeletName: if 'description' in codeletName:
result = (formulas.Temperature / 100.0) ** 2 result = (temperature.value() / 100.0) ** 2
else: else:
result = workspace.intraStringUnhappiness / 100.0 result = workspace.intraStringUnhappiness / 100.0
if 'correspondence' in codeletName: if 'correspondence' in codeletName:
@ -165,7 +165,7 @@ class Coderack(object):
urgency = 3 urgency = 3
if codeletName == 'breaker': if codeletName == 'breaker':
urgency = 1 urgency = 1
if formulas.Temperature < 25.0 and 'translator' in codeletName: if temperature.value() < 25.0 and 'translator' in codeletName:
urgency = 5 urgency = 5
for _ in xrange(howMany): for _ in xrange(howMany):
if formulas.coinFlip(probability): if formulas.coinFlip(probability):
@ -303,7 +303,7 @@ class Coderack(object):
def chooseCodeletToRun(self): def chooseCodeletToRun(self):
assert self.codelets 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) urgsum = sum(codelet.urgency ** scale for codelet in self.codelets)
threshold = random.random() * urgsum threshold = random.random() * urgsum
chosen = self.codelets[0] chosen = self.codelets[0]

View File

@ -17,7 +17,7 @@ def mainLoop(lastUpdate):
workspace.updateEverything() workspace.updateEverything()
coderack.updateCodelets() coderack.updateCodelets()
slipnet.update() slipnet.update()
workspace.updateTemperature() temperature.update(workspace.getUpdatedTemperature())
lastUpdate = currentTime lastUpdate = currentTime
logging.debug('Number of codelets: %d', len(coderack.codelets)) logging.debug('Number of codelets: %d', len(coderack.codelets))
coderack.chooseAndRunCodelet() coderack.chooseAndRunCodelet()
@ -36,7 +36,7 @@ def runTrial(answers):
answer = workspace.rule.finalAnswer answer = workspace.rule.finalAnswer
else: else:
answer = None answer = None
finalTemperature = temperature.value finalTemperature = temperature.last_unclamped_value
finalTime = coderack.codeletsRun finalTime = coderack.codeletsRun
print 'Answered %s (time %d, final temperature %.1f)' % (answer, finalTime, finalTemperature) print 'Answered %s (time %d, final temperature %.1f)' % (answer, finalTime, finalTemperature)
answers[answer] = answers.get(answer, {'count': 0, 'tempsum': 0, 'timesum': 0}) answers[answer] = answers.get(answer, {'count': 0, 'tempsum': 0, 'timesum': 0})

View File

@ -4,8 +4,6 @@ import random
from temperature import temperature from temperature import temperature
actualTemperature = Temperature = 100.0
def selectListPosition(probabilities): def selectListPosition(probabilities):
total = sum(probabilities) total = sum(probabilities)
@ -35,23 +33,18 @@ def weightedAverage(values):
def temperatureAdjustedValue(value): def temperatureAdjustedValue(value):
#logging.info('Temperature: %s' % Temperature) return value ** (((100.0 - temperature.value()) / 30.0) + 0.5)
#logging.info('actualTemperature: %s' % actualTemperature)
return value ** (((100.0 - Temperature) / 30.0) + 0.5)
def temperatureAdjustedProbability(value): 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 return value
if value < 0.5: if value < 0.5:
return 1.0 - temperatureAdjustedProbability(1.0 - value) return 1.0 - temperatureAdjustedProbability(1.0 - value)
coldness = 100.0 - temperature.value coldness = 100.0 - temperature.value()
a = math.sqrt(coldness) a = math.sqrt(coldness)
b = 10.0 - a c = (10 - a) / 100
c = b / 100 f = (c + 1) * value
d = c * (1.0 - (1.0 - value)) # as said the java
e = (1.0 - value) + d
f = 1.0 - e
return max(f, 0.5) return max(f, 0.5)

View File

@ -1,19 +1,29 @@
import logging
class Temperature(object): class Temperature(object):
def __init__(self): def __init__(self):
self.value = 100.0 self.actual_value = 100.0
self.last_unclamped_value = 100.0
self.clamped = True self.clamped = True
self.clampTime = 30 self.clampTime = 30
def update(self, value): 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): def tryUnclamp(self, currentTime):
if self.clamped and currentTime >= self.clampTime: if self.clamped and currentTime >= self.clampTime:
logging.info('unclamp temperature at %d', currentTime)
self.clamped = False self.clamped = False
def value(self):
return 100.0 if self.clamped else self.actual_value
temperature = Temperature() temperature = Temperature()

View File

@ -1,7 +1,6 @@
import logging import logging
import formulas import formulas
from temperature import temperature
from workspaceString import WorkspaceString from workspaceString import WorkspaceString
@ -85,19 +84,14 @@ class Workspace(object):
self.initial.updateIntraStringUnhappiness() self.initial.updateIntraStringUnhappiness()
self.target.updateIntraStringUnhappiness() self.target.updateIntraStringUnhappiness()
def updateTemperature(self): def getUpdatedTemperature(self):
self.assessTemperature() self.assessTemperature()
ruleWeakness = 100.0 ruleWeakness = 100.0
if self.rule: if self.rule:
self.rule.updateStrength() self.rule.updateStrength()
ruleWeakness = 100.0 - self.rule.totalStrength ruleWeakness = 100.0 - self.rule.totalStrength
values = ((self.totalUnhappiness, 0.8), (ruleWeakness, 0.2)) values = ((self.totalUnhappiness, 0.8), (ruleWeakness, 0.2))
above_actual_temperature = formulas.actualTemperature + 0.001 return formulas.weightedAverage(values)
formulas.actualTemperature = formulas.weightedAverage(values)
if temperature.clamped:
formulas.actualTemperature = 100.0
formulas.Temperature = formulas.actualTemperature
temperature.update(formulas.Temperature)
def numberOfUnrelatedObjects(self): def numberOfUnrelatedObjects(self):
"""A list of all objects in the workspace with >= 1 open bond slots""" """A list of all objects in the workspace with >= 1 open bond slots"""

View File

@ -1,7 +1,6 @@
import logging import logging
from workspace import workspace from workspace import workspace
from temperature import temperature
from slipnet import slipnet from slipnet import slipnet
import formulas import formulas