From 5462c033abd6bc681edb3691b58ac4cfee43e369 Mon Sep 17 00:00:00 2001 From: J Alan Brogan Date: Fri, 26 Oct 2012 17:35:08 +0100 Subject: [PATCH] Initial addition of Python scripts --- README.md | 6 +- bond.py | 182 ++++++++++ codelet.py | 10 + codeletMethods.py | 802 ++++++++++++++++++++++++++++++++++++++++++ coderack.py | 338 ++++++++++++++++++ coderackPressure.py | 133 +++++++ conceptMapping.py | 156 ++++++++ copycat.py | 68 ++++ correspondence.py | 189 ++++++++++ description.py | 56 +++ formulas.py | 146 ++++++++ group.py | 237 +++++++++++++ grouprun.py | 14 + letter.py | 48 +++ replacement.py | 9 + rule.py | 134 +++++++ sliplink.py | 28 ++ slipnet.py | 252 +++++++++++++ slipnode.py | 161 +++++++++ temperature.py | 23 ++ unready.py | 2 + utils.py | 140 ++++++++ workspace.py | 158 +++++++++ workspaceFormulas.py | 160 +++++++++ workspaceObject.py | 226 ++++++++++++ workspaceString.py | 80 +++++ workspaceStructure.py | 37 ++ 27 files changed, 3794 insertions(+), 1 deletion(-) create mode 100644 bond.py create mode 100644 codelet.py create mode 100644 codeletMethods.py create mode 100644 coderack.py create mode 100644 coderackPressure.py create mode 100644 conceptMapping.py create mode 100644 copycat.py create mode 100644 correspondence.py create mode 100644 description.py create mode 100644 formulas.py create mode 100644 group.py create mode 100644 grouprun.py create mode 100644 letter.py create mode 100644 replacement.py create mode 100644 rule.py create mode 100644 sliplink.py create mode 100644 slipnet.py create mode 100644 slipnode.py create mode 100644 temperature.py create mode 100644 unready.py create mode 100644 utils.py create mode 100644 workspace.py create mode 100644 workspaceFormulas.py create mode 100644 workspaceObject.py create mode 100644 workspaceString.py create mode 100644 workspaceStructure.py diff --git a/README.md b/README.md index f54e39d..2323998 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,8 @@ co.py.cat ========= -An implementation of the Hofstadter copycat algorithm \ No newline at end of file +An implementation of the Hofstadter [copycat](https://en.wikipedia.org/wiki/Copycat_%28software%29) algorithm + +This implementation is a copycat of Scott Boland's [Java implementation](http://itee.uq.edu.au/~scottb/_Copycat/), but re-written into Python. It's not a direct translation - but based on his code. I did not carry over the GUI, as this version can more usefully be run from command line, or imported for use by other Python scripts. + +In cases where I could not grok the Java implementation easily or directly I took ideas from the [LISP implementation](http://web.cecs.pdx.edu/~mm/how-to-get-copycat.html), or directly from [Melanie Mitchell](https://en.wikipedia.org/wiki/Melanie_Mitchell)'s "[Analogy-Making as Perception](http://www.amazon.com/Analogy-Making-Perception-Computer-Melanie-Mitchell/dp/0262132893/ref=tmm_hrd_title_0?ie=UTF8&qid=1351269085&sr=1-3)" diff --git a/bond.py b/bond.py new file mode 100644 index 0000000..e850601 --- /dev/null +++ b/bond.py @@ -0,0 +1,182 @@ +from workspaceStructure import WorkspaceStructure +from slipnet import slipnet +from workspace import workspace + +class Bond(WorkspaceStructure): + def __init__(self,source, destination, bondCategory, bondFacet, sourceDescriptor, destinationDescriptor): + WorkspaceStructure.__init__(self) + self.source = source + self.string = self.source.string + self.destination = destination + self.leftObject = self.source + self.rightObject = self.destination + self.directionCategory = slipnet.right + if self.source.leftStringPosition > self.destination.rightStringPosition: + self.leftObject = self.destination + self.rightObject = self.source + self.directionCategory = slipnet.left + self.facet = bondFacet + self.sourceDescriptor = sourceDescriptor + self.destinationDescriptor = destinationDescriptor + self.category = bondCategory + + self.destinationIsOnRight = self.destination == self.rightObject + self.bidirectional = self.sourceDescriptor == self.destinationDescriptor + if self.bidirectional: + self.directionCategory = None + + def flippedVersion(self): + """ + + """ + return Bond( + self.destination, self.get_source(), self.category.getRelatedNode(slipnet.opposite), + self.facet, self.destinationDescriptor, self.sourceDescriptor + ) + + def __repr__(self): + return '' % self.__str__() + + def __str__(self): + return '%s bond between %s and %s' % ( self.category.name, self.leftObject, self.rightObject) + + def buildBond(self): + workspace.structures += [ self ] + self.string.bonds += [ self ] + self.category.buffer = 100.0 + if self.directionCategory: + self.directionCategory.buffer = 100.0 + self.leftObject.rightBond = self + self.rightObject.leftBond = self + self.leftObject.bonds += [ self ] + self.rightObject.bonds += [ self ] + + def break_the_structure(self): + self.breakBond() + + def breakBond(self): + if self in workspace.structures: + workspace.structures.remove(self) + if self in self.string.bonds: + self.string.bonds.remove(self) + self.leftObject.rightBond = None + self.rightObject.leftBond = None + if self in self.leftObject.bonds: + self.leftObject.bonds.remove(self) + if self in self.rightObject.bonds: + self.rightObject.bonds.remove(self) + + def getIncompatibleCorrespondences(self): + # returns a list of correspondences that are incompatible with + # self bond + incompatibles = [] + if self.leftObject.leftmost and self.leftObject.correspondence: + correspondence = self.leftObject.correspondence + if self.string == workspace.initial: + objekt = self.leftObject.correspondence.objectFromTarget + else: + objekt = self.leftObject.correspondence.objectFromInitial + if objekt.leftmost and objekt.rightBond: + if objekt.rightBond.directionCategory and objekt.rightBond.directionCategory != self.directionCategory: + incompatibles += [ correspondence ] + if self.rightObject.rightmost and self.rightObject.correspondence: + correspondence = self.rightObject.correspondence + if self.string == workspace.initial: + objekt = self.rightObject.correspondence.objectFromTarget + else: + objekt = self.rightObject.correspondence.objectFromInitial + if objekt.rightmost and objekt.leftBond: + if objekt.leftBond.directionCategory and objekt.leftBond.directionCategory != self.directionCategory: + incompatibles += [ correspondence ] + return incompatibles + + def updateInternalStrength(self): + # bonds between objects of same type(ie. letter or group) are + # stronger than bonds between different types + sourceGap = self.get_source().leftStringPosition != self.get_source().rightStringPosition + destinationGap = self.destination.leftStringPosition != self.destination.rightStringPosition + if sourceGap == destinationGap: + memberCompatibility = 1.0 + else: + memberCompatibility = 0.7 + # letter category bonds are stronger + if self.facet == slipnet.letterCategory: + facetFactor = 1.0 + else: + facetFactor = 0.7 + strength = min(100.0,memberCompatibility * facetFactor * self.category.bondDegreeOfAssociation()) + self.internalStrength = strength + + def updateExternalStrength(self): + self.externalStrength = 0.0 + supporters = self.numberOfLocalSupportingBonds() + if supporters > 0.0: + density = self.localDensity() / 100.0 + density = density ** 0.5 * 100.0 + supportFactor = 0.6 ** (1.0 / supporters ** 3) + supportFactor = max(1.0,supportFactor) + strength = supportFactor * density + self.externalStrength = strength + + def numberOfLocalSupportingBonds(self): + return len([ b for b in self.string.bonds if b.string == self.get_source().string and + self.leftObject.letterDistance(b.leftObject) != 0 and + self.rightObject.letterDistance(b.rightObject) != 0 and + self.category == b.category and + self.directionCategory == b.directionCategory ]) + + def sameCategories(self,other): + return self.category == other.category and self.directionCategory == other.directionCategory + + def myEnds(self,object1,object2): + if self.get_source() == object1 and self.destination == object2: + return True + return self.get_source() == object2 and self.destination == object1 + + def localDensity(self): + # returns a rough measure of the density in the string + # of the same bond-category and the direction-category of + # the given bond + slotSum = supportSum = 0.0 + for object1 in workspace.objects: + if object1.string == self.string: + for object2 in workspace.objects: + if object1.beside(object2): + slotSum += 1.0 + for bond in self.string.bonds: + if bond != self and self.sameCategories(bond) and self.myEnds(object1,object2): + supportSum += 1.0 + if slotSum == 0.0: + return 0.0 + return 100.0 * supportSum / slotSum + + def sameNeighbours(self,other): + if self.leftObject == other.leftObject: + return True + return self.rightObject == other.rightObject + + def getIncompatibleBonds(self): + return [ b for b in self.string.bonds if self.sameNeighbours(b) ] + + def get_source(self): + return self.source + + def set_source(self, value): + self.source = value + +def possibleGroupBonds(bondCategory, directionCategory, bondFacet, bonds): + result = [] + for bond in bonds: + if bond.category == bondCategory and bond.directionCategory == directionCategory: + result += [ bond ] + else: + # a modified bond might be made + if bondCategory == slipnet.sameness: + return None # a different bond cannot be made here + if bond.category == bondCategory or bond.directionCategory == directionCategory: + return None # a different bond cannot be made here + if bond.category == slipnet.sameness: + return None + bond = Bond(bond.destination, bond.get_source(), bondCategory, bondFacet, bond.destinationDescriptor, bond.sourceDescriptor) + result += [ bond ] + return result diff --git a/codelet.py b/codelet.py new file mode 100644 index 0000000..59fb8c1 --- /dev/null +++ b/codelet.py @@ -0,0 +1,10 @@ +class Codelet(object): + def __init__(self, name, urgency, timestamp): + self.name = name + self.urgency = urgency + self.arguments = [] + self.pressure = None + self.timeStamp = timestamp + + def __repr__(self): + return '' % self.name diff --git a/codeletMethods.py b/codeletMethods.py new file mode 100644 index 0000000..138f96d --- /dev/null +++ b/codeletMethods.py @@ -0,0 +1,802 @@ +#import utils + +from coderack import coderack +from workspaceObject import WorkspaceObject +from letter import Letter +from replacement import Replacement +from formulas import * +from workspaceFormulas import * +from group import Group +from bond import Bond, possibleGroupBonds +from correspondence import Correspondence + +# some methods common to the codelets +def __showWhichStringObjectIsFrom(structure): + if not structure: + return + whence = 'other' + if isinstance(structure,WorkspaceObject): + whence='target' + if structure.string == workspace.initial: + whence='initial' + print 'object chosen = %s from %s string' % ( structure, whence ) + +def __getScoutSource(slipnode,relevanceMethod,typeName): + initialRelevance = relevanceMethod(workspace.initial,slipnode) + targetRelevance = relevanceMethod(workspace.target,slipnode) + initialUnhappiness = workspace.initial.intraStringUnhappiness + targetUnhappiness = workspace.target.intraStringUnhappiness + logging.info('initial : relevance = %d, unhappiness=%d' % (initialRelevance,int(initialUnhappiness)) ) + logging.info('target : relevance = %d, unhappiness=%d' % (targetRelevance,int(targetUnhappiness)) ) + string = workspace.initial + if utils.random() * (initialRelevance + initialUnhappiness+targetRelevance+targetUnhappiness) > (initialRelevance + initialUnhappiness): + string = workspace.target + logging.info('target string selected: %s for %s' % (workspace.target,typeName)) + else: + logging.info('initial string selected: %s for %s' % (workspace.initial,typeName)) + source = chooseUnmodifiedObject('intraStringSalience',string.objects) + return source + +def __getBondFacet(source,destination): + bondFacet = chooseBondFacet(source,destination) + assert bondFacet + return bondFacet + +def __getDescriptors(bondFacet,source,destination): + sourceDescriptor = source.getDescriptor(bondFacet) + destinationDescriptor = destination.getDescriptor(bondFacet) + assert sourceDescriptor and destinationDescriptor + return sourceDescriptor, destinationDescriptor + +def __allOppositeMappings(mappings): + return len([ m for m in mappings if m.label != slipnet.opposite ]) == 0 + +def __structureVsStructure(structure1,weight1,structure2,weight2): + structure1.updateStrength() + structure2.updateStrength() + weightedStrength1 = temperatureAdjustedValue(structure1.totalStrength * weight1) + weightedStrength2 = temperatureAdjustedValue(structure2.totalStrength * weight2) + rhs = (weightedStrength1 + weightedStrength2) * utils.random() + logging.info('%d > %d' % (weightedStrength1,rhs)) + return weightedStrength1 > rhs + +def __fightItOut(structure, structureWeight, incompatibles, incompatibleWeight): + if not (incompatibles and len(incompatibles)): + return True + for incompatible in incompatibles: + if not __structureVsStructure(structure, structureWeight, incompatible, incompatibleWeight): + logging.info('lost fight with %s' % incompatible) + return False + logging.info('won fight with %s' % incompatible) + return True + +def __fightIncompatibles(incompatibles,structure,name,structureWeight,incompatibleWeight): + if len(incompatibles): + if __fightItOut(structure,structureWeight,incompatibles,incompatibleWeight): + logging.info('broke the %s' % name) + return True + logging.info('failed to break %s: Fizzle' % name) + return False + logging.info('no incompatible %s' % name) + return True + +def __slippability(conceptMappings): + for mapping in conceptMappings: + slippiness = mapping.slipability() / 100.0 + probabilityOfSlippage = temperatureAdjustedProbability(slippiness) + if formulas.coinFlip(probabilityOfSlippage): + return True + return False + +# start the actual codelets +def breaker(): + probabilityOfFizzle = (100.0-Temperature)/100.0 + assert not coinFlip(probabilityOfFizzle) + # choose a structure at random + structures = [ s for s in workspace.structures if + isinstance(s,Group) or + isinstance(s,Bond) or + isinstance(s,Correspondence) ] + assert len(structures) + structure = utils.choice(structures) + __showWhichStringObjectIsFrom(structure) + breakObjects = [ structure ] + if isinstance(structure,Bond): + if structure.source.group and structure.source.group == structure.destination.group: + breakObjects += [ structure.source.group ] + # try to break all objects + for structure in breakObjects: + breakProbability = temperatureAdjustedProbability(structure.totalStrength/100.0) + if coinFlip(breakProbability): + return + for structure in breakObjects: + structure.break_the_structure() + +def bottom_up_description_scout(codelet): + chosenObject = chooseUnmodifiedObject('totalSalience',workspace.objects) + assert chosenObject + __showWhichStringObjectIsFrom(chosenObject) + description = chooseRelevantDescriptionByActivation(chosenObject) + assert description + sliplinks = similarPropertyLinks(description.descriptor) + assert sliplinks and len(sliplinks) + values = [ sliplink.degreeOfAssociation() * sliplink.destination.activation for sliplink in sliplinks ] + i = selectListPosition(values) + chosen = sliplinks[ i ] + chosenProperty = chosen.destination + coderack.proposeDescription(chosenObject,chosenProperty.category(),chosenProperty,codelet) + +def top_down_description_scout(codelet): + descriptionType = codelet.arguments[0] + chosenObject = chooseUnmodifiedObject('totalSalience',workspace.objects) + assert chosenObject + __showWhichStringObjectIsFrom(chosenObject) + descriptions = chosenObject.getPossibleDescriptions(descriptionType) + assert descriptions and len(descriptions) + values = [ n.activation for n in descriptions ] + i = selectListPosition(values) + chosenProperty = descriptions[ i ] + coderack.proposeDescription(chosenObject,chosenProperty.category(), chosenProperty,codelet) + +def description_strength_tester(codelet): + description = codelet.arguments[0] + description.descriptor.buffer = 100.0 + description.updateStrength() + strength = description.totalStrength + probability = temperatureAdjustedProbability(strength/100.0) + assert formulas.coinFlip(probability) + coderack.newCodelet('description-builder',codelet,strength) + +def description_builder(codelet): + description = codelet.arguments[0] + assert description.object in workspace.objects + if description.object.hasDescription(description.descriptor): + description.descriptionType.buffer = 100.0 + description.descriptor.buffer = 100.0 + else: + description.build() + +def bottom_up_bond_scout(codelet): + source = chooseUnmodifiedObject('intraStringSalience',workspace.objects) + __showWhichStringObjectIsFrom(source) + destination = chooseNeighbour(source) + assert destination + logging.info('destination: %s' % destination) + bondFacet = __getBondFacet(source,destination) + logging.info('chosen bond facet: %s' % bondFacet.get_name()) + logging.info('Source: %s, destination: %s' % (source,destination)) + sourceDescriptor, destinationDescriptor = __getDescriptors(bondFacet,source,destination) + logging.info("source descriptor: " + sourceDescriptor.name.upper()) + logging.info("destination descriptior: " + destinationDescriptor.name.upper()) + category = sourceDescriptor.getBondCategory(destinationDescriptor) + assert category + if category == slipnet.identity: + category = slipnet.sameness + logging.info('proposing %s bond ' % category.name) + coderack.proposeBond(source,destination,category,bondFacet,sourceDescriptor,destinationDescriptor,codelet) + +def rule_scout(codelet): + assert workspace.numberOfUnreplacedObjects() == 0 + changedObjects = [ o for o in workspace.initial.objects if o.changed ] + #assert len(changedObjects) < 2 + # if there are no changed objects, propose a rule with no changes + if not changedObjects: + return coderack.proposeRule(None,None,None,None,codelet) + + changed = changedObjects[-1] + # generate a list of distinguishing descriptions for the first object + # ie. string-position (leftmost,rightmost,middle or whole) or letter category + # if it is the only one of its type in the string + objectList = [] + position = changed.getDescriptor(slipnet.stringPositionCategory) + if position: + objectList += [ position ] + letter = changed.getDescriptor(slipnet.letterCategory) + otherObjectsOfSameLetter = [ o for o in workspace.initial.objects if not o != changed and o.getDescriptionType(letter) ] + if not len(otherObjectsOfSameLetter): # then the letter is a distinguishing feature + objectList += [ letter ] + # if this object corresponds to another object in the workspace + # objectList = the union of this and the distingushing descriptors + if changed.correspondence: + targetObject = changed.correspondence.objectFromTarget + newList = [] + slippages = workspace.slippages() + for node in objectList: + node = node.applySlippages(slippages) + if targetObject.hasDescription(node) and targetObject.distinguishingDescriptor(node): + newList += [ node ] + objectList = newList # XXX surely this should be += ("the union of this and the distinguishing descriptors") + assert objectList and len(objectList) + # use conceptual depth to choose a description + valueList = [] + for node in objectList: + depth = node.conceptualDepth + value = temperatureAdjustedValue(depth) + valueList += [ value ] + i = selectListPosition(valueList) + descriptor = objectList[ i ] + # choose the relation (change the letmost object to..xxxx) i.e. "successor" or "d" + objectList = [] + if changed.replacement.relation: + objectList += [ changed.replacement.relation ] + objectList += [ changed.replacement.objectFromModified.getDescriptor(slipnet.letterCategory) ] + # use conceptual depth to choose a relation + valueList = [] + for node in objectList: + depth = node.conceptualDepth + value = temperatureAdjustedValue(depth) + valueList += [ value ] + i = selectListPosition(valueList) + relation = objectList[ i ] + coderack.proposeRule(slipnet.letterCategory,descriptor,slipnet.letter,relation,codelet) + +def rule_strength_tester(codelet): + rule = codelet.arguments[0] + rule.updateStrength() + probability = temperatureAdjustedProbability(rule.totalStrength / 100.0) + assert utils.random() <= probability + coderack.newCodelet('rule-builder',codelet,rule.totalStrength,rule) + +def replacement_finder(): + # choose random letter in initial string + letters = [ o for o in workspace.initial.objects if isinstance(o,Letter) ] + letterOfInitialString = utils.choice(letters) + logging.info('selected letter in initial string = %s' % letterOfInitialString) + if letterOfInitialString.replacement: + logging.info("Replacement already found for %s, so fizzling" % letterOfInitialString) + return + position = letterOfInitialString.leftStringPosition + moreLetters = [ o for o in workspace.modified.objects if isinstance(o,Letter) and o.leftStringPosition == position ] + letterOfModifiedString = moreLetters and moreLetters[0] or None + assert letterOfModifiedString + position -= 1 + initialAscii = ord(workspace.initialString[position]) + modifiedAscii = ord(workspace.modifiedString[position]) + diff = initialAscii - modifiedAscii + if abs(diff) < 2: + relations = { 0:slipnet.sameness, -1:slipnet.successor, 1:slipnet.predecessor } + relation = relations[diff] + logging.info('Relation found: %s' % relation.name) + else: + relation = None + logging.info('no relation found') + letterOfInitialString.replacement = Replacement(letterOfInitialString,letterOfModifiedString,relation) + if relation != slipnet.sameness: + letterOfInitialString.changed = True + workspace.changedObject = letterOfInitialString + logging.info('building replacement') + +def top_down_bond_scout__category(codelet): + logging.info('top_down_bond_scout__category') + category = codelet.arguments[0] + source = __getScoutSource(category,localBondCategoryRelevance,'bond') + destination = chooseNeighbour(source) + logging.info('source: %s, destination: %s' % (source,destination)) + assert destination + bondFacet = __getBondFacet(source,destination) + sourceDescriptor, destinationDescriptor = __getDescriptors(bondFacet,source,destination) + forwardBond = sourceDescriptor.getBondCategory(destinationDescriptor) + if forwardBond == slipnet.identity: + forwardBond = slipnet.sameness + backwardBond = slipnet.sameness + else: + backwardBond = destinationDescriptor.getBondCategory(sourceDescriptor) + assert category == forwardBond or category == backwardBond + if category == forwardBond: + coderack.proposeBond(source,destination,category,bondFacet,sourceDescriptor, destinationDescriptor,codelet) + else: + coderack.proposeBond(destination,source,category,bondFacet,destinationDescriptor,sourceDescriptor,codelet) + +def top_down_bond_scout__direction(codelet): + direction = codelet.arguments[0] + source = __getScoutSource(direction,localDirectionCategoryRelevance,'bond') + destination = chooseDirectedNeighbor(source,direction) + assert destination + logging.info('to object: %s' % destination) + bondFacet = __getBondFacet(source,destination) + sourceDescriptor, destinationDescriptor = __getDescriptors(bondFacet,source,destination) + category = sourceDescriptor.getBondCategory(destinationDescriptor) + assert category + if category == slipnet.identity: + category = slipnet.sameness + coderack.proposeBond(source,destination,category,bondFacet,sourceDescriptor, destinationDescriptor,codelet) + +def bond_strength_tester(codelet): + bond = codelet.arguments[0] + __showWhichStringObjectIsFrom(bond) + bond.updateStrength() + strength = bond.totalStrength + probability = temperatureAdjustedProbability(strength/100.0) + logging.info('bond strength = %d for %s' % (strength,bond)) + assert formulas.coinFlip(probability) + bond.facet.buffer = 100.0 + bond.sourceDescriptor.buffer = 100.0 + bond.destinationDescriptor.buffer = 100.0 + logging.info("succeeded: posting bond-builder") + coderack.newCodelet('bond-builder',codelet,strength) + +def bond_builder(codelet): + bond = codelet.arguments[0] + __showWhichStringObjectIsFrom(bond) + bond.updateStrength() + assert (bond.source in workspace.objects or bond.destination in workspace.objects) + for stringBond in bond.string.bonds: + if bond.sameNeighbours(stringBond) and bond.sameCategories(stringBond): + if bond.directionCategory: + bond.directionCategory.buffer = 100.0 + bond.category.buffer = 100.0 + logging.info('already exists: activate descriptors & Fizzle') + return + incompatibleBonds = bond.getIncompatibleBonds() + logging.info('number of incompatibleBonds: %d' % len(incompatibleBonds)) + if len(incompatibleBonds): + logging.info('%s' % incompatibleBonds[0]) + assert __fightIncompatibles(incompatibleBonds,bond,'bonds',1.0,1.0) + incompatibleGroups = bond.source.getCommonGroups(bond.destination) + assert __fightIncompatibles(incompatibleGroups,bond,'groups',1.0,1.0) + # fight all incompatible correspondences + incompatibleCorrespondences = [] + if bond.leftObject.leftmost or bond.rightObject.rightmost: + if bond.directionCategory: + incompatibleCorrespondences = bond.getIncompatibleCorrespondences() + if incompatibleCorrespondences: + logging.info("trying to break incompatible correspondences") + assert __fightItOut(bond,2.0,incompatibleCorrespondences,3.0) + #assert __fightIncompatibles(incompatibleCorrespondences,bond,'correspondences',2.0,3.0) + for incompatible in incompatibleBonds: + incompatible.break_the_structure() + for incompatible in incompatibleGroups: + incompatible.break_the_structure() + for incompatible in incompatibleCorrespondences: + incompatible.break_the_structure() + logging.info('building bond %s' % bond) + bond.buildBond() + +def top_down_group_scout__category(codelet): + groupCategory = codelet.arguments[0] + category = groupCategory.getRelatedNode(slipnet.bondCategory) + assert category + source = __getScoutSource(category,localBondCategoryRelevance,'group') + assert source and not source.spansString() + if source.leftmost: + direction = slipnet.right + elif source.rightmost: + direction = slipnet.left + else: + activations = [ slipnet.left.activation ] + activations += [ slipnet.right.activation ] + if not selectListPosition(activations): + direction = slipnet.left + else: + direction = slipnet.right + if direction == slipnet.left: + firstBond = source.leftBond + else: + firstBond = source.rightBond + if not firstBond or firstBond.category != category: + # check the other side of object + if direction == slipnet.right: + firstBond = source.leftBond + else: + firstBond = source.rightBond + if not firstBond or firstBond.category != category: + if category == slipnet.sameness and isinstance(source,Letter): + group = Group(source.string,slipnet.samenessGroup, None,slipnet.letterCategory, [ source ], []) + probability = group.singleLetterGroupProbability() + assert utils.random() >= probability + coderack.proposeSingleLetterGroup( source, codelet) + return + direction = firstBond.directionCategory + search = True + bondFacet = None + # find leftmost object in group with these bonds + while search: + search = False + if source.leftBond: + if source.leftBond.category == category: + if not source.leftBond.directionCategory or source.leftBond.directionCategory == direction: + if not bondFacet or bondFacet == source.leftBond.facet: + bondFacet = source.leftBond.facet + direction = source.leftBond.directionCategory + source = source.leftBond.leftObject + search = True + # find rightmost object in group with these bonds + search = True + destination = source + while search: + search = False + if destination.rightBond: + if destination.rightBond.category == category: + if not destination.rightBond.directionCategory or destination.rightBond.directionCategory == direction: + if not bondFacet or bondFacet == destination.rightBond.facet: + bondFacet = destination.rightBond.facet + direction = source.rightBond.directionCategory + destination = destination.rightBond.rightObject + search = True + assert destination != source + objects = [ source ] + bonds = [] + while source != destination: + bonds += [ source.rightBond ] + objects += [ source.rightBond.rightObject ] + source = source.rightBond.rightObject + coderack.proposeGroup(objects,bonds,groupCategory,direction,bondFacet,codelet) + +def top_down_group_scout__direction(codelet): + direction = codelet.arguments[0] + source = __getScoutSource(direction,localDirectionCategoryRelevance,'direction') + logging.info('source chosen = %s' % source) + assert not source.spansString() + if source.leftmost : + mydirection = slipnet.right + elif source.rightmost: + mydirection = slipnet.left + else: + activations = [ slipnet.left.activation ] + activations += [ slipnet.right.activation ] + if not selectListPosition(activations): + mydirection = slipnet.left + else: + mydirection = slipnet.right + if mydirection == slipnet.left: + firstBond = source.leftBond + else: + firstBond = source.rightBond + if not firstBond: + logging.info('no firstBond') + else: + logging.info('firstBond: %s' % firstBond) + if firstBond and not firstBond.directionCategory: + direction = None + if not firstBond or firstBond.directionCategory != direction: + if mydirection == slipnet.right: + firstBond = source.leftBond + else: + firstBond = source.rightBond + if not firstBond: + logging.info('no firstBond2') + else: + logging.info('firstBond2: %s' % firstBond) + if firstBond and not firstBond.directionCategory: + direction = None + assert firstBond and firstBond.directionCategory == direction + logging.info('possible group: %s' % firstBond) + category = firstBond.category + assert category + groupCategory = category.getRelatedNode(slipnet.groupCategory) + logging.info('trying from %s to %s' % (source,category.name) ) + bondFacet = None + # find leftmost object in group with these bonds + search = True + while search: + search = False + if source.leftBond: + if source.leftBond.category == category: + if not source.leftBond.directionCategory or source.leftBond.directionCategory == direction: + if not bondFacet or bondFacet == source.leftBond.facet: + bondFacet = source.leftBond.facet + direction = source.leftBond.directionCategory + source = source.leftBond.leftObject + search = True + destination = source + search = True + while search: + search = False + if destination.rightBond: + if destination.rightBond.category == category: + if not destination.rightBond.directionCategory or destination.rightBond.directionCategory == direction: + if not bondFacet or bondFacet == destination.rightBond.facet: + bondFacet = destination.rightBond.facet + direction = source.rightBond.directionCategory + destination = destination.rightBond.rightObject + search = True + assert destination != source + logging.info('proposing group from %s to %s' % (source,destination) ) + objects = [ source ] + bonds = [] + while source != destination: + bonds += [ source.rightBond ] + objects += [ source.rightBond.rightObject ] + source = source.rightBond.rightObject + coderack.proposeGroup(objects,bonds,groupCategory,direction,bondFacet,codelet) + +#noinspection PyStringFormat +def group_scout__whole_string(codelet): + string = workspace.initial + if utils.random() > 0.5: + string = workspace.target + logging.info('target string selected: %s' % workspace.target ) + else: + logging.info('initial string selected: %s' % workspace.initial ) + # find leftmost object & the highest group to which it belongs + leftmost = None + for objekt in string.objects: + if objekt.leftmost: + leftmost = objekt + while leftmost.group and leftmost.group.bondCategory == slipnet.sameness: + leftmost = leftmost.group + if leftmost.spansString(): + # the object already spans the string - propose this object + group = leftmost + coderack.proposeGroup(group.objectList, group.bondList,group.groupCategory,group.directionCategory, group.facet,codelet) + return + bonds = [] + objects = [ leftmost ] + while leftmost.rightBond: + bonds += [ leftmost.rightBond ] + leftmost = leftmost.rightBond.rightObject + objects += [ leftmost ] + assert leftmost.rightmost + # choose a random bond from list + chosenBond = utils.choice(bonds) + category = chosenBond.category + directionCategory = chosenBond.directionCategory + bondFacet = chosenBond.facet + bonds = possibleGroupBonds(category, directionCategory, bondFacet, bonds) + assert bonds + groupCategory = category.getRelatedNode(slipnet.groupCategory) + coderack.proposeGroup(objects,bonds,groupCategory,directionCategory,bondFacet,codelet) + +def group_strength_tester(codelet): + # update strength value of the group + group = codelet.arguments[0] + __showWhichStringObjectIsFrom(group) + group.updateStrength() + strength = group.totalStrength + probability = temperatureAdjustedProbability(strength/100.0) + assert utils.random() <= probability + # it is strong enough - post builder & activate nodes + group.groupCategory.getRelatedNode(slipnet.bondCategory).buffer = 100.0 + if group.directionCategory: + group.directionCategory.buffer=100.0 + coderack.newCodelet('group-builder',codelet,strength) + +def group_builder(codelet): + # update strength value of the group + group = codelet.arguments[0] + #print '%s' % group + __showWhichStringObjectIsFrom(group) + equivalent = group.string.equivalentGroup(group) + if equivalent : + logging.info('already exists...activate descriptors & fizzle') + group.activateDescriptions() + equivalent.addDescriptions(group.descriptions) + return + # check to see if all objects are still there + for o in group.objectList: + assert o in workspace.objects + # check to see if bonds are there of the same direction + incompatibleBonds = [] # incompatible bond list + if len(group.objectList) > 1: + previous = group.objectList[0] + for objekt in group.objectList[1:]: + #print 770 + leftBond = objekt.leftBond + if leftBond: + lefty = leftBond.leftObject + if lefty != previous or leftBond.directionCategory != group.directionCategory: + incompatibleBonds += [ leftBond ] + previous = objekt + next = group.objectList[-1] + for objekt in reversed(group.objectList[:-1]): + rightBond = objekt.rightBond + if rightBond: + righty = rightBond.rightObject + if righty != next or rightBond.directionCategory != group.directionCategory: + incompatibleBonds += [ rightBond ] + next = objekt + # if incompatible bonds exist - fight + group.updateStrength() + assert __fightIncompatibles(incompatibleBonds,group,'bonds',1.0,1.0) + # fight incompatible groups + # fight all groups containing these objects + incompatibleGroups = group.getIncompatibleGroups() + assert __fightIncompatibles(incompatibleGroups,group,'Groups',1.0,1.0) + for incompatible in incompatibleBonds: + incompatible.break_the_structure() + # create new bonds + group.bondList = [] + for i in range(1,len(group.objectList)): + #print 803 + object1 = group.objectList[i - 1] + object2 = group.objectList[i] + if not object1.rightBond: + if group.directionCategory == slipnet.right: + source = object1 + destination = object2 + else: + source = object2 + destination = object1 + category = group.groupCategory.getRelatedNode(slipnet.bondCategory) + facet = group.facet + newBond = Bond(source,destination,category,facet,source.getDescriptor(facet),destination.getDescriptor(facet)) + newBond.buildBond() + group.bondList += [ object1.rightBond ] + for incompatible in incompatibleGroups: + incompatible.break_the_structure() + group.buildGroup() + group.activateDescriptions() + logging.info('building group') + +def rule_builder(codelet): + rule = codelet.arguments[0] + if rule.ruleEqual(workspace.rule): + rule.activateRuleDescriptions() + return + rule.updateStrength() + assert rule.totalStrength + # fight against other rules + if workspace.rule: + assert __structureVsStructure(rule,1.0,workspace.rule,1.0) + 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 ] + 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) * utils.random() + total = 0.0 + for i in range(0,len(distribution)): + total += distribution[i] + if total >= stop: + return i + 1 + return len(distribution) + +def rule_translator(): + assert workspace.rule + if len(workspace.initial) == 1 and len(workspace.target) == 1: + bondDensity = 1.0 + else: + numberOfBonds = len(workspace.initial.bonds) + 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 + assert cutoff >= formulas.actualTemperature + if workspace.rule.buildTranslatedRule(): + workspace.foundAnswer = True + else: + temperature.clampTime = coderack.codeletsRun + 100 + temperature.clamped = True + formulas.Temperature = 100.0 + +def bottom_up_correspondence_scout(codelet): + objectFromInitial = chooseUnmodifiedObject('interStringSalience',workspace.initial.objects) + objectFromTarget = chooseUnmodifiedObject('interStringSalience',workspace.target.objects) + assert objectFromInitial.spansString() == objectFromTarget.spansString() + # get the posible concept mappings + conceptMappings = getMappings( objectFromInitial, objectFromTarget, objectFromInitial.relevantDescriptions(), objectFromTarget.relevantDescriptions()) + assert conceptMappings and __slippability(conceptMappings) + #find out if any are distinguishing + distinguishingMappings = [ m for m in conceptMappings if m.distinguishing() ] + assert distinguishingMappings + # if both objects span the strings, check to see if the + # string description needs to be flipped + oppositeMappings = [ m for m in distinguishingMappings + if m.initialDescriptionType == slipnet.stringPositionCategory and + m.initialDescriptionType != slipnet.bondFacet ] + initialDescriptionTypes = [ m.initialDescriptionType for m in oppositeMappings ] + flipTargetObject = False + if objectFromInitial.spansString() and objectFromTarget.spansString() and slipnet.directionCategory in initialDescriptionTypes and __allOppositeMappings(oppositeMappings) and slipnet.opposite.activation != 100.0: + objectFromTarget = objectFromTarget.flippedVersion() + conceptMappings = getMappings( objectFromInitial, objectFromTarget, objectFromInitial.relevantDescriptions(), objectFromTarget.relevantDescriptions()) + flipTargetObject = True + coderack.proposeCorrespondence(objectFromInitial,objectFromTarget,conceptMappings,flipTargetObject,codelet) + +def important_object_correspondence_scout(codelet): + objectFromInitial = chooseUnmodifiedObject('relativeImportance',workspace.initial.objects) + descriptors = objectFromInitial.relevantDistinguishingDescriptors() + slipnode = chooseSlipnodeByConceptualDepth(descriptors) + assert slipnode + initialDescriptor = slipnode + for mapping in workspace.slippages(): + if mapping.initialDescriptor == slipnode: + initialDescriptor = mapping.targetDescriptor + targetCandidates = [] + for objekt in workspace.target.objects: + for description in objekt.relevantDescriptions(): + if description.descriptor == initialDescriptor: + targetCandidates += [ objekt ] + assert targetCandidates + objectFromTarget = chooseUnmodifiedObject('interStringSalience',targetCandidates) + assert objectFromInitial.spansString() == objectFromTarget.spansString() + # get the posible concept mappings + conceptMappings = getMappings( objectFromInitial, objectFromTarget, objectFromInitial.relevantDescriptions(), objectFromTarget.relevantDescriptions()) + assert conceptMappings and __slippability(conceptMappings) + #find out if any are distinguishing + distinguishingMappings = [ m for m in conceptMappings if m.distinguishing() ] + assert distinguishingMappings + # if both objects span the strings, check to see if the + # string description needs to be flipped + oppositeMappings = [ m for m in distinguishingMappings if m.initialDescriptionType == slipnet.stringPositionCategory and m.initialDescriptionType != slipnet.bondFacet ] + initialDescriptionTypes = [ m.initialDescriptionType for m in oppositeMappings ] + flipTargetObject = False + if objectFromInitial.spansString() and objectFromTarget.spansString() and slipnet.directionCategory in initialDescriptionTypes and __allOppositeMappings(oppositeMappings) and slipnet.opposite.activation != 100.0: + objectFromTarget = objectFromTarget.flippedVersion() + conceptMappings = getMappings( objectFromInitial, objectFromTarget,objectFromInitial.relevantDescriptions(), objectFromTarget.relevantDescriptions()) + flipTargetObject = True + coderack.proposeCorrespondence(objectFromInitial,objectFromTarget,conceptMappings,flipTargetObject,codelet) + +def correspondence_strength_tester(codelet): + correspondence = codelet.arguments[0] + objectFromInitial = correspondence.objectFromInitial + objectFromTarget = correspondence.objectFromTarget + assert objectFromInitial in workspace.objects and (objectFromTarget in workspace.objects or correspondence.flipTargetObject and not workspace.target.equivalentGroup(objectFromTarget.flipped_version())) + correspondence.updateStrength() + strength = correspondence.totalStrength + probability = temperatureAdjustedProbability(strength/100.0) + assert utils.random() <= probability + # activate some concepts + for mapping in correspondence.conceptMappings: + mapping.initialDescriptionType.buffer = 100.0 + mapping.initialDescriptor.buffer = 100.0 + mapping.targetDescriptionType.buffer = 100.0 + mapping.targetDescriptor.buffer = 100.0 + coderack.newCodelet('correspondence-builder',codelet,strength,correspondence) + +def correspondence_builder(codelet): + correspondence = codelet.arguments[0] + objectFromInitial = correspondence.objectFromInitial + objectFromTarget = correspondence.objectFromTarget + wantFlip = correspondence.flipTargetObject + if wantFlip: + flipper = objectFromTarget.flippedVersion() + targetNotFlipped = not workspace.target.equivalentGroup(flipper) + else: + targetNotFlipped = False + initialInObjects = objectFromInitial in workspace.objects + targetInObjects = objectFromTarget in workspace.objects + assert (initialInObjects or (not targetInObjects and (not (wantFlip and targetNotFlipped)))) + if correspondence.reflexive(): + # if the correspondence exists, activate concept mappings + # and add new ones to the existing corr. + existing = correspondence.objectFromInitial.correspondence + for mapping in correspondence.conceptMappings: + if mapping.label: + mapping.label.buffer = 100.0 + if not mapping.isContainedBy(existing.conceptMappings): + existing.conceptMappings += [ mapping ] + return + incompatibleCorrespondences = correspondence.getIncompatibleCorrespondences() + # fight against all correspondences + if incompatibleCorrespondences: + correspondenceSpans = correspondence.objectFromInitial.letterSpan() + correspondence.objectFromTarget.letterSpan() + for incompatible in incompatibleCorrespondences: + incompatibleSpans = incompatible.objectFromInitial.letterSpan() + incompatible.objectFromTarget.letterSpan() + assert __structureVsStructure(correspondence,correspondenceSpans,incompatible,incompatibleSpans) + incompatibleBond = None + incompatibleGroup = None + # if there is an incompatible bond then fight against it + if correspondence.objectFromInitial.leftmost or correspondence.objectFromInitial.rightmost and correspondence.objectFromTarget.leftmost or correspondence.objectFromTarget.rightmost: + # search for the incompatible bond + incompatibleBond = correspondence.getIncompatibleBond() + if incompatibleBond: + # bond found - fight against it + assert __structureVsStructure(correspondence,3.0,incompatibleBond,2.0) + # won against incompatible bond + incompatibleGroup = correspondence.objectFromTarget.group + if incompatibleGroup: + assert __structureVsStructure(correspondence,1.0,incompatibleGroup,1.0) + # if there is an incompatible rule, fight against it + incompatibleRule = None + if workspace.rule and workspace.rule.incompatibleRuleCorrespondence(correspondence): + incompatibleRule = workspace.rule + assert __structureVsStructure( correspondence,1.0,incompatibleRule,1.0) + for incompatible in incompatibleCorrespondences: + incompatible.break_the_structure() + # break incompatible group and bond if they exist + if incompatibleBond: + incompatibleBond.break_the_structure() + if incompatibleGroup: + incompatibleGroup.break_the_structure() + if incompatibleRule: + workspace.breakRule() + correspondence.buildCorrespondence() diff --git a/coderack.py b/coderack.py new file mode 100644 index 0000000..8cc55d0 --- /dev/null +++ b/coderack.py @@ -0,0 +1,338 @@ +import re, inspect, math, logging + +import utils +import formulas +import workspaceFormulas +from slipnet import slipnet +from codelet import Codelet +from coderackPressure import CoderackPressures + +NUMBER_OF_BINS = 7 +MAX_NUMBER_OF_CODELETS = 100 + +codeletsUsed = {} + +class CodeRack(object): + def __init__(self): + #logging.debug('coderack.__init__()') + self.speedUpBonds = False + self.removeBreakerCodelets = False + self.removeTerracedScan = False + self.pressures = CoderackPressures() + self.pressures.initialisePressures() + self.reset() + self.initialCodeletNames = ( 'bottom-up-bond-scout', 'replacement-finder', 'bottom-up-correspondence-scout' ) + self.codeletMethodsDir = None + self.runCodelets = {} + self.postings = {} + + def reset(self): + #logging.debug('coderack.reset()') + from temperature import temperature + + self.codelets = [] + self.codeletsRun = 0 + temperature.clamped = True + self.pressures.reset() + + def updateCodelets(self): + if self.codeletsRun > 0: + self.postTopDownCodelets() + self.postBottomUpCodelets() + + def getUrgencyBin(self, urgency): + bin = int(urgency) * NUMBER_OF_BINS + bin /= 100 + if bin >= NUMBER_OF_BINS: + bin = NUMBER_OF_BINS - 1 + return bin + 1 + + def post(self, codelet): + #logging.info('Posting codelet called: %s, with urgency %f' % (codelet.name,codelet.urgency)) + self.postings[codelet.name] = self.postings.get(codelet.name, 0) + 1 + self.pressures.addCodelet(codelet) + self.codelets += [codelet] + if len(self.codelets) > 100: + oldCodelet = self.chooseOldCodelet() + self.removeCodelet(oldCodelet) + + def postTopDownCodelets(self): + for node in slipnet.slipnodes: + #logging.info('Trying slipnode: %s' % node.get_name()) + if node.activation == 100.0: + #logging.info('using slipnode: %s' % node.get_name()) + for codeletName in node.codelets: + probability = workspaceFormulas.probabilityOfPosting(codeletName) + howMany = workspaceFormulas.howManyToPost(codeletName) + #print '%s:%d' % (codeletName,howMany) + for unused in range(0, howMany): + if utils.random() < probability: + urgency = self.getUrgencyBin(node.activation * node.conceptualDepth / 100.0) + codelet = Codelet(codeletName, urgency, self.codeletsRun) + codelet.arguments += [node] + logging.info('Post top down: %s, with urgency: %d' % (codelet.name, urgency)) + #logging.info("From slipnode %s, activation: %s, depth: %s" %(node.get_name(),node.activation,node.conceptual_depth) ) + self.post(codelet) + + def postBottomUpCodelets(self): + logging.info("posting bottom up codelets") + self.__postBottomUpCodelets('bottom-up-description-scout') + self.__postBottomUpCodelets('bottom-up-bond-scout') + self.__postBottomUpCodelets('group-scout--whole-string') + self.__postBottomUpCodelets('bottom-up-correspondence-scout') + self.__postBottomUpCodelets('important-object-correspondence-scout') + self.__postBottomUpCodelets('replacement-finder') + self.__postBottomUpCodelets('rule-scout') + self.__postBottomUpCodelets('rule-translator') + if not self.removeBreakerCodelets: + self.__postBottomUpCodelets('breaker') + + def __postBottomUpCodelets(self, codeletName): + probability = workspaceFormulas.probabilityOfPosting(codeletName) + howMany = workspaceFormulas.howManyToPost(codeletName) + #if codeletName == 'bottom-up-bond-scout': + # print 'post --> %f:%d' % (probability,howMany) + if self.speedUpBonds: + if 'bond' in codeletName or 'group' in codeletName: + howMany *= 3 + urgency = 3 + if codeletName == 'breaker': + urgency = 1 + if formulas.Temperature < 25.0 and 'translator' in codeletName: + urgency = 5 + for unused in range(0, howMany): + if utils.random() < probability: + codelet = Codelet(codeletName, urgency, self.codeletsRun) + self.post(codelet) + + def removeCodelet(self, codelet): + self.codelets.remove(codelet) + self.pressures.removeCodelet(codelet) + + def newCodelet(self, name, oldCodelet, strength, arguments=None): + #logging.debug('Posting new codelet called %s' % name) + urgency = self.getUrgencyBin(strength) + newCodelet = Codelet(name, urgency, self.codeletsRun) + if arguments: + newCodelet.arguments = [arguments] + else: + newCodelet.arguments = oldCodelet.arguments + newCodelet.pressure = oldCodelet.pressure + self.tryRun(newCodelet) + + def proposeRule(self, facet, description, category, relation, oldCodelet): + """Creates a proposed rule, and posts a rule-strength-tester codelet. + + The new codelet has urgency a function of the degree of conceptual-depth of the descriptions in the rule + """ + from rule import Rule + + rule = Rule(facet, description, category, relation) + rule.updateStrength() + if description and relation: + depths = description.conceptualDepth + relation.conceptualDepth + depths /= 200.0 + urgency = math.sqrt(depths) * 100.0 + else: + urgency = 0 + self.newCodelet('rule-strength-tester', oldCodelet, urgency, rule) + + def proposeCorrespondence(self, initialObject, targetObject, conceptMappings, flipTargetObject, oldCodelet): + from correspondence import Correspondence + + correspondence = Correspondence(initialObject, targetObject, conceptMappings, flipTargetObject) + for mapping in conceptMappings: + mapping.initialDescriptionType.buffer = 100.0 + mapping.initialDescriptor.buffer = 100.0 + mapping.targetDescriptionType.buffer = 100.0 + mapping.targetDescriptor.buffer = 100.0 + mappings = correspondence.distinguishingConceptMappings() + urgency = sum([mapping.strength() for mapping in mappings]) + numberOfMappings = len(mappings) + if urgency: + urgency /= numberOfMappings + bin = self.getUrgencyBin(urgency) + logging.info('urgency: %s, number: %d, bin: %d' % (urgency, numberOfMappings, bin)) + self.newCodelet('correspondence-strength-tester', oldCodelet, urgency, correspondence) + + def proposeDescription(self, objekt, descriptionType, descriptor, oldCodelet): + from description import Description + + description = Description(objekt, descriptionType, descriptor) + descriptor.buffer = 100.0 + urgency = descriptionType.activation + self.newCodelet('description-strength-tester', oldCodelet, urgency, description) + + def proposeSingleLetterGroup(self, source, codelet): + self.proposeGroup([source], [], slipnet.samenessGroup, None, slipnet.letterCategory, codelet) + + def proposeGroup(self, objects, bondList, groupCategory, directionCategory, bondFacet, oldCodelet ): + from group import Group + + bondCategory = groupCategory.getRelatedNode(slipnet.bondCategory) + bondCategory.buffer = 100.0 + if directionCategory: + directionCategory.buffer = 100.0 + group = Group(objects[0].string, groupCategory, directionCategory, bondFacet, objects, bondList) + urgency = bondCategory.bondDegreeOfAssociation() + self.newCodelet('group-strength-tester', oldCodelet, urgency, group) + + def proposeBond(self, source, destination, bondCategory, bondFacet, sourceDescriptor, destinationDescriptor, oldCodelet ): + from bond import Bond + + bondFacet.buffer = 100.0 + sourceDescriptor.buffer = 100.0 + destinationDescriptor.buffer = 100.0 + bond = Bond(source, destination, bondCategory, bondFacet, sourceDescriptor, destinationDescriptor) + urgency = bondCategory.bondDegreeOfAssociation() + self.newCodelet('bond-strength-tester', oldCodelet, urgency, bond) + + 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 = (coderack.codeletsRun - codelet.timeStamp) * (7.5 - codelet.urgency) + urgencies += [urgency] + threshold = utils.random() * sum(urgencies) + sumOfUrgencies = 0.0 + for i in range(0, len(self.codelets)): + sumOfUrgencies += urgencies[i] + if sumOfUrgencies > threshold: + return self.codelets[i] + return self.codelets[0] + + def postInitialCodelets(self): + #logging.debug('Posting initial codelets') + #logging.debug('Number of inital codelets: %d' % len(self.initialCodeletNames)) + #logging.debug('Number of workspaceObjects: %d' % workspace.numberOfObjects()) + for name in self.initialCodeletNames: + for unused in range(0, workspaceFormulas.numberOfObjects()): + codelet = Codelet(name, 1, self.codeletsRun) + self.post(codelet) + codelet2 = Codelet(name, 1, self.codeletsRun) + self.post(codelet2) + + def tryRun(self, newCodelet): + if self.removeTerracedScan: + self.run(newCodelet) + else: + self.post(newCodelet) + + def getCodeletmethods(self): + import codeletMethods + + self.codeletMethodsDir = dir(codeletMethods) + knownCodeletNames = ( + 'breaker', + 'bottom-up-description-scout', + 'top-down-description-scout', + 'description-strength-tester', + 'description-builder', + 'bottom-up-bond-scout', + 'top-down-bond-scout--category', + 'top-down-bond-scout--direction', + 'bond-strength-tester', + 'bond-builder', + 'top-down-group-scout--category', + 'top-down-group-scout--direction', + 'group-scout--whole-string', + 'group-strength-tester', + 'group-builder', + 'replacement-finder', + 'rule-scout', + 'rule-strength-tester', + 'rule-builder', + 'rule-translator', + 'bottom-up-correspondence-scout', + 'important-object-correspondence-scout', + 'correspondence-strength-tester', + 'correspondence-builder', + ) + self.methods = {} + for codeletName in knownCodeletNames: + methodName = re.sub('[ -]', '_', codeletName) + if methodName not in self.codeletMethodsDir: + raise NotImplementedError, 'Cannot find %s in codeletMethods' % methodName + method = getattr(codeletMethods, methodName) + self.methods[methodName] = method + + def chooseAndRunCodelet(self): + if not len(coderack.codelets): + coderack.postInitialCodelets() + codelet = self.chooseCodeletToRun() + if codelet: + self.run(codelet) + + def chooseCodeletToRun(self): + if not self.codelets: + return None + temp = formulas.Temperature + scale = ( 100.0 - temp + 10.0 ) / 15.0 + # threshold = sum( [ c.urgency ** scale for c in self.codelets ] ) * utils.random() + urgsum = 0.0 + for codelet in self.codelets: + urg = codelet.urgency ** scale + urgsum += urg + r = utils.random() + threshold = r * urgsum + chosen = None + urgencySum = 0.0 + logging.info('temperature: %f' % formulas.Temperature) + logging.info('actualTemperature: %f' % formulas.actualTemperature) + logging.info('Slipnet:') + for node in slipnet.slipnodes: + logging.info("\tnode %s, activation: %d, buffer: %d, depth: %s" % (node.get_name(), node.activation, node.buffer, node.conceptualDepth)) + logging.info('Coderack:') + for codelet in self.codelets: + logging.info('\t%s, %d' % (codelet.name, codelet.urgency)) + from workspace import workspace + + workspace.initial.log("Initial: ") + workspace.target.log("Target: ") + for codelet in self.codelets: + urgencySum += codelet.urgency ** scale + if not chosen and urgencySum > threshold: + chosen = codelet + break + if not chosen: + chosen = self.codelets[0] + self.removeCodelet(chosen) + logging.info('chosen codelet\n\t%s, urgency = %s' % (chosen.name, chosen.urgency)) + return chosen + + def run(self, codelet): + methodName = re.sub('[ -]', '_', codelet.name) + self.codeletsRun += 1 + self.runCodelets[methodName] = self.runCodelets.get(methodName, 0) + 1 + + #if self.codeletsRun > 2000: + #import sys + #print "running too many codelets" + #for name,count in self.postings.iteritems(): + #print '%d:%s' % (count,name) + #raise ValueError + #else: + # print 'running %d' % self.codeletsRun + if not self.codeletMethodsDir: + self.getCodeletmethods() + #if not self.codeletMethodsDir: + method = self.methods[methodName] + if not method: + raise ValueError, 'Found %s in codeletMethods, but cannot get it' % methodName + if not callable(method): + raise RuntimeError, 'Cannot call %s()' % methodName + args, varargs, varkw, defaults = inspect.getargspec(method) + #global codeletsUsed + #codeletsUsed[methodName] = codeletsUsed.get(methodName,0) + 1 + try: + if 'codelet' in args: + method(codelet) + else: + method() + except AssertionError: + pass + +coderack = CodeRack() diff --git a/coderackPressure.py b/coderackPressure.py new file mode 100644 index 0000000..4b925f6 --- /dev/null +++ b/coderackPressure.py @@ -0,0 +1,133 @@ +import logging +from formulas import Temperature +from slipnet import slipnet + +class CoderackPressure(object): + def __init__(self, name): + self.name = name + + def reset(self): + self.unmodifedValues = [] + self.values = [] + self.codelets = [] + +class CoderackPressures(object): + def __init__(self): + #logging.debug('coderackPressures.__init__()') + self.initialisePressures() + self.reset() + + def initialisePressures(self): + #logging.debug('coderackPressures.initialisePressures()') + self.pressures = [] + self.pressures += [CoderackPressure('Bottom Up Bonds')] + self.pressures += [CoderackPressure('Top Down Successor Bonds')] + self.pressures += [CoderackPressure('Top Down Predecessor Bonds')] + self.pressures += [CoderackPressure('Top Down Sameness Bonds')] + self.pressures += [CoderackPressure('Top Down Left Bonds')] + self.pressures += [CoderackPressure('Top Down Right Bonds')] + self.pressures += [CoderackPressure('Top Down Successor Group')] + self.pressures += [CoderackPressure('Top Down Predecessor Group')] + self.pressures += [CoderackPressure('Top Down Sameness Group')] + self.pressures += [CoderackPressure('Top Down Left Group')] + self.pressures += [CoderackPressure('Top Down Right Group')] + self.pressures += [CoderackPressure('Bottom Up Whole Group')] + self.pressures += [CoderackPressure('Replacement Finder')] + self.pressures += [CoderackPressure('Rule Codelets')] + self.pressures += [CoderackPressure('Rule Translator')] + self.pressures += [CoderackPressure('Bottom Up Correspondences')] + self.pressures += [CoderackPressure('Important Object Correspondences')] + self.pressures += [CoderackPressure('Breakers')] + + def calculatePressures(self): + #logging.debug('coderackPressures.calculatePressures()') + scale = ( 100.0 - Temperature + 10.0 ) / 15.0 + values = [] + for pressure in self.pressures: + value = sum([c.urgency ** scale for c in pressure.codelets]) + values += [value] + totalValue = sum(values) + if not totalValue: + totalValue = 1.0 + values = [value / totalValue for value in values] + self.maxValue = max(values) + for pressure, value in zip(self.pressures, values): + pressure.values += [value * 100.0] + for codelet in self.removedCodelets: + if codelet.pressure: + codelet.pressure.codelets.removeElement(codelet) + self.removedCodelets = [] + + def reset(self): + #logging.debug('coderackPressures.reset()') + self.maxValue = 0.001 + for pressure in self.pressures: + pressure.reset() + self.removedCodelets = [] + + def addCodelet(self, codelet): + node = None + i = -1 + if codelet.name == 'bottom-up-bond-scout': + i = 0 + if codelet.name == 'top-down-bond-scout--category': + node = codelet.arguments[0] + if node == slipnet.successor: + i = 1 + elif node == slipnet.predecessor: + i = 2 + else: + i = 3 + if codelet.name == 'top-down-bond-scout--direction': + node = codelet.arguments[0] + if node == slipnet.left: + i = 4 + elif node == slipnet.right: + i = 5 + else: + i = 3 + if codelet.name == 'top-down-group-scout--category': + node = codelet.arguments[0] + if node == slipnet.successorGroup: + i = 6 + elif node == slipnet.predecessorGroup: + i = 7 + else: + i = 8 + if codelet.name == 'top-down-group-scout--direction': + node = codelet.arguments[0] + if node == slipnet.left: + i = 9 + elif node == slipnet.right: + i = 10 + if codelet.name == 'group-scout--whole-string': + i = 11 + if codelet.name == 'replacement-finder': + i = 12 + if codelet.name == 'rule-scout': + i = 13 + if codelet.name == 'rule-translator': + i = 14 + if codelet.name == 'bottom-up-correspondence-scout': + i = 15 + if codelet.name == 'important-object-correspondence-scout': + i = 16 + if codelet.name == 'breaker': + i = 17 + if i >= 0: + self.pressures[i].codelets += [codelet] + if codelet.pressure: + codelet.pressure.codelets += [codelet] # XXX why do this + if i >= 0: + codelet.pressure = self.pressures[i] # when following with this ? + logging.info('Add %s: %d' % (codelet.name, i)) + if node: + logging.info('Node: %s' % node.name) + + def removeCodelet(self, codelet): + self.removedCodelets += [codelet] + + def numberOfPressures(self): + return len(self.pressures) + +coderackPressures = CoderackPressures() diff --git a/conceptMapping.py b/conceptMapping.py new file mode 100644 index 0000000..d3045a4 --- /dev/null +++ b/conceptMapping.py @@ -0,0 +1,156 @@ +import logging +from slipnet import slipnet + +class ConceptMapping(object): + def __init__(self, initialDescriptionType, targetDescriptionType, initialDescriptor, targetDescriptor, initialObject, targetObject): + logging.info('make a map: %s-%s' % (initialDescriptionType.get_name(), targetDescriptionType.get_name())) + self.initialDescriptionType = initialDescriptionType + self.targetDescriptionType = targetDescriptionType + self.initialDescriptor = initialDescriptor + self.targetDescriptor = targetDescriptor + self.initialObject = initialObject + self.targetObject = targetObject + self.label = initialDescriptor.getBondCategory(targetDescriptor) + + def __repr__(self): + return '' % (self.__str__(), self.initialDescriptor, self.targetDescriptor) + + def __str__(self): + if self.label: + return self.label.name + return 'anonymous' + + def slipability(self): + association = self.__degreeOfAssociation() + if association == 100.0: + return 100.0 + depth = self.__conceptualDepth() / 100.0 + return association * ( 1 - depth * depth ) + + def __degreeOfAssociation(self): + #assumes the 2 descriptors are connected in the slipnet by at most 1 link + if self.initialDescriptor == self.targetDescriptor: + return 100.0 + for link in self.initialDescriptor.lateralSlipLinks: + if link.destination == self.targetDescriptor: + return link.degreeOfAssociation() + return 0.0 + + def strength(self): + association = self.__degreeOfAssociation() + if association == 100.0: + return 100.0 + depth = self.__conceptualDepth() / 100.0 + return association * ( 1 + depth * depth ) + + def __conceptualDepth(self): + return (self.initialDescriptor.conceptualDepth + self.targetDescriptor.conceptualDepth ) / 2.0 + + def distinguishing(self): + if self.initialDescriptor == slipnet.whole and self.targetDescriptor == slipnet.whole: + return False + if not self.initialObject.distinguishingDescriptor(self.initialDescriptor): + return False + if not self.targetObject.distinguishingDescriptor(self.targetDescriptor): + return False + return True + + def sameInitialType(self, other): + return self.initialDescriptionType == other.initialDescriptionType + + def sameTargetType(self, other): + return self.targetDescriptionType == other.targetDescriptionType + + def sameTypes(self, other): + return self.sameInitialType(other) and self.sameTargetType(other) + + def sameInitialDescriptor(self, other): + return self.initialDescriptor == other.initialDescriptor + + def sameTargetDescriptor(self, other): + return self.targetDescriptor == other.targetDescriptor + + def sameDescriptors(self, other): + return self.sameInitialDescriptor(other) and self.sameTargetDescriptor(other) + + def sameKind(self, other): + return self.sameTypes(other) and self.sameDescriptors(other) + + def nearlySameKind(self, other): + return self.sameTypes(other) and self.sameInitialDescriptor(other) + + def isContainedBy(self, mappings): + for mapping in mappings: + if self.sameKind(mapping): + return True + return False + + def isNearlyContainedBy(self, mappings): + for mapping in mappings: + if self.nearlySameKind(mapping): + return True + return False + + def related(self, other): + if self.initialDescriptor.related(other.initialDescriptor): + return True + return self.targetDescriptor.related(other.targetDescriptor) + + def incompatible(self, other): + # Concept-mappings (a -> b) and (c -> d) are incompatible if a is + # related to c or if b is related to d, and the a -> b relationship is + # different from the c -> d relationship. E.g., rightmost -> leftmost + # is incompatible with right -> right, since rightmost is linked + # to right, but the relationships (opposite and identity) are different. + # Notice that slipnet distances are not looked at, only slipnet links. This + # should be changed eventually. + if not self.related(other): + return False + if not self.label or not other.label: + return False + if self.label != other.label: + return True + return False + + def supports(self, other): + # Concept-mappings (a -> b) and (c -> d) support each other if a is related + # to c and if b is related to d and the a -> b relationship is the same as the + # c -> d relationship. E.g., rightmost -> rightmost supports right -> right + # and leftmost -> leftmost. Notice that slipnet distances are not looked + # at, only slipnet links. This should be changed eventually. + + # If the two concept-mappings are the same, then return t. This + # means that letter->group supports letter->group, even though these + # concept-mappings have no label. + + if self.initialDescriptor == other.initialDescriptor and self.targetDescriptor == other.targetDescriptor: + return True + # if the descriptors are not related return false + if not self.related(other): + return False + if not self.label or not other.label: + return False + if self.label == other.label: + return True + return False + + def relevant(self): + return self.initialDescriptionType.fully_active() and self.targetDescriptionType.fully_active() + + def slippage(self): + return self.label != slipnet.sameness and self.label != slipnet.identity + + def symmetricVersion(self): + if not self.slippage(): + return self + if self.targetDescriptor.getBondCategory(self.initialDescriptor) == self.label: + return self + return ConceptMapping( + self.targetDescriptionType, + self.initialDescriptionType, + self.targetDescriptor, + self.initialDescriptor1, + self.initialObject, + self.targetObject + ) + diff --git a/copycat.py b/copycat.py new file mode 100644 index 0000000..ba538e9 --- /dev/null +++ b/copycat.py @@ -0,0 +1,68 @@ +import sys +import logging +logging.basicConfig( + level=logging.INFO, + #format='%(asctime)s %(filename)s:%(lineno)d %(message)s', + format='%(message)s', + filename='./copycat.log', + filemode='w' +) + +from workspace import workspace +from workspaceFormulas import workspaceFormulas +from slipnet import slipnet +from temperature import temperature +from coderack import coderack +from coderackPressure import coderackPressures + +def updateEverything(): + workspace.updateEverything() + coderack.updateCodelets() + slipnet.update() + workspaceFormulas.updateTemperature() + coderackPressures.calculatePressures() + +def mainLoop(lastUpdate): + temperature.tryUnclamp() + result = lastUpdate + if coderack.codeletsRun - lastUpdate >= slipnet.timeStepLength or not coderack.codeletsRun: + updateEverything() + result = coderack.codeletsRun + logging.debug('Number of codelets: %d' % len(coderack.codelets)) + coderack.chooseAndRunCodelet() + return result + +def runTrial(): + """Run a trial of the copycat algorithm""" + answers = {} + slipnet.reset() + workspace.reset() + coderack.reset() + lastUpdate = 0 + while not workspace.foundAnswer: + lastUpdate = mainLoop(lastUpdate) + if workspace.rule: + answer = workspace.rule.finalAnswer + else: + answer = None + print '%d: %s' % (coderack.codeletsRun,answer) + answers[answer] = answers.get(answer,0) + 1 + logging.debug('codelets used:') + for answer,count in answers.iteritems(): + print '%s:%d' % (answer,count) + +def main(): + #slipnet.setConceptualDepths(50.0) + """Run the program""" + argc = len(sys.argv) + if argc == 4: + workspace.setStrings(initial=sys.argv[1].lower(),modified=sys.argv[2].lower(),target=sys.argv[3].lower()) + elif argc == 1: + workspace.setStrings(initial='abc',modified='abd',target='ijk') + else: + print >> sys.stderr, 'Usage: %s [initial modified target]' % sys.argv[0] + return + runTrial() + +if __name__ == '__main__': + main() diff --git a/correspondence.py b/correspondence.py new file mode 100644 index 0000000..b3e6c7c --- /dev/null +++ b/correspondence.py @@ -0,0 +1,189 @@ +from workspace import workspace +from workspaceStructure import WorkspaceStructure +from formulas import getMappings + +class Correspondence(WorkspaceStructure): + def __init__(self,objectFromInitial,objectFromTarget,conceptMappings,flipTargetObject): + WorkspaceStructure.__init__(self) + self.objectFromInitial = objectFromInitial + self.objectFromTarget = objectFromTarget + self.conceptMappings = conceptMappings + self.flipTargetObject = flipTargetObject + self.accessoryConceptMappings = [] + + def __repr__(self): + return '<%s>' % self.__str__() + + def __str__(self): + return 'Correspondence between %s and %s' % (self.objectFromInitial,self.objectFromTarget) + + def distinguishingConceptMappings(self): + return [ m for m in self.conceptMappings if m.distinguishing() ] + + def relevantDistinguishingConceptMappings(self): + return [ m for m in self.conceptMappings if m.distinguishing() and m.relevant() ] + + def extract_target_bond(self): + targetBond = False + if self.objectFromTarget.leftmost: + targetBond = self.objectFromTarget.rightBond + elif self.objectFromTarget.rightmost: + targetBond = self.objectFromTarget.leftBond + return targetBond + + def extract_initial_bond(self): + initialBond = False + if self.objectFromInitial.leftmost: + initialBond = self.objectFromInitial.rightBond + elif self.objectFromInitial.rightmost: + initialBond = self.objectFromInitial.leftBond + return initialBond + + def getIncompatibleBond(self): + initialBond = self.extract_initial_bond() + if not initialBond: + return None + targetBond = self.extract_target_bond() + if not targetBond: + return None + from conceptMapping import ConceptMapping + from slipnet import slipnet + if initialBond.directionCategory and targetBond.directionCategory: + mapping = ConceptMapping( + slipnet.directionCategory, + slipnet.directionCategory, + initialBond.directionCategory, + targetBond.directionCategory, + None, + None + ) + for m in self.conceptMappings: + if m.incompatible(mapping): + return targetBond + return None + + def getIncompatibleCorrespondences(self): + return [ o.correspondence for o in workspace.initial.objects if o and self.incompatible(o.correspondence) ] + + def incompatible(self,other): + if not other: + return False + if self.objectFromInitial == other.objectFromInitial: + return True + if self.objectFromTarget == other.objectFromTarget: + return True + for mapping in self.conceptMappings: + for otherMapping in other.conceptMappings: + if mapping.incompatible(otherMapping): + return True + return False + + def supporting(self,other): + if self == other: + return False + if self.objectFromInitial == other.objectFromInitial: + return False + if self.objectFromTarget == other.objectFromTarget: + return False + if self.incompatible(other): + return False + for mapping in self.distinguishingConceptMappings(): + for otherMapping in other.distinguishingConceptMappings(): + if mapping.supports(otherMapping): + return True + return False + + def support(self): + from letter import Letter + if isinstance(self.objectFromInitial,Letter) and self.objectFromInitial.spansString(): + return 100.0 + if isinstance(self.objectFromTarget,Letter) and self.objectFromTarget.spansString(): + return 100.0 + total = sum([ c.totalStrength for c in workspace.correspondences() if self.supporting(c) ]) + total = min(total,100.0) + return total + + def updateInternalStrength(self): + """A function of how many conceptMappings there are, their strength and how well they cohere""" + relevantDistinguishingMappings = self.relevantDistinguishingConceptMappings() + numberOfConceptMappings = len(relevantDistinguishingMappings) + if numberOfConceptMappings < 1: + self.internalStrength = 0.0 + return + totalStrength = sum([ m.strength() for m in relevantDistinguishingMappings ]) + averageStrength = totalStrength / numberOfConceptMappings + if numberOfConceptMappings == 1.0: + numberOfConceptMappingsFactor = 0.8 + elif numberOfConceptMappings == 2.0: + numberOfConceptMappingsFactor = 1.2 + else: + numberOfConceptMappingsFactor = 1.6 + if self.internallyCoherent(): + internalCoherenceFactor = 2.5 + else: + internalCoherenceFactor = 1.0 + internalStrength = averageStrength * internalCoherenceFactor * numberOfConceptMappingsFactor + self.internalStrength = min(internalStrength,100.0) + + def updateExternalStrength(self): + self.externalStrength = self.support() + + def internallyCoherent(self): + """Whether any pair of relevant distinguishing mappings support each other""" + mappings = self.relevantDistinguishingConceptMappings() + for i in range(0,len(mappings)): + for j in range(0,len(mappings)): + if i != j: + if mappings[i].supports(mappings[j]): + return True + return False + + def slippages(self): + mappings = [ m for m in self.conceptMappings if m.slippage() ] + mappings += [ m for m in self.accessoryConceptMappings if m.slippage() ] + return mappings + + def reflexive(self): + if not self.objectFromInitial.correspondence: + return False + if self.objectFromInitial.correspondence.objectFromTarget == self.objectFromTarget: + return True + return False + + def buildCorrespondence(self): + workspace.structures += [ self ] + if self.objectFromInitial.correspondence: + self.objectFromInitial.correspondence.breakCorrespondence() + if self.objectFromTarget.correspondence: + self.objectFromTarget.correspondence.breakCorrespondence() + self.objectFromInitial.correspondence = self + self.objectFromTarget.correspondence = self + # add mappings to accessory-concept-mapping-list + relevantMappings = self.relevantDistinguishingConceptMappings() + for mapping in relevantMappings: + if mapping.slippage(): + self.accessoryConceptMappings += [ mapping.symmetricVersion() ] + from group import Group + if isinstance(self.objectFromInitial,Group) and isinstance(self.objectFromTarget,Group): + bondMappings = getMappings( + self.objectFromInitial, + self.objectFromTarget, + self.objectFromInitial.bondDescriptions, + self.objectFromTarget.bondDescriptions + ) + for mapping in bondMappings: + self.accessoryConceptMappings += [ mapping ] + if mapping.slippage(): + self.accessoryConceptMappings += [ mapping.symmetricVersion() ] + for mapping in self.conceptMappings: + if mapping.label: + mapping.label.activation = 100.0 + + def break_the_structure(self): + self.breakCorrespondence() + + def breakCorrespondence(self): + workspace.structures.remove(self) + self.objectFromInitial.correspondence = None + self.objectFromTarget.correspondence = None + diff --git a/description.py b/description.py new file mode 100644 index 0000000..b8b30bf --- /dev/null +++ b/description.py @@ -0,0 +1,56 @@ +import logging +from workspaceStructure import WorkspaceStructure + +class Description(WorkspaceStructure): + def __init__(self,workspaceObject,descriptionType,descriptor): + WorkspaceStructure.__init__(self) + self.object = workspaceObject + self.string = workspaceObject.string + self.descriptionType = descriptionType + self.descriptor = descriptor + + def __repr__(self): + return '' % self.__str__() + + def __str__(self): + s = 'description(%s) of %s' % (self.descriptor.get_name(),self.object) + from workspace import workspace + if self.object.string == workspace.initial: + s += ' in initial string' + else: + s += ' in target string' + return s + + def updateInternalStrength(self): + self.internalStrength = self.descriptor.conceptualDepth + + def updateExternalStrength(self): + self.externalStrength = (self.localSupport() + self.descriptionType.activation) / 2 + + def localSupport(self): + from workspace import workspace + supporters = 0 # number of objects in the string with a descriptionType like self + for other in workspace.otherObjects(self.object): + if not ( self.object.isWithin(other) or other.isWithin(self.object) ): + for description in other.descriptions: + if description.descriptionType == self.descriptionType: + supporters += 1 + results = { 0:0.0, 1:20.0, 2:60.0, 3:90.0 } + if supporters in results: + return results[supporters] + return 100.0 + + def build(self): + self.descriptionType.buffer = 100.0 + self.descriptor.buffer = 100.0 + if not self.object.hasDescription(self.descriptor): + logging.info('Add %s to descriptions' % self) + self.object.descriptions += [ self ] + + def breakDescription(self): + from workspace import workspace + if self in workspace.structures: + workspace.structures.remove(self) + self.object.descriptions.remove(self) + + diff --git a/formulas.py b/formulas.py new file mode 100644 index 0000000..2ac26a2 --- /dev/null +++ b/formulas.py @@ -0,0 +1,146 @@ +import math #, random +import logging + +import utils + +from temperature import temperature + +actualTemperature = Temperature = 100.0 + +def selectListPosition(probabilities): + total = sum(probabilities) + #logging.info('total: %s' % total) + r = utils.random() + stopPosition = total * r + #logging.info('stopPosition: %s' % stopPosition) + total = 0 + index = 0 + for probability in probabilities: + total += probability + if total > stopPosition: + return index + index += 1 + return 0 + +def weightedAverage(values): + total = 0.0 + totalWeights = 0.0 + for value,weight in values: + total += value * weight + totalWeights += weight + if not totalWeights: + return 0.0 + return total / totalWeights + +def temperatureAdjustedValue(value): + #logging.info('Temperature: %s' % Temperature) + #logging.info('actualTemperature: %s' % actualTemperature) + return value ** (((100.0-Temperature)/30.0)+0.5) + +def temperatureAdjustedProbability(value): + if not value or value == 0.5 or not temperature.value: + return value + if value < 0.5: + return 1.0 - temperatureAdjustedProbability(1.0 - value) + coldness = 100.0 - temperature.value + a = math.sqrt(coldness) + b = 10.0 - a + c = b / 100 + d = c * ( 1.0 - ( 1.0 - value ) ) # aka c * value, but we're following the java + e = ( 1.0 - value ) + d + f = 1.0 - e + return max(f,0.5) + +def coinFlip(chance=0.5): + return utils.random() < chance + +def blur(value): + root = math.sqrt(value) + if coinFlip(): + return value + root + return value - root + +def chooseObjectFromList(objects,attribute): + if not objects: + return None + probabilities = [] + for object in objects: + value = getattr(object,attribute) + probability = temperatureAdjustedValue(value) + logging.info('Object: %s, value: %d, probability: %d' % (object,value,probability)) + probabilities += [ probability ] + index = selectListPosition(probabilities) + logging.info("Selected: %d" % index) + return objects[index] + +def chooseRelevantDescriptionByActivation(workspaceObject): + descriptions = workspaceObject.relevantDescriptions() + if not descriptions: + return None + activations = [ description.descriptor.activation for description in descriptions ] + index = selectListPosition(activations) + return descriptions[ index ] + +def similarPropertyLinks(slip_node): + result = [] + for slip_link in slip_node.propertyLinks: + association = slip_link.degreeOfAssociation() / 100.0 + probability = temperatureAdjustedProbability( association ) + if coinFlip(probability): + result += [ slip_link ] + return result + +def chooseSlipnodeByConceptualDepth(slip_nodes): + if not slip_nodes: + return None + depths = [ temperatureAdjustedValue(n.conceptualDepth) for n in slip_nodes ] + i = selectListPosition(depths) + return slip_nodes[ i ] + +def __relevantCategory(objekt,slipnode): + return objekt.rightBond and objekt.rightBond.category == slipnode + +def __relevantDirection(objekt,slipnode): + return objekt.rightBond and objekt.rightBond.directionCategory == slipnode + +def __localRelevance(string,slipnode,relevance): + numberOfObjectsNotSpanning = numberOfMatches = 0.0 + #logging.info("find relevance for a string: %s" % string); + for objekt in string.objects: + #logging.info('object: %s' % objekt) + if not objekt.spansString(): + #logging.info('non spanner: %s' % objekt) + numberOfObjectsNotSpanning += 1.0 + if relevance(objekt,slipnode): + numberOfMatches += 1.0 + #logging.info("matches: %d, not spanning: %d" % (numberOfMatches,numberOfObjectsNotSpanning)) + if numberOfObjectsNotSpanning == 1: + return 100.0 * numberOfMatches + return 100.0 * numberOfMatches / (numberOfObjectsNotSpanning - 1.0) + +def localBondCategoryRelevance(string, category): + if len(string.objects) == 1: + return 0.0 + return __localRelevance(string,category,__relevantCategory) + +def localDirectionCategoryRelevance(string, direction): + return __localRelevance(string,direction,__relevantDirection) + +def getMappings(objectFromInitial,objectFromTarget, initialDescriptions, targetDescriptions): + mappings = [] + from conceptMapping import ConceptMapping + for initialDescription in initialDescriptions: + for targetDescription in targetDescriptions: + if initialDescription.descriptionType == targetDescription.descriptionType: + if initialDescription.descriptor == targetDescription.descriptor or initialDescription.descriptor.slipLinked(targetDescription.descriptor): + mapping = ConceptMapping( + initialDescription.descriptionType, + targetDescription.descriptionType, + initialDescription.descriptor, + targetDescription.descriptor, + objectFromInitial, + objectFromTarget + ) + mappings += [ mapping ] + return mappings + diff --git a/group.py b/group.py new file mode 100644 index 0000000..9244866 --- /dev/null +++ b/group.py @@ -0,0 +1,237 @@ +import utils, logging + +from workspace import workspace +from workspaceObject import WorkspaceObject +from slipnet import slipnet +import formulas + +class Group(WorkspaceObject): + def __init__(self,string,groupCategory,directionCategory,facet,objectList,bondList): + WorkspaceObject.__init__(self,string) + self.groupCategory = groupCategory + self.directionCategory = directionCategory + self.facet = facet + self.objectList = objectList + self.bondList = bondList + self.bondCategory = self.groupCategory.getRelatedNode(slipnet.bondCategory) + + leftObject = objectList[0] + rightObject = objectList[-1] + self.leftStringPosition = leftObject.leftStringPosition + self.leftmost = self.leftStringPosition == 1 + self.rightStringPosition = rightObject.rightStringPosition + self.rightmost = self.rightStringPosition == len(self.string) + + self.descriptions = [] + self.bondDescriptions = [] + self.extrinsicDescriptions = [] + self.outgoingBonds = [] + self.incomingBonds = [] + self.bonds = [] + self.leftBond = None + self.rightBond = None + self.correspondence = None + self.changed = False + self.newAnswerLetter = False + self.clampSalience = False + self.name = '' + + + from description import Description + if self.bondList and len(self.bondList): + firstFacet = self.bondList[0].facet + self.addBondDescription(Description(self, slipnet.bondFacet, firstFacet)) + self.addBondDescription(Description(self, slipnet.bondCategory, self.bondCategory)) + + self.addDescription(slipnet.objectCategory, slipnet.group) + self.addDescription(slipnet.groupCategory, self.groupCategory) + if not self.directionCategory: + # sameness group - find letterCategory + letter = self.objectList[0].getDescriptor(self.facet) + self.addDescription(self.facet, letter) + if self.directionCategory: + self.addDescription(slipnet.directionCategory, self.directionCategory) + if self.spansString(): + self.addDescription(slipnet.stringPositionCategory, slipnet.whole) + elif self.leftStringPosition == 1: + self.addDescription(slipnet.stringPositionCategory, slipnet.leftmost) + elif self.rightStringPosition == self.string.length: + self.addDescription(slipnet.stringPositionCategory, slipnet.rightmost) + elif self.middleObject(): + self.addDescription(slipnet.stringPositionCategory, slipnet.middle) + + #check whether or not to add length description category + probability = self.lengthDescriptionProbability() + if utils.random() < probability: + length = len(self.objectList) + if length < 6: + self.addDescription(slipnet.length, slipnet.numbers[length - 1]) + + def __str__(self): + s = self.string.__str__() + l = self.leftStringPosition - 1 + r = self.rightStringPosition + return 'group[%d:%d] == %s' % ( l, r - 1, s[l: r ]) + + def getIncompatibleGroups(self): + result = [] + for objekt in self.objectList: + while objekt.group: + result += [ objekt.group ] + objekt = objekt.group + return result + + def addBondDescription(self,description): + self.bondDescriptions += [ description ] + + def singleLetterGroupProbability(self): + numberOfSupporters = self.numberOfLocalSupportingGroups() + if not numberOfSupporters: + return 0.0 + if numberOfSupporters == 1: + exp = 4.0 + elif numberOfSupporters == 2: + exp = 2.0 + else: + exp = 1.0 + support = self.localSupport() / 100.0 + activation = slipnet.length.activation / 100.0 + supportedActivation = (support * activation) ** exp + return formulas.temperatureAdjustedProbability(supportedActivation) + + def flippedVersion(self): + flippedBonds = [ b.flippedversion() for b in self.bondList ] + flippedGroup = self.groupCategory.getRelatedNode(slipnet.flipped) + flippedDirection = self.directionCategory.getRelatedNode(slipnet.flipped) + return Group(self.string, flippedGroup, flippedDirection, self.facet, self.objectList, flippedBonds) + + def buildGroup(self): + workspace.objects += [ self ] + workspace.structures += [ self ] + self.string.objects += [ self ] + for objekt in self.objectList: + objekt.group = self + workspace.buildDescriptions(self) + self.activateDescriptions() + + def activateDescriptions(self): + for description in self.descriptions: + logging.info('Activate: %s' % description) + description.descriptor.buffer = 100.0 + + def lengthDescriptionProbability(self): + length = len(self.objectList) + if length > 5: + return 0.0 + cubedlength = length ** 3 + fred = cubedlength * (100.0 - slipnet.length.activation) / 100.0 + probability = 0.5 ** fred + value = formulas.temperatureAdjustedProbability(probability) + if value < 0.06: + value = 0.0 # otherwise 1/20 chance always + return value + + def break_the_structure(self): + self.breakGroup() + + def breakGroup(self): + while len(self.descriptions): + description = self.descriptions[-1] + description.breakDescription() + for objekt in self.objectList: + objekt.group = None + if self.group: + self.group.breakGroup() + if self in workspace.structures: + workspace.structures.remove(self) + if self in workspace.objects: + workspace.objects.remove(self) + if self in self.string.objects: + self.string.objects.remove(self) + if self.correspondence: + self.correspondence.breakCorrespondence() + if self.leftBond: + self.leftBond.breakBond() + if self.rightBond: + self.rightBond.breakBond() + + def updateInternalStrength(self): + relatedBondAssociation = self.groupCategory.getRelatedNode(slipnet.bondCategory).degreeOfAssociation() + bondWeight = relatedBondAssociation ** 0.98 + length = len(self.objectList) + if length == 1: + lengthFactor = 5.0 + elif length == 2: + lengthFactor = 20.0 + elif length == 3: + lengthFactor = 60.0 + else: + lengthFactor = 90.0 + lengthWeight = 100.0 - bondWeight + weightList = ( (relatedBondAssociation, bondWeight), (lengthFactor, lengthWeight) ) + self.internalStrength = formulas.weightedAverage(weightList) + + def updateExternalStrength(self): + if self.spansString(): + self.externalStrength = 100.0 + else: + self.externalStrength = self.localSupport() + + def localSupport(self): + numberOfSupporters = self.numberOfLocalSupportingGroups() + if numberOfSupporters == 0.0: + return 0.0 + supportFactor = min(1.0,0.6 ** (1 / (numberOfSupporters ** 3 ))) + densityFactor = 100.0 * ((self.localDensity() / 100.0) ** 0.5) + return densityFactor * supportFactor + + def numberOfLocalSupportingGroups(self): + count = 0 + for objekt in self.string.objects: + if isinstance(objekt,Group): + if objekt.rightStringPosition < self.leftStringPosition or objekt.leftStringPosition > self.rightStringPosition: + if objekt.groupCategory == self.groupCategory and objekt.directionCategory == self.directionCategory: + count += 1 + return count + + def localDensity(self): + numberOfSupporters = self.numberOfLocalSupportingGroups() + halfLength = len(self.string) / 2.0 + return 100.0 * numberOfSupporters / halfLength + + def sameGroup(self,other): + if self.leftStringPosition != other.leftStringPosition: + return False + if self.rightStringPosition != other.rightStringPosition: + return False + if self.groupCategory != other.groupCategory: + return False + if self.directionCategory != other.directionCategory: + return False + if self.facet != other.facet: + return False + return True + + def morePossibleDescriptions(self,node): + result = [] + i = 1 + for number in slipnet.numbers: + if node == number and len(self.objects) == i: + result += [ node ] + i += 1 + return result + + def distinguishingDescriptor(self,descriptor): + """Whether no other object of the same type (group) has the same descriptor""" + if not WorkspaceObject.distinguishingDescriptor(self,descriptor): + return False + for objekt in self.string.objects: + # check to see if they are of the same type + if isinstance(objekt,Group) and objekt != self: + # check all descriptions for the descriptor + for description in objekt.descriptions: + if description.descriptor == descriptor: + return False + return True + + diff --git a/grouprun.py b/grouprun.py new file mode 100644 index 0000000..a7f5395 --- /dev/null +++ b/grouprun.py @@ -0,0 +1,14 @@ +from workspace import workspace + +class GroupRun(object): + def __init__(self): + self.name = 'xxx' + self.maximumNumberOfRuns = 1000 + self.runStrings = [] + self.answers = [] + self.scores = [0] * 100 + self.initial = workspace.initial + self.modified = workspace.modified + self.target = workspace.target + +groupRun = GroupRun() diff --git a/letter.py b/letter.py new file mode 100644 index 0000000..55d8898 --- /dev/null +++ b/letter.py @@ -0,0 +1,48 @@ +from workspaceObject import WorkspaceObject +from slipnet import slipnet + +class Letter(WorkspaceObject): + def __init__(self,string,position,length): + WorkspaceObject.__init__(self,string) + from workspace import workspace + workspace.objects += [ self ] + string.objects += [self ] + self.leftStringPosition = position + self.leftmost = self.leftStringPosition == 1 + self.rightStringPosition = position + self.rightmost = self.rightStringPosition == length + + def describe(self,position,length): + if length == 1: + self.addDescription(slipnet.stringPositionCategory,slipnet.single) + if self.leftmost and length > 1: # ? why check length ? + self.addDescription(slipnet.stringPositionCategory,slipnet.leftmost) + if self.rightmost and length > 1: # ? why check length ? + self.addDescription(slipnet.stringPositionCategory,slipnet.rightmost) + if length > 2 and position * 2 == length + 1: + self.addDescription(slipnet.stringPositionCategory,slipnet.middle) + + def __repr__(self): + return '' % self.__str__() + + def __str__(self): + if not self.string: + return '' + i = self.leftStringPosition - 1 + if len(self.string) <= i: + raise ValueError, 'len(self.string) <= self.leftStringPosition :: %d <= %d' % (len(self.string),self.leftStringPosition) + return self.string[ i ] + + def distinguishingDescriptor(self,descriptor): + """Whether no other object of the same type (letter) has the same descriptor""" + if not WorkspaceObject.distinguishingDescriptor(self,descriptor): + return False + for objekt in self.string.objects: + # check to see if they are of the same type + if isinstance(objekt,Letter) and objekt != self: + # check all descriptions for the descriptor + for description in objekt.descriptions: + if description.descriptor == descriptor: + return False + return True + diff --git a/replacement.py b/replacement.py new file mode 100644 index 0000000..ea65a75 --- /dev/null +++ b/replacement.py @@ -0,0 +1,9 @@ +from workspaceStructure import WorkspaceStructure + +class Replacement(WorkspaceStructure): + def __init__(self, objectFromInitial, objectFromModified, relation): + WorkspaceStructure.__init__(self) + self.objectFromInitial = objectFromInitial + self.objectFromModified = objectFromModified + self.relation = relation + diff --git a/rule.py b/rule.py new file mode 100644 index 0000000..8eca725 --- /dev/null +++ b/rule.py @@ -0,0 +1,134 @@ +from slipnet import slipnet +from workspace import workspace +from workspaceStructure import WorkspaceStructure +from formulas import * + +class Rule(WorkspaceStructure): + def __init__(self,facet,descriptor,category,relation): + WorkspaceStructure.__init__(self) + self.facet = facet + self.descriptor = descriptor + self.category = category + self.relation = relation + + def __str__(self): + if not self.facet: + return 'Empty rule' + return 'replace %s of %s %s by %s' % (self.facet.name, self.descriptor.name, self.category.name, self.relation.name) + + def updateExternalStrength(self): + self.externalStrength = self.internalStrength + + def updateInternalStrength(self): + if not ( self.descriptor and self.relation ): + self.internalStrength = 0.0 + return + averageDepth = ( self.descriptor.conceptualDepth + self.relation.conceptualDepth ) / 2.0 + averageDepth **= 1.1 + # see if the object corresponds to an object + # if so, see if the descriptor is present (modulo slippages) in the + # corresponding object + changedObjects = [ o for o in workspace.initial.objects if o.changed ] + changed = changedObjects[0] + sharedDescriptorTerm = 0.0 + if changed and changed.correspondence: + targetObject = changed.correspondence.objectFromTarget + slippages = workspace.slippages() + slipnode = self.descriptor.applySlippages(slippages) + if not targetObject.hasDescription(slipnode): + self.internalStrength = 0.0 + return + sharedDescriptorTerm = 100.0 + sharedDescriptorWeight = ((100.0 - self.descriptor.conceptualDepth) / 10.0) ** 1.4 + depthDifference = 100.0 - abs(self.descriptor.conceptualDepth - self.relation.conceptualDepth) + weights = ( (depthDifference,12), (averageDepth,18), (sharedDescriptorTerm,sharedDescriptorWeight) ) + self.internalStrength = weightedAverage( weights ) + if self.internalStrength > 100.0: + self.internalStrength = 100.0 + + def ruleEqual(self,other): + if not other: + return False + if self.relation != other.relation: + return False + if self.facet != other.facet: + return False + if self.category != other.category: + return False + if self.descriptor != other.descriptor: + return False + return True + + def activateRuleDescriptions(self): + if self.relation: + self.relation.buffer = 100.0 + if self.facet: + self.facet.buffer = 100.0 + if self.category: + self.category.buffer = 100.0 + if self.descriptor: + self.descriptor.buffer = 100.0 + + def incompatibleRuleCorrespondence(self, correspondence): + if not correspondence: + return False + # find changed object + changeds = [ o for o in workspace.initial.objects if o.changed ] + if not changeds: + return False + changed = changeds[0] + if correspondence.objectFromInitial != changed: + return False + # it is incompatible if the rule descriptor is not in the mapping list + if len([ m for m in correspondence.conceptMappings if m.initialDescriptor == self.descriptor ]): + return False + return True + + def __changeString(self,string): + # applies the changes to self string ie. successor + if self.facet == slipnet.length: + if self.relation == slipnet.predecessor: + return string[0:-1] + if self.relation == slipnet.successor: + return string + string[0:1] + return string + # apply character changes + if self.relation == slipnet.predecessor: + if 'a' in string: + return None + return ''.join([ chr(ord(c) - 1) for c in string]) + elif self.relation == slipnet.successor: + if 'z' in string: + return None + return ''.join([ chr(ord(c) + 1) for c in string]) + else: + return self.relation.name.lower() + + def buildTranslatedRule(self): + slippages = workspace.slippages() + self.category = self.category.applySlippages(slippages) + self.facet = self.facet.applySlippages(slippages) + self.descriptor = self.descriptor.applySlippages(slippages) + self.relation = self.relation.applySlippages(slippages) + # generate the final string + self.finalAnswer = workspace.targetString + changeds = [ o for o in workspace.target.objects if + o.hasDescription(self.descriptor) and + o.hasDescription(self.category) ] + changed = changeds and changeds[0] or None + logging.debug('changed object = %s' % changed) + if changed: + left = changed.leftStringPosition + startString = '' + if left > 1: + startString = self.finalAnswer[0: left - 1] + right = changed.rightStringPosition + middleString = self.__changeString(self.finalAnswer[left - 1: right]) + if not middleString: + return False + endString = '' + if right < len(self.finalAnswer): + endString = self.finalAnswer[right:] + self.finalAnswer = startString + middleString + endString + return True + diff --git a/sliplink.py b/sliplink.py new file mode 100644 index 0000000..81a5afa --- /dev/null +++ b/sliplink.py @@ -0,0 +1,28 @@ +#from slipnode import Slipnode + +class Sliplink(object): + def __init__(self, source, destination, label=None, length=0.0): + self.source = source + self.destination = destination + self.label = label + self.fixedLength = length + source.outgoingLinks += [self] + destination.incomingLinks += [self] + + def degreeOfAssociation(self): + if self.fixedLength > 0 or not self.label: + return 100.0 - self.fixedLength + return self.label.degreeOfAssociation() + + def intrinsicDegreeOfAssociation(self): + if self.fixedLength > 1: + return 100.0 - self.fixedLength + if self.label: + return 100.0 - self.label.intrinsicLinkLength + return 0.0 + + def spread_activation(self): + self.destination.buffer += self.intrinsicDegreeOfAssociation() + + def points_at(self, other): + return self.destination == other \ No newline at end of file diff --git a/slipnet.py b/slipnet.py new file mode 100644 index 0000000..4c9b348 --- /dev/null +++ b/slipnet.py @@ -0,0 +1,252 @@ +import logging + +from slipnode import Slipnode +from sliplink import Sliplink + +class SlipNet(object): + def __init__(self): + logging.debug("SlipNet.__init__()") + self.initiallyClampedSlipnodes = [] + self.slipnodes = [] + self.sliplinks = [] + self.bondFacets = [] + self.timeStepLength = 15 + self.numberOfUpdates = 0 + self.__addInitialNodes() + self.__addInitialLinks() + self.predecessor = None + + def __repr__(self): + return '' + + def setConceptualDepths(self, depth): + logging.debug('slipnet set all depths to %f' % depth) + [node.setConceptualDepth(depth) for node in self.slipnodes] + + def reset(self): + logging.debug('slipnet.reset()') + self.numberOfUpdates = 0 + for node in self.slipnodes: + node.reset() + if node in self.initiallyClampedSlipnodes: + node.clampHigh() + + def update(self): + logging.debug('slipnet.update()') + self.numberOfUpdates += 1 + if self.numberOfUpdates == 50: + [node.unclamp() for node in self.initiallyClampedSlipnodes] + [node.update() for node in self.slipnodes] + # Note - spreadActivation() affects more than one node, so the following + # cannot go in a general "for node" loop with the other node actions + for node in self.slipnodes: + node.spread_activation() + for node in self.slipnodes: + node.addBuffer() + node.jump() + node.buffer = 0.0 + + def __addInitialNodes(self): + self.slipnodes = [] + self.letters = [] + for c in 'abcdefghijklmnopqrstuvwxyz': + slipnode = self.__addNode(c, 10.0) + self.letters += [slipnode] + self.numbers = [] + for c in '12345': + slipnode = self.__addNode(c, 30.0) + self.numbers += [slipnode] + + # string positions + self.leftmost = self.__addNode('leftmost', 40.0) + self.rightmost = self.__addNode('rightmost', 40.0) + self.middle = self.__addNode('middle', 40.0) + self.single = self.__addNode('single', 40.0) + self.whole = self.__addNode('whole', 40.0) + + # alphabetic positions + self.first = self.__addNode('first', 60.0) + self.last = self.__addNode('last', 60.0) + + # directions + self.left = self.__addNode('left', 40.0) + self.left.codelets += ['top-down-bond-scout--direction'] + self.left.codelets += ['top-down-group-scout--direction'] + self.right = self.__addNode('right', 40.0) + self.right.codelets += ['top-down-bond-scout--direction'] + self.right.codelets += ['top-down-group-scout--direction'] + + # bond types + self.predecessor = self.__addNode('predecessor', 50.0, 60.0) + self.predecessor.codelets += ['top-down-bond-scout--category'] + self.successor = self.__addNode('successor', 50.0, 60.0) + self.successor.codelets += ['top-down-bond-scout--category'] + self.sameness = self.__addNode('sameness', 80.0) + self.sameness.codelets += ['top-down-bond-scout--category'] + + # group types + self.predecessorGroup = self.__addNode('predecessorGroup', 50.0) + self.predecessorGroup.codelets += ['top-down-group-scout--category'] + self.successorGroup = self.__addNode('successorGroup', 50.0) + self.successorGroup.codelets += ['top-down-group-scout--category'] + self.samenessGroup = self.__addNode('samenessGroup', 80.0) + self.samenessGroup.codelets += ['top-down-group-scout--category'] + + # other relations + self.identity = self.__addNode('identity', 90.0) + self.opposite = self.__addNode('opposite', 90.0, 80.0) + + # objects + self.letter = self.__addNode('letter', 20.0) + self.group = self.__addNode('group', 80.0) + + # categories + self.letterCategory = self.__addNode('letterCategory', 30.0) + self.stringPositionCategory = self.__addNode('stringPositionCategory', 70.0) + self.stringPositionCategory.codelets += ['top-down-description-scout'] + self.alphabeticPositionCategory = self.__addNode('alphabeticPositionCategory', 80.0) + self.alphabeticPositionCategory.codelets += ['top-down-description-scout'] + self.directionCategory = self.__addNode('directionCategory', 70.0) + self.bondCategory = self.__addNode('bondCategory', 80.0) + self.groupCategory = self.__addNode('groupCategory', 80.0) + self.length = self.__addNode('length', 60.0) + self.objectCategory = self.__addNode('objectCategory', 90.0) + self.bondFacet = self.__addNode('bondFacet', 90.0) + + # specify the descriptor types that bonds can form between + self.bondFacets += [self.letterCategory] + self.bondFacets += [self.length] + + # + self.initiallyClampedSlipnodes += [self.letterCategory] + self.letterCategory.clamped = True + self.initiallyClampedSlipnodes += [self.stringPositionCategory] + self.stringPositionCategory.clamped = True + + def __addInitialLinks(self): + self.sliplinks = [] + self.__link_items_to_their_neighbours(self.letters) + self.__link_items_to_their_neighbours(self.numbers) + # letter categories + for letter in self.letters: + self.__addInstanceLink(self.letterCategory, letter, 97.0) + self.__addCategoryLink(self.samenessGroup, self.letterCategory, 50.0) + # lengths + for number in self.numbers: + self.__addInstanceLink(self.length, number, 100.0) + self.__addNonSlipLink(self.predecessorGroup, self.length, length=95.0) + self.__addNonSlipLink(self.successorGroup, self.length, length=95.0) + self.__addNonSlipLink(self.samenessGroup, self.length, length=95.0) + # opposites + self.__addOppositeLink(self.first, self.last) + self.__addOppositeLink(self.leftmost, self.rightmost) + self.__addOppositeLink(self.left, self.right) + self.__addOppositeLink(self.successor, self.predecessor) + self.__addOppositeLink(self.successorGroup, self.predecessorGroup) + # properties + self.__addPropertyLink(self.letters[0], self.first, 75.0) + self.__addPropertyLink(self.letters[-1], self.last, 75.0) + # object categories + self.__addInstanceLink(self.objectCategory, self.letter, 100.0) + self.__addInstanceLink(self.objectCategory, self.group, 100.0) + # string positions + self.__addInstanceLink(self.stringPositionCategory, self.leftmost, 100.0) + self.__addInstanceLink(self.stringPositionCategory, self.rightmost, 100.0) + self.__addInstanceLink(self.stringPositionCategory, self.middle, 100.0) + self.__addInstanceLink(self.stringPositionCategory, self.single, 100.0) + self.__addInstanceLink(self.stringPositionCategory, self.whole, 100.0) + # alphabetic positions + self.__addInstanceLink(self.alphabeticPositionCategory, self.first, 100.0) + self.__addInstanceLink(self.alphabeticPositionCategory, self.last, 100.0) + # direction categories + self.__addInstanceLink(self.directionCategory, self.left, 100.0) + self.__addInstanceLink(self.directionCategory, self.right, 100.0) + # bond categories + self.__addInstanceLink(self.bondCategory, self.predecessor, 100.0) + self.__addInstanceLink(self.bondCategory, self.successor, 100.0) + self.__addInstanceLink(self.bondCategory, self.sameness, 100.0) + # group categories + self.__addInstanceLink(self.groupCategory, self.predecessorGroup, 100.0) + self.__addInstanceLink(self.groupCategory, self.successorGroup, 100.0) + self.__addInstanceLink(self.groupCategory, self.samenessGroup, 100.0) + # link bonds to their groups + self.__addNonSlipLink(self.sameness, self.samenessGroup, label=self.groupCategory, length=30.0) + self.__addNonSlipLink(self.successor, self.successorGroup, label=self.groupCategory, length=60.0) + self.__addNonSlipLink(self.predecessor, self.predecessorGroup, label=self.groupCategory, length=60.0) + # link bond groups to their bonds + self.__addNonSlipLink(self.samenessGroup, self.sameness, label=self.bondCategory, length=90.0) + self.__addNonSlipLink(self.successorGroup, self.successor, label=self.bondCategory, length=90.0) + self.__addNonSlipLink(self.predecessorGroup, self.predecessor, label=self.bondCategory, length=90.0) + # bond facets + self.__addInstanceLink(self.bondFacet, self.letterCategory, 100.0) + self.__addInstanceLink(self.bondFacet, self.length, 100.0) + # letter category to length + self.__addSlipLink(self.letterCategory, self.length, length=95.0) + self.__addSlipLink(self.length, self.letterCategory, length=95.0) + # letter to group + self.__addSlipLink(self.letter, self.group, length=90.0) + self.__addSlipLink(self.group, self.letter, length=90.0) + # direction-position, direction-neighbour, position-neighbour + self.__addBidirectionalLink(self.left, self.leftmost, 90.0) + self.__addBidirectionalLink(self.right, self.rightmost, 90.0) + self.__addBidirectionalLink(self.right, self.leftmost, 100.0) + self.__addBidirectionalLink(self.left, self.rightmost, 100.0) + self.__addBidirectionalLink(self.leftmost, self.first, 100.0) + self.__addBidirectionalLink(self.rightmost, self.first, 100.0) + self.__addBidirectionalLink(self.leftmost, self.last, 100.0) + self.__addBidirectionalLink(self.rightmost, self.last, 100.0) + # other + self.__addSlipLink(self.single, self.whole, length=90.0) + self.__addSlipLink(self.whole, self.single, length=90.0) + + def __addLink(self, source, destination, label=None, length=0.0): + link = Sliplink(source, destination, label=label, length=length) + self.sliplinks += [link] + return link + + def __addSlipLink(self, source, destination, label=None, length=0.0): + link = self.__addLink(source, destination, label, length) + source.lateralSlipLinks += [link] + + def __addNonSlipLink(self, source, destination, label=None, length=0.0): + link = self.__addLink(source, destination, label, length) + source.lateralNonSlipLinks += [link] + + def __addBidirectionalLink(self, source, destination, length): + self.__addNonSlipLink(source, destination, length=length) + self.__addNonSlipLink(destination, source, length=length) + + def __addCategoryLink(self, source, destination, length): + #noinspection PyArgumentEqualDefault + link = self.__addLink(source, destination, None, length) + source.categoryLinks += [link] + + def __addInstanceLink(self, source, destination, length): + categoryLength = source.conceptualDepth - destination.conceptualDepth + self.__addCategoryLink(destination, source, categoryLength) + #noinspection PyArgumentEqualDefault + link = self.__addLink(source, destination, None, length) + source.instanceLinks += [link] + + def __addPropertyLink(self, source, destination, length): + #noinspection PyArgumentEqualDefault + link = self.__addLink(source, destination, None, length) + source.propertyLinks += [link] + + def __addOppositeLink(self, source, destination): + self.__addSlipLink(source, destination, label=self.opposite) + self.__addSlipLink(destination, source, label=self.opposite) + + def __addNode(self, name, depth, length=0): + slipnode = Slipnode(name, depth, length) + self.slipnodes += [slipnode] + return slipnode + + def __link_items_to_their_neighbours(self,items): + previous = items[0] + for item in items[1:]: + self.__addNonSlipLink(previous, item, label=self.successor) + self.__addNonSlipLink(item, previous, label=self.predecessor) + previous = item + +slipnet = SlipNet() diff --git a/slipnode.py b/slipnode.py new file mode 100644 index 0000000..7d596f2 --- /dev/null +++ b/slipnode.py @@ -0,0 +1,161 @@ +import math +import utils +import logging + +def full_activation(): + return 100 + +def jump_threshold(): + return 55.0 + +class Slipnode(object): + def __init__(self, name, depth, length=0.0): + # logging.info('depth to %s for %s' % (depth,name)) + self.conceptual_depth = depth + self.usualConceptualDepth = depth + self.name = name + self.intrinsicLinkLength = length + self.shrunkLinkLength = length * 0.4 + + self.activation = 0.0 + self.buffer = 0.0 + self.clamped = False + self.bondFacetFactor = 0.0 + self.categoryLinks = [] + self.instanceLinks = [] + self.propertyLinks = [] + self.lateralSlipLinks = [] + self.lateralNonSlipLinks = [] + self.incomingLinks = [] + self.outgoingLinks = [] + self.codelets = [] + self.clampBondDegreeOfAssociation = False + + def __repr__(self): + return '' % self.name + + def reset(self): + self.buffer = 0.0 + self.activation = 0.0 + + def clampHigh(self): + self.clamped = True + self.activation = 100.0 + + def unclamp(self): + self.clamped = False + + def setConceptualDepth(self, depth): + logging.info('set depth to %s for %s' % (depth, self.name)) + self.conceptual_depth = depth + + def category(self): + if not len(self.categoryLinks): + return None + link = self.categoryLinks[0] + return link.destination + + def fully_active(self): + """Whether this node has full activation""" + return self.activation > full_activation() - 0.00001 # allow a little leeway for floats + + def activate_fully(self): + """Make this node fully active""" + self.activation = full_activation() + + def bondDegreeOfAssociation(self): + linkLength = self.intrinsicLinkLength + if (not self.clampBondDegreeOfAssociation) and self.fully_active(): + linkLength = self.shrunkLinkLength + result = math.sqrt(100 - linkLength) * 11.0 + return min(100.0, result) + + def degreeOfAssociation(self): + linkLength = self.intrinsicLinkLength + if self.fully_active(): + linkLength = self.shrunkLinkLength + return 100.0 - linkLength + + def update(self): + act = self.activation + self.oldActivation = act + self.buffer -= self.activation * ( 100.0 - self.conceptual_depth) / 100.0 + + def linked(self, other): + """Whether the other is among the outgoing links""" + return self.points_at(self.outgoingLinks, other) + + def slipLinked(self, other): + """Whether the other is among the lateral links""" + return self.points_at(self.lateralSlipLinks, other) + + def points_at(self, links, other): + """Whether any of the links points at the other""" + return any([l.points_at(other) for l in links]) + + def related(self, other): + """Same or linked""" + return self == other or self.linked(other) + + def applySlippages(self, slippages): + for slippage in slippages: + if self == slippage.initialDescriptor: + return slippage.targetDescriptor + return self + + def getRelatedNode(self, relation): + """Return the node that is linked to this node via this relation. + + If no linked node is found, return None + """ + from slipnet import slipnet + + if relation == slipnet.identity: + return self + destinations = [l.destination for l in self.outgoingLinks if l.label == relation] + if destinations: + return destinations[0] + node = None + return node + + def getBondCategory(self, destination): + """Return the label of the link between these nodes if it exists. + + If it does not exist return None + """ + from slipnet import slipnet + + result = None + if self == destination: + result = slipnet.identity + else: + for link in self.outgoingLinks: + if link.destination == destination: + result = link.label + break + if result: + logging.info('Got bond: %s' % result.name) + else: + logging.info('Got no bond') + return result + + def spread_activation(self): + if self.fully_active(): + [ link.spread_activation() for link in self.outgoingLinks ] + + def addBuffer(self): + if not self.clamped: + self.activation += self.buffer + self.activation = min(self.activation, 100) + self.activation = max(self.activation, 0) + + def jump(self): + value = (self.activation / 100.0) ** 3 + #logging.info('jumping for %s at activation %s' % (self.name,self.activation)) + if self.activation > jump_threshold() and utils.random() < value and not self.clamped: + self.activate_fully() + + def get_name(self): + if len(self.name) == 1: + return self.name.upper() + return self.name diff --git a/temperature.py b/temperature.py new file mode 100644 index 0000000..6cc2e4d --- /dev/null +++ b/temperature.py @@ -0,0 +1,23 @@ +import logging + +class Temperature(object): + def __init__(self): + self.value = 100.0 + self.clamped = True + self.clampTime = 30 + + def update(self, value): + logging.debug('update to %s' % value) + self.value = value + + def tryUnclamp(self): + from coderack import coderack + + if self.clamped and coderack.codeletsRun >= self.clampTime: + logging.info('unclamp temperature at %d' % coderack.codeletsRun) + self.clamped = False + + def log(self): + logging.debug('temperature.value: %f' % self.value) + +temperature = Temperature() diff --git a/unready.py b/unready.py new file mode 100644 index 0000000..d472e23 --- /dev/null +++ b/unready.py @@ -0,0 +1,2 @@ +from exceptions import NotImplementedError +raise NotImplementedError diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..3a4cb43 --- /dev/null +++ b/utils.py @@ -0,0 +1,140 @@ + +def any(things): + """Return True if any of the things are True. + + things should be iterable. + + If the things are empty, then we can't say any are True + >>> any([]) + False + + If all the things are False, then we can't say any are True + >>> any([False,False,False]) + False + + If all the things are equivalent to False, then we can't say any are True + >>> any([0,[],'']) + False + + The type of the true thing should not matter + >>> any([1,[],'']) + True + >>> any([0,(2,),'']) + True + >>> any([0,[],'foo']) + True + >>> any([0,[],True,'']) + True + + It should not matter where the True thing is + >>> any((True,False,False,False,False,)) + True + >>> any((False,False,True,False,False,)) + True + >>> any((False,False,False,False,True,)) + True + + The size of the sequence should not matter + >>> True == any((True,)) == any((True,True,)) == any((True,True,True,True,)) + True + + Any string is True + >>> any('foo') + True + + Except an empty string + >>> any('') + False + + The function cannot be applied to ints + >>> any(7) + Traceback (most recent call last): + ... + TypeError: iteration over non-sequence + """ + for thing in things: + if thing: + return True + return False + +def all(things): + """Return True if all of the things are True. + + things should be iterable. + + If the things are empty, then we can't say all are True + >>> all([]) + False + + If all the things are False, then we can't say all are True + >>> all([False,False,False]) + False + + If all the things are equivalent to False, then we can't say all are True + >>> all([0,[],'']) + False + + The type of the false thing should not matter + >>> all([0,True,True,]) + False + >>> all([True,(),True,]) + False + >>> all([True,True,'',]) + False + + Position of the false thing should not matter + >>> all((False,True,True,)) + False + >>> all((True,False,True,)) + False + >>> all((True,True,False,)) + False + + any string is True + >>> all('foo') + True + + Except an empty string + >>> all('') + False + + The function cannot be applied to ints + >>> all(7) + Traceback (most recent call last): + ... + TypeError: iteration over non-sequence + """ + for thing in things: + if not thing: + return False + return len(things) > 0 + +import logging + +seed = 999.0 +count = 0 +testably_random = True +def random(): + global testably_random + if testably_random: + from random import random + return random() + global seed + global count + seed += 1.0 + count += 1 + if seed > 1999: + seed = 0.0 + logging.info("count: %d" % count) + #if seed == 998: + # sys.exit(1) + return seed / 2000.0 + +def choice(aList): + i = int(random() * len(aList)) + return aList[i] + +if __name__ == '__main__': + import doctest + doctest.testmod() + diff --git a/workspace.py b/workspace.py new file mode 100644 index 0000000..9279336 --- /dev/null +++ b/workspace.py @@ -0,0 +1,158 @@ +import logging + +from workspaceString import WorkspaceString + +unknownAnswer = '?' + +class Workspace(object): + def __init__(self): + #logging.debug('workspace.__init__()') + self.setStrings('', '', '') + self.reset() + self.totalUnhappiness = 0.0 + self.intraStringUnhappiness = 0.0 + self.interStringUnhappiness = 0.0 + + def __repr__(self): + return '' % (self.initialString, self.modifiedString, self.targetString) + + def setStrings(self, initial, modified, target): + self.targetString = target + self.initialString = initial + self.modifiedString = modified + + def reset(self): + #logging.debug('workspace.reset()') + self.foundAnswer = False + self.changedObject = None + self.objects = [] + self.structures = [] + self.rule = None + self.initial = WorkspaceString(self.initialString) + self.modified = WorkspaceString(self.modifiedString) + self.target = WorkspaceString(self.targetString) + + def __adjustUnhappiness(self, values): + result = sum(values) / 2 + if result > 100.0: + result = 100.0 + return result + + def assessUnhappiness(self): + self.intraStringUnhappiness = self.__adjustUnhappiness([o.relativeImportance * o.intraStringUnhappiness for o in self.objects]) + self.interStringUnhappiness = self.__adjustUnhappiness([o.relativeImportance * o.interStringUnhappiness for o in self.objects]) + self.totalUnhappiness = self.__adjustUnhappiness([o.relativeImportance * o.totalUnhappiness for o in self.objects]) + + def assessTemperature(self): + self.calculateIntraStringUnhappiness() + self.calculateInterStringUnhappiness() + self.calculateTotalUnhappiness() + + def calculateIntraStringUnhappiness(self): + values = [o.relativeImportance * o.intraStringUnhappiness for o in self.objects] + value = sum(values) / 2.0 + self.intraStringUnhappiness = min(value, 100.0) + + def calculateInterStringUnhappiness(self): + values = [o.relativeImportance * o.interStringUnhappiness for o in self.objects] + value = sum(values) / 2.0 + self.interStringUnhappiness = min(value, 100.0) + + def calculateTotalUnhappiness(self): + for o in self.objects: + logging.info("object: %s, totalUnhappiness: %d, relativeImportance: %d" % ( + o, o.totalUnhappiness, o.relativeImportance * 1000)) + values = [o.relativeImportance * o.totalUnhappiness for o in self.objects] + value = sum(values) / 2.0 + self.totalUnhappiness = min(value, 100.0) + + def updateEverything(self): + for structure in self.structures: + structure.updateStrength() + for obj in self.objects: + obj.updateValue() + self.initial.updateRelativeImportance() + self.target.updateRelativeImportance() + self.initial.updateIntraStringUnhappiness() + self.target.updateIntraStringUnhappiness() + + def otherObjects(self, anObject): + return [o for o in self.objects if o != anObject] + + def numberOfUnrelatedObjects(self): + """A list of all objects in the workspace that have at least one bond slot open.""" + objects = [o for o in self.objects if o.string == self.initial or o.string == self.target] + #print 'A: %d' % len(objects) + objects = [o for o in objects if not o.spansString()] + #print 'B: %d' % len(objects) + objects = [o for o in objects if (not o.leftBond and not o.leftmost) or (not o.rightBond and not o.rightmost)] + #print 'C: %d' % len(objects) + #objects = [ o for o in objects if ] + #print 'D: %d' % len(objects) + return len(objects) + + def numberOfUngroupedObjects(self): + """A list of all objects in the workspace that have no group.""" + objects = [o for o in self.objects if o.string == self.initial or o.string == self.target] + objects = [o for o in objects if not o.spansString()] + objects = [o for o in objects if not o.group] + return len(objects) + + def numberOfUnreplacedObjects(self): + """A list of all objects in the inital string that have not been replaced.""" + from letter import Letter + + objects = [o for o in self.objects if o.string == self.initial and isinstance(o, Letter)] + objects = [o for o in objects if not o.replacement] + return len(objects) + + def numberOfUncorrespondingObjects(self): + """A list of all objects in the inital string that have not been replaced.""" + objects = [o for o in self.objects if o.string == self.initial or o.string == self.target] + objects = [o for o in objects if not o.correspondence] + return len(objects) + + def numberOfBonds(self): + """The number of bonds in the workspace""" + from bond import Bond + + return len([o for o in self.structures if isinstance(o, Bond)]) + + def correspondences(self): + from correspondence import Correspondence + + return [s for s in self.structures if isinstance(s, Correspondence)] + + def slippages(self): + result = [] + if self.changedObject and self.changedObject.correspondence: + result = [m for m in self.changedObject.correspondence.conceptMappings] + for objekt in workspace.initial.objects: + if objekt.correspondence: + for mapping in objekt.correspondence.slippages(): + if not mapping.isNearlyContainedBy(result): + result += [mapping] + return result + + def buildRule(self, rule): + if self.rule: + self.structures.remove(self.rule) + self.rule = rule + self.structures += [rule] + rule.activateRuleDescriptions() + + def breakRule(self): + self.rule = None + + def buildDescriptions(self, objekt): + for description in objekt.descriptions: + description.descriptionType.buffer = 100.0 + #logging.info("Set buffer to 100 for " + description.descriptionType.get_name()); + description.descriptor.buffer = 100.0 + #logging.info("Set buffer to 100 for " + description.descriptor.get_name()); + if description not in self.structures: + self.structures += [description] + + +workspace = Workspace() + diff --git a/workspaceFormulas.py b/workspaceFormulas.py new file mode 100644 index 0000000..ca504c2 --- /dev/null +++ b/workspaceFormulas.py @@ -0,0 +1,160 @@ +import logging + +from workspace import workspace +from temperature import temperature +from slipnet import slipnet +import formulas + +class WorkspaceFormulas(object): + def __init__(self): + self.clampTemperature = False + + def updateTemperature(self): + logging.debug('updateTemperature') + workspace.assessTemperature() + ruleWeakness = 100.0 + if workspace.rule: + workspace.rule.updateStrength() + ruleWeakness = 100.0 - workspace.rule.totalStrength + values = ( (workspace.totalUnhappiness, 0.8), (ruleWeakness, 0.2), ) + slightly_above_actual_temperature = formulas.actualTemperature + 0.001 + logging.info('actualTemperature: %f' % slightly_above_actual_temperature) + formulas.actualTemperature = formulas.weightedAverage(values) + logging.info('unhappiness: %f, weakness: %f, actualTemperature: %f' % ( + workspace.totalUnhappiness + 0.001, ruleWeakness + 0.001, formulas.actualTemperature + 0.001)) + if temperature.clamped: + formulas.actualTemperature = 100.0 + logging.info('actualTemperature: %f' % (formulas.actualTemperature + 0.001)) + temperature.update(formulas.actualTemperature) + if not self.clampTemperature: + formulas.Temperature = formulas.actualTemperature + temperature.update(formulas.Temperature) + +workspaceFormulas = WorkspaceFormulas() + +def numberOfObjects(): + return len(workspace.objects) + +def chooseUnmodifiedObject(attribute,inObjects): + 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 formulas.chooseObjectFromList(objects,attribute) + +def chooseNeighbour(source): + objects = [] + for objekt in workspace.objects: + if objekt.string != source.string: + continue + if objekt.leftStringPosition == source.rightStringPosition + 1: + objects += [ objekt ] + elif source.leftStringPosition == objekt.rightStringPosition + 1: + objects += [ objekt ] + return formulas.chooseObjectFromList(objects,"intraStringSalience") + +def chooseDirectedNeighbor(source,direction): + if direction == slipnet.left: + logging.info('Left') + return __chooseLeftNeighbor(source) + logging.info('Right') + return __chooseRightNeighbor(source) + +def __chooseLeftNeighbor(source): + objects = [] + for o in workspace.objects: + if o.string == source.string : + if source.leftStringPosition == o.rightStringPosition + 1: + logging.info('%s is on left of %s' % (o,source)) + objects += [ o ] + else: + logging.info('%s is not on left of %s' % (o,source)) + logging.info('Number of left objects: %s' % len(objects)) + return formulas.chooseObjectFromList(objects,'intraStringSalience') + +def __chooseRightNeighbor(source): + objects = [ o for o in workspace.objects if + o.string == source.string and + o.leftStringPosition == source.rightStringPosition + 1 + ] + return formulas.chooseObjectFromList(objects,'intraStringSalience') + +def chooseBondFacet(source, destination): + 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 ] + +def __supportForDescriptionType(descriptionType,string): + return ( descriptionType.activation + __descriptionTypeSupport(descriptionType,string) ) / 2 + +def __descriptionTypeSupport(descriptionType,string): + """The proportion of objects in the string that have a description with this descriptionType""" + numberOfObjects = totalNumberOfObjects = 0.0 + for objekt in workspace.objects: + if objekt.string == string: + totalNumberOfObjects += 1.0 + for description in objekt.descriptions: + if description.descriptionType == descriptionType: + numberOfObjects += 1.0 + return numberOfObjects / totalNumberOfObjects + +def probabilityOfPosting(codeletName): + if codeletName == 'breaker': + return 1.0 + if 'description' in codeletName: + result = ( formulas.Temperature / 100.0 ) ** 2 + else: + result = workspace.intraStringUnhappiness / 100.0 + if 'correspondence' in codeletName: + result = workspace.interStringUnhappiness / 100.0 + if 'replacement' in codeletName: + if workspace.numberOfUnreplacedObjects() > 0: + return 1.0 + return 0.0 + if 'rule' in codeletName: + if not workspace.rule: + return 1.0 + return workspace.rule.totalWeakness() / 100.0 + if 'translator' in codeletName: + if not workspace.rule: + assert 0 + return 0.0 + assert 0 + return 1.0 + return result + +def howManyToPost(codeletName): + if codeletName == 'breaker': + return 1 + if 'description' in codeletName: + return 1 + if 'translator' in codeletName: + if not workspace.rule: + return 0 + return 1 + if 'rule' in codeletName: + return 2 + if 'group' in codeletName and not workspace.numberOfBonds(): + return 0 + if 'replacement' in codeletName and workspace.rule: + return 0 + number = 0 + if 'bond' in codeletName: + number = workspace.numberOfUnrelatedObjects() +# print 'post number of unrelated: %d, objects: %d' % (number,len(workspace.objects)) + if 'group' in codeletName: + number = workspace.numberOfUngroupedObjects() + if 'replacement' in codeletName: + number = workspace.numberOfUnreplacedObjects() + if 'correspondence' in codeletName: + number = workspace.numberOfUncorrespondingObjects() + if number < formulas.blur(2.0): + return 1 + if number < formulas.blur(4.0): + return 2 + return 3 + + diff --git a/workspaceObject.py b/workspaceObject.py new file mode 100644 index 0000000..235b357 --- /dev/null +++ b/workspaceObject.py @@ -0,0 +1,226 @@ +import logging + +from description import Description +from slipnet import slipnet +from workspaceStructure import WorkspaceStructure + +class WorkspaceObject(WorkspaceStructure): + def __init__(self,workspaceString): + WorkspaceStructure.__init__(self) + self.string = workspaceString + #self.string.objects += [ self ] + self.descriptions = [] + self.extrinsicDescriptions = [] + self.incomingBonds = [] + self.outgoingBonds = [] + self.bonds = [] + self.group = None + self.changed = None + self.correspondence = None + self.clampSalience = False + self.rawImportance = 0.0 + self.relativeImportance = 0.0 + self.leftBond = None + self.rightBond = None + self.newAnswerLetter = False + self.name = '' + self.replacement = None + self.rightStringPosition = 0 + self.leftStringPosition = 0 + self.leftmost = False + self.rightmost = False + self.intraStringSalience = 0.0 + self.interStringSalience = 0.0 + self.totalSalience = 0.0 + self.intraStringUnhappiness = 0.0 + self.interStringUnhappiness = 0.0 + self.totalUnhappiness = 0.0 + + def __str__(self): + return 'object' + + def spansString(self): + return self.leftmost and self.rightmost + + def addDescription(self,descriptionType,descriptor): + description = Description(self,descriptionType,descriptor) + logging.info("Adding description: %s to %s" % (description,self)) + self.descriptions += [ description ] + + def addDescriptions(self,descriptions): + #print 'addDescriptions 1' + #print 'add %d to %d of %s' % (len(descriptions),len(self.descriptions), self.string.string) + copy = descriptions[:] # in case we add to our own descriptions, which turns the loop infinite + for description in copy: + #print '%d addDescriptions 2 %s ' % (len(descriptions),description) + logging.info('might add: %s' % description) + if not self.containsDescription(description): + #print '%d addDescriptions 3 %s ' % (len(descriptions),description) + self.addDescription(description.descriptionType,description.descriptor) + #print '%d addDescriptions 4 %s ' % (len(descriptions),description) + else: + logging.info("Won't add it") + #print '%d added, have %d ' % (len(descriptions),len(self.descriptions)) + from workspace import workspace + workspace.buildDescriptions(self) + + def __calculateIntraStringHappiness(self): + if self.spansString(): + return 100.0 + if self.group: + return self.group.totalStrength + bondStrength = 0.0 + for bond in self.bonds: + bondStrength += bond.totalStrength + divisor = 6.0 + if self.spansString(): # XXX then we have already returned + divisor = 3.0 + return bondStrength / divisor + + def __calculateRawImportance(self): + """Calculate the raw importance of this object. + + Which is the sum of all relevant descriptions""" + result = 0.0 + for description in self.descriptions: + if description.descriptionType.fully_active(): + result += description.descriptor.activation + else: + result += description.descriptor.activation / 20.0 + if self.group: + result *= 2.0 / 3.0 + if self.changed: + result *= 2.0 + return result + + def updateValue(self): + self.rawImportance = self.__calculateRawImportance() + intraStringHappiness = self.__calculateIntraStringHappiness() + self.intraStringUnhappiness = 100.0 - intraStringHappiness + + interStringHappiness = 0.0 + if self.correspondence: + interStringHappiness = self.correspondence.totalStrength + self.interStringUnhappiness = 100.0 - interStringHappiness + #logging.info("Unhappy: %s"%self.interStringUnhappiness) + + averageHappiness = ( intraStringHappiness + interStringHappiness ) / 2 + self.totalUnhappiness = 100.0 - averageHappiness + + if self.clampSalience: + self.intraStringSalience = 100.0 + self.interStringSalience = 100.0 + else: + from formulas import weightedAverage + self.intraStringSalience = weightedAverage( ((self.relativeImportance,0.2), (self.intraStringUnhappiness,0.8)) ) + self.interStringSalience = weightedAverage( ((self.relativeImportance,0.8), (self.interStringUnhappiness,0.2)) ) + self.totalSalience = (self.intraStringSalience + self.interStringSalience) / 2.0 + logging.info('Set salience of %s to %f = (%f + %f)/2' % ( + self.__str__(),self.totalSalience, self.intraStringSalience, self.interStringSalience)) + + def isWithin(self,other): + return self.leftStringPosition >= other.leftStringPosition and self.rightStringPosition <= other.rightStringPosition + + def relevantDescriptions(self): + return [ d for d in self.descriptions if d.descriptionType.fully_active() ] + + def morePossibleDescriptions(self,node): + return [] + + def getPossibleDescriptions(self,descriptionType): + logging.info('getting possible descriptions for %s' % self) + descriptions = [ ] + from group import Group + for link in descriptionType.instanceLinks: + node = link.destination + if node == slipnet.first and self.hasDescription(slipnet.letters[0]): + descriptions += [ node ] + if node == slipnet.last and self.hasDescription(slipnet.letters[-1]): + descriptions += [ node ] + i = 1 + for number in slipnet.numbers: + if node == number and isinstance(self,Group) and len(self.objectList) == i: + descriptions += [ node ] + i += 1 + if node == slipnet.middle and self.middleObject(): + descriptions += [ node ] + s = '' + for d in descriptions: + s = '%s, %s' % (s,d.get_name()) + logging.info(s) + return descriptions + + def containsDescription(self,sought): + soughtType = sought.descriptionType + soughtDescriptor = sought.descriptor + for d in self.descriptions: + if soughtType == d.descriptionType and soughtDescriptor == d.descriptor: + return True + return False + + def hasDescription(self,slipnode): + return [ d for d in self.descriptions if d.descriptor == slipnode ] and True or False + + def middleObject(self): + # XXX only works if string is 3 chars long + # as we have access to the string, why not just " == len / 2" ? + objectOnMyRightIsRightmost = objectOnMyLeftIsLeftmost = False + for objekt in self.string.objects: + if objekt.leftmost and objekt.rightStringPosition == self.leftStringPosition - 1: + objectOnMyLeftIsLeftmost = True + if objekt.rightmost and objekt.leftStringPosition == self.rightStringPosition + 1: + objectOnMyRightIsRightmost = True + return objectOnMyRightIsRightmost and objectOnMyLeftIsLeftmost + + def distinguishingDescriptor(self,descriptor): + """Whether no other object of the same type (ie. letter or group) has the same descriptor""" + if descriptor == slipnet.letter: + return False + if descriptor == slipnet.group: + return False + for number in slipnet.numbers: + if number == descriptor: + return False + return True + + def relevantDistinguishingDescriptors(self): + return [ d.descriptor for d in self.relevantDescriptions() if self.distinguishingDescriptor(d.descriptor) ] + + def getDescriptor(self,descriptionType): + """The description attached to this object of the specified description type.""" + descriptor = None + logging.info("\nIn %s, trying for type: %s" % (self,descriptionType.get_name())) + for description in self.descriptions: + logging.info("Trying description: %s" % description) + if description.descriptionType == descriptionType: + return description.descriptor + return descriptor + + def getDescriptionType(self,sought_description): + """The description_type attached to this object of the specified description""" + for description in self.descriptions: + if description.descriptor == sought_description: + return description.descriptionType + description = None + return description + + def getCommonGroups(self,other): + return [ o for o in self.string.objects if self.isWithin(o) and other.isWithin(o) ] + + def letterDistance(self,other): + if other.leftStringPosition > self.rightStringPosition: + return other.leftStringPosition - self.rightStringPosition + if self.leftStringPosition > other.rightStringPosition: + return self.leftStringPosition - other.rightStringPosition + return 0 + + def letterSpan(self): + return self.rightStringPosition - self.leftStringPosition + 1 + + def beside(self,other): + if self.string != other.string: + return False + if self.leftStringPosition == other.rightStringPosition + 1: + return True + return other.leftStringPosition == self.rightStringPosition + 1 + diff --git a/workspaceString.py b/workspaceString.py new file mode 100644 index 0000000..53f26f7 --- /dev/null +++ b/workspaceString.py @@ -0,0 +1,80 @@ +import logging +from letter import Letter +from slipnet import slipnet + +class WorkspaceString(object): + def __init__(self, s): + self.string = s + self.bonds = [] + self.objects = [] + self.letters = [] + self.length = len(s) + self.intraStringUnhappiness = 0.0 + if not self.length: + return + position = 0 + from workspace import workspace + + for c in self.string.upper(): + value = ord(c) - ord('A') + letter = Letter(self, position + 1, self.length) + letter.workspaceString = self + letter.addDescription(slipnet.objectCategory, slipnet.letter) + letter.addDescription(slipnet.letterCategory, slipnet.letters[value]) + letter.describe(position + 1, self.length) + workspace.buildDescriptions(letter) + self.letters += [letter] + position += 1 + + def __repr__(self): + return '' % self.string + + def __str__(self): + return '%s with %d letters, %d objects, %d bonds' % (self.string, len(self.letters), len(self.objects), len(self.bonds)) + + def log(self, heading): + s = '%s: %s - ' % (heading, self) + for l in self.letters: + s += ' %s' % l + s += '; ' + for o in self.objects: + s += ' %s' % o + s += '; ' + for b in self.bonds: + s += ' %s' % b + s += '.' + logging.info(s) + + def __len__(self): + return len(self.string) + + def __getitem__(self, i): + return self.string[i] + + def updateRelativeImportance(self): + """Update the normalised importance of all objects in the string""" + total = sum([o.rawImportance for o in self.objects]) + if not total: + for o in self.objects: + o.relativeImportance = 0.0 + else: + for o in self.objects: + logging.info('object: %s, relative: %d = raw: %d / total: %d' % ( + o, o.relativeImportance * 1000, o.rawImportance, total )) + o.relativeImportance = o.rawImportance / total + + def updateIntraStringUnhappiness(self): + if not len(self.objects): + self.intraStringUnhappiness = 0.0 + return + total = sum([o.intraStringUnhappiness for o in self.objects]) + self.intraStringUnhappiness = total / len(self.objects) + + def equivalentGroup(self, sought): + from group import Group + + for objekt in self.objects: + if isinstance(objekt, Group): + if objekt.sameGroup(sought): + return objekt + return None diff --git a/workspaceStructure.py b/workspaceStructure.py new file mode 100644 index 0000000..0b41260 --- /dev/null +++ b/workspaceStructure.py @@ -0,0 +1,37 @@ +import formulas + +class WorkspaceStructure(object): + def __init__(self): + self.string = None + self.internalStrength = 0.0 + self.externalStrength = 0.0 + self.totalStrength = 0.0 + + def updateStrength(self): + self.updateInternalStrength() + self.updateExternalStrength() + self.updateTotalStrength() + + def updateTotalStrength(self): + """Recalculate the total strength based on internal and external strengths""" + weights = ( (self.internalStrength, self.internalStrength), (self.externalStrength, 100 - self.internalStrength) ) + strength = formulas.weightedAverage(weights) + self.totalStrength = strength + + def totalWeakness(self): + """The total weakness is derived from total strength""" + return 100 - self.totalStrength ** 0.95 + + def updateInternalStrength(self): + """How internally cohesive the structure is""" + raise NotImplementedError, 'call of abstract method: WorkspaceStructure.updateInternalStrength()' + + def updateExternalStrength(self): + raise NotImplementedError, 'call of abstract method: WorkspaceStructure.updateExternalStrength()' + + def break_the_structure(self): + """Break this workspace structure + + Exactly what is broken depends on sub-class + """ + raise NotImplementedError, 'call of abstract method: WorkspaceStructure.break_the_structure()'