Compare commits

..

331 Commits

Author SHA1 Message Date
19e97d882f Merge master branch into main
Consolidating all project history from master into main branch.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-28 15:59:46 +00:00
4788ffbc05 Commit project files before pushing to Gitea 2025-10-06 17:19:27 +01:00
5593a109ab Update README.md 2025-04-23 21:39:29 +02:00
c80329fea0 Update README.md 2025-04-23 21:36:53 +02:00
6b6155b501 Update README.md 2025-04-23 21:34:16 +02:00
12c81be128 Update README.md 2025-04-23 21:27:58 +02:00
c766d446c5 Update README.md 2025-04-23 21:27:11 +02:00
50d6643bbb Update README.md 2025-04-23 21:25:30 +02:00
3b82892136 Update README.md 2025-04-23 21:23:11 +02:00
7d324e44e9 Update README.md 2025-04-23 21:20:58 +02:00
69a04a724b Update README.md 2025-04-23 21:20:05 +02:00
bdbb964d5d Update README.md 2025-04-23 21:19:07 +02:00
85650b7acb docs: update main README with system analysis 2025-04-23 20:56:19 +02:00
bae3a17d24 docs: add detailed codelet decorator explanation with comments 2025-04-23 19:42:05 +02:00
0c40e4b265 docs: add comprehensive README files for all components 2025-04-23 18:48:21 +02:00
599c4c497d Rename documentation files to source_README.md format 2025-04-23 17:31:00 +02:00
978383638b Documentation of source files (by LLM) 2025-04-23 17:28:30 +02:00
c05e3e832c Merge pull request #2 from mmcguill/python3-tk-nav-toolbar-fix
Fix to run with Python 3.8.1
2020-03-26 07:39:11 -04:00
ce1ac214dc Fix to run with Python 3.8.1 - NavigationToolbar2TkAgg is not available anymore 2020-01-23 10:40:52 +01:00
8e59c72c60 Merge pull request #1 from jalanb/patch-1
Add dot
2018-08-23 18:52:07 -03:00
5c24c082f5 Add dot
OMG! I've been quoted on the InterWebs.

Thank You for the awesome curation work here!

