Merge master branch into main
Consolidating all project history from master into main branch. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
197
.gitignore
vendored
197
.gitignore
vendored
@ -1,176 +1,43 @@
|
||||
# ---> Python
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.py[co]
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
# Packages
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
*.egg-info
|
||||
dist
|
||||
build
|
||||
eggs
|
||||
parts
|
||||
bin
|
||||
var
|
||||
sdist
|
||||
develop-eggs
|
||||
.installed.cfg
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
.tox
|
||||
.log
|
||||
copycat.log
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
# Other filesystems
|
||||
.svn
|
||||
.DS_Store
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# UV
|
||||
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
#uv.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
||||
.pdm.toml
|
||||
.pdm-python
|
||||
.pdm-build/
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
# Ruff stuff:
|
||||
.ruff_cache/
|
||||
|
||||
# PyPI configuration file
|
||||
.pypirc
|
||||
# Editors
|
||||
.*.swp
|
||||
|
||||
# Output
|
||||
output/*
|
||||
<<<<<<< HEAD
|
||||
copycat.log
|
||||
papers/*.log
|
||||
papers/*.pdf
|
||||
papers/*.out
|
||||
papers/*.aux
|
||||
papers/words
|
||||
*.txt
|
||||
=======
|
||||
>>>>>>> develop
|
||||
|
||||
6
.ipynb_checkpoints/Copycat-checkpoint.ipynb
Normal file
6
.ipynb_checkpoints/Copycat-checkpoint.ipynb
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"cells": [],
|
||||
"metadata": {},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
10
.landscape.yaml
Normal file
10
.landscape.yaml
Normal file
@ -0,0 +1,10 @@
|
||||
pylint:
|
||||
options:
|
||||
dummy-variables-rgx: _
|
||||
max-branchs: 30
|
||||
|
||||
pyflakes:
|
||||
run: false
|
||||
|
||||
mccabe:
|
||||
run: false
|
||||
BIN
.old_distributions
Normal file
BIN
.old_distributions
Normal file
Binary file not shown.
12
.travis.yml
Normal file
12
.travis.yml
Normal file
@ -0,0 +1,12 @@
|
||||
language: python
|
||||
branches:
|
||||
only:
|
||||
- "develop"
|
||||
- "master"
|
||||
python:
|
||||
- "3.6"
|
||||
install:
|
||||
- pip3 install -r requirements.txt
|
||||
script:
|
||||
- python3 tests.py
|
||||
|
||||
81
Copycat.ipynb
Normal file
81
Copycat.ipynb
Normal file
@ -0,0 +1,81 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Copycat \n",
|
||||
"\n",
|
||||
"Just type your copycat example, and the number of iterations."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Answered iijjkl (time 1374, final temperature 13.5)\n",
|
||||
"Answered iijjll (time 665, final temperature 19.6)\n",
|
||||
"Answered iijjll (time 406, final temperature 16.6)\n",
|
||||
"Answered iijjkl (time 379, final temperature 47.9)\n",
|
||||
"Answered iijjll (time 556, final temperature 19.2)\n",
|
||||
"Answered iijjkl (time 813, final temperature 42.8)\n",
|
||||
"Answered iijjll (time 934, final temperature 15.5)\n",
|
||||
"Answered iijjkl (time 1050, final temperature 49.5)\n",
|
||||
"Answered iijjkl (time 700, final temperature 44.0)\n",
|
||||
"Answered iijjkl (time 510, final temperature 34.8)\n",
|
||||
"Answered iijjkl (time 673, final temperature 18.1)\n",
|
||||
"Answered iijjkl (time 1128, final temperature 19.8)\n",
|
||||
"Answered iijjll (time 961, final temperature 19.9)\n",
|
||||
"Answered iijjll (time 780, final temperature 16.5)\n",
|
||||
"Answered iijjll (time 607, final temperature 17.8)\n",
|
||||
"Answered iijjll (time 594, final temperature 39.7)\n",
|
||||
"Answered iijjll (time 736, final temperature 18.4)\n",
|
||||
"Answered iijjll (time 903, final temperature 18.6)\n",
|
||||
"Answered iijjll (time 601, final temperature 20.6)\n",
|
||||
"Answered iijjll (time 949, final temperature 42.4)\n",
|
||||
"iijjll: 12 (avg time 724.3, avg temp 22.1)\n",
|
||||
"iijjkl: 8 (avg time 828.4, avg temp 33.8)\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"%run main.py abc abd iijjkk --iterations 20"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"collapsed": true
|
||||
},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.6.1"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright © 2013 J Alan Brogan <licensing@al-got-rhythm.net>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the “Software”), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
105
README.md
105
README.md
@ -1,3 +1,104 @@
|
||||
# copycat
|
||||
Copycat
|
||||
=========
|
||||
|
||||
Open Source copycat (python)
|
||||

|
||||
|
||||
An implementation of [Douglas Hofstadter](http://prelectur.stanford.edu/lecturers/hofstadter/) and [Melanie Mitchell](https://melaniemitchell.me/)'s Copycat algorithm.
|
||||
The Copycat algorithm is explained [on Wikipedia](https://en.wikipedia.org/wiki/Copycat_%28software%29), in Melanie Mitchell's Book [Analogy-making as perception](https://www.amazon.com/Analogy-Making-Perception-Computer-Modeling-Connectionism/dp/026251544X/ref=sr_1_5?crid=1FC76DCS33513&dib=eyJ2IjoiMSJ9.TQVbRbFf696j7ZYj_sb4tIM3ZbFbuCIdtdYCy-Mq3EmJI6xbG5hhVXuyOPjeb7E4b8jhKiJlfr6NnD_O09rEEkNMwD_1zFxkLT9OkF81RSFL4kMCLOT7K-7KnPwBFbrc9tZuhLKFOWbxMGNL75koMcetQl2Lf6V7xsNYLYLCHBlXMCrusJ88Kv3Y8jiPKwrEr1hUwhWB8vtwEG9vSYXU7Gw-b4fZRNNbUtBBWNwiK3k.IJZZ8kA_QirWQK1ax5i42zD2nV7XvKoPYRgN94en4Dc&dib_tag=se&keywords=melanie+mitchell&qid=1745436638&sprefix=melanie+mitchell%2Caps%2C206&sr=8-5#), and in [this paper](https://github.com/Alex-Linhares/FARGonautica/blob/master/Literature/Foundations-Chalmers.French.and.Hofstadter-1992-Journal%20of%20Experimental%20and%20Theoretical%20Artificial%20Intelligence.pdf). The wikipedia page has additional links for deeper reading. See also [FARGonautica](https://github.com/Alex-Linhares/Fargonautica), where a collection of Fluid Concepts projects are available.
|
||||
|
||||
This implementation is a copycat of Scott Boland's [Java implementation](https://archive.org/details/JavaCopycat).
|
||||
The original Java-to-Python translation work was done by J Alan Brogan (@jalanb on GitHub).
|
||||
The Java version has a GUI similar to the original Lisp; this Python version has no GUI code built in but can be incorporated into a larger GUI program.
|
||||
|
||||
J. Alan Brogan writes:
|
||||
> In cases where I could not grok the Java implementation easily, 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 book
|
||||
> "[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)".
|
||||
|
||||
Running the command-line program
|
||||
--------------------------------
|
||||
|
||||
To clone the repo locally, run these commands:
|
||||
|
||||
```
|
||||
$ git clone https://github.com/fargonauts/copycat.git
|
||||
$ cd copycat/copycat
|
||||
$ python main.py abc abd ppqqrr --iterations 10
|
||||
```
|
||||
|
||||
The script takes three or four arguments.
|
||||
The first two are a pair of strings with some change, for example "abc" and "abd".
|
||||
The third is a string which the script should try to change analogously.
|
||||
The fourth (which defaults to "1") is a number of iterations.
|
||||
|
||||
This might produce output such as
|
||||
|
||||
```
|
||||
ppqqss: 6 (avg time 869.0, avg temp 23.4)
|
||||
ppqqrs: 4 (avg time 439.0, avg temp 37.3)
|
||||
```
|
||||
|
||||
The first number indicates how many times Copycat chose that string as its answer; higher means "more obvious".
|
||||
The last number indicates the average final temperature of the workspace; lower means "more elegant".
|
||||
|
||||
Code structure
|
||||
---------------------
|
||||
|
||||
This Copycat system consists of 4,981 lines of Python code across 40 files. Here's a breakdown.
|
||||
|
||||
Core Components:
|
||||
- codeletMethods.py: 1,124 lines (largest file)
|
||||
- curses_reporter.py: 436 lines
|
||||
- coderack.py: 310 lines
|
||||
- slipnet.py: 248 lines
|
||||
|
||||
Workspace Components:
|
||||
|
||||
- group.py: 237 lines
|
||||
- bond.py: 211 lines
|
||||
- correspondence.py: 204 lines
|
||||
- workspace.py: 195 lines
|
||||
- workspaceObject.py: 194 lines
|
||||
|
||||
Control Components:
|
||||
- temperature.py: 175 lines
|
||||
- conceptMapping.py: 153 lines
|
||||
- rule.py: 149 lines
|
||||
- copycat.py: 144 lines
|
||||
|
||||
GUI Components:
|
||||
- gui/gui.py: 96 lines
|
||||
- gui/workspacecanvas.py: 70 lines
|
||||
- gui/status.py: 66 lines
|
||||
- gui/control.py: 59 lines
|
||||
|
||||
|
||||
The system is well-organized with clear separation of concerns:
|
||||
- Core logic (codelets, coderack, slipnet)
|
||||
- Workspace management (groups, bonds, correspondences)
|
||||
- Control systems (temperature, rules)
|
||||
- User interface (GUI components)
|
||||
|
||||
The largest file, codeletMethods.py, contains all the codelet behavior implementations, which makes sense as it's the heart of the system's analogical reasoning capabilities.
|
||||
|
||||
{code.py}README.md Files
|
||||
---------------------
|
||||
|
||||
We've got an LLM to document every code file, so people can look at a particular readme before delving into the work (Here's one [Example](main_README.md)).
|
||||
|
||||
|
||||
Installing the module
|
||||
---------------------
|
||||
|
||||
To install the Python module and get started with it, run these commands:
|
||||
|
||||
```
|
||||
$ pip install -e git+git://github.com/fargonauts/copycat.git#egg=copycat
|
||||
$ python
|
||||
>>> from copycat import Copycat
|
||||
>>> Copycat().run('abc', 'abd', 'ppqqrr', 10)
|
||||
{'ppqqrs': {'count': 4, 'avgtime': 439, 'avgtemp': 37.3}, 'ppqqss': {'count': 6, 'avgtime': 869, 'avgtemp': 23.4}}
|
||||
```
|
||||
|
||||
The result of `run` is a dict containing the same information as was printed by `main.py` above.
|
||||
|
||||
4
copycat/__init__.py
Normal file
4
copycat/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
from .copycat import Copycat, Reporter # noqa
|
||||
from .problem import Problem
|
||||
from .plot import plot_answers
|
||||
from .io import save_answers
|
||||
211
copycat/bond.py
Normal file
211
copycat/bond.py
Normal file
@ -0,0 +1,211 @@
|
||||
from .workspaceStructure import WorkspaceStructure
|
||||
|
||||
|
||||
class Bond(WorkspaceStructure):
|
||||
# pylint: disable=too-many-arguments
|
||||
def __init__(self, ctx, source, destination, bondCategory, bondFacet,
|
||||
sourceDescriptor, destinationDescriptor):
|
||||
WorkspaceStructure.__init__(self, ctx)
|
||||
slipnet = self.ctx.slipnet
|
||||
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.leftIndex > self.destination.rightIndex:
|
||||
self.leftObject = self.destination
|
||||
self.rightObject = self.source
|
||||
self.directionCategory = slipnet.left
|
||||
self.facet = bondFacet
|
||||
self.sourceDescriptor = sourceDescriptor
|
||||
self.destinationDescriptor = destinationDescriptor
|
||||
self.category = bondCategory
|
||||
if (self.sourceDescriptor == self.destinationDescriptor):
|
||||
self.directionCategory = None
|
||||
|
||||
def flippedVersion(self):
|
||||
slipnet = self.ctx.slipnet
|
||||
return Bond(
|
||||
self.ctx,
|
||||
self.destination, self.source,
|
||||
self.category.getRelatedNode(slipnet.opposite),
|
||||
self.facet, self.destinationDescriptor, self.sourceDescriptor
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return '<Bond: %s>' % self.__str__()
|
||||
|
||||
def __str__(self):
|
||||
return '%s bond between %s and %s' % (
|
||||
self.category.name, self.leftObject, self.rightObject,
|
||||
)
|
||||
|
||||
def buildBond(self):
|
||||
workspace = self.ctx.workspace
|
||||
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):
|
||||
workspace = self.ctx.workspace
|
||||
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
|
||||
workspace = self.ctx.workspace
|
||||
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):
|
||||
slipnet = self.ctx.slipnet
|
||||
# bonds between objects of same type(ie. letter or group) are
|
||||
# stronger than bonds between different types
|
||||
sourceGap = self.source.leftIndex != self.source.rightIndex
|
||||
destinationGap = (self.destination.leftIndex !=
|
||||
self.destination.rightIndex)
|
||||
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 sum(
|
||||
1 for b in self.string.bonds if
|
||||
b.string == self.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.source == object1 and self.destination == object2:
|
||||
return True
|
||||
return self.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
|
||||
workspace = self.ctx.workspace
|
||||
slotSum = 0.0
|
||||
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
|
||||
try:
|
||||
return 100.0 * supportSum / slotSum
|
||||
except ZeroDivisionError:
|
||||
return 0.0
|
||||
|
||||
def sameNeighbors(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.sameNeighbors(b)]
|
||||
|
||||
def set_source(self, value):
|
||||
self.source = value
|
||||
|
||||
def possibleGroupBonds(self, bonds):
|
||||
result = []
|
||||
slipnet = self.ctx.slipnet
|
||||
for bond in bonds:
|
||||
if (
|
||||
bond.category == self.category and
|
||||
bond.directionCategory == self.directionCategory
|
||||
):
|
||||
result += [bond]
|
||||
else:
|
||||
# a modified bond might be made
|
||||
if bond.category == self.category:
|
||||
return [] # a different bond cannot be made here
|
||||
if bond.directionCategory == self.directionCategory:
|
||||
return [] # a different bond cannot be made here
|
||||
if slipnet.sameness in [self.category, bond.category]:
|
||||
return []
|
||||
bond = Bond(
|
||||
bond.ctx, bond.destination, bond.source, self.category,
|
||||
self.facet, bond.destinationDescriptor,
|
||||
bond.sourceDescriptor
|
||||
)
|
||||
result += [bond]
|
||||
return result
|
||||
54
copycat/bond_README.md
Normal file
54
copycat/bond_README.md
Normal file
@ -0,0 +1,54 @@
|
||||
# README_bond.md
|
||||
|
||||
## Overview
|
||||
`bond.py` implements the Bond system, a key component of the Copycat system that manages the relationships between objects in strings. It handles the creation, evaluation, and management of bonds that represent meaningful connections between objects based on their properties and relationships.
|
||||
|
||||
## Core Components
|
||||
- `Bond` class: Main class that represents a bond between objects
|
||||
- Bond evaluation system
|
||||
- Bond compatibility management
|
||||
|
||||
## Key Features
|
||||
- Manages bonds between objects in strings
|
||||
- Evaluates bond strength based on multiple factors
|
||||
- Handles bond direction and category
|
||||
- Supports bond flipping and versioning
|
||||
- Manages bond compatibility and support
|
||||
|
||||
## Bond Components
|
||||
- `source`: Source object of the bond
|
||||
- `destination`: Destination object of the bond
|
||||
- `category`: Category of the bond
|
||||
- `facet`: Aspect of the bond
|
||||
- `directionCategory`: Direction of the bond
|
||||
- `sourceDescriptor`: Descriptor of the source object
|
||||
- `destinationDescriptor`: Descriptor of the destination object
|
||||
|
||||
## Main Methods
|
||||
- `updateInternalStrength()`: Calculate internal bond strength
|
||||
- `updateExternalStrength()`: Calculate external bond strength
|
||||
- `buildBond()`: Create and establish bond
|
||||
- `breakBond()`: Remove bond
|
||||
- `localSupport()`: Calculate local support
|
||||
- `numberOfLocalSupportingBonds()`: Count supporting bonds
|
||||
- `sameCategories()`: Compare bond categories
|
||||
- `localDensity()`: Calculate local bond density
|
||||
|
||||
## Bond Types
|
||||
- Letter category bonds
|
||||
- Direction-based bonds
|
||||
- Category-based bonds
|
||||
- Flipped bonds
|
||||
- Modified bonds
|
||||
|
||||
## Dependencies
|
||||
- Requires `workspaceStructure` module
|
||||
- Used by the main `copycat` module
|
||||
|
||||
## Notes
|
||||
- Bonds are evaluated based on member compatibility and facet factors
|
||||
- The system supports both same-type and different-type bonds
|
||||
- Bonds can have direction categories (left, right)
|
||||
- The system handles bond compatibility and support
|
||||
- Bonds can be flipped to create alternative versions
|
||||
- Local density and support factors influence bond strength
|
||||
9
copycat/codelet.py
Normal file
9
copycat/codelet.py
Normal file
@ -0,0 +1,9 @@
|
||||
class Codelet(object):
|
||||
def __init__(self, name, urgency, arguments, currentTime):
|
||||
self.name = name
|
||||
self.urgency = urgency
|
||||
self.arguments = arguments
|
||||
self.birthdate = currentTime
|
||||
|
||||
def __repr__(self):
|
||||
return '<Codelet: %s>' % self.name
|
||||
2012
copycat/codeletMethods.py
Normal file
2012
copycat/codeletMethods.py
Normal file
File diff suppressed because it is too large
Load Diff
51
copycat/codeletMethods_README.md
Normal file
51
copycat/codeletMethods_README.md
Normal file
@ -0,0 +1,51 @@
|
||||
# Codelet Methods
|
||||
|
||||
## Overview
|
||||
The codelet methods system is a core component of the Copycat architecture that implements the various operations and behaviors that codelets can perform. This system defines the actual implementation of codelet behaviors that drive the analogical reasoning process.
|
||||
|
||||
## Key Components
|
||||
- Codelet operation implementations
|
||||
- Behavior definitions
|
||||
- Action handlers
|
||||
- State management
|
||||
- Event processing
|
||||
|
||||
## Codelet Types
|
||||
1. **Workspace Codelets**
|
||||
- Object creation and modification
|
||||
- Structure building
|
||||
- Relationship formation
|
||||
- Group management
|
||||
|
||||
2. **Slipnet Codelets**
|
||||
- Concept activation
|
||||
- Node management
|
||||
- Link formation
|
||||
- Activation spreading
|
||||
|
||||
3. **Correspondence Codelets**
|
||||
- Mapping creation
|
||||
- Relationship matching
|
||||
- Structure alignment
|
||||
- Similarity assessment
|
||||
|
||||
## Usage
|
||||
Codelet methods are called by the coderack system when codelets are executed:
|
||||
|
||||
```python
|
||||
# Example of a codelet method implementation
|
||||
def some_codelet_method(workspace, slipnet, coderack):
|
||||
# Perform operations
|
||||
# Update state
|
||||
# Create new codelets
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
- Python 3.x
|
||||
- No external dependencies required
|
||||
|
||||
## Related Components
|
||||
- Coderack: Manages codelet execution
|
||||
- Workspace: Provides objects to operate on
|
||||
- Slipnet: Provides conceptual knowledge
|
||||
- Correspondence: Manages mappings between structures
|
||||
98
copycat/codelet_README.md
Normal file
98
copycat/codelet_README.md
Normal file
@ -0,0 +1,98 @@
|
||||
# Codelet System
|
||||
|
||||
## Overview
|
||||
The codelet system is a fundamental component of the Copycat architecture that defines the basic structure and behavior of codelets. Codelets are small, specialized agents that perform specific operations in the workspace, forming the basis of the system's parallel processing capabilities.
|
||||
|
||||
## Key Features
|
||||
- Codelet structure
|
||||
- Behavior definition
|
||||
- Priority management
|
||||
- State tracking
|
||||
- Execution control
|
||||
|
||||
## Codelet Types
|
||||
1. **Basic Codelets**
|
||||
- Scout codelets
|
||||
- Builder codelets
|
||||
- Evaluator codelets
|
||||
- Breaker codelets
|
||||
|
||||
2. **Specialized Codelets**
|
||||
- Group codelets
|
||||
- Bond codelets
|
||||
- Correspondence codelets
|
||||
- Rule codelets
|
||||
|
||||
3. **Control Codelets**
|
||||
- Temperature codelets
|
||||
- Pressure codelets
|
||||
- Urgency codelets
|
||||
- Cleanup codelets
|
||||
|
||||
## Usage
|
||||
Codelets are created and managed through the codelet system.
|
||||
|
||||
The behavior parameter in codelet.set_behavior would typically be a reference to one of the **many codelet methods defined in codeletMethods.py**.
|
||||
For example, a codelet might be assigned a behavior like:
|
||||
- build_group - to create a new group of related elements
|
||||
- evaluate_bond - to assess the strength of a bond
|
||||
- scout_for_correspondence - to look for potential mappings between structures
|
||||
The behavior is what gives each codelet its purpose and role in the system's parallel processing architecture. When the codelet is executed via codelet.run(), it performs this assigned behavior in the context of the current workspace state.
|
||||
|
||||
```python
|
||||
# Create a codelet
|
||||
codelet = Codelet(name, priority)
|
||||
|
||||
# Set behavior
|
||||
codelet.set_behavior(behavior)
|
||||
|
||||
# Execute codelet
|
||||
result = codelet.run()
|
||||
```
|
||||
|
||||
## Codelet Decorator
|
||||
The `@codelet` decorator is defined in `codeletMethods.py` and is used to mark functions as codelet behaviors. Here's its implementation:
|
||||
|
||||
```python
|
||||
def codelet(name):
|
||||
"""Decorator for otherwise-unused functions that are in fact used as codelet behaviors"""
|
||||
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 type name for reference
|
||||
f.codelet_name = name
|
||||
return f
|
||||
return wrap
|
||||
```
|
||||
|
||||
The decorator:
|
||||
1. Takes a `name` parameter that identifies the type of codelet
|
||||
2. Wraps the decorated function with additional metadata:
|
||||
- Marks the function as a codelet method with `is_codelet_method = True`
|
||||
- Stores the codelet name with `codelet_name = name`
|
||||
3. Verifies that the decorated function has the correct signature (must take `ctx` and `codelet` parameters)
|
||||
|
||||
Example usage:
|
||||
```python
|
||||
@codelet('breaker')
|
||||
def breaker(ctx, codelet):
|
||||
# Codelet behavior implementation
|
||||
pass
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
- Python 3.x
|
||||
- No external dependencies required
|
||||
|
||||
## Related Components
|
||||
- Coderack: Manages codelet execution
|
||||
- CodeletMethods: Provides codelet behaviors
|
||||
- Workspace: Environment for codelets
|
||||
- Temperature: Influences codelet behavior
|
||||
310
copycat/coderack.py
Normal file
310
copycat/coderack.py
Normal file
@ -0,0 +1,310 @@
|
||||
import math
|
||||
import logging
|
||||
|
||||
from . import codeletMethods
|
||||
from .bond import Bond
|
||||
from .codelet import Codelet
|
||||
from .correspondence import Correspondence
|
||||
from .description import Description
|
||||
from .group import Group
|
||||
from .rule import Rule
|
||||
|
||||
|
||||
NUMBER_OF_BINS = 7
|
||||
|
||||
|
||||
def getUrgencyBin(urgency):
|
||||
i = int(urgency) * NUMBER_OF_BINS / 100
|
||||
if i >= NUMBER_OF_BINS:
|
||||
return NUMBER_OF_BINS
|
||||
return i + 1
|
||||
|
||||
|
||||
class Coderack(object):
|
||||
def __init__(self, ctx):
|
||||
self.ctx = ctx
|
||||
self.reset()
|
||||
self.methods = {}
|
||||
|
||||
for name in dir(codeletMethods):
|
||||
method = getattr(codeletMethods, name)
|
||||
if getattr(method, 'is_codelet_method', False):
|
||||
self.methods[method.codelet_name] = method
|
||||
|
||||
assert set(self.methods.keys()) == set([
|
||||
'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',
|
||||
])
|
||||
|
||||
def reset(self):
|
||||
self.codelets = []
|
||||
self.codeletsRun = 0
|
||||
|
||||
def updateCodelets(self):
|
||||
if self.codeletsRun > 0:
|
||||
self.postTopDownCodelets()
|
||||
self.postBottomUpCodelets()
|
||||
|
||||
def probabilityOfPosting(self, codeletName):
|
||||
# TODO: use entropy
|
||||
temperature = self.ctx.temperature
|
||||
workspace = self.ctx.workspace
|
||||
if codeletName == 'breaker':
|
||||
return 1.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 'correspondence' in codeletName:
|
||||
return workspace.interStringUnhappiness / 100.0
|
||||
if 'description' in codeletName:
|
||||
# TODO: use entropy
|
||||
return (temperature.value() / 100.0) ** 2
|
||||
return workspace.intraStringUnhappiness / 100.0
|
||||
|
||||
def howManyToPost(self, codeletName):
|
||||
random = self.ctx.random
|
||||
workspace = self.ctx.workspace
|
||||
if codeletName == 'breaker' or '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()
|
||||
if 'group' in codeletName:
|
||||
number = workspace.numberOfUngroupedObjects()
|
||||
if 'replacement' in codeletName:
|
||||
number = workspace.numberOfUnreplacedObjects()
|
||||
if 'correspondence' in codeletName:
|
||||
number = workspace.numberOfUncorrespondingObjects()
|
||||
if number < random.sqrtBlur(2.0):
|
||||
return 1
|
||||
if number < random.sqrtBlur(4.0):
|
||||
return 2
|
||||
return 3
|
||||
|
||||
def post(self, codelet):
|
||||
self.codelets += [codelet]
|
||||
if len(self.codelets) > 100:
|
||||
oldCodelet = self.chooseOldCodelet()
|
||||
self.removeCodelet(oldCodelet)
|
||||
|
||||
def postTopDownCodelets(self):
|
||||
random = self.ctx.random
|
||||
slipnet = self.ctx.slipnet
|
||||
for node in slipnet.slipnodes:
|
||||
if node.activation != 100.0:
|
||||
continue
|
||||
for codeletName in node.codelets:
|
||||
probability = self.probabilityOfPosting(codeletName)
|
||||
howMany = self.howManyToPost(codeletName)
|
||||
for _ in range(howMany):
|
||||
if not random.coinFlip(probability):
|
||||
continue
|
||||
urgency = getUrgencyBin(
|
||||
node.activation * node.conceptualDepth / 100.0)
|
||||
codelet = Codelet(codeletName, urgency, [node], self.codeletsRun)
|
||||
logging.info('Post top down: %s, with urgency: %d',
|
||||
codelet.name, urgency)
|
||||
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')
|
||||
self.__postBottomUpCodelets('breaker')
|
||||
|
||||
def __postBottomUpCodelets(self, codeletName):
|
||||
random = self.ctx.random
|
||||
# TODO: use entropy
|
||||
temperature = self.ctx.temperature
|
||||
probability = self.probabilityOfPosting(codeletName)
|
||||
howMany = self.howManyToPost(codeletName)
|
||||
urgency = 3
|
||||
if codeletName == 'breaker':
|
||||
urgency = 1
|
||||
|
||||
# TODO: use entropy
|
||||
if temperature.value() < 25.0 and 'translator' in codeletName:
|
||||
urgency = 5
|
||||
for _ in range(howMany):
|
||||
if random.coinFlip(probability):
|
||||
codelet = Codelet(codeletName, urgency, [], self.codeletsRun)
|
||||
self.post(codelet)
|
||||
|
||||
def removeCodelet(self, codelet):
|
||||
self.codelets.remove(codelet)
|
||||
|
||||
def newCodelet(self, name, strength, arguments):
|
||||
urgency = getUrgencyBin(strength)
|
||||
newCodelet = Codelet(name, urgency, arguments, self.codeletsRun)
|
||||
self.post(newCodelet)
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
def proposeRule(self, facet, description, category, relation):
|
||||
"""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
|
||||
"""
|
||||
rule = Rule(self.ctx, facet, description, category, relation)
|
||||
rule.updateStrength()
|
||||
if description and relation:
|
||||
averageDepth = (description.conceptualDepth + relation.conceptualDepth) / 2.0
|
||||
urgency = 100.0 * math.sqrt(averageDepth / 100.0)
|
||||
else:
|
||||
urgency = 0
|
||||
self.newCodelet('rule-strength-tester', urgency, [rule])
|
||||
|
||||
def proposeCorrespondence(self, initialObject, targetObject,
|
||||
conceptMappings, flipTargetObject):
|
||||
correspondence = Correspondence(self.ctx, 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
|
||||
binn = getUrgencyBin(urgency)
|
||||
logging.info('urgency: %s, number: %d, bin: %d',
|
||||
urgency, numberOfMappings, binn)
|
||||
self.newCodelet('correspondence-strength-tester',
|
||||
urgency, [correspondence])
|
||||
|
||||
def proposeDescription(self, objekt, type_, descriptor):
|
||||
description = Description(objekt, type_, descriptor)
|
||||
descriptor.buffer = 100.0
|
||||
urgency = type_.activation
|
||||
self.newCodelet('description-strength-tester',
|
||||
urgency, [description])
|
||||
|
||||
def proposeSingleLetterGroup(self, source):
|
||||
slipnet = self.ctx.slipnet
|
||||
self.proposeGroup([source], [], slipnet.samenessGroup, None,
|
||||
slipnet.letterCategory)
|
||||
|
||||
def proposeGroup(self, objects, bondList, groupCategory, directionCategory,
|
||||
bondFacet):
|
||||
slipnet = self.ctx.slipnet
|
||||
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', urgency, [group])
|
||||
|
||||
def proposeBond(self, source, destination, bondCategory, bondFacet,
|
||||
sourceDescriptor, destinationDescriptor):
|
||||
bondFacet.buffer = 100.0
|
||||
sourceDescriptor.buffer = 100.0
|
||||
destinationDescriptor.buffer = 100.0
|
||||
bond = Bond(self.ctx, source, destination, bondCategory, bondFacet,
|
||||
sourceDescriptor, destinationDescriptor)
|
||||
urgency = bondCategory.bondDegreeOfAssociation()
|
||||
self.newCodelet('bond-strength-tester', urgency, [bond])
|
||||
|
||||
def chooseOldCodelet(self):
|
||||
# selects an old codelet to remove from the coderack
|
||||
# more likely to select lower urgency codelets
|
||||
urgencies = []
|
||||
for codelet in self.codelets:
|
||||
urgency = ((self.codeletsRun - codelet.birthdate) *
|
||||
(7.5 - codelet.urgency))
|
||||
urgencies += [urgency]
|
||||
random = self.ctx.random
|
||||
return random.weighted_choice(self.codelets, urgencies)
|
||||
|
||||
def postInitialCodelets(self):
|
||||
workspace = self.ctx.workspace
|
||||
n = len(workspace.objects)
|
||||
if n == 0:
|
||||
# The most pathological case.
|
||||
codeletsToPost = [
|
||||
('rule-scout', 1),
|
||||
]
|
||||
else:
|
||||
codeletsToPost = [
|
||||
('bottom-up-bond-scout', 2 * n),
|
||||
('replacement-finder', 2 * n),
|
||||
('bottom-up-correspondence-scout', 2 * n),
|
||||
]
|
||||
for name, count in codeletsToPost:
|
||||
for _ in range(count):
|
||||
codelet = Codelet(name, 1, [], self.codeletsRun)
|
||||
self.post(codelet)
|
||||
|
||||
def chooseAndRunCodelet(self):
|
||||
if not len(self.codelets):
|
||||
# Indeed, this happens fairly often.
|
||||
self.postInitialCodelets()
|
||||
codelet = self.chooseCodeletToRun()
|
||||
self.run(codelet)
|
||||
|
||||
def chooseCodeletToRun(self):
|
||||
random = self.ctx.random
|
||||
# TODO: use entropy
|
||||
temperature = self.ctx.temperature
|
||||
assert self.codelets
|
||||
|
||||
# TODO: use entropy
|
||||
scale = (100.0 - temperature.value() + 10.0) / 15.0
|
||||
chosen = random.weighted_choice(self.codelets, [codelet.urgency ** scale for codelet in self.codelets])
|
||||
self.removeCodelet(chosen)
|
||||
return chosen
|
||||
|
||||
def run(self, codelet):
|
||||
methodName = codelet.name
|
||||
self.codeletsRun += 1
|
||||
method = self.methods[methodName]
|
||||
try:
|
||||
method(self.ctx, codelet)
|
||||
except AssertionError:
|
||||
pass
|
||||
49
copycat/coderack_README.md
Normal file
49
copycat/coderack_README.md
Normal file
@ -0,0 +1,49 @@
|
||||
# README_coderack.md
|
||||
|
||||
## Overview
|
||||
`coderack.py` implements the Coderack, a key component of the Copycat system that manages the execution of codelets (small, focused procedures) that drive the analogical reasoning process. It handles the posting, selection, and execution of codelets based on their urgency and the current state of the system.
|
||||
|
||||
## Core Components
|
||||
- `Coderack` class: Main class that manages codelet execution
|
||||
- Codelet management system
|
||||
- Urgency-based codelet selection
|
||||
|
||||
## Key Features
|
||||
- Manages a collection of codelets (small procedures)
|
||||
- Implements urgency-based codelet selection
|
||||
- Supports both top-down and bottom-up codelet posting
|
||||
- Handles codelet execution and removal
|
||||
- Manages rule, correspondence, description, and group proposals
|
||||
|
||||
## Codelet Types
|
||||
- Breaker codelets
|
||||
- Description codelets (top-down and bottom-up)
|
||||
- Bond codelets (top-down and bottom-up)
|
||||
- Group codelets (top-down and whole-string)
|
||||
- Rule codelets (scout, strength-tester, builder, translator)
|
||||
- Correspondence codelets (bottom-up and important-object)
|
||||
- Replacement finder codelets
|
||||
|
||||
## Main Methods
|
||||
- `post(codelet)`: Add a codelet to the coderack
|
||||
- `chooseAndRunCodelet()`: Select and execute a codelet
|
||||
- `postTopDownCodelets()`: Post codelets from activated slipnet nodes
|
||||
- `postBottomUpCodelets()`: Post codelets based on workspace state
|
||||
- `proposeRule()`: Create and post a rule proposal
|
||||
- `proposeCorrespondence()`: Create and post a correspondence proposal
|
||||
- `proposeDescription()`: Create and post a description proposal
|
||||
- `proposeGroup()`: Create and post a group proposal
|
||||
- `proposeBond()`: Create and post a bond proposal
|
||||
|
||||
## Dependencies
|
||||
- Requires `codeletMethods` module
|
||||
- Uses `bond`, `codelet`, `correspondence`, `description`, `group`, and `rule` modules
|
||||
- Uses `logging` for debug output
|
||||
- Uses `math` for urgency calculations
|
||||
|
||||
## Notes
|
||||
- Codelets are organized by urgency bins
|
||||
- The system maintains a maximum of 100 codelets
|
||||
- Codelet selection is influenced by temperature and workspace state
|
||||
- The system supports both deterministic and probabilistic codelet posting
|
||||
- Codelet urgency is calculated based on various factors including conceptual depth
|
||||
153
copycat/conceptMapping.py
Normal file
153
copycat/conceptMapping.py
Normal file
@ -0,0 +1,153 @@
|
||||
class ConceptMapping(object):
|
||||
def __init__(self, initialDescriptionType, targetDescriptionType,
|
||||
initialDescriptor, targetDescriptor,
|
||||
initialObject, targetObject):
|
||||
self.slipnet = initialDescriptionType.slipnet
|
||||
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 '<ConceptMapping: %s from %s to %s>' % (
|
||||
self.__str__(), self.initialDescriptor, self.targetDescriptor)
|
||||
|
||||
def __str__(self):
|
||||
return self.label.name if self.label else 'anonymous'
|
||||
|
||||
def slippability(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 <= 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):
|
||||
slipnet = self.slipnet
|
||||
if self.initialDescriptor == slipnet.whole:
|
||||
if self.targetDescriptor == slipnet.whole:
|
||||
return False
|
||||
if not self.initialObject.distinguishingDescriptor(
|
||||
self.initialDescriptor):
|
||||
return False
|
||||
return self.targetObject.distinguishingDescriptor(
|
||||
self.targetDescriptor)
|
||||
|
||||
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):
|
||||
if self.sameInitialDescriptor(other):
|
||||
return self.sameTargetDescriptor(other)
|
||||
return False
|
||||
|
||||
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):
|
||||
return any(self.sameKind(mapping) for mapping in mappings)
|
||||
|
||||
def isNearlyContainedBy(self, mappings):
|
||||
return any(self.nearlySameKind(mapping) for mapping in mappings)
|
||||
|
||||
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
|
||||
return self.label != other.label
|
||||
|
||||
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.sameDescriptors(other):
|
||||
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
|
||||
return self.label == other.label
|
||||
|
||||
def relevant(self):
|
||||
if self.initialDescriptionType.fully_active():
|
||||
return self.targetDescriptionType.fully_active()
|
||||
return False
|
||||
|
||||
def slippage(self):
|
||||
slipnet = self.slipnet
|
||||
return self.label not in [slipnet.sameness, slipnet.identity]
|
||||
|
||||
def symmetricVersion(self):
|
||||
if not self.slippage():
|
||||
return self
|
||||
bond = self.targetDescriptor.getBondCategory(self.initialDescriptor)
|
||||
if bond == self.label:
|
||||
return self
|
||||
return ConceptMapping(
|
||||
self.targetDescriptionType,
|
||||
self.initialDescriptionType,
|
||||
self.targetDescriptor,
|
||||
self.initialDescriptor1,
|
||||
self.initialObject,
|
||||
self.targetObject
|
||||
)
|
||||
54
copycat/conceptMapping_README.md
Normal file
54
copycat/conceptMapping_README.md
Normal file
@ -0,0 +1,54 @@
|
||||
# Concept Mapping System
|
||||
|
||||
## Overview
|
||||
The concept mapping system is a crucial component of the Copycat architecture that manages the mapping between concepts and their representations in the workspace. This system handles the translation between abstract concepts and concrete instances.
|
||||
|
||||
## Key Features
|
||||
- Concept-to-instance mapping
|
||||
- Mapping validation
|
||||
- Relationship tracking
|
||||
- State management
|
||||
- Event handling
|
||||
|
||||
## Mapping Types
|
||||
1. **Direct Mappings**
|
||||
- Letter-to-concept mappings
|
||||
- Number-to-concept mappings
|
||||
- Symbol-to-concept mappings
|
||||
- Pattern-to-concept mappings
|
||||
|
||||
2. **Structural Mappings**
|
||||
- Group-to-concept mappings
|
||||
- Bond-to-concept mappings
|
||||
- Hierarchy-to-concept mappings
|
||||
- Pattern-to-concept mappings
|
||||
|
||||
3. **Special Mappings**
|
||||
- Rule-to-concept mappings
|
||||
- Context-to-concept mappings
|
||||
- Meta-concept mappings
|
||||
- Derived mappings
|
||||
|
||||
## Usage
|
||||
Concept mappings are created and managed through the mapping system:
|
||||
|
||||
```python
|
||||
# Create a new concept mapping
|
||||
mapping = ConceptMapping(source, target)
|
||||
|
||||
# Access mapping properties
|
||||
strength = mapping.get_strength()
|
||||
|
||||
# Modify mapping state
|
||||
mapping.set_strength(new_value)
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
- Python 3.x
|
||||
- No external dependencies required
|
||||
|
||||
## Related Components
|
||||
- Slipnet: Provides concepts to map
|
||||
- Workspace: Provides instances to map to
|
||||
- Codelets: Operate on mappings
|
||||
- Correspondence: Manages mapping relationships
|
||||
144
copycat/copycat.py
Normal file
144
copycat/copycat.py
Normal file
@ -0,0 +1,144 @@
|
||||
from .coderack import Coderack
|
||||
from .randomness import Randomness
|
||||
from .slipnet import Slipnet
|
||||
from .temperature import Temperature
|
||||
from .workspace import Workspace
|
||||
from .gui import GUI
|
||||
|
||||
from pprint import pprint
|
||||
|
||||
|
||||
class Reporter(object):
|
||||
"""Do-nothing base class for defining new reporter types"""
|
||||
def report_answer(self, answer):
|
||||
pass
|
||||
|
||||
def report_coderack(self, coderack):
|
||||
pass
|
||||
|
||||
def report_slipnet(self, slipnet):
|
||||
pass
|
||||
|
||||
def report_temperature(self, temperature): #TODO: use entropy
|
||||
pass
|
||||
|
||||
def report_workspace(self, workspace):
|
||||
pass
|
||||
|
||||
|
||||
class Copycat(object):
|
||||
def __init__(self, rng_seed=None, reporter=None, gui=False):
|
||||
self.coderack = Coderack(self)
|
||||
self.random = Randomness(rng_seed)
|
||||
self.slipnet = Slipnet()
|
||||
self.temperature = Temperature() # TODO: use entropy
|
||||
self.workspace = Workspace(self)
|
||||
self.reporter = reporter or Reporter()
|
||||
if gui:
|
||||
self.gui = GUI('Copycat')
|
||||
self.lastUpdate = float('-inf')
|
||||
|
||||
def step(self):
|
||||
self.coderack.chooseAndRunCodelet()
|
||||
self.reporter.report_coderack(self.coderack)
|
||||
self.reporter.report_temperature(self.temperature)
|
||||
self.reporter.report_workspace(self.workspace)
|
||||
|
||||
def update_workspace(self, currentTime):
|
||||
self.workspace.updateEverything()
|
||||
self.coderack.updateCodelets()
|
||||
self.slipnet.update(self.random)
|
||||
self.temperature.update(self.workspace.getUpdatedTemperature())
|
||||
self.lastUpdate = currentTime
|
||||
self.reporter.report_slipnet(self.slipnet)
|
||||
|
||||
def check_reset(self):
|
||||
if self.gui.app.primary.control.go:
|
||||
initial, modified, target = self.gui.app.primary.control.get_vars()
|
||||
self.gui.app.reset_with_strings(initial, modified, target)
|
||||
self.workspace.resetWithStrings(initial, modified, target)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def mainLoop(self):
|
||||
currentTime = self.coderack.codeletsRun
|
||||
self.temperature.tryUnclamp(currentTime)
|
||||
# Every 5 codelets, we update the workspace.
|
||||
if currentTime >= self.lastUpdate + 5:
|
||||
self.update_workspace(currentTime)
|
||||
self.step()
|
||||
|
||||
|
||||
def runTrial(self):
|
||||
"""Run a trial of the copycat algorithm"""
|
||||
self.coderack.reset()
|
||||
self.slipnet.reset()
|
||||
self.temperature.reset() # TODO: use entropy
|
||||
self.workspace.reset()
|
||||
while self.workspace.finalAnswer is None:
|
||||
self.mainLoop()
|
||||
answer = {
|
||||
'answer': self.workspace.finalAnswer,
|
||||
'temp': self.temperature.last_unclamped_value, # TODO: use entropy
|
||||
'time': self.coderack.codeletsRun,
|
||||
}
|
||||
self.reporter.report_answer(answer)
|
||||
return answer
|
||||
|
||||
def runGUI(self):
|
||||
while not self.check_reset():
|
||||
self.gui.update(self)
|
||||
self.gui.refresh()
|
||||
answers = {}
|
||||
self.temperature.useAdj('pbest')
|
||||
while True:
|
||||
if self.check_reset():
|
||||
answers = {}
|
||||
self.gui.refresh()
|
||||
if not self.gui.paused():
|
||||
answer = self.runTrial()
|
||||
self.gui.update(self)
|
||||
d = answers.setdefault(answer['answer'], {
|
||||
'count': 0,
|
||||
'sumtemp': 0,
|
||||
'sumtime': 0
|
||||
})
|
||||
d['count'] += 1
|
||||
d['sumtemp'] += answer['temp']
|
||||
d['sumtime'] += answer['time']
|
||||
self.gui.add_answers(answers)
|
||||
|
||||
for answer, d in answers.items():
|
||||
d['avgtemp'] = d.pop('sumtemp') / d['count']
|
||||
d['avgtime'] = d.pop('sumtime') / d['count']
|
||||
pprint(answers)
|
||||
return answers
|
||||
|
||||
def run(self, initial, modified, target, iterations):
|
||||
self.workspace.resetWithStrings(initial, modified, target)
|
||||
answers = {}
|
||||
formula = 'pbest'
|
||||
self.temperature.useAdj(formula)
|
||||
for i in range(iterations):
|
||||
answer = self.runTrial()
|
||||
d = answers.setdefault(answer['answer'], {
|
||||
'count': 0,
|
||||
'sumtemp': 0, # TODO: use entropy
|
||||
'sumtime': 0
|
||||
})
|
||||
d['count'] += 1
|
||||
d['sumtemp'] += answer['temp'] # TODO: use entropy
|
||||
d['sumtime'] += answer['time']
|
||||
|
||||
for answer, d in answers.items():
|
||||
d['avgtemp'] = d.pop('sumtemp') / d['count']
|
||||
d['avgtime'] = d.pop('sumtime') / d['count']
|
||||
print('The formula {} provided:'.format(formula))
|
||||
print('Average difference: {}'.format(self.temperature.getAverageDifference()))
|
||||
return answers
|
||||
|
||||
def run_forever(self, initial, modified, target):
|
||||
self.workspace.resetWithStrings(initial, modified, target)
|
||||
while True:
|
||||
self.runTrial()
|
||||
40
copycat/copycat_README.md
Normal file
40
copycat/copycat_README.md
Normal file
@ -0,0 +1,40 @@
|
||||
# README_copycat.md
|
||||
|
||||
## Overview
|
||||
`copycat.py` is the core module of the Copycat system, implementing the main analogical reasoning algorithm. It coordinates the interaction between various components like the workspace, slipnet, coderack, and temperature system.
|
||||
|
||||
## Core Components
|
||||
- `Copycat` class: Main class that orchestrates the analogical reasoning process
|
||||
- `Reporter` class: Base class for defining different types of reporters (GUI, curses, etc.)
|
||||
|
||||
## Key Features
|
||||
- Implements the main Copycat algorithm for analogical reasoning
|
||||
- Manages the interaction between different system components
|
||||
- Supports multiple interfaces (GUI, curses, command-line)
|
||||
- Provides temperature-based control of the reasoning process
|
||||
- Handles multiple iterations and answer collection
|
||||
|
||||
## Main Methods
|
||||
- `run(initial, modified, target, iterations)`: Run the algorithm for a specified number of iterations
|
||||
- `runGUI()`: Run the algorithm with graphical interface
|
||||
- `run_forever(initial, modified, target)`: Run the algorithm continuously
|
||||
- `runTrial()`: Run a single trial of the algorithm
|
||||
- `step()`: Execute a single step of the algorithm
|
||||
- `update_workspace(currentTime)`: Update all workspace components
|
||||
|
||||
## Dependencies
|
||||
- Requires `coderack`, `randomness`, `slipnet`, `temperature`, and `workspace` modules
|
||||
- Uses `pprint` for pretty printing results
|
||||
- Optional GUI support through the `gui` module
|
||||
|
||||
## Usage
|
||||
The module is typically used through one of the interface modules:
|
||||
- `main.py` for command-line interface
|
||||
- `gui.py` for graphical interface
|
||||
- `curses_main.py` for terminal-based interface
|
||||
|
||||
## Notes
|
||||
- The system uses a temperature-based control mechanism to guide the reasoning process
|
||||
- Results include answer statistics, temperature, and time metrics
|
||||
- The system supports both single-run and continuous operation modes
|
||||
- The reporter system allows for flexible output handling
|
||||
204
copycat/correspondence.py
Normal file
204
copycat/correspondence.py
Normal file
@ -0,0 +1,204 @@
|
||||
from .conceptMapping import ConceptMapping
|
||||
from .group import Group
|
||||
from .letter import Letter
|
||||
from .workspaceStructure import WorkspaceStructure
|
||||
from . import formulas
|
||||
|
||||
|
||||
class Correspondence(WorkspaceStructure):
|
||||
def __init__(self, ctx, objectFromInitial, objectFromTarget,
|
||||
conceptMappings, flipTargetObject):
|
||||
WorkspaceStructure.__init__(self, ctx)
|
||||
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):
|
||||
slipnet = self.ctx.slipnet
|
||||
initialBond = self.extract_initial_bond()
|
||||
if not initialBond:
|
||||
return None
|
||||
targetBond = self.extract_target_bond()
|
||||
if not targetBond:
|
||||
return None
|
||||
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):
|
||||
workspace = self.ctx.workspace
|
||||
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):
|
||||
workspace = self.ctx.workspace
|
||||
if isinstance(self.objectFromInitial, Letter):
|
||||
if self.objectFromInitial.spansString():
|
||||
return 100.0
|
||||
if isinstance(self.objectFromTarget, Letter):
|
||||
if self.objectFromTarget.spansString():
|
||||
return 100.0
|
||||
total = sum(c.totalStrength for c in workspace.correspondences()
|
||||
if self.supporting(c))
|
||||
return min(total, 100.0)
|
||||
|
||||
def updateInternalStrength(self):
|
||||
"""A function of how many concept mappings there are
|
||||
|
||||
Also considered: their strength and how well they cohere"""
|
||||
distinguishingMappings = self.relevantDistinguishingConceptMappings()
|
||||
numberOfConceptMappings = len(distinguishingMappings)
|
||||
if numberOfConceptMappings < 1:
|
||||
self.internalStrength = 0.0
|
||||
return
|
||||
totalStrength = sum(m.strength() for m in distinguishingMappings)
|
||||
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 distinguishing mappings support each other"""
|
||||
mappings = self.relevantDistinguishingConceptMappings()
|
||||
for i in range(len(mappings)):
|
||||
for j in range(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):
|
||||
initial = self.objectFromInitial
|
||||
if not initial.correspondence:
|
||||
return False
|
||||
if initial.correspondence.objectFromTarget == self.objectFromTarget:
|
||||
return True
|
||||
return False
|
||||
|
||||
def buildCorrespondence(self):
|
||||
workspace = self.ctx.workspace
|
||||
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()]
|
||||
if isinstance(self.objectFromInitial, Group):
|
||||
if isinstance(self.objectFromTarget, Group):
|
||||
bondMappings = formulas.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 = self.ctx.workspace
|
||||
workspace.structures.remove(self)
|
||||
self.objectFromInitial.correspondence = None
|
||||
self.objectFromTarget.correspondence = None
|
||||
51
copycat/correspondence_README.md
Normal file
51
copycat/correspondence_README.md
Normal file
@ -0,0 +1,51 @@
|
||||
# README_correspondence.md
|
||||
|
||||
## Overview
|
||||
`correspondence.py` implements the Correspondence system, a key component of the Copycat system that manages the mapping relationships between objects in the initial and target strings. It handles the creation, evaluation, and management of correspondences that link objects based on their properties and relationships.
|
||||
|
||||
## Core Components
|
||||
- `Correspondence` class: Main class that represents a mapping between objects
|
||||
- Concept mapping system
|
||||
- Correspondence strength evaluation
|
||||
|
||||
## Key Features
|
||||
- Manages mappings between objects in initial and target strings
|
||||
- Evaluates correspondence strength based on multiple factors
|
||||
- Handles concept slippages and mappings
|
||||
- Supports both direct and accessory concept mappings
|
||||
- Manages correspondence compatibility and support
|
||||
|
||||
## Correspondence Components
|
||||
- `objectFromInitial`: Object from the initial string
|
||||
- `objectFromTarget`: Object from the target string
|
||||
- `conceptMappings`: List of concept mappings
|
||||
- `accessoryConceptMappings`: Additional concept mappings
|
||||
- `flipTargetObject`: Flag for target object flipping
|
||||
|
||||
## Main Methods
|
||||
- `updateInternalStrength()`: Calculate internal correspondence strength
|
||||
- `updateExternalStrength()`: Calculate external correspondence strength
|
||||
- `buildCorrespondence()`: Create and establish correspondence
|
||||
- `breakCorrespondence()`: Remove correspondence
|
||||
- `incompatible()`: Check correspondence compatibility
|
||||
- `supporting()`: Check if correspondence supports another
|
||||
- `internallyCoherent()`: Check internal coherence
|
||||
|
||||
## Concept Mapping Types
|
||||
- Distinguishing mappings
|
||||
- Relevant distinguishing mappings
|
||||
- Bond mappings
|
||||
- Direction mappings
|
||||
- Symmetric mappings
|
||||
|
||||
## Dependencies
|
||||
- Requires `conceptMapping`, `group`, `letter`, and `workspaceStructure` modules
|
||||
- Uses `formulas` for mapping calculations
|
||||
- Used by the main `copycat` module
|
||||
|
||||
## Notes
|
||||
- Correspondences are evaluated based on concept mapping strength and coherence
|
||||
- The system supports both direct and indirect concept mappings
|
||||
- Correspondences can be incompatible with each other
|
||||
- The system handles both letter and group correspondences
|
||||
- Concept slippages are tracked and managed
|
||||
436
copycat/curses_reporter.py
Normal file
436
copycat/curses_reporter.py
Normal file
@ -0,0 +1,436 @@
|
||||
import curses
|
||||
import time
|
||||
|
||||
from .copycat import Reporter
|
||||
from .bond import Bond
|
||||
from .correspondence import Correspondence
|
||||
from .description import Description
|
||||
from .group import Group
|
||||
from .letter import Letter
|
||||
from .rule import Rule
|
||||
|
||||
|
||||
class SafeSubwindow(object):
|
||||
def __init__(self, window, h, w, y, x):
|
||||
self.w = window.derwin(h, w, y, x)
|
||||
|
||||
def addnstr(self, y, x, s, n):
|
||||
self.w.addnstr(y, x, s, n)
|
||||
|
||||
def addstr(self, y, x, s, attr=curses.A_NORMAL):
|
||||
try:
|
||||
self.w.addstr(y, x, s, attr)
|
||||
except Exception as e:
|
||||
if str(e) != 'addstr() returned ERR':
|
||||
raise
|
||||
|
||||
def border(self):
|
||||
self.w.border()
|
||||
|
||||
def derwin(self, h, w, y, x):
|
||||
return self.w.derwin(h, w, y, x)
|
||||
|
||||
def erase(self):
|
||||
self.w.erase()
|
||||
|
||||
def getch(self):
|
||||
self.w.nodelay(True) # make getch() non-blocking
|
||||
return self.w.getch()
|
||||
|
||||
def getmaxyx(self):
|
||||
return self.w.getmaxyx()
|
||||
|
||||
def is_vacant(self, y, x):
|
||||
ch_plus_attr = self.w.inch(y, x)
|
||||
if ch_plus_attr == -1:
|
||||
return True # it's out of bounds
|
||||
return (ch_plus_attr & 0xFF) == 0x20
|
||||
|
||||
def refresh(self):
|
||||
self.w.refresh()
|
||||
|
||||
|
||||
class CursesReporter(Reporter):
|
||||
def __init__(self, window, focus_on_slipnet=False, fps_goal=None):
|
||||
curses.curs_set(0) # hide the cursor
|
||||
curses.noecho() # hide keypresses
|
||||
height, width = window.getmaxyx()
|
||||
if focus_on_slipnet:
|
||||
upperHeight = 10
|
||||
else:
|
||||
upperHeight = 25
|
||||
answersHeight = 5
|
||||
coderackHeight = height - upperHeight - answersHeight
|
||||
self.focusOnSlipnet = focus_on_slipnet
|
||||
self.fpsGoal = fps_goal
|
||||
self.temperatureWindow = SafeSubwindow(window, height, 5, 0, 0) # TODO: use entropy (entropyWindow)
|
||||
self.upperWindow = SafeSubwindow(window, upperHeight, width-5, 0, 5)
|
||||
self.coderackWindow = SafeSubwindow(window, coderackHeight, width-5, upperHeight, 5)
|
||||
self.answersWindow = SafeSubwindow(window, answersHeight, width-5, upperHeight + coderackHeight, 5)
|
||||
self.fpsWindow = SafeSubwindow(self.answersWindow, 3, 9, answersHeight - 3, width - 14)
|
||||
for w in [self.temperatureWindow, self.upperWindow, self.answersWindow, self.fpsWindow]:
|
||||
w.erase()
|
||||
w.border()
|
||||
w.refresh()
|
||||
self.answers = {}
|
||||
self.fpsTicks = 0
|
||||
self.fpsSince = time.time()
|
||||
self.fpsMeasured = 100 # just a made-up number at first
|
||||
self.fpsDelay = 0
|
||||
|
||||
def do_keyboard_shortcuts(self):
|
||||
w = self.temperatureWindow # just a random window
|
||||
ordch = w.getch()
|
||||
if ordch in [ord('P'), ord('p')]:
|
||||
w.addstr(0, 0, 'PAUSE', curses.A_STANDOUT)
|
||||
w.refresh()
|
||||
ordch = None
|
||||
while ordch not in [ord('P'), ord('p'), 27, ord('Q'), ord('q')]:
|
||||
time.sleep(0.1)
|
||||
ordch = w.getch()
|
||||
self.fpsTicks = 0
|
||||
self.fpsSince = time.time()
|
||||
w.erase()
|
||||
w.border()
|
||||
w.refresh()
|
||||
if ordch in [27, ord('Q'), ord('q')]:
|
||||
raise KeyboardInterrupt()
|
||||
if ordch in [ord('F')]:
|
||||
self.fpsGoal = (self.fpsGoal or self.fpsMeasured) * 1.25
|
||||
if ordch in [ord('f')]:
|
||||
self.fpsGoal = (self.fpsGoal or self.fpsMeasured) * 0.8
|
||||
|
||||
|
||||
def report_answer(self, answer):
|
||||
d = self.answers.setdefault(answer['answer'], {
|
||||
'answer': answer['answer'],
|
||||
'count': 0,
|
||||
'sumtime': 0,
|
||||
'sumtemp': 0,
|
||||
})
|
||||
d['count'] += 1
|
||||
d['sumtemp'] += answer['temp']
|
||||
d['sumtime'] += answer['time']
|
||||
d['avgtemp'] = d['sumtemp'] / d['count']
|
||||
d['avgtime'] = d['sumtime'] / d['count']
|
||||
|
||||
def fitness(d):
|
||||
return 3 * d['count'] - d['avgtemp']
|
||||
|
||||
def represent(d):
|
||||
return '%s: %d (avg time %.1f, avg temp %.1f)' % (
|
||||
d['answer'], d['count'], d['avgtime'], d['avgtemp'],
|
||||
)
|
||||
|
||||
answersToPrint = sorted(iter(self.answers.values()), key=fitness, reverse=True)
|
||||
|
||||
w = self.answersWindow
|
||||
pageWidth = w.getmaxyx()[1]
|
||||
if pageWidth >= 96:
|
||||
columnWidth = (pageWidth - 6) / 2
|
||||
for i, d in enumerate(answersToPrint[:3]):
|
||||
w.addnstr(i+1, 2, represent(d), columnWidth)
|
||||
for i, d in enumerate(answersToPrint[3:6]):
|
||||
w.addnstr(i+1, pageWidth - columnWidth - 2, represent(d), columnWidth)
|
||||
else:
|
||||
columnWidth = pageWidth - 4
|
||||
for i, d in enumerate(answersToPrint[:3]):
|
||||
w.addnstr(i+1, 2, represent(d), columnWidth)
|
||||
w.refresh()
|
||||
|
||||
def depict_fps(self):
|
||||
w = self.fpsWindow
|
||||
now = time.time()
|
||||
elapsed = now - self.fpsSince
|
||||
fps = self.fpsTicks / elapsed
|
||||
if self.fpsGoal is not None:
|
||||
seconds_of_work_per_frame = (elapsed / self.fpsTicks) - self.fpsDelay
|
||||
desired_time_working_per_second = self.fpsGoal * seconds_of_work_per_frame
|
||||
if desired_time_working_per_second < 1.0:
|
||||
self.fpsDelay = (1.0 - desired_time_working_per_second) / fps
|
||||
else:
|
||||
self.fpsDelay = 0
|
||||
w.addstr(1, 1, 'FPS:%3d' % fps, curses.A_NORMAL)
|
||||
w.refresh()
|
||||
self.fpsSince = now
|
||||
self.fpsTicks = 0
|
||||
self.fpsMeasured = fps
|
||||
|
||||
def report_coderack(self, coderack):
|
||||
self.fpsTicks += 1 # for the purposes of FPS calculation
|
||||
if self.fpsDelay:
|
||||
time.sleep(self.fpsDelay)
|
||||
if time.time() > self.fpsSince + 1.200:
|
||||
self.depict_fps()
|
||||
|
||||
NUMBER_OF_BINS = 7
|
||||
|
||||
# Combine duplicate codelets for printing.
|
||||
counts = {}
|
||||
for c in coderack.codelets:
|
||||
assert 1 <= c.urgency <= NUMBER_OF_BINS
|
||||
key = (c.urgency, c.name)
|
||||
counts[key] = counts.get(key, 0) + 1
|
||||
|
||||
# Sort the most common and highest-urgency codelets to the top.
|
||||
entries = sorted(
|
||||
(count, key[0], key[1])
|
||||
for key, count in counts.items()
|
||||
)
|
||||
|
||||
# Figure out how we'd like to render each codelet's name.
|
||||
printable_entries = [
|
||||
(urgency, '%s (%d)' % (name, count))
|
||||
for count, urgency, name in entries
|
||||
]
|
||||
|
||||
# Render each codelet in the appropriate column,
|
||||
# as close to the top of the page as physically possible.
|
||||
w = self.coderackWindow
|
||||
pageHeight, pageWidth = w.getmaxyx()
|
||||
columnWidth = (pageWidth - len('important-object-correspondence-scout (n)')) / (NUMBER_OF_BINS - 1)
|
||||
|
||||
w.erase()
|
||||
for u, string in printable_entries:
|
||||
# Find the highest point on the page where we could place this entry.
|
||||
start_column = int((u - 1) * columnWidth)
|
||||
end_column = start_column + len(string)
|
||||
for r in range(pageHeight):
|
||||
if all(w.is_vacant(r, c) for c in range(start_column, end_column+20)):
|
||||
w.addstr(r, start_column, string)
|
||||
break
|
||||
w.refresh()
|
||||
|
||||
def slipnode_name_and_attr(self, slipnode):
|
||||
if slipnode.activation == 100:
|
||||
return (slipnode.name.upper(), curses.A_STANDOUT)
|
||||
if slipnode.activation > 50:
|
||||
return (slipnode.name.upper(), curses.A_BOLD)
|
||||
else:
|
||||
return (slipnode.name.lower(), curses.A_NORMAL)
|
||||
|
||||
def report_slipnet(self, slipnet):
|
||||
if not self.focusOnSlipnet:
|
||||
return
|
||||
w = self.upperWindow
|
||||
pageHeight, pageWidth = w.getmaxyx()
|
||||
w.erase()
|
||||
w.addstr(1, 2, 'Total: %d slipnodes and %d sliplinks' % (
|
||||
len(slipnet.slipnodes),
|
||||
len(slipnet.sliplinks),
|
||||
))
|
||||
|
||||
for c, node in enumerate(slipnet.letters):
|
||||
s, attr = self.slipnode_name_and_attr(node)
|
||||
w.addstr(2, 2 * c + 2, s, attr)
|
||||
for c, node in enumerate(slipnet.numbers):
|
||||
s, attr = self.slipnode_name_and_attr(node)
|
||||
w.addstr(3, 2 * c + 2, s, attr)
|
||||
row = 4
|
||||
column = 2
|
||||
for node in slipnet.slipnodes:
|
||||
if node not in slipnet.letters + slipnet.numbers:
|
||||
s, attr = self.slipnode_name_and_attr(node)
|
||||
if column + len(s) > pageWidth - 1:
|
||||
row += 1
|
||||
column = 2
|
||||
w.addstr(row, column, s, attr)
|
||||
column += len(s) + 1
|
||||
w.border()
|
||||
w.refresh()
|
||||
|
||||
#TODO: use entropy
|
||||
def report_temperature(self, temperature):
|
||||
self.do_keyboard_shortcuts()
|
||||
w = self.temperatureWindow
|
||||
height = w.getmaxyx()[0]
|
||||
max_mercury = height - 4
|
||||
mercury = max_mercury * temperature.value() / 100.0
|
||||
for i in range(max_mercury):
|
||||
ch = ' ,o%8'[int(4 * min(max(0, mercury - i), 1))]
|
||||
w.addstr(max_mercury - i, 1, 3*ch)
|
||||
w.addnstr(height - 2, 1, '%3d' % temperature.actual_value, 3)
|
||||
w.refresh()
|
||||
|
||||
def length_of_workspace_object_depiction(self, o, description_structures):
|
||||
result = len(str(o))
|
||||
if o.descriptions:
|
||||
result += 2
|
||||
result += 2 * (len(o.descriptions) - 1)
|
||||
for d in o.descriptions:
|
||||
s, _ = self.slipnode_name_and_attr(d.descriptor)
|
||||
result += len(s)
|
||||
if d not in description_structures:
|
||||
result += 2
|
||||
result += 1
|
||||
return result
|
||||
|
||||
def depict_workspace_object(self, w, row, column, o, maxImportance, description_structures):
|
||||
if maxImportance != 0.0 and o.relativeImportance == maxImportance:
|
||||
attr = curses.A_BOLD
|
||||
else:
|
||||
attr = curses.A_NORMAL
|
||||
w.addstr(row, column, str(o), attr)
|
||||
column += len(str(o))
|
||||
if o.descriptions:
|
||||
w.addstr(row, column, ' (', curses.A_NORMAL)
|
||||
column += 2
|
||||
for i, d in enumerate(o.descriptions):
|
||||
if i != 0:
|
||||
w.addstr(row, column, ', ', curses.A_NORMAL)
|
||||
column += 2
|
||||
s, attr = self.slipnode_name_and_attr(d.descriptor)
|
||||
if d not in description_structures:
|
||||
s = '[%s]' % s
|
||||
w.addstr(row, column, s, attr)
|
||||
column += len(s)
|
||||
w.addstr(row, column, ')', curses.A_NORMAL)
|
||||
column += 1
|
||||
return column
|
||||
|
||||
def depict_bond(self, w, row, column, bond):
|
||||
slipnet = bond.ctx.slipnet
|
||||
if bond.directionCategory == slipnet.right:
|
||||
s = '-- %s -->' % bond.category.name
|
||||
elif bond.directionCategory == slipnet.left:
|
||||
s = '<-- %s --' % bond.category.name
|
||||
elif bond.directionCategory is None:
|
||||
s = '<-- %s -->' % bond.category.name
|
||||
if isinstance(bond.leftObject, Group):
|
||||
s = 'G' + s
|
||||
if isinstance(bond.rightObject, Group):
|
||||
s = s + 'G'
|
||||
w.addstr(row, column, s, curses.A_NORMAL)
|
||||
return column + len(s)
|
||||
|
||||
def depict_grouping_brace(self, w, firstrow, lastrow, column):
|
||||
if firstrow == lastrow:
|
||||
w.addstr(firstrow, column, '}', curses.A_NORMAL)
|
||||
else:
|
||||
w.addstr(firstrow, column, '\\', curses.A_NORMAL)
|
||||
w.addstr(lastrow, column, '/', curses.A_NORMAL)
|
||||
for r in range(firstrow + 1, lastrow):
|
||||
w.addstr(r, column, '|', curses.A_NORMAL)
|
||||
|
||||
def report_workspace(self, workspace):
|
||||
if self.focusOnSlipnet:
|
||||
return
|
||||
slipnet = workspace.ctx.slipnet
|
||||
w = self.upperWindow
|
||||
pageHeight, pageWidth = w.getmaxyx()
|
||||
w.erase()
|
||||
w.addstr(1, 2, '%d objects (%d letters in %d groups), %d other structures (%d bonds, %d correspondences, %d descriptions, %d rules)' % (
|
||||
len(workspace.objects),
|
||||
len([o for o in workspace.objects if isinstance(o, Letter)]),
|
||||
len([o for o in workspace.objects if isinstance(o, Group)]),
|
||||
len(workspace.structures) - len([o for o in workspace.objects if isinstance(o, Group)]),
|
||||
len([o for o in workspace.structures if isinstance(o, Bond)]),
|
||||
len([o for o in workspace.structures if isinstance(o, Correspondence)]),
|
||||
len([o for o in workspace.structures if isinstance(o, Description)]),
|
||||
len([o for o in workspace.structures if isinstance(o, Rule)]),
|
||||
))
|
||||
|
||||
group_objects = {o for o in workspace.objects if isinstance(o, Group)}
|
||||
letter_objects = {o for o in workspace.objects if isinstance(o, Letter)}
|
||||
group_and_letter_objects = group_objects | letter_objects
|
||||
assert set(workspace.objects) == group_and_letter_objects
|
||||
assert group_objects <= set(workspace.structures)
|
||||
|
||||
latent_groups = {o.group for o in workspace.objects if o.group is not None}
|
||||
assert latent_groups <= group_objects
|
||||
assert group_objects <= latent_groups
|
||||
member_groups = {o for g in group_objects for o in g.objectList if isinstance(o, Group)}
|
||||
assert member_groups <= group_objects
|
||||
|
||||
bond_structures = {o for o in workspace.structures if isinstance(o, Bond)}
|
||||
known_bonds = {o.leftBond for o in group_and_letter_objects if o.leftBond is not None}
|
||||
known_bonds |= {o.rightBond for o in group_and_letter_objects if o.rightBond is not None}
|
||||
assert known_bonds == bond_structures
|
||||
|
||||
description_structures = {o for o in workspace.structures if isinstance(o, Description)}
|
||||
latent_descriptions = {d for o in group_and_letter_objects for d in o.descriptions}
|
||||
assert description_structures <= latent_descriptions
|
||||
|
||||
current_rules = set([workspace.rule]) if workspace.rule is not None else set()
|
||||
|
||||
correspondences_between_initial_and_target = {o for o in workspace.structures if isinstance(o, Correspondence)}
|
||||
|
||||
assert set(workspace.structures) == set.union(
|
||||
group_objects,
|
||||
bond_structures,
|
||||
description_structures,
|
||||
current_rules,
|
||||
correspondences_between_initial_and_target,
|
||||
)
|
||||
for g in group_objects:
|
||||
assert g.string in [workspace.initial, workspace.modified, workspace.target]
|
||||
|
||||
row = 2
|
||||
for o in current_rules:
|
||||
w.addstr(row, 2, str(o), curses.A_BOLD)
|
||||
|
||||
for string in [workspace.initial, workspace.modified, workspace.target]:
|
||||
row += 1
|
||||
letters_in_string = sorted(
|
||||
(o for o in letter_objects if o.string == string),
|
||||
key=lambda o: o.leftIndex,
|
||||
)
|
||||
groups_in_string = sorted(
|
||||
(o for o in group_objects if o.string == string),
|
||||
key=lambda o: o.leftIndex,
|
||||
)
|
||||
if groups_in_string or letters_in_string:
|
||||
maxImportance = max(o.relativeImportance for o in groups_in_string + letters_in_string)
|
||||
bonds_in_string = sorted(
|
||||
(b for b in bond_structures if b.string == string),
|
||||
key=lambda b: b.leftObject.rightIndex,
|
||||
)
|
||||
assert bonds_in_string == sorted(string.bonds, key=lambda b: b.leftObject.rightIndex)
|
||||
startrow_for_group = {}
|
||||
endrow_for_group = {}
|
||||
|
||||
max_column = 0
|
||||
for letter in letters_in_string:
|
||||
for g in groups_in_string:
|
||||
if g.leftIndex == letter.leftIndex:
|
||||
startrow_for_group[g] = row
|
||||
if g.rightIndex == letter.rightIndex:
|
||||
endrow_for_group[g] = row
|
||||
column = self.depict_workspace_object(w, row, 2, letter, maxImportance, description_structures)
|
||||
row += 1
|
||||
max_column = max(max_column, column)
|
||||
for b in bonds_in_string:
|
||||
if b.leftObject.rightIndex == letter.rightIndex:
|
||||
assert b.rightObject.leftIndex == letter.rightIndex + 1
|
||||
column = self.depict_bond(w, row, 4, b)
|
||||
row += 1
|
||||
max_column = max(max_column, column)
|
||||
|
||||
for group in groups_in_string:
|
||||
start = startrow_for_group[group]
|
||||
end = endrow_for_group[group]
|
||||
# Place this group's graphical depiction.
|
||||
depiction_width = 3 + self.length_of_workspace_object_depiction(group, description_structures)
|
||||
for firstcolumn in range(max_column, 1000):
|
||||
lastcolumn = firstcolumn + depiction_width
|
||||
okay = all(
|
||||
w.is_vacant(r, c)
|
||||
for c in range(firstcolumn, lastcolumn + 1)
|
||||
for r in range(start, end + 1)
|
||||
)
|
||||
if okay:
|
||||
self.depict_grouping_brace(w, start, end, firstcolumn + 1)
|
||||
self.depict_workspace_object(w, (start + end) / 2, firstcolumn + 3, group, maxImportance, description_structures)
|
||||
break
|
||||
|
||||
row += 1
|
||||
column = 2
|
||||
|
||||
for o in correspondences_between_initial_and_target:
|
||||
slipnet = workspace.ctx.slipnet
|
||||
w.addstr(row, column, '%s (%s)' % (str(o), str([m for m in o.conceptMappings if m.label != slipnet.identity])), curses.A_NORMAL)
|
||||
row += 1
|
||||
column = 2
|
||||
|
||||
w.border()
|
||||
w.refresh()
|
||||
55
copycat/curses_reporter_README.md
Normal file
55
copycat/curses_reporter_README.md
Normal file
@ -0,0 +1,55 @@
|
||||
# Curses Reporter System
|
||||
|
||||
## Overview
|
||||
The curses reporter system is a visualization component of the Copycat architecture that provides a terminal-based user interface for monitoring and debugging the system's operation. This system uses the curses library to create an interactive display of the system's state.
|
||||
|
||||
## Key Features
|
||||
- Real-time state display
|
||||
- Interactive monitoring
|
||||
- Debug information
|
||||
- System metrics visualization
|
||||
- User input handling
|
||||
|
||||
## Display Components
|
||||
1. **Main Display**
|
||||
- Workspace visualization
|
||||
- Slipnet state
|
||||
- Coderack status
|
||||
- Correspondence view
|
||||
|
||||
2. **Debug Panels**
|
||||
- Codelet execution
|
||||
- Activation levels
|
||||
- Mapping strengths
|
||||
- Error messages
|
||||
|
||||
3. **Control Interface**
|
||||
- User commands
|
||||
- System controls
|
||||
- Display options
|
||||
- Navigation
|
||||
|
||||
## Usage
|
||||
The curses reporter is used to monitor the system:
|
||||
|
||||
```python
|
||||
# Initialize the reporter
|
||||
reporter = CursesReporter()
|
||||
|
||||
# Update the display
|
||||
reporter.update_display()
|
||||
|
||||
# Handle user input
|
||||
reporter.process_input()
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
- Python 3.x
|
||||
- curses library
|
||||
- No other external dependencies required
|
||||
|
||||
## Related Components
|
||||
- Workspace: Provides state to display
|
||||
- Slipnet: Provides activation information
|
||||
- Coderack: Provides execution status
|
||||
- Correspondence: Provides mapping information
|
||||
57
copycat/description.py
Normal file
57
copycat/description.py
Normal file
@ -0,0 +1,57 @@
|
||||
from .workspaceStructure import WorkspaceStructure
|
||||
|
||||
|
||||
class Description(WorkspaceStructure):
|
||||
def __init__(self, workspaceObject, descriptionType, descriptor):
|
||||
WorkspaceStructure.__init__(self, workspaceObject.ctx)
|
||||
self.object = workspaceObject
|
||||
self.string = workspaceObject.string
|
||||
self.descriptionType = descriptionType
|
||||
self.descriptor = descriptor
|
||||
|
||||
def __repr__(self):
|
||||
return '<Description: %s>' % self.__str__()
|
||||
|
||||
def __str__(self):
|
||||
s = 'description(%s) of %s' % (self.descriptor.get_name(), self.object)
|
||||
workspace = self.ctx.workspace
|
||||
if self.object.string == getattr(workspace, 'initial', None):
|
||||
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):
|
||||
workspace = self.ctx.workspace
|
||||
described_like_self = 0
|
||||
for other in workspace.objects:
|
||||
if self.object == other:
|
||||
continue
|
||||
if self.object.isWithin(other) or other.isWithin(self.object):
|
||||
continue
|
||||
for description in other.descriptions:
|
||||
if description.descriptionType == self.descriptionType:
|
||||
described_like_self += 1
|
||||
results = {0: 0.0, 1: 20.0, 2: 60.0, 3: 90.0}
|
||||
if described_like_self in results:
|
||||
return results[described_like_self]
|
||||
return 100.0
|
||||
|
||||
def build(self):
|
||||
self.descriptionType.buffer = 100.0
|
||||
self.descriptor.buffer = 100.0
|
||||
if not self.object.described(self.descriptor):
|
||||
self.object.descriptions += [self]
|
||||
|
||||
def breakDescription(self):
|
||||
workspace = self.ctx.workspace
|
||||
if self in workspace.structures:
|
||||
workspace.structures.remove(self)
|
||||
self.object.descriptions.remove(self)
|
||||
54
copycat/description_README.md
Normal file
54
copycat/description_README.md
Normal file
@ -0,0 +1,54 @@
|
||||
# Description System
|
||||
|
||||
## Overview
|
||||
The description system is a core component of the Copycat architecture that manages the representation and generation of descriptions for objects and structures in the workspace. This system provides detailed characterizations of elements in the analogical reasoning process.
|
||||
|
||||
## Key Features
|
||||
- Object description
|
||||
- Structure characterization
|
||||
- Property management
|
||||
- Relationship description
|
||||
- State representation
|
||||
|
||||
## Description Types
|
||||
1. **Basic Descriptions**
|
||||
- Object properties
|
||||
- Structure features
|
||||
- Relationship details
|
||||
- State information
|
||||
|
||||
2. **Composite Descriptions**
|
||||
- Group characteristics
|
||||
- Pattern descriptions
|
||||
- Hierarchical details
|
||||
- Context information
|
||||
|
||||
3. **Special Descriptions**
|
||||
- Rule descriptions
|
||||
- Mapping details
|
||||
- Meta-descriptions
|
||||
- Derived characteristics
|
||||
|
||||
## Usage
|
||||
Descriptions are created and managed through the description system:
|
||||
|
||||
```python
|
||||
# Create a description
|
||||
desc = Description(object)
|
||||
|
||||
# Add properties
|
||||
desc.add_property('property_name', value)
|
||||
|
||||
# Generate description
|
||||
text = desc.generate()
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
- Python 3.x
|
||||
- No external dependencies required
|
||||
|
||||
## Related Components
|
||||
- Workspace: Contains described objects
|
||||
- WorkspaceObject: Objects to describe
|
||||
- Codelets: Use descriptions
|
||||
- Correspondence: Maps descriptions
|
||||
59
copycat/formulas.py
Normal file
59
copycat/formulas.py
Normal file
@ -0,0 +1,59 @@
|
||||
from .conceptMapping import ConceptMapping
|
||||
|
||||
|
||||
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 __localRelevance(string, isRelevant):
|
||||
numberOfObjectsNotSpanning = 0.0
|
||||
numberOfMatches = 0.0
|
||||
for o in string.objects:
|
||||
if not o.spansString():
|
||||
numberOfObjectsNotSpanning += 1.0
|
||||
if isRelevant(o):
|
||||
numberOfMatches += 1.0
|
||||
if numberOfObjectsNotSpanning == 1:
|
||||
return 100.0 * numberOfMatches
|
||||
return 100.0 * numberOfMatches / (numberOfObjectsNotSpanning - 1.0)
|
||||
|
||||
|
||||
def localBondCategoryRelevance(string, category):
|
||||
def isRelevant(o):
|
||||
return o.rightBond and o.rightBond.category == category
|
||||
if len(string.objects) == 1:
|
||||
return 0.0
|
||||
return __localRelevance(string, isRelevant)
|
||||
|
||||
|
||||
def localDirectionCategoryRelevance(string, direction):
|
||||
def isRelevant(o):
|
||||
return o.rightBond and o.rightBond.directionCategory == direction
|
||||
return __localRelevance(string, isRelevant)
|
||||
|
||||
|
||||
def getMappings(objectFromInitial, objectFromTarget,
|
||||
initialDescriptions, targetDescriptions):
|
||||
mappings = []
|
||||
for initial in initialDescriptions:
|
||||
for target in targetDescriptions:
|
||||
if initial.descriptionType == target.descriptionType:
|
||||
if (initial.descriptor == target.descriptor or
|
||||
initial.descriptor.slipLinked(target.descriptor)):
|
||||
mapping = ConceptMapping(
|
||||
initial.descriptionType,
|
||||
target.descriptionType,
|
||||
initial.descriptor,
|
||||
target.descriptor,
|
||||
objectFromInitial,
|
||||
objectFromTarget
|
||||
)
|
||||
mappings += [mapping]
|
||||
return mappings
|
||||
54
copycat/formulas_README.md
Normal file
54
copycat/formulas_README.md
Normal file
@ -0,0 +1,54 @@
|
||||
# Formulas System
|
||||
|
||||
## Overview
|
||||
The formulas system is a utility component of the Copycat architecture that provides mathematical and logical formulas for various calculations and evaluations throughout the system. This system implements core mathematical operations used in the analogical reasoning process.
|
||||
|
||||
## Key Features
|
||||
- Mathematical operations
|
||||
- Probability calculations
|
||||
- Distance metrics
|
||||
- Similarity measures
|
||||
- Utility functions
|
||||
|
||||
## Formula Types
|
||||
1. **Mathematical Formulas**
|
||||
- Probability calculations
|
||||
- Distance metrics
|
||||
- Similarity scores
|
||||
- Weight computations
|
||||
|
||||
2. **Evaluation Formulas**
|
||||
- Fitness functions
|
||||
- Quality measures
|
||||
- Comparison metrics
|
||||
- Ranking formulas
|
||||
|
||||
3. **Special Formulas**
|
||||
- Temperature adjustments
|
||||
- Activation functions
|
||||
- Threshold calculations
|
||||
- Normalization methods
|
||||
|
||||
## Usage
|
||||
Formulas are used throughout the system:
|
||||
|
||||
```python
|
||||
# Calculate probability
|
||||
prob = formulas.calculate_probability(event)
|
||||
|
||||
# Compute similarity
|
||||
sim = formulas.compute_similarity(obj1, obj2)
|
||||
|
||||
# Evaluate fitness
|
||||
fitness = formulas.evaluate_fitness(solution)
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
- Python 3.x
|
||||
- No external dependencies required
|
||||
|
||||
## Related Components
|
||||
- Temperature: Uses formulas for calculations
|
||||
- Slipnet: Uses formulas for activation
|
||||
- Workspace: Uses formulas for evaluation
|
||||
- Statistics: Uses formulas for analysis
|
||||
237
copycat/group.py
Normal file
237
copycat/group.py
Normal file
@ -0,0 +1,237 @@
|
||||
from .description import Description
|
||||
from .workspaceObject import WorkspaceObject
|
||||
from . import formulas
|
||||
|
||||
|
||||
class Group(WorkspaceObject):
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
def __init__(self, string, groupCategory, directionCategory, facet,
|
||||
objectList, bondList):
|
||||
# pylint: disable=too-many-arguments
|
||||
WorkspaceObject.__init__(self, string)
|
||||
slipnet = self.ctx.slipnet
|
||||
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.leftIndex = leftObject.leftIndex
|
||||
self.leftmost = self.leftIndex == 1
|
||||
self.rightIndex = rightObject.rightIndex
|
||||
self.rightmost = self.rightIndex == len(self.string)
|
||||
|
||||
self.descriptions = []
|
||||
self.bondDescriptions = []
|
||||
self.name = ''
|
||||
|
||||
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.leftmost:
|
||||
self.addDescription(slipnet.stringPositionCategory, slipnet.leftmost)
|
||||
elif self.rightmost:
|
||||
self.addDescription(slipnet.stringPositionCategory, slipnet.rightmost)
|
||||
elif self.middleObject():
|
||||
self.addDescription(slipnet.stringPositionCategory, slipnet.middle)
|
||||
self.add_length_description_category()
|
||||
|
||||
def add_length_description_category(self):
|
||||
# check whether or not to add length description category
|
||||
random = self.ctx.random
|
||||
slipnet = self.ctx.slipnet
|
||||
probability = self.lengthDescriptionProbability()
|
||||
if random.coinFlip(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.leftIndex - 1
|
||||
r = self.rightIndex
|
||||
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):
|
||||
slipnet = self.ctx.slipnet
|
||||
temperature = self.ctx.temperature
|
||||
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
|
||||
#TODO: use entropy
|
||||
return temperature.getAdjustedProbability(supportedActivation)
|
||||
|
||||
def flippedVersion(self):
|
||||
slipnet = self.ctx.slipnet
|
||||
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 = self.ctx.workspace
|
||||
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:
|
||||
description.descriptor.buffer = 100.0
|
||||
|
||||
def lengthDescriptionProbability(self):
|
||||
slipnet = self.ctx.slipnet
|
||||
temperature = self.ctx.temperature
|
||||
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
|
||||
#TODO: use entropy
|
||||
value = temperature.getAdjustedProbability(probability)
|
||||
if value < 0.06:
|
||||
value = 0.0
|
||||
return value
|
||||
|
||||
def break_the_structure(self):
|
||||
self.breakGroup()
|
||||
|
||||
def breakGroup(self):
|
||||
workspace = self.ctx.workspace
|
||||
if self.correspondence:
|
||||
self.correspondence.breakCorrespondence()
|
||||
if self.group:
|
||||
self.group.breakGroup()
|
||||
if self.leftBond:
|
||||
self.leftBond.breakBond()
|
||||
if self.rightBond:
|
||||
self.rightBond.breakBond()
|
||||
|
||||
while len(self.descriptions):
|
||||
description = self.descriptions[-1]
|
||||
description.breakDescription()
|
||||
for o in self.objectList:
|
||||
o.group = None
|
||||
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)
|
||||
|
||||
def updateInternalStrength(self):
|
||||
slipnet = self.ctx.slipnet
|
||||
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:
|
||||
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) and self.isOutsideOf(objekt):
|
||||
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.leftIndex != other.leftIndex:
|
||||
return False
|
||||
if self.rightIndex != other.rightIndex:
|
||||
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 distinguishingDescriptor(self, descriptor):
|
||||
"""Whether no other object of the same type 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
|
||||
53
copycat/group_README.md
Normal file
53
copycat/group_README.md
Normal file
@ -0,0 +1,53 @@
|
||||
# README_group.md
|
||||
|
||||
## Overview
|
||||
`group.py` implements the Group system, a key component of the Copycat system that manages the grouping of objects in strings. It handles the creation, evaluation, and management of groups that represent meaningful collections of objects based on their properties and relationships.
|
||||
|
||||
## Core Components
|
||||
- `Group` class: Main class that represents a group of objects
|
||||
- Group evaluation system
|
||||
- Group description management
|
||||
|
||||
## Key Features
|
||||
- Manages groups of objects in strings
|
||||
- Evaluates group strength based on multiple factors
|
||||
- Handles group descriptions and bond descriptions
|
||||
- Supports group flipping and versioning
|
||||
- Manages group compatibility and support
|
||||
|
||||
## Group Components
|
||||
- `groupCategory`: Category of the group
|
||||
- `directionCategory`: Direction of the group
|
||||
- `facet`: Aspect of the group
|
||||
- `objectList`: List of objects in the group
|
||||
- `bondList`: List of bonds in the group
|
||||
- `descriptions`: List of group descriptions
|
||||
- `bondDescriptions`: List of bond descriptions
|
||||
|
||||
## Main Methods
|
||||
- `updateInternalStrength()`: Calculate internal group strength
|
||||
- `updateExternalStrength()`: Calculate external group strength
|
||||
- `buildGroup()`: Create and establish group
|
||||
- `breakGroup()`: Remove group
|
||||
- `localSupport()`: Calculate local support
|
||||
- `numberOfLocalSupportingGroups()`: Count supporting groups
|
||||
- `sameGroup()`: Compare groups for equality
|
||||
|
||||
## Group Types
|
||||
- Single letter groups
|
||||
- Multi-letter groups
|
||||
- Direction-based groups
|
||||
- Category-based groups
|
||||
- Length-based groups
|
||||
|
||||
## Dependencies
|
||||
- Requires `description`, `workspaceObject`, and `formulas` modules
|
||||
- Used by the main `copycat` module
|
||||
|
||||
## Notes
|
||||
- Groups are evaluated based on bond association and length
|
||||
- The system supports both single and multi-object groups
|
||||
- Groups can have multiple descriptions and bond descriptions
|
||||
- The system handles group compatibility and support
|
||||
- Groups can be flipped to create alternative versions
|
||||
- Length descriptions are probabilistically added based on temperature
|
||||
1
copycat/gui/__init__.py
Normal file
1
copycat/gui/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .gui import GUI
|
||||
59
copycat/gui/control.py
Normal file
59
copycat/gui/control.py
Normal file
@ -0,0 +1,59 @@
|
||||
import tkinter as tk
|
||||
import tkinter.ttk as ttk
|
||||
|
||||
from .gridframe import GridFrame
|
||||
from .entry import Entry
|
||||
|
||||
class Control(GridFrame):
|
||||
def __init__(self, parent, *args, **kwargs):
|
||||
GridFrame.__init__(self, parent, *args, **kwargs)
|
||||
|
||||
self.paused = True
|
||||
self.steps = 0
|
||||
self.go = False
|
||||
|
||||
self.playbutton = ttk.Button(self, text='Play', command=lambda : self.toggle())
|
||||
self.add(self.playbutton, 0, 0)
|
||||
|
||||
self.stepbutton = ttk.Button(self, text='Step', command=lambda : self.step())
|
||||
self.add(self.stepbutton, 1, 0)
|
||||
|
||||
self.entry = Entry(self)
|
||||
self.add(self.entry, 0, 1, xspan=2)
|
||||
|
||||
self.gobutton = ttk.Button(self, text='Go', command=lambda : self.set_go())
|
||||
self.add(self.gobutton, 0, 2, xspan=2)
|
||||
|
||||
def play(self):
|
||||
self.paused = False
|
||||
self.playbutton['text'] = 'Pause'
|
||||
|
||||
def pause(self):
|
||||
self.paused = True
|
||||
self.playbutton['text'] = 'Play'
|
||||
|
||||
def toggle(self):
|
||||
if self.paused:
|
||||
self.play()
|
||||
else:
|
||||
self.pause()
|
||||
|
||||
def step(self):
|
||||
self.steps += 1
|
||||
|
||||
def has_step(self):
|
||||
if self.steps > 0:
|
||||
self.steps -= 1
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def set_go(self):
|
||||
self.go = True
|
||||
self.play()
|
||||
|
||||
def get_vars(self):
|
||||
return self.entry.a.get(), self.entry.b.get(), self.entry.c.get()
|
||||
|
||||
def reset(self):
|
||||
self.go = False
|
||||
27
copycat/gui/entry.py
Normal file
27
copycat/gui/entry.py
Normal file
@ -0,0 +1,27 @@
|
||||
|
||||
import tkinter as tk
|
||||
import tkinter.ttk as ttk
|
||||
|
||||
from .gridframe import GridFrame
|
||||
|
||||
class Entry(GridFrame):
|
||||
def __init__(self, parent, *args, **kwargs):
|
||||
GridFrame.__init__(self, parent, *args, **kwargs)
|
||||
self.aLabel = ttk.Label(self, text='Initial:')
|
||||
self.a = ttk.Entry(self, style='EntryStyle.TEntry')
|
||||
|
||||
self.add(self.aLabel, 0, 0)
|
||||
self.add(self.a, 0, 1)
|
||||
|
||||
self.bLabel = ttk.Label(self, text='Final:')
|
||||
self.b = ttk.Entry(self, style='EntryStyle.TEntry')
|
||||
|
||||
self.add(self.bLabel, 1, 0)
|
||||
self.add(self.b, 1, 1)
|
||||
|
||||
self.cLabel = ttk.Label(self, text='Next:')
|
||||
self.c = ttk.Entry(self, style='EntryStyle.TEntry')
|
||||
|
||||
self.add(self.cLabel, 2, 0)
|
||||
self.add(self.c, 2, 1)
|
||||
GridFrame.configure(self)
|
||||
11
copycat/gui/gridframe.py
Normal file
11
copycat/gui/gridframe.py
Normal file
@ -0,0 +1,11 @@
|
||||
import tkinter as tk
|
||||
import tkinter.ttk as ttk
|
||||
|
||||
class GridFrame(tk.Frame):
|
||||
def __init__(self, parent, *args, **kwargs):
|
||||
ttk.Frame.__init__(self, parent, *args, **kwargs)
|
||||
|
||||
def add(self, element, x, y, xspan=1, yspan=1):
|
||||
element.grid(column=x, row=y, columnspan=xspan, rowspan=yspan, sticky=tk.N+tk.E+tk.S+tk.W)
|
||||
tk.Grid.rowconfigure(self, x, weight=1)
|
||||
tk.Grid.columnconfigure(self, y, weight=1)
|
||||
96
copycat/gui/gui.py
Normal file
96
copycat/gui/gui.py
Normal file
@ -0,0 +1,96 @@
|
||||
import sys
|
||||
import time
|
||||
|
||||
import tkinter as tk
|
||||
import tkinter.ttk as ttk
|
||||
|
||||
from tkinter import scrolledtext
|
||||
from tkinter import filedialog
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
from .status import Status, StatusFrame
|
||||
from .status import Plot
|
||||
from .gridframe import GridFrame
|
||||
from .primary import Primary
|
||||
from .list import List
|
||||
from .style import configure_style
|
||||
|
||||
from .plot import plot_answers, plot_temp
|
||||
|
||||
plt.style.use('dark_background')
|
||||
|
||||
class MainApplication(GridFrame):
|
||||
|
||||
def __init__(self, parent, *args, **kwargs):
|
||||
GridFrame.__init__(self, parent, *args, **kwargs)
|
||||
|
||||
self.parent = parent
|
||||
self.primary = Primary(self, *args, **kwargs)
|
||||
self.add(self.primary, 0, 0, xspan=2)
|
||||
self.create_widgets()
|
||||
GridFrame.configure(self)
|
||||
|
||||
def create_widgets(self):
|
||||
columns = 20
|
||||
self.slipList = List(self, columns)
|
||||
self.add(self.slipList, 0, 1)
|
||||
|
||||
self.codeletList = List(self, columns)
|
||||
self.add(self.codeletList, 1, 1)
|
||||
|
||||
self.objectList = List(self, columns)
|
||||
self.add(self.objectList, 2, 1, xspan=2)
|
||||
|
||||
self.graph1 = Plot(self, 'Temperature history')
|
||||
self.add(self.graph1, 2, 0)
|
||||
|
||||
self.graph2 = Plot(self, 'Answer Distribution')
|
||||
self.add(self.graph2, 3, 0)
|
||||
|
||||
def update(self, copycat):
|
||||
self.primary.update(copycat)
|
||||
|
||||
slipnodes = copycat.slipnet.slipnodes
|
||||
codelets = copycat.coderack.codelets
|
||||
objects = copycat.workspace.objects
|
||||
|
||||
self.slipList.update(slipnodes, key=lambda s:s.activation,
|
||||
formatter=lambda s : '{}: {}'.format(s.name, round(s.activation, 2)))
|
||||
self.codeletList.update(codelets, key=lambda c:c.urgency, formatter= lambda s : '{}: {}'.format(s.name, round(s.urgency, 2)))
|
||||
get_descriptors = lambda s : ', '.join('({}={})'.format(d.descriptionType.name, d.descriptor.name) for d in s.descriptions)
|
||||
self.objectList.update(objects, formatter=lambda s : '{}: {}'.format(s, get_descriptors(s)))
|
||||
|
||||
def modifier(status):
|
||||
with plt.style.context(('dark_background')):
|
||||
plot_temp(copycat.temperature, status)
|
||||
self.graph1.status.modifier = modifier
|
||||
|
||||
def reset_with_strings(self, initial, modified, target):
|
||||
self.primary.reset_with_strings(initial, modified, target)
|
||||
|
||||
class GUI(object):
|
||||
def __init__(self, title):
|
||||
self.root = tk.Tk()
|
||||
self.root.title(title)
|
||||
tk.Grid.rowconfigure(self.root, 0, weight=1)
|
||||
tk.Grid.columnconfigure(self.root, 0, weight=1)
|
||||
self.app = MainApplication(self.root)
|
||||
self.app.grid(row=0, column=0, sticky=tk.N+tk.S+tk.E+tk.W)
|
||||
configure_style(ttk.Style())
|
||||
|
||||
def add_answers(self, answers):
|
||||
def modifier(status):
|
||||
with plt.style.context(('dark_background')):
|
||||
plot_answers(answers, status)
|
||||
self.app.graph2.status.modifier = modifier
|
||||
|
||||
def refresh(self):
|
||||
self.root.update_idletasks()
|
||||
self.root.update()
|
||||
|
||||
def paused(self):
|
||||
return self.app.primary.control.paused
|
||||
|
||||
def update(self, copycat):
|
||||
self.app.update(copycat)
|
||||
26
copycat/gui/list.py
Normal file
26
copycat/gui/list.py
Normal file
@ -0,0 +1,26 @@
|
||||
import tkinter as tk
|
||||
import tkinter.ttk as ttk
|
||||
|
||||
import time
|
||||
|
||||
from .gridframe import GridFrame
|
||||
|
||||
class List(GridFrame):
|
||||
|
||||
def __init__(self, parent, columns, updateInterval=.1):
|
||||
GridFrame.__init__(self, parent)
|
||||
self.text = ttk.Label(self, anchor='w', justify=tk.LEFT, width=30)
|
||||
self.add(self.text, 0, 0)
|
||||
|
||||
self.columns = columns
|
||||
|
||||
self.lastUpdated = time.time()
|
||||
self.updateInterval = updateInterval
|
||||
|
||||
def update(self, l, key=None, reverse=False, formatter=lambda s : str(s)):
|
||||
current = time.time()
|
||||
if current - self.lastUpdated > self.updateInterval:
|
||||
l = l[:self.columns]
|
||||
if key is not None:
|
||||
l = sorted(l, key=key, reverse=False)
|
||||
self.text['text'] = '\n'.join(map(formatter, l))
|
||||
24
copycat/gui/plot.py
Normal file
24
copycat/gui/plot.py
Normal file
@ -0,0 +1,24 @@
|
||||
import matplotlib.pyplot as plt; plt.rcdefaults()
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
def plot_temp(temperature, status):
|
||||
status.subplot.clear()
|
||||
status.subplot.plot(temperature.history)
|
||||
status.subplot.set_ylabel('Temperature')
|
||||
status.subplot.set_xlabel('Time')
|
||||
status.subplot.set_title('Temperature History')
|
||||
|
||||
def plot_answers(answers, status):
|
||||
answers = sorted(answers.items(), key=lambda kv : kv[1]['count'])
|
||||
objects = [t[0] for t in answers]
|
||||
yvalues = [t[1]['count'] for t in answers]
|
||||
|
||||
y_pos = np.arange(len(objects))
|
||||
|
||||
status.subplot.clear()
|
||||
status.subplot.bar(y_pos, yvalues, align='center', alpha=0.5)
|
||||
status.subplot.set_xticks(y_pos)
|
||||
status.subplot.set_xticklabels(tuple(objects))
|
||||
status.subplot.set_ylabel('Count')
|
||||
status.subplot.set_title('Answers')
|
||||
30
copycat/gui/primary.py
Normal file
30
copycat/gui/primary.py
Normal file
@ -0,0 +1,30 @@
|
||||
import tkinter as tk
|
||||
import tkinter.ttk as ttk
|
||||
|
||||
from tkinter import scrolledtext
|
||||
from tkinter import filedialog
|
||||
|
||||
from .control import Control
|
||||
from .gridframe import GridFrame
|
||||
|
||||
from .workspacecanvas import WorkspaceCanvas
|
||||
|
||||
class Primary(GridFrame):
|
||||
|
||||
def __init__(self, parent, *args, **kwargs):
|
||||
GridFrame.__init__(self, parent, *args, **kwargs)
|
||||
|
||||
self.canvas = WorkspaceCanvas(self)
|
||||
self.add(self.canvas, 0, 0, xspan=2)
|
||||
|
||||
self.control = Control(self)
|
||||
self.add(self.control, 0, 2)
|
||||
|
||||
GridFrame.configure(self)
|
||||
|
||||
def update(self, copycat):
|
||||
self.canvas.update(copycat)
|
||||
|
||||
def reset_with_strings(self, initial, modified, target):
|
||||
self.canvas.reset_with_strings(initial, modified, target)
|
||||
self.control.reset()
|
||||
66
copycat/gui/status.py
Normal file
66
copycat/gui/status.py
Normal file
@ -0,0 +1,66 @@
|
||||
import matplotlib
|
||||
matplotlib.use("TkAgg")
|
||||
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
|
||||
from matplotlib.figure import Figure
|
||||
|
||||
import tkinter as tk
|
||||
import tkinter.ttk as ttk
|
||||
|
||||
import time
|
||||
import matplotlib.animation as animation
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
plt.style.use('dark_background')
|
||||
|
||||
from .gridframe import GridFrame
|
||||
|
||||
class Plot(GridFrame):
|
||||
def __init__(self, parent, title):
|
||||
GridFrame.__init__(self, parent)
|
||||
self.status = Status()
|
||||
self.sframe = StatusFrame(self, self.status, title)
|
||||
self.add(self.sframe, 0, 0, xspan=2)
|
||||
|
||||
self.savebutton = ttk.Button(self, text='Save to path:', command=lambda : self.save())
|
||||
self.add(self.savebutton, 0, 1)
|
||||
|
||||
self.pathentry = ttk.Entry(self, style='EntryStyle.TEntry', textvariable='output/dist.png')
|
||||
self.add(self.pathentry, 1, 1)
|
||||
|
||||
def save(self):
|
||||
path = self.pathentry.get()
|
||||
if len(path) > 0:
|
||||
try:
|
||||
self.status.figure.savefig(path)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
class StatusFrame(ttk.Frame):
|
||||
def __init__(self, parent, status, title):
|
||||
ttk.Frame.__init__(self, parent)
|
||||
self.status = status
|
||||
|
||||
self.canvas = FigureCanvasTkAgg(status.figure, self)
|
||||
self.canvas.show()
|
||||
self.canvas.get_tk_widget().pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True)
|
||||
|
||||
self.animation = animation.FuncAnimation(status.figure, lambda i : status.update_plots(i), interval=1000)
|
||||
|
||||
class Status(object):
|
||||
def __init__(self):
|
||||
self.figure = Figure(figsize=(5,5), dpi=100)
|
||||
self.subplot = self.figure.add_subplot(111)
|
||||
self.x = []
|
||||
self.y = []
|
||||
|
||||
def modifier(status):
|
||||
with plt.style.context(('dark_background')):
|
||||
status.subplot.plot(status.x, status.y)
|
||||
|
||||
self.modifier = modifier
|
||||
self.update_plots(0)
|
||||
|
||||
def update_plots(self, i):
|
||||
self.subplot.clear()
|
||||
self.modifier(self)
|
||||
33
copycat/gui/style.py
Normal file
33
copycat/gui/style.py
Normal file
@ -0,0 +1,33 @@
|
||||
style_dict = dict(foreground='white',
|
||||
background='black')
|
||||
|
||||
map_options = dict(
|
||||
foreground=[('disabled', 'black'),
|
||||
('pressed', 'white'),
|
||||
('active', 'white')],
|
||||
background=[('disabled', 'black'),
|
||||
('pressed', '!focus', 'black'),
|
||||
('active', 'black')],
|
||||
highlightcolor=[('focus', 'black'),
|
||||
('!focus', 'black')])
|
||||
|
||||
def configure_style(style):
|
||||
style.configure('TButton', **style_dict)
|
||||
style.map('TButton', **map_options)
|
||||
style.configure('TLabel', **style_dict)
|
||||
#style.configure('TEntry', **style_dict)
|
||||
#style.map('TEntry', **map_options)
|
||||
|
||||
# A hack to change entry style
|
||||
style.element_create("plain.field", "from", "clam")
|
||||
style.layout("EntryStyle.TEntry",
|
||||
[('Entry.plain.field', {'children': [(
|
||||
'Entry.background', {'children': [(
|
||||
'Entry.padding', {'children': [(
|
||||
'Entry.textarea', {'sticky': 'nswe'})],
|
||||
'sticky': 'nswe'})], 'sticky': 'nswe'})],
|
||||
'border':'2', 'sticky': 'nswe'})])
|
||||
style.configure("EntryStyle.TEntry",
|
||||
background="black",
|
||||
foreground="white",
|
||||
fieldbackground="black")
|
||||
70
copycat/gui/workspacecanvas.py
Normal file
70
copycat/gui/workspacecanvas.py
Normal file
@ -0,0 +1,70 @@
|
||||
import tkinter as tk
|
||||
import tkinter.ttk as ttk
|
||||
|
||||
from .gridframe import GridFrame
|
||||
|
||||
font1Size = 32
|
||||
font1 = ('Helvetica', font1Size)
|
||||
|
||||
class WorkspaceCanvas(GridFrame):
|
||||
|
||||
def __init__(self, parent, *args, **kwargs):
|
||||
GridFrame.__init__(self, parent, *args, **kwargs)
|
||||
|
||||
self.chars = []
|
||||
|
||||
self.initial = ''
|
||||
self.modified = ''
|
||||
self.target = ''
|
||||
self.answer = ''
|
||||
|
||||
self.changed = False
|
||||
|
||||
self.canvas = tk.Canvas(self, background='black')
|
||||
#self.canvas['width'] = 1600
|
||||
self.add(self.canvas, 0, 0)
|
||||
|
||||
GridFrame.configure(self)
|
||||
|
||||
def update(self, copycat):
|
||||
answer = '' if copycat.workspace.rule is None else copycat.workspace.rule.buildTranslatedRule()
|
||||
if answer != self.answer:
|
||||
self.changed = True
|
||||
|
||||
if self.changed:
|
||||
self.canvas.delete('all')
|
||||
del self.chars[:]
|
||||
self.add_text()
|
||||
|
||||
def add_text(self):
|
||||
padding = 100
|
||||
|
||||
def add_sequences(sequences, x, y):
|
||||
for sequence in sequences:
|
||||
x += padding
|
||||
if sequence is None:
|
||||
sequence = ''
|
||||
for char in sequence:
|
||||
self.chars.append((char, (x, y)))
|
||||
self.canvas.create_text(x, y, text=char, anchor=tk.NW, font=font1, fill='white')
|
||||
x += font1Size
|
||||
return x, y
|
||||
|
||||
x = 0
|
||||
y = padding
|
||||
|
||||
add_sequences([self.initial, self.modified], x, y)
|
||||
|
||||
x = 0
|
||||
y += padding
|
||||
|
||||
add_sequences([self.target, self.answer], x, y)
|
||||
|
||||
def reset_with_strings(self, initial, modified, target):
|
||||
if initial != self.initial or \
|
||||
modified != self.modified or \
|
||||
target != self.target:
|
||||
self.changed = True
|
||||
self.initial = initial
|
||||
self.modified = modified
|
||||
self.target = target
|
||||
9
copycat/io.py
Normal file
9
copycat/io.py
Normal file
@ -0,0 +1,9 @@
|
||||
|
||||
def save_answers(answers, filename):
|
||||
answers = sorted(answers.items(), key=lambda kv : kv[1]['count'])
|
||||
keys = [k for k, v in answers]
|
||||
counts = [str(v['count']) for k, v in answers]
|
||||
with open(filename, 'w') as outfile:
|
||||
outfile.write(','.join(keys))
|
||||
outfile.write('\n')
|
||||
outfile.write(','.join(counts))
|
||||
54
copycat/io_README.md
Normal file
54
copycat/io_README.md
Normal file
@ -0,0 +1,54 @@
|
||||
# Input/Output System
|
||||
|
||||
## Overview
|
||||
The input/output system is a utility component of the Copycat architecture that handles file and data input/output operations. This system provides interfaces for reading and writing data to and from various sources.
|
||||
|
||||
## Key Features
|
||||
- File operations
|
||||
- Data parsing
|
||||
- Format conversion
|
||||
- Error handling
|
||||
- State management
|
||||
|
||||
## Operation Types
|
||||
1. **File Operations**
|
||||
- File reading
|
||||
- File writing
|
||||
- File management
|
||||
- Path handling
|
||||
|
||||
2. **Data Operations**
|
||||
- Data parsing
|
||||
- Format conversion
|
||||
- Data validation
|
||||
- Error handling
|
||||
|
||||
3. **Special Operations**
|
||||
- Configuration loading
|
||||
- Logging
|
||||
- Debug output
|
||||
- State persistence
|
||||
|
||||
## Usage
|
||||
I/O operations are performed through the I/O system:
|
||||
|
||||
```python
|
||||
# Read from a file
|
||||
data = io.read_file(file_path)
|
||||
|
||||
# Write to a file
|
||||
io.write_file(file_path, data)
|
||||
|
||||
# Parse data
|
||||
parsed = io.parse_data(data)
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
- Python 3.x
|
||||
- No external dependencies required
|
||||
|
||||
## Related Components
|
||||
- Workspace: Uses I/O for data loading
|
||||
- Statistics: Uses I/O for logging
|
||||
- Curses Reporter: Uses I/O for display
|
||||
- Problem: Uses I/O for problem loading
|
||||
49
copycat/letter.py
Normal file
49
copycat/letter.py
Normal file
@ -0,0 +1,49 @@
|
||||
from .workspaceObject import WorkspaceObject
|
||||
|
||||
|
||||
class Letter(WorkspaceObject):
|
||||
def __init__(self, string, position, length):
|
||||
WorkspaceObject.__init__(self, string)
|
||||
workspace = self.ctx.workspace
|
||||
workspace.objects += [self]
|
||||
string.objects += [self]
|
||||
self.leftIndex = position
|
||||
self.leftmost = self.leftIndex == 1
|
||||
self.rightIndex = position
|
||||
self.rightmost = self.rightIndex == length
|
||||
|
||||
def describe(self, position, length):
|
||||
slipnet = self.ctx.slipnet
|
||||
if length == 1:
|
||||
self.addDescription(slipnet.stringPositionCategory, slipnet.single)
|
||||
if self.leftmost:
|
||||
self.addDescription(slipnet.stringPositionCategory, slipnet.leftmost)
|
||||
if self.rightmost:
|
||||
self.addDescription(slipnet.stringPositionCategory, slipnet.rightmost)
|
||||
if position * 2 == length + 1:
|
||||
self.addDescription(slipnet.stringPositionCategory, slipnet.middle)
|
||||
|
||||
def __repr__(self):
|
||||
return '<Letter: %s>' % self.__str__()
|
||||
|
||||
def __str__(self):
|
||||
if not self.string:
|
||||
return ''
|
||||
i = self.leftIndex - 1
|
||||
if len(self.string) <= i:
|
||||
raise ValueError('len(self.string) <= self.leftIndex :: %d <= %d',
|
||||
len(self.string), self.leftIndex)
|
||||
return self.string[i]
|
||||
|
||||
def distinguishingDescriptor(self, descriptor):
|
||||
"""Whether no other object of the same type 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
|
||||
54
copycat/letter_README.md
Normal file
54
copycat/letter_README.md
Normal file
@ -0,0 +1,54 @@
|
||||
# Letter System
|
||||
|
||||
## Overview
|
||||
The letter system is a specialized component of the Copycat architecture that handles the representation and manipulation of individual letters and characters in the workspace. This system manages the properties and behaviors of letter objects in the analogical reasoning process.
|
||||
|
||||
## Key Features
|
||||
- Letter representation
|
||||
- Character properties
|
||||
- Letter manipulation
|
||||
- Pattern matching
|
||||
- State management
|
||||
|
||||
## Letter Types
|
||||
1. **Basic Letters**
|
||||
- Alphabetic characters
|
||||
- Numeric characters
|
||||
- Special characters
|
||||
- Whitespace
|
||||
|
||||
2. **Structured Letters**
|
||||
- Grouped letters
|
||||
- Bonded letters
|
||||
- Hierarchical letters
|
||||
- Pattern letters
|
||||
|
||||
3. **Special Letters**
|
||||
- Rule letters
|
||||
- Mapping letters
|
||||
- Context letters
|
||||
- Meta-letters
|
||||
|
||||
## Usage
|
||||
Letter operations are performed through the letter system:
|
||||
|
||||
```python
|
||||
# Create a letter
|
||||
letter = Letter(char, properties)
|
||||
|
||||
# Access letter properties
|
||||
char = letter.get_char()
|
||||
|
||||
# Modify letter state
|
||||
letter.set_properties(new_properties)
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
- Python 3.x
|
||||
- No external dependencies required
|
||||
|
||||
## Related Components
|
||||
- Workspace: Contains letter objects
|
||||
- WorkspaceObject: Base class for letters
|
||||
- Codelets: Operate on letters
|
||||
- Correspondence: Maps between letters
|
||||
20
copycat/plot.py
Normal file
20
copycat/plot.py
Normal file
@ -0,0 +1,20 @@
|
||||
import matplotlib.pyplot as plt; plt.rcdefaults()
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
|
||||
def plot_answers(answers, show=True, save=True, filename='distribution.png'):
|
||||
answers = sorted(answers.items(), key=lambda kv : kv[1]['count'])
|
||||
objects = [t[0] + ' (temp:{})'.format(round(t[1]['avgtemp'], 2)) for t in answers]
|
||||
yvalues = [t[1]['count'] for t in answers]
|
||||
|
||||
y_pos = np.arange(len(objects))
|
||||
|
||||
plt.bar(y_pos, yvalues, align='center', alpha=0.5)
|
||||
plt.xticks(y_pos, objects)
|
||||
plt.ylabel('Count')
|
||||
plt.title('Answers')
|
||||
if show:
|
||||
plt.show()
|
||||
if save:
|
||||
plt.savefig('output/{}'.format(filename))
|
||||
56
copycat/plot_README.md
Normal file
56
copycat/plot_README.md
Normal file
@ -0,0 +1,56 @@
|
||||
# Plot System
|
||||
|
||||
## Overview
|
||||
The plot system is a visualization component of the Copycat architecture that provides plotting and graphing capabilities for analyzing and displaying system data. This system helps in visualizing various metrics and relationships in the analogical reasoning process.
|
||||
|
||||
## Key Features
|
||||
- Data visualization
|
||||
- Graph generation
|
||||
- Metric plotting
|
||||
- Analysis display
|
||||
- State management
|
||||
|
||||
## Plot Types
|
||||
1. **Performance Plots**
|
||||
- Activation curves
|
||||
- Execution timelines
|
||||
- Success rates
|
||||
- Error distributions
|
||||
|
||||
2. **System Plots**
|
||||
- Object counts
|
||||
- Link distributions
|
||||
- State changes
|
||||
- Memory usage
|
||||
|
||||
3. **Analysis Plots**
|
||||
- Pattern frequencies
|
||||
- Relationship maps
|
||||
- Similarity matrices
|
||||
- Correlation graphs
|
||||
|
||||
## Usage
|
||||
Plots are generated through the plot system:
|
||||
|
||||
```python
|
||||
# Create a plot
|
||||
plot = Plot(data, plot_type)
|
||||
|
||||
# Configure plot
|
||||
plot.set_title("Title")
|
||||
plot.set_labels("X", "Y")
|
||||
|
||||
# Display plot
|
||||
plot.show()
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
- Python 3.x
|
||||
- matplotlib library
|
||||
- No other external dependencies required
|
||||
|
||||
## Related Components
|
||||
- Statistics: Provides data for plotting
|
||||
- Curses Reporter: Uses plots for display
|
||||
- Workspace: Provides object data
|
||||
- Slipnet: Provides activation data
|
||||
69
copycat/problem.py
Normal file
69
copycat/problem.py
Normal file
@ -0,0 +1,69 @@
|
||||
from .copycat import Copycat
|
||||
|
||||
from pprint import pprint
|
||||
|
||||
class Problem:
|
||||
def __init__(self, initial, modified, target, iterations, distributions=None, formulas=None):
|
||||
self.formulas = formulas
|
||||
if formulas is not None:
|
||||
assert hasattr(Copycat(), 'temperature')
|
||||
else:
|
||||
if hasattr(Copycat(), 'temperature'):
|
||||
self.formulas = set(Copycat().temperature.adj_formulas())
|
||||
print(self.formulas)
|
||||
self.initial = initial
|
||||
self.modified = modified
|
||||
self.target = target
|
||||
|
||||
self.iterations = iterations
|
||||
if distributions is None:
|
||||
self.distributions = self.solve()
|
||||
else:
|
||||
self.distributions = distributions
|
||||
print(self.formulas)
|
||||
|
||||
def test(self, comparison, expected=None):
|
||||
print('-' * 120)
|
||||
print('Testing copycat problem: {} : {} :: {} : _'.format(self.initial,
|
||||
self.modified,
|
||||
self.target))
|
||||
print('expected:')
|
||||
if expected is None:
|
||||
expected = self.distributions
|
||||
pprint(expected)
|
||||
|
||||
actual = self.solve()
|
||||
print('actual:')
|
||||
pprint(actual)
|
||||
comparison(actual, expected)
|
||||
print('-' * 120)
|
||||
|
||||
def solve(self):
|
||||
print('-' * 120)
|
||||
print('Testing copycat problem: {} : {} :: {} : _'.format(self.initial,
|
||||
self.modified,
|
||||
self.target))
|
||||
copycat = Copycat()
|
||||
answers = dict()
|
||||
if self.formulas == None:
|
||||
if hasattr(copycat, 'temperature'):
|
||||
formula = copycat.temperature.getAdj()
|
||||
else:
|
||||
formula = None
|
||||
answers[formula] = copycat.run(self.initial,
|
||||
self.modified,
|
||||
self.target,
|
||||
self.iterations)
|
||||
else:
|
||||
print(self.formulas)
|
||||
for formula in self.formulas:
|
||||
copycat.temperature.useAdj(formula)
|
||||
answers[formula] = copycat.run(self.initial,
|
||||
self.modified,
|
||||
self.target,
|
||||
self.iterations)
|
||||
print('Done with {}'.format(formula))
|
||||
return answers
|
||||
|
||||
def generate(self):
|
||||
self.distributions = self.solve()
|
||||
54
copycat/problem_README.md
Normal file
54
copycat/problem_README.md
Normal file
@ -0,0 +1,54 @@
|
||||
# Problem System
|
||||
|
||||
## Overview
|
||||
The problem system is a core component of the Copycat architecture that manages the representation and handling of analogical reasoning problems. This system defines the structure and properties of problems that the system attempts to solve.
|
||||
|
||||
## Key Features
|
||||
- Problem representation
|
||||
- Problem loading
|
||||
- Problem validation
|
||||
- Solution tracking
|
||||
- State management
|
||||
|
||||
## Problem Types
|
||||
1. **Basic Problems**
|
||||
- String problems
|
||||
- Pattern problems
|
||||
- Rule problems
|
||||
- Mapping problems
|
||||
|
||||
2. **Composite Problems**
|
||||
- Multi-step problems
|
||||
- Hierarchical problems
|
||||
- Network problems
|
||||
- Context problems
|
||||
|
||||
3. **Special Problems**
|
||||
- Test problems
|
||||
- Debug problems
|
||||
- Meta-problems
|
||||
- Derived problems
|
||||
|
||||
## Usage
|
||||
Problems are created and managed through the problem system:
|
||||
|
||||
```python
|
||||
# Create a problem
|
||||
problem = Problem(initial_state, target_state)
|
||||
|
||||
# Load a problem
|
||||
problem = Problem.load_from_file(file_path)
|
||||
|
||||
# Solve a problem
|
||||
solution = problem.solve()
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
- Python 3.x
|
||||
- No external dependencies required
|
||||
|
||||
## Related Components
|
||||
- Workspace: Contains problem state
|
||||
- Slipnet: Provides concepts for solving
|
||||
- Codelets: Operate on problems
|
||||
- Correspondence: Maps problem elements
|
||||
43
copycat/randomness.py
Normal file
43
copycat/randomness.py
Normal file
@ -0,0 +1,43 @@
|
||||
import bisect
|
||||
import math
|
||||
import random
|
||||
|
||||
|
||||
def accumulate(iterable):
|
||||
total = 0
|
||||
for v in iterable:
|
||||
total += v
|
||||
yield total
|
||||
|
||||
|
||||
class Randomness(object):
|
||||
def __init__(self, seed=None):
|
||||
self.rng = random.Random(seed)
|
||||
|
||||
def coinFlip(self, p=0.5):
|
||||
return self.rng.random() < p
|
||||
|
||||
def choice(self, seq):
|
||||
return self.rng.choice(seq)
|
||||
|
||||
def weighted_choice(self, seq, weights):
|
||||
if not seq:
|
||||
# Many callers rely on this behavior.
|
||||
return None
|
||||
else:
|
||||
cum_weights = list(accumulate(weights))
|
||||
total = cum_weights[-1]
|
||||
return seq[bisect.bisect_left(cum_weights, self.rng.random() * total)]
|
||||
|
||||
def weighted_greater_than(self, first, second):
|
||||
total = first + second
|
||||
if total == 0:
|
||||
return False
|
||||
return self.coinFlip(float(first) / total)
|
||||
|
||||
def sqrtBlur(self, value):
|
||||
# This is exceedingly dumb, but it matches the Java code.
|
||||
root = math.sqrt(value)
|
||||
if self.coinFlip():
|
||||
return value + root
|
||||
return value - root
|
||||
54
copycat/randomness_README.md
Normal file
54
copycat/randomness_README.md
Normal file
@ -0,0 +1,54 @@
|
||||
# Randomness System
|
||||
|
||||
## Overview
|
||||
The randomness system is a utility component of the Copycat architecture that provides controlled random number generation and probabilistic operations. This system ensures consistent and reproducible random behavior across the analogical reasoning process.
|
||||
|
||||
## Key Features
|
||||
- Random number generation
|
||||
- Probability distributions
|
||||
- State management
|
||||
- Seed control
|
||||
- Reproducibility
|
||||
|
||||
## Operation Types
|
||||
1. **Basic Operations**
|
||||
- Random number generation
|
||||
- Probability sampling
|
||||
- Distribution selection
|
||||
- State initialization
|
||||
|
||||
2. **Advanced Operations**
|
||||
- Weighted selection
|
||||
- Distribution mixing
|
||||
- State transitions
|
||||
- Pattern generation
|
||||
|
||||
3. **Special Operations**
|
||||
- Seed management
|
||||
- State persistence
|
||||
- Debug control
|
||||
- Test reproducibility
|
||||
|
||||
## Usage
|
||||
Random operations are performed through the randomness system:
|
||||
|
||||
```python
|
||||
# Generate a random number
|
||||
value = randomness.random()
|
||||
|
||||
# Sample from a distribution
|
||||
sample = randomness.sample(distribution)
|
||||
|
||||
# Set random seed
|
||||
randomness.set_seed(seed)
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
- Python 3.x
|
||||
- No external dependencies required
|
||||
|
||||
## Related Components
|
||||
- Codelets: Use randomness for selection
|
||||
- Temperature: Uses randomness for control
|
||||
- Statistics: Uses randomness for sampling
|
||||
- Workspace: Uses randomness for operations
|
||||
9
copycat/replacement.py
Normal file
9
copycat/replacement.py
Normal file
@ -0,0 +1,9 @@
|
||||
from .workspaceStructure import WorkspaceStructure
|
||||
|
||||
|
||||
class Replacement(WorkspaceStructure):
|
||||
def __init__(self, ctx, objectFromInitial, objectFromModified, relation):
|
||||
WorkspaceStructure.__init__(self, ctx)
|
||||
self.objectFromInitial = objectFromInitial
|
||||
self.objectFromModified = objectFromModified
|
||||
self.relation = relation
|
||||
54
copycat/replacement_README.md
Normal file
54
copycat/replacement_README.md
Normal file
@ -0,0 +1,54 @@
|
||||
# Replacement System
|
||||
|
||||
## Overview
|
||||
The replacement system is a utility component of the Copycat architecture that manages the substitution and replacement of objects and structures in the workspace. This system handles the transformation of elements during the analogical reasoning process.
|
||||
|
||||
## Key Features
|
||||
- Object replacement
|
||||
- Structure transformation
|
||||
- Pattern substitution
|
||||
- State management
|
||||
- History tracking
|
||||
|
||||
## Operation Types
|
||||
1. **Basic Operations**
|
||||
- Element replacement
|
||||
- Pattern substitution
|
||||
- Structure transformation
|
||||
- State updates
|
||||
|
||||
2. **Advanced Operations**
|
||||
- Chain replacements
|
||||
- Group transformations
|
||||
- Context updates
|
||||
- History tracking
|
||||
|
||||
3. **Special Operations**
|
||||
- Rule application
|
||||
- Mapping translation
|
||||
- Meta-replacements
|
||||
- Derived transformations
|
||||
|
||||
## Usage
|
||||
Replacements are performed through the replacement system:
|
||||
|
||||
```python
|
||||
# Replace an object
|
||||
new_obj = replacement.replace(old_obj, new_obj)
|
||||
|
||||
# Apply a transformation
|
||||
result = replacement.transform(obj, rule)
|
||||
|
||||
# Track changes
|
||||
history = replacement.get_history()
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
- Python 3.x
|
||||
- No external dependencies required
|
||||
|
||||
## Related Components
|
||||
- Workspace: Contains objects to replace
|
||||
- Rule: Provides replacement rules
|
||||
- Correspondence: Maps replacements
|
||||
- Codelets: Execute replacements
|
||||
149
copycat/rule.py
Normal file
149
copycat/rule.py
Normal file
@ -0,0 +1,149 @@
|
||||
import logging
|
||||
|
||||
|
||||
from .workspaceStructure import WorkspaceStructure
|
||||
from . import formulas
|
||||
|
||||
|
||||
class Rule(WorkspaceStructure):
|
||||
def __init__(self, ctx, facet, descriptor, category, relation):
|
||||
WorkspaceStructure.__init__(self, ctx)
|
||||
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):
|
||||
workspace = self.ctx.workspace
|
||||
if not (self.descriptor and self.relation):
|
||||
self.internalStrength = 50.0
|
||||
return
|
||||
averageDepth = (self.descriptor.conceptualDepth +
|
||||
self.relation.conceptualDepth) / 2.0
|
||||
averageDepth **= 1.1 # LSaldyt: This value (1.1) seems 100% contrived.
|
||||
# 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.described(slipnode):
|
||||
self.internalStrength = 0.0
|
||||
return
|
||||
sharedDescriptorTerm = 100.0
|
||||
conceptual_height = (100.0 - self.descriptor.conceptualDepth) / 10.0 # LSaldyt: 10?
|
||||
sharedDescriptorWeight = conceptual_height ** 1.4 # LSaldyt: 1.4 is also seemingly contrived
|
||||
depthDifference = 100.0 - abs(self.descriptor.conceptualDepth -
|
||||
self.relation.conceptualDepth)
|
||||
weights = ((depthDifference, 12), # LSaldyt: ???
|
||||
(averageDepth, 18), # ????
|
||||
(sharedDescriptorTerm, sharedDescriptorWeight)) # 12 and 18 can be reduced to 2 and 3, depending on sharedDescriptorWeight
|
||||
self.internalStrength = formulas.weightedAverage(weights)
|
||||
if self.internalStrength > 100.0: # LSaldyt: A better formula wouldn't need to do this.
|
||||
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):
|
||||
workspace = self.ctx.workspace
|
||||
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
|
||||
return any(m.initialDescriptor == self.descriptor
|
||||
for m in correspondence.conceptMappings)
|
||||
|
||||
def __changeString(self, string):
|
||||
slipnet = self.ctx.slipnet
|
||||
# applies the changes to self string ie. successor
|
||||
if self.facet == slipnet.length:
|
||||
if self.relation == slipnet.predecessor:
|
||||
return string[:-1]
|
||||
elif self.relation == slipnet.successor:
|
||||
# This seems to be happening at the wrong level of abstraction.
|
||||
# "Lengthening" is not an operation that makes sense on strings;
|
||||
# it makes sense only on *groups*, and here we've lost the
|
||||
# "groupiness" of this string. What gives?
|
||||
return string + string[0]
|
||||
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):
|
||||
workspace = self.ctx.workspace
|
||||
if not (self.descriptor and self.relation):
|
||||
return workspace.targetString
|
||||
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
|
||||
changeds = [o for o in workspace.target.objects if
|
||||
o.described(self.descriptor) and
|
||||
o.described(self.category)]
|
||||
if len(changeds) == 0:
|
||||
return workspace.targetString
|
||||
elif len(changeds) > 1:
|
||||
logging.info("More than one letter changed. Sorry, I can't solve problems like this right now.")
|
||||
return None
|
||||
else:
|
||||
changed = changeds[0]
|
||||
logging.debug('changed object = %s', changed)
|
||||
left = changed.leftIndex - 1
|
||||
right = changed.rightIndex
|
||||
s = workspace.targetString
|
||||
changed_middle = self.__changeString(s[left:right])
|
||||
if changed_middle is None:
|
||||
return None
|
||||
return s[:left] + changed_middle + s[right:]
|
||||
47
copycat/rule_README.md
Normal file
47
copycat/rule_README.md
Normal file
@ -0,0 +1,47 @@
|
||||
# README_rule.md
|
||||
|
||||
## Overview
|
||||
`rule.py` implements the Rule system, a key component of the Copycat system that manages the transformation rules used in analogical reasoning. It handles the creation, evaluation, and application of rules that describe how to transform strings based on their properties and relationships.
|
||||
|
||||
## Core Components
|
||||
- `Rule` class: Main class that represents a transformation rule
|
||||
- Rule evaluation system
|
||||
- Rule translation and application system
|
||||
|
||||
## Key Features
|
||||
- Defines transformation rules with facets, descriptors, categories, and relations
|
||||
- Evaluates rule strength based on multiple factors
|
||||
- Supports rule translation through concept slippages
|
||||
- Handles string transformations based on rules
|
||||
- Manages rule compatibility with correspondences
|
||||
|
||||
## Rule Components
|
||||
- `facet`: The aspect of the object to change (e.g., length)
|
||||
- `descriptor`: The property being changed
|
||||
- `category`: The type of object being changed
|
||||
- `relation`: The transformation to apply
|
||||
|
||||
## Main Methods
|
||||
- `updateInternalStrength()`: Calculate rule strength
|
||||
- `updateExternalStrength()`: Update external strength
|
||||
- `activateRuleDescriptions()`: Activate rule-related concepts
|
||||
- `buildTranslatedRule()`: Apply rule to target string
|
||||
- `incompatibleRuleCorrespondence()`: Check rule-correspondence compatibility
|
||||
- `ruleEqual()`: Compare rules for equality
|
||||
|
||||
## Rule Types
|
||||
- Length-based rules (predecessor, successor)
|
||||
- Character-based rules (predecessor, successor)
|
||||
- Category-based rules
|
||||
|
||||
## Dependencies
|
||||
- Requires `workspaceStructure` and `formulas` modules
|
||||
- Uses `logging` for debug output
|
||||
- Used by the main `copycat` module
|
||||
|
||||
## Notes
|
||||
- Rules are evaluated based on conceptual depth and descriptor sharing
|
||||
- Rule strength is calculated using weighted averages
|
||||
- Rules can be translated through concept slippages
|
||||
- The system supports both single-character and length-based transformations
|
||||
- Rules can be incompatible with certain correspondences
|
||||
4
copycat/sampleText.txt
Normal file
4
copycat/sampleText.txt
Normal file
@ -0,0 +1,4 @@
|
||||
1,2
|
||||
3,4
|
||||
7,7
|
||||
100,100
|
||||
27
copycat/sliplink.py
Normal file
27
copycat/sliplink.py
Normal file
@ -0,0 +1,27 @@
|
||||
|
||||
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
|
||||
54
copycat/sliplink_README.md
Normal file
54
copycat/sliplink_README.md
Normal file
@ -0,0 +1,54 @@
|
||||
# Sliplink System
|
||||
|
||||
## Overview
|
||||
The sliplink system is a core component of the Copycat architecture that manages the connections between nodes in the slipnet. This system handles the creation, modification, and traversal of links between conceptual nodes in the network.
|
||||
|
||||
## Key Features
|
||||
- Link representation
|
||||
- Connection management
|
||||
- Weight handling
|
||||
- State tracking
|
||||
- Event processing
|
||||
|
||||
## Link Types
|
||||
1. **Basic Links**
|
||||
- Category links
|
||||
- Property links
|
||||
- Instance links
|
||||
- Similarity links
|
||||
|
||||
2. **Structural Links**
|
||||
- Hierarchy links
|
||||
- Composition links
|
||||
- Association links
|
||||
- Pattern links
|
||||
|
||||
3. **Special Links**
|
||||
- Rule links
|
||||
- Context links
|
||||
- Meta-links
|
||||
- Derived links
|
||||
|
||||
## Usage
|
||||
Sliplinks are created and managed through the sliplink system:
|
||||
|
||||
```python
|
||||
# Create a sliplink
|
||||
link = Sliplink(source_node, target_node, weight)
|
||||
|
||||
# Access link properties
|
||||
weight = link.get_weight()
|
||||
|
||||
# Modify link state
|
||||
link.set_weight(new_weight)
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
- Python 3.x
|
||||
- No external dependencies required
|
||||
|
||||
## Related Components
|
||||
- Slipnet: Contains sliplinks
|
||||
- Slipnode: Connected by sliplinks
|
||||
- Codelets: Use sliplinks for traversal
|
||||
- Workspace: Uses links for relationships
|
||||
248
copycat/slipnet.py
Normal file
248
copycat/slipnet.py
Normal file
@ -0,0 +1,248 @@
|
||||
from .slipnode import Slipnode
|
||||
from .sliplink import Sliplink
|
||||
|
||||
|
||||
class Slipnet(object):
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
def __init__(self):
|
||||
self.__addInitialNodes()
|
||||
self.__addInitialLinks()
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.numberOfUpdates = 0
|
||||
for node in self.slipnodes:
|
||||
node.reset()
|
||||
for node in self.initiallyClampedSlipnodes:
|
||||
node.clampHigh()
|
||||
|
||||
def update(self, random):
|
||||
self.numberOfUpdates += 1
|
||||
if self.numberOfUpdates == 50:
|
||||
for node in self.initiallyClampedSlipnodes:
|
||||
node.unclamp()
|
||||
for node in self.slipnodes:
|
||||
node.update()
|
||||
for node in self.slipnodes:
|
||||
node.spread_activation()
|
||||
for node in self.slipnodes:
|
||||
node.addBuffer()
|
||||
node.jump(random)
|
||||
node.buffer = 0.0
|
||||
|
||||
def isDistinguishingDescriptor(self, descriptor):
|
||||
"""Whether no other object of the same type has the same descriptor"""
|
||||
if descriptor == self.letter:
|
||||
return False
|
||||
if descriptor == self.group:
|
||||
return False
|
||||
if descriptor in self.numbers:
|
||||
return False
|
||||
return True
|
||||
|
||||
def __addInitialNodes(self):
|
||||
# pylint: disable=too-many-statements
|
||||
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)
|
||||
|
||||
# some factors are considered "very relevant" a priori
|
||||
self.initiallyClampedSlipnodes = [
|
||||
self.letterCategory,
|
||||
self.stringPositionCategory,
|
||||
]
|
||||
|
||||
def __addInitialLinks(self):
|
||||
self.sliplinks = []
|
||||
self.__link_items_to_their_neighbors(self.letters)
|
||||
self.__link_items_to_their_neighbors(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)
|
||||
groups = [self.predecessorGroup, self.successorGroup, self.samenessGroup]
|
||||
for group in groups:
|
||||
self.__addNonSlipLink(group, self.length, length=95.0)
|
||||
opposites = [
|
||||
(self.first, self.last),
|
||||
(self.leftmost, self.rightmost),
|
||||
(self.left, self.right),
|
||||
(self.successor, self.predecessor),
|
||||
(self.successorGroup, self.predecessorGroup),
|
||||
]
|
||||
for a, b in opposites:
|
||||
self.__addOppositeLink(a, b)
|
||||
# properties
|
||||
self.__addPropertyLink(self.letters[0], self.first, 75.0)
|
||||
self.__addPropertyLink(self.letters[-1], self.last, 75.0)
|
||||
links = [
|
||||
# object categories
|
||||
(self.objectCategory, self.letter),
|
||||
(self.objectCategory, self.group),
|
||||
# string positions,
|
||||
(self.stringPositionCategory, self.leftmost),
|
||||
(self.stringPositionCategory, self.rightmost),
|
||||
(self.stringPositionCategory, self.middle),
|
||||
(self.stringPositionCategory, self.single),
|
||||
(self.stringPositionCategory, self.whole),
|
||||
# alphabetic positions,
|
||||
(self.alphabeticPositionCategory, self.first),
|
||||
(self.alphabeticPositionCategory, self.last),
|
||||
# direction categories,
|
||||
(self.directionCategory, self.left),
|
||||
(self.directionCategory, self.right),
|
||||
# bond categories,
|
||||
(self.bondCategory, self.predecessor),
|
||||
(self.bondCategory, self.successor),
|
||||
(self.bondCategory, self.sameness),
|
||||
# group categories
|
||||
(self.groupCategory, self.predecessorGroup),
|
||||
(self.groupCategory, self.successorGroup),
|
||||
(self.groupCategory, self.samenessGroup),
|
||||
# bond facets
|
||||
(self.bondFacet, self.letterCategory),
|
||||
(self.bondFacet, self.length),
|
||||
]
|
||||
for a, b in links:
|
||||
self.__addInstanceLink(a, b)
|
||||
# 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)
|
||||
# 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-neighbor, position-neighbor
|
||||
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=100.0):
|
||||
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(self, name, depth, length)
|
||||
self.slipnodes += [slipnode]
|
||||
return slipnode
|
||||
|
||||
def __link_items_to_their_neighbors(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
|
||||
51
copycat/slipnet_README.md
Normal file
51
copycat/slipnet_README.md
Normal file
@ -0,0 +1,51 @@
|
||||
# README_slipnet.md
|
||||
|
||||
## Overview
|
||||
`slipnet.py` implements the Slipnet, a key component of the Copycat system that represents the conceptual network of relationships between different concepts. It manages a network of nodes (concepts) and links (relationships) that are used in analogical reasoning.
|
||||
|
||||
## Core Components
|
||||
- `Slipnet` class: Main class that manages the conceptual network
|
||||
- Network of `Slipnode` objects representing concepts
|
||||
- Network of `Sliplink` objects representing relationships
|
||||
|
||||
## Key Features
|
||||
- Manages a network of conceptual nodes and their relationships
|
||||
- Handles activation spreading between nodes
|
||||
- Supports both slip and non-slip links
|
||||
- Implements various types of relationships:
|
||||
- Letter categories
|
||||
- String positions
|
||||
- Alphabetic positions
|
||||
- Directions
|
||||
- Bond types
|
||||
- Group types
|
||||
- Other relations
|
||||
|
||||
## Node Types
|
||||
- Letters (a-z)
|
||||
- Numbers (1-5)
|
||||
- String positions (leftmost, rightmost, middle, single, whole)
|
||||
- Alphabetic positions (first, last)
|
||||
- Directions (left, right)
|
||||
- Bond types (predecessor, successor, sameness)
|
||||
- Group types (predecessorGroup, successorGroup, samenessGroup)
|
||||
- Categories (letterCategory, stringPositionCategory, etc.)
|
||||
|
||||
## Link Types
|
||||
- Slip links (lateral connections)
|
||||
- Non-slip links (fixed connections)
|
||||
- Category links (hierarchical connections)
|
||||
- Instance links (specific examples)
|
||||
- Property links (attributes)
|
||||
- Opposite links (antonyms)
|
||||
|
||||
## Dependencies
|
||||
- Requires `slipnode` and `sliplink` modules
|
||||
- Used by the main `copycat` module
|
||||
|
||||
## Notes
|
||||
- The network is initialized with predefined nodes and links
|
||||
- Activation spreads through the network during reasoning
|
||||
- Some nodes are initially clamped to high activation
|
||||
- The network supports both hierarchical and lateral relationships
|
||||
- The system uses conceptual depth to determine link strengths
|
||||
140
copycat/slipnode.py
Normal file
140
copycat/slipnode.py
Normal file
@ -0,0 +1,140 @@
|
||||
import math
|
||||
|
||||
|
||||
def jump_threshold():
|
||||
return 55.0
|
||||
|
||||
|
||||
class Slipnode(object):
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
def __init__(self, slipnet, name, depth, length=0.0):
|
||||
self.slipnet = slipnet
|
||||
self.name = name
|
||||
self.conceptualDepth = depth
|
||||
self.intrinsicLinkLength = length
|
||||
self.shrunkLinkLength = length * 0.4
|
||||
|
||||
self.activation = 0.0
|
||||
self.buffer = 0.0
|
||||
self.clamped = False
|
||||
self.categoryLinks = []
|
||||
self.instanceLinks = []
|
||||
self.propertyLinks = []
|
||||
self.lateralSlipLinks = []
|
||||
self.lateralNonSlipLinks = []
|
||||
self.incomingLinks = []
|
||||
self.outgoingLinks = []
|
||||
self.codelets = []
|
||||
|
||||
def __repr__(self):
|
||||
return '<Slipnode: %s>' % 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 unclamped(self):
|
||||
return not self.clamped
|
||||
|
||||
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"""
|
||||
float_margin = 0.00001
|
||||
return self.activation > 100.0 - float_margin
|
||||
|
||||
def bondDegreeOfAssociation(self):
|
||||
linkLength = self.intrinsicLinkLength
|
||||
if 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 linked(self, other):
|
||||
"""Whether the other is among the outgoing links"""
|
||||
return any(l.points_at(other) for l in self.outgoingLinks)
|
||||
|
||||
def slipLinked(self, other):
|
||||
"""Whether the other is among the lateral links"""
|
||||
return any(l.points_at(other) for l in self.lateralSlipLinks)
|
||||
|
||||
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
|
||||
"""
|
||||
slipnet = self.slipnet
|
||||
if relation == slipnet.identity:
|
||||
return self
|
||||
destinations = [l.destination
|
||||
for l in self.outgoingLinks if l.label == relation]
|
||||
if destinations:
|
||||
return destinations[0]
|
||||
return None
|
||||
|
||||
def getBondCategory(self, destination):
|
||||
"""Return the label of the link between these nodes if it exists.
|
||||
|
||||
If it does not exist return None
|
||||
"""
|
||||
slipnet = self.slipnet
|
||||
if self == destination:
|
||||
return slipnet.identity
|
||||
else:
|
||||
for link in self.outgoingLinks:
|
||||
if link.destination == destination:
|
||||
return link.label
|
||||
return None
|
||||
|
||||
def update(self):
|
||||
self.oldActivation = self.activation
|
||||
self.buffer -= self.activation * (100.0 - self.conceptualDepth) / 100.0
|
||||
|
||||
def spread_activation(self):
|
||||
if self.fully_active():
|
||||
for link in self.outgoingLinks:
|
||||
link.spread_activation()
|
||||
|
||||
def addBuffer(self):
|
||||
if self.unclamped():
|
||||
self.activation += self.buffer
|
||||
self.activation = min(max(0, self.activation), 100)
|
||||
|
||||
def jump(self, random):
|
||||
if self.clamped or self.activation <= jump_threshold():
|
||||
return
|
||||
value = (self.activation / 100.0) ** 3
|
||||
if random.coinFlip(value):
|
||||
self.activation = 100.0
|
||||
|
||||
def get_name(self):
|
||||
if len(self.name) == 1:
|
||||
return self.name.upper()
|
||||
return self.name
|
||||
54
copycat/slipnode_README.md
Normal file
54
copycat/slipnode_README.md
Normal file
@ -0,0 +1,54 @@
|
||||
# Slipnode System
|
||||
|
||||
## Overview
|
||||
The slipnode system is a fundamental component of the Copycat architecture that implements the nodes in the slipnet. Slipnodes represent concepts and their relationships in the conceptual network, forming the basis for analogical reasoning.
|
||||
|
||||
## Key Features
|
||||
- Concept representation
|
||||
- Activation management
|
||||
- Link formation
|
||||
- State tracking
|
||||
- Event handling
|
||||
|
||||
## Node Types
|
||||
1. **Concept Nodes**
|
||||
- Letter concepts
|
||||
- Number concepts
|
||||
- Relationship concepts
|
||||
- Category concepts
|
||||
|
||||
2. **Structural Nodes**
|
||||
- Group concepts
|
||||
- Bond concepts
|
||||
- Hierarchy concepts
|
||||
- Pattern concepts
|
||||
|
||||
3. **Special Nodes**
|
||||
- Rule nodes
|
||||
- Mapping nodes
|
||||
- Context nodes
|
||||
- Meta-concept nodes
|
||||
|
||||
## Usage
|
||||
Slipnodes are created and managed through the slipnet system:
|
||||
|
||||
```python
|
||||
# Create a new slipnode
|
||||
node = Slipnode(name, initial_activation)
|
||||
|
||||
# Access node properties
|
||||
activation = node.get_activation()
|
||||
|
||||
# Modify node state
|
||||
node.set_activation(new_value)
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
- Python 3.x
|
||||
- No external dependencies required
|
||||
|
||||
## Related Components
|
||||
- Slipnet: Main container for slipnodes
|
||||
- Sliplinks: Connect slipnodes
|
||||
- Codelets: Operate on slipnodes
|
||||
- Workspace: Provides context for node activation
|
||||
128
copycat/statistics.py
Normal file
128
copycat/statistics.py
Normal file
@ -0,0 +1,128 @@
|
||||
from collections import defaultdict
|
||||
from pprint import pprint
|
||||
from math import log
|
||||
|
||||
# comparison values for n degrees freedom
|
||||
# These values are useable for both the chi^2 and G tests
|
||||
|
||||
_ptable = {
|
||||
1:3.841,
|
||||
2:5.991,
|
||||
3:7.815,
|
||||
4:9.488,
|
||||
5:11.071,
|
||||
6:12.592,
|
||||
7:14.067,
|
||||
8:15.507,
|
||||
9:16.919,
|
||||
10:18.307,
|
||||
11:19.7,
|
||||
12:21,
|
||||
13:22.4,
|
||||
14:23.7,
|
||||
15:25,
|
||||
16:26.3
|
||||
}
|
||||
|
||||
|
||||
_get_count = lambda k, d : d[k]['count'] if k in d else 0
|
||||
|
||||
def g_value(actual, expected):
|
||||
# G = 2 * sum(Oi * ln(Oi/Ei))
|
||||
answerKeys = set(list(actual.keys()) + list(expected.keys()))
|
||||
degreesFreedom = len(answerKeys)
|
||||
G = 0
|
||||
|
||||
for k in answerKeys:
|
||||
E = _get_count(k, expected)
|
||||
O = _get_count(k, actual)
|
||||
if E == 0:
|
||||
print(' Warning! Expected 0 counts of {}, but got {}'.format(k, O))
|
||||
elif O == 0:
|
||||
print(' Warning! O = {}'.format(O))
|
||||
else:
|
||||
G += O * log(O/E)
|
||||
G *= 2
|
||||
return degreesFreedom, G
|
||||
|
||||
def chi_value(actual, expected):
|
||||
answerKeys = set(list(actual.keys()) + list(expected.keys()))
|
||||
degreesFreedom = len(answerKeys)
|
||||
chiSquared = 0
|
||||
|
||||
for k in answerKeys:
|
||||
E = _get_count(k, expected)
|
||||
O = _get_count(k, actual)
|
||||
if E == 0:
|
||||
print(' Warning! Expected 0 counts of {}, but got {}'.format(k, O))
|
||||
else:
|
||||
chiSquared += (O - E) ** 2 / E
|
||||
return degreesFreedom, chiSquared
|
||||
|
||||
def probability_difference(actual, expected):
|
||||
actualC = 0
|
||||
expectedC = 0
|
||||
|
||||
for k in set(list(actual.keys()) + list(expected.keys())):
|
||||
expectedC += _get_count(k, expected)
|
||||
actualC += _get_count(k, actual)
|
||||
|
||||
p = 0
|
||||
|
||||
Et = 0
|
||||
Ot = 0
|
||||
|
||||
for k in set(list(actual.keys()) + list(expected.keys())):
|
||||
E = _get_count(k, expected)
|
||||
O = _get_count(k, actual)
|
||||
Ep = E / expectedC
|
||||
Op = O / actualC
|
||||
p += abs(Ep - Op)
|
||||
|
||||
p /= 2 # P is between 0 and 2 -> P is between 0 and 1
|
||||
|
||||
return p
|
||||
|
||||
def dist_test(actual, expected, calculation):
|
||||
df, p = calculation(actual, expected)
|
||||
if df not in _ptable:
|
||||
raise Exception('{} degrees of freedom does not have a corresponding chi squared value.' + \
|
||||
' Please look up the value and add it to the table in copycat/statistics.py'.format(df))
|
||||
return (p < _ptable[df])
|
||||
|
||||
def cross_formula_table(actualDict, expectedDict, calculation, probs=False):
|
||||
data = dict()
|
||||
for ka, actual in actualDict.items():
|
||||
for ke, expected in expectedDict.items():
|
||||
if probs:
|
||||
data[(ka, ke)] = probability_difference(actual, expected)
|
||||
else:
|
||||
data[(ka, ke)] = dist_test(actual, expected, calculation)
|
||||
return data
|
||||
|
||||
def cross_table(problemSets, calculation=g_value, probs=False):
|
||||
table = defaultdict(dict)
|
||||
for i, (a, problemSetA) in enumerate(problemSets):
|
||||
for b, problemSetB in problemSets[i + 1:]:
|
||||
for problemA in problemSetA:
|
||||
for problemB in problemSetB:
|
||||
if (problemA.initial == problemB.initial and
|
||||
problemA.modified == problemB.modified and
|
||||
problemA.target == problemB.target):
|
||||
answersA = problemA.distributions
|
||||
answersB = problemB.distributions
|
||||
table[(problemA.initial,
|
||||
problemA.modified,
|
||||
problemA.target)][(a, b)] = (
|
||||
cross_formula_table(
|
||||
answersA, answersB, calculation, probs))
|
||||
return table
|
||||
|
||||
def iso_chi_squared(actualDict, expectedDict):
|
||||
for key in expectedDict.keys():
|
||||
assert key in actualDict, 'The key {} was not tested'.format(key)
|
||||
actual = actualDict[key]
|
||||
expected = expectedDict[key]
|
||||
if not dist_test(actual, expected, g_value):
|
||||
raise Exception('Value of G higher than expected')
|
||||
|
||||
54
copycat/statistics_README.md
Normal file
54
copycat/statistics_README.md
Normal file
@ -0,0 +1,54 @@
|
||||
# Statistics System
|
||||
|
||||
## Overview
|
||||
The statistics system is a utility component of the Copycat architecture that collects and analyzes various metrics and statistics about the system's operation. This system helps in understanding and debugging the analogical reasoning process.
|
||||
|
||||
## Key Features
|
||||
- Performance metrics collection
|
||||
- Statistical analysis
|
||||
- Data aggregation
|
||||
- Reporting utilities
|
||||
- Debugging support
|
||||
|
||||
## Metric Types
|
||||
1. **Performance Metrics**
|
||||
- Codelet execution counts
|
||||
- Activation levels
|
||||
- Mapping strengths
|
||||
- Processing times
|
||||
|
||||
2. **System Metrics**
|
||||
- Memory usage
|
||||
- Object counts
|
||||
- Link counts
|
||||
- State changes
|
||||
|
||||
3. **Analysis Metrics**
|
||||
- Success rates
|
||||
- Error rates
|
||||
- Pattern frequencies
|
||||
- Relationship distributions
|
||||
|
||||
## Usage
|
||||
Statistics are collected and analyzed through the statistics system:
|
||||
|
||||
```python
|
||||
# Record a statistic
|
||||
statistics.record_metric('metric_name', value)
|
||||
|
||||
# Get statistical analysis
|
||||
analysis = statistics.analyze_metric('metric_name')
|
||||
|
||||
# Generate report
|
||||
report = statistics.generate_report()
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
- Python 3.x
|
||||
- No external dependencies required
|
||||
|
||||
## Related Components
|
||||
- Coderack: Provides execution metrics
|
||||
- Workspace: Provides object statistics
|
||||
- Slipnet: Provides activation statistics
|
||||
- Correspondence: Provides mapping statistics
|
||||
175
copycat/temperature.py
Normal file
175
copycat/temperature.py
Normal file
@ -0,0 +1,175 @@
|
||||
import math
|
||||
|
||||
# Alternate formulas for getAdjustedProbability
|
||||
|
||||
def _original(temp, prob):
|
||||
if prob == 0 or prob == 0.5 or temp == 0:
|
||||
return prob
|
||||
if prob < 0.5:
|
||||
return 1.0 - _original(temp, 1.0 - prob)
|
||||
coldness = 100.0 - temp
|
||||
a = math.sqrt(coldness)
|
||||
c = (10 - a) / 100
|
||||
f = (c + 1) * prob
|
||||
return max(f, 0.5)
|
||||
|
||||
def _entropy(temp, prob):
|
||||
if prob == 0 or prob == 0.5 or temp == 0:
|
||||
return prob
|
||||
if prob < 0.5:
|
||||
return 1.0 - _original(temp, 1.0 - prob)
|
||||
coldness = 100.0 - temp
|
||||
a = math.sqrt(coldness)
|
||||
c = (10 - a) / 100
|
||||
f = (c + 1) * prob
|
||||
return -f * math.log2(f)
|
||||
|
||||
def _weighted(temp, s, u):
|
||||
weighted = (temp / 100) * s + ((100 - temp) / 100) * u
|
||||
return weighted
|
||||
|
||||
def _weighted_inverse(temp, prob):
|
||||
iprob = 1 - prob
|
||||
return _weighted(temp, iprob, prob)
|
||||
|
||||
def _fifty_converge(temp, prob): # Uses .5 instead of 1-prob
|
||||
return _weighted(temp, .5, prob)
|
||||
|
||||
def _soft_curve(temp, prob): # Curves to the average of the (1-p) and .5
|
||||
return min(1, _weighted(temp, (1.5-prob)/2, prob))
|
||||
|
||||
def _weighted_soft_curve(temp, prob): # Curves to the weighted average of the (1-p) and .5
|
||||
weight = 100
|
||||
gamma = .5 # convergance value
|
||||
alpha = 1 # gamma weight
|
||||
beta = 3 # iprob weight
|
||||
curved = min(1, (temp / weight) * ((alpha * gamma + beta * (1 - prob)) / (alpha + beta)) + ((weight - temp) / weight) * prob)
|
||||
return curved
|
||||
|
||||
def _alt_fifty(temp, prob):
|
||||
s = .5
|
||||
u = prob ** 2 if prob < .5 else math.sqrt(prob)
|
||||
return _weighted(temp, s, u)
|
||||
|
||||
def _averaged_alt(temp, prob):
|
||||
s = (1.5 - prob)/2
|
||||
u = prob ** 2 if prob < .5 else math.sqrt(prob)
|
||||
return _weighted(temp, s, u)
|
||||
|
||||
|
||||
def _working_best(temp, prob):
|
||||
s = .5 # convergence
|
||||
r = 1.05 # power
|
||||
u = prob ** r if prob < .5 else prob ** (1/r)
|
||||
return _weighted(temp, s, u)
|
||||
|
||||
def _soft_best(temp, prob):
|
||||
s = .5 # convergence
|
||||
r = 1.05 # power
|
||||
u = prob ** r if prob < .5 else prob ** (1/r)
|
||||
return _weighted(temp, s, u)
|
||||
|
||||
def _parameterized_best(temp, prob):
|
||||
# (D$66/100)*($E$64*$B68 + $G$64*$F$64)/($E$64 + $G$64)+((100-D$66)/100)*IF($B68 > 0.5, $B68^(1/$H$64), $B68^$H$64)
|
||||
# (T/100) * (alpha * p + beta * .5) / (alpha + beta) + ((100 - T)/100) * IF(p > .5, p^(1/2), p^2)
|
||||
alpha = 5
|
||||
beta = 1
|
||||
s = .5
|
||||
s = (alpha * prob + beta * s) / (alpha + beta)
|
||||
r = 1.05
|
||||
u = prob ** r if prob < .5 else prob ** (1/r)
|
||||
return _weighted(temp, s, u)
|
||||
|
||||
def _meta(temp, prob):
|
||||
r = _weighted(temp, 1, 2) # Make r a function of temperature
|
||||
s = .5
|
||||
u = prob ** r if prob < .5 else prob ** (1/r)
|
||||
return _weighted(temp, s, u)
|
||||
|
||||
def _meta_parameterized(temp, prob):
|
||||
r = _weighted(temp, 1, 2) # Make r a function of temperature
|
||||
|
||||
alpha = 5
|
||||
beta = 1
|
||||
s = .5
|
||||
s = (alpha * prob + beta * s) / (alpha + beta)
|
||||
u = prob ** r if prob < .5 else prob ** (1/r)
|
||||
|
||||
return _weighted(temp, s, u)
|
||||
|
||||
def _none(temp, prob):
|
||||
return prob
|
||||
|
||||
class Temperature(object):
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
self.adjustmentType = 'inverse'
|
||||
self._adjustmentFormulas = {
|
||||
'original' : _original,
|
||||
'entropy' : _entropy,
|
||||
'inverse' : _weighted_inverse,
|
||||
'fifty_converge' : _fifty_converge,
|
||||
'soft' : _soft_curve,
|
||||
'weighted_soft' : _weighted_soft_curve,
|
||||
'alt_fifty' : _alt_fifty,
|
||||
'average_alt' : _averaged_alt,
|
||||
'best' : _working_best,
|
||||
'sbest' : _soft_best,
|
||||
'pbest' : _parameterized_best,
|
||||
'meta' : _meta,
|
||||
'pmeta' : _meta_parameterized,
|
||||
'none' : _none}
|
||||
self.diffs = 0
|
||||
self.ndiffs = 0
|
||||
|
||||
def reset(self):
|
||||
self.history = [100.0]
|
||||
self.actual_value = 100.0
|
||||
self.last_unclamped_value = 100.0
|
||||
self.clamped = True
|
||||
self.clampTime = 30
|
||||
|
||||
def update(self, value):
|
||||
self.last_unclamped_value = value
|
||||
if self.clamped:
|
||||
self.actual_value = 100.0
|
||||
else:
|
||||
self.history.append(value)
|
||||
self.actual_value = value
|
||||
|
||||
def clampUntil(self, when):
|
||||
self.clamped = True
|
||||
self.clampTime = when
|
||||
# but do not modify self.actual_value until someone calls update()
|
||||
|
||||
def tryUnclamp(self, currentTime):
|
||||
if self.clamped and currentTime >= self.clampTime:
|
||||
self.clamped = False
|
||||
|
||||
def value(self):
|
||||
return 100.0 if self.clamped else self.actual_value
|
||||
|
||||
def getAdjustedValue(self, value):
|
||||
return value ** (((100.0 - self.value()) / 30.0) + 0.5)
|
||||
|
||||
def getAdjustedProbability(self, value):
|
||||
temp = self.value()
|
||||
prob = value
|
||||
adjusted = self._adjustmentFormulas[self.adjustmentType](temp, prob)
|
||||
|
||||
self.diffs += abs(adjusted - prob)
|
||||
self.ndiffs += 1
|
||||
return adjusted
|
||||
|
||||
def getAverageDifference(self):
|
||||
return self.diffs / self.ndiffs
|
||||
|
||||
def useAdj(self, adj):
|
||||
print('Changing to adjustment formula {}'.format(adj))
|
||||
self.adjustmentType = adj
|
||||
|
||||
def getAdj(self):
|
||||
return self.adjustmentType
|
||||
|
||||
def adj_formulas(self):
|
||||
return self._adjustmentFormulas.keys()
|
||||
51
copycat/temperature_README.md
Normal file
51
copycat/temperature_README.md
Normal file
@ -0,0 +1,51 @@
|
||||
# README_temperature.md
|
||||
|
||||
## Overview
|
||||
`temperature.py` implements the Temperature system, a crucial component of the Copycat system that controls the balance between exploration and exploitation during analogical reasoning. It provides various formulas for adjusting probabilities based on the current temperature, which affects how the system makes decisions.
|
||||
|
||||
## Core Components
|
||||
- `Temperature` class: Main class that manages the temperature system
|
||||
- Multiple adjustment formulas for probability modification
|
||||
- Temperature history tracking
|
||||
|
||||
## Key Features
|
||||
- Manages temperature-based control of the reasoning process
|
||||
- Provides multiple formulas for probability adjustment
|
||||
- Supports temperature clamping and unclamping
|
||||
- Tracks temperature history and differences
|
||||
- Allows dynamic switching between adjustment formulas
|
||||
|
||||
## Adjustment Formulas
|
||||
- `original`: Basic temperature-based adjustment
|
||||
- `entropy`: Entropy-based adjustment
|
||||
- `inverse`: Inverse weighted adjustment
|
||||
- `fifty_converge`: Convergence to 0.5
|
||||
- `soft_curve`: Soft curve adjustment
|
||||
- `weighted_soft_curve`: Weighted soft curve
|
||||
- `alt_fifty`: Alternative fifty convergence
|
||||
- `average_alt`: Averaged alternative
|
||||
- `best`: Working best formula
|
||||
- `sbest`: Soft best formula
|
||||
- `pbest`: Parameterized best
|
||||
- `meta`: Meta-level adjustment
|
||||
- `pmeta`: Parameterized meta
|
||||
- `none`: No adjustment
|
||||
|
||||
## Main Methods
|
||||
- `update(value)`: Update temperature value
|
||||
- `clampUntil(when)`: Clamp temperature until specified time
|
||||
- `tryUnclamp(currentTime)`: Attempt to unclamp temperature
|
||||
- `value()`: Get current temperature
|
||||
- `getAdjustedProbability(value)`: Get temperature-adjusted probability
|
||||
- `useAdj(adj)`: Switch to different adjustment formula
|
||||
|
||||
## Dependencies
|
||||
- Uses `math` for mathematical operations
|
||||
- Used by the main `copycat` module
|
||||
|
||||
## Notes
|
||||
- Temperature starts at 100.0 and can be clamped
|
||||
- Lower temperatures encourage more conservative decisions
|
||||
- Higher temperatures allow more exploratory behavior
|
||||
- The system tracks average differences between adjusted and original probabilities
|
||||
- Different adjustment formulas can be used for different reasoning strategies
|
||||
195
copycat/workspace.py
Normal file
195
copycat/workspace.py
Normal file
@ -0,0 +1,195 @@
|
||||
"""Workspace module."""
|
||||
|
||||
|
||||
from . import formulas
|
||||
from .bond import Bond
|
||||
from .correspondence import Correspondence
|
||||
from .letter import Letter
|
||||
from .workspaceString import WorkspaceString
|
||||
|
||||
|
||||
def __adjustUnhappiness(values):
|
||||
result = sum(values) / 2
|
||||
if result > 100.0:
|
||||
result = 100.0
|
||||
return result
|
||||
|
||||
|
||||
class Workspace(object):
|
||||
def __init__(self, ctx):
|
||||
"""To initialize the workspace."""
|
||||
self.ctx = ctx
|
||||
self.totalUnhappiness = 0.0
|
||||
self.intraStringUnhappiness = 0.0
|
||||
self.interStringUnhappiness = 0.0
|
||||
|
||||
# LSaldyt: default initializations for GUI entry
|
||||
self.targetString = ''
|
||||
self.initialString = ''
|
||||
self.modifiedString = ''
|
||||
self.finalAnswer = None
|
||||
self.changedObject = None
|
||||
self.objects = []
|
||||
self.structures = []
|
||||
self.rule = None
|
||||
|
||||
def __repr__(self):
|
||||
return '<Workspace trying %s:%s::%s:?>' % (
|
||||
self.initialString, self.modifiedString, self.targetString,
|
||||
)
|
||||
|
||||
def resetWithStrings(self, initial, modified, target):
|
||||
self.targetString = target
|
||||
self.initialString = initial
|
||||
self.modifiedString = modified
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.finalAnswer = None
|
||||
self.changedObject = None
|
||||
self.objects = []
|
||||
self.structures = []
|
||||
self.rule = None # Only one rule? : LSaldyt
|
||||
self.initial = WorkspaceString(self.ctx, self.initialString)
|
||||
self.modified = WorkspaceString(self.ctx, self.modifiedString)
|
||||
self.target = WorkspaceString(self.ctx, self.targetString)
|
||||
|
||||
'''
|
||||
# TODO: Initial part of refactoring in this method
|
||||
def getAssessedUnhappiness(self, unhappiness):
|
||||
o.Unhappiness = __adjustUnhappiness(
|
||||
o.relativeImportance * o.Unhappiness
|
||||
for o in self.objects)
|
||||
pass
|
||||
'''
|
||||
|
||||
# TODO: Extract method?
|
||||
def assessUnhappiness(self):
|
||||
self.intraStringUnhappiness = __adjustUnhappiness(
|
||||
o.relativeImportance * o.intraStringUnhappiness
|
||||
for o in self.objects)
|
||||
self.interStringUnhappiness = __adjustUnhappiness(
|
||||
o.relativeImportance * o.interStringUnhappiness
|
||||
for o in self.objects)
|
||||
self.totalUnhappiness = __adjustUnhappiness(
|
||||
o.relativeImportance * o.totalUnhappiness
|
||||
for o in self.objects)
|
||||
|
||||
# TODO: these 3 methods seem to be the same... are they? If so, Extract method.
|
||||
def calculateIntraStringUnhappiness(self):
|
||||
value = sum(
|
||||
o.relativeImportance * o.intraStringUnhappiness
|
||||
for o in self.objects
|
||||
) / 2.0
|
||||
self.intraStringUnhappiness = min(value, 100.0)
|
||||
|
||||
def calculateInterStringUnhappiness(self):
|
||||
value = sum(
|
||||
o.relativeImportance * o.interStringUnhappiness
|
||||
for o in self.objects
|
||||
) / 2.0
|
||||
self.interStringUnhappiness = min(value, 100.0)
|
||||
|
||||
def calculateTotalUnhappiness(self):
|
||||
value = sum(
|
||||
o.relativeImportance * o.totalUnhappiness
|
||||
for o in self.objects
|
||||
) / 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()
|
||||
|
||||
# TODO: use entropy
|
||||
def getUpdatedTemperature(self):
|
||||
'''
|
||||
Calculation of global tolerance towards irrelevance
|
||||
'''
|
||||
self.calculateIntraStringUnhappiness()
|
||||
self.calculateInterStringUnhappiness()
|
||||
self.calculateTotalUnhappiness()
|
||||
if self.rule:
|
||||
self.rule.updateStrength()
|
||||
ruleWeakness = 100.0 - self.rule.totalStrength
|
||||
else:
|
||||
ruleWeakness = 100.0
|
||||
return formulas.weightedAverage((
|
||||
(self.totalUnhappiness, 0.8),
|
||||
(ruleWeakness, 0.2)
|
||||
))
|
||||
|
||||
def numberOfUnrelatedObjects(self):
|
||||
"""Computes the number of all objects in the workspace with >= 1 open bond slots."""
|
||||
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.leftBond and not o.leftmost) or
|
||||
(not o.rightBond and not o.rightmost)]
|
||||
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 unreplaced objects in the initial string."""
|
||||
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 uncorresponded objects in the initial string."""
|
||||
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."""
|
||||
return sum(1 for o in self.structures if isinstance(o, Bond))
|
||||
|
||||
def correspondences(self):
|
||||
return [s for s in self.structures if isinstance(s, Correspondence)]
|
||||
|
||||
def slippages(self):
|
||||
result = []
|
||||
if self.changedObject and self.changedObject.correspondence:
|
||||
result += self.changedObject.correspondence.conceptMappings
|
||||
for o in self.initial.objects:
|
||||
if o.correspondence:
|
||||
for mapping in o.correspondence.slippages():
|
||||
if not mapping.isNearlyContainedBy(result):
|
||||
result += [mapping]
|
||||
return result
|
||||
|
||||
def buildRule(self, rule):
|
||||
if self.rule is not None:
|
||||
self.structures.remove(self.rule)
|
||||
self.rule = rule
|
||||
self.structures += [rule]
|
||||
rule.activateRuleDescriptions()
|
||||
|
||||
def breakRule(self):
|
||||
if self.rule is not None:
|
||||
self.structures.remove(self.rule)
|
||||
self.rule = None
|
||||
|
||||
def buildDescriptions(self, objekt):
|
||||
for description in objekt.descriptions:
|
||||
description.descriptionType.buffer = 100.0
|
||||
description.descriptor.buffer = 100.0
|
||||
if description not in self.structures:
|
||||
self.structures += [description]
|
||||
38
copycat/workspaceFormulas.py
Normal file
38
copycat/workspaceFormulas.py
Normal file
@ -0,0 +1,38 @@
|
||||
|
||||
def __chooseObjectFromList(ctx, objects, attribute):
|
||||
# TODO: use entropy
|
||||
random = ctx.random
|
||||
temperature = ctx.temperature
|
||||
weights = [
|
||||
temperature.getAdjustedValue(
|
||||
getattr(o, attribute)
|
||||
)
|
||||
for o in objects
|
||||
]
|
||||
return random.weighted_choice(objects, weights)
|
||||
|
||||
|
||||
def chooseUnmodifiedObject(ctx, attribute, inObjects):
|
||||
workspace = ctx.workspace
|
||||
objects = [o for o in inObjects if o.string != workspace.modified]
|
||||
return __chooseObjectFromList(ctx, objects, attribute)
|
||||
|
||||
|
||||
def chooseNeighbor(ctx, source):
|
||||
workspace = ctx.workspace
|
||||
objects = [o for o in workspace.objects if o.beside(source)]
|
||||
return __chooseObjectFromList(ctx, objects, "intraStringSalience")
|
||||
|
||||
|
||||
def chooseDirectedNeighbor(ctx, source, direction):
|
||||
slipnet = ctx.slipnet
|
||||
workspace = ctx.workspace
|
||||
if direction == slipnet.left:
|
||||
objects = [o for o in workspace.objects
|
||||
if o.string == source.string
|
||||
and source.leftIndex == o.rightIndex + 1]
|
||||
else:
|
||||
objects = [o for o in workspace.objects
|
||||
if o.string == source.string
|
||||
and o.leftIndex == source.rightIndex + 1]
|
||||
return __chooseObjectFromList(ctx, objects, 'intraStringSalience')
|
||||
54
copycat/workspaceFormulas_README.md
Normal file
54
copycat/workspaceFormulas_README.md
Normal file
@ -0,0 +1,54 @@
|
||||
# Workspace Formulas System
|
||||
|
||||
## Overview
|
||||
The workspace formulas system is a utility component of the Copycat architecture that provides mathematical and logical formulas for workspace operations. This system implements various calculations and transformations used in the analogical reasoning process.
|
||||
|
||||
## Key Features
|
||||
- Mathematical formulas
|
||||
- Logical operations
|
||||
- Transformation rules
|
||||
- Calculation utilities
|
||||
- State management
|
||||
|
||||
## Formula Types
|
||||
1. **Mathematical Formulas**
|
||||
- Distance calculations
|
||||
- Similarity measures
|
||||
- Probability computations
|
||||
- Activation formulas
|
||||
|
||||
2. **Logical Formulas**
|
||||
- Boolean operations
|
||||
- Condition evaluations
|
||||
- Rule applications
|
||||
- Constraint checks
|
||||
|
||||
3. **Transformation Formulas**
|
||||
- Object transformations
|
||||
- State transitions
|
||||
- Value mappings
|
||||
- Structure modifications
|
||||
|
||||
## Usage
|
||||
Formulas are used throughout the workspace system:
|
||||
|
||||
```python
|
||||
# Apply a formula
|
||||
result = workspace_formulas.calculate_distance(obj1, obj2)
|
||||
|
||||
# Evaluate a condition
|
||||
is_valid = workspace_formulas.check_condition(condition)
|
||||
|
||||
# Transform an object
|
||||
transformed = workspace_formulas.apply_transformation(obj)
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
- Python 3.x
|
||||
- No external dependencies required
|
||||
|
||||
## Related Components
|
||||
- Workspace: Uses formulas for operations
|
||||
- WorkspaceObject: Provides objects for calculations
|
||||
- Codelets: Apply formulas in operations
|
||||
- Statistics: Uses formulas for metrics
|
||||
194
copycat/workspaceObject.py
Normal file
194
copycat/workspaceObject.py
Normal file
@ -0,0 +1,194 @@
|
||||
from .description import Description
|
||||
from .formulas import weightedAverage
|
||||
from .workspaceStructure import WorkspaceStructure
|
||||
|
||||
class WorkspaceObject(WorkspaceStructure):
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
def __init__(self, workspaceString):
|
||||
WorkspaceStructure.__init__(self, workspaceString.ctx)
|
||||
self.string = workspaceString
|
||||
self.descriptions = []
|
||||
self.bonds = []
|
||||
self.group = None
|
||||
self.changed = False
|
||||
self.correspondence = None
|
||||
self.rawImportance = 0.0
|
||||
self.relativeImportance = 0.0
|
||||
self.leftBond = None
|
||||
self.rightBond = None
|
||||
self.name = ''
|
||||
self.replacement = None
|
||||
self.rightIndex = 0
|
||||
self.leftIndex = 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)
|
||||
self.descriptions += [description]
|
||||
|
||||
def addDescriptions(self, descriptions):
|
||||
workspace = self.ctx.workspace
|
||||
copy = descriptions[:] # in case we add to our own descriptions
|
||||
for description in copy:
|
||||
if not self.containsDescription(description):
|
||||
self.addDescription(description.descriptionType,
|
||||
description.descriptor)
|
||||
workspace.buildDescriptions(self)
|
||||
|
||||
def __calculateIntraStringHappiness(self):
|
||||
if self.spansString():
|
||||
return 100.0
|
||||
if self.group:
|
||||
return self.group.totalStrength
|
||||
bondStrength = sum(bond.totalStrength for bond in self.bonds)
|
||||
return bondStrength / 6.0
|
||||
|
||||
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
|
||||
|
||||
averageHappiness = (intraStringHappiness + interStringHappiness) / 2
|
||||
self.totalUnhappiness = 100.0 - averageHappiness
|
||||
|
||||
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
|
||||
|
||||
def isWithin(self, other):
|
||||
return (self.leftIndex >= other.leftIndex and
|
||||
self.rightIndex <= other.rightIndex)
|
||||
|
||||
def isOutsideOf(self, other):
|
||||
return (self.leftIndex > other.rightIndex or
|
||||
self.rightIndex < other.leftIndex)
|
||||
|
||||
def relevantDescriptions(self):
|
||||
return [d for d in self.descriptions
|
||||
if d.descriptionType.fully_active()]
|
||||
|
||||
def getPossibleDescriptions(self, descriptionType):
|
||||
from .group import Group # gross, TODO FIXME
|
||||
slipnet = self.ctx.slipnet
|
||||
descriptions = []
|
||||
for link in descriptionType.instanceLinks:
|
||||
node = link.destination
|
||||
if node == slipnet.first and self.described(slipnet.letters[0]):
|
||||
descriptions += [node]
|
||||
if node == slipnet.last and self.described(slipnet.letters[-1]):
|
||||
descriptions += [node]
|
||||
for i, number in enumerate(slipnet.numbers, 1):
|
||||
if node == number and isinstance(self, Group):
|
||||
if len(self.objectList) == i:
|
||||
descriptions += [node]
|
||||
if node == slipnet.middle and self.middleObject():
|
||||
descriptions += [node]
|
||||
return descriptions
|
||||
|
||||
def containsDescription(self, sought):
|
||||
soughtType = sought.descriptionType
|
||||
soughtDescriptor = sought.descriptor
|
||||
for d in self.descriptions:
|
||||
if soughtType == d.descriptionType:
|
||||
if soughtDescriptor == d.descriptor:
|
||||
return True
|
||||
return False
|
||||
|
||||
def described(self, slipnode):
|
||||
return any(d.descriptor == slipnode for d in self.descriptions)
|
||||
|
||||
def middleObject(self):
|
||||
# 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.rightIndex == self.leftIndex - 1:
|
||||
objectOnMyLeftIsLeftmost = True
|
||||
if objekt.rightmost and objekt.leftIndex == self.rightIndex + 1:
|
||||
objectOnMyRightIsRightmost = True
|
||||
return objectOnMyRightIsRightmost and objectOnMyLeftIsLeftmost
|
||||
|
||||
def distinguishingDescriptor(self, descriptor):
|
||||
slipnet = self.ctx.slipnet
|
||||
return slipnet.isDistinguishingDescriptor(descriptor)
|
||||
|
||||
def relevantDistinguishingDescriptors(self):
|
||||
slipnet = self.ctx.slipnet
|
||||
return [d.descriptor
|
||||
for d in self.relevantDescriptions()
|
||||
if slipnet.isDistinguishingDescriptor(d.descriptor)]
|
||||
|
||||
def getDescriptor(self, descriptionType):
|
||||
"""The description attached to this object of the description type."""
|
||||
for description in self.descriptions:
|
||||
if description.descriptionType == descriptionType:
|
||||
return description.descriptor
|
||||
return None
|
||||
|
||||
def getDescriptionType(self, sought_description):
|
||||
"""The description_type attached to this object of that description"""
|
||||
for description in self.descriptions:
|
||||
if description.descriptor == sought_description:
|
||||
return description.descriptionType
|
||||
return None
|
||||
|
||||
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.leftIndex > self.rightIndex:
|
||||
return other.leftIndex - self.rightIndex
|
||||
if self.leftIndex > other.rightIndex:
|
||||
return self.leftIndex - other.rightIndex
|
||||
return 0
|
||||
|
||||
def letterSpan(self):
|
||||
return self.rightIndex - self.leftIndex + 1
|
||||
|
||||
def beside(self, other):
|
||||
if self.string != other.string:
|
||||
return False
|
||||
if self.leftIndex == other.rightIndex + 1:
|
||||
return True
|
||||
return other.leftIndex == self.rightIndex + 1
|
||||
62
copycat/workspaceObject_README.md
Normal file
62
copycat/workspaceObject_README.md
Normal file
@ -0,0 +1,62 @@
|
||||
# Workspace Object System
|
||||
|
||||
## Overview
|
||||
The workspace object system is a fundamental component of the Copycat architecture that manages the representation and manipulation of objects in the workspace. The system consists of several related files:
|
||||
|
||||
- `workspaceObject.py`: Core implementation of workspace objects
|
||||
- `workspaceString.py`: String-specific object implementations
|
||||
- `workspaceStructure.py`: Structural object implementations
|
||||
- `workspaceFormulas.py`: Formula and calculation utilities
|
||||
|
||||
## Workspace Objects
|
||||
Workspace objects are the basic building blocks of the workspace, representing:
|
||||
- Letters and characters
|
||||
- Groups and collections
|
||||
- Bonds and relationships
|
||||
- Structural elements
|
||||
|
||||
## Key Features
|
||||
- Object hierarchy and inheritance
|
||||
- Property management and access
|
||||
- Relationship tracking
|
||||
- State management
|
||||
- Event handling
|
||||
|
||||
## Object Types
|
||||
1. **Basic Objects**
|
||||
- Letters and characters
|
||||
- Numbers and symbols
|
||||
- Special markers
|
||||
|
||||
2. **Structural Objects**
|
||||
- Groups and collections
|
||||
- Bonds and connections
|
||||
- Hierarchical structures
|
||||
|
||||
3. **Composite Objects**
|
||||
- Formulas and expressions
|
||||
- Complex relationships
|
||||
- Derived objects
|
||||
|
||||
## Usage
|
||||
Workspace objects are created and managed through the workspace system:
|
||||
|
||||
```python
|
||||
# Create a new workspace object
|
||||
obj = WorkspaceObject(properties)
|
||||
|
||||
# Access object properties
|
||||
value = obj.get_property('property_name')
|
||||
|
||||
# Modify object state
|
||||
obj.set_property('property_name', new_value)
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
- Python 3.x
|
||||
- No external dependencies required
|
||||
|
||||
## Related Components
|
||||
- Workspace: Main container for workspace objects
|
||||
- Codelets: Operate on workspace objects
|
||||
- Slipnet: Provides conceptual knowledge for object interpretation
|
||||
62
copycat/workspaceString.py
Normal file
62
copycat/workspaceString.py
Normal file
@ -0,0 +1,62 @@
|
||||
from .group import Group
|
||||
from .letter import Letter
|
||||
|
||||
|
||||
class WorkspaceString(object):
|
||||
def __init__(self, ctx, s):
|
||||
slipnet = ctx.slipnet
|
||||
workspace = ctx.workspace
|
||||
self.ctx = ctx
|
||||
self.string = s
|
||||
self.bonds = []
|
||||
self.objects = []
|
||||
self.letters = []
|
||||
self.length = len(s)
|
||||
self.intraStringUnhappiness = 0.0
|
||||
|
||||
for position, c in enumerate(self.string.upper(), 1):
|
||||
value = ord(c) - ord('A')
|
||||
letter = Letter(self, position, self.length)
|
||||
letter.workspaceString = self
|
||||
letter.addDescription(slipnet.objectCategory, slipnet.letter)
|
||||
letter.addDescription(slipnet.letterCategory, slipnet.letters[value])
|
||||
letter.describe(position, self.length)
|
||||
workspace.buildDescriptions(letter)
|
||||
self.letters += [letter]
|
||||
|
||||
def __repr__(self):
|
||||
return '<WorkspaceString: %s>' % 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 __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:
|
||||
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):
|
||||
for objekt in self.objects:
|
||||
if isinstance(objekt, Group):
|
||||
if objekt.sameGroup(sought):
|
||||
return objekt
|
||||
return None
|
||||
54
copycat/workspaceString_README.md
Normal file
54
copycat/workspaceString_README.md
Normal file
@ -0,0 +1,54 @@
|
||||
# Workspace String System
|
||||
|
||||
## Overview
|
||||
The workspace string system is a specialized component of the Copycat architecture that handles string representations and operations in the workspace. This system manages the manipulation and analysis of string-based objects in the analogical reasoning process.
|
||||
|
||||
## Key Features
|
||||
- String representation
|
||||
- String manipulation
|
||||
- Pattern matching
|
||||
- String analysis
|
||||
- State management
|
||||
|
||||
## String Types
|
||||
1. **Basic Strings**
|
||||
- Character sequences
|
||||
- Word strings
|
||||
- Symbol strings
|
||||
- Special markers
|
||||
|
||||
2. **Structured Strings**
|
||||
- Grouped strings
|
||||
- Bonded strings
|
||||
- Hierarchical strings
|
||||
- Pattern strings
|
||||
|
||||
3. **Special Strings**
|
||||
- Rule strings
|
||||
- Mapping strings
|
||||
- Context strings
|
||||
- Meta-strings
|
||||
|
||||
## Usage
|
||||
String operations are performed through the workspace string system:
|
||||
|
||||
```python
|
||||
# Create a workspace string
|
||||
string = WorkspaceString(content)
|
||||
|
||||
# Access string properties
|
||||
length = string.get_length()
|
||||
|
||||
# Modify string state
|
||||
string.set_content(new_content)
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
- Python 3.x
|
||||
- No external dependencies required
|
||||
|
||||
## Related Components
|
||||
- Workspace: Contains string objects
|
||||
- WorkspaceObject: Base class for strings
|
||||
- Codelets: Operate on strings
|
||||
- Correspondence: Maps between strings
|
||||
35
copycat/workspaceStructure.py
Normal file
35
copycat/workspaceStructure.py
Normal file
@ -0,0 +1,35 @@
|
||||
from . import formulas
|
||||
|
||||
|
||||
class WorkspaceStructure(object):
|
||||
def __init__(self, ctx):
|
||||
self.ctx = ctx
|
||||
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 strength from internal and external strengths"""
|
||||
weights = ((self.internalStrength, self.internalStrength),
|
||||
(self.externalStrength, 100 - self.internalStrength))
|
||||
strength = formulas.weightedAverage(weights)
|
||||
self.totalStrength = strength
|
||||
|
||||
def totalWeakness(self):
|
||||
return 100 - self.totalStrength ** 0.95
|
||||
|
||||
def updateInternalStrength(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def updateExternalStrength(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def break_the_structure(self):
|
||||
"""Break this workspace structure (Bond, Correspondence, or Group)"""
|
||||
raise NotImplementedError()
|
||||
54
copycat/workspaceStructure_README.md
Normal file
54
copycat/workspaceStructure_README.md
Normal file
@ -0,0 +1,54 @@
|
||||
# Workspace Structure System
|
||||
|
||||
## Overview
|
||||
The workspace structure system is a fundamental component of the Copycat architecture that manages the structural organization of objects in the workspace. This system handles the creation and management of hierarchical and relational structures that form the basis of analogical reasoning.
|
||||
|
||||
## Key Features
|
||||
- Structure representation
|
||||
- Hierarchy management
|
||||
- Relationship tracking
|
||||
- Pattern recognition
|
||||
- State management
|
||||
|
||||
## Structure Types
|
||||
1. **Basic Structures**
|
||||
- Linear sequences
|
||||
- Hierarchical trees
|
||||
- Network graphs
|
||||
- Pattern structures
|
||||
|
||||
2. **Composite Structures**
|
||||
- Group structures
|
||||
- Bond structures
|
||||
- Rule structures
|
||||
- Mapping structures
|
||||
|
||||
3. **Special Structures**
|
||||
- Context structures
|
||||
- Meta-structures
|
||||
- Derived structures
|
||||
- Temporary structures
|
||||
|
||||
## Usage
|
||||
Structures are created and managed through the workspace structure system:
|
||||
|
||||
```python
|
||||
# Create a new structure
|
||||
structure = WorkspaceStructure(type, properties)
|
||||
|
||||
# Access structure properties
|
||||
children = structure.get_children()
|
||||
|
||||
# Modify structure state
|
||||
structure.add_child(child)
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
- Python 3.x
|
||||
- No external dependencies required
|
||||
|
||||
## Related Components
|
||||
- Workspace: Contains structures
|
||||
- WorkspaceObject: Base class for structures
|
||||
- Codelets: Operate on structures
|
||||
- Correspondence: Maps between structures
|
||||
42
copycat/workspace_README.md
Normal file
42
copycat/workspace_README.md
Normal file
@ -0,0 +1,42 @@
|
||||
# README_workspace.md
|
||||
|
||||
## Overview
|
||||
`workspace.py` implements the Workspace, a central component of the Copycat system that manages the current state of the analogical reasoning process. It maintains the strings being compared, their objects, structures, and the overall state of the reasoning process.
|
||||
|
||||
## Core Components
|
||||
- `Workspace` class: Main class that manages the reasoning workspace
|
||||
- `WorkspaceString` objects for initial, modified, and target strings
|
||||
- Collection of objects and structures (bonds, correspondences, etc.)
|
||||
|
||||
## Key Features
|
||||
- Manages the three strings involved in the analogy (initial, modified, target)
|
||||
- Tracks objects and their relationships
|
||||
- Maintains structures like bonds, correspondences, and rules
|
||||
- Calculates various unhappiness metrics
|
||||
- Updates object values and structure strengths
|
||||
- Manages the temperature-based control system
|
||||
|
||||
## Main Methods
|
||||
- `resetWithStrings(initial, modified, target)`: Initialize workspace with new strings
|
||||
- `updateEverything()`: Update all objects and structures
|
||||
- `getUpdatedTemperature()`: Calculate current temperature
|
||||
- `buildRule(rule)`: Add or replace the current rule
|
||||
- `breakRule()`: Remove the current rule
|
||||
- `buildDescriptions(objekt)`: Add descriptions to an object
|
||||
|
||||
## State Management
|
||||
- Tracks total, intra-string, and inter-string unhappiness
|
||||
- Maintains lists of objects and structures
|
||||
- Manages the current rule and final answer
|
||||
- Tracks changed objects and their correspondences
|
||||
|
||||
## Dependencies
|
||||
- Requires `formulas`, `bond`, `correspondence`, `letter`, and `workspaceString` modules
|
||||
- Used by the main `copycat` module
|
||||
|
||||
## Notes
|
||||
- The workspace is the central state container for the reasoning process
|
||||
- Unhappiness metrics guide the temperature-based control system
|
||||
- The system supports both rule-based and correspondence-based reasoning
|
||||
- Objects can have multiple descriptions and relationships
|
||||
- The workspace maintains the final answer when reasoning is complete
|
||||
53
cross_compare.py
Executable file
53
cross_compare.py
Executable file
@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import pickle
|
||||
|
||||
from pprint import pprint
|
||||
from collections import defaultdict
|
||||
|
||||
from copycat import Problem
|
||||
from copycat.statistics import cross_table
|
||||
|
||||
def compare_sets():
|
||||
pass
|
||||
|
||||
def main(args):
|
||||
branchProblemSets = dict()
|
||||
problemSets = []
|
||||
for filename in args:
|
||||
with open(filename, 'rb') as infile:
|
||||
pSet = pickle.load(infile)
|
||||
branchProblemSets[filename] = pSet
|
||||
problemSets.append((filename, pSet))
|
||||
crossTable = cross_table(problemSets, probs=True)
|
||||
key_sorted_items = lambda d : sorted(d.items(), key=lambda t:t[0])
|
||||
|
||||
tableItems = key_sorted_items(crossTable)
|
||||
assert len(tableItems) > 0, 'Empty table'
|
||||
|
||||
headKey, headSubDict = tableItems[0]
|
||||
# Create table and add headers
|
||||
table = [['source', 'compare', 'source formula', 'compare formula']]
|
||||
for key, _ in tableItems:
|
||||
problem = '{}:{}::{}:_'.format(*key)
|
||||
table[-1].append(problem)
|
||||
|
||||
# Arranged results in terms of copycat variants and formulas
|
||||
arranged = defaultdict(list)
|
||||
for key, subdict in tableItems:
|
||||
for subkey, subsubdict in key_sorted_items(subdict):
|
||||
for subsubkey, result in key_sorted_items(subsubdict):
|
||||
arranged[subkey + subsubkey].append((key, result))
|
||||
|
||||
# Add test results to table
|
||||
for key, results in arranged.items():
|
||||
table.append(list(map(str, [*key])))
|
||||
for _, result in results:
|
||||
table[-1].append(str(result))
|
||||
|
||||
with open('output/cross_compare.csv', 'w') as outfile:
|
||||
outfile.write('\n'.join(','.join(row) for row in table) + '\n')
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
37
cross_compare_README.md
Normal file
37
cross_compare_README.md
Normal file
@ -0,0 +1,37 @@
|
||||
# README_cross_compare.md
|
||||
|
||||
## Overview
|
||||
`cross_compare.py` is a utility script that performs cross-comparison analysis between different problem sets in the Copycat system. It reads pickled problem sets and generates a comparison table showing how different variants of the Copycat system perform across various problems.
|
||||
|
||||
## Usage
|
||||
Run the program from the terminal with the following command:
|
||||
```bash
|
||||
python cross_compare.py problem_set1.pkl problem_set2.pkl ...
|
||||
```
|
||||
|
||||
### Arguments
|
||||
- One or more pickled problem set files (`.pkl` files) to compare
|
||||
|
||||
## Output
|
||||
The script generates a CSV file at `output/cross_compare.csv` containing:
|
||||
- A comparison table of different Copycat variants
|
||||
- Problem formulas and their results
|
||||
- Cross-comparison metrics between different problem sets
|
||||
|
||||
## Features
|
||||
- Reads multiple pickled problem sets
|
||||
- Generates a cross-comparison table
|
||||
- Organizes results by Copycat variants and formulas
|
||||
- Exports results to CSV format
|
||||
|
||||
## Dependencies
|
||||
- Requires the `copycat` module
|
||||
- Uses `pickle` for reading problem sets
|
||||
- Uses `collections.defaultdict` for data organization
|
||||
- Uses `pprint` for pretty printing (though not actively used in the current code)
|
||||
|
||||
## File Structure
|
||||
The output CSV file contains:
|
||||
1. Headers: source, compare, source formula, compare formula
|
||||
2. Problem descriptions in the format `A:B::C:_`
|
||||
3. Results for each combination of variants and formulas
|
||||
36
curses_main.py
Executable file
36
curses_main.py
Executable file
@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import curses
|
||||
import logging
|
||||
|
||||
from copycat import Copycat
|
||||
from copycat.curses_reporter import CursesReporter
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logging.basicConfig(level=logging.INFO, format='%(message)s', filename='./copycat.log', filemode='w')
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--focus-on-slipnet', action='store_true', help='Show the slipnet and coderack, instead of the workspace.')
|
||||
parser.add_argument('--fps', type=float, default=None, help='Aim for this many frames per second.')
|
||||
parser.add_argument('--seed', type=int, default=None, help='Provide a deterministic seed for the RNG.')
|
||||
parser.add_argument('initial', type=str, help='A...')
|
||||
parser.add_argument('modified', type=str, help='...is to B...')
|
||||
parser.add_argument('target', type=str, help='...as C is to... what?')
|
||||
options = parser.parse_args()
|
||||
|
||||
try:
|
||||
window = curses.initscr()
|
||||
copycat = Copycat(
|
||||
reporter=CursesReporter(
|
||||
window,
|
||||
focus_on_slipnet=options.focus_on_slipnet,
|
||||
fps_goal=options.fps,
|
||||
),
|
||||
rng_seed=options.seed,
|
||||
)
|
||||
copycat.run_forever(options.initial, options.modified, options.target)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
curses.endwin()
|
||||
36
curses_main_README.md
Normal file
36
curses_main_README.md
Normal file
@ -0,0 +1,36 @@
|
||||
# README_curses_main.md
|
||||
|
||||
## Overview
|
||||
`curses_main.py` is a terminal-based interface for the Copycat program that provides a visual representation of the analogical reasoning process using the curses library. It offers a real-time view of the system's operation, including the workspace, slipnet, and coderack.
|
||||
|
||||
## Usage
|
||||
Run the program from the terminal with the following command:
|
||||
```bash
|
||||
python curses_main.py abc abd ppqqrr
|
||||
```
|
||||
|
||||
### Arguments
|
||||
- `initial`: The first string in the analogy (e.g., "abc")
|
||||
- `modified`: The second string showing the transformation (e.g., "abd")
|
||||
- `target`: The third string to be transformed (e.g., "ppqqrr")
|
||||
- `--focus-on-slipnet` (optional): Show the slipnet and coderack instead of the workspace
|
||||
- `--fps` (optional): Target frames per second for the display
|
||||
- `--seed` (optional): Provide a deterministic seed for the random number generator
|
||||
|
||||
## Features
|
||||
- Interactive terminal-based interface
|
||||
- Real-time visualization of the Copycat system's operation
|
||||
- Option to focus on different components (workspace or slipnet/coderack)
|
||||
- Configurable display speed
|
||||
- Detailed logging to `./copycat.log`
|
||||
|
||||
## Dependencies
|
||||
- Requires the `copycat` module
|
||||
- Uses `curses` for terminal-based interface
|
||||
- Uses `argparse` for command-line argument parsing
|
||||
- Uses `logging` for output logging
|
||||
|
||||
## Notes
|
||||
- The program can be interrupted with Ctrl+C
|
||||
- The display is automatically cleaned up when the program exits
|
||||
- The interface provides a more detailed view of the system's operation compared to the standard command-line interface
|
||||
BIN
distributions/.adj-tests
Normal file
BIN
distributions/.adj-tests
Normal file
Binary file not shown.
BIN
distributions/.legacy
Normal file
BIN
distributions/.legacy
Normal file
Binary file not shown.
BIN
distributions/.no-adj
Normal file
BIN
distributions/.no-adj
Normal file
Binary file not shown.
BIN
distributions/.no-fizzle
Normal file
BIN
distributions/.no-fizzle
Normal file
Binary file not shown.
BIN
distributions/.no-prob-adj
Normal file
BIN
distributions/.no-prob-adj
Normal file
Binary file not shown.
BIN
distributions/.nuke-temp
Normal file
BIN
distributions/.nuke-temp
Normal file
Binary file not shown.
BIN
distributions/.soft-remove
Normal file
BIN
distributions/.soft-remove
Normal file
Binary file not shown.
28
gui.py
Executable file
28
gui.py
Executable file
@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
from copycat import Copycat, Reporter
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
plt.style.use('dark_background')
|
||||
|
||||
class SimpleReporter(Reporter):
|
||||
def report_answer(self, answer):
|
||||
print('Answered %s (time %d, final temperature %.1f)' % (
|
||||
answer['answer'], answer['time'], answer['temp'],
|
||||
))
|
||||
|
||||
def main():
|
||||
logging.basicConfig(level=logging.INFO, format='%(message)s', filename='./output/copycat.log', filemode='w')
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--seed', type=int, default=None, help='Provide a deterministic seed for the RNG.')
|
||||
options = parser.parse_args()
|
||||
|
||||
copycat = Copycat(reporter=SimpleReporter(), rng_seed=options.seed, gui=True)
|
||||
copycat.runGUI()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
31
gui_README.md
Normal file
31
gui_README.md
Normal file
@ -0,0 +1,31 @@
|
||||
# README_gui.md
|
||||
|
||||
## Overview
|
||||
`gui.py` is a graphical user interface implementation for the Copycat program. It provides a visual interface for running the analogical reasoning system, using matplotlib for visualization with a dark background theme.
|
||||
|
||||
## Usage
|
||||
Run the program from the terminal with the following command:
|
||||
```bash
|
||||
python gui.py
|
||||
```
|
||||
|
||||
### Arguments
|
||||
- `--seed` (optional): Provide a deterministic seed for the random number generator
|
||||
|
||||
## Features
|
||||
- Graphical user interface for the Copycat system
|
||||
- Dark background theme for better visibility
|
||||
- Real-time visualization of the system's operation
|
||||
- Detailed logging to `./output/copycat.log`
|
||||
|
||||
## Dependencies
|
||||
- Requires the `copycat` module
|
||||
- Uses `matplotlib` for visualization
|
||||
- Uses `argparse` for command-line argument parsing
|
||||
- Uses `logging` for output logging
|
||||
|
||||
## Notes
|
||||
- The GUI provides a more user-friendly interface compared to the command-line version
|
||||
- Results are displayed both in the GUI and printed to the console
|
||||
- The interface includes temperature and time information for each answer
|
||||
- The dark background theme is optimized for better visibility of the visualization
|
||||
0
input/.placeholder
Normal file
0
input/.placeholder
Normal file
9
input/problems.csv
Normal file
9
input/problems.csv
Normal file
@ -0,0 +1,9 @@
|
||||
abc,abd,ijk
|
||||
aabc,aabd,ijkk
|
||||
abc,abd,kji
|
||||
abc,abd,mrrjjj
|
||||
abc,abd,rssttt
|
||||
abc,abd,xyz
|
||||
abc,abd,ijjkkk
|
||||
rst,rsu,xyz
|
||||
abc,abd,xyyzzz
|
||||
|
4
input/reduced_problems.csv
Normal file
4
input/reduced_problems.csv
Normal file
@ -0,0 +1,4 @@
|
||||
abc,abd,ijk
|
||||
aabc,aabd,ijkk
|
||||
abc,abd,xyz
|
||||
abc,abd,ijjkkk
|
||||
|
75
main.py
Executable file
75
main.py
Executable file
@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Main Copycat program.
|
||||
|
||||
To run it, type at the terminal:
|
||||
|
||||
> python main.py abc abd ppqqrr --interations 10
|
||||
|
||||
The script takes three to five arguments. The first two are a pair of strings
|
||||
with some change, for example "abc" and "abd". The third is a string which the
|
||||
script should try to change analogously. The fourth (which defaults to "1") is
|
||||
a number of iterations. One can also specify a defined seed value for the
|
||||
random number generator.
|
||||
|
||||
This instruction produces output such as:
|
||||
|
||||
iiijjjlll: 670 (avg time 1108.5, avg temp 23.6)
|
||||
iiijjjd: 2 (avg time 1156.0, avg temp 35.0)
|
||||
iiijjjkkl: 315 (avg time 1194.4, avg temp 35.5)
|
||||
iiijjjkll: 8 (avg time 2096.8, avg temp 44.1)
|
||||
iiijjjkkd: 5 (avg time 837.2, avg temp 48.0)
|
||||
|
||||
wyz: 5 (avg time 2275.2, avg temp 14.9)
|
||||
xyd: 982 (avg time 2794.4, avg temp 17.5)
|
||||
yyz: 7 (avg time 2731.9, avg temp 25.1)
|
||||
dyz: 2 (avg time 3320.0, avg temp 27.1)
|
||||
xyy: 2 (avg time 4084.5, avg temp 31.1)
|
||||
xyz: 2 (avg time 1873.5, avg temp 52.1)
|
||||
|
||||
The first number indicates how many times Copycat chose that string as its
|
||||
answer; higher means "more obvious". The last number indicates the average
|
||||
final temperature of the workspace; lower means "more elegant".
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
from copycat import Copycat, Reporter, plot_answers, save_answers
|
||||
|
||||
class SimpleReporter(Reporter):
|
||||
"""Reports results from a single run."""
|
||||
|
||||
def report_answer(self, answer):
|
||||
"""Self-explanatory code."""
|
||||
print('Answered %s (time %d, final temperature %.1f)' % (
|
||||
answer['answer'], answer['time'], answer['temp'],
|
||||
))
|
||||
|
||||
|
||||
def main():
|
||||
"""Program's main entrance point. Self-explanatory code."""
|
||||
logging.basicConfig(level=logging.INFO, format='%(message)s', filename='./output/copycat.log', filemode='w')
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--seed', type=int, default=None, help='Provide a deterministic seed for the RNG.')
|
||||
parser.add_argument('--iterations', type=int, default=1, help='Run the given case this many times.')
|
||||
parser.add_argument('--plot', action='store_true', help='Plot a bar graph of answer distribution')
|
||||
parser.add_argument('--noshow', action='store_true', help='Don\'t display bar graph at end of run')
|
||||
parser.add_argument('initial', type=str, help='A...')
|
||||
parser.add_argument('modified', type=str, help='...is to B...')
|
||||
parser.add_argument('target', type=str, help='...as C is to... what?')
|
||||
options = parser.parse_args()
|
||||
|
||||
copycat = Copycat(reporter=SimpleReporter(), rng_seed=options.seed)
|
||||
answers = copycat.run(options.initial, options.modified, options.target, options.iterations)
|
||||
|
||||
for answer, d in sorted(iter(answers.items()), key=lambda kv: kv[1]['avgtemp']):
|
||||
print('%s: %d (avg time %.1f, avg temp %.1f)' % (answer, d['count'], d['avgtime'], d['avgtemp']))
|
||||
|
||||
if options.plot:
|
||||
plot_answers(answers, show=not options.noshow)
|
||||
save_answers(answers, 'output/answers.csv')
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
41
main_README.md
Normal file
41
main_README.md
Normal file
@ -0,0 +1,41 @@
|
||||
# README_main.md
|
||||
|
||||
## Overview
|
||||
`main.py` is the primary entry point for the Copycat program, which implements an analogical reasoning system. The program takes three strings as input and attempts to find an analogous transformation between them.
|
||||
|
||||
## Usage
|
||||
Run the program from the terminal with the following command:
|
||||
```bash
|
||||
python main.py abc abd ppqqrr --iterations 10
|
||||
```
|
||||
|
||||
### Arguments
|
||||
- `initial`: The first string in the analogy (e.g., "abc")
|
||||
- `modified`: The second string showing the transformation (e.g., "abd")
|
||||
- `target`: The third string to be transformed (e.g., "ppqqrr")
|
||||
- `--iterations` (optional): Number of times to run the case (default: 1)
|
||||
- `--seed` (optional): Provide a deterministic seed for the random number generator
|
||||
- `--plot` (optional): Generate a bar graph of answer distribution
|
||||
- `--noshow` (optional): Don't display the bar graph at the end of the run
|
||||
|
||||
## Output
|
||||
The program produces output in the following format:
|
||||
```
|
||||
iiijjjlll: 670 (avg time 1108.5, avg temp 23.6)
|
||||
iiijjjd: 2 (avg time 1156.0, avg temp 35.0)
|
||||
...
|
||||
```
|
||||
Where:
|
||||
- The first number indicates how many times Copycat chose that string as its answer (higher means "more obvious")
|
||||
- The last number indicates the average final temperature of the workspace (lower means "more elegant")
|
||||
|
||||
## Features
|
||||
- Implements the Copycat analogical reasoning system
|
||||
- Provides detailed logging to `./output/copycat.log`
|
||||
- Can generate visualizations of answer distributions
|
||||
- Saves results to `output/answers.csv`
|
||||
|
||||
## Dependencies
|
||||
- Requires the `copycat` module
|
||||
- Uses `argparse` for command-line argument parsing
|
||||
- Uses `logging` for output logging
|
||||
0
output/.placeholder
Normal file
0
output/.placeholder
Normal file
12
papers/Makefile
Normal file
12
papers/Makefile
Normal file
@ -0,0 +1,12 @@
|
||||
|
||||
all:
|
||||
make draft
|
||||
make clean
|
||||
|
||||
draft:
|
||||
pdflatex draft.tex
|
||||
biber draft
|
||||
pdflatex draft.tex
|
||||
|
||||
clean:
|
||||
rm *.out *.log *.xml *.bbl *.bcf *.blg *.aux
|
||||
356
papers/draft.tex
Normal file
356
papers/draft.tex
Normal file
@ -0,0 +1,356 @@
|
||||
\documentclass[a4paper]{article}
|
||||
|
||||
%% Sets page size and margins
|
||||
\usepackage[a4paper,top=3cm,bottom=2cm,left=3cm,right=3cm,marginparwidth=1.75cm]{geometry}
|
||||
|
||||
%% Useful packages
|
||||
\usepackage{listings}
|
||||
\usepackage{amsmath}
|
||||
\usepackage{pdfpages}
|
||||
\usepackage{graphicx}
|
||||
\usepackage{indentfirst} %% Personal taste of LSaldyt
|
||||
|
||||
\usepackage[utf8]{inputenc}
|
||||
\usepackage[english]{babel}
|
||||
|
||||
\usepackage[backend=biber]{biblatex}
|
||||
\addbibresource{sources.bib}
|
||||
|
||||
\usepackage[colorinlistoftodos]{todonotes}
|
||||
\usepackage[colorlinks=true, allcolors=blue]{hyperref}
|
||||
|
||||
|
||||
\definecolor{lightgrey}{rgb}{0.9, 0.9, 0.9}
|
||||
\lstset{ %
|
||||
backgroundcolor=\color{lightgrey}}
|
||||
|
||||
\title{Distributed Behavior in a Fluid Analogy Architecture}
|
||||
\author{Lucas Saldyt, Alexandre Linhares}
|
||||
|
||||
\begin{document}
|
||||
\maketitle
|
||||
|
||||
\begin{abstract}
|
||||
This project focuses on effectively simulating intelligent processes behind fluid analogy-making through increasingly distributed decision-making.
|
||||
In the process, it discusses creating an effective scientific framework for fluid analogy architectures.
|
||||
This draft assumes extensive knowledge with the Copycat software, which was pioneered by Melanie Mitchell \cite{analogyasperception}.
|
||||
A humanistic search algorithm, the Parallel Terraced Scan, is altered and tested.
|
||||
Originally, this search algorithm contains a centralizing variable, called \emph{temperature}.
|
||||
This paper investigates the influence of this centralizing variable by modifying, testing, and eventually removing all code related to it.
|
||||
In this process, several variants of the copycat software are created.
|
||||
The produced answer distributions of each resulting branch of the copycat software were then cross-compared with a Pearson's $\chi^2$ distribution test.
|
||||
This paper draft explores tests done on five novel copycat problems with thirty answers given per cross comparison.
|
||||
[For now, it is safest to say that the results of this paper are inconclusive: See Results section]
|
||||
%% Based on this cross-comparison, the original adjustment formulas have no significant effect (But these results are preliminary, see Results section for more detail).
|
||||
\end{abstract}
|
||||
|
||||
\section{Introduction}
|
||||
|
||||
This paper stems from Melanie Mitchell's \cite{analogyasperception} and Douglas Hofstadter's \& FARG's \cite{fluidconcepts} work on the copycat program.
|
||||
It is also based on work from a previous paper by Alexandre Linhares \cite{linhares}.
|
||||
This project focuses on effectively simulating intelligent processes through increasingly distributed decision-making.
|
||||
In the process of evaluating the distributed nature of copycat, this paper also proposes a "Normal Science" framework.
|
||||
Copycat's behavior is based on the "Parallel Terraced Scan," a humanistic-inspired search algorithm.
|
||||
The Parallel Terraced Scan is, roughly, a mix between a depth-first and breadth-first search.
|
||||
To switch between modes of search, FARG models use the global variable \emph{temperature}.
|
||||
\emph{Temperature} is ultimately a function of the workspace rule strength then the importance and happiness of each workspace structure.
|
||||
Therefore, \emph{temperature} is a global metric, but is sometimes used to make local decisions.
|
||||
Since copycat means to simulate intelligence in a distributed nature, it should make use of local metrics for local decisions.
|
||||
This paper explores the extent to which copycat's behavior can be improved through distributing decision making.
|
||||
|
||||
Specifically, the effects of temperature are first tested.
|
||||
Then, once the statistically significant effects of temperature are understood, work is done to replace temperature with a distributed metric.
|
||||
Initially, temperature is removed destructively, essentially removing any lines of code that mention it, simply to see what effect it has.
|
||||
Then, a surgical removal of temperature is attempted, leaving in tact affected structures or replacing them by effective distributed mechanisms.
|
||||
|
||||
To evaluate the distributed nature of copycat, this paper focuses on the creation of a `normal science' framework.
|
||||
By `Normal science,' this paper means the term created by Thomas Kuhn--the collaborative enterprise of furthering understanding within a paradigm.
|
||||
Today, "normal science" is simply not done on FARG architectures (and on most computational cognitive architectures too... see Addyman \& French \cite{compmodeling}).
|
||||
Unlike mathematical theories or experiments, which can be replicated by following the materials and methods, computational models generally have dozens of particularly tuned variables, undocumented procedures, multiple assumptions about the users computational environment, etc.
|
||||
It then becomes close to impossible to reproduce a result, or to test some new idea scientifically.
|
||||
This paper focuses on the introduction of statistical techniques, reduction of "magic numbers", improvement and documentation of formulas, and proposals for statistical human comparison.
|
||||
Each of these methods will reduce the issues with scientific inquiry in the copycat architecture.
|
||||
|
||||
To evaluate two different versions of copycat, the resulting answer distributions from a problem are compared with a Pearson's $\chi^2$ test.
|
||||
Using this, the degree of difference between distributions can be calculated.
|
||||
Then, desirability of answer distributions can be found as well, and the following hypotheses can be tested:
|
||||
|
||||
\begin{enumerate}
|
||||
\item $H_i$ Centralized, global variables constrict copycat's ability.
|
||||
\item $H_0$ Centralized, global variables either improve or have no effect on copycat's ability.
|
||||
\end{enumerate}
|
||||
|
||||
\subsection{Objective}
|
||||
|
||||
The aim of this paper is to create and test a new version of the copycat software that makes effective use of a multiple level description.
|
||||
Until now, copycat has made many of its decisions, even local ones, based on a global variable, \emph{temperature}.
|
||||
Two approaches will be taken toward improving copycat.
|
||||
First, small portions of copycat will be removed and then tested individually.
|
||||
If they do not significantly change the answer distributions given by copycat, they will be collectively removed from a working version of copycat.
|
||||
Then, alternate, distributed versions of copycat will be compared to the original copycat software to effectively decide on which design choices to make.
|
||||
|
||||
\subsection{Theory}
|
||||
|
||||
\subsubsection{Centralized Structures}
|
||||
|
||||
Since computers are universal and have vastly improved in the past five decades, it is clear that computers are capable of simulating intelligent processes \cite{computerandthebrain}.
|
||||
The primary obstacle blocking strong A.I. is \emph{comprehension} of intelligent processes.
|
||||
Once the brain is truly understood, writing software that emulates intelligence will be a (relatively) simple engineering task when compared to understanding the brain.
|
||||
|
||||
In making progress towards understanding the brain fully, models must remain true to what is already known about intelligent processes.
|
||||
Outside of speed, the largest difference between the computer and the brain is the distributed nature of computation.
|
||||
Specifically, our computers as they exist today have central processing units, where literally all of computation happens.
|
||||
Brains have some centralized structures, but certainly no single central location where all processing happens.
|
||||
Luckily, the difference in speed between brains and computers allows computers to simulate brains even when they are running serial code.
|
||||
From a design perspective, however, software should take the distributed nature of the brain into consideration, because it is most likely that distributed computation plays a large role in the brain's functionality.
|
||||
|
||||
For example, codelets should behave more like ants in an anthill, as described in \emph{Gödel, Escher, Bach} \cite{geb}.
|
||||
Instead of querying a global structure (i.e. the queen), ants might query each other, and each carry information about what they've last seen.
|
||||
In this way, distributed computation can be carried out through many truly parallel (non-blocking) agents.
|
||||
|
||||
It is clear from basic classical psychology that the brain contains some centralized structures.
|
||||
For example, Broca's area and Wernicke's area are specialized for linguistic input and output.
|
||||
Another great example is the hippocampi.
|
||||
If any of these specialized chunks of brain are surgically removed, for instance, then the ability to perform certain tasks is greatly impacted.
|
||||
To some extent, the same is true for copycat.
|
||||
For example, removing the ability to update the workspace would be \emph{*roughly*} equivalent to removing both hippocampi from a human.
|
||||
This paper means to first test the impact of centralized structures, like \emph{temperature}, by removing or altering them and then performing tests.
|
||||
Then, distributed structures will be proposed and tested in place of centralized ones.
|
||||
|
||||
However: “How gullible are you? Is your gullibility located in some "gullibility center" in your brain? Could a neurosurgeon reach in and perform some delicate operation to lower your gullibility, otherwise leaving you alone? If you believe this, you are pretty gullible, and should perhaps consider such an operation.”
|
||||
― Douglas R. Hofstadter, Gödel, Escher, Bach: An Eternal Golden Braid
|
||||
|
||||
Outside of \emph{temperature}, other structures in copycat, like the workspace itself, or the coderack, are also centralized.
|
||||
Hopefully, these centralized structures are not constraining, but it possible they are.
|
||||
If they are, their unifying effect should be taken into account.
|
||||
For example, the workspace is atomic, just like centralized structures in the brain, like the hippocampi, are also atomic.
|
||||
If copycat can be run such that -- during the majority of the program's runtime -- codelets may actually execute at the same time (without pausing to access globals), then it will much better replicate the human brain.
|
||||
A good model for this is the functional-programming \emph{map} procedure.
|
||||
From this perspective, the brain would simply be carrying out the same function in many locations (i.e. \emph{map}ping neuron.process() across each of its neurons)
|
||||
Note that this is more similar to the behavior of a GPU than a CPU.
|
||||
This model doesn't work when code has to synchronize to access global variables.
|
||||
|
||||
Notably, however, functional distributed code is Turing complete just like imperative centralized code is Turing complete.
|
||||
Especially given the speed of modern computers, functional code cannot do anything that imperative code can't.
|
||||
However, working in a mental framework that models the functionality of the human brain may assist in actually modeling its processes.
|
||||
|
||||
\subsubsection{Local Descriptions}
|
||||
|
||||
A global description of the system (\emph{temperature}) is, at times, potentially useful.
|
||||
However, in summing together the values of each workspace object, information is lost regarding which workspace objects are offending.
|
||||
In general, the changes that occur will eventually be object-specific.
|
||||
So, it seems to me that going from object-specific descriptions to a global description back to an object-specific action is a waste of time, at least when the end action is an object-specific action.
|
||||
A global description shouldn't be \emph{obliterated} (removed 100\%).
|
||||
Maybe a global description should be reserved for \emph{only} when global actions are taking place.
|
||||
For example, when deciding that copycat has found a satisfactory answer, a global description should be used, because deciding to stop copycat is a global action.
|
||||
However, when deciding to remove a particular structure, a global description should not be used, because removing a particular offending structure is NOT a global action.
|
||||
|
||||
Of course, global description has some benefits even when it is being used to change local information.
|
||||
For example, the global formula for temperature converts the raw importance value for each object into a relative importance value for each object.
|
||||
If a distributed metric was used, this importance value would have to be left in its raw form.
|
||||
|
||||
\section{Methods}
|
||||
|
||||
\subsection{Formula Documentation}
|
||||
|
||||
Many of copycat's formulas use magic numbers and marginally documented formulas.
|
||||
This is less of a problem in the original LISP code, and more of a problem in the twice-translated Python3 version of copycat.
|
||||
However, even in copycat's LISP implementation, formulas have redundant parameters.
|
||||
For example, if given two formulas: $f(x) = x^2$ and $g(x) = 2x$, a single formula can be written $h(x) = 4x^2$ (The composed and then simplified formula).
|
||||
Ideally, the adjustment formulas within copycat could be reduced in the same way, so that much of copycat's behavior rested on a handful of parameters in a single location, as opposed to more than ten parameters scattered throughout the repository.
|
||||
Also, often parameters in copycat have little statistically significant effect.
|
||||
As will be discussed in the $\chi^2$ distribution testing section, any copycat formulas without a significant effect will be hard-removed.
|
||||
|
||||
\subsection{Testing the Effect of Temperature}
|
||||
|
||||
To begin with, the existing effect of the centralizing variable, temperature, will be analyzed.
|
||||
As the probability adjustment formulas are used by default, very little effect is had.
|
||||
To evaluate the effect of temperature-based probability adjustment formulas, a spreadsheet was created that showed a color gradient based on each formula.
|
||||
View the spreadsheets \href{https://docs.google.com/spreadsheets/d/1JT2yCBUAsFzMcbKsQUcH1DhcBbuWDKTgPvUwD9EqyTY/edit?usp=sharing}{here}.
|
||||
Then, to evaluate the effect of different temperature usages, separate usages of temperature were individually removed and answer distributions were compared statistically (See section: $\chi^2$ Distribution Testing).
|
||||
|
||||
\subsection{Temperature Probability Adjustment}
|
||||
|
||||
Once the effect of temperature was evaluated, new temperature-based probability adjustment formulas were proposed that each had a significant effect on the answer distributions produced by copycat.
|
||||
Instead of representing a temperature-less, decentralized version of copycat, these formulas are meant to represent the centralized branch of copycat.
|
||||
|
||||
These formulas curve probabilities, making unlikely events more likely and likely events less likely as a function of the global \emph{temperature} variable.
|
||||
|
||||
The desired (LISP documented) behavior is as follows:
|
||||
At high temperatures, the system should explore options that would otherwise be unlikely.
|
||||
So, at temperatures above half of the maximum temperature, probabilities with a base value less than fifty percent will be curved higher, to some threshold.
|
||||
At temperatures below half of the maximum temperature, probabilities with a base value above fifty percent will be curved lower, to some threshold.
|
||||
|
||||
The original formulas being used to do this were overly complicated.
|
||||
In summary, many formulas were tested in a spreadsheet, and an optimal one was chosen that replicated the desired behavior.
|
||||
The remainder of the section discusses different formulas and their advantages/disadvantages.
|
||||
Also, as a general rule, changing these formulas causes copycat to produce statistically significantly different answer distributions.
|
||||
|
||||
The original formula for curving probabilities in copycat:
|
||||
\lstinputlisting[language=Python]{resources/original.py}
|
||||
|
||||
An alternative that seems to improve performance on the "abd:abd::xyz:\_" problem:
|
||||
This formula produces probabilities that are not bounded between 0 and 1. These are generally truncated.
|
||||
\lstinputlisting[language=Python]{resources/entropy.py}
|
||||
|
||||
However, this formula worsens performance on non "xyz" problems.
|
||||
Likely, because of how novel the "xyz" problem is, it will require more advanced architecture changes.
|
||||
For instance, MetaCat claims to assist in solving the "xyz" problem.
|
||||
|
||||
The entropy formula is an improvement, but other formulas are possible too.
|
||||
|
||||
Below are variations on a "weighted" formula.
|
||||
The general structure is:
|
||||
|
||||
\[\emph{p'} = \frac{T}{100} * S + \frac{100-T}{100} * U\]
|
||||
|
||||
Where: $S$ is the convergence value for when $T = 100$ and
|
||||
$U$ is the convergence value for when $T = 0$.
|
||||
The below formulas simply experiment with different values for $S$ and $U$
|
||||
|
||||
\lstinputlisting[language=Python]{resources/weighted.py}
|
||||
|
||||
After some experimentation and reading the original copycat documentation, it was clear that $S$ should be chosen to be $0.5$ (All events are equally likely at high temperature) and that $U$ should implement the probability curving desired at low temperatures.
|
||||
|
||||
The following formulas let $U = p^r$ if $p < 0.5$ and let $U = p^\frac{1}{r}$ if $p >= 0.5$.
|
||||
This controls whether/when curving happens.
|
||||
Now, the \emph{single} parameter $r$ simply controls the degree to which curving happens.
|
||||
Different values of $r$ were experimented with (values between $10$ and $1$ were experimented with at increasingly smaller step sizes).
|
||||
$2$ and $1.05$ are both good choices at opposite "extremes".
|
||||
$2$ works because it is large enough to produce novel changes in behavior at extreme temperatures without totally disregarding the original probabilities.
|
||||
Values above $2$ do not work because they make probabilities too uniform.
|
||||
Values below $2$ (and above $1.05$) are feasible, but produce less curving and therefore less unique behavior.
|
||||
$1.05$ works because it very closely replicates the original copycat formulas, providing a very smooth curving.
|
||||
Values beneath $1.05$ essentially leave probabilities unaffected, producing no significant unique behavior dependent on temperature.
|
||||
|
||||
\lstinputlisting[language=Python]{resources/best.py}
|
||||
|
||||
All of these separate formulas will later be cross-compared to other variants of the copycat software using a Pearson's $\chi^2$ test.
|
||||
|
||||
\subsection{Temperature Usage Adjustment}
|
||||
|
||||
Once the behavior based on temperature was well understood, experimentation was made with hard and soft removals of temperature and features that depend on it.
|
||||
For example, first probability adjustments based on temperature were removed.
|
||||
Then, the new branch of copycat was $\chi^2$ compared against the original branch.
|
||||
Then, breaker-fizzling, an independent temperature-related feature was removed from the original branch and another $\chi^2$ comparison was made.
|
||||
The same process was repeated for non-probability temperature-based adjustments, and then for the copycat stopping decision.
|
||||
Then, a temperature-less branch of the repository was created and tested.
|
||||
Then, a branch of the repository was created that removed probability adjustments, value adjustments, and fizzling, and made all other temperature-related operations use a dynamic temperature calculation.
|
||||
All repository branches were then cross compared using a $\chi^2$ distribution test.
|
||||
|
||||
\subsection{$\chi^2$ Distribution Testing}
|
||||
|
||||
To test each different branch of the repository, a scientific framework was created.
|
||||
Each run of copycat on a particular problem produces a distribution of answers.
|
||||
Distributions of answers can be compared against one another with a (Pearson's) $\chi^2$ distribution test.
|
||||
|
||||
$$\chi^2 = \sum_{i=1}^{n} \frac{(O_i - E_i)^2}{E_i}$$
|
||||
Where:
|
||||
\newline\indent
|
||||
$O_i = $ The number of observations of a particular answer
|
||||
\newline\indent
|
||||
$E_i = $ The number of expected observations of a particular answer
|
||||
\newline
|
||||
\newline\indent
|
||||
Then, $\chi^2$ is calculated, using one copycat variant as a source for expected observations, and another copycat variant as a source for novel observations.
|
||||
If the $\chi^2$ value is above some threshold (dependent on degrees of freedom and confidence level), then the two copycat variants are significantly different.
|
||||
A standard confidence level of $95\%$ is used, and degrees of freedom is calculated as the number of different answers given from the source-variant of copycat.
|
||||
Because of this, comparing copycat variants like this is \emph{not} always commutative.
|
||||
|
||||
\subsection{Effectiveness Definition}
|
||||
|
||||
Quantitatively evaluating the effectiveness of a cognitive architecture is difficult.
|
||||
However, for copycat specifically, effectiveness can be defined as a function of the frequency of desirable answers and equivalently as the inverse frequency of undesirable answers.
|
||||
Since answers are desirable to the extent that they respect the original transformation of letter sequences, desirability can also be approximated by a concrete metric.
|
||||
A simple metric for desirability is simply the existing temperature formula.
|
||||
So, one metric for effectiveness of a copycat variant is the frequency of low-temperature answers.
|
||||
$$e = \frac{\sum_{i=i}^{n} \frac{O_i}{T_i}}{N} $$
|
||||
For simplicity, only this metric will be used.
|
||||
However, this metric could be extended relatively easily.
|
||||
For example, the unique variants in copycat answers could be taken into account ($n$).
|
||||
|
||||
\section{Results}
|
||||
|
||||
\subsection{Cross $\chi^2$ Table}
|
||||
|
||||
The Cross$\chi^2$ table summarizes the results of comparing each copycat-variant's distribution with each other copycat-variant and with different internal formulas.
|
||||
For the table, please see \href{"https://docs.google.com/spreadsheets/d/1d4EyEbWLJpLYlE7qSPPb8e1SqCAZUvtqVCd0Ns88E-8/edit?usp=sharing"}{google sheets}.
|
||||
This table contains a lot of information, but most importantly it shows which copycat variants produce novel changes and which do not.
|
||||
The following variants of copycat were created:
|
||||
\begin{enumerate}
|
||||
\item The original copycat (legacy)
|
||||
\item Copycat with no probability adjustment formulas (no-prob-adj)
|
||||
\item Copycat with no fizzling (no-fizzle)
|
||||
\item Copycat with no adjustment formulas at all (no-adj)
|
||||
\item Copycat with several different internal adjustment formulas (adj-tests)
|
||||
\begin{enumerate}
|
||||
\item alt\_fifty
|
||||
\item average\_alt
|
||||
\item best
|
||||
\item entropy
|
||||
\item fifty\_converge
|
||||
\item inverse
|
||||
\item meta
|
||||
\item none
|
||||
\item original
|
||||
\item pbest
|
||||
\item pmeta
|
||||
\item sbest
|
||||
\item soft
|
||||
\item weighted\_soft
|
||||
\end{enumerate}
|
||||
\item Copycat with temperature 100\% removed (nuke-temp)
|
||||
\item Copycat with a surgically removed temperature (soft-remove)
|
||||
\end{enumerate}
|
||||
|
||||
Each variant was cross-compared with each other variant on this set of problems (from \cite{fluidconcepts}).
|
||||
\begin{enumerate}
|
||||
\item abc:abd::efg:\_
|
||||
\item abc:abd::ijk:\_
|
||||
\item abc:abd::ijkk:\_
|
||||
\item abc:abd::mrrjjj:\_
|
||||
\item abc:abd::xyz:\_
|
||||
\end{enumerate}
|
||||
|
||||
On a trial run with thirty iterations each, the following cross-comparisons showed \emph{no} difference in answer distributions:
|
||||
\begin{enumerate}
|
||||
\item .no-adj x .adj-tests(none)
|
||||
\item .no-adj x .adj-tests(original)
|
||||
\item .no-adj x .no-prob-adj
|
||||
\item .no-prob-adj x .adj-tests(original)
|
||||
\item .no-prob-adj x .adj-tests(pbest)
|
||||
\item .no-prob-adj x .adj-tests(weighted\_soft)
|
||||
\item .nuke-temp x .adj-tests(entropy)
|
||||
\item .soft-remove x .adj-tests(best)
|
||||
\item .soft-remove x .no-prob-adj
|
||||
\end{enumerate}
|
||||
|
||||
There are also several variant comparisons that only vary on one or two problems.
|
||||
As discussed below, it will be easier to evaluate them with more data.
|
||||
|
||||
Before the final draft of this paper, a trial will be conducted with a larger number of iterations and a variant of the Pearson's $\chi^2$ test that accounts for zero-count answer frequencies.
|
||||
Also, because the comparison test is non commutative, "backwards" tests will be conducted.
|
||||
Additionally, more problems will be added to the problem set, even if they are reducible.
|
||||
This will provide additional data points for comparison (If two copycat variants are indistinguishable on some novel problem, they should be indistinguishable on some structurally identifical variant of the novel problem).
|
||||
It is also possible that additional versions of copycat will be tested (I plan on testing small features of copycat, like parameters and so on, and removing them bit by bit).
|
||||
|
||||
\section{Discussion}
|
||||
|
||||
\subsection{Interpretation of table}
|
||||
|
||||
It is clear that the original copycat probability adjustment formula had no statistically significant effects.
|
||||
Additionally, new formulas that emulate the performance of the original formula also have no significant effects.
|
||||
However, novel adjustment formulas, like the "best" formula, provide the same results as soft-removing temperature.
|
||||
Soft-removing temperature is also identical to running copycat with no probability adjustments.
|
||||
|
||||
\subsection{Distributed Computation Accuracy}
|
||||
|
||||
[Summary of introduction, elaboration based on results]
|
||||
|
||||
\subsection{Prediction??}
|
||||
|
||||
Even though imperative, serial, centralized code is Turing complete just like functional, parallel, distributed code, I predict that the most progressive cognitive architectures of the future will be created using functional programming languages that run distributively and are at least capable of running in true, CPU-bound parallel.
|
||||
|
||||
\printbibliography
|
||||
|
||||
\end{document}
|
||||
336
papers/legacy/draft.tex
Normal file
336
papers/legacy/draft.tex
Normal file
@ -0,0 +1,336 @@
|
||||
\documentclass[a4paper]{article}
|
||||
|
||||
%% Language and font encodings
|
||||
\usepackage[english]{babel}
|
||||
\usepackage[utf8x]{inputenc}
|
||||
\usepackage[T1]{fontenc}
|
||||
|
||||
%% Sets page size and margins
|
||||
\usepackage[a4paper,top=3cm,bottom=2cm,left=3cm,right=3cm,marginparwidth=1.75cm]{geometry}
|
||||
|
||||
%% Useful packages
|
||||
\usepackage{listings}
|
||||
\usepackage{amsmath}
|
||||
\usepackage{graphicx}
|
||||
\usepackage[colorinlistoftodos]{todonotes}
|
||||
\usepackage[colorlinks=true, allcolors=blue]{hyperref}
|
||||
|
||||
\definecolor{lightgrey}{rgb}{0.9, 0.9, 0.9}
|
||||
\lstset{ %
|
||||
backgroundcolor=\color{lightgrey}}
|
||||
|
||||
\title{Distributed Behavior in a Fluid Analogy Architecture}
|
||||
\author{Lucas Saldyt, Alexandre Linhares}
|
||||
|
||||
\begin{document}
|
||||
\maketitle
|
||||
|
||||
\begin{abstract}
|
||||
We investigate the distributed nature of computation in a FARG architecture, Copycat.
|
||||
One of the foundations of those models is the \emph{Parallel Terraced Scan}--a psychologically-plausible model that enables a system to fluidly move between different modes of processing.
|
||||
Previous work has modeled decision-making under Parallel Terraced Scan by using a central variable of \emph{Temperature}.
|
||||
However, it is unlikely that this design decision accurately replicates the processes in the human brain.
|
||||
This paper proposes several changes to copycat architectures that will increase their modeling accuracy.
|
||||
\end{abstract}
|
||||
|
||||
\section{Introduction}
|
||||
|
||||
This paper stems from Mitchell's (1993) and Hofstadter's \& FARG's (1995) work on the copycat program.
|
||||
This project focuses on effectively simulating intelligent processes through increasingly distributed decision-making.
|
||||
In the process of evaluating the distributed nature of copycat, this paper also proposes a "Normal Science" framework.
|
||||
|
||||
First, copycat uses a "Parallel Terraced Scan" as a humanistic inspired search algorithm.
|
||||
The Parallel Terraced Scan corresponds to the psychologically-plausible behavior of briefly browsing, say, a book, and delving deeper whenever something sparks one's interest.
|
||||
In a way, it is a mix between a depth-first and breadth-first search.
|
||||
This type of behavior seems to very fluidly change the intensity of an activity based on local, contextual cues.
|
||||
Previous FARG models use centralized structures, like the global temperature value, to control the behavior of the Parallel Terraced Scan.
|
||||
This paper explores how to maintain the same behavior while distributing decision-making throughout the system.
|
||||
|
||||
Specifically, this paper attempts different refactors of the copycat architecture.
|
||||
First, the probability adjustment formulas based on temperature are changed.
|
||||
Then, we experiment with two methods for replacing temperature with a distributed metric.
|
||||
Initially, temperature is removed destructively, essentially removing any lines of code that mention it, simply to see what effect it has.
|
||||
Then, a surgical removal of temperature is attempted, leaving in tact affected structures or replacing them by effective distributed mechanisms.
|
||||
|
||||
To evaluate the distributed nature of copycat, this paper focuses on the creation of a `normal science' framework.
|
||||
By `Normal science,' this paper means the term created by Thomas Kuhn--the collaborative enterprise of furthering understanding within a paradigm.
|
||||
Today, "normal science" is simply not done on FARG architectures (and on most computational cognitive architectures too... see Addyman \& French 2012).
|
||||
Unlike mathematical theories or experiments, which can be replicated by following the materials and methods, computational models generally have dozens of particularly tuned variables, undocumented procedures, multiple assumptions about the users computational environment, etc.
|
||||
It then becomes close to impossible to reproduce a result, or to test some new idea scientifically.
|
||||
This paper focuses on the introduction of statistical techniques, reduction of "magic numbers", improvement and documentation of formulas, and proposals for statistical human comparison.
|
||||
|
||||
We also discuss, in general, the nature of the brain as a distributed system.
|
||||
While the removal of a single global variable may initially seem trivial, one must realize that copycat and other cognitive architectures have many central structures.
|
||||
This paper explores the justification of these central structures in general.
|
||||
Is it possible to model intelligence with them, or are they harmful?
|
||||
|
||||
\section{Theory}
|
||||
\subsection{Notes}
|
||||
|
||||
According to the differences we can enumerate between brains and computers, it is clear that, since computers are universal and have vastly improved in the past five decades, that computers are capable of simulating intelligent processes.
|
||||
[Cite Von Neumann].
|
||||
Primarily, the main obstacle now lies in our comprehension of intelligent processes.
|
||||
Once we truly understand the brain, writing software that emulates intelligence will be a relatively simple software engineering task.
|
||||
However, we must be careful to remain true to what we already know about intelligent processes so that we may come closer to learning more about them and eventually replicating them in full.
|
||||
The largest difference between the computer and the brain is the distributed nature of computation.
|
||||
Specifically, our computers as they exist today have central processing units, where literally all of computation happens.
|
||||
On the other hand, our brains have no central location where all processing happens.
|
||||
Luckily, the speed advantage and universality of computers makes it possible to simulate the distributed behavior of the brain.
|
||||
However, this simulation is only possible if computers are programmed with concern for the distributed nature of the brain.
|
||||
[Actually, I go back and forth on this: global variables might be plausible, but likely aren't]
|
||||
Also, even though the brain is distributed, some clustered processes must take place.
|
||||
In general, centralized structures should be removed from the copycat software, because they will likely improve the accuracy of simulating intelligent processes.
|
||||
It isn't clear to what degree this refactor should take place.
|
||||
The easiest target is the central variable, temperature, but other central structures exist.
|
||||
This paper focuses primarily on temperature, and the unwanted global unification associated with it.
|
||||
|
||||
Even though copycat uses simulated parallel code, if copycat were actually parallelized, the global variable of temperature would actually prevent most copycat codelets from running at the same time.
|
||||
If this global variable and other constricting centralized structures were removed, copycat's code would more closely replicate intelligent processes and would be able to be run much faster.
|
||||
From a function-programming like perspective (i.e. LISP, the original language of copycat), the brain should simply be carrying out the same function in many locations (i.e. mapping neuron.process() across each of its neurons, if you will...)
|
||||
Note that this is more similar to the behavior of a GPU than a CPU....?
|
||||
However, in violating this model with the introduction of global variables......
|
||||
|
||||
Global variables seem like a construct that people use to model the real world.
|
||||
...
|
||||
|
||||
It is entirely possible that at the level of abstraction that copycat uses, global variables are perfectly acceptable.
|
||||
For example, a quick grep-search of copycat shows that the workspace singleton also exists as a global variable.
|
||||
Making all of copycat distributed clearly would require a full rewrite of the software....
|
||||
|
||||
If copycat can be run such that codelets may actually execute at the same time (without pausing to access globals), then it will much better replicate the human brain.
|
||||
|
||||
However, I question the assumption that the human brain has absolutely no centralized processing.
|
||||
For example, input and output channels (i.e. speech mechanisms) are not accessible from the entire brain.
|
||||
Also, brain-region science leads me to believe that some (for example, research concerning wernicke's or broca's areas) brain regions truly are "specialized," and thus lend some support to the existence of centralized structures in a computer model of the brain.
|
||||
However, these centralized structures may be emergent?
|
||||
|
||||
So, to re-iterate: Two possibilities exist (hypotheses)
|
||||
|
||||
A computer model of the brain can contain centralized structures and still be effective in its modeling.
|
||||
A computer model cannot have any centralzied structures if it is going to be effective in its modeling.
|
||||
|
||||
Another important problem is defining the word "effective".
|
||||
I suppose that "effective" would mean capable of solving fluid analogy problems, producing similar answers to an identically biased human.
|
||||
However, it isn't clear to me that removing temperature increases the ability to solve problems effectively.
|
||||
Is this because models are allowed to have centralized structures, or because temperature isn't the only centralized structure?
|
||||
|
||||
Clearly, creating a model of copycat that doesn't have centralized structures will take an excessive amount of effort.
|
||||
|
||||
\break
|
||||
|
||||
The calculation for temperature in the first place is extremely convoluted (in the Python version of copycat).
|
||||
It lacks any documentation, is full of magic numbers, and contains seemingly arbitrary conditionals.
|
||||
(If I submitted this as a homework assignment, I would probably get a C. Lol)
|
||||
|
||||
Edit: Actually, the lisp version of copycat does a very good job of documenting magic numbers and procedures.
|
||||
My main complaint is that this hasn't been translated into the Python version of copycat.
|
||||
However, the Python version is translated from the Java version..
|
||||
Lost in translation.
|
||||
|
||||
|
||||
My goal isn't to roast copycat's code, however.
|
||||
Instead, what I see is that all this convolution is \emph{unnecessary}.
|
||||
Ideally, a future version of copycat, or an underlying FARG architecure will remove this convolution, and make temperature calculation simpler, streamlined, documented, understandble.
|
||||
How will this happen, though?
|
||||
|
||||
A global description of the system is, at times, potentially useful.
|
||||
However, in summing together the values of each workspace object, information is lost regarding which workspace objects are offending.
|
||||
In general, the changes that occur will eventually be object-specific.
|
||||
So, it seems to me that going from object-specific descriptions to a global description back to an object-specific action is a waste of time.
|
||||
|
||||
I don't think that a global description should be \emph{obliterated} (removed 100\%).
|
||||
I just think that a global description should be reserved for when global actions are taking place.
|
||||
For example, when deciding that copycat has found a satisfactory answer, a global description should be used, because deciding to stop copycat is a global action.
|
||||
However, when deciding to remove a particular structure, a global description should not be used, because removing a particular offending structure is NOT a global action.
|
||||
|
||||
Summary: it is silly to use global information to make local decisions that would be better made using local information (self-evident).
|
||||
|
||||
Benefits of using local information to make local decisions:
|
||||
|
||||
Code can be truly distributed, running in true parallel, CPU-bound.
|
||||
This means that copycat would be faster and more like a human brain.
|
||||
Specific structures would be removed based on their own offenses.
|
||||
This means that relvant structures would remain untouched, which would be great!
|
||||
Likely, this change to copycat would produce better answer distributions testable through the normal science framework.
|
||||
|
||||
On the other hand (I've never met a one-handed researcher), global description has some benefits.
|
||||
For example, the global formula for temperature converts the raw importance value for each object into a relative importance value for each object.
|
||||
If a distributed metric was used, this importance value would have to be left in its raw form.
|
||||
|
||||
\break
|
||||
|
||||
The original copycat was written in LISP, a mixed-paradigm language.
|
||||
Because of LISP's preference for functional code, global variables must be explicitly marked with surrounding asterisks.
|
||||
Temperature, the workspace, and final answers are all marked global variables as discussed in this paper.
|
||||
These aspects of copycat are all - by definition - impure, and therefore imperative code that relies on central state changes.
|
||||
It is clear that, since imperative, mutation-focused languages (like Python) are turing complete in the same way that functional, purity-focused languages (like Haskell) are turing complete, each method is clearly capable of modeling the human brain.
|
||||
However, the algorithm run by the brain is more similar to distributed, parallel functional code than it is to centralized, serial imperative code.
|
||||
While there is some centralization in the brain, and evidently some state changes, it is clear that 100\% centralized 100\% serial code is not a good model of the brain.
|
||||
|
||||
Also, temperature is, ultimately, just a function of objects in the global workspace.
|
||||
The git branch soft-temp-removal hard-removes most usages of temperature, but continues to use a functional version of the temperature calculation for certain processes, like determining if the given answer is satisfactory or not.
|
||||
So, all mentions of temperature could theoretically be removed and replaced with a dynamic calculation of temperature instead.
|
||||
It is clear that in this case, this change is unnecessary.
|
||||
With the goal of creating a distributed model in mind, what actually bothers me more is the global nature of the workspace, coderack, and other singleton copycat structures.
|
||||
Really, when temperature is removed and replaced with some distributed metric, it is clear that the true "offending" global is the workspace/coderack.
|
||||
|
||||
Alternatively, codelets could be equated to ants in an anthill (see anthill analogy in GEB).
|
||||
Instead of querying a global structure, codelets could query their neighbors, the same way that ants query their neighbors (rather than, say, relying on instructions from their queen).
|
||||
|
||||
Biological or psychological plausibility only matters if it actually affects the presence of intelligent processes. For example, neurons don't exist in copycat because we feel that they aren't required to simulate the processes being studied. Instead, copycat uses higher-level structures to simulate the same emergent processes that neurons do. However, codelets and the control of them relies on a global function representing tolerance to irrelevant structures. Other higher level structures in copycat likely rely on globals as well. Another central variable in copycat is the "rule" structure, of which there is only one. While some global variables might be viable, others may actually obstruct the ability to model intelligent processes. For example, a distributed notion of temperature will not only increase biological and psychological plausibility, but increase copycat's effectiveness at producing acceptable answer distributions.
|
||||
|
||||
We must also realize that copycat is only a model, so even if we take goals (level of abstraction) and biological plausibility into account...
|
||||
It is only worth changing temperature if it affects the model.
|
||||
Arguably, it does affect the model. (Or, rather, we hypothesize that it does. There is only one way to find out for sure, and that's the point of this paper)
|
||||
|
||||
So, maybe this is a paper about goals, model accuracy, and an attempt to find which cognitive details matter and which don't. It also might provide some insight into making a "Normal Science" framework.
|
||||
|
||||
Copycat is full of random uncommented parameters and formulas. Personally, I would advocate for removing or at least documenting as many of these as possible. In an ideal model, all of the numbers present might be either from existing mathematical formulas, or present for a very good (emergent and explainable - so that no other number would make sense in the same place) reason. However, settling on so called "magic" numbers because the authors of the program believed that their parameterizations were correct is very dangerous. If we removed random magic numbers, we would gain confidence in our model, progress towards a normal science, and gain a better understanding of cognitive processes.
|
||||
|
||||
Similarly, a lot of the testing of copycat is based on human perception of answer distributions. However, I suggest that we move to a more statistical approach. For example, deciding on some arbitrary baseline answer distribution and then modifying copycat to obtain other answer distributions and then comparing distributions with a statistical significance test would actually be indicative of what effect each change had. This paper will include code changes and proposals that lead copycat (and FARG projects in general) to a more statistical and verifiable approach.
|
||||
While there is a good argument about copycat representing an individual with biases and therefore being incomparable to a distributed group of individuals, I believe that additional effort should be made to test copycat against human subjects. I may include in this paper a concrete proposal on how such an experiment might be done.
|
||||
|
||||
Let's simply test the hypothesis: \[H_i\] Copycat will have an improved (significantly different with increased frequencies of more desirable answers and decreased frequencies of less desirable answers: desirability will be determined by some concrete metric, such as the number of relationships that are preserved or mirrored) answer distribution if temperature is turned to a set of distributed metrics. \[H_0\] Copycat's answer distribution will be unaffected by changing temperature to a set of distributed metrics.
|
||||
|
||||
\subsection{Normal Science}
|
||||
\subsubsection{Scientific Style}
|
||||
The Python3 version of copycat contains many undocumented formulas and magic numbers.
|
||||
Also, because of the random nature of copycat, sometimes answer distributions can be affected by the computer architecture that the software is being executed on.
|
||||
To avoid this, this paper suggests documentation of formulas, removal or clear justification of magic numbers, and the use of seeding to get around random processes.
|
||||
Additionally, I might discuss how randomness doesn't *really* exist.
|
||||
Because of this, maybe the explicit psuedo-random nature of Copycat shouldn't exist?
|
||||
Instead.. The distributed nature of computation might act as a psuedo-random process in and of itself.
|
||||
\subsubsection{Scientific Testing}
|
||||
Previously, no statistical tests have been done with the copycat software.
|
||||
Copycat can be treated like a black box, where, when given a particular problem, copycat produces a distribution of answers as output.
|
||||
In this perspective, copycat can be tweaked, and then output distributions on the same problem can be compared with a statistical test, like a $\chi^2$ test.
|
||||
The $\chi^2$ value indicates the degree to which a new copycat distribution differs from an old one.
|
||||
So, a $\chi^2$ test is useful both as a unit test and as a form of scientific inquiry.
|
||||
For example, if a new feature is added to copycat (say, the features included in the Metcat software), then the new distributions can be compared to the distributions produced by the original version of copycat.
|
||||
Ideally, these distributions will differ, giving us a binary indication of whether the changes to the software actually had any effect.
|
||||
Then, once we know that a distribution is statistically novel, we can decide on metrics that evaluate its effectiveness in solving the given problem.
|
||||
For example, since Metacat claims to solve the "xyz" problem, and "wyz" is generally seen as the best answer to the "xyz" problem, a metric that evaluates the health of a distribution might simply be the percentage of "wyz" answers.
|
||||
This can be generalized to the percentage of desirable answers given by some copycat variant in general.
|
||||
Another metric might be the inverse percentage of undesirable answers.
|
||||
For example, "xyd" is an undesirable answer to the "xyz" problem.
|
||||
So, if Metacat produced large quantities of "xyd," it would be worse than the legacy copycat.
|
||||
However, the legacy copycat produces large quantities of "xyd" and small quantities of "wyz".
|
||||
Given these two discussed metrics, it would be clear that, through our normal science framework, Metacat is superior at solving the "xyz" problem.
|
||||
|
||||
Ideally, this framework can be applied to other copycat variants and on other problems.
|
||||
Through the lens of this framework, copycat can be evaluated scientifically.
|
||||
\subsection{Distribution}
|
||||
\subsubsection{Von Neumann Discussion}
|
||||
|
||||
An objective, scientifically oriented framework is essential to making progress in the domain of cognitive science.
|
||||
[John Von Neumann: The Computer and the Brain?
|
||||
He pointed out that there were good grounds merely in terms of electrical analysis to show that the mind, the brain itself, could not be working on a digital system. It did not have enough accuracy; or... it did not have enough memory. ...And he wrote some classical sentences saying there is a statistical language in the brain... different from any other statistical language that we use... this is what we have to discover. ...I think we shall make some progress along the lines of looking for what kind of statistical language would work.]
|
||||
Notion that the brain obeys statistical, entropical mathematics
|
||||
|
||||
\subsubsection{Turing Completeness}
|
||||
In a nutshell, because computers are turing complete, it is clear that they can simulate the human brain, given enough power/time.
|
||||
\subsubsection{Simulation of Distributed Processes}
|
||||
Despite the ability of computers to simulate the human brain, simulation may not always be accurate unless programmed to be accurate...
|
||||
\subsubsection{Efficiency of True Distribution}
|
||||
\subsubsection{Temperature in Copycat}
|
||||
\subsubsection{Other Centralizers in Copycat}
|
||||
\subsubsection{The Motivation for Removing Centralizers in Copycat}
|
||||
\section{Methods}
|
||||
\subsection{Formula Adjustments}
|
||||
\subsubsection{Temperature Probability Adjustment}
|
||||
|
||||
This research begin with adjustments to probability weighting formulas.
|
||||
|
||||
In copycat, temperature affects the simulation in multiple ways:
|
||||
|
||||
\begin{enumerate}
|
||||
\item Certain codelets are probabalistically chosen to run
|
||||
\item Certain structures are probabalistically chosen to be destroyed
|
||||
\item ...
|
||||
\end{enumerate}
|
||||
|
||||
In many cases, the formulas "get-adjusted-probability" and "get-adjusted-value" are used.
|
||||
Each curves a probability as a function of temperature.
|
||||
The desired behavior is as follows:
|
||||
At high temperatures, the system should explore options that would otherwise be unlikely.
|
||||
So, at temperatures above half of the maximum temperature, probabilities with a base value less than fifty percent will be curved higher, to some threshold.
|
||||
At temperatures below half of the maximum temperature, probabilities with a base value above fifty percent will be curved lower, to some threshold.
|
||||
|
||||
The original formulas being used to do this were overly complicated.
|
||||
In summary, many formulas were tested in a spreadsheet, and an optimal one was chosen that replicated the desired behavior.
|
||||
|
||||
The original formula for curving probabilties in copycat:
|
||||
\lstinputlisting[language=Python]{formulas/original.py}
|
||||
|
||||
An alternative that seems to improve performance on the abd->abd xyz->? problem:
|
||||
This formula produces probabilities that are not bounded between 0 and 1. These are generally truncated.
|
||||
\lstinputlisting[language=Python]{formulas/entropy.py}
|
||||
|
||||
Ultimately, it wasn't clear to me that the so-called "xyz" problem should even be considered.
|
||||
As discussed in [the literature], the "xyz" problem is a novel example of a cognitive obstacle.
|
||||
Generally, the best techniques for solving the "xyz" problem are discussed in the the publications around the "Metacat" project, which gives copycat a temporary memory and levels of reflection upon its actions.
|
||||
However, it is possible that the formula changes that target improvement in other problems may produce better results for the "xyz" problem.
|
||||
Focusing on the "xyz" problem, however, will likely be harmful to the improvement of performanace on other problems.
|
||||
|
||||
So, the original copycat formula is overly complicated, and doesn't perform optimally on several problems.
|
||||
The entropy formula is an improvement, but other formulas are possible too.
|
||||
|
||||
Below are variations on a "weighted" formula.
|
||||
The general structure is:
|
||||
|
||||
\[\emph{p'} = \frac{T}{100} * S + \frac{100-T}{100} * U\]
|
||||
|
||||
Where: $S$ is the convergence value for when $T = 0$ and
|
||||
$U$ is the convergence value for when $T = 100$.
|
||||
The below formulas simply experiment with different values for $S$ and $U$
|
||||
The values of $\alpha$ and $\beta$ can be used to provide additional weighting for the formula, but are not used in this section.
|
||||
|
||||
\lstinputlisting[language=Python]{formulas/weighted.py}
|
||||
|
||||
[Discuss inverse formula and why $S$ was chosen to be constant]
|
||||
|
||||
After some experimentation and reading the original copycat documentation, it was clear that $S$ should be chosen to be $0.5$ and that $U$ should implement the probability curving desired at high temperatures.
|
||||
The following formulas let $U = p^r$ if $p < 0.5$ and let $U = p^\frac{1}{r}$ if $p >= 0.5$.
|
||||
This controls whether/when curving happens.
|
||||
Now, the parameter $r$ simply controls the degree to which curving happens.
|
||||
Different values of $r$ were experimented with (values between $10$ and $1$ were experimented with at increasingly smaller step sizes).
|
||||
$2$ and $1.05$ are both good choices at opposite "extremes".
|
||||
$2$ works because it is large enough to produce novel changes in behavior at extreme temperatures without totally disregarding the original probabilities.
|
||||
Values above $2$ do not work because they make probabilities too uniform.
|
||||
Values below $2$ (and above $1.05$) are feasible, but produce less curving and therefore less unique behavior.
|
||||
$1.05$ works because it very closely replicates the original copycat formulas, providing a very smooth curving.
|
||||
Values beneath $1.05$ essentially leave probabilities unaffected, producing no significant unique behavior dependent on temperature.
|
||||
|
||||
\lstinputlisting[language=Python]{formulas/best.py}
|
||||
|
||||
Random thought:
|
||||
It would be interesting to not hardcode the value of $r$, but to instead leave it as a variable between $0$ and $2$ that changes depending on frustration.
|
||||
However, this would be much like temperature in the first place....?
|
||||
$r$ could itself be a function of temperature. That would be.... meta.... lol.
|
||||
|
||||
\break
|
||||
...
|
||||
\break
|
||||
|
||||
And ten minutes later, it was done.
|
||||
The "meta" formula performs as well as the "best" formula on the "ijjkkk" problem, which I consider the most novel.
|
||||
Interestingly, I noticed that the paramterized formulas aren't as good on this problem. What did I parameterize them for? Was it well justified?
|
||||
(Probably not)
|
||||
|
||||
At this point, I plan on using the git branch "feature-normal-science-framework" to implement a system that takes in a problem set and provides several answer distributions as output.
|
||||
Then, I'll do a massive cross-formula answer distribution comparison with $\chi^2$ tests. This will give me an idea about which formula and which changes are best.
|
||||
I'll also be able to compare all of these answer distributions to the frequencies obtained in temperature removal branches of the repository.
|
||||
|
||||
\subsubsection{Temperature Calculation Adjustment}
|
||||
\subsubsection{Temperature Usage Adjustment}
|
||||
\subsection{$\chi^2$ Distribution Testing}
|
||||
\section{Results}
|
||||
\subsection{$\chi^2$ Table}
|
||||
\section{Discussion}
|
||||
\subsection{Distributed Computation Accuracy}
|
||||
\subsection{Prediction}
|
||||
|
||||
\bibliographystyle{alpha}
|
||||
\bibliography{sample}
|
||||
|
||||
\end{document}
|
||||
292
papers/legacy/legacy.tex
Normal file
292
papers/legacy/legacy.tex
Normal file
@ -0,0 +1,292 @@
|
||||
|
||||
\section{LSaldyt: Brainstorm, Planning, and Outline}
|
||||
|
||||
\subsection{Steps/plan}
|
||||
|
||||
Normal Science:
|
||||
\begin{enumerate}
|
||||
\item Introduce statistical techniques
|
||||
\item Reduce magic number usage, document reasoning and math
|
||||
\item Propose effective human subject comparison
|
||||
\end{enumerate}
|
||||
Temperature:
|
||||
\begin{enumerate}
|
||||
\item Propose formula improvements
|
||||
\item Experiment with a destructive removal of temperature
|
||||
\item Experiment with a "surgical" removal of temperature
|
||||
\item Assess different copycat versions with/without temperature
|
||||
\end{enumerate}
|
||||
|
||||
\subsection{Semi-structured Notes}
|
||||
|
||||
Biological or psychological plausibility only matters if it actually affects the presence of intelligent processes. For example, neurons don't exist in copycat because we feel that they aren't required to simulate the processes being studied. Instead, copycat uses higher-level structures to simulate the same emergent processes that neurons do. However, codelets and the control of them relies on a global function representing tolerance to irrelevant structures. Other higher level structures in copycat likely rely on globals as well. Another central variable in copycat is the "rule" structure, of which there is only one. While some global variables might be viable, others may actually obstruct the ability to model intelligent processes. For example, a distributed notion of temperature will not only increase biological and psychological plausibility, but increase copycat's effectiveness at producing acceptable answer distributions.
|
||||
|
||||
We must also realize that copycat is only a model, so even if we take goals (level of abstraction) and biological plausibility into account...
|
||||
It is only worth changing temperature if it affects the model.
|
||||
Arguably, it does affect the model. (Or, rather, we hypothesize that it does. There is only one way to find out for sure, and that's the point of this paper)
|
||||
|
||||
So, maybe this is a paper about goals, model accuracy, and an attempt to find which cognitive details matter and which don't. It also might provide some insight into making a "Normal Science" framework.
|
||||
|
||||
Copycat is full of random uncommented parameters and formulas. Personally, I would advocate for removing or at least documenting as many of these as possible. In an ideal model, all of the numbers present might be either from existing mathematical formulas, or present for a very good (emergent and explainable - so that no other number would make sense in the same place) reason. However, settling on so called "magic" numbers because the authors of the program believed that their parameterizations were correct is very dangerous. If we removed random magic numbers, we would gain confidence in our model, progress towards a normal science, and gain a better understanding of cognitive processes.
|
||||
|
||||
Similarly, a lot of the testing of copycat is based on human perception of answer distributions. However, I suggest that we move to a more statistical approach. For example, deciding on some arbitrary baseline answer distribution and then modifying copycat to obtain other answer distributions and then comparing distributions with a statistical significance test would actually be indicative of what effect each change had. This paper will include code changes and proposals that lead copycat (and FARG projects in general) to a more statistical and verifiable approach.
|
||||
While there is a good argument about copycat representing an individual with biases and therefore being incomparable to a distributed group of individuals, I believe that additional effort should be made to test copycat against human subjects. I may include in this paper a concrete proposal on how such an experiment might be done.
|
||||
|
||||
Let's simply test the hypothesis: \[H_i\] Copycat will have an improved (significantly different with increased frequencies of more desirable answers and decreased frequencies of less desirable answers: desirability will be determined by some concrete metric, such as the number of relationships that are preserved or mirrored) answer distribution if temperature is turned to a set of distributed metrics. \[H_0\] Copycat's answer distribution will be unaffected by changing temperature to a set of distributed metrics.
|
||||
|
||||
\subsection{Random Notes}
|
||||
|
||||
This is all just free-flow unstructured notes. Don't take anything too seriously :).
|
||||
|
||||
Below are a list of relevant primary and secondary sources I am reviewing:
|
||||
|
||||
Biological/Psychological Plausibility:
|
||||
\begin{verbatim}
|
||||
http://www.cell.com/trends/cognitive-sciences/abstract/S1364-6613(16)30217-0
|
||||
"There is no evidence for a single site of working memory storage."
|
||||
https://ekmillerlab.mit.edu/2017/01/10/the-distributed-nature-of-working-memory/
|
||||
|
||||
Creativity as a distributed process (SECONDARY: Review primaries)
|
||||
https://blogs.scientificamerican.com/beautiful-minds/the-real-neuroscience-of-creativity/
|
||||
cognition results from the dynamic interactions of distributed brain areas operating in large-scale networks
|
||||
http://scottbarrykaufman.com/wp-content/uploads/2013/08/Bressler_Large-Scale_Brain_10.pdf
|
||||
|
||||
MIT Encyclopedia of the Cognitive Sciences:
|
||||
In reference to connectionist models:
|
||||
"Advantages of distribution are generally held to include greater representational capacity, content addressability, automatic generalization, fault tolerance, and biological plausibility. Disadvantages include slow learning, catastrophic interference, and binding problems."
|
||||
|
||||
Cites:
|
||||
French, R. (1992). Semi-distributed representation and catastrophic forgetting in connectionist networks.
|
||||
Smolensky, P. (1991). Connectionism, constituency, and the language of thought.
|
||||
[...]
|
||||
\end{verbatim}
|
||||
|
||||
(Sure, we know that the brain is a distributed system, but citing some neuroscience makes me feel much safer.)
|
||||
|
||||
Goal related sources:
|
||||
\begin{verbatim}
|
||||
This will all most likely be FARG related stuff
|
||||
Isolating and enumerating FARG's goals will help show me what direction to take
|
||||
[..]
|
||||
\end{verbatim}
|
||||
|
||||
Eliminating global variables might create a program that is more psychologically and biologically plausible, as according to the above. But our goals should be kept in mind. If we wanted full psychological and biological plausibility, we would just replicate a human mind atom for atom, particle for particle, or string for string.
|
||||
|
||||
Levels of abstraction in modeling the human brain and its processes:
|
||||
\begin{enumerate}
|
||||
\item Cloning a brain at the smallest scale possible (i.e. preserving quantum states of electrons or something)
|
||||
\item Simulating true neurons, abstracting away quantum mechanical detail
|
||||
\item Artificial neurons that abstract away electrochemical detail
|
||||
\item Simulation of higher-level brain structures and behaviors that transcends individual neurons
|
||||
...
|
||||
\item Highest level of abstraction that still produces intelligent processes
|
||||
\end{enumerate}
|
||||
|
||||
How far do we plan to go? What are we even abstracting? Which details matter and which don't?
|
||||
|
||||
One side: Abstraction from biological detail may eventually mean that global variables become plausible
|
||||
Alt: Abstraction may remove some features and not others. Global variables may \emph{never} be plausible, even at the highest level of abstraction. (Of course, this extreme is probably not the case).
|
||||
|
||||
Lack of a centralized structure versus lack of a global phenomena
|
||||
|
||||
Since temperature, for example, is really just a function of several local phenomena, how global is it? I mean: If a centralized decision maker queried local phenomena separately, and made decisions based on that, it would be the same. Maybe centralized decision makers don't exist. Decisions, while seemingly central, have to emerge from agent processes. But what level of abstraction are we working on?
|
||||
|
||||
Clearly, if we knew 100\% which details mattered, we would already have an effective architecture.
|
||||
|
||||
|
||||
\section{A formalization of the model}
|
||||
|
||||
Let $\Omega = \{\omega_1, \omega_2, ..., \omega_n\}$ be a finite discrete space. In FARG models $\Omega$ represents the \emph{working short-term memory} of the system and the goal is to craft a context-sensitive representation (cite FRENCH here). Hence $\Omega$ holds \emph{all possible configurations} of objects that could possibly exist in one's working memory; a large space.
|
||||
|
||||
Let us define the neighborhood function $A:(\Omega,$C$) \to 2^\Omega$ as the set of \emph{perceived affordances} under \emph{context} $C$. The affordances $A$ define which state transitions $\omega_i \to \omega_j$ are possible at a particular context $C$. Another term that has been used in the complexity literature is \emph{the adjacent possible}.
|
||||
|
||||
A context is defined by the high-level ideas, the concepts that are active at a particular point in time.
|
||||
|
||||
The \emph{Cohesion} of the system is measured by the mutual information between the external memory, the short-term memory state $\omega_i$, and the context $C$.
|
||||
|
||||
\subsection{Copycat}
|
||||
|
||||
% LUCAS: this entire section is copies from my old "minds and machines" paper... so we should discuss at some point whether to re-write it or not.
|
||||
|
||||
\subsubsection{The letter-analogy domain}
|
||||
|
||||
Consider the following, seemingly trivial, analogy problem: $abc \to abd:ijk \to ?$, that is, if the letter string “abc” changes to the letter string “abd”, how would the letter string “ijk” change “in the same way”? This is the domain of the Copycat project, and before we attempt a full description of the system, let us discuss in more detail some of the underlying intricacies. Most people will in this case come up with a rule of transformation that looks like: “Replace the rightmost letter by its successor in the alphabet”, the application of which would lead to $ijl$. This is a simple and straightforward example. But other examples bring us the full subtlety of this domain. The reader unfamiliar with the Copycat project is invited to consider the following problems: $abc\to abd: ijjkkk?\to $, $abc\to abd: xyz\to ?$, $abc\to abd: mrrkkk\to ?$, among others (Mitchell, 2003) to have a sense of the myriad of subtle intuitions involved in solving these problems.
|
||||
|
||||
To solve this type of problem, one could come up with a scheme where the computer must first find a representation that models the change and then apply that change to the new string. This natural sequence of operations is \emph{not possible}, however, because \emph{the transformation rule representing the change itself must bend to contextual cues and adapt to the particularities of the letter strings}. For example, in the problem $abc\to abd: xyz\to ?$, the system may at first find a rule like “change rightmost letter to its successor in the alphabet”. However, this explicit rule cannot be carried out in this case, simply because $z$ has no successor. This leads to an impasse, out of which the only alternative by the system is to use a flexible, context-sensitive, representation system.
|
||||
|
||||
The reader may have noticed that this cognitive processing bears some similarities to the process of chess perception. Perception obviously plays a significant role in letter string analogies, as it is necessary to connect a set of individual units--in this case, letter sequences--, into a meaningful interpretation which stresses the underlying pressures of the analogy. In chess it is also necessary to connect disparate pieces into a meaningful description stressing the position’s pressures. But the most striking similarities with chess perception (in what concerns bounded rationality) seems to be the absolute lack of a single objectively correct answer, we have instead just an intuitive subjective feeling, given by the great number of simultaneous pressures arising in each problem.
|
||||
|
||||
In the previous section we have made reference to some studies considering multiple, incompatible chunks that emerge in chess positions. In letter strings this same problem appears. Consider for instance the following problem:
|
||||
|
||||
If $aabc\to aabd: ijkk?$
|
||||
|
||||
\begin{itemize}
|
||||
\item One may chunk the initial strings as $(a)(abc)$ and $(a)(abd)$ and find a `corresponding’ chunk $(ijk)(k)$, which could lead to the following transformation rule: “change the last letter of the increasing sequence to its successor in the alphabet”. This interpretation would lead to the answer $ijlk$.
|
||||
|
||||
\item Or, alternatively, one may chunk the initial strings as $(aa)(b)(c)$ and $(aa)(b)(d)$ and find a counterpart string with the chunking $(i)(j)(kk)$, and, in this case, the mapping can be inverted: The first letter group $(aa)$ maps to the last letter group $(kk)$, and this will also invert the other mappings, leading to $(b)$ mapping to $(j)$ and $(c)$ mapping to $(i)$. Because this viewpoint substantially stresses the concept `opposite’, Copycat is able to create the transformation rule “change the first letter to its predecessor in the alphabet”, leading to the solution $hjkk$, which preserves symmetry between group letter sizes and between successorship and predecessorship relations.
|
||||
|
||||
\item Other potential transformation rules could lead, in this problem, to $ijkl$ (change the last letter to its successor in the alphabet), $ijll$ (change the last group of letters to its successor in the alphabet), or $jjkk$ (change the first letter to its successor in the alphabet). This problem of many incompatible (and overlapping) chunkings is of importance. The specific chunking of a problem is directly linked to its solution, because chunks stress what is important on the underlying relations.
|
||||
\end{itemize}
|
||||
|
||||
\subsubsection{The FARG architecture of Copycat}
|
||||
|
||||
How does the Copycat system work? Before reviewing its underlying parts, let us bear in mind one of its principal philosophical points. Copycat is not intended solely as a letter-string analogy program. The intention of the project is the test of a theory; a theory of `statistically emergent active symbols’ (Hofstadter 1979; Hofstadter 1985) which is diametrically opposite to the “symbol system hypothesis” (Newell, 1980; Simon, 1980). The major idea of active symbols is that instead of being tokens passively manipulated by programs, active symbols emerge from high numbers of interdependent subcognitive processes, which swarm over the system and drive its processing by triggering a complex `chain reaction of concepts’. The system is termed `subsymbolic’ because these processes are intended to correspond to subliminal human information processes of few milliseconds, such as a subtle activation of a concept (i.e., priming), or an unconscious urge to look for a particular object. So the models are of collective (or emergent) computation, where a multitude of local processes gradually build a context-sensitive representation of the problem. These symbols are active because they drive processing, leading a chain reaction of activation spreading, in which active concepts continuously trigger related concepts, and short-term memory structures are construed to represent the symbol (in this philosophical view a token does not have any associated meaning, while a meaningful representation, a symbol, emerges from an interlocked interpretation of many subcognitive pressing urges).
|
||||
|
||||
This cognitively plausible architecture has been applied to numerous domains (see for instance French 1992; Mitchell and Hofstadter 1990; Mitchell 1993; McGraw 1995; Marshall 1999; Rehling 2001 MANY ARE MISSING HERE!). It has five principal components:
|
||||
|
||||
\begin{enumerate}
|
||||
|
||||
\item A workspace that interacts with external memory--this is the working short-term memory of the model. The workspace is where the representations are construed, with innumerable pressing urges waiting for attention and their corresponding impulsive processes swarming over the representation, independently perceiving and creating many types of subpatterns. Common examples of such subpatterns are bonds between letters – such as group bonds between $a*a$ or successor bonds between successive letters $a*b$ –, or relations between objects, awareness of abstract roles played by objects, and so on.
|
||||
|
||||
\item Pressing urges and impulsive processes – The computational processes constructing the representations on short-term memory are subcognitive impulsive processes named codelets. The system perceives a great number of subtle pressures that immediately invoke subcognitive urges to handle them. These urges will eventually become impulsive processes. Some of these impulsive processes may look for particular objects, some may look for particular relations between objects and create bonds between them, some may group objects into chunks, or associate descriptions to objects, etc. The collective computation of these impulsive processes, at any given time, stands for the working memory of the model. These processes can be described as impulsive for a number of reasons: first of all, they are involuntary, as there is no conscious decision required for their triggering. (As Daniel Dennett once put it, if I ask you “not to think of an elephant”, it is too late, you already have done so, in an involuntary way.) They are also automatic, as there is no need for conscious decisions to be taken in their internal processing; they simply know how to do their job without asking for help. They are fast, with only a few operations carried out. They accomplish direct connections between their micro-perceptions and their micro-actions. Processing is also granular and fragmented – as opposed to a linearly structured sequence of operations that cannot be interrupted (Linhares 2003). Finally, they are functional, associated with a subpattern, and operate on a subsymbolic level (but not restricted to the manipulation of internal numerical parameters as opposed to most connectionist systems).
|
||||
|
||||
|
||||
\item List of parallel priorities— Each impulsive process executes a local, incremental, change to the emerging representation, but the philosophy of the system is that all pressing urges are perceived simultaneously, in parallel. So there is at any point in time a list of subcognitive urges ready to execute, fighting for the attention of the system and waiting probabilistically to fire as an impulsive process. This list of parallel priorities is named in Copycat as the coderack.
|
||||
|
||||
\item A semantic associative network undergoing constant flux– The system has very limited basic knowledge: it knows the 26 letters of the alphabet, and the immediate successorship relations entailed (it does not, for instance, know that the shapes of lowercase letters p, b, q bear some resemblance). The long-term memory of the system is embedded over a network of nodes representing concepts with links between nodes associating related concepts. This network is a crucial part for the formation of the chain reaction of conceptual activation: any specific concept, when activated, propagates activation to its related concepts, which will in turn launch top-down expectation-driven urges to look for those related concepts. This mode of computation not only enforces a context-sensitive search but also is the basis of the chain reaction of activation spreading – hence the term ‘active symbols’. This network is named in Copycat as the slipnet. One of the most original features of the slipnet is the ability to “slip one concept into another”, in which analogies between concepts are made (for details see Hofstadter 1995, Mitchell 1993).
|
||||
|
||||
\item A temperature measure – It should be obvious that the system does not zoom in immediately and directly into a faultless representation. The process of representation construction is gradual, tentative, and numerous impulsive processes are executed erroneously. At start, the system has no expectations of the content of letter strings, so it slowly wanders through many possibilities before converging on an specific interpretation, a process named the parallel terraced scan (Hofstadter 1995); and embedded within it is a control parameter of temperature that is similar in some aspects to that found in simulated annealing (Cagan and Kotovsky 1997; Hofstadter 1995). The temperature measures the global amount of disorder and misunderstanding contained in the situation. So at the beginning of the process, when no relevant information has been gathered, the temperature will be high, but it will gradually decrease as intricate relationships are perceived, first concepts are activated, the abstract roles played by letters and chunks are found; and meaning starts to emerge. Though other authors have proposed a relationship between temperature and understanding (Cagan and Kotovsky, 1997), there is still a crucial difference here (see Hofstadter 1985, 1995): unlike the simulated annealing process that has a forcedly monotonically decreasing temperature schedule, the construction of a representation for these letter strings does not necessarily get monotonically improved as time flows. As in the $abc\to abd : xyz\to ?$ problem, there are many instants when roadblocks are reached, when snags appear, and incompatible structures arise. At these moments, complexity (and entropy and confusion) grows, and so the temperature decrease is not monotonic.
|
||||
|
||||
Finally, temperature does not act as a control parameter dictated by the user, that is, \emph{forced} to go either down or up, but it also acts \emph{as a feedback mechanism} to the system, which may reorganize itself, accepting or rejecting changes as temperature allows. As pressing urges are perceived, their corresponding impulses eventually propose changes to working memory, to construct or to destruct structures. How do these proposed changes get accepted? Through the guidance of temperature. At start $T$ is high and the vast majority of proposed structures are built, but as it decreases it becomes increasingly more important for a proposed change to be compatible with the existing interpretation. And the system may thus focus on developing a particular viewpoint.
|
||||
|
||||
\end{enumerate}
|
||||
|
||||
\begin{figure}
|
||||
\centering
|
||||
\includegraphics[width=0.9\textwidth]{fig4-copycat.png}
|
||||
\caption{\label{fig:run-1}Copycat after 110 codelets have executed. This implementation was carried out by Scott Bolland from the University of Queensland, Australia (2003, available online).}
|
||||
\end{figure}
|
||||
|
||||
\subsubsection{An example run}
|
||||
|
||||
Let us consider an example run of the Copycat system, and look at some specific steps in its processing of the problem $abc\to abd : iijjkk \to ?$
|
||||
|
||||
|
||||
|
||||
|
||||
Figure \ref{fig:run-1} presents the working memory (workspace) after 110 codelets. The system at this point has not perceived much structure. It has perceived each individual letter, it has mapped the letters $c$ and $d$ between the original and target strings, and it has perceived some initial bonds between neighboring letters. Some of these bonds are sameness bonds (such as $i*i$), some are successorship bonds (such as $i*j$), and some are predecessorship bonds (such as $b*c$). In fact, there is confusion between the competing views of successorship and predecessorship relations in the string $abc$. These incompatible interpretations will occasionally compete. The system is also mapping the leftmost letter $a$ to the leftmost letter $i$.
|
||||
|
||||
|
||||
|
||||
|
||||
Notice that a first chunk has been created in the group `$jj$'. Now \emph{this chunk is an individual object on its own}, capable of bonding with (and relating to) other objects. Notice also that the system has not yet perceived---and built the corresponding bond between---the two $k$'s in succession. So perception in Copycat is granular, fragmented over large numbers of small `micro-events'.
|
||||
|
||||
\begin{figure}
|
||||
\centering
|
||||
\includegraphics[width=0.9\textwidth]{fig5-copycat.png}
|
||||
\caption{\label{fig:run-2}Copycat’s working memory after the execution of 260 codelets.}
|
||||
\end{figure}
|
||||
|
||||
|
||||
After an additional 150 codelets have been executed (Figure \ref{fig:run-2}), more structure is built: we now have three group chunks perceived; and there is also less confusion in the $abc$, as a `staircase' relation is perceived: that is, the system now perceives $abc$ as a successorship group, another chunked object. Finally, an initial translation rule appears: replace letter category of rightmost letter by successor. If the system were to stop processing at this stage it would apply this rule rather crudely and obtain the answer $iijjkl$. Note that temperature is dropping as more structure is created.
|
||||
|
||||
\begin{figure}
|
||||
\centering
|
||||
\includegraphics[width=0.9\textwidth]{fig6-copycat.png}
|
||||
\caption{\label{fig:run-3}Copycat’s working memory after the execution of 280 codelets. }
|
||||
\end{figure}
|
||||
|
||||
|
||||
Let us slow down our overview a little bit and return in Figure \ref{fig:run-3} after only 20 codelets have run, to illustrate an important phenomenon: though $c$ now will map to the group $kk$, which is an important discovery, the global temperature will still be higher than that of the previous point (Figure \ref{fig:run-2}). This occurs because there is some `confusion' arising from the predecessorship bond which was found between chunks `$ii$' and `$jj$', which does not seem to fit well with all those successorship relations already perceived and with the high activation of the successorship concept. So temperature does not always drop monotonically.
|
||||
|
||||
|
||||
\begin{figure}
|
||||
\centering
|
||||
\includegraphics[width=0.9\textwidth]{fig7-copycat.png}
|
||||
\caption{\label{fig:frog}Copycat's working memory the after execution of 415 codelets.}
|
||||
\end{figure}
|
||||
|
||||
On the next step we can perceive two important changes: first, the system perceives some successorship relations between the groups $ii$ and $jj$ and between the groups $jj$ and $kk$, but these relations are perceived in isolation from each other. Another important discovery is that $jj$ is interpreted as being in `the middle of' $iijjkk$, which will eventually lead to its mapping to the letter $b$ in the original string.
|
||||
|
||||
|
||||
\begin{figure}
|
||||
\centering
|
||||
\includegraphics[width=0.9\textwidth]{fig8-copycat.png}
|
||||
\caption{\label{fig:f8}Copycat’s working memory after the execution of 530 codelets.}
|
||||
\end{figure}
|
||||
|
||||
|
||||
\begin{figure}
|
||||
\centering
|
||||
\includegraphics[width=0.9\textwidth]{fig9-copycat.png}
|
||||
\caption{\label{fig:f9}Final solution obtained after the execution of 695 codelets.}
|
||||
\end{figure}
|
||||
|
||||
|
||||
The system finally perceives that the successorship relations between the $ii$, $jj$, and $kk$ groups are not isolated and creates a single successorship group encompassing these three sameness groups. Thus two successor groups are perceived on the workspace, and a mapping between them is built. However, a still maps to the letter $i$, instead of to the group $ii$, and $c$ still maps to the letter $k$, instead of to the group $kk$.
|
||||
|
||||
From this stage it still remains for the letter $a$ to map to the group $ii$ and for the letter $c$ to map to group $kk$, which will lead naturally to the translated rule ``replace letter category of rightmost group to successor'', illustrating the slipping of the concept letter to the concept group.
|
||||
|
||||
After 695 codelets, the system reaches the answer $iijjll$. The workspace may seem very clean and symmetric, but it has evolved from a great deal of disorder and from many microscopic `battles' between incompatible interpretations.
|
||||
|
||||
The most important concepts activated in this example were group and successor group. Once some sameness bonds were constructed, they rapidly activated the concept sameness group which re-inforced the search to find sameness groups, such as $kk$. Once the initial successorship bonds were created, the activation of the corresponding concept rapidly enabled the system to find other instances of successorship relations (between, for instance, the sameness groups $jj$ and $kk$). Different problems would activate other sets of concepts. For example, `$abc\to abd: xyz\to ?$’ would probably activate the concept \emph{opposite}. And `$abc\to abd: mrrjjj\to ?$' would probably activate the concept length (Mitchell 1993). This rapid activation of concepts (and their top-down pressing urges), with the associated propagation of activation to related concepts, creates a chain reaction of impulsive cognition, and is the key to active symbols theory. The reader is refereed to Mitchell (1993) and to Marshall (1999) to have an idea of how the answers provided by Copycat resemble human intuition.
|
||||
|
||||
We may safely conclude at this point that there are many similarities between copycat and the chess perception process, including: (i) an iterative ‘locking in’ process into a representation; (ii) smaller units bond and combine to form higher level, meaningfully coherent structures; (iii) the perception process is fragmented, granular, with great levels of confusion and entropy at start, but as time progresses it is able to gradually converge into a context-sensitive representation; (iv) there is a high interaction between an external memory, a limited size short term memory, and a long term memory; and (v) this interaction is done simultaneously by bottom-up and top-down processes.
|
||||
|
||||
\subsection{How to include Figures}
|
||||
|
||||
|
||||
First you have to upload the image file from your computer using the upload link the project menu. Then use the includegraphics command to include it in your document. Use the figure environment and the caption command to add a number and a caption to your figure. See the code for Figure \ref{fig:frog} in this section for an example.
|
||||
|
||||
|
||||
|
||||
|
||||
\subsection{How to add Comments}
|
||||
|
||||
Comments can be added to your project by clicking on the comment icon in the toolbar above. % * <john.hammersley@gmail.com> 2016-07-03T09:54:16.211Z:
|
||||
%
|
||||
% Here's an example comment!
|
||||
%
|
||||
To reply to a comment, simply click the reply button in the lower right corner of the comment, and you can close them when you're done.
|
||||
|
||||
Comments can also be added to the margins of the compiled PDF using the todo command\todo{Here's a comment in the margin!}, as shown in the example on the right. You can also add inline comments:
|
||||
|
||||
\todo[inline, color=green!40]{This is an inline comment.}
|
||||
|
||||
\subsection{How to add Tables}
|
||||
|
||||
Use the table and tabular commands for basic tables --- see Table~\ref{tab:widgets}, for example.
|
||||
|
||||
\begin{table}
|
||||
\centering
|
||||
\begin{tabular}{l|r}
|
||||
Item & Quantity \\\hline
|
||||
Widgets & 42 \\
|
||||
Gadgets & 13
|
||||
\end{tabular}
|
||||
\caption{\label{tab:widgets}An example table.}
|
||||
\end{table}
|
||||
|
||||
\subsection{How to write Mathematics}
|
||||
|
||||
\LaTeX{} is great at typesetting mathematics. Let $X_1, X_2, \ldots, X_n$ be a sequence of independent and identically distributed random variables with $\text{E}[X_i] = \mu$ and $\text{Var}[X_i] = \sigma^2 < \infty$, and let
|
||||
\[S_n = \frac{X_1 + X_2 + \cdots + X_n}{n}
|
||||
= \frac{1}{n}\sum_{i}^{n} X_i\]
|
||||
denote their mean. Then as $n$ approaches infinity, the random variables $\sqrt{n}(S_n - \mu)$ converge in distribution to a normal $\mathcal{N}(0, \sigma^2)$.
|
||||
|
||||
|
||||
\subsection{How to create Sections and Subsections}
|
||||
|
||||
Use section and subsections to organize your document. Simply use the section and subsection buttons in the toolbar to create them, and we'll handle all the formatting and numbering automatically.
|
||||
|
||||
\subsection{How to add Lists}
|
||||
|
||||
You can make lists with automatic numbering \dots
|
||||
|
||||
\begin{enumerate}
|
||||
\item Like this,
|
||||
\item and like this.
|
||||
\end{enumerate}
|
||||
\dots or bullet points \dots
|
||||
\begin{itemize}
|
||||
\item Like this,
|
||||
\item and like this.
|
||||
\end{itemize}
|
||||
|
||||
\subsection{How to add Citations and a References List}
|
||||
|
||||
You can upload a \verb|.bib| file containing your BibTeX entries, created with JabRef; or import your \href{https://www.overleaf.com/blog/184}{Mendeley}, CiteULike or Zotero library as a \verb|.bib| file. You can then cite entries from it, like this: \cite{greenwade93}. Just remember to specify a bibliography style, as well as the filename of the \verb|.bib|.
|
||||
|
||||
You can find a \href{https://www.overleaf.com/help/97-how-to-include-a-bibliography-using-bibtex}{video tutorial here} to learn more about BibTeX.
|
||||
|
||||
We hope you find Overleaf useful, and please let us know if you have any feedback using the help menu above --- or use the contact form at \url{https://www.overleaf.com/contact}!
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user