The slipnet node previously called "bondFacet" was misleading since "facet" is used throughout the codebase as a property name on Bond, Group, and Rule objects. Renamed to "bond_edge_type" to better reflect its role as a descriptor type category. Also removed precomputed minPathToLetter data from slipnet.json. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2013 lines
88 KiB
Python
2013 lines
88 KiB
Python
"""
|
|
Codelet Methods Module
|
|
|
|
This module contains all the behavior functions that codelets execute in the Copycat algorithm.
|
|
Codelets are autonomous agents that propose, test, and build structures (bonds, groups,
|
|
correspondences, rules, etc.) in the workspace.
|
|
|
|
The main components:
|
|
1. @codelet decorator - Marks functions as codelet behaviors
|
|
2. Helper functions - Utility functions for common operations
|
|
3. Codelet behaviors - Specific algorithm implementations
|
|
"""
|
|
|
|
import inspect
|
|
import logging
|
|
|
|
from . import formulas
|
|
from .workspaceFormulas import chooseDirectedNeighbor
|
|
from .workspaceFormulas import chooseNeighbor
|
|
from .workspaceFormulas import chooseUnmodifiedObject
|
|
from .workspaceObject import WorkspaceObject
|
|
from .letter import Letter
|
|
from .replacement import Replacement
|
|
from .group import Group
|
|
from .bond import Bond
|
|
from .correspondence import Correspondence
|
|
|
|
|
|
def codelet(name):
|
|
"""
|
|
Decorator for marking functions as codelet behaviors.
|
|
|
|
Codelets are autonomous agents in the Copycat algorithm that execute specific behaviors
|
|
to propose, test, or build structures in the workspace. This decorator validates that
|
|
the decorated function has the correct signature and marks it as a codelet method.
|
|
|
|
Args:
|
|
name (str): The name of the codelet behavior for identification
|
|
|
|
Returns:
|
|
function: The wrapped function with codelet metadata
|
|
|
|
Requirements:
|
|
The decorated function must have exactly two parameters:
|
|
- ctx: The context object containing workspace, slipnet, temperature, etc.
|
|
- codelet: The codelet instance executing the behavior
|
|
|
|
Raises:
|
|
AssertionError: If the function doesn't have the correct signature
|
|
"""
|
|
def wrap(f):
|
|
# Verify that the decorated function has exactly two parameters:
|
|
# 1. ctx - the context object containing workspace, slipnet, etc.
|
|
# 2. codelet - the codelet instance itself
|
|
# The None values in the tuple represent: no default args, no *args, no **kwargs
|
|
assert tuple(inspect.getargspec(f)) == (['ctx', 'codelet'], None, None, None)
|
|
|
|
# Mark this function as a valid codelet method
|
|
f.is_codelet_method = True
|
|
|
|
# Store the codelet name
|
|
f.codelet_name = name
|
|
|
|
# Return the decorated function
|
|
return f
|
|
return wrap
|
|
|
|
# Helper functions common to multiple codelets
|
|
|
|
def __showWhichStringObjectIsFrom(structure):
|
|
"""
|
|
Log which string (initial or target) a workspace structure belongs to.
|
|
|
|
This is a debugging/logging utility that helps trace which string an object
|
|
came from during codelet execution. Useful for understanding algorithm flow.
|
|
|
|
Args:
|
|
structure: A workspace object or structure to identify
|
|
"""
|
|
if not structure:
|
|
return
|
|
workspace = structure.ctx.workspace
|
|
whence = 'other'
|
|
if isinstance(structure, WorkspaceObject):
|
|
whence = 'target'
|
|
if structure.string == workspace.initial:
|
|
whence = 'initial'
|
|
logging.info('object chosen = %s from %s string' % (structure, whence))
|
|
|
|
|
|
def __getScoutSource(ctx, slipnode, relevanceMethod, typeName):
|
|
"""
|
|
Choose which string (initial or target) to scout for objects based on relevance.
|
|
|
|
This function implements the core heuristic for scouting behaviors, where the algorithm
|
|
probabilistically chooses between the initial and target strings based on relevance
|
|
and unhappiness scores. Higher relevance and unhappiness create pressure to explore
|
|
that string.
|
|
|
|
Args:
|
|
ctx: The context object containing random, workspace, etc.
|
|
slipnode: The slipnet node to evaluate relevance against
|
|
relevanceMethod: Function that calculates relevance between string and slipnode
|
|
typeName: String name for logging purposes (e.g., 'bond', 'group')
|
|
|
|
Returns:
|
|
WorkspaceObject: A randomly chosen unmodified object from the selected string
|
|
|
|
Notes:
|
|
The decision process balances:
|
|
1. Relevance of the slipnode to each string
|
|
2. Intra-string unhappiness (pressure to resolve problems)
|
|
3. Random probabilistic selection
|
|
"""
|
|
random = ctx.random
|
|
workspace = ctx.workspace
|
|
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
|
|
initials = initialRelevance + initialUnhappiness
|
|
targets = targetRelevance + targetUnhappiness
|
|
if random.weighted_greater_than(targets, initials):
|
|
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(ctx, 'intraStringSalience', string.objects)
|
|
return source
|
|
|
|
|
|
def __getDescriptors(bond_edge_type, source, destination):
|
|
"""
|
|
Extract descriptors from source and destination objects for a given bond facet.
|
|
|
|
Args:
|
|
bond_edge_type: The descriptor type/facet for the bond (e.g., letterCategory)
|
|
source: The source workspace object
|
|
destination: The destination workspace object
|
|
|
|
Returns:
|
|
tuple: (sourceDescriptor, destinationDescriptor) describing the objects
|
|
|
|
Raises:
|
|
AssertionError: If either object lacks the required descriptor type
|
|
"""
|
|
sourceDescriptor = source.getDescriptor(bond_edge_type)
|
|
destinationDescriptor = destination.getDescriptor(bond_edge_type)
|
|
assert sourceDescriptor and destinationDescriptor
|
|
return sourceDescriptor, destinationDescriptor
|
|
|
|
|
|
def __structureVsStructure(structure1, weight1, structure2, weight2):
|
|
"""
|
|
Compare two structures to determine which is stronger after temperature adjustment.
|
|
|
|
This function implements the core competition mechanism in Copycat. Structures
|
|
compete with each other for dominance in the workspace. The comparison considers
|
|
both the inherent strength of each structure and temperature-adjusted randomness.
|
|
|
|
Args:
|
|
structure1: First structure to compare
|
|
weight1: Weight multiplier for structure1's strength
|
|
structure2: Second structure to compare
|
|
weight2: Weight multiplier for structure2's strength
|
|
|
|
Returns:
|
|
bool: True if structure1 wins the competition, False if structure2 wins
|
|
|
|
Notes:
|
|
The competition process:
|
|
1. Updates both structures' current strength values
|
|
2. Applies temperature-adjusted weighting
|
|
3. Uses probabilistic comparison based on weighted strengths
|
|
4. Higher temperature = more random outcomes, lower temperature = more deterministic
|
|
"""
|
|
ctx = structure1.ctx
|
|
random = ctx.random
|
|
# TODO: use entropy
|
|
temperature = ctx.temperature
|
|
structure1.updateStrength()
|
|
structure2.updateStrength()
|
|
# TODO: use entropy
|
|
weightedStrength1 = temperature.getAdjustedValue(
|
|
structure1.totalStrength * weight1)
|
|
# TODO: use entropy
|
|
weightedStrength2 = temperature.getAdjustedValue(
|
|
structure2.totalStrength * weight2)
|
|
return random.weighted_greater_than(weightedStrength1, weightedStrength2)
|
|
|
|
|
|
def __fight(structure, structureWeight, incompatibles, incompatibleWeight):
|
|
"""
|
|
Execute a series of fights between a structure and incompatible structures.
|
|
|
|
Args:
|
|
structure: The structure fighting for dominance
|
|
structureWeight: Weight multiplier for the main structure's strength
|
|
incompatibles: List of incompatible structures to fight against
|
|
incompatibleWeight: Weight multiplier for incompatible structures' strength
|
|
|
|
Returns:
|
|
bool: True if the structure wins against all incompatibles, False otherwise
|
|
"""
|
|
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):
|
|
"""
|
|
Execute fights against incompatible structures and log the results.
|
|
|
|
This is a wrapper around __fight() that provides additional logging about
|
|
the fight outcomes and whether structural changes ("breaking") occurred.
|
|
|
|
Args:
|
|
incompatibles: List of incompatible structures to fight
|
|
structure: The structure fighting for dominance
|
|
name: Descriptive name for logging (e.g., 'bonds', 'groups')
|
|
structureWeight: Weight multiplier for the main structure's strength
|
|
incompatibleWeight: Weight multiplier for incompatible structures' strength
|
|
|
|
Returns:
|
|
bool: True if fights are successful, False if the structure lost
|
|
"""
|
|
if len(incompatibles):
|
|
if __fight(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(ctx, conceptMappings):
|
|
"""
|
|
Determine if concept slippage (metaphorical thinking) is possible.
|
|
|
|
Concept slippage allows the algorithm to make metaphorical connections
|
|
between similar concepts rather than requiring exact literal matches.
|
|
For example, seeing 'ABC' as 'first letter, second letter, third letter'
|
|
and mapping it to 'XYZ' as 'last letter, second-last letter, third-last letter'.
|
|
|
|
Args:
|
|
ctx: The context object containing random and temperature
|
|
conceptMappings: List of possible concept mappings to evaluate
|
|
|
|
Returns:
|
|
bool: True if at least one concept mapping allows slippage
|
|
"""
|
|
random = ctx.random
|
|
temperature = ctx.temperature
|
|
for mapping in conceptMappings:
|
|
slippiness = mapping.slippability() / 100.0
|
|
probabilityOfSlippage = temperature.getAdjustedProbability(slippiness)
|
|
if random.coinFlip(probabilityOfSlippage):
|
|
return True
|
|
return False
|
|
|
|
|
|
@codelet('breaker')
|
|
def breaker(ctx, codelet):
|
|
"""
|
|
Breaker codelet that destroys existing structures based on their weakness.
|
|
|
|
The breaker is a destructive agent that probabilistically destroys existing
|
|
structures (bonds, groups, correspondences) in the workspace. This creates
|
|
pressure for structural reorganization and prevents the algorithm from getting
|
|
stuck in local optima.
|
|
|
|
Behavior:
|
|
1. Probabilistically decides whether to fizzle (do nothing) based on temperature
|
|
2. If not fizzling, randomly selects a structure to potentially break
|
|
3. Calculates break probability based on structure's total weakness (inverse strength)
|
|
4. Destroys structures that pass the probabilistic threshold
|
|
5. If breaking a bond within a group, also breaks the containing group
|
|
|
|
Args:
|
|
ctx: Context containing random, temperature, workspace components
|
|
codelet: The breaker codelet instance executing this behavior
|
|
|
|
Notes:
|
|
Higher temperature = more likely to break structures (exploration)
|
|
Lower temperature = less likely to break structures (exploitation)
|
|
Structure weakness = 100 - structure_strength, converted to break probability
|
|
"""
|
|
# From the original LISP:
|
|
'''
|
|
First decides probabilistically whether or not to fizzle, based on
|
|
temperature. Chooses a structure and random and decides probabilistically
|
|
whether or not to break it as a function of its total weakness.
|
|
|
|
If the structure is a bond in a group, have to break the group in
|
|
order to break the bond.
|
|
'''
|
|
random = ctx.random
|
|
temperature = ctx.temperature
|
|
workspace = ctx.workspace
|
|
probabilityOfFizzle = (100.0 - temperature.value()) / 100.0
|
|
if random.coinFlip(probabilityOfFizzle):
|
|
return
|
|
# choose a structure at random
|
|
structures = [s for s in workspace.structures if
|
|
isinstance(s, (Group, Bond, Correspondence))]
|
|
assert len(structures)
|
|
structure = random.choice(structures)
|
|
__showWhichStringObjectIsFrom(structure)
|
|
breakObjects = [structure]
|
|
if isinstance(structure, Bond):
|
|
if structure.source.group:
|
|
if structure.source.group == structure.destination.group:
|
|
breakObjects += [structure.source.group]
|
|
# Break all the objects or none of them; this matches the Java
|
|
# "all objects" means a bond and its group, if it has one.
|
|
|
|
for structure in breakObjects:
|
|
breakProbability = temperature.getAdjustedProbability(
|
|
structure.totalStrength / 100.0)
|
|
if random.coinFlip(breakProbability):
|
|
return
|
|
for structure in breakObjects:
|
|
structure.break_the_structure()
|
|
|
|
|
|
def chooseRelevantDescriptionByActivation(ctx, workspaceObject):
|
|
"""
|
|
Choose a relevant description based on descriptor activation levels.
|
|
|
|
This utility function selects among all descriptions that are relevant
|
|
to a workspace object, weighting the selection by how activated each
|
|
description's descriptor is in the slipnet.
|
|
|
|
Args:
|
|
ctx: Context containing random number generator
|
|
workspaceObject: The object to choose descriptions for
|
|
|
|
Returns:
|
|
Description: A randomly chosen description, weighted by descriptor activation
|
|
"""
|
|
random = ctx.random
|
|
descriptions = workspaceObject.relevantDescriptions()
|
|
weights = [description.descriptor.activation for description in descriptions]
|
|
return random.weighted_choice(descriptions, weights)
|
|
|
|
|
|
def similarPropertyLinks(ctx, slip_node):
|
|
"""
|
|
Find property links from a slipnode that pass temperature-adjusted probability tests.
|
|
|
|
This function implements property similarity spreading through the slipnet.
|
|
It examines all property links emanating from a given slipnode and probabilistically
|
|
activates links based on their association strength and current temperature.
|
|
|
|
Args:
|
|
ctx: Context containing random and temperature components
|
|
slip_node: The slipnet node to evaluate property links from
|
|
|
|
Returns:
|
|
list: List of slipnode links that passed the probability test
|
|
|
|
Notes:
|
|
Higher association strength = higher probability of activation
|
|
Higher temperature = more random link activations (exploration)
|
|
Lower temperature = more deterministic link activations (exploitation)
|
|
"""
|
|
random = ctx.random
|
|
# TODO: use entropy
|
|
temperature = ctx.temperature
|
|
result = []
|
|
for slip_link in slip_node.propertyLinks:
|
|
association = slip_link.degreeOfAssociation() / 100.0
|
|
# TODO:use entropy
|
|
probability = temperature.getAdjustedProbability(association)
|
|
if random.coinFlip(probability):
|
|
result += [slip_link]
|
|
return result
|
|
|
|
|
|
@codelet('bottom-up-description-scout')
|
|
def bottom_up_description_scout(ctx, codelet):
|
|
"""
|
|
Bottom-up description scout that discovers new descriptions for workspace objects.
|
|
|
|
This codelet implements data-driven description generation, starting from an object
|
|
in the workspace and working upward to discover new descriptions via property links
|
|
in the slipnet. It's called "bottom-up" because it starts with concrete objects rather
|
|
than abstract concepts.
|
|
|
|
Behavior:
|
|
1. Selects an object based on total salience (not yet fully described)
|
|
2. Chooses among the object's relevant descriptions, weighted by activation
|
|
3. Follows property links from the chosen description's descriptor
|
|
4. Probabilistically activates property links based on association strength
|
|
5. Proposes a new description using the destination of an activated link
|
|
|
|
Args:
|
|
ctx: Context containing coderack, random, workspace components
|
|
codelet: The description scout codelet instance
|
|
|
|
Notes:
|
|
This supports conceptual slippage by following property associations.
|
|
Higher activation = more likely to choose a description/link
|
|
Higher association strength = more likely to activate a property link
|
|
"""
|
|
coderack = ctx.coderack
|
|
random = ctx.random
|
|
workspace = ctx.workspace
|
|
chosenObject = chooseUnmodifiedObject(ctx, 'totalSalience', workspace.objects)
|
|
assert chosenObject
|
|
__showWhichStringObjectIsFrom(chosenObject)
|
|
# choose relevant description by activation
|
|
descriptions = chosenObject.relevantDescriptions()
|
|
weights = [d.descriptor.activation for d in descriptions]
|
|
description = random.weighted_choice(descriptions, weights)
|
|
assert description
|
|
sliplinks = similarPropertyLinks(ctx, description.descriptor)
|
|
assert sliplinks
|
|
weights = [sliplink.degreeOfAssociation() * sliplink.destination.activation
|
|
for sliplink in sliplinks]
|
|
chosen = random.weighted_choice(sliplinks, weights)
|
|
chosenProperty = chosen.destination
|
|
coderack.proposeDescription(chosenObject, chosenProperty.category(),
|
|
chosenProperty)
|
|
|
|
|
|
@codelet('top-down-description-scout')
|
|
def top_down_description_scout(ctx, codelet):
|
|
"""
|
|
Top-down description scout that generates descriptions guided by concept types.
|
|
|
|
This codelet implements concept-driven description generation, starting from a
|
|
specific description type (e.g., letter category, position) and working downward
|
|
to find objects that could have that description. It's called "top-down" because
|
|
it starts with abstract concept types rather than конкретные objects.
|
|
|
|
Behavior:
|
|
1. Gets a description type from the codelet's arguments
|
|
2. Selects an object based on total salience (not yet fully described)
|
|
3. Retrieves all possible descriptions of that type for the object
|
|
4. Chooses among possible descriptions, weighted by activation
|
|
5. Proposes a new description using the chosen property
|
|
|
|
Args:
|
|
ctx: Context containing coderack, random, workspace components
|
|
codelet: The description scout codelet instance with descriptionType argument
|
|
|
|
Notes:
|
|
This enables goal-directed description generation based on concept categories
|
|
Contrasts with bottom-up scouts that generate descriptions opportunistically
|
|
Used when the system has a preference for certain types of descriptions
|
|
"""
|
|
coderack = ctx.coderack
|
|
random = ctx.random
|
|
workspace = ctx.workspace
|
|
descriptionType = codelet.arguments[0]
|
|
chosenObject = chooseUnmodifiedObject(ctx, 'totalSalience', workspace.objects)
|
|
assert chosenObject
|
|
__showWhichStringObjectIsFrom(chosenObject)
|
|
descriptions = chosenObject.getPossibleDescriptions(descriptionType)
|
|
assert descriptions and len(descriptions)
|
|
weights = [n.activation for n in descriptions]
|
|
chosenProperty = random.weighted_choice(descriptions, weights)
|
|
coderack.proposeDescription(chosenObject, chosenProperty.category(),
|
|
chosenProperty)
|
|
|
|
|
|
@codelet('description-strength-tester')
|
|
def description_strength_tester(ctx, codelet):
|
|
"""
|
|
Tests the strength of a proposed description to see if it's worth building.
|
|
|
|
This codelet evaluates whether a previously proposed description is strong enough
|
|
to warrant creation in the workspace. Only descriptions that pass this probabilistic
|
|
strength test proceed to the description-builder.
|
|
|
|
Behavior:
|
|
1. Activates the description's descriptor (maximul buffer activation)
|
|
2. Updates and evaluates the description's total Strength
|
|
3. Converts strength to probability via temperature adjustment
|
|
4. If passes probability test, queues description-builder codelet
|
|
5. If fails, the description is abandoned (fizzles)
|
|
|
|
Args:
|
|
ctx: Context containing coderack, random, temperature components
|
|
codelet: Codelet with argument containing the description to test
|
|
|
|
Notes:
|
|
Higher description strength = higher probability of acceptance
|
|
Temperature affects the acceptance probability (higher temp = more lenient)
|
|
Failed descriptions are discarded, promoting quality over quantity
|
|
"""
|
|
coderack = ctx.coderack
|
|
random = ctx.random
|
|
# TODO: use entropy
|
|
temperature = ctx.temperature
|
|
description = codelet.arguments[0]
|
|
description.descriptor.buffer = 100.0
|
|
description.updateStrength()
|
|
strength = description.totalStrength
|
|
# TODO: use entropy
|
|
probability = temperature.getAdjustedProbability(strength / 100.0)
|
|
assert random.coinFlip(probability)
|
|
coderack.newCodelet('description-builder', strength, [description])
|
|
|
|
|
|
@codelet('description-builder')
|
|
def description_builder(ctx, codelet):
|
|
"""
|
|
Builds a description that has passed the strength test.
|
|
|
|
This codelet actually constructs descriptions in the workspace. It handles two
|
|
scenarios: either the object already has this descriptor (activation only) or
|
|
the descriptor needs to be newly created.
|
|
|
|
Behavior:
|
|
1. Verifies the target object still exists in the workspace
|
|
2. Checks if the object already has this specific descriptor
|
|
3. If already described: activates concepts only
|
|
4. If not described: builds the new descriptor relationship
|
|
5. Updates buffer activations for relevant concepts
|
|
|
|
Args:
|
|
ctx: Context containing workspace
|
|
codelet: Codelet with argument containing the description to build
|
|
|
|
Notes:
|
|
This implements the final step of description creation after strength testing
|
|
Distinguishes between creating new descriptions vs. reactivating existing ones
|
|
Ensures consistency with workspace state
|
|
"""
|
|
workspace = ctx.workspace
|
|
description = codelet.arguments[0]
|
|
assert description.object in workspace.objects
|
|
if description.object.described(description.descriptor):
|
|
description.descriptionType.buffer = 100.0
|
|
description.descriptor.buffer = 100.0
|
|
else:
|
|
description.build()
|
|
|
|
|
|
def __supportForDescriptionType(ctx, descriptionType, string):
|
|
"""
|
|
Calculate support score for a description type within a specific string.
|
|
|
|
This function measures how well-supported a description type is within a string
|
|
by examining both the activation of the concept in the slipnet and the
|
|
prevalence of that description type among objects in the string.
|
|
|
|
Args:
|
|
ctx: Context containing workspace
|
|
descriptionType: The type of description to evaluate (e.g., letterCategory)
|
|
string: The workspace string to analyze
|
|
|
|
Returns:
|
|
float: Support score between 0-1 (higher = better supported)
|
|
|
|
Notes:
|
|
Support combines conceptual activation and empirical prevalence
|
|
High support indicates the description type is both conceptually active
|
|
and empirically common within the string
|
|
"""
|
|
workspace = ctx.workspace
|
|
described_count = 0
|
|
total = 0
|
|
for o in workspace.objects:
|
|
if o.string == string:
|
|
total += 1
|
|
described_count += sum(1 for d in o.descriptions if d.descriptionType == descriptionType)
|
|
string_support = described_count / float(total)
|
|
return (descriptionType.activation + string_support) / 2
|
|
|
|
|
|
def __chooseBondFacet(ctx, source, destination):
|
|
"""
|
|
Select the best descriptor type/facet for forming a bond between two objects.
|
|
|
|
This function implements the bond facet selection heuristic, choosing among
|
|
available descriptor types that both objects can participate in, weighting
|
|
the choice by how well-supported each facet is in the source object's string.
|
|
|
|
Args:
|
|
ctx: Context containing random, slipnet components
|
|
source: Source object for the bond
|
|
destination: Destination object for the bond
|
|
|
|
Returns:
|
|
Slipnode: The chosen bond facet/slot for the bond
|
|
|
|
Notes:
|
|
Only considers descriptor types that both objects actually have
|
|
Supports include letterCategory and length by default
|
|
Weighted selection favors facets with higher support scores
|
|
Supports probabilistic exploration of bond possibilities
|
|
"""
|
|
random = ctx.random
|
|
slipnet = ctx.slipnet
|
|
|
|
# specify the descriptor types that bonds can form between
|
|
b = [
|
|
slipnet.letterCategory,
|
|
slipnet.length,
|
|
]
|
|
|
|
sourceFacets = [d.descriptionType for d in source.descriptions if d.descriptionType in b]
|
|
bond_edge_types = [d.descriptionType for d in destination.descriptions if d.descriptionType in sourceFacets]
|
|
supports = [__supportForDescriptionType(ctx, f, source.string) for f in bond_edge_types]
|
|
return random.weighted_choice(bond_edge_types, supports)
|
|
|
|
|
|
@codelet('bottom-up-bond-scout')
|
|
def bottom_up_bond_scout(ctx, codelet):
|
|
"""
|
|
Bottom-up bond scout that discovers bonds between neighboring objects.
|
|
|
|
This codelet implements data-driven bond formation, starting from objects
|
|
in the workspace and seeking to create bonds with their immediate neighbors.
|
|
It's called "bottom-up" because it starts with concrete objects rather than
|
|
abstract bond categories.
|
|
|
|
Behavior:
|
|
1. Selects a source object based on intra-string salience
|
|
2. Chooses a neighbor of the source object as destination
|
|
3. Determines the best bond facet/description type to use
|
|
4. Extracts descriptors for both objects using the chosen facet
|
|
5. Determines bond category (sameness, successor, etc.) from descriptors
|
|
6. Proposes the bond for strength testing and potential creation
|
|
|
|
Args:
|
|
ctx: Context containing coderack, slipnet, workspace components
|
|
codelet: The bond scout codelet instance
|
|
|
|
Notes:
|
|
This enables opportunistic bond discovery between adjacent objects
|
|
Considers both letter-based and structural (length) relationships
|
|
Maps onto adjacent objects to form bonds within strings
|
|
Successor bonds require specific letter relationships
|
|
"""
|
|
coderack = ctx.coderack
|
|
slipnet = ctx.slipnet
|
|
workspace = ctx.workspace
|
|
source = chooseUnmodifiedObject(ctx, 'intraStringSalience', workspace.objects)
|
|
assert source is not None
|
|
__showWhichStringObjectIsFrom(source)
|
|
destination = chooseNeighbor(ctx, source)
|
|
assert destination
|
|
logging.info('destination: %s', destination)
|
|
bond_edge_type = __chooseBondFacet(ctx, source, destination)
|
|
assert bond_edge_type
|
|
logging.info('chosen bond facet: %s', bond_edge_type.get_name())
|
|
logging.info('Source: %s, destination: %s', source, destination)
|
|
bond_descriptors = __getDescriptors(bond_edge_type, source, destination)
|
|
sourceDescriptor, destinationDescriptor = bond_descriptors
|
|
logging.info("source descriptor: %s", sourceDescriptor.name.upper())
|
|
logging.info("destination descriptor: %s",
|
|
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, bond_edge_type,
|
|
sourceDescriptor, destinationDescriptor)
|
|
|
|
|
|
@codelet('rule-scout')
|
|
def rule_scout(ctx, codelet):
|
|
"""
|
|
Rule scout that generates transformation rules based on letter changes.
|
|
|
|
This codelet analyzes which letters changed between initial and modified strings
|
|
and attempts to generalize these changes into rules. Rules represent patterns
|
|
like "change leftmost letter to its successor" or "change 'b' to 'd'".
|
|
|
|
Behavior:
|
|
1. Assumes all letters have been replaced (no unreplaced objects)
|
|
2. Finds objects that have changed (have replacements)
|
|
3. If no changes, proposes empty rule
|
|
4. For the last changed object, generates distinguishing descriptors
|
|
5. Filters descriptors based on correspondence slippages and target objects
|
|
6. Selects descriptor using conceptual depth and temperature weighting
|
|
7. Selects relational transformation using conceptual depth weighting
|
|
8. Proposes the rule for strength testing
|
|
|
|
Args:
|
|
ctx: Context containing coderack, random, slipnet, temperature, workspace
|
|
codelet: The rule scout codelet instance
|
|
|
|
Notes:
|
|
Distinguishing descriptors include position (leftmost, rightmost, middle)
|
|
and letter category (if unique within the string)
|
|
Conceptual depth guides selection toward more abstract, human-like rules
|
|
Temperature affects both descriptor and relation selection probabilities
|
|
Only generates rules for letter transformations, not other structure changes
|
|
"""
|
|
coderack = ctx.coderack
|
|
random = ctx.random
|
|
slipnet = ctx.slipnet
|
|
# TODO: use entropy
|
|
temperature = ctx.temperature
|
|
workspace = ctx.workspace
|
|
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)
|
|
|
|
changed = changedObjects[-1]
|
|
# generate a list of distinguishing descriptions for the first object
|
|
# ie. string-position (left-,right-most,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):
|
|
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.described(node):
|
|
if targetObject.distinguishingDescriptor(node):
|
|
newList += [node]
|
|
objectList = newList # surely this should be +=
|
|
# "union of this and distinguishing descriptors"
|
|
assert objectList
|
|
# use conceptual depth to choose a description
|
|
# TODO: use entropy
|
|
weights = [
|
|
temperature.getAdjustedValue(node.conceptualDepth)
|
|
for node in objectList
|
|
]
|
|
descriptor = random.weighted_choice(objectList, weights)
|
|
# choose the relation (change the leftmost object to "successor" or "d"
|
|
objectList = []
|
|
if changed.replacement.relation:
|
|
objectList += [changed.replacement.relation]
|
|
objectList += [changed.replacement.objectFromModified.getDescriptor(
|
|
slipnet.letterCategory)]
|
|
# TODO: use entropy
|
|
# use conceptual depth to choose a relation
|
|
weights = [
|
|
temperature.getAdjustedValue(node.conceptualDepth)
|
|
for node in objectList
|
|
]
|
|
relation = random.weighted_choice(objectList, weights)
|
|
coderack.proposeRule(slipnet.letterCategory, descriptor,
|
|
slipnet.letter, relation)
|
|
|
|
|
|
@codelet('rule-strength-tester')
|
|
def rule_strength_tester(ctx, codelet):
|
|
"""
|
|
Tests the strength of a proposed rule to determine if it should be built.
|
|
|
|
This codelet evaluates whether a rule generated by rule-scout is strong enough
|
|
to warrant creation in the workspace. Only rules that pass this probabilistic
|
|
strength test proceed to the rule-builder.
|
|
|
|
Behavior:
|
|
1. Gets the rule to test from codelet arguments
|
|
2. Updates the rule's strength based on current workspace state
|
|
3. Converts strength to acceptance probability via temperature adjustment
|
|
4. If passes probability test, queues rule-builder codelet
|
|
5. If fails, the rule is discarded (fizzles)
|
|
|
|
Args:
|
|
ctx: Context containing coderack, random, temperature components
|
|
codelet: Codelet with argument containing the rule to test
|
|
|
|
Notes:
|
|
Rule strength depends on how well the rule describes the observed changes
|
|
Higher temperature = more lenient rule acceptance (exploration)
|
|
Lower temperature = stricter rule acceptance (exploitation)
|
|
Failed rules are discarded, ensuring only coherent patterns are adopted
|
|
"""
|
|
coderack = ctx.coderack
|
|
random = ctx.random
|
|
# TODO: use entropy
|
|
temperature = ctx.temperature
|
|
rule = codelet.arguments[0]
|
|
rule.updateStrength()
|
|
# TODO: use entropy
|
|
probability = temperature.getAdjustedProbability(rule.totalStrength / 100.0)
|
|
if random.coinFlip(probability):
|
|
coderack.newCodelet('rule-builder', rule.totalStrength, [rule])
|
|
|
|
|
|
@codelet('replacement-finder')
|
|
def replacement_finder(ctx, codelet):
|
|
"""
|
|
Finds and establishes letter replacements between initial and modified strings.
|
|
|
|
This codelet analyzes character-by-character changes between the initial string
|
|
and modified string, creating Replacement objects that represent letter transformations.
|
|
This is a prerequisite for rule generation, as rules describe patterns in letter changes.
|
|
|
|
Behavior:
|
|
1. Randomly selects an unmodified letter from the initial string
|
|
2. Finds the corresponding letter at the same position in modified string
|
|
3. Calculates the ASCII difference between the letters
|
|
4. Maps small differences to semantic relations (sameness, successor, predecessor)
|
|
5. Creates a Replacement object linking the letters with their relation
|
|
6. Marks the letter as changed if transformation is not sameness
|
|
7. Updates the workspace's changed object for rule generation
|
|
|
|
Args:
|
|
ctx: Context containing random, slipnet, workspace components
|
|
codelet: The replacement finder codelet instance
|
|
|
|
Notes:
|
|
Only processes ASCII differences < 2 (immediate neighbors in alphabet)
|
|
Larger differences create replacements without semantic relations
|
|
Successor/predecessor relations support rule patterns like "increment alphabet"
|
|
Changed objects trigger rule generation by rule-scout codelets
|
|
Requires exact positional correspondence between initial and modified strings
|
|
"""
|
|
random = ctx.random
|
|
slipnet = ctx.slipnet
|
|
workspace = ctx.workspace
|
|
# choose random letter in initial string
|
|
letters = [o for o in workspace.initial.objects if isinstance(o, Letter)]
|
|
assert letters
|
|
letterOfInitialString = random.choice(letters)
|
|
assert not letterOfInitialString.replacement
|
|
position = letterOfInitialString.leftIndex
|
|
moreLetters = [o for o in workspace.modified.objects
|
|
if isinstance(o, Letter) and o.leftIndex == position]
|
|
assert moreLetters
|
|
letterOfModifiedString = moreLetters[0]
|
|
initialAscii = ord(workspace.initialString[position - 1])
|
|
modifiedAscii = ord(workspace.modifiedString[position - 1])
|
|
diff = initialAscii - modifiedAscii
|
|
if abs(diff) < 2:
|
|
relations = {
|
|
0: slipnet.sameness,
|
|
-1: slipnet.successor,
|
|
1: slipnet.predecessor
|
|
}
|
|
relation = relations[diff]
|
|
else:
|
|
relation = None
|
|
letterOfInitialString.replacement = Replacement(ctx, letterOfInitialString,
|
|
letterOfModifiedString, relation)
|
|
if relation != slipnet.sameness:
|
|
letterOfInitialString.changed = True
|
|
workspace.changedObject = letterOfInitialString
|
|
|
|
|
|
@codelet('top-down-bond-scout--category')
|
|
def top_down_bond_scout__category(ctx, codelet):
|
|
"""
|
|
Top-down bond scout guided by specific bond category (sameness, successor, etc.).
|
|
|
|
This codelet implements concept-driven bond formation, starting from a specific
|
|
bond category and seeking to create bonds that match that category. It's called
|
|
"top-down" because it starts with abstract relationship concepts rather than
|
|
concrete object properties.
|
|
|
|
Behavior:
|
|
1. Gets bond category from codelet arguments (e.g., sameness, successor)
|
|
2. Selects source object using category relevance to initial/target strings
|
|
3. Chooses neighbor of source object as destination
|
|
4. Determines best bond facet/description type for both objects
|
|
5. Extracts descriptors for source and destination objects
|
|
6. Calculates forward bond category (source -> destination)
|
|
7. Handles identity bonds by converting to sameness category
|
|
8. Calculates backward bond category (destination -> source)
|
|
9. Proposes bond in correct direction to match the desired category
|
|
|
|
Args:
|
|
ctx: Context containing coderack, slipnet, workspace components
|
|
codelet: Codelet with bond category as argument
|
|
|
|
Notes:
|
|
Bidirectional bond evaluation ensures optimal direction matching
|
|
Category-guided selection promotes goal-directed bond formation
|
|
Distinguishes between forward and backward bond categories
|
|
Integrates with conceptual relevance assessment for source selection
|
|
"""
|
|
coderack = ctx.coderack
|
|
slipnet = ctx.slipnet
|
|
logging.info('top_down_bond_scout__category')
|
|
category = codelet.arguments[0]
|
|
source = __getScoutSource(ctx, category, formulas.localBondCategoryRelevance,
|
|
'bond')
|
|
destination = chooseNeighbor(ctx, source)
|
|
logging.info('source: %s, destination: %s', source, destination)
|
|
assert destination
|
|
bond_edge_type = __chooseBondFacet(ctx, source, destination)
|
|
assert bond_edge_type
|
|
sourceDescriptor, destinationDescriptor = __getDescriptors(
|
|
bond_edge_type, 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,
|
|
bond_edge_type, sourceDescriptor,
|
|
destinationDescriptor)
|
|
else:
|
|
coderack.proposeBond(destination, source, category,
|
|
bond_edge_type, destinationDescriptor,
|
|
sourceDescriptor)
|
|
|
|
|
|
@codelet('top-down-bond-scout--direction')
|
|
def top_down_bond_scout__direction(ctx, codelet):
|
|
"""
|
|
Top-down bond scout guided by specific direction category (left, right).
|
|
|
|
This codelet implements direction-driven bond formation, starting from a specific
|
|
direction concept (left or right) and seeking to create bonds in that direction.
|
|
It's another form of top-down processing that focuses on spatial relationships
|
|
rather than semantic bond categories.
|
|
|
|
Behavior:
|
|
1. Gets direction category from codelet arguments (left or right)
|
|
2. Selects source object using direction relevance to initial/target strings
|
|
3. Chooses neighbor in the specific direction using chooseDirectedNeighbor
|
|
4. Determines best bond facet/description type for both objects
|
|
5. Extracts descriptors for source and destination objects
|
|
6. Determines bond category from the descriptor relationship
|
|
7. Handles identity bonds by converting to sameness category
|
|
8. Proposes bond with determined category and descriptors
|
|
|
|
Args:
|
|
ctx: Context containing coderack, slipnet, workspace components
|
|
codelet: Codelet with direction category as argument
|
|
|
|
Notes:
|
|
Direction-guided selection promotes spatially coherent bond formation
|
|
Unlike category scouts, this focuses on object adjacency and position
|
|
Uses direction relevance assessment for optimal source object selection
|
|
Enables goal-directed bond discovery based on directional preferences
|
|
"""
|
|
coderack = ctx.coderack
|
|
slipnet = ctx.slipnet
|
|
direction = codelet.arguments[0]
|
|
source = __getScoutSource(ctx, direction, formulas.localDirectionCategoryRelevance,
|
|
'bond')
|
|
destination = chooseDirectedNeighbor(ctx, source, direction)
|
|
assert destination
|
|
logging.info('to object: %s', destination)
|
|
bond_edge_type = __chooseBondFacet(ctx, source, destination)
|
|
assert bond_edge_type
|
|
sourceDescriptor, destinationDescriptor = __getDescriptors(
|
|
bond_edge_type, source, destination)
|
|
category = sourceDescriptor.getBondCategory(destinationDescriptor)
|
|
assert category
|
|
if category == slipnet.identity:
|
|
category = slipnet.sameness
|
|
coderack.proposeBond(source, destination, category, bond_edge_type,
|
|
sourceDescriptor, destinationDescriptor)
|
|
|
|
|
|
@codelet('bond-strength-tester')
|
|
def bond_strength_tester(ctx, codelet):
|
|
"""
|
|
Tests the strength of a proposed bond to determine if it should be built.
|
|
|
|
This codelet evaluates whether a bond generated by bond scouts is strong enough
|
|
to warrant creation in the workspace. Only bonds that pass this probabilistic
|
|
strength test proceed to the bond-builder.
|
|
|
|
Behavior:
|
|
1. Gets the bond to test from codelet arguments
|
|
2. Updates the bond's strength based on current workspace state
|
|
3. Converts strength to acceptance probability via temperature adjustment
|
|
4. Logs bond strength for debugging purposes
|
|
5. If passes probability test, activates bond descriptors and queues bond-builder
|
|
6. If fails, the bond is discarded (fizzles)
|
|
|
|
Args:
|
|
ctx: Context containing coderack, random, temperature components
|
|
codelet: Codelet with argument containing the bond to test
|
|
|
|
Notes:
|
|
Bond strength depends on descriptor compatibility and workspace constraints
|
|
Higher temperature = more lenient bond acceptance (exploration)
|
|
Lower temperature = stricter bond acceptance (exploitation)
|
|
Activating descriptors promotes relevant concepts for future bond formation
|
|
"""
|
|
coderack = ctx.coderack
|
|
random = ctx.random
|
|
# TODO: use entropy
|
|
temperature = ctx.temperature
|
|
bond = codelet.arguments[0]
|
|
__showWhichStringObjectIsFrom(bond)
|
|
bond.updateStrength()
|
|
strength = bond.totalStrength
|
|
# TODO: use entropy
|
|
probability = temperature.getAdjustedProbability(strength / 100.0)
|
|
logging.info('bond strength = %d for %s', strength, bond)
|
|
assert random.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', strength, [bond])
|
|
|
|
|
|
@codelet('bond-builder')
|
|
def bond_builder(ctx, codelet):
|
|
"""
|
|
Builds a bond that has passed the strength test.
|
|
|
|
This codelet actually constructs bonds in the workspace after they have
|
|
passed strength testing. It handles conflicts with existing structures
|
|
and ensures bond creation follows workspace constraints.
|
|
|
|
Behavior:
|
|
1. Verifies source and destination objects still exist in workspace
|
|
2. Checks for existing equivalent bonds (same neighbors and categories)
|
|
3. If equivalent exists, activates descriptors and exits early
|
|
4. Identifies incompatible bonds that would conflict with this bond
|
|
5. Fights incompatible bonds using strength-based competition
|
|
6. Identifies incompatible groups that would conflict
|
|
7. Fights incompatible groups using strength-based competition
|
|
8. Handles special cases for end-of-string bonds with correspondences
|
|
9. Breaks/removes all defeated incompatible structures
|
|
10. Finally constructs the bond in the workspace
|
|
|
|
Args:
|
|
ctx: Context containing workspace
|
|
codelet: Codelet with argument containing the bond to build
|
|
|
|
Notes:
|
|
Bond building involves complex conflict resolution with existing structures
|
|
Incompatible structures are removed only if the new bond wins competitions
|
|
Special consideration for end-of-string bonds affects correspondence handling
|
|
Successful bond creation activates bond categories and descriptor concepts
|
|
"""
|
|
workspace = ctx.workspace
|
|
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.sameNeighbors(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 __fight(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()
|
|
|
|
|
|
# pylint: disable=too-many-branches
|
|
# pylint: disable=too-many-statements
|
|
@codelet('top-down-group-scout--category')
|
|
def top_down_group_scout__category(ctx, codelet):
|
|
"""
|
|
Top-down group scout guided by specific group category.
|
|
|
|
This codelet implements concept-driven group formation, starting from a specific
|
|
group category (sameness group, successor group, etc.) and seeking to create
|
|
groups that match that category. It's called "top-down" because it starts with
|
|
abstract group concepts rather than concrete object relationships.
|
|
|
|
Behavior:
|
|
1. Gets group category from codelet arguments
|
|
2. Maps group category to corresponding bond category
|
|
3. Selects source object using bond category relevance to initial/target strings
|
|
4. Determines group direction (left/right) based on source position or preference
|
|
5. Searches leftward from source to find leftmost object with matching bonds
|
|
6. Searchers rightward from leftmost to find rightmost object with matching bonds
|
|
7. Collects all objects and bonds between the span
|
|
8. Proposes group if valid span is found
|
|
|
|
Args:
|
|
ctx: Context containing coderack, random, slipnet, workspace components
|
|
codelet: Codelet with group category as argument
|
|
|
|
Notes:
|
|
Groups combine multiple objects linked by bonds of the same category and direction
|
|
Search strategy ensures cohesive groups with consistent bond properties
|
|
Handles singleton letter groups when no bonds match the desired category
|
|
Direction preference influences group formation toward left or right
|
|
"""
|
|
coderack = ctx.coderack
|
|
random = ctx.random
|
|
slipnet = ctx.slipnet
|
|
groupCategory = codelet.arguments[0]
|
|
category = groupCategory.getRelatedNode(slipnet.bondCategory)
|
|
assert category
|
|
source = __getScoutSource(ctx, category, formulas.localBondCategoryRelevance,
|
|
'group')
|
|
assert source and not source.spansString()
|
|
if source.leftmost:
|
|
direction = slipnet.right
|
|
elif source.rightmost:
|
|
direction = slipnet.left
|
|
else:
|
|
direction = random.weighted_choice(
|
|
[slipnet.left, slipnet.right],
|
|
[slipnet.left.activation, slipnet.right.activation]
|
|
)
|
|
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()
|
|
if random.coinFlip(probability):
|
|
coderack.proposeSingleLetterGroup(source)
|
|
return
|
|
direction = firstBond.directionCategory
|
|
search = True
|
|
bond_edge_type = None
|
|
# find leftmost object in group with these bonds
|
|
while search:
|
|
search = False
|
|
if not source.leftBond:
|
|
continue
|
|
if source.leftBond.category != category:
|
|
continue
|
|
if source.leftBond.directionCategory != direction:
|
|
if source.leftBond.directionCategory:
|
|
continue
|
|
if not bond_edge_type or bond_edge_type == source.leftBond.facet:
|
|
bond_edge_type = 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 not destination.rightBond:
|
|
continue
|
|
if destination.rightBond.category != category:
|
|
continue
|
|
if destination.rightBond.directionCategory != direction:
|
|
if destination.rightBond.directionCategory:
|
|
continue
|
|
if not bond_edge_type or bond_edge_type == destination.rightBond.facet:
|
|
bond_edge_type = 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, bond_edge_type)
|
|
|
|
|
|
@codelet('top-down-group-scout--direction')
|
|
def top_down_group_scout__direction(ctx, codelet):
|
|
"""
|
|
Top-down group scout guided by specific direction category.
|
|
|
|
This codelet implements direction-driven group formation, starting from a specific
|
|
direction concept (left or right) and seeking to create groups in that direction.
|
|
It's another form of top-down processing that focuses on spatial directional
|
|
relationships rather than semantic group categories.
|
|
|
|
Behavior:
|
|
1. Gets direction category from codelet arguments (left or right)
|
|
2. Selects source object using direction relevance to initial/target strings
|
|
3. Determines preferred direction based on source position or activation
|
|
4. Checks for existing bonds with matching direction category
|
|
5. If no suitable bonds, searches in alternate direction
|
|
6. Extends group by walking leftward from source with matching bonds
|
|
7. Extends group by walking rightward from leftmost with matching bonds
|
|
8. Collects all objects and bonds within the directional span
|
|
9. Proposes group with determined category and direction
|
|
|
|
Args:
|
|
ctx: Context containing coderack, random, slipnet, workspace components
|
|
codelet: Codelet with direction category as argument
|
|
|
|
Notes:
|
|
Direction preference influences which bonds are considered for group formation
|
|
Groups formed this way maintain consistent directional flow
|
|
Handles cases where no suitable directional bonds exist initially
|
|
Enables goal-directed group discovery based on directional coherence
|
|
"""
|
|
coderack = ctx.coderack
|
|
random = ctx.random
|
|
slipnet = ctx.slipnet
|
|
direction = codelet.arguments[0]
|
|
source = __getScoutSource(ctx, direction,
|
|
formulas.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:
|
|
mydirection = random.weighted_choice(
|
|
[slipnet.left, slipnet.right],
|
|
[slipnet.left.activation, slipnet.right.activation]
|
|
)
|
|
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)
|
|
bond_edge_type = None
|
|
# find leftmost object in group with these bonds
|
|
search = True
|
|
while search:
|
|
search = False
|
|
if not source.leftBond:
|
|
continue
|
|
if source.leftBond.category != category:
|
|
continue
|
|
if source.leftBond.directionCategory != direction:
|
|
if source.leftBond.directionCategory:
|
|
continue
|
|
if not bond_edge_type or bond_edge_type == source.leftBond.facet:
|
|
bond_edge_type = source.leftBond.facet
|
|
direction = source.leftBond.directionCategory
|
|
source = source.leftBond.leftObject
|
|
search = True
|
|
destination = source
|
|
search = True
|
|
while search:
|
|
search = False
|
|
if not destination.rightBond:
|
|
continue
|
|
if destination.rightBond.category != category:
|
|
continue
|
|
if destination.rightBond.directionCategory != direction:
|
|
if destination.rightBond.directionCategory:
|
|
continue
|
|
if not bond_edge_type or bond_edge_type == destination.rightBond.facet:
|
|
bond_edge_type = 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, bond_edge_type)
|
|
|
|
|
|
# noinspection PyStringFormat
|
|
@codelet('group-scout--whole-string')
|
|
def group_scout__whole_string(ctx, codelet):
|
|
"""
|
|
Whole-string group scout that attempts to group entire strings.
|
|
|
|
This codelet implements comprehensive group formation by attempting to create
|
|
groups that span entire strings or large portions thereof. It starts from the
|
|
leftmost object and walks through all bonds to identify potential whole-string
|
|
groupings.
|
|
|
|
Behavior:
|
|
1. Randomly selects either initial or target string to analyze
|
|
2. Finds the leftmost object in the selected string
|
|
3. Walks up the group hierarchy to find highest sameness group candidate
|
|
4. If object already spans string, proposes that existing group or singleton
|
|
5. Otherwise, walks through all bonds from leftmost to rightmost
|
|
6. Randomly selects one bond to determine group category and direction
|
|
7. Filters bonds to those compatible with group formation constraints
|
|
8. Proposes group spanning entire string with consistent properties
|
|
|
|
Args:
|
|
ctx: Context containing coderack, random, slipnet, workspace components
|
|
codelet: The whole-string group scout codelet instance
|
|
|
|
Notes:
|
|
This implements the most comprehensive group formation strategy
|
|
Considers existing group hierarchies to avoid redundant groupings
|
|
Random bond selection introduces probabilistic diversity in group categories
|
|
Handles both full string spanning groups and individual singleton letters
|
|
Enables discovery of patterns that encompass entire string structures
|
|
"""
|
|
coderack = ctx.coderack
|
|
random = ctx.random
|
|
slipnet = ctx.slipnet
|
|
workspace = ctx.workspace
|
|
string = random.choice([workspace.initial, workspace.target])
|
|
# find leftmost object & the highest group to which it belongs
|
|
leftmost = next((o for o in string.objects if o.leftmost), None)
|
|
assert leftmost is not None
|
|
while leftmost.group and leftmost.group.bondCategory == slipnet.sameness:
|
|
leftmost = leftmost.group
|
|
if leftmost.spansString():
|
|
# the object already spans the string - propose this object
|
|
if isinstance(leftmost, Group):
|
|
group = leftmost
|
|
coderack.proposeGroup(group.objectList, group.bondList,
|
|
group.groupCategory, group.directionCategory,
|
|
group.facet)
|
|
else:
|
|
coderack.proposeSingleLetterGroup(leftmost)
|
|
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 = random.choice(bonds)
|
|
bonds = chosenBond.possibleGroupBonds(bonds)
|
|
assert bonds
|
|
category = chosenBond.category
|
|
groupCategory = category.getRelatedNode(slipnet.groupCategory)
|
|
directionCategory = chosenBond.directionCategory
|
|
bond_edge_type = chosenBond.facet
|
|
coderack.proposeGroup(objects, bonds, groupCategory, directionCategory, bond_edge_type)
|
|
|
|
|
|
@codelet('group-strength-tester')
|
|
def group_strength_tester(ctx, codelet):
|
|
"""
|
|
Tests the strength of a proposed group to determine if it should be built.
|
|
|
|
This codelet evaluates whether a group generated by group scouts is strong enough
|
|
to warrant creation in the workspace. Only groups that pass this probabilistic
|
|
strength test proceed to the group-builder.
|
|
|
|
Behavior:
|
|
1. Gets the group to test from codelet arguments
|
|
2. Updates the group's strength based on current workspace state
|
|
3. Converts strength to acceptance probability via temperature adjustment
|
|
4. Logs group strength for debugging purposes
|
|
5. If passes probability test, activates related concepts and queues group-builder
|
|
6. If fails, the group is discarded (fizzles)
|
|
|
|
Args:
|
|
ctx: Context containing coderack, random, temperature components
|
|
codelet: Codelet with argument containing the group to test
|
|
|
|
Notes:
|
|
Group strength depends on cohesion of constituent bonds and object relationships
|
|
Higher temperature = more lenient group acceptance (exploration)
|
|
Lower temperature = stricter group acceptance (exploitation)
|
|
Activating related bond and direction concepts supports future group formation
|
|
Failed groups are discarded, ensuring only coherent patterns are adopted
|
|
"""
|
|
coderack = ctx.coderack
|
|
random = ctx.random
|
|
slipnet = ctx.slipnet
|
|
# TODO: use entropy
|
|
temperature = ctx.temperature
|
|
# update strength value of the group
|
|
group = codelet.arguments[0]
|
|
__showWhichStringObjectIsFrom(group)
|
|
group.updateStrength()
|
|
strength = group.totalStrength
|
|
# TODO: use entropy
|
|
probability = temperature.getAdjustedProbability(strength / 100.0)
|
|
if random.coinFlip(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', strength, [group])
|
|
|
|
|
|
@codelet('group-builder')
|
|
def group_builder(ctx, codelet):
|
|
"""
|
|
Builds a group that has passed the strength test.
|
|
|
|
This codelet actually constructs groups in the workspace after they have
|
|
passed strength testing. It handles conflicts with existing structures,
|
|
creates necessary bonds, and ensures group creation follows workspace
|
|
constraints.
|
|
|
|
Behavior:
|
|
1. Verifies the group proposal and checks for equivalent existing groups
|
|
2. If equivalent exists, activates group descriptors and exits early
|
|
3. Validates that all group objects still exist in workspace
|
|
4. Identifies incompatible bonds that would conflict with group formation
|
|
5. Fights incompatible bonds using strength-based competition
|
|
6. Identifies incompatible groups that contain conflicting object subsets
|
|
7. Fights incompatible groups using strength-based competition
|
|
8. Creates missing bonds between adjacent objects in the group
|
|
9. Builds the group structure in the workspace
|
|
10. Activates group-related conceptual descriptions
|
|
|
|
Args:
|
|
ctx: Context containing slipnet, workspace components
|
|
codelet: Codelet with argument containing the group to build
|
|
|
|
Notes:
|
|
Group building involves complex conflict resolution with existing structures
|
|
Incompatible structures are removed only if the new group wins competitions
|
|
Missing bonds are automatically created between group objects
|
|
Successful group creation activates group categories and descriptor concepts
|
|
Groups provide hierarchical organization for sets of related objects
|
|
"""
|
|
slipnet = ctx.slipnet
|
|
workspace = ctx.workspace
|
|
# update strength value of the group
|
|
group = codelet.arguments[0]
|
|
__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:]:
|
|
leftBond = objekt.leftBond
|
|
if leftBond:
|
|
if leftBond.leftObject == previous:
|
|
continue
|
|
if leftBond.directionCategory == group.directionCategory:
|
|
continue
|
|
incompatibleBonds += [leftBond]
|
|
previous = objekt
|
|
next_object = group.objectList[-1]
|
|
for objekt in reversed(group.objectList[:-1]):
|
|
rightBond = objekt.rightBond
|
|
if rightBond:
|
|
if rightBond.rightObject == next_object:
|
|
continue
|
|
if rightBond.directionCategory == group.directionCategory:
|
|
continue
|
|
incompatibleBonds += [rightBond]
|
|
next_object = 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)):
|
|
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(ctx, 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')
|
|
|
|
|
|
@codelet('rule-builder')
|
|
def rule_builder(ctx, codelet):
|
|
"""
|
|
Builds a rule that has passed the strength test.
|
|
|
|
This codelet actually constructs rules in the workspace after they have
|
|
passed strength testing. It handles rule equivalence checks and implements
|
|
the competitive rule replacement mechanism.
|
|
|
|
Behavior:
|
|
1. Gets the rule to build from codelet arguments
|
|
2. Checks if the rule is equivalent to the existing workspace rule
|
|
3. If equivalent, activates rule descriptors and exits early
|
|
4. Updates rule strength based on current workspace state
|
|
5. If workspace already has a rule, fights for replacement
|
|
6. Only proceeds if new rule wins the strength competition
|
|
7. Builds the rule in the workspace, replacing any existing rule
|
|
|
|
Args:
|
|
ctx: Context containing workspace
|
|
codelet: Codelet with argument containing the rule to build
|
|
|
|
Notes:
|
|
Rules represent transformation patterns that map initial to target letters
|
|
Only one rule can exist at a time in the workspace
|
|
Rule replacement requires competitive strength testing
|
|
Equivalent rules activate their conceptual descriptions without replacement
|
|
Rules serve as the final abstraction layer in the Copycat algorithm
|
|
"""
|
|
workspace = ctx.workspace
|
|
rule = codelet.arguments[0]
|
|
if rule.ruleEqual(workspace.rule):
|
|
rule.activateRuleDescriptions()
|
|
return
|
|
rule.updateStrength()
|
|
assert rule.totalStrength
|
|
# fight against other rules
|
|
if workspace.rule is not None:
|
|
assert __structureVsStructure(rule, 1.0, workspace.rule, 1.0)
|
|
workspace.buildRule(rule)
|
|
|
|
|
|
def __getCutoffWeights(bondDensity):
|
|
"""
|
|
Get weighted probabilities for selecting answer cutoff thresholds based on bond density.
|
|
|
|
This function implements an adaptive strategy for deciding when the system has found
|
|
sufficient structural information to translate a rule into a final answer. Higher bond
|
|
density indicates more complexity in the workspace, affecting the optimal cutoff strategy.
|
|
|
|
Args:
|
|
bondDensity (float): Density of bonds in workspace (0.0 to 1.0+)
|
|
|
|
Returns:
|
|
list: List of 10 probability weights for cutoff thresholds
|
|
|
|
Notes:
|
|
Higher bond density -> earlier cutoff thresholds weighted heavily
|
|
Lower bond density -> later cutoff thresholds weighted heavily
|
|
Adaptive weighting helps system find optimal solution timing
|
|
Cutoff range typically 10-100 codelet executions
|
|
"""
|
|
if bondDensity > 0.8:
|
|
return [5.0, 150.0, 5.0, 2.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]
|
|
elif bondDensity > 0.6:
|
|
return [2.0, 5.0, 150.0, 5.0, 2.0, 1.0, 1.0, 1.0, 1.0, 1.0]
|
|
elif bondDensity > 0.4:
|
|
return [1.0, 2.0, 5.0, 150.0, 5.0, 2.0, 1.0, 1.0, 1.0, 1.0]
|
|
elif bondDensity > 0.2:
|
|
return [1.0, 1.0, 2.0, 5.0, 150.0, 5.0, 2.0, 1.0, 1.0, 1.0]
|
|
else:
|
|
return [1.0, 1.0, 1.0, 2.0, 5.0, 150.0, 5.0, 2.0, 1.0, 1.0]
|
|
|
|
|
|
@codelet('rule-translator')
|
|
def rule_translator(ctx, codelet):
|
|
"""
|
|
Translates a rule into a final answer when termination criteria are met.
|
|
|
|
This codelet implements the final step of the Copycat algorithm, translating
|
|
the discovered rule pattern into a concrete answer for the letter sequence
|
|
problem. It uses adaptive cutoff strategies based on workspace complexity.
|
|
|
|
Behavior:
|
|
1. Verifies that a rule exists in the workspace
|
|
2. Calculates bond density as a measure of workspace complexity
|
|
3. Uses adaptive cutoff weights based on bond density
|
|
4. Randomly selects termination cutoff threshold
|
|
5. If current temperature reaches cutoff, attempts rule translation
|
|
6. If translation succeeds, sets workspace final answer
|
|
7. If translation fails, clamps temperature to delay termination
|
|
|
|
Args:
|
|
ctx: Context containing coderack, random, temperature, workspace components
|
|
codelet: The rule translator codelet instance
|
|
|
|
Notes:
|
|
Bond density indicates workspace complexity (higher = more complex)
|
|
Higher bond density leads to earlier termination cutoffs
|
|
Lower bond density leads to later termination cutoffs
|
|
Cutoff range typically spans from 10-100 codelet executions
|
|
Translation failure triggers temperature clamping to prevent early termination
|
|
Only successful translations produce final answers
|
|
"""
|
|
coderack = ctx.coderack
|
|
random = ctx.random
|
|
# TODO: use entropy
|
|
temperature = ctx.temperature
|
|
workspace = ctx.workspace
|
|
assert workspace.rule
|
|
if len(workspace.initial) + len(workspace.target) <= 2:
|
|
bondDensity = 1.0
|
|
else:
|
|
numberOfBonds = len(workspace.initial.bonds) + len(workspace.target.bonds)
|
|
nearlyTotalLength = len(workspace.initial) + len(workspace.target) - 2
|
|
bondDensity = numberOfBonds / nearlyTotalLength
|
|
bondDensity = min(bondDensity, 1.0)
|
|
weights = __getCutoffWeights(bondDensity)
|
|
cutoff = 10.0 * random.weighted_choice(list(range(1, 11)), weights)
|
|
# TODO: use entropy
|
|
if cutoff >= temperature.actual_value:
|
|
result = workspace.rule.buildTranslatedRule()
|
|
if result is not None:
|
|
workspace.finalAnswer = result
|
|
else:
|
|
temperature.clampUntil(coderack.codeletsRun + 100)
|
|
|
|
|
|
@codelet('bottom-up-correspondence-scout')
|
|
def bottom_up_correspondence_scout(ctx, codelet):
|
|
"""
|
|
Bottom-up correspondence scout that discovers mappings between strings.
|
|
|
|
This codelet implements data-driven correspondence formation, starting from
|
|
specific objects in the workspace and seeking to create correspondences
|
|
between objects in the initial and target strings. It's called "bottom-up"
|
|
because it starts with concrete objects rather than abstract correspondence
|
|
concepts.
|
|
|
|
Behavior:
|
|
1. Selects object from initial string based on inter-string salience
|
|
2. Selects object from target string based on inter-string salience
|
|
3. Ensures both objects have same span structure (single vs multi-object)
|
|
4. Computes possible concept mappings between object descriptions
|
|
5. Verifies that concept slippage is possible for at least one mapping
|
|
6. Filters mappings to only those that are distinguishing
|
|
7. Handles string-level flipping when objects span entire strings
|
|
8. Proposes correspondence with viable concept mappings
|
|
|
|
Args:
|
|
ctx: Context containing coderack, slipnet, workspace components
|
|
codelet: The correspondence scout codelet instance
|
|
|
|
Notes:
|
|
This enables opportunistic correspondence discovery between string objects
|
|
Concept mappings allow metaphorical connections beyond literal matches
|
|
Distinguishing mappings ensure correspondences are meaningful and unique
|
|
String flipping handles reverse mappings (ABC->XYZ vs XYZ->ABC)
|
|
Inter-string salience guides selection toward most relevant objects
|
|
"""
|
|
coderack = ctx.coderack
|
|
slipnet = ctx.slipnet
|
|
workspace = ctx.workspace
|
|
objectFromInitial = chooseUnmodifiedObject(ctx, 'interStringSalience',
|
|
workspace.initial.objects)
|
|
assert objectFromInitial is not None
|
|
objectFromTarget = chooseUnmodifiedObject(ctx, 'interStringSalience',
|
|
workspace.target.objects)
|
|
assert objectFromTarget is not None
|
|
assert objectFromInitial.spansString() == objectFromTarget.spansString()
|
|
# get the posible concept mappings
|
|
conceptMappings = formulas.getMappings(
|
|
objectFromInitial, objectFromTarget,
|
|
objectFromInitial.relevantDescriptions(),
|
|
objectFromTarget.relevantDescriptions())
|
|
assert conceptMappings and __slippability(ctx, 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
|
|
opposites = [m for m in distinguishingMappings
|
|
if m.initialDescriptionType == slipnet.stringPositionCategory
|
|
and m.initialDescriptionType != slipnet.bond_edge_type]
|
|
initialDescriptionTypes = [m.initialDescriptionType for m in opposites]
|
|
flipTargetObject = False
|
|
if (objectFromInitial.spansString() and
|
|
objectFromTarget.spansString() and
|
|
slipnet.directionCategory in initialDescriptionTypes
|
|
and all(m.label == slipnet.opposite for m in opposites) # unreached?
|
|
and slipnet.opposite.activation != 100.0):
|
|
objectFromTarget = objectFromTarget.flippedVersion()
|
|
conceptMappings = formulas.getMappings(
|
|
objectFromInitial, objectFromTarget,
|
|
objectFromInitial.relevantDescriptions(),
|
|
objectFromTarget.relevantDescriptions())
|
|
flipTargetObject = True
|
|
coderack.proposeCorrespondence(objectFromInitial, objectFromTarget,
|
|
conceptMappings, flipTargetObject)
|
|
|
|
|
|
@codelet('important-object-correspondence-scout')
|
|
def important_object_correspondence_scout(ctx, codelet):
|
|
"""
|
|
Important object correspondence scout guided by conceptual importance.
|
|
|
|
This codelet implements concept-driven correspondence formation, starting from
|
|
the most important objects in the initial string and seeking to create
|
|
correspondences based on key distinguishing descriptors. It focuses on
|
|
objects that are conceptually significant rather than just salient.
|
|
|
|
Behavior:
|
|
1. Selects object from initial string based on relative importance
|
|
2. Gets distinguishing descriptors from the selected object
|
|
3. Applies concept slippages to map descriptors across strings
|
|
4. Chooses descriptor based on temperature-adjusted conceptual depth
|
|
5. Finds target objects that have matching descriptions
|
|
6. Selects target object based on inter-string salience
|
|
7. Ensures both objects have same span structure
|
|
8. Computes concept mappings between object descriptions
|
|
9. Verifies concept slippage possibility and distinguishing properties
|
|
10. Handles string-level flipping for reverse mappings
|
|
11. Proposes correspondence with viable concept mappings
|
|
|
|
Args:
|
|
ctx: Context containing coderack, random, slipnet, temperature, workspace components
|
|
codelet: The important object correspondence scout codelet instance
|
|
|
|
Notes:
|
|
This enables goal-directed correspondence formation based on importance
|
|
Conceptual depth weighting favors more abstract, human-like mappings
|
|
Temperature adjustment allows for probabilistic descriptor selection
|
|
Important objects drive the formation of key structural relationships
|
|
Contrasts with bottom-up scouts that work opportunistically
|
|
"""
|
|
coderack = ctx.coderack
|
|
random = ctx.random
|
|
slipnet = ctx.slipnet
|
|
# TODO: use entropy
|
|
temperature = ctx.temperature
|
|
workspace = ctx.workspace
|
|
objectFromInitial = chooseUnmodifiedObject(ctx, 'relativeImportance',
|
|
workspace.initial.objects)
|
|
assert objectFromInitial is not None
|
|
descriptors = objectFromInitial.relevantDistinguishingDescriptors()
|
|
# choose descriptor by conceptual depth
|
|
# TODO: use entropy
|
|
weights = [temperature.getAdjustedValue(n.conceptualDepth) for n in descriptors]
|
|
slipnode = random.weighted_choice(descriptors, weights)
|
|
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(ctx, 'interStringSalience',
|
|
targetCandidates)
|
|
assert objectFromInitial.spansString() == objectFromTarget.spansString()
|
|
# get the posible concept mappings
|
|
conceptMappings = formulas.getMappings(
|
|
objectFromInitial, objectFromTarget,
|
|
objectFromInitial.relevantDescriptions(),
|
|
objectFromTarget.relevantDescriptions())
|
|
assert conceptMappings and __slippability(ctx, 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
|
|
opposites = [m for m in distinguishingMappings
|
|
if m.initialDescriptionType == slipnet.stringPositionCategory
|
|
and m.initialDescriptionType != slipnet.bond_edge_type]
|
|
initialDescriptionTypes = [m.initialDescriptionType for m in opposites]
|
|
flipTargetObject = False
|
|
if (objectFromInitial.spansString()
|
|
and objectFromTarget.spansString()
|
|
and slipnet.directionCategory in initialDescriptionTypes
|
|
and all(m.label == slipnet.opposite for m in opposites) # unreached?
|
|
and slipnet.opposite.activation != 100.0):
|
|
objectFromTarget = objectFromTarget.flippedVersion()
|
|
conceptMappings = formulas.getMappings(
|
|
objectFromInitial, objectFromTarget,
|
|
objectFromInitial.relevantDescriptions(),
|
|
objectFromTarget.relevantDescriptions())
|
|
flipTargetObject = True
|
|
coderack.proposeCorrespondence(objectFromInitial, objectFromTarget,
|
|
conceptMappings, flipTargetObject)
|
|
|
|
|
|
@codelet('correspondence-strength-tester')
|
|
def correspondence_strength_tester(ctx, codelet):
|
|
"""
|
|
Tests the strength of a proposed correspondence to determine if it should be built.
|
|
|
|
This codelet evaluates whether a correspondence generated by correspondence scouts
|
|
is strong enough to warrant creation in the workspace. Only correspondences that
|
|
pass this probabilistic strength test proceed to the correspondence-builder.
|
|
|
|
Behavior:
|
|
1. Gets the correspondence to test from codelet arguments
|
|
2. Validates that source and target objects still exist appropriately
|
|
3. Updates correspondence strength based on current workspace state
|
|
4. Converts strength to acceptance probability via temperature adjustment
|
|
5. If passes probability test, activates concept mappings and queues correspondence-builder
|
|
6. If fails, the correspondence is discarded (fizzles)
|
|
|
|
Args:
|
|
ctx: Context containing coderack, random, temperature, workspace components
|
|
codelet: Codelet with argument containing the correspondence to test
|
|
|
|
Notes:
|
|
Correspondence strength depends on mapping quality and workspace constraints
|
|
Higher temperature = more lenient correspondence acceptance (exploration)
|
|
Lower temperature = stricter correspondence acceptance (exploitation)
|
|
Activating concept mappings promotes relevant relationships for future formation
|
|
Failed correspondences are discarded, ensuring only coherent mappings are adopted
|
|
"""
|
|
coderack = ctx.coderack
|
|
random = ctx.random
|
|
# TODO: use entropy
|
|
temperature = ctx.temperature
|
|
workspace = ctx.workspace
|
|
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
|
|
# TODO: use entropy
|
|
probability = temperature.getAdjustedProbability(strength / 100.0)
|
|
if random.coinFlip(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',
|
|
strength, [correspondence])
|
|
|
|
|
|
@codelet('correspondence-builder')
|
|
def correspondence_builder(ctx, codelet):
|
|
"""
|
|
Builds a correspondence that has passed the strength test.
|
|
|
|
This codelet actually constructs correspondences in the workspace after they have
|
|
passed strength testing. It handles complex conflict resolution with existing
|
|
structures and ensures correspondence creation follows workspace constraints.
|
|
|
|
Behavior:
|
|
1. Gets correspondence to build from codelet arguments
|
|
2. Handles reflexive correspondences by adding mappings to existing ones
|
|
3. Validates object existence and flipping relationships
|
|
4. Fights against all incompatible correspondences using span-based strength
|
|
5. Checks for incompatible bonds and groups at string endpoints
|
|
6. Fights incompatible bonds with weighted strength advantages
|
|
7. Fights incompatible groups that would conflict
|
|
8. Checks for incompatible rules and fights with competitive criteria
|
|
9. Breaks all defeated incompatible structures
|
|
10. Finally constructs the correspondence in the workspace
|
|
|
|
Args:
|
|
ctx: Context containing workspace
|
|
codelet: Codelet with argument containing the correspondence to build
|
|
|
|
Notes:
|
|
Correspondence building involves complex conflict resolution with existing structures
|
|
Incompatible structures are removed only if the new correspondence wins competitions
|
|
Special handling for end-of-string correspondences affects bond and group conflicts
|
|
Span-based strength balancing ensures fair competition between correspondences
|
|
Successful correspondence creation establishes key structural relationships
|
|
"""
|
|
workspace = ctx.workspace
|
|
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
|
|
incompatibles = correspondence.getIncompatibleCorrespondences()
|
|
# fight against all correspondences
|
|
if incompatibles:
|
|
correspondenceSpans = (correspondence.objectFromInitial.letterSpan() +
|
|
correspondence.objectFromTarget.letterSpan())
|
|
for incompatible in incompatibles:
|
|
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
|
|
initial = correspondence.objectFromInitial
|
|
target = correspondence.objectFromTarget
|
|
if (initial.leftmost or initial.rightmost and
|
|
target.leftmost or target.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 = target.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:
|
|
if workspace.rule.incompatibleRuleCorrespondence(correspondence):
|
|
incompatibleRule = workspace.rule
|
|
assert __structureVsStructure(correspondence, 1.0,
|
|
incompatibleRule, 1.0)
|
|
for incompatible in incompatibles:
|
|
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()
|