Looking forward to alanogising along your shelves.
2018-08-23 09:07:24 +01:00
550117cf15 Update README instructions 2018-06-20 07:58:07 -06:00
bcb7ba2707 Updates README with updated gui screenshot 2018-01-16 08:27:36 -07:00
e877060064 merge branch 'develop' 2018-01-12 15:36:12 -07:00
54732b1477 Merge branch 'revision-2.0' into develop 2018-01-12 15:33:51 -07:00
84cc3bf9c1 Adds probability difference to copycat/statistics 2018-01-12 15:32:53 -07:00
1506e673a2 Further cleans resulting tables 2018-01-12 15:09:13 -07:00
6215ccb08a Cleans table generation and transposes output 2018-01-12 14:30:35 -07:00
0a0369f5e1 Generalizes X^2 test to use G value 2018-01-12 14:19:11 -07:00
aed399ef34 Minor edits 2018-01-09 21:42:42 -07:00
5138126148 Reverts comparison script 2017-12-09 19:55:14 -07:00
58b0fd20eb Adds results section, additional sources: 2017-12-09 19:54:49 -07:00
6a826966a1 Spell checks draft.tex, adds sources 2017-12-09 18:44:25 -07:00
be921108ab Fixes sources + adds makefile 2017-12-09 18:08:44 -07:00
100eb11a99 Adds bibliography 2017-12-09 17:21:30 -07:00
28e1ddae30 Updates chi2 code for final comparison 2017-12-09 15:15:06 -07:00
a8b9675d2f Updates paper draft for final chi2 table 2017-12-09 15:14:53 -07:00
7eb7378ed3 Removes alpha/beta from weighted formula 2017-12-08 10:49:58 -07:00
08f332b936 Adds initial probability-adjustment-formula changes 2017-12-08 10:48:41 -07:00
81d2590fa6 Adds Theory section edits 2017-12-08 10:16:58 -07:00
9bf2980668 Merge branch 'develop' 2017-12-07 17:16:38 -07:00
0973e75813 Fixes issue with no gui 2017-12-07 17:15:20 -07:00
646cdbb484 Editing of draft.tex 2017-12-05 14:22:29 -07:00
cfee3303ba Remove some comments from draft 2017-12-04 19:36:30 -07:00
c513f66ed9 Adds revise notes to copycat draft introduction 2017-12-04 19:27:27 -07:00
4932bf6826 Adds no-fizzle distributions 2017-12-04 16:09:43 -07:00
3849632037 WIP 2017-12-04 15:40:05 -07:00
fe599c535e Adds no probability adjustment comparison 2017-12-04 13:55:53 -07:00
d61dbed3ba Adds no-adj distribution set 2017-12-04 13:48:13 -07:00
b4b1db5107 Draft progress 2017-12-04 13:39:16 -07:00
e05bf2ea19 Organizes paper drafts 2017-12-04 12:19:40 -07:00
f51379df1c Merge branch 'master' into revision-2.0 2017-12-04 12:17:19 -07:00
9c03520aeb Fixes cli/gui conflicts 2017-12-04 12:14:31 -07:00
25c0fbf3ab Merge branch 'develop' into revision-2.0 2017-12-04 12:11:32 -07:00
d1b8100c2c Merge branch 'feature-gui' into revision-2.0 2017-12-04 12:10:48 -07:00
47af738f7c Merge branch 'feature-temperature-effect-analysis' into revision-2.0 2017-12-04 12:09:51 -07:00
3d9079cfbd Merge branch 'legacy' into revision-2.0 2017-12-04 12:08:39 -07:00
93614db3dd Removes huge files 2017-12-04 12:06:34 -07:00
d605855bbc Merge branch 'paper' into revision-2.0 2017-12-04 12:06:12 -07:00
0242456138 Adds better cross-compare output 2017-12-04 12:05:07 -07:00
2f3d934a20 WIP 2017-12-04 12:03:35 -07:00
322329b646 Adds second draft structure 2017-12-04 11:46:00 -07:00
282a4307d4 Adds normal science section notes 2017-12-02 13:58:39 -07:00
bb3bdf251d Moves notes into paper structure 2017-12-02 13:36:48 -07:00
b6789b96f9 Enumerates a few of the pre-discussion points 2017-12-01 12:40:37 -07:00
4bd3983e71 Adds draft.tex, an organized paper draft to be.. 2017-12-01 12:35:37 -07:00
652511b420 Adds some final temperature-calculation notes 2017-12-01 12:23:38 -07:00
b58ec09722 Adds additional notes and plans to paper 2017-11-27 19:00:04 -07:00
95a10c902e Fixes typo 2017-11-20 18:26:30 -07:00
835c43199e Adds updated distribution and results 2017-11-19 14:30:58 -07:00
98c2b497a6 Fixes bugs with multi-formula distributions 2017-11-19 14:29:09 -07:00
6757d5b842 Adds cross-chi2 results 2017-11-19 11:39:22 -07:00
402e66409a Adds cross-chi2 comparison script 2017-11-19 11:25:11 -07:00
ee20de8297 Adds distributions from separate branches 2017-11-19 11:05:35 -07:00
bac0eb05c4 Updates distributions 2017-11-19 11:03:14 -07:00
06c5625f75 Updates distributions 2017-11-19 10:58:02 -07:00
be6d1fa495 Merge branch 'feature-normal-science-backport' into legacy 2017-11-18 18:44:24 -07:00
91dcbeefec Merge branch 'feature-normal-science-backport' into feature-temperature-effect-analysis 2017-11-18 18:44:10 -07:00
35ae49d5a4 Merge branch 'feature-normal-science-backport' into feature-normal-science-framework 2017-11-18 18:34:25 -07:00
ec9e0c333e Merge branch 'feature-normal-science-backport' into feature-gui 2017-11-18 18:32:13 -07:00
4388bede7d Removes old tests 2017-11-18 18:30:55 -07:00
9c47615c5a Fixes issues with variable initialization order 2017-11-18 18:30:23 -07:00
ee483851d8 Creates normal science backport 2017-11-18 18:25:24 -07:00
ff152c6398 WIP 2017-11-18 17:32:37 -07:00
89d26c1e8a Adds temperature history plotting 2017-11-17 11:17:15 -07:00
589b93bfc9 WIP 2017-11-17 09:58:28 -07:00
3f26d302c8 Adds gitignore 2017-11-17 09:40:28 -07:00
bd8bec2d37 WIP: Add cross-chi-2 tests 2017-11-16 08:45:11 -07:00
bdf47b634c WIP 2017-11-16 08:03:01 -07:00
d16e347f04 Adds problem class 2017-11-14 18:05:58 -07:00
57221b9c45 Adds random notes and plans 2017-11-14 17:28:43 -07:00
6951fd5de7 Adds meta and parameterized meta formulas, for fun 2017-11-14 17:16:02 -07:00
f6f5fffc78 Adds initial writeup for adjustment formulas 2017-11-14 17:08:23 -07:00
a3d693d457 Adds adjustment formulas and embeds them in paper 2017-11-13 10:49:15 -07:00
7a39d6b00d Fixes some silly wording in paper 2017-11-13 10:40:18 -07:00
ccf10b8a0c Merge branch 'feature-temperature-effect-analysis' into paper 2017-11-13 10:40:07 -07:00
20d754faa7 Changes to pbest only formula 2017-11-12 15:24:08 -07:00
906799e32d Adds old distribution data for testing against 2017-11-12 15:20:32 -07:00
45ca7ff912 Adds distribution saving and cl args 2017-11-12 14:52:46 -07:00
4ba37827e4 Adds free-form notes influenced by Von Neumann 2017-11-12 10:03:16 -07:00
1ac590ad06 Updates gitignore + WIP on paper 2017-11-07 21:18:24 -07:00
b391e2ca83 Separates paper 2017-11-06 20:48:48 -07:00
6fd1539924 Initial commit (move from openleaf) 2017-11-06 20:14:32 -07:00
782e38a50c Update README.md 2017-11-03 13:00:56 -07:00
d0c98247c6 Updates README with GUI screenshot 2017-11-03 12:59:48 -07:00
25841db648 Fixes graph updates 2017-11-03 12:56:44 -07:00
97c9b2eb57 Updates gitignore 2017-11-02 16:27:45 -07:00
6b02fe3ca0 WIP 2017-11-02 16:19:01 -07:00
ac99a2ad9a Merge branch 'feature-adjustment-formula' into feature-temperature-effect-analysis 2017-11-01 17:47:06 -07:00
d744f19986 Removes unneeded files 2017-11-01 11:15:31 -07:00
d6a073dfc8 Merge branch 'develop' 2017-11-01 11:14:24 -07:00
bb4f758a39 Merge branch 'feature-adjustment-formula' into develop 2017-11-01 11:09:42 -07:00
15c435c9f4 Merge branch 'feature-gui' into develop 2017-11-01 11:06:21 -07:00
9a5aca5b80 Adds parameterized and soft "best" formula alts 2017-11-01 11:01:40 -07:00
af72289ee5 Improves descriptor log 2017-11-01 10:41:39 -07:00
af1e2e042e Fixes bugs with pausing 2017-11-01 10:19:22 -07:00
20b25125d8 WIP 2017-10-26 19:02:39 -07:00
fe5110f00b Merge branch 'feature-gui' into develop 2017-10-26 12:55:17 -07:00
80b6e49581 WIP 2017-10-26 12:52:58 -07:00
2187511706 Adds plot saving 2017-10-26 11:00:08 -07:00
50bbb468b7 Separates style 2017-10-25 20:06:10 -07:00
8227072ebd Converts to ttk 2017-10-25 19:31:13 -07:00
8143259170 Adds interval-updated list widgets 2017-10-25 19:19:25 -07:00
cc35cb9de2 Adds play on go 2017-10-25 10:54:54 -07:00
2cf1320672 Creates a GUI specific runnable 2017-10-25 10:21:07 -07:00
3a6b2ac18f Adds logging and basic object view 2017-10-24 13:29:57 -07:00
433067a045 Fixes some displays 2017-10-22 13:15:26 -07:00
dcf5b252c3 Adds entry method 2017-10-22 13:05:28 -07:00
354470bcd7 Adds live temperature and answer graphs 2017-10-21 15:24:53 -07:00
0a8a9a8f23 Adds GridFrame and Step button 2017-10-21 14:36:04 -07:00
aa218988fd Adds play/pause button 2017-10-21 14:12:42 -07:00
0d972a6776 Removes log 2017-10-21 14:12:33 -07:00
95eb1a0b97 Updates to dark theme 2017-10-21 13:38:38 -07:00
300a0a157a WIP 2017-10-21 12:57:30 -07:00
f51525450d Creates primary gui frame class 2017-10-21 12:53:23 -07:00
9d06021f5d WIP unify GUI 2017-10-21 12:32:35 -07:00
71c7b34f63 GUI Progress: Inline animated graphs 2017-10-21 10:41:46 -07:00
aafb0de433 Makes GUI stretchy 2017-10-20 11:42:22 -07:00
397d49cc58 Random scattered analysis 2017-10-19 20:37:40 -07:00
ebee40323c WIP 2017-10-18 10:29:03 -07:00
176e6cd4e2 Adds final formula comparison code 2017-10-16 18:18:55 -07:00
a9a7a2c504 Merge branch 'develop' 2017-10-16 13:43:30 -07:00
268e00998a Merge branch 'feature-adjustment-formula' into develop 2017-10-16 13:43:01 -07:00
1835367ea9 Updates tests: All tests pass. 2017-10-16 13:41:47 -07:00
765323c3cd Updates adjustment formula to a satisfactory formula
Out of all of the formulas I've tried, I believe this formula to be a more elegant alternative to Mitchell's formula.
However, like Mitchell's original formula, it still provides weird answers in some cases, but I believe these to source from elsewhere,
not from the adjustment formula.

