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()'