diff --git a/copycat/codeletMethods.py b/copycat/codeletMethods.py index 91ae140..98d8e92 100644 --- a/copycat/codeletMethods.py +++ b/copycat/codeletMethods.py @@ -1,6 +1,5 @@ import inspect import logging -import random import formulas from workspaceFormulas import chooseDirectedNeighbor @@ -37,7 +36,9 @@ def __showWhichStringObjectIsFrom(structure): #print 'object chosen = %s from %s string' % (structure, whence) -def __getScoutSource(workspace, slipnode, relevanceMethod, typeName): +def __getScoutSource(ctx, slipnode, relevanceMethod, typeName): + random = ctx.random + workspace = ctx.workspace initialRelevance = relevanceMethod(workspace.initial, slipnode) targetRelevance = relevanceMethod(workspace.target, slipnode) initialUnhappiness = workspace.initial.intraStringUnhappiness @@ -47,11 +48,9 @@ def __getScoutSource(workspace, slipnode, relevanceMethod, typeName): logging.info('target : relevance = %d, unhappiness=%d', targetRelevance, int(targetUnhappiness)) string = workspace.initial - relevances = initialRelevance + targetRelevance - unhappinesses = initialUnhappiness + targetUnhappiness - randomized = random.random() * (relevances + unhappinesses) initials = initialRelevance + initialUnhappiness - if randomized > initials: + targets = targetRelevance + targetUnhappiness + if random.weighted_greater_than(targets, initials): string = workspace.target logging.info('target string selected: %s for %s', workspace.target, typeName) @@ -76,7 +75,9 @@ def __getDescriptors(bondFacet, source, destination): def __structureVsStructure(structure1, weight1, structure2, weight2): + """Return true if the first structure comes out stronger than the second.""" ctx = structure1.ctx + random = ctx.random temperature = ctx.temperature structure1.updateStrength() structure2.updateStrength() @@ -84,9 +85,7 @@ def __structureVsStructure(structure1, weight1, structure2, weight2): structure1.totalStrength * weight1) weightedStrength2 = temperature.getAdjustedValue( structure2.totalStrength * weight2) - rhs = (weightedStrength1 + weightedStrength2) * random.random() - logging.info('%d > %d', weightedStrength1, rhs) - return weightedStrength1 > rhs + return random.weighted_greater_than(weightedStrength1, weightedStrength2) def __fight(structure, structureWeight, incompatibles, incompatibleWeight): @@ -115,21 +114,23 @@ def __fightIncompatibles(incompatibles, structure, name, def __slippability(ctx, conceptMappings): + random = ctx.random temperature = ctx.temperature for mapping in conceptMappings: slippiness = mapping.slippability() / 100.0 probabilityOfSlippage = temperature.getAdjustedProbability(slippiness) - if formulas.coinFlip(probabilityOfSlippage): + if random.coinFlip(probabilityOfSlippage): return True return False @codelet('breaker') def breaker(ctx, codelet): + random = ctx.random temperature = ctx.temperature workspace = ctx.workspace probabilityOfFizzle = (100.0 - temperature.value()) / 100.0 - if formulas.coinFlip(probabilityOfFizzle): + if random.coinFlip(probabilityOfFizzle): return # choose a structure at random structures = [s for s in workspace.structures if @@ -146,18 +147,28 @@ def breaker(ctx, codelet): for structure in breakObjects: breakProbability = temperature.getAdjustedProbability( structure.totalStrength / 100.0) - if formulas.coinFlip(breakProbability): + if random.coinFlip(breakProbability): return for structure in breakObjects: structure.break_the_structure() -def similarPropertyLinks(slip_node, temperature): +def chooseRelevantDescriptionByActivation(ctx, workspaceObject): + random = ctx.random + descriptions = workspaceObject.relevantDescriptions() + weights = [description.descriptor.activation + for description in descriptions] + return random.weighted_choice(descriptions, weights) + + +def similarPropertyLinks(ctx, slip_node): + random = ctx.random + temperature = ctx.temperature result = [] for slip_link in slip_node.propertyLinks: association = slip_link.degreeOfAssociation() / 100.0 probability = temperature.getAdjustedProbability(association) - if formulas.coinFlip(probability): + if random.coinFlip(probability): result += [slip_link] return result @@ -165,19 +176,21 @@ def similarPropertyLinks(slip_node, temperature): @codelet('bottom-up-description-scout') def bottom_up_description_scout(ctx, codelet): coderack = ctx.coderack - temperature = ctx.temperature + random = ctx.random workspace = ctx.workspace chosenObject = chooseUnmodifiedObject('totalSalience', workspace.objects) assert chosenObject __showWhichStringObjectIsFrom(chosenObject) - description = formulas.chooseRelevantDescriptionByActivation(chosenObject) + # choose relevant description by activation + descriptions = chosenObject.relevantDescriptions() + weights = [d.descriptor.activation for d in descriptions] + description = random.weighted_choice(descriptions, weights) assert description - sliplinks = similarPropertyLinks(description.descriptor, temperature) + sliplinks = similarPropertyLinks(ctx, description.descriptor) assert sliplinks - values = [sliplink.degreeOfAssociation() * sliplink.destination.activation + weights = [sliplink.degreeOfAssociation() * sliplink.destination.activation for sliplink in sliplinks] - i = formulas.selectListPosition(values) - chosen = sliplinks[i] + chosen = random.weighted_choice(sliplinks, weights) chosenProperty = chosen.destination coderack.proposeDescription(chosenObject, chosenProperty.category(), chosenProperty, codelet) @@ -186,6 +199,7 @@ def bottom_up_description_scout(ctx, codelet): @codelet('top-down-description-scout') def top_down_description_scout(ctx, codelet): coderack = ctx.coderack + random = ctx.random workspace = ctx.workspace descriptionType = codelet.arguments[0] chosenObject = chooseUnmodifiedObject('totalSalience', workspace.objects) @@ -193,9 +207,8 @@ def top_down_description_scout(ctx, codelet): __showWhichStringObjectIsFrom(chosenObject) descriptions = chosenObject.getPossibleDescriptions(descriptionType) assert descriptions and len(descriptions) - values = [n.activation for n in descriptions] - i = formulas.selectListPosition(values) - chosenProperty = descriptions[i] + weights = [n.activation for n in descriptions] + chosenProperty = random.weighted_choice(descriptions, weights) coderack.proposeDescription(chosenObject, chosenProperty.category(), chosenProperty, codelet) @@ -203,13 +216,14 @@ def top_down_description_scout(ctx, codelet): @codelet('description-strength-tester') def description_strength_tester(ctx, codelet): coderack = ctx.coderack + random = ctx.random temperature = ctx.temperature description = codelet.arguments[0] description.descriptor.buffer = 100.0 description.updateStrength() strength = description.totalStrength probability = temperature.getAdjustedProbability(strength / 100.0) - assert formulas.coinFlip(probability) + assert random.coinFlip(probability) coderack.newCodelet('description-builder', codelet, strength) @@ -255,6 +269,7 @@ def bottom_up_bond_scout(ctx, codelet): @codelet('rule-scout') def rule_scout(ctx, codelet): coderack = ctx.coderack + random = ctx.random slipnet = ctx.slipnet temperature = ctx.temperature workspace = ctx.workspace @@ -292,15 +307,13 @@ def rule_scout(ctx, codelet): newList += [node] objectList = newList # surely this should be += # "union of this and distinguishing descriptors" - assert objectList and len(objectList) + assert objectList # use conceptual depth to choose a description - valueList = [] - for node in objectList: - depth = node.conceptualDepth - value = temperature.getAdjustedValue(depth) - valueList += [value] - i = formulas.selectListPosition(valueList) - descriptor = objectList[i] + weights = [ + temperature.getAdjustedValue(node.conceptualDepth) + for node in objectList + ] + descriptor = random.weighted_choice(objectList, weights) # choose the relation (change the letmost object to "successor" or "d" objectList = [] if changed.replacement.relation: @@ -308,13 +321,11 @@ def rule_scout(ctx, codelet): objectList += [changed.replacement.objectFromModified.getDescriptor( slipnet.letterCategory)] # use conceptual depth to choose a relation - valueList = [] - for node in objectList: - depth = node.conceptualDepth - value = temperature.getAdjustedValue(depth) - valueList += [value] - i = formulas.selectListPosition(valueList) - relation = objectList[i] + weights = [ + temperature.getAdjustedValue(node.conceptualDepth) + for node in objectList + ] + relation = random.weighted_choice(objectList, weights) coderack.proposeRule(slipnet.letterCategory, descriptor, slipnet.letter, relation, codelet) @@ -322,16 +333,18 @@ def rule_scout(ctx, codelet): @codelet('rule-strength-tester') def rule_strength_tester(ctx, codelet): coderack = ctx.coderack + random = ctx.random temperature = ctx.temperature rule = codelet.arguments[0] rule.updateStrength() probability = temperature.getAdjustedProbability(rule.totalStrength / 100.0) - if formulas.coinFlip(probability): + if random.coinFlip(probability): coderack.newCodelet('rule-builder', codelet, rule.totalStrength, rule) @codelet('replacement-finder') def replacement_finder(ctx, codelet): + random = ctx.random slipnet = ctx.slipnet workspace = ctx.workspace # choose random letter in initial string @@ -374,10 +387,9 @@ def replacement_finder(ctx, codelet): def top_down_bond_scout__category(ctx, codelet): coderack = ctx.coderack slipnet = ctx.slipnet - workspace = ctx.workspace logging.info('top_down_bond_scout__category') category = codelet.arguments[0] - source = __getScoutSource(workspace, category, formulas.localBondCategoryRelevance, + source = __getScoutSource(ctx, category, formulas.localBondCategoryRelevance, 'bond') destination = chooseNeighbor(source) logging.info('source: %s, destination: %s', source, destination) @@ -406,9 +418,8 @@ def top_down_bond_scout__category(ctx, codelet): def top_down_bond_scout__direction(ctx, codelet): coderack = ctx.coderack slipnet = ctx.slipnet - workspace = ctx.workspace direction = codelet.arguments[0] - source = __getScoutSource(workspace, + source = __getScoutSource(ctx, direction, formulas.localDirectionCategoryRelevance, 'bond') destination = chooseDirectedNeighbor(source, direction) assert destination @@ -427,6 +438,7 @@ def top_down_bond_scout__direction(ctx, codelet): @codelet('bond-strength-tester') def bond_strength_tester(ctx, codelet): coderack = ctx.coderack + random = ctx.random temperature = ctx.temperature bond = codelet.arguments[0] __showWhichStringObjectIsFrom(bond) @@ -434,7 +446,7 @@ def bond_strength_tester(ctx, codelet): strength = bond.totalStrength probability = temperature.getAdjustedProbability(strength / 100.0) logging.info('bond strength = %d for %s', strength, bond) - assert formulas.coinFlip(probability) + assert random.coinFlip(probability) bond.facet.buffer = 100.0 bond.sourceDescriptor.buffer = 100.0 bond.destinationDescriptor.buffer = 100.0 @@ -489,12 +501,12 @@ def bond_builder(ctx, codelet): @codelet('top-down-group-scout--category') def top_down_group_scout__category(ctx, codelet): coderack = ctx.coderack + random = ctx.random slipnet = ctx.slipnet - workspace = ctx.workspace groupCategory = codelet.arguments[0] category = groupCategory.getRelatedNode(slipnet.bondCategory) assert category - source = __getScoutSource(workspace, category, formulas.localBondCategoryRelevance, + source = __getScoutSource(ctx, category, formulas.localBondCategoryRelevance, 'group') assert source and not source.spansString() if source.leftmost: @@ -502,11 +514,10 @@ def top_down_group_scout__category(ctx, codelet): elif source.rightmost: direction = slipnet.left else: - activations = [slipnet.left.activation, slipnet.right.activation] - if not formulas.selectListPosition(activations): - direction = slipnet.left - else: - direction = slipnet.right + direction = random.weighted_choice( + [slipnet.left, slipnet.right], + [slipnet.left.activation, slipnet.right.activation] + ) if direction == slipnet.left: firstBond = source.leftBond else: @@ -522,7 +533,7 @@ def top_down_group_scout__category(ctx, codelet): group = Group(source.string, slipnet.samenessGroup, None, slipnet.letterCategory, [source], []) probability = group.singleLetterGroupProbability() - if formulas.coinFlip(probability): + if random.coinFlip(probability): coderack.proposeSingleLetterGroup(source, codelet) return direction = firstBond.directionCategory @@ -574,10 +585,10 @@ def top_down_group_scout__category(ctx, codelet): @codelet('top-down-group-scout--direction') def top_down_group_scout__direction(ctx, codelet): coderack = ctx.coderack + random = ctx.random slipnet = ctx.slipnet - workspace = ctx.workspace direction = codelet.arguments[0] - source = __getScoutSource(workspace, direction, + source = __getScoutSource(ctx, direction, formulas.localDirectionCategoryRelevance, 'direction') logging.info('source chosen = %s', source) @@ -587,12 +598,10 @@ def top_down_group_scout__direction(ctx, codelet): elif source.rightmost: mydirection = slipnet.left else: - activations = [slipnet.left.activation] - activations += [slipnet.right.activation] - if not formulas.selectListPosition(activations): - mydirection = slipnet.left - else: - mydirection = slipnet.right + mydirection = random.weighted_choice( + [slipnet.left, slipnet.right], + [slipnet.left.activation, slipnet.right.activation] + ) if mydirection == slipnet.left: firstBond = source.leftBond else: @@ -669,9 +678,10 @@ def top_down_group_scout__direction(ctx, codelet): @codelet('group-scout--whole-string') def group_scout__whole_string(ctx, codelet): coderack = ctx.coderack + random = ctx.random slipnet = ctx.slipnet workspace = ctx.workspace - if formulas.coinFlip(): + if random.coinFlip(): string = workspace.target logging.info('target string selected: %s', workspace.target) else: @@ -713,6 +723,7 @@ def group_scout__whole_string(ctx, codelet): @codelet('group-strength-tester') def group_strength_tester(ctx, codelet): coderack = ctx.coderack + random = ctx.random slipnet = ctx.slipnet temperature = ctx.temperature # update strength value of the group @@ -721,7 +732,7 @@ def group_strength_tester(ctx, codelet): group.updateStrength() strength = group.totalStrength probability = temperature.getAdjustedProbability(strength / 100.0) - if formulas.coinFlip(probability): + if random.coinFlip(probability): # it is strong enough - post builder & activate nodes group.groupCategory.getRelatedNode(slipnet.bondCategory).buffer = 100.0 if group.directionCategory: @@ -821,29 +832,23 @@ def rule_builder(ctx, codelet): workspace.buildRule(rule) -def __getCutOff(density): - if density > 0.8: - distribution = [5.0, 150.0, 5.0, 2.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0] - elif density > 0.6: - distribution = [2.0, 5.0, 150.0, 5.0, 2.0, 1.0, 1.0, 1.0, 1.0, 1.0] - elif density > 0.4: - distribution = [1.0, 2.0, 5.0, 150.0, 5.0, 2.0, 1.0, 1.0, 1.0, 1.0] - elif density > 0.2: - distribution = [1.0, 1.0, 2.0, 5.0, 150.0, 5.0, 2.0, 1.0, 1.0, 1.0] +def __getCutoffWeights(bondDensity): + if bondDensity > 0.8: + return [5.0, 150.0, 5.0, 2.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0] + elif bondDensity > 0.6: + return [2.0, 5.0, 150.0, 5.0, 2.0, 1.0, 1.0, 1.0, 1.0, 1.0] + elif bondDensity > 0.4: + return [1.0, 2.0, 5.0, 150.0, 5.0, 2.0, 1.0, 1.0, 1.0, 1.0] + elif bondDensity > 0.2: + return [1.0, 1.0, 2.0, 5.0, 150.0, 5.0, 2.0, 1.0, 1.0, 1.0] else: - distribution = [1.0, 1.0, 1.0, 2.0, 5.0, 150.0, 5.0, 2.0, 1.0, 1.0] - stop = sum(distribution) * random.random() - total = 0.0 - for i in xrange(len(distribution)): - total += distribution[i] - if total >= stop: - return i + 1 - return len(distribution) + return [1.0, 1.0, 1.0, 2.0, 5.0, 150.0, 5.0, 2.0, 1.0, 1.0] @codelet('rule-translator') def rule_translator(ctx, codelet): coderack = ctx.coderack + random = ctx.random temperature = ctx.temperature workspace = ctx.workspace assert workspace.rule @@ -854,9 +859,9 @@ def rule_translator(ctx, codelet): len(workspace.target.bonds)) nearlyTotalLength = len(workspace.initial) + len(workspace.target) - 2 bondDensity = numberOfBonds / nearlyTotalLength - if bondDensity > 1.0: - bondDensity = 1.0 - cutoff = __getCutOff(bondDensity) * 10.0 + bondDensity = min(bondDensity, 1.0) + weights = __getCutoffWeights(bondDensity) + cutoff = 10.0 * random.weighted_choice(range(1, 11), weights) if cutoff >= temperature.actual_value: if workspace.rule.buildTranslatedRule(): workspace.foundAnswer = True @@ -905,24 +910,19 @@ def bottom_up_correspondence_scout(ctx, codelet): conceptMappings, flipTargetObject, codelet) -def chooseSlipnodeByConceptualDepth(slip_nodes, temperature): - if not slip_nodes: - return None - depths = [temperature.getAdjustedValue(n.conceptualDepth) for n in slip_nodes] - i = formulas.selectListPosition(depths) - return slip_nodes[i] - - @codelet('important-object-correspondence-scout') def important_object_correspondence_scout(ctx, codelet): coderack = ctx.coderack + random = ctx.random slipnet = ctx.slipnet temperature = ctx.temperature workspace = ctx.workspace objectFromInitial = chooseUnmodifiedObject('relativeImportance', workspace.initial.objects) descriptors = objectFromInitial.relevantDistinguishingDescriptors() - slipnode = chooseSlipnodeByConceptualDepth(descriptors, temperature) + # choose descriptor by conceptual depth + weights = [temperature.getAdjustedValue(n.conceptualDepth) for n in descriptors] + slipnode = random.weighted_choice(descriptors, weights) assert slipnode initialDescriptor = slipnode for mapping in workspace.slippages(): @@ -971,6 +971,7 @@ def important_object_correspondence_scout(ctx, codelet): @codelet('correspondence-strength-tester') def correspondence_strength_tester(ctx, codelet): coderack = ctx.coderack + random = ctx.random temperature = ctx.temperature workspace = ctx.workspace correspondence = codelet.arguments[0] @@ -984,7 +985,7 @@ def correspondence_strength_tester(ctx, codelet): correspondence.updateStrength() strength = correspondence.totalStrength probability = temperature.getAdjustedProbability(strength / 100.0) - if formulas.coinFlip(probability): + if random.coinFlip(probability): # activate some concepts for mapping in correspondence.conceptMappings: mapping.initialDescriptionType.buffer = 100.0 diff --git a/copycat/coderack.py b/copycat/coderack.py index a97f39f..330e5b4 100644 --- a/copycat/coderack.py +++ b/copycat/coderack.py @@ -1,9 +1,7 @@ import math import logging -import random import codeletMethods -import formulas from bond import Bond from codelet import Codelet from correspondence import Correspondence @@ -95,6 +93,7 @@ class Coderack(object): return result def howManyToPost(self, codeletName): + random = self.ctx.random workspace = self.ctx.workspace if codeletName == 'breaker' or 'description' in codeletName: return 1 @@ -117,9 +116,9 @@ class Coderack(object): number = workspace.numberOfUnreplacedObjects() if 'correspondence' in codeletName: number = workspace.numberOfUncorrespondingObjects() - if number < formulas.blur(2.0): + if number < random.sqrtBlur(2.0): return 1 - if number < formulas.blur(4.0): + if number < random.sqrtBlur(4.0): return 2 return 3 @@ -131,6 +130,7 @@ class Coderack(object): self.removeCodelet(oldCodelet) def postTopDownCodelets(self): + random = self.ctx.random slipnet = self.ctx.slipnet for node in slipnet.slipnodes: #logging.info('Trying slipnode: %s' % node.get_name()) @@ -141,7 +141,7 @@ class Coderack(object): probability = self.probabilityOfPosting(codeletName) howMany = self.howManyToPost(codeletName) for _ in xrange(howMany): - if not formulas.coinFlip(probability): + if not random.coinFlip(probability): continue urgency = getUrgencyBin( node.activation * node.conceptualDepth / 100.0) @@ -164,6 +164,7 @@ class Coderack(object): self.__postBottomUpCodelets('breaker') def __postBottomUpCodelets(self, codeletName): + random = self.ctx.random temperature = self.ctx.temperature probability = self.probabilityOfPosting(codeletName) howMany = self.howManyToPost(codeletName) @@ -173,7 +174,7 @@ class Coderack(object): if temperature.value() < 25.0 and 'translator' in codeletName: urgency = 5 for _ in xrange(howMany): - if formulas.coinFlip(probability): + if random.coinFlip(probability): codelet = Codelet(codeletName, urgency, self.codeletsRun) self.post(codelet) @@ -264,20 +265,13 @@ class Coderack(object): def chooseOldCodelet(self): # selects an old codelet to remove from the coderack # more likely to select lower urgency codelets - if not len(self.codelets): - return None urgencies = [] for codelet in self.codelets: urgency = ((self.codeletsRun - codelet.birthdate) * (7.5 - codelet.urgency)) urgencies += [urgency] - threshold = random.random() * sum(urgencies) - sumOfUrgencies = 0.0 - for i in xrange(len(self.codelets)): - sumOfUrgencies += urgencies[i] - if sumOfUrgencies > threshold: - return self.codelets[i] - return self.codelets[0] + random = self.ctx.random + return random.weighted_choice(self.codelets, urgencies) def postInitialCodelets(self): workspace = self.ctx.workspace @@ -300,22 +294,12 @@ class Coderack(object): self.run(codelet) def chooseCodeletToRun(self): + random = self.ctx.random temperature = self.ctx.temperature assert self.codelets 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] - urgencySum = 0.0 - - for codelet in self.codelets: - urgencySum += codelet.urgency ** scale - if urgencySum > threshold: - chosen = codelet - break + chosen = random.weighted_choice(self.codelets, [codelet.urgency ** scale for codelet in self.codelets]) self.removeCodelet(chosen) - logging.info('chosen codelet\n\t%s, urgency = %s', - chosen.name, chosen.urgency) return chosen def run(self, codelet): diff --git a/copycat/context.py b/copycat/context.py index 6317af4..10efe2a 100644 --- a/copycat/context.py +++ b/copycat/context.py @@ -3,10 +3,11 @@ import logging class Context(object): def __init__(self): - self.temperature = None self.coderack = None - self.workspace = None + self.random = None self.slipnet = None + self.temperature = None + self.workspace = None def mainLoop(self, lastUpdate): currentTime = self.coderack.codeletsRun @@ -15,7 +16,7 @@ class Context(object): if currentTime >= lastUpdate + 15: self.workspace.updateEverything() self.coderack.updateCodelets() - self.slipnet.update() + self.slipnet.update(self.random) self.temperature.update(self.workspace.getUpdatedTemperature()) lastUpdate = currentTime logging.debug('Number of codelets: %d', len(self.coderack.codelets)) diff --git a/copycat/copycat.py b/copycat/copycat.py index 24b1d06..d1adc58 100644 --- a/copycat/copycat.py +++ b/copycat/copycat.py @@ -1,16 +1,16 @@ -import logging - -from workspace import Workspace +from coderack import Coderack +from randomness import Randomness from slipnet import Slipnet from temperature import Temperature -from coderack import Coderack +from workspace import Workspace from context import context +context.coderack = Coderack(context) +context.random = Randomness(42) context.slipnet = Slipnet() context.temperature = Temperature() -context.coderack = Coderack(context) context.workspace = Workspace(context) diff --git a/copycat/formulas.py b/copycat/formulas.py index 14671dd..aa7e976 100644 --- a/copycat/formulas.py +++ b/copycat/formulas.py @@ -1,25 +1,6 @@ -import math -import random - from conceptMapping import ConceptMapping -def selectListPosition(probabilities): - total = sum(probabilities) - #logging.info('total: %s' % total) - r = random.random() - stopPosition = total * r - #logging.info('stopPosition: %s' % stopPosition) - total = 0 - i = 0 - for probability in probabilities: - total += probability - if total > stopPosition: - return i - i += 1 - return 0 - - def weightedAverage(values): total = 0.0 totalWeights = 0.0 @@ -31,27 +12,6 @@ def weightedAverage(values): return total / totalWeights -def coinFlip(chance=0.5): - return random.random() < chance - - -def blur(value): - root = math.sqrt(value) - if coinFlip(): - return value + root - return value - root - - -def chooseRelevantDescriptionByActivation(workspaceObject): - descriptions = workspaceObject.relevantDescriptions() - if not descriptions: - return None - activations = [description.descriptor.activation - for description in descriptions] - i = selectListPosition(activations) - return descriptions[i] - - def __relevantCategory(objekt, slipnode): return objekt.rightBond and objekt.rightBond.category == slipnode diff --git a/copycat/group.py b/copycat/group.py index c182f67..6882636 100644 --- a/copycat/group.py +++ b/copycat/group.py @@ -1,5 +1,4 @@ import logging -import random from workspaceObject import WorkspaceObject import formulas @@ -73,9 +72,10 @@ class Group(WorkspaceObject): def add_length_description_category(self): #check whether or not to add length description category + random = self.ctx.random slipnet = self.ctx.slipnet probability = self.lengthDescriptionProbability() - if random.random() < probability: + if random.coinFlip(probability): length = len(self.objectList) if length < 6: self.addDescription(slipnet.length, diff --git a/copycat/main.py b/copycat/main.py index 7e1616e..feb1a68 100644 --- a/copycat/main.py +++ b/copycat/main.py @@ -1,11 +1,8 @@ """Run the copycat program""" import logging -import random import sys -random.seed(42) - import copycat diff --git a/copycat/randomness.py b/copycat/randomness.py new file mode 100644 index 0000000..9011b3b --- /dev/null +++ b/copycat/randomness.py @@ -0,0 +1,41 @@ +import bisect +import math +import random + +def accumulate(iterable): + total = 0 + for v in iterable: + total += v + yield total + +class Randomness(object): + def __init__(self, seed=None): + self.rng = random.Random(seed) + + def coinFlip(self, p=0.5): + return self.rng.random() < p + + def choice(self, seq): + return self.rng.choice(seq) + + def weighted_choice(self, seq, weights): + if not seq: + # Many callers rely on this behavior. + return None + else: + cum_weights = list(accumulate(weights)) + total = cum_weights[-1] + return seq[bisect.bisect_left(cum_weights, self.rng.random() * total)] + + def weighted_greater_than(self, first, second): + total = first + second + if total == 0: + return False + return self.coinFlip(float(first) / total) + + def sqrtBlur(self, value): + # This is exceedingly dumb, but it matches the Java code. + root = math.sqrt(value) + if self.coinFlip(): + return value + root + return value - root diff --git a/copycat/slipnet.py b/copycat/slipnet.py index 8372025..930e03f 100644 --- a/copycat/slipnet.py +++ b/copycat/slipnet.py @@ -32,7 +32,7 @@ class Slipnet(object): for node in self.initiallyClampedSlipnodes: node.clampHigh() - def update(self): + def update(self, random): logging.debug('slipnet.update()') self.numberOfUpdates += 1 if self.numberOfUpdates == 50: @@ -44,7 +44,7 @@ class Slipnet(object): node.spread_activation() for node in self.slipnodes: node.addBuffer() - node.jump() + node.jump(random) node.buffer = 0.0 def isDistinguishingDescriptor(self, descriptor): diff --git a/copycat/slipnode.py b/copycat/slipnode.py index 23a0f70..ec42bc4 100644 --- a/copycat/slipnode.py +++ b/copycat/slipnode.py @@ -1,10 +1,5 @@ import math import logging -import random - - -def full_activation(): - return 100 def jump_threshold(): @@ -65,11 +60,7 @@ class Slipnode(object): def fully_active(self): """Whether this node has full activation""" float_margin = 0.00001 - return self.activation > full_activation() - float_margin - - def activate_fully(self): - """Make this node fully active""" - self.activation = full_activation() + return self.activation > 100.0 - float_margin def bondDegreeOfAssociation(self): linkLength = self.intrinsicLinkLength @@ -112,7 +103,8 @@ class Slipnode(object): If no linked node is found, return None """ - if relation == self.slipnet.identity: + slipnet = self.slipnet + if relation == slipnet.identity: return self destinations = [l.destination for l in self.outgoingLinks if l.label == relation] @@ -125,9 +117,10 @@ class Slipnode(object): If it does not exist return None """ + slipnet = self.slipnet result = None if self == destination: - result = self.slipnet.identity + result = slipnet.identity else: for link in self.outgoingLinks: if link.destination == destination: @@ -149,17 +142,14 @@ class Slipnode(object): self.activation += self.buffer self.activation = min(max(0, self.activation), 100) - def can_jump(self): + def jump(self, random): if self.activation <= jump_threshold(): - return False + return if self.clamped: - return False + return value = (self.activation / 100.0) ** 3 - return random.random() < value - - def jump(self): - if self.can_jump(): - self.activate_fully() + if random.coinFlip(value): + self.activation = 100.0 def get_name(self): if len(self.name) == 1: diff --git a/copycat/workspaceFormulas.py b/copycat/workspaceFormulas.py index 2e596d0..891eb6a 100644 --- a/copycat/workspaceFormulas.py +++ b/copycat/workspaceFormulas.py @@ -1,34 +1,29 @@ import logging -import formulas - -def __chooseObjectFromList(temperature, objects, attribute): - if not objects: - return None +def __chooseObjectFromList(ctx, objects, attribute): + random = ctx.random + temperature = ctx.temperature weights = [ temperature.getAdjustedValue( getattr(o, attribute) ) for o in objects ] - i = formulas.selectListPosition(weights) - return objects[i] + return random.weighted_choice(objects, weights) def chooseUnmodifiedObject(attribute, inObjects): from context import context as ctx - temperature = ctx.temperature workspace = ctx.workspace objects = [o for o in inObjects if o.string != workspace.modified] if not len(objects): print 'no objects available in initial or target strings' - return __chooseObjectFromList(temperature, objects, attribute) + return __chooseObjectFromList(ctx, objects, attribute) def chooseNeighbor(source): from context import context as ctx - temperature = ctx.temperature workspace = ctx.workspace objects = [] for objekt in workspace.objects: @@ -38,7 +33,7 @@ def chooseNeighbor(source): objects += [objekt] elif source.leftIndex == objekt.rightIndex + 1: objects += [objekt] - return __chooseObjectFromList(temperature, objects, "intraStringSalience") + return __chooseObjectFromList(ctx, objects, "intraStringSalience") def chooseDirectedNeighbor(source, direction): @@ -53,7 +48,6 @@ def chooseDirectedNeighbor(source, direction): def __chooseLeftNeighbor(source): from context import context as ctx - temperature = ctx.temperature workspace = ctx.workspace objects = [] for o in workspace.objects: @@ -64,32 +58,29 @@ def __chooseLeftNeighbor(source): else: logging.info('%s is not on left of %s', o, source) logging.info('Number of left objects: %s', len(objects)) - return __chooseObjectFromList(temperature, objects, 'intraStringSalience') + return __chooseObjectFromList(ctx, objects, 'intraStringSalience') def __chooseRightNeighbor(source): from context import context as ctx - temperature = ctx.temperature workspace = ctx.workspace objects = [o for o in workspace.objects if o.string == source.string and o.leftIndex == source.rightIndex + 1] - return __chooseObjectFromList(temperature, objects, 'intraStringSalience') + return __chooseObjectFromList(ctx, objects, 'intraStringSalience') def chooseBondFacet(source, destination): from context import context as ctx + random = ctx.random slipnet = ctx.slipnet sourceFacets = [d.descriptionType for d in source.descriptions if d.descriptionType in slipnet.bondFacets] bondFacets = [d.descriptionType for d in destination.descriptions if d.descriptionType in sourceFacets] - if not bondFacets: - return None supports = [__supportForDescriptionType(f, source.string) for f in bondFacets] - i = formulas.selectListPosition(supports) - return bondFacets[i] + return random.weighted_choice(bondFacets, supports) def __supportForDescriptionType(descriptionType, string):