For more information, please see:

https://docs.google.com/spreadsheets/d/1JT2yCBUAsFzMcbKsQUcH1DhcBbuWDKTgPvUwD9EqyTY/edit?usp=sharing
wq
2017-10-16 13:21:19 -07:00
6985dedb18 Adds additional adjustment formulas 2017-10-15 14:38:48 -06:00
dd0e7d8f37 Adds codelet list display 2017-10-13 15:24:47 -06:00
fffc4863f2 Adds slipnode list 2017-10-13 15:15:45 -06:00
5d82c5186e Adds main canvas and improved temp reading 2017-10-13 14:53:36 -06:00
23def40750 Fixes main display size 2017-10-12 18:00:07 -06:00
0ba421029c Adds additional formulas, weights, and tests 2017-10-11 22:12:53 -06:00
67fdcc70e7 WIP 2017-10-11 19:58:03 -06:00
ef0e177bad WIP 2017-10-11 19:50:33 -06:00
81f2ef2473 Merge branch 'feature-adjustment-formula' into develop 2017-10-09 17:55:02 -06:00
f3386b91be Adds automated chi^2 testing 2017-10-09 17:54:26 -06:00
73558f378c Reverts to original adjustment formula for tests 2017-10-09 14:18:27 -06:00
b7c073d16b Modifies tests for inverse adjustment formula 2017-10-09 14:06:41 -06:00
6ff43f8d5a Updates tests 2017-10-09 14:01:17 -06:00
ca38b16e72 Updates travis testing installation step 2017-10-09 13:08:35 -06:00
81984d24e2 Restricts testing to develop and master 2017-10-09 13:03:12 -06:00
08ea0b2e10 Merge branch 'feature-temperature-improvements' into develop 2017-10-09 13:01:42 -06:00
f3e1c875de Adds travis testing skeleton 2017-10-09 13:01:18 -06:00
be06e22f64 Moves tests 2017-10-09 13:01:11 -06:00
73d0721286 Moves log location 2017-10-09 12:53:54 -06:00
27a55668be Experiments with alt inverse:
Equal probabilities for all items when temperature is equal to 100
2017-10-09 12:20:45 -06:00
3bf417e38a WIP 2017-10-09 11:06:16 -06:00
874683bf18 Adds clarification to breaker codelet docs 2017-10-07 23:38:48 -06:00
96c7c6e08c Cleans code, moving formula choice to copycat.py 2017-10-05 15:17:39 -06:00
c2c5d24f0d TAG: Formula testing code 2017-10-05 15:03:58 -06:00
8203cebb15 Calculate Chi^2 values for getAdj- formulas 2017-10-04 15:37:22 -06:00
b90bae2584 Adds automatic running, formula tests 2017-10-04 15:20:59 -06:00
7abb40f849 Adds problems and result saving 2017-10-04 15:20:48 -06:00
430e2b3750 Adds base temp formula 2017-09-29 17:55:24 -06:00
6b1c4634fe Fixes plot labels (slightly) 2017-09-29 16:59:26 -06:00
fa8b493948 Adds annotations and formula notes 2017-09-29 15:01:57 -06:00
665bf1f778 Adds notes to temperature.py 2017-09-29 13:47:03 -06:00
60a5274066 Adds additional plotting options 2017-09-29 13:34:26 -06:00
6142033631 Stop tracking copycat log
The log should still appear locally.
2017-09-29 13:18:03 -06:00
1c570735f8 Add simple matplotlib bar plots per run
As well as a flag to turn plotting on
2017-09-29 13:16:25 -06:00
42a875a492 Minor annotations to temperature calculations 2017-09-29 13:12:16 -06:00
c90dbd91e7 WIP 2017-09-28 15:44:41 -06:00
6d42f2c1a4 Changes default window size to 1200x800 2017-09-28 15:37:09 -06:00
33e2eb980d Fixes slipnode display 2017-09-28 15:35:15 -06:00
cd3ad65ff8 Documents usages of temperature 2017-09-28 15:04:42 -06:00
70494daf2c WIP gui changes 2017-09-28 10:53:37 -06:00
1b84b22e3f seems like the bug is in the sameness group or something very close to it 2017-09-28 01:18:13 -03:00
1cc18e75bd something is rotten somewhere 2017-09-28 00:54:07 -03:00
29b5987c4f ...and bingo! 2017-09-28 00:46:19 -03:00
9781e3ceed addtl testing... 2017-09-28 00:42:30 -03:00
4b1518a1af xyz? 2017-09-27 23:11:56 -03:00
3c8b21140d Experiments to refer to Lucas 2017-09-27 22:37:38 -03:00
75df81c8fd Experiments for Lucas email.
Merge branch 'master' of https://github.com/Alex-Linhares/co.py.cat
2017-09-27 22:33:54 -03:00
5605417e31 Preparing for refactor 2017-09-27 22:33:20 -03:00
b547306376 Preparing for refactor 2017-09-27 20:48:27 -03:00
a0048d16b5 Delete copycat.log 2017-09-27 20:34:31 -03:00
02558289ad Preparing for refactor 2017-09-27 20:32:54 -03:00
a564e43dff Preparing for refactor 2017-09-27 20:00:02 -03:00
120aa3a293 Merge branch 'master' of https://github.com/Alex-Linhares/co.py.cat 2017-09-27 16:02:39 -03:00
51e4ba64e2 Created simple jupyter notebook 2017-09-27 16:02:34 -03:00
ae24034288 WIP add gui elements 2017-09-27 12:30:42 -06:00
9a2a30ea4c Adds very simple gui to copycat 2017-09-27 11:38:32 -06:00
4348554fa7 Add simple matplotlib bar plots per run 2017-09-26 21:16:20 -06:00
27bbc6118e Preparing for refactor... 2017-09-26 22:47:09 -03:00
7ff0488861 Merge branch 'master' of https://github.com/Alex-Linhares/co.py.cat 2017-09-25 22:33:10 -03:00
0905d35680 start work om distributed decision making 2017-09-25 22:32:57 -03:00
36a1a31fe2 Update README.md 2017-09-07 19:26:10 -03:00
0a54c0ee83 Update README.md 2017-09-06 16:06:58 -03:00
729f6ec30c Merge branch 'master' of https://github.com/Alex-Linhares/co.py.cat 2017-08-28 00:02:46 -03:00
b5e35a35dd Found entry points for the research project 2017-08-28 00:02:34 -03:00
8611e415de Update README.md 2017-08-27 23:33:52 -03:00
67c87c7fde Update README.md 2017-08-27 23:28:41 -03:00
cc58c8d50a Merge pull request #1 from LSaldyt/master
Ports to Python3
2017-08-27 12:37:36 -03:00
2cdb9bbb36 Update README.md 2017-08-27 12:30:12 -03:00
197bbd361e Update README.md 2017-08-27 12:29:47 -03:00
bc848e8f2d Ports to Python3 2017-07-31 17:08:26 -06:00
318d0e2349 Fix a lot of crashes with empty or single-letter inputs. 2017-05-03 02:01:57 -07:00
2a48245c15 Add "frames per second" to the CursesReporter.
You can now set the FPS goal with `--fps=10` (or whatever) on the command line;
and the current (measured) FPS is displayed in the lower right corner.

During the run, you can bump the FPS goal up and down with `F` and `f` respectively!
2017-05-02 18:37:40 -07:00
0eec6a5259 Massively improve CursesReporter.
The Slipnet itself turns out to be boring to look at.
More interest is found in the Workspace structures, such as bonds,
groups, and correspondences.

The old behavior of `curses_main.py` is still accessible via

    python curses_main.py abc abd xyz --focus-on-slipnet
2017-05-02 18:01:46 -07:00
ef4a9c56c5 Try to fix up breakGroup.
With the new CursesReporter, I'm able to observe groups getting built
and broken; and I observed that sometimes a Bond (between a Letter and
a Group) would apparently survive the Group's breaking.
Reorder the operations in `breakGroup` so that the higher-level ones
("detach this Group from its external bonds") come strictly before
the lower-level ones ("ungroup this Group's members and remove this
Group from the Workspace, thus destroying it").

However, the "buggy" behavior I observed turned out to be due to a bug
in my display code and not due to anything wrong with `breakGroup`.
I suspect this patch is actually purely cosmetic.
2017-05-02 17:46:25 -07:00
730239f464 Rip out dead Bond.destinationIsOnRight and Bond.bidirectional. NFC. 2017-05-02 12:37:15 -07:00
5793fb887c Rip out dead method morePossibleDescriptions. NFC.
This code is already present in `getPossibleDescriptions`... which is
also a terrible function from the philosophical point of view, because
it secretly encodes knowledge about every predicate known to the system.
2017-05-02 11:33:43 -07:00
864c28609c Smartness update! A single letter is both "leftmost" and "rightmost".
Before this change, Copycat was unable to formulate more than the empty rule for
    abc : abd :: f : f
    abc : dbc :: f : f
    abc : aac :: f : f
After this change, Copycat strongly prefers
    abc : abd :: f : g  ("Replace the rightmost letter with its successor")
    abc : dbc :: f : d  ("Replace the leftmost letter with d")
    abc : aac :: f : e  ("Replace the middle letter with its predecessor")
2017-05-02 11:17:23 -07:00
ecc2c2e407 Add report_workspace() to Reporter, and remove dead rules from the workspace.
I think the change to `workspace.breakRule()` is harmless. In theory, it
should make Copycat less hesitant to come up with rules that conflict with
the already-broken rule.
2017-05-01 15:28:38 -07:00
25d73785de Further Pythonicity. NFC. 2017-05-01 13:07:19 -07:00
ceaf640147 Remove some more logging cruft. NFC. 2017-04-30 15:26:19 -07:00
c4e30f7399 Make possibleGroupBonds into a member function of Bond. NFC. 2017-04-30 15:18:19 -07:00
7947e955d7 More Pythonicisms. NFC. 2017-04-30 14:45:20 -07:00
ddfb34973d Rip out unused coderack.postings and coderack.runCodelets. NFC. 2017-04-30 10:38:42 -07:00
f9fc255151 Refactor coderack.probabilityOfPosting. NFC. 2017-04-30 10:27:55 -07:00
48c45e4b0a Fix more flake8 cruft; remove a bunch of logging. 2017-04-29 15:55:54 -07:00
c9bc26e03d Minor Pythonicisms. NFC. 2017-04-29 14:29:43 -07:00
11e9571ee0 Oops, add Reporter to the list of exported names. 2017-04-23 01:36:40 -07:00
34157be1f9 Shorten the setup.py for the copycat module. NFC. 2017-04-22 22:46:22 -07:00
9a2a1d6010 Add the Slipnet to the curses reporter.
This isn't terribly useful to the human observer, actually.
It seems like the most useful factors that ought to be displayed
are really the groups/bonds in the workspace and the current
"rule" (if any). Particularly, with the current design of Copycat,
it seems like the "rule" should be part of the displayed output
just the same as the modified target string.
2017-04-22 19:00:57 -07:00
16aae98c59 Fix a bunch of flake8 spam. NFC. 2017-04-22 18:41:48 -07:00
ec2c172ce0 Rip out some unused members of Slipnode. NFC. 2017-04-22 18:37:55 -07:00
b5b04c77a1 Remove a redundant "opposite" link from the slipnet.
This does change the micro behavior of Copycat. I would hope it doesn't
change the macro behavior, or at least changes it for the better.
2017-04-22 17:59:32 -07:00
e3e6b051d3 Whitespace. NFC. 2017-04-22 17:56:46 -07:00
3de933dbfa Redo all the argument parsing with argparse. 2017-04-22 17:53:06 -07:00
192ec2f106 Clean up some overly Java-ish base class stuff. NFC. 2017-04-18 23:44:38 -07:00
f2ffac4e66 Add a curses front-end. This is looking good now!
And clean up some logic in `rule.py`. This is the place where the
"brains" of Copycat really live, it seems; Copycat can only succeed
at solving a puzzle if it can take the `Rule` it deduced and apply
it to the target string to produce a new string. And it can only
do that if the necessary *actions* have been programmed into `rule.py`.
Right now, it explicitly can't deal with "rules" that involve more
than one local change; that involve reversal; or more importantly,
IIUC, rules that involve "ascending runs", because the idea of a
successor-group is(?) known to the Slipnet but not to `rule.py`;
the latter deals only in "strings", not in "workspace objects".
This seems like a major flaw in the system... but maybe I'm missing
something.
2017-04-18 23:18:26 -07:00
9f8bc8e66e Remove all print statements from the Copycat library.
Convert the most important one to logging; kill the rest.
2017-04-18 20:55:56 -07:00
65124fa45d Add a "setup.py" for pip-installing from GitHub.
You can now install Copycat into your Python virtualenv without even
checking out this repository! Just run this command:

    pip install -e git+git://github.com/Quuxplusone/co.py.cat.git#egg=copycat

To check out a specific branch,

    pip install -e git+git://github.com/Quuxplusone/co.py.cat.git@branch#egg=copycat
2017-04-18 18:22:32 -07:00
a3b977846e git mv context.py -> copycat.py; and start work on a "reporter" API.
The goal here is to use `curses` to display the coderack, slipnet,
and temperature in real time. A further goal would be a reporter
that sent the data over websockets to a browser, at which point
I could throw this thing up on Heroku and let people mess with it.
(Not that that would be very entertaining, yet.)
2017-04-18 01:59:51 -07:00
189bce2aa2 Remove one not-very-useful logging line. NFC. 2017-04-18 01:31:39 -07:00
db7dc21f83 Fix a crash on main.py aa b zz.
The "leftmost object" in the string `b` does span the whole string,
but it's not a `Group`, so the old code would crash when it got
to the evaluation of `group.objectList` (since `Letter`s don't have
`objectList`s).
2017-04-18 01:12:27 -07:00
fd74290d39 Clean up the handling of codelet arguments. NFC.
Just make all argument-passing explicit; which means the coderack
no longer cares about `oldCodelet` (which was being used only to
get the implicit arguments to the new codelet).
2017-04-18 01:12:27 -07:00
f08c57fac3 Fix some flake8 spam. NFC. 2017-04-18 01:12:27 -07:00
7388eaec54 Teach Context to be self-sufficient. NFC.
You can now create and run a Copycat instance by just saying
`Context().run('abc', 'abd', 'efg', 100)`!
2017-04-18 01:12:27 -07:00
12283b0836 Move some harmless imports to file scope. NFC. 2017-04-18 01:12:27 -07:00
30f8c623e5 Demagic workspaceFormulas.py. NFC. 2017-04-18 01:12:26 -07:00
3732ae8475 Major overhaul of "randomness" throughout.
- Nobody does `import random` anymore.

- Random numbers are gotten from `ctx.random`, which is an object
of type `Randomness` with all the convenience methods that used to
be obnoxious functions in the `formulas` module.

- Every place that was using `random.random()` to implement the
equivalent of Python3 `random.choices(seq, weights)` has been updated
to use `ctx.random.weighted_choice(seq, weights)`.

This has a functional effect, since the details of random number
generation have changed. The *statistical* effect should be small.
I do observe that Copycat is having trouble inventing the "mrrjjjj"
solution right now (even in 1000 test runs), so maybe something is
slightly broken.
2017-04-18 01:12:26 -07:00
8fdb9d06e6 Demagic everything in formulas.py. NFC.
Only one file left to go!
2017-04-18 01:12:26 -07:00
6165f77d3c Move a couple single-use helpers from formulas to codeletMethods. NFC. 2017-04-18 01:12:26 -07:00
ff389bd653 Move temperatureAdjustedFoo into the Temperature class. NFC.
And demagic all the callers of this function. Notice that with this
move, it becomes *harder* for these "getAdjustedFoo" functions to
access other contextual state, such as the state of the coderack
and the state of the workspace. This is a good thing for modularity
but possibly a misfeature in terms of flexibility-re-logic-changes.
2017-04-18 01:12:26 -07:00
99dc05f829 Demagic everything except the formulas and workspaceFormulas. NFC. 2017-04-18 01:12:26 -07:00
7581a328f7 Give WorkspaceString a self.ctx. Demagic all WorkspaceObjects. NFC. 2017-04-18 01:12:26 -07:00
bd4790a3f1 Kill all the globals (except context)! NFC. 2017-04-18 01:12:25 -07:00
22b15c3866 Demagic all the WorkspaceStructure children who aren't WorkspaceObjects. NFC. 2017-04-18 01:12:25 -07:00
b16666e4d7 Demagic WorkspaceStructure. NFC. 2017-04-18 01:12:25 -07:00
482c374886 Give every WorkspaceStructure a self.ctx member variable.
...which is currently initialized "by magic"; but removing that magic
will be the next step.
2017-04-18 01:12:25 -07:00
25ba9bfe93 (Almost) contextualize all the things! NFC.
The only top-level imports now are needed for inheritance relationships.

The only function-level imports are HACKS that I need to FIXME; they
all `from context import context as ctx` and then fetch whatever they
actually need from the `ctx` just as if `ctx` had been passed in by the
caller instead of fetched from this magical global storage.
2017-04-18 01:12:25 -07:00
3096c49fb9 This is working! 2017-04-18 01:12:24 -07:00
e6cbb347de testing 2017-04-18 01:12:24 -07:00
965bd13298 Disentangle another reference to slipnet. 2017-04-18 01:12:24 -07:00
6871d7a86c Disentangle one reference to slipnet. 2017-04-18 01:12:24 -07:00
cc288161a4 Major overhaul of temperature logic. Behavioral change.
I think the reason the temperature logic was so confused in the old code
is because the Java code has a class `Temperature` that is used for
graphical display *and* two variables in `formulas` that are used for
most of the actual math. But somewhere along the line, some of the code
in `formulas.java` started reading from `Temperature.value` as well.
So the Python code was just faithfully copying that confusion.

The actual abstraction here is a very simple "temperature" object
with a stored value. It can be "clamped" to 100.0 for a given period.
The only complication is that one of the codelets (the rule-transformer
codelet) wants to get access to the "actual value" of the temperature
even when it is clamped.

The Python rule-transformer codelet also had a bug: it was accidentally
setting `temperature.value` on the `temperature` module instead of on
the `temperature.temperature` object! This turned some of its behavior
into a no-op, for whatever that's worth.

Lastly, the calculation of `finalTemperature` in the main program can
now report 100.0 if the answer is found while the temperature is clamped.
I don't fully understand why this didn't happen in the old code.
I've hacked around it with `temperature.last_unclamped_value` for now,
but I should TODO FIXME.
2017-04-18 01:12:24 -07:00
6a56fdd898 Bikeshed some time-related names. 2017-04-18 01:12:23 -07:00
e5d44ae75c Bah! Remove CoderackPressures as it's not hooked up to anything. 2017-04-18 01:12:23 -07:00
44e5a8c59f Decouple Coderack from Slipnet. 2017-04-18 01:12:23 -07:00
e17dc2aa45 Untangle some initialization code. Assert invariants. NFC. 2017-04-18 01:12:23 -07:00
fa2142efaa Replace the coderack->workspaceFormulas coupling with coderack->workspace.
This feels slightly less tangled. Still needs work.
2017-04-18 01:12:23 -07:00
63b3fd4999 Decouple Slipnode from the global slipnet. 2017-04-18 01:12:23 -07:00
10f65fcf55 Inline the constant slipnet.timeStepLength. NFC. 2017-04-18 01:12:23 -07:00
d2436601ba Decouple coderack: remove global variable coderack.
Or at least, hide it upstairs in `copycat.py`.
`copycat.py` will eventually become a class, I'm guessing,
but let's pull everything into it first.
2017-04-18 01:12:22 -07:00
f2e28c0e19 Clean some dead code in __calculateIntraStringHappiness.
Indeed, it's dead in the Java version too.
2017-04-18 01:12:22 -07:00
ae0434d910 codeletMethods.py: Replace some random.random()s with coinFlip().
There is a bugfix in here as well: one of the probabilities was
being taken the wrong way around. The result should have been to
make single-letter groups very often, I guess? Fixing this bug
doesn't show any change in Copycat's macro behavior, except that
it seems like average temperatures have gotten hotter.
2017-04-18 01:12:22 -07:00
a3b122b75c Massive overhaul of "codelet methods" and the coderack.
Should be no functional change, but this gets rid of one circular
import (some codelet methods need a pointer to the coderack, but
they should be getting that pointer from their caller, not from
the global scope) and a lot of reflection-magic.
2017-04-18 01:12:22 -07:00
f37b88d032 random.seed(42) for testing; TODO revert me 2017-04-18 01:12:22 -07:00
3d630ba389 Decouple temperature from coderack. 2017-04-18 01:12:21 -07:00
51178c049d Inline trivial function Bond.get_source(). NFC. 2017-04-18 01:12:21 -07:00
a41b639487 Remove global variable coderackPressures (bugfix?)
Before this patch, `coderackPressures.updatePressures()` was always
a no-op, as evidenced by the until-now-harmless misspelling of Python's
list `remove` operation as `removeElement`.

I can't tell if this broke anything; my tests still pass.
2017-04-18 01:12:21 -07:00
5423d078e8 Move updateTemperature() from workspaceFormulas to workspace.
And remove dead and/or logging code to simplify the logic.
2017-04-18 01:12:21 -07:00
8171b68cbe Remove some unused global variables. NFC. 2017-04-18 01:12:20 -07:00
6fcf2f3350 git rm grouprun.py 2017-04-18 01:12:20 -07:00
d60ba5277c Remove a crash-causing line in slipnet.py.
Without this patch, `python main.py abc aabbcc milk` will reliably crash.
I believe what happens here is that we initialize all the slipnodes and
everything, and then `slipnet.predecessor` becomes `None`, which means
that if that concept ever arises on its own (vs. arising as the "opposite"
of "successor"), we'll be passing around `None` instead of a proper `Slipnode`
and everything goes sideways.

This line doesn't correspond obviously to anything in the Java code,
so I think it's just bogus --- an experiment in "brain damage" that was
accidentally committed?
2017-04-18 01:07:51 -07:00
0ff9d49111 Further Pythonicity and flake8 cleanup. NFC. 2017-04-16 18:22:57 -07:00
31323cd2bc Add some end-to-end tests!
These tests are intended to protect against regressions in the high-level
behavior of Copycat. They're not super precise, and they're VERY slow.
2017-04-16 18:22:57 -07:00
8e10814802 Further Pythonicity; and remove a bunch of logging from the inner loop. 2017-04-16 01:19:36 -07:00
77bfaaf5a7 Further refactor the main harness. Print average time for each solution. 2017-04-16 00:55:18 -07:00
3103f54ada Untie some loopy logic in addCodelet. (Functional change!) 2017-04-15 23:08:12 -07:00
e094160dcd More Pythonic cleanups. NFC. 2017-04-15 23:07:28 -07:00
a2260cdaf6 Run multiple iterations. Print final temperatures. Reduce stdout spew.
This makes the output of the program more closely resemble that of the
original Copycat described in "FCCA" page 236:

> [T]he average final temperature of an answer can be thought of as
> the program's own assessment of that answer's quality, with lower
> temperatures meaning higher quality.

For example, running `python main.py abc abd ijk 100` produced the
following output:

    ijl: 98 (avg temp 16.0)
    jjk: 1 (avg temp 56.3)
    ijk: 1 (avg temp 57.9)

And for `python main.py abc abd ijkk 100`:

    ijkkk: 2 (avg temp 19.8)
    ijkl: 51 (avg temp 28.1)
    ijll: 46 (avg temp 28.9)
    djkk: 1 (avg temp 77.4)
2017-04-15 22:29:46 -07:00
ed1d95896e More Pythonic idioms in coderackPressure.py.
No functional change.
2017-04-15 22:29:46 -07:00
88ee2ddd8d Spelling: neighbour -> neighbor.
The old code mixed both spellings; we might as well be consistent.
2017-04-15 22:29:46 -07:00
5735888d02 Minor Pythonicity cleanups.
No functional change.
2017-04-14 11:37:43 -07:00
69f75c3f42 Spelling: slipability -> slippability
No functional change.
2017-04-14 11:19:25 -07:00
bcfd8a5a10 Ignore mccabe complexity smells 2015-10-28 01:34:16 +00:00
c46e3b6db0 Allow more complex functions in Landscape 2015-10-28 01:29:43 +00:00
52402b99b3 Add Landscape configuration 2015-10-28 01:25:54 +00:00
aeb8cda755 Tidy references (which were broken by daeff3d) #5 2015-06-01 10:52:20 +01:00
daeff3d9bf Pylint the code 2014-12-22 23:44:09 +00:00
a5930b486c PEP 8 - line length 2014-12-22 20:18:54 +00:00
39fb7fc9b7 outdent 2014-12-22 16:56:53 +00:00
d4bb38b858 Github calls it sh, not shell 2014-12-22 16:56:16 +00:00
98357913e9 Make a separate para of final instruction 2014-12-22 16:53:02 +00:00
0f51434191 Better linkage #4 2014-12-22 16:50:15 +00:00
c38102c02e Extend readme to explain install & run, #4 2014-12-22 16:44:51 +00:00
94a0ecae48 PEP 008, mostly lines too long 2014-12-22 16:38:10 +00:00
c0971ce029 Link to a license file actually breaks license
Namely "copyright notice and this permission notice shall be included in all copies"
2014-03-21 12:25:58 +00:00
e58e449be2 Consistent licencing across projects 2013-08-16 10:24:41 +01:00
331114ebc3 Merge pull request #3 from jtauber/master
improved PEP compliance and fixed errors preventing it from running
2012-12-10 07:59:00 -08:00
8332b1387e fixed indentation problem 2012-12-01 02:15:25 -05:00
ab27b745be fixed missing random imports 2012-12-01 02:13:31 -05:00
b939f3ec3f fixed conceptual_depth for conceptualDepth 2012-12-01 02:12:22 -05:00
2281870cf2 removed unnecessary utils 2012-12-01 02:10:33 -05:00
33cf41b585 fix linter errors and warnings 2012-12-01 02:00:03 -05:00
cfaebd150f tabs to spaces 2012-11-30 02:12:44 -05:00
1ca7f1839f proper nouns don't take articles 2012-11-30 02:03:59 -05:00
53149013cc avoid duplicitous wording 2012-11-20 21:54:15 +00:00
b7b2a738b0 Ignore filesystem and editor files 2012-11-20 21:53:54 +00:00
86f0bf8016 Spell my own name correctly! 2012-10-26 18:25:24 +01:00
b3d46f3a68 Don't ignore unused files 2012-10-26 18:22:34 +01:00
feae97a988 Simpler returns 2012-10-26 18:20:26 +01:00
331b1ad3eb Separate out the main method 2012-10-26 18:20:15 +01:00
073f4fe05c I like to think Mr Hofstadter would appreciate the self-reference 2012-10-26 17:54:13 +01:00
47c0b457b3 Add license 2012-10-26 17:50:00 +01:00
b12ae322eb Make a package from the python scripts 2012-10-26 17:40:20 +01:00
d58dca3309 That's "Hofstadter" to me 2012-10-26 17:38:37 +01:00
5462c033ab Initial addition of Python scripts 2012-10-26 17:35:08 +01:00
90eb4a7b2a Initial commit 2012-10-26 09:16:21 -07:00
113 changed files with 10428 additions and 167 deletions

197
.gitignore vendored
View File

@ -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

View File

@ -0,0 +1,6 @@
{
"cells": [],
"metadata": {},
"nbformat": 4,
"nbformat_minor": 2
}

10
.landscape.yaml Normal file
View File

@ -0,0 +1,10 @@
pylint:
options:
dummy-variables-rgx: _
max-branchs: 30
pyflakes:
run: false
mccabe:
run: false

BIN
.old_distributions Normal file

Binary file not shown.

12
.travis.yml Normal file
View 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
View 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
View 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
View File

@ -1,3 +1,104 @@
# copycat
Copycat
=========
Open Source copycat (python)
![GUI](https://i.imgur.com/AhhpzVQ.png)
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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

View 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
View 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
View 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

View 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
View 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
)

View 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
View 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
View 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
View 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

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

View 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
View 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)

View 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
View 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

View 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
View 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
View 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
View File

@ -0,0 +1 @@
from .gui import GUI

59
copycat/gui/control.py Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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")

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

View 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
View 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

View 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
View 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
View 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
View File

@ -0,0 +1,4 @@
1,2
3,4
7,7
100,100

27
copycat/sliplink.py Normal file
View 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

View 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
View 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
View 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
View 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

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

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

View 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
View 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]

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

View 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
View 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

View 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

View 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

View 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

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

View 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

View 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
View 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
View 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
View 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
View 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

Binary file not shown.

BIN
distributions/.legacy Normal file

Binary file not shown.

BIN
distributions/.no-adj Normal file

Binary file not shown.

BIN
distributions/.no-fizzle Normal file

Binary file not shown.

BIN
distributions/.no-prob-adj Normal file

Binary file not shown.

BIN
distributions/.nuke-temp Normal file

Binary file not shown.

BIN
distributions/.soft-remove Normal file

Binary file not shown.

28
gui.py Executable file
View 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
View 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
View File

9
input/problems.csv Normal file
View 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
1 abc abd ijk
2 aabc aabd ijkk
3 abc abd kji
4 abc abd mrrjjj
5 abc abd rssttt
6 abc abd xyz
7 abc abd ijjkkk
8 rst rsu xyz
9 abc abd xyyzzz

View File

@ -0,0 +1,4 @@
abc,abd,ijk
aabc,aabd,ijkk
abc,abd,xyz
abc,abd,ijjkkk
1 abc abd ijk
2 aabc aabd ijkk
3 abc abd xyz
4 abc abd ijjkkk

75
main.py Executable file
View 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
View 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
View File

12
papers/Makefile Normal file
View 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
View 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
View 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
View 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 positions 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}Copycats 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}Copycats 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}Copycats 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