# Source: README.md
CREATING INTELLIGENCE
# Documentation
This is a software framework for cognitive computing, based on hyperdimensional sparse representations.
It is used for modeling and simulating spiking neural networks that transmit sparse discrete codes between instances of Topological Associative Memory. In this framework, mathematical sets are the fundamental datatype, representing either Sparse Distributed Representations (SDRs) or Sparse Holographic Representations (SHRs).
## LLM-ready documentation
Download [llms-docs.txt](https://creatingintelligence.org/llms-docs.txt) to manually upload this documentation into an AI chat context window.
## Getting started
- [Installation](installation.md)
- [Quickstart](quickstart.md)
## Software architecture
This framework is divided into two layers:
1. **Circuits frontend:** Orchestrates the dataflow between memory instances
2. **Memory backend:** The core topological associative memory algorithm
The Circuits frontend is scripted via JSON configuration files. No programming is required
to set up networks based on standard components.
The package implements a data-driven software paradigm, using configuration dictionaries
and closures to encapsulate stateful functions. This architecture entirely avoids class hierarchies. Custom circuit components can be plugged into the framework simply by inserting them into the package's namespace.
## Platform support
The Circuits package is available for Python and Mathematica. Both versions are functionally identical.
The Memory backend is implemented in Standard C as a zero-dependency library, and
integrated with Python and Mathematica via foreign function interfaces.
Literal implementations of the core memory algorithm are available for Python and Mathematica, serving as a baseline for derived and experimental memory variants.
# Source: installation.md
GETTING STARTED
# Installation
See also: [Quickstart](quickstart.md)
## Install the built distribution
```python
pip install creating-intelligence
```
Release information: https://pypi.org/project/creating-intelligence/
## Build from source
```bash
git clone https://github.com/peterovermann/creatingintelligence
creatingintelligence/src/python/setup.sh
```
Building the package from source requires a C development environment.
## Optional components
Graphviz (*dot*) is required for advanced circuit schematics. If it is not present at runtime, visualization features will use a fallback layout.
Install Graphviz manually if needed:
- **Windows:** winget install Graphviz.Graphviz
- **macOS:** brew install graphviz
- **Ubuntu/Debian:** sudo apt-get install graphviz
- **Fedora:** sudo dnf install graphviz
- **Arch:** sudo pacman -S graphviz
# Source: quickstart.md
GETTING STARTED
# Quickstart
See also: [Installation](installation.md)
## Circuits frontend
Circuit configurations are specified by a either a Python dictionary or the equivalent JSON string:
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "plugin": "codec", "send": [1]},
{"component": "output", "plugin": "codec", "receive": [1]}
]}
```
The above configuration rendered as schematics:

The `Circuit` factory function compiles a circuit configuration (dict or JSON) and returns a dispatch dictionary. This output includes a `function` property, representing the set-processing function defined by the circuit.
The circuit `function` takes one argument per input component, and returns a tuple consisting of one element per output component.
The default type of input and output blocks is mathematical sets or multisets. Excitatory
signals are represented as positive integers, while their negative counterparts represent
inhibitory signals. Optional input encoders and output decoders convert between external datatypes and the internal set-based representations.
**Code**
```python
from creating_intelligence import Circuit
config = {
'hyperparameters': {'default': [1000, 10]},
'dataflow': [
{'component': 'input', 'plugin': 'codec', 'send': [1]},
{'component': 'output','plugin': 'codec', 'receive': [1]}
]
}
circ = Circuit(config)
print(circ['function']('Hello, World!'))
```
**Output**
```text
('Hello, World!',)
```
Inspect the dispatch dictionary:
**Code**
```python
from pprint import pprint
pprint(circ)
```
**Output**
```python
{'clear': .clear at 0x10a0b9640>,
'function': .f at 0x10a0b94e0>,
'nodes': . at 0x10a0b96f0>,
'receive_blocks': [[1000, 10]],
'schematics': .schematics at 0x10a0b9590>,
'send_blocks': [[1000, 10]]}
```
Properties of the output dictionary:
| Property | Description |
|:------------|:------------------------------------------------|
| `function` | the circuit's function interface |
| `clear` | circuit destructor function |
| `receive_blocks` | list of hyperparameters [N,P] per input component |
| `send_blocks` | list of hyperparameters [N,P] per output component |
| `nodes` | internal configuration details |
| `schematics` | circuit schematics as PNG file |
## Memory backend
Like the `Circuit` frontend, the `Memory` backend is constructed via a factory function.
This mechanism is usually encapsulated within the circuit's memory components.
Here we create a standalone Topological Associative Memory instance:
**Code**
```python
from creating_intelligence import Memory
config = { "A_parameters": [1000, 10],"B_parameters": [1000, 10]}
mem = Memory(config)
from pprint import pprint
pprint(mem)
```
**Output**
```python
{'A_parameters': (1000, 10),
'B_parameters': (1000, 10),
'T': 7,
'backend': 'c_ffi',
'clear': .clear at 0x10d7fcd50>,
'memorycount': .memorycount at 0x109b95380>,
'retrieve': .retrieve at 0x109a65c70>,
'store': .store at 0x109a65d20>}
```
Store an autoassociation in memory, then retrieve it from a partial, noisy query pattern:
**Code**
```python
mem['store']([1,2,3,4,5,6,7,8,9,10])
mem['retrieve']([1,2,3,4,5,6,7,50,51,52,53,54,55,56])
```
**Output**
```python
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
```
Properties of the output dictionary:
| Property | Description |
|:------------|:------------------------------------------------|
| `A_parameters` | input layer hyperparameters [N,P] |
| `B_parameters` | output layer parameters |
| `T` | retrieval pattern matching threshold |
| `store` | write to memory |
| `retrieve` | read from memory |
| `memorycount` | memory usage (bits) |
| `backend` | backend version identifier |
| `clear` | destructor function |
This framework includes multiple, functionally equivalent implementations of the
Topological Associative Memory backend By default, a performance-optimized version
written in Standard C is used. Switch to a native Python
backend by setting this environment variable:
```python
MEMORY_BACKEND="python"
```
The same mechanism allows custom backend versions to be plugged into the framework
and selected via the environment variable.
# Source: circuits.md
CIRCUIT CONFIGURATON
# Circuits
Within this framework, circuits represent networks of Topological Associative Memory instances encapsulated in circuit components and connected through pathways.
See also: https://creatingintelligence.org/#circuits
## Circuit configuration
Circuits are specified by a JSON string or the equivalent Python dictionary:
```json
{ "$schema": "https://creatingintelligence.org/schemas/circuit-v1.json",
"options": {"name": "Reservoir"},
"hyperparameters": {"default": [2000, 8], "61": [2000, 8]},
"dataflow": [
{"component": "input", "plugin": "codec", "send": [11]},
{"component": "delay", "send": [21], "receive": [11]},
{"component": "heteroencoder", "send": [51], "receive": [11, 21]},
{"component": "delay", "send": [61],
"receive": [[51, {"slot": 62, "tags": ["permute"]}]]},
{"component": "delay", "send": [62],
"receive": [61], "rate_limit": 3.0},
{"component": "predictor", "send": [101], "receive": [11, [11, 61]]},
{"component": "output", "plugin": "codec", "receive": [101]}
]}
```
The above configuration rendered as circuit schematics:

## Details and properties
- Circuits are composed of stateful [components](components.md) connected through stateless [pathways](pathways.md).
- Dataflow is synchronized, governed by a global clock.
- At every time step, components process their current input and pass it into the output buffer.
- A circuit must include at least one input and one output component.
- Circuits can be [nested](circuit.md).
| Property | Description |
|:------------|:----------------------------------------|
| `$schema` | link to JSON schema |
| `options` | global circuit options |
| `hyperparameters` | pathway dimensions and populations |
| `dataflow` | nodes and edges of the circuit graph |
#### options
| Property | Description |
|:------------|:------------------------------------------------|
| `name` | the circuit's registry identifier, used for embedding circuits |
| `multiplex` | temporal gating pattern, given as a list of 0s or 1s (optional) |
#### hyperparameters
| Property | Description |
|:------------|:------------------------------------------------|
| `default` | default slot dimension and population [N, P] |
| `integer` | a specific slot's dimension and population [N, P] |
#### dataflow
| Property | Description |
|:------------|:------------------------------------------------|
| `component` | the component's factory function name |
| `plugin` | plugin factor function name (optional) |
| `send` | output slots, given as a list of integers |
| `receive` | list of input slots or tagged pathways |
# Source: components.md
CIRCUIT CONFIGURATON
# Components

Components are the fundamental building blocks of circuits, representing stateful operations on sparse sets or multisets.
See also: https://creatingintelligence.org/#circuits
## List of all circuit components
| Component | Functionality |
|:---------------------|:----------------------------------------------------------------------|
| [`input`](input.md) & [`output`](output.md) | Gateway components that connect to the circuit's function interface, reducing multisets to sets by default while optionally loading encoder or decoder plugins. |
| [`delay`](delay.md) & [`noise`](noise.md) | [`delay`](delay.md) defers incoming signals by one cycle and integrates them temporally based on update rules; [`noise`](noise.md) generates pseudo-random sparse sets without requiring input blocks. |
| [`circuit`](circuit.md) | Embeds nested circuits locally via the `file` plugin or globally via the `shared` registry plugin. |
| [`auto`](auto.md) | Auto-associative memory that incrementally learns at each cycle, applying update rules to manage how the retrieved pattern interacts with query pattern. |
| [`temporal`](temporal.md) | Temporal associative memory that maps temporal states to incoming inputs, automatically learning higher-order sequences on the fly when predictions fail. |
| [`associator`](associator.md) | Hetero-associative memory for supervised learning that resolves multiset and inhibitory input, trains when the label input block is non-empty, and infers when the label block is empty. |
| [`predictor`](predictor.md) | Hetero-associative memory for predictive learning that associates data from the previous cycle with the current label. |
| [`heteroencoder`](heteroencoder.md) | Hetero-associative memory for unsupervised learning that maps similar sets to stable SHRs on the fly, generating and learning new unique tokens for unknown inputs. |
## Details and properties
- Data flow components, such as [`delay`](delay.md), control the temporal integration of data over multiple timesteps.
- Memory components, such as [`auto`](auto.md) or [`temporal`](temporal.md), encapsulate a topological associative memory instance.
- Excitatory signals are processed as positive integers, whereas inhibitory signals are processed as negative integers.
- Circuit components generally maintain a state across multiple execution cycles, whereas pathways are strictly stateless.
- The functionality of components can be extended via a standardized plugin mechanism.
- This framework supports the seamless integration of user-defined, custom components and plugins.
- Certain components may receive and send multiple signal partitions (block coding).
- Components are visualized as circles (data flow components) or squares (memory components) in circuit schematics.
| Property | Description |
|:------------|:----------------------------------------|
| `send` | output slots, as a list of integers |
| `receive` | list of input slots or tagged pathways |
| `plugin` | extension module |
| `label` | user-defined vertex label in circuit schematics |
| `shape` | vertex shape ("square", "circle", or "none")
| `fill` | background color (palette index 0 to 15) |
| `size` | font size, given in points |
| `color` | font color (palette index 0 to 15) |
## Sending and receiving data
A pathway is defined via a slot number that occurs in one component's `send`
parameters and in another (or the same) component's receive parameter. The choice
of slot numbers is arbitrary. The same slot can be received by multiple components (multi-casting). However, the same slot number must not occur
in multiple `send` parameters.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "output", "receive": [1]}
]}
```

Lists of slot numbers denote disjoint blocks. Here the [`delay`](delay.md) component
receives and sends two disjoint set partitions:
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "input", "send": [2]},
{"component": "delay", "send": [11, 12], "receive": [1, 2]},
{"component": "output", "receive": [11]},
{"component": "output", "receive": [12]}
]}
```

`receive` parameters wrapped in a list denotes signal merging:
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "input", "send": [2]},
{"component": "delay", "send": [11], "receive": [[1, 2]]},
{"component": "output", "receive": [11]}
]}
```

A circuit that combines pathway merging and block coding:
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1], "label": "in1"},
{"component": "input", "send": [2], "label": "in2"},
{"component": "delay", "send": [11, 12], "receive": [1, 2]},
{"component": "output", "receive": [11], "label": "out1"},
{"component": "output", "receive": [[12, 1]], "label": "out2"}
]}
```

A simple feedback system, routing the component's output slot directly back
into its input:
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "delay", "send": [11], "receive": [[1,11]], "rate_limit": 1},
{"component": "output", "receive": [11]}
]}
```

## Encoders, decoders, and update rules
Plugins extend the functionality of base circuit components while inheriting their properties and hyperparameters.
Update rule plugins govern state updates in [`auto`](auto.md), [`delay`](delay.md), and [`temporal`](temporal.md) components to control temporal integration and associative behavior.
The [`input`](input.md) and [`output`](output.md) components can be extended with encoder and decoder plugins, respectively.
| Plugin Type | Implementations |
|:---------------------|:----------------------------------------------------------------------|
| Encoders & decoders | [`codec`](codec.md) maps symbolic tokens to random SHRs using a globally shared lexicon based on auto-associative pattern matching. [`category`](category.md) encodes integers 0 to K-1 as non-overlapping SHRs. [`binning`](binning.md) clusters similar SDRs into categorized bins based on matching thresholds. |
| Vector encoders | [`vectorencoder`](vectorencoder.md) projects dense vectors into hyperdimensional space using sparse binary matrices and kWTA. [`flyhash`](flyhash.md) applies the classic flyhash algorithm to achieve similar SDR mapping for real-valued vectors. |
| Base update rules | [`replacement`](replacement.md) entirely substitutes the previous state with new data, bypassing capacity limits. [`augmentation`](augmentation.md) computes the multiset aggregation of signals, which converges incrementally to a stable state equivalent to a deduplicated set union. |
| Subtractive rules | [`residual`](residual.md) removes the retrieved pattern from the memory’s state to leave only novel elements for anomaly detection. [`difference`](difference.md) applies a symmetric multiset difference to enable generative retrieval behavior. [`complement`](complement.md) removes the current incoming signal from the state entirely. |
| Filtering & sequence rules | [`coincidence`](coincidence.md) retains only matching elements between query and retrieved patterns. [`permutation`](permutation.md) replaces the temporal state with its permutation before augmenting it with current signals, thereby preserving sequence order in temporal tracking. |
In the following example, the [`input`](input.md) component loads an encoder plugin,
the [`delay`](delay.md) component is extended via an update rule plugin, and the
[`output`](output.md) component uses a decoder plugin.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "plugin": "codec", "send": [1]},
{"component": "delay", "plugin": "latch", "send": [11],
"receive": [1], "capacity": 1},
{"component": "output", "plugin": "codec", "receive": [11]}
]}
```

As shown in the example above, plugins typically modify the visual appearance of components in circuit schematics.
## Component visualization
In circuit schematics, you can customize individual components' `label`, `color`, `fill`, and `size` properties:
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1],
"label": "A", "color": 11, "size": 20},
{"component": "output", "receive": [1],
"label": "B", "fill": 15, "size": 12}
]}
```

# Source: pathways.md
CIRCUIT CONFIGURATON
# Pathways

Pathways represent directed, stateless connections between circuit components,
transporting sparse sets or multisets.
See also: https://creatingintelligence.org/#circuits
## Details and properties
- Pathways are strictly stateless, whereas circuit components generally maintain a state across multiple execution cycles.
- The dataflow along pathways is clocked and globally synchronized.
- By default, a pathway transports sets, automatically removing duplicate and inhibitory elements.
- Pathways can be configured to transport multisets (duplicate elements) and inhibitory signals (negative elements).
- Pathways may be configured to modify their payload.
- Dataflow within a circuit can be controlled via pathway merging options.
- Within the dataflow description format, pathway properties are specified within the components' `receive` parameters.
| Property | Description |
|:------------|:----------------------------------------|
| `slot` | integer slot number, linking to an upstream component |
| `tags` | list of signal flow modifiers |
| `gating` | implements temporal multiplexing, specified by a recurring array of 0s and 1s, to close or open paths at specific intervals aligned with the global clock |
## Pathway tagging
A regular pathway:
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "output", "receive": [1]}
]}
```

The same network with a tagged pathway:
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "output", "receive": [{"slot": 1, "tags": ["permute"]}]}
]}
```

This setup merges two pathways, each individually tagged:
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "input", "send": [2]},
{"component": "output",
"receive": [[{"slot": 1, "tags": ["permute"]},
{"slot": 2, "tags": ["threshold"]}]]}
]}
```

## Gated pathways
The `gating` property opens and closes the pathway at specific time
intervals. In the following example, the pathway transports data for two
cycles, then blocks for one cycle. All repeating gating patterns are aligned to
start simultaneously with the global clock.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "output", "receive": [{"slot": 1, "gating": [1, 1, 0]}]}
]}
```

## Signal modifications
| Tag | Display | Description |
|:------------|:-----:|:----------------------------------------|
| `multiset` | blue arrow | enables multiset signals |
| `inhibit` | red arrow | enables multiset signals, flips positive elements to negative (inhibitory) elements |
| `permute` | π | applies a permutation unique to the specified path |
| `rate_limit` | R | caps the population at the path's default population |
| `threshold` | T | clears signals that fall below the path's default population |
| `noise` | ~ | generates random noise if signal is non-empty, otherwise clearing the path |
This pathway conveys multisets:
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "output", "receive": [{"slot": 1, "tags": ["multiset"]}]}
]}
```

Inhibitory pathways transport multisets, flipping all positive elements to negative elements. Within this framework, this mechanism is the exclusive source of inhibitory signals apart from external input.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "output", "receive": [{"slot": 1, "tags": ["inhibit"]}]}
]}
```

## Pathway merging and signal flow control
| Tag | Display | Description |
|:------------|:-----:|:----------------------------------------|
| `veto` | X | clears all paths if a veto path is non-empty |
| `mandatory` | * | clears all paths if a mandatory path is empty |
| `priority` | ! | clears all non-priority paths if a priority path is non-empty |
| `dependency` | & | clears dependency paths if any non-dependency path is empty (AND) |
| `fallback` | | | clears fallback paths if any non-fallback path is non-empty (NOR) |
| `barrier` | = | clears barrier paths if any barrier path is empty |
| `kwta_excitatory` | K | filters for top-K excitatory signals, with K taken to be the path's default population |
| `kwta_absolute` | k | filteres for top-k signals based on total saliency, with k taken to be the path's default population |
In this circuit, to paths are merged via kWTA:
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "input", "send": [2]},
{"component": "delay", "send": [11],
"receive": [[{"slot": 1, "tags": ["kwta_absolute"]},
{"slot": 2, "tags": ["kwta_absolute"]}]]},
{"component": "output", "receive": [11]}
]}
```

## Schematics rendering and data logging
| Tag | Display | Description |
|:------------|:-----:|:----------------------------------------|
| `label` | *string* | custom edge label |
| `show_slot` | *integer* | render the path's slot number |
| `show_dimension` | *N* | render the path's dimension hyperparameter |
| `show_population` | *P* | render the path's population hyperparameter |
| `log` | ? | print the path's payload to the console at every timestep |
Customize edge labels:
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "input", "send": [2]},
{"component": "delay", "send": [7], "receive":
[[{"slot": 1, "label": "P=", "tags": ["show_population"]},
{"slot": 2, "label": "N=", "tags": ["show_dimension"]}]]},
{"component": "output", "receive":
[{"slot": 7, "label": "slot=", "tags": ["show_slot"]}]}
]}
```

Tagging a pathway with `log` prints its payload to the console
at every evaluation cycle.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "output", "receive": [{"slot": 1, "tags": ["log"]}]}
]}
```

# Source: input.md
CIRCUIT COMPONENTS
# input

A gateway component that receives data from the circuit's function interface,
optionally encoding it through a plugin.
## Details and properties
- Circuits may have multiple [`input`](input.md) nodes, each representing one block of data.
- Supports encoder plugins, mapping external data representations to sets.
- Transparently handles multisets and inhibitory signals.
- Sends data downstream without delay.
| Property | Default | Description |
|:------------|:-----:|:----------------------------------------|
| `send` | *required* | `[integer]` representing a single output slot |
| `plugin` | | encoder plugin |
Use standard [style options](components.md) to customize the component's appearance in circuit schematics.
## Basic input and output
This is the simplest possible circuit, routing signals directly from
the `input` node to the [`output`](output.md) node.
Multiset or inhibitory inputs are reduced to sets along the pathway.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "output", "receive": [1]}
]}
```

## Multiple inputs
Each input node represents a disjoint partition (or block) of the overall input signal.
This merges the signals from two input nodes into a single output node. Note
that the list notation in the `receive` parameter denotes merging.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "input", "send": [2]},
{"component": "output", "receive": [[1, 2]]}
]}
```

## Multisets
This circuit routes multisets from the input to the output.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "output",
"receive": [{"slot": 1, "tags": ["multiset"]}]}
]}
```

## Inhibitory multisets
The following circuit transports multiset data including inhibitory signals. Note that inhibitory pathways implicitly carry multisets.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "output",
"receive": [{"slot": 1, "tags": ["inhibit"]}]}
]}
```

## Encoding input data
The `input` component supports encoder plugins that convert external data
to sets.
The following circuit uses the [`codec`](#codec.md) plugin which encodes symbolic tokens as random Sparse Holographic Representations (SHRs). Here, the [`output`](#output.md) component uses the
same plugin instance to decode the SHRs, exactly reversing the mapping.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "plugin": "codec", "send": [1]},
{"component": "output", "plugin": "codec", "receive": [1]}
]}
```

This uses the [`flyhash`](flyhash.md) encoder:
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "plugin": "flyhash", "send": [1]},
{"component": "output", "receive": [1]}
]}
```

## Source code
```python
def input(config):
"""
At the beginning of each cycle, external input is pushed
into the input's send slots. Input nodes have no "receive" slots.
The callback function serves as an optional encoder/preprocessing plugin.
"""
pluginconfig = config.copy()
if "send_blocks" in config and config["send_blocks"]:
pluginconfig["hyperparameters"] = config["send_blocks"][0]
plugin_factory = config.get("plugin")
if callable(plugin_factory):
plugin = plugin_factory(pluginconfig)
else:
# Default Identity function
plugin = {"function": lambda x: x}
return plugin | {"size": 10, "checks": ["input", "oneout"]}
```
# Source: output.md
CIRCUIT COMPONENTS
# output

A gateway component that returns data to the circuit's function interface, optionally
decoding it through a plugin.
## Details and properties
- Circuits may have multiple [`output`](output.md) nodes, each representing one block of data.
- Supports decoder plugins, mapping sets to external data representations.
- Transparently handles multisets and inhibitory signals.
- Receives data from the upstream component without delay.
| Property | Default | Description |
|:------------|:-----:|:----------------------------------------|
| `receive` | *required* | input slots with optional pathway tags |
| `plugin` | | decoder plugin |
Use standard [style options](components.md) to customize the component's appearance in circuit schematics.
## Basic input and output
This is the simplest possible circuit, routing signals directly from
the [`input`](#input.md) node to the `output` node.
Multiset or inhibitory inputs are reduced to sets along the pathway.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "output", "receive": [1]}
]}
```

## Multiple outputs
Each output node represents a disjoint partition (or block) of the overall output signal.
The following circuit broadcasts the input signal to two outputs:
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "output", "receive": [1]},
{"component": "output", "receive": [1]}
]}
```

## Multisets
This circuit outputs a multiset:
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "output",
"receive": [{"slot": 1, "tags": ["multiset"]}]}
]}
```

## Decoding output data
The `output` component supports decoder plugins that convert sets or multisets
to external data representations.
The following circuit uses the [`codec`](#codec.md) plugin which encodes symbolic tokens as random Sparse Holographic Representations (SHRs). Here, the [`output`](#output.md) component uses the
same plugin instance to decode the SHRs, exactly reversing the mapping.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "plugin": "codec", "send": [1]},
{"component": "output", "plugin": "codec", "receive": [1]}
]}
```

## Source code
```python
def output(config):
"""
At the end of each cycle, the output's input edges are read out.
Output nodes have no "send" slots.
The callback function serves as an optional decoder/postprocessing plugin.
"""
pluginconfig = config.copy()
if "receive_blocks" in config and config["receive_blocks"]:
pluginconfig["hyperparameters"] = config["receive_blocks"][0]
plugin_factory = config.get("plugin")
if callable(plugin_factory):
plugin = plugin_factory(pluginconfig)
else:
# Default Identity function
plugin = {"function": lambda x: x}
return plugin | {"size": 10, "checks": ["output", "oneinp"]}
```
# Source: delay.md
CIRCUIT COMPONENTS
# delay

Dataflow component, delaying incoming signals by one execution cycle and
optionally integrating signals over multiple timesteps.
## Details and properties
- By default, the `delay` component delays the inbound signal by one time step.
- Supports temporal integration of signals across evaluation cycles, governed by
update rule plugins.
- Uses the same set of update rules for temporal integration as the [`temporal`](temporal.md) component.
- Is stateless by default (`replacement` update rule) and stateful with any other update rule.
- Controls sparsity through stochastic subsampling mechanisms.
- Transparently handles multisets and inhibitory signals.
- Supports multiple input or output blocks.
| Property | Default | Description |
|:------------|:-----:|:----------------------------------------|
| `send` | *required* | `[integer,...]` representing one or more output slots |
| `receive` | *required* | one or more input slots with pathway tags |
| `plugin` | `replacement` | update rule, governing the temporal integration logic |
| `latch` | false | whether to skip the update if the input is empty |
| `threshold` | 0 | clear input if relative population is below threshold |
| `rate_limit` | infinite | subsample input if population exceeds the rate limit |
| `decay` | 0 | pre-integration decay (leak rate) |
| `capacity` | 1 | relative population carried over from previous state (bypassed with default `replacement` plugin) |
| `decimate` | 1 | proportional stochastic decimation |
Use standard [style options](components.md) to customize the component's appearance in circuit schematics.
## Stateless delay
The following circuit transports the signal, applying a proportional stochastic decimation by 80 percent. Note that this setup does not delay the signal flow because receiving data from inputs and sending data to outputs is instantaneous.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "delay", "send": [11],
"receive": [1], "decimate": 0.8},
{"component": "output", "receive": [11]}
]}
```

## Delay chains
The following setup, chaining two `delay` nodes, delays the signal by one timestep:
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "delay", "send": [11], "receive": [1]},
{"component": "delay", "send": [21], "receive": [11]},
{"component": "output", "receive": [21]}
]}
```

## Block coding
The `delay` component may receive or send multiple blocks of data. The total
input dimensions and output dimensions must be the same.
Here the `delay` node transparently merges two blocks into a single block that has twice the dimension of the inputs:
```json
{ "hyperparameters": {"default": [1000, 10], "11": [2000, 20]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "input", "send": [2]},
{"component": "delay", "send": [11], "receive": [1, 2]},
{"component": "output", "receive": [11]}
]}
```

In this circuit, two disjoint blocks flow through the `delay` node:
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "input", "send": [2]},
{"component": "delay", "send": [11, 12], "receive": [1, 2]},
{"component": "output", "receive": [11]},
{"component": "output", "receive": [12]}
]}
```

## Pathway merging
In the following circuit, the two incoming blocks of information are merged.
The output dimension is the same as either input dimension.
Note that the list notation in the `receive` specification denotes signal merging.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "input", "send": [2]},
{"component": "delay", "send": [11], "receive": [[1, 2]], "decimate": 0.8},
{"component": "output", "receive": [11]}
]}
```

## Temporal integration via augmentation
This circuit uses the [`augmentation`](augmentation.md) plugin, carrying over
twice the default population and applying a pre-integration stochastic decay of 15 percent.
The accumulated state does not retain the temporal order of signals.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "delay", "plugin": "augmentation", "send": [11],
"receive": [1], "capacity": 2, "decay": 0.15},
{"component": "output", "receive": [11]}
]}
```

## Temporal sequence permutation
Using the [`permutation`](permutation.md) plugin as the update rule, a permutation
is applied to the previous state before integration with the current incoming signal.
The accumulated state retains information about the order within the temporal sequence.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "delay", "plugin": "permutation", "send": [11],
"receive": [1], "capacity": 5},
{"component": "output", "receive": [11]}
]}
```

## Source code
```python
def delay(config):
plugin_factory = config.get("plugin", replacement)
if isinstance(plugin_factory, str):
import sys
plugin_factory = getattr(
sys.modules[__name__],
plugin_factory,
replacement)
plugin = plugin_factory(config)
if plugin_factory is replacement:
plugin.pop("label", None)
dims1 = [b[0] for b in config.get("receive_blocks", [])]
dims2 = [b[0] for b in config.get("send_blocks", [])]
pop = sum(b[1] for b in config.get("receive_blocks", []))
threshold_factor = config.get("threshold", 0.0)
absthreshold = round(threshold_factor * pop)
rate_limit_factor = config.get("rate_limit", float('inf'))
absratelimit = round(
rate_limit_factor *
pop) if rate_limit_factor != float('inf') else float('inf')
decay = config.get("decay", 0.0)
capacity_factor = config.get("capacity", 1.0)
abscapacity = round(capacity_factor * pop)
decimation = config.get("decimate", 1.0)
latch = config.get("latch", False)
Xstate = []
def f(*blocks):
nonlocal Xstate
X = multiset_block_join(list(blocks), dims1)
# Enforce minimum population
if len(X) < absthreshold:
X = []
if len(X) > absratelimit:
X = sorted(
rng.choice(
X,
size=absratelimit,
replace=False).tolist())
if decay > 0.0:
keep_size = math.floor((1.0 - decay) * len(Xstate))
Xstate = sorted(
rng.choice(
Xstate,
size=keep_size,
replace=False).tolist())
if len(Xstate) > abscapacity:
Xstate = sorted(
rng.choice(
Xstate,
size=abscapacity,
replace=False).tolist())
# Temporal integration via update rules
if not (latch and len(X) == 0):
Xstate = plugin["updaterule"](X, Xstate)
# Proportional multiset subsampling applied to output
Xdec = Xstate
if decimation < 1.0:
sample_size = math.floor(decimation * len(Xdec))
Xdec = sorted(
rng.choice(
Xdec,
size=sample_size,
replace=False).tolist())
return tuple(multiset_block_split(Xdec, dims2))
result = dict(plugin)
result.update({
"function": f,
"checks": ["arginp", "argout", "totaldim"],
"fill": 13
})
return result
```
# Source: noise.md
CIRCUIT COMPONENTS
# noise

A circuit component that generates pseudo-random sparse sets.
## Details and properties
- The `noise` component has no input and returns one output block.
- The dimension and population of the generated representations is governed by the outbound slot's hyperparameters.
| Property | Default | Description |
|:------------|:-----:|:----------------------------------------|
| `send` | *required* | a single output slot |
Use standard [style options](components.md) to customize the component's appearance in circuit schematics.
## Additive noise
Add random noise to the input signal:
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "noise", "send": [2]},
{"component": "output", "receive": [[1, 2]]}
]}
```

## Subtractive noise
Use an inhibitory pathway to make the noise subtractive:
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "noise", "send": [2]},
{"component": "output", "receive": [[1, {"slot": 2, "tags": ["inhibit"]}]]}
]}
```

## Source code
```python
def noise(config):
n, p = config.get("send_blocks", [[0, 0]])[0]
def f(*blocks):
return (
sorted(
rng.choice(range(1, n + 1),
size=p,
replace=False).tolist()),
)
return {
"function": f,
"checks": ["input", "oneout"],
"label": "~", "fill": 13, "size": 18
}
```
# Source: circuit.md
CIRCUIT COMPONENTS
# circuit

A component that embeds an entire circuit.
See also: https://creatingintelligence.org/#circuits
## Details and properties
- Circuits can be nested to any depth, provided the nesting is acyclic.
- Uses the `shared` plugin for embedding a shared circuit.
- Uses the `file` plugin to create a local embedded circuit.
- Exchanges sets or multisets with the embedded circuit.
- The embedded circuit may use [`input`](#input.md) or [`output`](output.md) plugins to preprocess or postprocess the exchanged data.
| Property | Default | Description |
|:------------|:---------:|:---------------------------------------|
| `send` | *required* | output slots, as a list of integers |
| `receive` | *required* | input slots with optional pathway tags |
| `plugin` | `shared` | extension module |
| `name` | *required* | registry identifier / filename |
## Shared embedded circuits
An instantiated circuit can be shared to and embedded by other circuits
by registering it via a `name` option:
```json
{ "options": {"name": "PERM"},
"hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "output", "receive": [{"slot": 1, "tags": ["permute"]}]}
]}
```

The following circuit embeds the above instance by referring to its
registry name. The same shared instance can be embedded by multiple `circuit`
components.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "circuit", "plugin": "shared", "send": [11],
"receive": [1], "name": "PERM"},
{"component": "output", "receive": [11]}
]}
```

The hyperparameters of shared embedded circuits must
match the parameters of its inbound and outbound slots.
## Local embedded circuits
Using the `file` plugin, the `circuit` component imports a JSON circuit
description and embeds a local instance:
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "circuit", "plugin": "file", "send": [11],
"receive": [1], "name": "permutation.json"},
{"component": "output", "receive": [11]}
]}
```

If the embedded circuit specifies a registry name, it can be shared
across multiple `circuit` components.
The hyperparameters of local embedded circuits are automatically rescaled to
match the parameters of its inbound and outbound slots.
## Source code
```python
def circuit(config):
"""
Embedded circuit component.
Multiple circuit components can reference the same embedded instance.
"""
plugin_factory = config.get("plugin", shared)
if isinstance(plugin_factory, str):
import sys
plugin_factory = getattr(sys.modules[__name__], plugin_factory, shared)
plugin = plugin_factory(config)
if not plugin:
return {}
params = [plugin.get("receive_blocks", []), plugin.get("send_blocks", [])]
if [config.get("receive_blocks", []), config.get(
"send_blocks", [])] != params:
raise ValueError(
f"Hyperparameters of embedded circuit do not match: {params}")
def f(*blocks):
return plugin["function"](*blocks)
result = dict(plugin)
result.update({
"function": f,
"checks": ["arginp", "argout"],
"shape": "square", "size": 9
})
return result
```
### Plugins
```python
def shared(config):
"""Plug-in for embedding a precompiled circuit."""
name = config.get("name")
if not name:
raise ValueError(
f"Missing 'name' property in shared circuit: {config}")
sub = circuit_registry.get(name)
if not sub:
raise ValueError(f"Unknown circuit '{name}'.")
result = dict(sub)
result["label"] = name
result.pop("clear", None)
return result
```
```python
def file(config):
file_path = config.get("name")
if not file_path:
raise CircuitError(f"Missing 'name' property in {config}")
if not file_path.endswith(".json"):
file_path += ".json"
# Search current working directory first, then sys.path
search_paths = [""] + sys.path
full_path = None
for base in search_paths:
target = os.path.join(base, file_path) if base else file_path
if os.path.exists(target):
full_path = target
break
if not full_path:
raise CircuitError(
f"File '{file_path}' not found in current directory or sys.path.")
with open(full_path, "r") as f:
circ_expr = json.load(f)
# Rescale hyperparameters of embedded circuit
scale = config.get("scale")
default_hp = circ_expr.get("hyperparameters", {}).get("default")
if (isinstance(scale, (list, tuple)) and len(scale) == 2 and
isinstance(default_hp, (list, tuple)) and len(default_hp) == 2):
# Calculate separate scaling factors
rescale_factor_n = scale[0] / default_hp[0]
rescale_factor_p = scale[1] / default_hp[1]
# Apply scaling to all hyperparameters in the embedded circuit
for k, v in circ_expr["hyperparameters"].items():
if isinstance(v, (list, tuple)) and len(v) == 2:
circ_expr["hyperparameters"][k] = [
round(v[0] * rescale_factor_n),
round(v[1] * rescale_factor_p)
]
# Compile the imported circuit
sub = Circuit(circ_expr)
# Extract up to 5 characters from the filename for the label
base_name = os.path.splitext(os.path.basename(file_path))[0]
sub["label"] = base_name[:5]
return sub
```
# Source: auto.md
CIRCUIT COMPONENTS
# auto

Auto-associative topological memory component, storing items and hypergraphs.
See also: https://creatingintelligence.org/#auto-associative-memory
## Details and properties
- Incrementally learns auto-associations at every execution cycle.
- Uses update rule plugins to control the mechanics of state updates.
- Controls sparsity through stochastic subsampling mechanisms.
- Deduplicates incoming multisets and inhibitory signals, internally processing the
underlying set of unique elements.
- Supports multiple data blocks, joined internally as the memory's shared input/output layer. The hyperparameters of input and output blocks must be identical.
| Property | Default | Description |
|:------------|:-----:|:----------------------------------------|
| `send` | *required* | `[integer,...]` representing one or more output slots |
| `receive` | *required* | same as `send`, with optional pathway tags |
| `plugin` | `replacement` | auto-associative update rule |
| `threshold` | *automatic* | relative auto-associative pattern matching threshold |
| `rate_limit` | infinite | subsample input if it exceeds the specified relative rate limit |
| `decimate` | 1 | proportional stochastic decimation |
| `learn` | *P* | |
Use standard [style options](components.md) to customize the component's appearance in circuit schematics.
## Auto-associative memory with replacement update
As the default update rule for the [`auto`](auto.md) component, `replacement`
substitutes the memory's state with the retrieved value.
This is the classic auto-associative setup, most useful for pattern completion, denoising, and item stores. With this configuration, the auto-associative memory converges to a stable state. Stability is normally reached with just a single cycle because denoising iterations are already encapsulated within the memory retrieval algorithm.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "auto", "plugin": "replacement",
"send": [2], "receive": [1]},
{"component": "output", "receive": [2]}
]}
```

## Auto-associative memory with residual update
As a plugin for the [`auto`](auto.md) component, the `residual` update rule
removes the retrieved pattern from the query, leaving only the novel elements.
This mechanism is the basis for associative novelty detection.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "auto", "plugin": "residual", "send": [2], "receive": [1]},
{"component": "output", "receive": [2]}
]}
```

## Auto-associative memory with difference update
As a plugin for the [`auto`](auto.md) component, the `difference` update rule
replaces the memory's state with the symmetric difference between the query and the retrieved data, removing the matching elements.
This update rule enables generative behavior in auto-associative memory retrieval.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "auto", "plugin": "difference",
"send": [2], "receive": [1]},
{"component": "output", "receive": [2]}
]}
```

## Auto-associative memory with complement update
As a plugin for the [`auto`](auto.md) component, the `complement` update
removes the retrieved pattern from the query pattern, leaving only the novel elements.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "auto", "plugin": "residual", "send": [2], "receive": [1]},
{"component": "output", "receive": [2]}
]}
```

## Auto-associative memory with augmentation update
As a plugin for the [`auto`](auto.md) component, `augmentation`
aggregates the memory's state with the retrieved value, then
deduplicating the multiset to give its underlying set of unique elements.
The `augmentation` rule completes the input while retaining non-matching elements. Used iteratively in conjunction with stochastic subsampling, this mechanism converges incrementally to a stable state.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "auto", "plugin": "augmentation",
"send": [2], "receive": [1]},
{"component": "output", "receive": [2]}
]}
```

## Auto-associative memory with coincidence update
As a plugin for the [`auto`](auto.md) component, the `coincidence` update rule
retaining the matching elements between the query and the retrieved pattern.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "auto", "plugin": "coincidence",
"send": [2], "receive": [1]},
{"component": "output", "receive": [2]}
]}
```

## Source code
```python
def auto(config):
plugin_factory = config.get("plugin", replacement)
if isinstance(plugin_factory, str):
# Resolve string name to function dynamically
import sys
plugin_factory = getattr(
sys.modules[__name__],
plugin_factory,
replacement)
plugin = plugin_factory(config)
receive_blocks = config.get("receive_blocks", [])
dims = [b[0] for b in receive_blocks]
# params = Plus @@ config["receive_blocks"] (Sums Ns and Ps respectively)
params = [sum(b[0] for b in receive_blocks), sum(b[1]
for b in receive_blocks)]
pop = params[1] if len(params) > 1 else 0
rate_limit_factor = config.get("rate_limit", float('inf'))
absratelimit = round(
rate_limit_factor *
pop) if rate_limit_factor != float('inf') else float('inf')
decimation = config.get("decimate", 1.0)
# Learning thresholds
min_val = pop
max_val = pop
learn_val = config.get("learn", False)
if isinstance(learn_val, (int, float)) and not isinstance(learn_val, bool):
min_val = max_val = round(learn_val * pop)
elif isinstance(learn_val, (list, tuple)) and len(learn_val) == 2:
min_val = round(learn_val[0] * pop)
max_val = round(learn_val[1] * pop)
# Memory with identical input and output parameters
m_config = dict(config)
m_config.update({
"A_parameters": params,
"B_parameters": params
})
# Memory backend
M = Memory(m_config)
def f(*blocks):
# Join partitions and apply inhibition
A = resolve_block_join_normal(list(blocks), dims)
X = A
# Limit the rate of incoming positives
if len(X) > absratelimit:
X = sorted(
rng.choice(
X,
size=absratelimit,
replace=False).tolist())
# Proportional decimation
if decimation < 1.0:
sample_size = math.floor(decimation * len(X))
Xdec = sorted(
rng.choice(
X,
size=sample_size,
replace=False).tolist())
else:
Xdec = X
# Always learn if "learn" is True
if learn_val is True:
M["store"](A)
# Memory retrieval with decimated and rate-limited input state
Y = M["retrieve"](Xdec)
# Learn input A if retrieval fails and it falls within thresholds
if not Y and min_val <= len(A) <= max_val:
if learn_val is not True:
M["store"](A)
Y = A
X_out = plugin["updaterule"](Y, X)
X_out = sorted(list(set(X_out)))
return tuple(multiset_block_split(X_out, dims))
# Merge plugin attributes, component defaults, and Memory closures (e.g.,
# "clear")
result = dict(plugin)
result.update({
"function": f,
"checks": ["arginp", "argout", "ident"],
"shape": "square", "fill": 8
})
result.update(M)
return result
```
# Source: temporal.md
CIRCUIT COMPONENTS
# temporal

Temporal associative memory component.
## Details and properties
- Learns the association of the temporal state with the current input.
- Automatically learns if the previous prediction was incorrect.
- A closed feedback loop enables generative behavior.
- Predicts the next input based on the state.
- Supports temporal integration of signals across evaluation cycles, governed by
update rule plugins.
- Uses update rule plugins to control the mechanics of state updates.
- Supports the same set of update rules for temporal integration as the [`delay`](delay.md) component.
- Controls sparsity through stochastic subsampling mechanisms.
- Supports multiple input or output blocks.
| Property | Default | Description |
|:------------|:-----:|:----------------------------------------|
| `send` | *required* | `[integer,...]` representing one or more output slots |
| `receive` | *required* | one or more input slots with pathway tags |
| `plugin` | [`replacement`](replacement.md) | update rule, governing the temporal integration logic |
| `latch` | false | whether to skip the update if the input is empty |
| `threshold` | 0 | clear input if relative population is below threshold |
| `rate_limit` | infinite | subsample input if population exceeds the rate limit |
| `decay` | 0 | pre-integration decay (leak rate) |
| `capacity` | 1 | relative population carried over from previous state (bypassed with default `replacement` plugin) |
| `decimate` | 1 | proportional stochastic decimation |
Use standard [style options](components.md) to customize the component's appearance in circuit schematics.
## Temporal associative memory with replacement update
The `temporal` component applies the [`replacement`](replacement.md) update rule by default.
At every timestep, it replaces its internal state with the current input,
bypassing the `capacity` setting. This mechanism directly associates
each signal to the subsequent signal.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "temporal", "send": [11], "receive": [1]},
{"component": "output", "receive": [11]}
]}
```

## Temporal associative memory with difference update
The [`difference`](difference.md)
update rule replaces the internal state with
the symmetric multiset difference of the state and the incoming signal.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "temporal", "plugin": "difference",
"send": [11], "receive": [1]},
{"component": "output", "receive": [11]}
]}
```

## Temporal associative memory with complement plugin
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "temporal", "plugin": "complement",
"send": [11], "receive": [1], "capacity": 2},
{"component": "output", "receive": [11]}
]}
```

## Temporal associative memory with state augmentation
The [`augmentation`](augmentation.md) update rule plugin
aggregates an orderless temporal state.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "temporal", "plugin": "augmentation",
"send": [11], "receive": [1], "capacity": 5},
{"component": "output", "receive": [11]}
]}
```

## Temporal associative memory with state permutation
The [`permutation`](permutation.md) update rule plugin accumulates a temporal state by iteratively permuting it at every timestep, encoding the ordering of prior inputs.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "temporal", "plugin": "permutation",
"send": [11], "receive": [1], "capacity": 2},
{"component": "output", "receive": [11]}
]}
```

## Source code
```python
def temporal(config):
plugin_factory = config.get("plugin", replacement)
if isinstance(plugin_factory, str):
import sys
plugin_factory = getattr(
sys.modules[__name__],
plugin_factory,
replacement)
plugin = plugin_factory(config)
receive_blocks = config.get("receive_blocks", [])
dims = [b[0] for b in receive_blocks]
params = [sum(b[0] for b in receive_blocks), sum(b[1]
for b in receive_blocks)]
pop = params[1] if len(params) > 1 else 0
threshold_factor = config.get("threshold", 0.0)
absthreshold = round(threshold_factor * pop)
rate_limit_factor = config.get("rate_limit", float('inf'))
absratelimit = round(
rate_limit_factor *
pop) if rate_limit_factor != float('inf') else float('inf')
decay = config.get("decay", 0.0)
capacity_factor = config.get("capacity", 1.0)
abscapacity = round(capacity_factor * pop)
decimation = config.get("decimate", 1.0)
latch = config.get("latch", False)
# Memory with identical input and output parameters
m_config = dict(config)
m_config.update({
"A_parameters": params,
"B_parameters": params
})
M = Memory(m_config)
Xstate = []
prediction = []
def f(*blocks):
nonlocal Xstate, prediction
X = multiset_block_join(list(blocks), dims)
# Enforce minimum population
if len(X) < absthreshold:
X = []
# Rate limiting equally applies to positive and negative elements
if len(X) > absratelimit:
X = sorted(
rng.choice(
X,
size=absratelimit,
replace=False).tolist())
# Pre-integration stochastic decay
if decay > 0.0:
keep_size = math.floor((1.0 - decay) * len(Xstate))
Xstate = sorted(
rng.choice(
Xstate,
size=keep_size,
replace=False).tolist())
# Learn state -> current input if prediction was incorrect.
if prediction != X:
M["store"](resolve_normal(Xstate), resolve_normal(X))
# Multiset subsampling applied to previous state
if len(Xstate) > abscapacity:
Xstate = sorted(
rng.choice(
Xstate,
size=abscapacity,
replace=False).tolist())
# Temporal integration via update rules
if not (latch and len(X) == 0):
Xstate = plugin["updaterule"](X, Xstate)
# Proportional multiset subsampling applied to the integrated state
Xdec = Xstate
if decimation < 1.0:
sample_size = math.floor(decimation * len(Xdec))
Xdec = sorted(
rng.choice(
Xdec,
size=sample_size,
replace=False).tolist())
# Predict next token
prediction = M["retrieve"](resolve_normal(Xdec))
return tuple(multiset_block_split(prediction, dims))
result = dict(plugin)
result.update({
"function": f,
"checks": ["arginp", "argout", "ident"],
"shape": "square", "fill": 14
})
result.update(M)
return result
```
# Source: associator.md
CIRCUIT COMPONENTS
# associator

Hetero-associative memory component for supervised learning.
See also: https://creatingintelligence.org/#supervised-learning
## Details and properties
- Wrapper for a hetero-associative topological memory instance.
- Receives the label from its first `receive` block and data from the remaining blocks.
- Training is triggered if the label input is non-empty.
- Inference is triggered by an empty label input.
- Sends the inferred label.
- Resolves multiset and inibitory input prior to processing.
| Property | Default | Description |
|:------------|:-----:|:----------------------------------------|
| `send` | *required* | integer, representing a single output slot |
| `receive` | *required* | two or more input slots with pathway tags |
| `threshold` | *automatic* | relative pattern matching threshold |
| `decimate` | 1 | proportional stochastic decimation applied to training data |
Use standard [style options](components.md) to customize the component's appearance in circuit schematics.
## Supervised learning
A basic supervised learning setup using hetero-associative memory:
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1], "label": "label"},
{"component": "input", "send": [2], "label": "data"},
{"component": "associator", "send": [11], "receive": [1, 2]},
{"component": "output", "receive": [11]}
]}
```

## Source code
```python
def associator(config):
"""
Vanilla hetero-associative node for supervised learning A -> B.
B is the first input argument.
A is given by the rest of the input arguments.
Can be block-coded. Always learns if B != [].
"""
receive_blocks = config.get("receive_blocks", [])
Adims = [b[0] for b in receive_blocks[1:]]
params_A = [sum(b[0] for b in receive_blocks[1:]), sum(b[1]
for b in receive_blocks[1:])]
params_B = receive_blocks[0]
m_config = dict(config)
m_config.update({
"A_parameters": params_A,
"B_parameters": params_B
})
M = Memory(m_config)
def f(B, *blocks):
X = resolve_block_join_normal(list(blocks), Adims)
Y = resolve_normal(B)
if not Y:
return (M["retrieve"](X),)
M["store"](X, Y)
return ([],)
result = {
"function": f,
"checks": ["dimfirst", "oneout"],
"shape": "square", "fill": 11, "size": 18, "label": "▶●"
}
result.update(M)
return result
```
# Source: predictor.md
CIRCUIT COMPONENTS
# predictor

Hetero-associative memory component for predictive learning.
See also: https://creatingintelligence.org/#predictive-learning
## Details and properties
- Wrapper for a hetero-associative topological memory instance.
- Associates the data from the previous execution cycle with the current label signal.
- Typically used as readout node in reservoir architectures.
- Receives the label from its first `receive` block and data from the remaining blocks.
- Learns if the prediction does not match the subsequent input.
- A closed feedback loop enables generative prediction.
- Sends the inferred label.
- Resolves multiset and inibitory input prior to processing.
| Property | Default | Description |
|:------------|:-----:|:----------------------------------------|
| `send` | *required* | integer, representing a single output slot |
| `receive` | *required* | two or more input slots with pathway tags |
| `threshold` | *automatic* | relative pattern matching threshold |
Use standard [style options](components.md) to customize the component's appearance in circuit schematics.
## Predictive learning
A basic setup for predictive learning with hetero-associative memory:
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1], "label": "label"},
{"component": "input", "send": [2], "label": "data"},
{"component": "predictor", "send": [11], "receive": [1, 2]},
{"component": "output", "receive": [11], "label": "pred"}
]}
```

## Source code
```python
def predictor(config):
"""
Generates a prediction based on the current input (slot #1)
and context (slots #2,...).
Automatically learns the correct prediction in the following cycle.
"""
receive_blocks = config.get("receive_blocks", [])
itemconfig = receive_blocks[0]
contextconfig = receive_blocks[1:]
# Extract decimation parameter, defaulting to 1.0
decimation = config.get("decimate", 1.0)
params_A = [sum(b[0] for b in contextconfig), sum(b[1]
for b in contextconfig)]
params_B = itemconfig
m_config = dict(config)
m_config.update({
"A_parameters": params_A,
"B_parameters": params_B
})
M = Memory(m_config)
X = []
prediction = []
def f(item, *blocks):
nonlocal X, prediction
Y = resolve_normal(item)
if prediction != X:
M["store"](X, Y)
X = resolve_block_join_normal(
list(blocks), [b[0] for b in contextconfig])
# Apply stochastic subsampling to X before retrieval
if decimation < 1.0:
sample_size = math.floor(decimation * len(X))
Xdec = sorted(
rng.choice(
X,
size=sample_size,
replace=False).tolist())
else:
Xdec = X
prediction = M["retrieve"](Xdec)
return (prediction,)
result = {
"function": f,
"checks": ["dimfirst", "oneout"],
"shape": "square", "fill": 11, "size": 18, "label": "▶▶"
}
result.update(M)
return result
```
# Source: heteroencoder.md
CIRCUIT COMPONENTS
# heteroencoder

Hetero-associative memory component for unsupervised learning.
See also: https://creatingintelligence.org/#heteroencoder
## Details and properties
- Encapsulates a hetero-associative topological memory instance.
- Maps similar sets (SDRs or SHRs) to stable symbolic tokens (SHRs) on the fly.
- Has no input for a teaching signal.
- Generates and learns a unique output SHR when encountering unknown input.
- May receive block-coded input.
- Resolves multiset and inibitory input prior to processing.
| Property | Default | Description |
|:------------|:-----:|:----------------------------------------|
| `send` | *required* | integer, representing a single output slot |
| `receive` | *required* | one or several input slots with pathway tags |
| `threshold` | *automatic* | relative pattern matching threshold |
Use standard [style options](components.md) to customize the component's appearance in circuit schematics.
## Unsupervised learning
A simple hetero-encoding circuit:
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "heteroencoder", "send": [11], "receive": [1]},
{"component": "output", "receive": [11]}
]}
```

## Source code
```python
def heteroencoder(config):
"""
Heteroencoder A -> B. A may be partitioned. Has no input for B.
"""
receive_blocks = config.get("receive_blocks", [])
dims = [b[0] for b in receive_blocks]
params_A = [sum(b[0] for b in receive_blocks), sum(b[1]
for b in receive_blocks)]
params_B = config.get("send_blocks", [[0, 0]])[0]
m_config = dict(config)
m_config.update({
"A_parameters": params_A,
"B_parameters": params_B
})
M = Memory(m_config)
def f(*blocks):
X = resolve_block_join_normal(list(blocks), dims)
Y = M["retrieve"](X)
if not Y:
n, p = params_B
Y = sorted(
rng.choice(
range(1, n + 1),
size=p, replace=False).tolist())
M["store"](X, Y)
return (Y,)
result = {
"function": f,
"checks": ["arginp", "oneout"],
"shape": "square",
"fill": 11,
"size": 18,
"label": "◀▶"
}
result.update(M)
return result
```
# Source: replacement.md
UPDATE RULE PLUGINS
# replacement

Update rule plugin, entirely replacing the previous state with new information.
See also: https://creatingintelligence.org/#update-rules
## Details
- Default plugin for [`auto`](auto.md), [`delay`](delay.md), and [`temporal`](temporal.md) components.
- Bypasses the component's `capacity` setting.
- Transparently handles multisets and inhibitory signals.
## Stateless delay
The `replacement` update mechanism is the default behavior of the [`delay`](delay.md)
component.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "delay", "send": [11], "receive": [1]},
{"component": "output", "receive": [11]}
]}
```

## Auto-associative memory with replacement update
As the default update rule for the [`auto`](auto.md) component, `replacement`
substitutes the memory's state with the retrieved value.
This is the classic auto-associative setup, most useful for pattern completion, denoising, and item stores. With this configuration, the auto-associative memory converges to a stable state. Stability is normally reached with just a single cycle because denoising iterations are already encapsulated within the memory retrieval algorithm.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "auto", "send": [2], "receive": [1]},
{"component": "output", "receive": [2]}
]}
```

See also: https://creatingintelligence.org/#auto-associative-memory
## Temporal associative memory with replacement update
The [`temporal`](temporal.md) component applies the `replacement` update rule by default.
At every execution cycle, it replaces the internal state with the current input,
bypassing the `capacity` setting. This mechanism directly associates
each signal to the subsequent signal.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "temporal", "send": [11], "receive": [1]},
{"component": "output", "receive": [11]}
]}
```

## Source code
```python
def replacement(config):
return {"updaterule": lambda y, x: y, "label": "▼", "size": 18}
```
# Source: residual.md
UPDATE RULE PLUGINS
# residual

Update rule plugin for auto-associative memory, removing the retrieved pattern from the memory’s state, leaving only the novel elements.
See also: https://creatingintelligence.org/#update-rules
## Details
- Plugin for the [`auto`](auto.md) component.
- Not applicable for temporal integration ([`delay`](delay.md) and [`temporal`](temporal.md)).
- Transparently handles multisets and inhibitory signals.
- Corresponds to the set complement after deduplication.
## Auto-associative memory with residual update
As a plugin for the [`auto`](auto.md) component, the `residual` update rule
removes the retrieved pattern from the query, leaving only the novel elements.
This mechanism is the basis for associative novelty detection.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "auto", "plugin": "residual", "send": [2], "receive": [1]},
{"component": "output", "receive": [2]}
]}
```

See also: https://creatingintelligence.org/#auto-associative-memory
## Source code
```python
def residual(config):
return {"updaterule": lambda y, x: resolve_graded(
multiset([x, [-i for i in y]])), "label": "▲", "size": 18}
```
# Source: difference.md
UPDATE RULE PLUGINS
# difference

Update rule plugin, augmenting a state with new elements while dropping
elements that are common to the state and the new information.
See also: https://creatingintelligence.org/#update-rules
## Details
- Plugin for [`auto`](auto.md), [`delay`](delay.md), and [`temporal`](temporal.md) components.
- Transparently handles multisets and inhibitory signals.
- Corresponds to the set symmetric difference after deduplication.
## Auto-associative memory with difference update
As a plugin for the [`auto`](auto.md) component, the `difference` update rule
replaces the memory's state with the symmetric difference between the query and the retrieved data, removing the matching elements.
This update rule enables generative behavior in auto-associative memory retrieval.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "auto", "plugin": "difference",
"send": [2], "receive": [1]},
{"component": "output", "receive": [2]}
]}
```

See also: https://creatingintelligence.org/#auto-associative-memory
## Temporal associative memory with difference update
Used as a plugin for the [`temporal`](temporal.md) component, the `difference`
update rule replaces the internal state with
the symmetric multiset difference of the state and the incoming signal.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "temporal", "plugin": "difference",
"send": [11], "receive": [1]},
{"component": "output", "receive": [11]}
]}
```

## Source code
```python
def difference(config):
return {"updaterule": lambda y, x: sorted(
[abs(i) for i in multiset([y, [-i for i in x]])]), "label": "△", "size": 20}
```
# Source: complement.md
UPDATE RULE PLUGINS
# complement

Update rule plugin for auto-associative memory and temporal integration, updating
the state by removing the current signal.
See also: https://creatingintelligence.org/#update-rules
## Details
- Plugin for [`auto`](auto.md), [`delay`](delay.md), and [`temporal`](temporal.md) components.
- Transparently handles multisets and inhibitory signals.
- Corresponds to the set complement after deduplication.
## Auto-associative memory with complement update
As a plugin for the [`auto`](auto.md) component, the `complement` update
removes the retrieved pattern from the query pattern, leaving only the novel elements.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "auto", "plugin": "residual", "send": [2], "receive": [1]},
{"component": "output", "receive": [2]}
]}
```

See also: https://creatingintelligence.org/#auto-associative-memory
## Delay with complement plugin
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "delay", "plugin": "complement",
"send": [11], "receive": [1], "capacity": 1},
{"component": "output", "receive": [11]}
]}
```

## Temporal associative memory with complement plugin
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "temporal", "plugin": "complement",
"send": [11], "receive": [1], "capacity": 2},
{"component": "output", "receive": [11]}
]}
```

## Source code
```python
def complement(config):
return {"updaterule": lambda y, x: resolve_graded(
multiset([y, [-i for i in x]])), "label": "▽", "size": 20}
```
# Source: augmentation.md
UPDATE RULE PLUGINS
# augmentation

Update rule plugin, computing the multiset aggregation of signals.
See also: https://creatingintelligence.org/#update-rules
## Details
- Plugin for [`auto`](auto.md), [`delay`](delay.md), and [`temporal`](temporal.md) components.
- Computes the multiset aggregation of its inputs.
- Corresponds to the set union after deduplication.
## Auto-associative memory with augmentation update
As a plugin for the [`auto`](auto.md) component, `augmentation`
aggregates the memory's state with the retrieved value, then
deduplicating the multiset to give its underlying set of unique elements.
The `augmentation` rule completes the input while retaining non-matching elements. Used iteratively in conjunction with stochastic subsampling, this mechanism converges incrementally to a stable state.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "auto", "plugin": "augmentation",
"send": [2], "receive": [1]},
{"component": "output", "receive": [2]}
]}
```

See also: https://creatingintelligence.org/#auto-associative-memory
## Delay component with temporal state augmentation
The [`delay`](delay.md) component uses the `augmentation` mechanism for
temporal signal integration.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "delay", "plugin": "augmentation",
"send": [11], "receive": [1], "capacity": 2, "decay": 0.15},
{"component": "output", "receive": [11]}
]}
```

## Temporal associative memory with state augmentation
Used as a plugin for the [`temporal`](temporal.md) component, `augmentation`
aggregates an orderless state representing prior inputs.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "temporal", "plugin": "augmentation",
"send": [11], "receive": [1], "capacity": 5},
{"component": "output", "receive": [11]}
]}
```

## Source code
```python
def augmentation(config):
return {"updaterule": lambda y, x: multiset(
[y, x]), "label": "∪", "size": 14}
```
# Source: coincidence.md
UPDATE RULE PLUGINS
# coincidence

Update rule plugin for auto-associative memory, retaining the matching elements
between the query and the retrieved pattern.
See also: https://creatingintelligence.org/#update-rules
## Details
- Plugin for the [`auto`](auto.md) component.
- Not applicable for temporal integration ([`delay`](delay.md) and [`temporal`](temporal.md)).
- Transparently handles multisets and inhibitory signals.
- Corresponds to the set intersection after deduplication.
## Auto-associative memory with coincidence update
As a plugin for the [`auto`](auto.md) component, the `coincidence` update rule
retains only the matching elements, without completing the pattern.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "auto", "plugin": "coincidence",
"send": [2], "receive": [1]},
{"component": "output", "receive": [2]}
]}
```

See also: https://creatingintelligence.org/#auto-associative-memory
## Source code
```python
def coincidence(config):
def multiset_intersection(y, x):
counts_y = Counter(y)
counts_x = Counter(x)
res = []
for k, v in counts_y.items():
res.extend([k] * min(v, counts_x.get(k, 0)))
return sorted(res)
return {"updaterule": multiset_intersection, "label": "∩", "size": 14}
```
# Source: permutation.md
UPDATE RULE PLUGINS
# permutation

Update rule plugin, replacing the state with its permutation, augmented by the current signal.
See also: https://creatingintelligence.org/#permutation
## Details
- Plugin for [`delay`](delay.md) and [`temporal`](temporal.md) components.
- Not applicable with auto-associative memory ([`auto`](auto.md)).
- Transparently handles multiset and inhibitory signals.
- Repeated permutation of the temporal state encodes temporal sequences.
## Delay component with temporal sequence permutation
The [`delay`](delay.md) component applying the `permutation` update rule:
```
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "delay", "plugin": "augmentation",
"send": [11], "receive": [1], "capacity": 2, "decay": 0.15},
{"component": "output", "receive": [11]}
]}
```

## Temporal associative memory with state permutation
Used as a plugin for the [`temporal`](temporal.md) component, `permutation`
aggregates a state that preservers the order of prior inputs.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "temporal", "plugin": "permutation",
"send": [11], "receive": [1], "capacity": 2},
{"component": "output", "receive": [11]}
]}
```

## Source code
```python
def permutation(config):
dims = sum(b[0] for b in config.get("receive_blocks", []))
dim = dims if dims else 0
perm = rng.choice(
range(1, dim + 1),
size = dim,
replace=False).tolist() if dim > 0 else []
def updaterule(y, x):
mapped_x = sorted([(1 if i > 0 else -1 if i < 0 else 0)
* perm[abs(i) - 1] for i in x if i != 0])
return multiset([y, mapped_x])
return {"updaterule": updaterule, "label": "π", "size": 16}
```
# Source: codec.md
ENCODERS & DECODERS
# codec

General-purpose encoder/decoder for symbolic tokens.
See also: https://creatingintelligence.org/#sparse-holographic-representations
## Details
- Dynamically creates a lexicon that maps tokens to Sparse Holographic Representations (SHRs).
- Encoder plugin for the [`input`](input.md) component.
- Decoder plugin for the [`output`](output.md) component.
- Encodes string tokens as well as general data structures.
- Creates a random SHR for each unique token.
- Decoding is based on the auto-associative pattern matching threshold.
- Maintains a globally shared lexicon for each hyperparameter combination [N, P].
Use standard [style options](components.md) to customize the plugin's appearance in circuit schematics.
## Encoding and decoding chain for symbolic tokens
The following circuit encodes a token to an SHR and subsequently decodes it,
mirroring the input.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "plugin": "codec", "send": [1]},
{"component": "output", "plugin": "codec", "receive": [1]}
]}
```

## Source code
```python
def codec(config):
"""General-purpose SHR encoder/decoder for symbolic expressions."""
# Extract hyperparameters from config
hyperparameters = config.get("hyperparameters", [0, 0])
n, p = hyperparameters[0], hyperparameters[1]
lex_key = (n, p)
# Initialize global hash table if missing for these dimensions
if lex_key not in circuit_codec_lexicon:
circuit_codec_lexicon[lex_key] = {None: []}
lex = circuit_codec_lexicon[lex_key]
# Use auto-associative pattern matching threshold
T = matching_threshold((n, p), (n, p))
def f(expr):
# Encoding logic
if config.get("component") == "input":
if isinstance(expr, list):
# Bundle multiple tokens
bundled = []
for e in expr:
res = f(e)
if res:
bundled.extend(res)
return sorted(list(set(bundled)))
if expr not in lex:
# 1-based indexing for native arrays
sampled = rng.choice(range(1, n + 1), size=p, replace=False)
lex[expr] = sorted(sampled.tolist())
return lex[expr]
# Decoding logic
if not expr:
return None
overlaps = {k: len(set(v).intersection(expr))
for k, v in lex.items() if k is not None}
result = [k for k, overlap in overlaps.items() if overlap >= T]
if len(result) == 0:
return None
elif len(result) == 1:
return result[0]
else:
return sorted(result)
def clear():
lex.clear()
lex[None] = []
return {
"label": "SYM",
"function": f,
"clear": clear
}
```
# Source: category.md
ENCODERS & DECODERS
# category

Encoder/decoder for a fixed number of categories.
See also: https://creatingintelligence.org/#sparse-holographic-representations
## Details and properties
- Typically used for supervised learning.
- Similar to [`codec`](#codec.md), but generates non-overlapping encodings.
- Encoder plugin for the [`input`](input.md) component.
- Decoder plugin for the [`output`](output.md) component.
- Encodes integers 0 to K-1 as non-overlapping SHRs.
- The default number of categories is taken to be the ratio of hyperparameters N/P.
- If an explicit number K of categories is specified, the SHR population is set to N/K.
- Mappings with identical parameters [N, P, K] are globally shared, enabling encode-decode roundtrips.
| Property | Default | Description |
|:------------|:-----:|:----------------------------------------|
| `categories` | floor(N/P) | number of categories |
## Encoding and decoding chain for categories
The following circuit encodes integers 0 to 19 as non-overlapping SHRs with population 50,
and subsequently decodes the internal representation, mirroring the input.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "plugin": "category",
"send": [1], "categories": 20},
{"component": "output", "plugin": "category",
"receive": [1], "categories": 20}
]}
```

## Source code
```python
def category(config):
hyperparameters = config.get("hyperparameters", [0, 0])
n, p = hyperparameters[0], hyperparameters[1]
K = config.get("categories", n // p if p > 0 else 0)
if K * p > n:
raise ValueError(
f"Encoder requires dimension {K * p} or greater.")
partitionsize = n // K if K > 0 else 0
lex_key = (n, p, K)
if lex_key not in circuit_category_lexicon:
circuit_category_lexicon[lex_key] = {None: []}
lex = circuit_category_lexicon[lex_key]
T = matching_threshold((n, p), (n, p))
def f(expr):
if config.get("component") == "input":
if not isinstance(expr, int) or expr < 0 or expr >= K:
return []
if expr not in lex:
# 1-based indexing for native arrays
start = partitionsize * expr + 1
end = start + partitionsize
lex[expr] = list(range(start, end))
pool = lex[expr]
sample_size = min(p, len(pool))
return sorted(
rng.choice(
pool,
size=sample_size,
replace=False).tolist())
if not expr:
return None
overlaps = {k: len(set(v).intersection(expr))
for k, v in lex.items() if k is not None}
result = [k for k, overlap in overlaps.items() if overlap >= T]
if len(result) == 0:
return None
elif len(result) == 1:
return result[0]
else:
return sorted(result)
def clear():
lex.clear()
lex[None] = []
return {
"label": "CAT",
"function": f,
"clear": clear
}
```
# Source: binning.md
ENCODERS & DECODERS
# binning

Decoder plugin, clustering similar Sparse Distributed Representations (SDRs).
See also: https://creatingintelligence.org/#sparse-distributed-representations
## Details
- Decoder plugin for the [`output`](output.md) component.
- Clusters similar SDRs into a variable number of bins.
- Uses the auto-associative pattern matching threshold to determine similarity.
- Assigns and returns bin numbers 1,2,...
- Maintains a globally shared mapping for each hyperparameter combination [N, P].
## Clustering SDRs
The following circuit classifies SDRs, returning bin numbers 1,2,...
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "send": [1]},
{"component": "output", "plugin": "binning", "receive": [1]}
]}
```

## Source code
```python
def binning(config):
hyperparameters = config.get("hyperparameters", [0, 0])
n, p = hyperparameters[0], hyperparameters[1]
lex_key = (n, p)
if lex_key not in circuit_binning_lexicon:
circuit_binning_lexicon[lex_key] = {None: []}
lex = circuit_binning_lexicon[lex_key]
T = matching_threshold((n, p), (n, p))
if config.get("component") == "input":
raise ValueError("binning cannot be used as an encoder.")
def f(expr):
if not expr:
return 0
overlaps = {k: len(set(v).intersection(expr))
for k, v in lex.items() if k is not None}
bins = [k for k, overlap in overlaps.items() if overlap >= T]
if len(bins) == 0:
new_bin = len(lex)
lex[new_bin] = expr
return new_bin
elif len(bins) == 1:
return bins[0]
else:
return sorted(bins)
def clear():
lex.clear()
lex[None] = []
return {
"label": "BIN",
"function": f,
"clear": clear
}
```
# Source: vectorencoder.md
ENCODERS & DECODERS
# vectorencoder

Encodes dense vector data as Sparse Distributed Representations (SDRs).
See also: https://creatingintelligence.org/#sdr-encoders
## Details and properties
- Encoder plugin for the [`input`](input.md) component.
- Encodes dense, real-valued vectors to SDRs.
- Maps similar vectors to similar SDRs.
- Projects vectors into the hyperdimensional space given by hyperparameters [N, P]
through multiplication with a sparse binary matrix followed by kWTA.
| Property | Default | Description |
|:------------|:-----:|:----------------------------------------|
| `sparsity` | 1/sqrt(D) | projection matrix sparsity (D is the input dimension) |
## Vector-to-SDR encoding
The following circuit encodes dense vectors as SDRs.
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "plugin": "vectorencoder", "send": [1]},
{"component": "output", "receive": [1]}
]}
```

## Source code
```python
def vectorencoder(config):
hyperparameters = config.get("hyperparameters", [0, 0])
n, p = hyperparameters[0], hyperparameters[1]
key = (n, p)
if key not in circuit_vectorencoder_matrices:
circuit_vectorencoder_matrices[key] = {}
R = circuit_vectorencoder_matrices[key]
if config.get("component") == "output":
raise ValueError("vectorencoder cannot be used as a decoder.")
def f(lst):
if not isinstance(lst, list):
lst = list(lst)
d = len(lst)
if p <= 0 or d == 0:
return []
sparsity = config.get("sparsity", float(1.0 / math.sqrt(d)))
if d not in R:
num_ones = round(n * d * sparsity)
flat_indices = rng.choice(n * d, size=num_ones, replace=False)
rows = flat_indices // d
cols = flat_indices % d
vals = np.ones(num_ones)
R[d] = sp.csr_matrix((vals, (rows, cols)), shape=(n, d))
matrix = R[d]
vec = matrix.dot(np.array(lst))
min_val, max_val = np.min(vec), np.max(vec)
span = max_val - min_val
scale = max(abs(min_val), abs(max_val))
tolerance = 1e-10
if scale == 0 or span <= tolerance * scale:
return []
k = min(p, len(vec))
top_k_indices = np.argsort(vec)[-k:]
return sorted((top_k_indices + 1).tolist())
def clear():
R.clear()
return {
"label": "VEC",
"function": f,
"clear": clear
}
```
# Source: flyhash.md
ENCODERS & DECODERS
# flyhash

Encodes dense vector data as Sparse Distributed Representations (SDRs)
using the classic flyhash algorithm.
See also: https://creatingintelligence.org/#sdr-encoders
## Details
- Encoder plugin for the [`input`](input.md) component.
- Encodes dense, real-valued vectors to SDRs.
- Maps similar vectors to similar SDRs.
## Flyhash encoding
The following circuit encodes dense vectors as SDRs:
```json
{ "hyperparameters": {"default": [1000, 10]},
"dataflow": [
{"component": "input", "plugin": "flyhash", "send": [1]},
{"component": "output", "receive": [1]}
]}
```

## Source code
```python
def flyhash(config):
hyperparameters = config.get("hyperparameters", [0, 0])
n, p = hyperparameters[0], hyperparameters[1]
key = (n, p)
if key not in circuit_flyhash_matrices:
circuit_flyhash_matrices[key] = {}
circuit_flyhash_resting[key] = {}
R = circuit_flyhash_matrices[key]
restingpotential = circuit_flyhash_resting[key]
sparsity = config.get("sparsity", 0.1)
if config.get("component") == "output":
raise ValueError("flyhash cannot be used as a decoder.")
def f(lst):
if not isinstance(lst, list):
lst = list(lst)
d = len(lst)
if p <= 0 or d == 0:
return []
arr = np.array(lst)
centeredlist = arr - np.mean(arr)
tolerance = 1e-10
if d not in R:
c = max(1, round(d * sparsity))
rows = np.repeat(np.arange(n), c)
cols = np.concatenate(
[rng.choice(d, size=c, replace=False) for _ in range(n)])
vals = np.ones(n * c)
R[d] = sp.csr_matrix((vals, (rows, cols)), shape=(n, d))
restingpotential[d] = rng.uniform(0, tolerance * 0.01, size=n)
vec = R[d].dot(centeredlist) + restingpotential[d]
min_val, max_val = np.min(vec), np.max(vec)
span = max_val - min_val
scale = max(abs(min_val), abs(max_val))
if scale == 0 or span <= tolerance * scale:
return []
k = min(p, len(vec))
top_k_indices = np.argsort(vec)[-k:]
return sorted((top_k_indices + 1).tolist())
def clear():
R.clear()
restingpotential.clear()
return {
"label": "FLY",
"function": f,
"clear": clear
}
```
# Source: circuits_source.md
SOURCE CODE
# Circuits
See also: https://creatingintelligence.org/#circuits
## Python source code
```python
"""
Copyright (c) 2026 Peter Overmann
SPDX-License-Identifier: MIT
This file is part of the "Creating Intelligence" project. It is licensed
under the MIT License. You may obtain a copy of the License in the LICENSE
file in the root directory of this repository.
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.
"""
"""
This package employs a standardized plugin mechanism for circuit components,
encoders (preprocessing), decoders (postprocessing), circuit embedding mechanisms,
and auto-associative update rules.
Plugins are defined via factory functions that take a dictionary as input
and dispatch a dictionary, using closures encapsulate stateful evaluation functions.
The function names match the corresponding value strings in the JSON dataflow description,
for example {"component": "auto", "plugin": "replacement", ...}
Users can patch custom plugins into the circuits namespace via:
import creating_intelligence
from myplugins import mycomponent
circuits.mycomponent = mycomponent
"""
# Formatting: autopep8 --ignore=E251 -a -a -i circuits.py
import itertools
import math
import sys
import os
import json
from collections import Counter
import numpy as np
import scipy.sparse as sp
import networkx as nx
import matplotlib.pyplot as plt
from creating_intelligence import Memory
# Global RNG
rng = np.random.default_rng()
def multiset(u):
"""
Summates excitatory and inhibitory signals.
Outputs a net multiset. No element will exist as both positive and negative.
"""
if not isinstance(u, list):
u = [u]
# Flatten nested structures if necessary
flat_u = []
def _flatten(items):
for item in items:
if isinstance(item, list):
_flatten(item)
else:
flat_u.append(item)
_flatten(u)
tally = Counter()
pos = [x for x in flat_u if x > 0]
neg = [-x for x in flat_u if x < 0]
tally.update(pos)
tally.subtract(neg)
result = []
for k, v in tally.items():
if v > 0:
result.extend([k] * v)
elif v < 0:
result.extend([-k] * abs(v))
return sorted(result)
def resolve_normal(x):
"""Applies graded inhibition, drops negatives, squashes excitatory survivors."""
ae = [i for i in x if i > 0]
ai = [-i for i in x if i < 0]
if not ai: # Fast path for pure boolean
return sorted(list(set(ae)))
counts_e = Counter(ae)
counts_i = Counter(ai)
survivors = [k for k, v in counts_e.items() if v > counts_i.get(k, 0)]
return sorted(survivors)
def resolve_graded(x):
"""Applies graded inhibition, drops negatives, keeps excitatory survivors."""
ae = [i for i in x if i > 0]
ai = [-i for i in x if i < 0]
if not ai: # Fast path for pure multisets
return sorted(ae)
counts_e = Counter(ae)
counts_i = Counter(ai)
result = []
for k, v in counts_e.items():
surviving_count = max(0, v - counts_i.get(k, 0))
result.extend([k] * surviving_count)
return sorted(result)
def multiset_block_join(blocks, dims):
"""Transparently joins multisets. Preserves duplicates and negative signs."""
offsets = list(itertools.accumulate([0] + dims[:-1]))
result = []
for sublist, offset in zip(blocks, offsets):
for val in sublist:
sign = 1 if val > 0 else -1 if val < 0 else 0
shifted = sign * (abs(val) + offset)
result.append(shifted)
return sorted(result)
def multiset_block_split(A, dims):
"""Transparently splits a joined multiset back into its original blocks."""
limits = list(itertools.accumulate([0] + dims))
bounds = list(zip(limits[:-1], limits[1:]))
split_blocks = []
for b_start, b_end in bounds:
chunk = []
for val in A:
if b_start < abs(val) <= b_end:
sign = 1 if val > 0 else -1 if val < 0 else 0
restored = sign * (abs(val) - b_start)
chunk.append(restored)
split_blocks.append(chunk)
return split_blocks
def multiset_block_join_normal(blocks, dims):
"""
Join blocks with dimensions into a single list.
Applies graded inhibition and drops inhibitory elements. Preserves multisets.
"""
offsets = list(itertools.accumulate([0] + dims[:-1]))
ae = []
ai = []
for sublist, offset in zip(blocks, offsets):
for val in sublist:
shifted_abs = abs(val) + offset
if val > 0:
ae.append(shifted_abs)
elif val < 0:
ai.append(shifted_abs)
counts_e = Counter(ae)
counts_i = Counter(ai)
net_excitatory = []
for k, v in counts_e.items():
surviving_count = max(0, v - counts_i.get(k, 0))
net_excitatory.extend([k] * surviving_count)
return sorted(net_excitatory)
def resolve_block_join_normal(blocks, dims):
"""
Applies graded subtraction, drops negatives, and squashes the survivors.
Outputs a flat, Boolean excitatory set.
"""
offsets = list(itertools.accumulate([0] + dims[:-1]))
ae = []
ai = []
for sublist, offset in zip(blocks, offsets):
for val in sublist:
shifted_abs = abs(val) + offset
if val > 0:
ae.append(shifted_abs)
elif val < 0:
ai.append(shifted_abs)
counts_e = Counter(ae)
counts_i = Counter(ai)
survivors = [k for k, v in counts_e.items() if v > counts_i.get(k, 0)]
return sorted(survivors)
def memory_capacity(A_params, B_params):
NA, PA = A_params
NB, PB = B_params
if NA == NB and PA == PB:
return round((math.log(2.0) * NB * (NA - 1) * (NA - 2)) /
(PB * (PA - 1) * (PA - 2)))
else:
return round((math.log(2.0) * NB * NA * (NA - 1)) /
(PB * PA * (PA - 1)))
def matching_threshold(A_params, B_params):
NA, PA = A_params
NB, PB = B_params
if PA <= 2 or PB <= 2:
return 0
cap = memory_capacity(A_params, B_params)
T = 1
while T < PA and (
cap**2 *
math.comb(
PA,
T) *
math.comb(
NA -
PA,
PA -
T) /
math.comb(
NA,
PA)) >= 1:
T += 1
return T
# Encoders and decoders
# Shared state structure keyed by (N, P) tuple
# By design, circuits with identical hyperparameters (N, P) share
# encoder/decoder instances.
circuit_codec_lexicon = {}
def codec(config):
"""General-purpose SHR encoder/decoder for symbolic expressions."""
# Extract hyperparameters from config
hyperparameters = config.get("hyperparameters", [0, 0])
n, p = hyperparameters[0], hyperparameters[1]
lex_key = (n, p)
# Initialize global hash table if missing for these dimensions
if lex_key not in circuit_codec_lexicon:
circuit_codec_lexicon[lex_key] = {None: []}
lex = circuit_codec_lexicon[lex_key]
# Use auto-associative pattern matching threshold
T = matching_threshold((n, p), (n, p))
def f(expr):
# Encoding logic
if config.get("component") == "input":
if isinstance(expr, list):
# Bundle multiple tokens
bundled = []
for e in expr:
res = f(e)
if res:
bundled.extend(res)
return sorted(list(set(bundled)))
if expr not in lex:
# 1-based indexing for native arrays
sampled = rng.choice(range(1, n + 1), size=p, replace=False)
lex[expr] = sorted(sampled.tolist())
return lex[expr]
# Decoding logic
if not expr:
return None
overlaps = {k: len(set(v).intersection(expr))
for k, v in lex.items() if k is not None}
result = [k for k, overlap in overlaps.items() if overlap >= T]
if len(result) == 0:
return None
elif len(result) == 1:
return result[0]
else:
return sorted(result)
def clear():
lex.clear()
lex[None] = []
return {
"label": "SYM",
"function": f,
"clear": clear
}
# Shared state structures keyed by (n, p) tuple
circuit_category_lexicon = {}
circuit_category_lexicon = {}
def category(config):
hyperparameters = config.get("hyperparameters", [0, 0])
n, p = hyperparameters[0], hyperparameters[1]
K = config.get("categories", n // p if p > 0 else 0)
if K * p > n:
raise ValueError(
f"Encoder requires dimension {K * p} or greater.")
partitionsize = n // K if K > 0 else 0
lex_key = (n, p, K)
if lex_key not in circuit_category_lexicon:
circuit_category_lexicon[lex_key] = {None: []}
lex = circuit_category_lexicon[lex_key]
T = matching_threshold((n, p), (n, p))
def f(expr):
if config.get("component") == "input":
if not isinstance(expr, int) or expr < 0 or expr >= K:
return []
if expr not in lex:
# 1-based indexing for native arrays
start = partitionsize * expr + 1
end = start + partitionsize
lex[expr] = list(range(start, end))
pool = lex[expr]
sample_size = min(p, len(pool))
return sorted(
rng.choice(
pool,
size=sample_size,
replace=False).tolist())
if not expr:
return None
overlaps = {k: len(set(v).intersection(expr))
for k, v in lex.items() if k is not None}
result = [k for k, overlap in overlaps.items() if overlap >= T]
if len(result) == 0:
return None
elif len(result) == 1:
return result[0]
else:
return sorted(result)
def clear():
lex.clear()
lex[None] = []
return {
"label": "CAT",
"function": f,
"clear": clear
}
circuit_binning_lexicon = {}
def binning(config):
hyperparameters = config.get("hyperparameters", [0, 0])
n, p = hyperparameters[0], hyperparameters[1]
lex_key = (n, p)
if lex_key not in circuit_binning_lexicon:
circuit_binning_lexicon[lex_key] = {None: []}
lex = circuit_binning_lexicon[lex_key]
T = matching_threshold((n, p), (n, p))
if config.get("component") == "input":
raise ValueError("binning cannot be used as an encoder.")
def f(expr):
if not expr:
return 0
overlaps = {k: len(set(v).intersection(expr))
for k, v in lex.items() if k is not None}
bins = [k for k, overlap in overlaps.items() if overlap >= T]
if len(bins) == 0:
new_bin = len(lex)
lex[new_bin] = expr
return new_bin
elif len(bins) == 1:
return bins[0]
else:
return sorted(bins)
def clear():
lex.clear()
lex[None] = []
return {
"label": "BIN",
"function": f,
"clear": clear
}
circuit_vectorencoder_matrices = {}
def vectorencoder(config):
hyperparameters = config.get("hyperparameters", [0, 0])
n, p = hyperparameters[0], hyperparameters[1]
key = (n, p)
if key not in circuit_vectorencoder_matrices:
circuit_vectorencoder_matrices[key] = {}
R = circuit_vectorencoder_matrices[key]
if config.get("component") == "output":
raise ValueError("vectorencoder cannot be used as a decoder.")
def f(lst):
if not isinstance(lst, list):
lst = list(lst)
d = len(lst)
if p <= 0 or d == 0:
return []
sparsity = config.get("sparsity", float(1.0 / math.sqrt(d)))
if d not in R:
num_ones = round(n * d * sparsity)
flat_indices = rng.choice(n * d, size=num_ones, replace=False)
rows = flat_indices // d
cols = flat_indices % d
vals = np.ones(num_ones)
R[d] = sp.csr_matrix((vals, (rows, cols)), shape=(n, d))
matrix = R[d]
vec = matrix.dot(np.array(lst))
min_val, max_val = np.min(vec), np.max(vec)
span = max_val - min_val
scale = max(abs(min_val), abs(max_val))
tolerance = 1e-10
if scale == 0 or span <= tolerance * scale:
return []
k = min(p, len(vec))
top_k_indices = np.argsort(vec)[-k:]
return sorted((top_k_indices + 1).tolist())
def clear():
R.clear()
return {
"label": "VEC",
"function": f,
"clear": clear
}
circuit_flyhash_matrices = {}
circuit_flyhash_resting = {}
def flyhash(config):
hyperparameters = config.get("hyperparameters", [0, 0])
n, p = hyperparameters[0], hyperparameters[1]
key = (n, p)
if key not in circuit_flyhash_matrices:
circuit_flyhash_matrices[key] = {}
circuit_flyhash_resting[key] = {}
R = circuit_flyhash_matrices[key]
restingpotential = circuit_flyhash_resting[key]
sparsity = config.get("sparsity", 0.1)
if config.get("component") == "output":
raise ValueError("flyhash cannot be used as a decoder.")
def f(lst):
if not isinstance(lst, list):
lst = list(lst)
d = len(lst)
if p <= 0 or d == 0:
return []
arr = np.array(lst)
centeredlist = arr - np.mean(arr)
tolerance = 1e-10
if d not in R:
c = max(1, round(d * sparsity))
rows = np.repeat(np.arange(n), c)
cols = np.concatenate(
[rng.choice(d, size=c, replace=False) for _ in range(n)])
vals = np.ones(n * c)
R[d] = sp.csr_matrix((vals, (rows, cols)), shape=(n, d))
restingpotential[d] = rng.uniform(0, tolerance * 0.01, size=n)
vec = R[d].dot(centeredlist) + restingpotential[d]
min_val, max_val = np.min(vec), np.max(vec)
span = max_val - min_val
scale = max(abs(min_val), abs(max_val))
if scale == 0 or span <= tolerance * scale:
return []
k = min(p, len(vec))
top_k_indices = np.argsort(vec)[-k:]
return sorted((top_k_indices + 1).tolist())
def clear():
R.clear()
restingpotential.clear()
return {
"label": "FLY",
"function": f,
"clear": clear
}
# Circuit Components
def input(config):
"""
At the beginning of each cycle, external input is pushed
into the input's send slots. Input nodes have no "receive" slots.
The callback function serves as an optional encoder/preprocessing plugin.
"""
pluginconfig = config.copy()
if "send_blocks" in config and config["send_blocks"]:
pluginconfig["hyperparameters"] = config["send_blocks"][0]
plugin_factory = config.get("plugin")
if callable(plugin_factory):
plugin = plugin_factory(pluginconfig)
else:
# Default Identity function
plugin = {"function": lambda x: x}
return plugin | {"size": 10, "checks": ["input", "oneout"]}
def output(config):
"""
At the end of each cycle, the output's input edges are read out.
Output nodes have no "send" slots.
The callback function serves as an optional decoder/postprocessing plugin.
"""
pluginconfig = config.copy()
if "receive_blocks" in config and config["receive_blocks"]:
pluginconfig["hyperparameters"] = config["receive_blocks"][0]
plugin_factory = config.get("plugin")
if callable(plugin_factory):
plugin = plugin_factory(pluginconfig)
else:
# Default Identity function
plugin = {"function": lambda x: x}
return plugin | {"size": 10, "checks": ["output", "oneinp"]}
def delay(config):
plugin_factory = config.get("plugin", replacement)
if isinstance(plugin_factory, str):
import sys
plugin_factory = getattr(
sys.modules[__name__],
plugin_factory,
replacement)
plugin = plugin_factory(config)
if plugin_factory is replacement:
plugin.pop("label", None)
dims1 = [b[0] for b in config.get("receive_blocks", [])]
dims2 = [b[0] for b in config.get("send_blocks", [])]
pop = sum(b[1] for b in config.get("receive_blocks", []))
threshold_factor = config.get("threshold", 0.0)
absthreshold = round(threshold_factor * pop)
rate_limit_factor = config.get("rate_limit", float('inf'))
absratelimit = round(
rate_limit_factor *
pop) if rate_limit_factor != float('inf') else float('inf')
decay = config.get("decay", 0.0)
capacity_factor = config.get("capacity", 1.0)
abscapacity = round(capacity_factor * pop)
decimation = config.get("decimate", 1.0)
latch = config.get("latch", False)
Xstate = []
def f(*blocks):
nonlocal Xstate
X = multiset_block_join(list(blocks), dims1)
# Enforce minimum population
if len(X) < absthreshold:
X = []
if len(X) > absratelimit:
X = sorted(
rng.choice(
X,
size=absratelimit,
replace=False).tolist())
if decay > 0.0:
keep_size = math.floor((1.0 - decay) * len(Xstate))
Xstate = sorted(
rng.choice(
Xstate,
size=keep_size,
replace=False).tolist())
if len(Xstate) > abscapacity:
Xstate = sorted(
rng.choice(
Xstate,
size=abscapacity,
replace=False).tolist())
# Temporal integration via update rules
if not (latch and len(X) == 0):
Xstate = plugin["updaterule"](X, Xstate)
# Proportional multiset subsampling applied to output
Xdec = Xstate
if decimation < 1.0:
sample_size = math.floor(decimation * len(Xdec))
Xdec = sorted(
rng.choice(
Xdec,
size=sample_size,
replace=False).tolist())
return tuple(multiset_block_split(Xdec, dims2))
result = dict(plugin)
result.update({
"function": f,
"checks": ["arginp", "argout", "totaldim"],
"fill": 13
})
return result
# Update rules for auto-associative memory components ("auto")
# and temporal integration ("delay").
def replacement(config):
return {"updaterule": lambda y, x: y, "label": "▼", "size": 18}
def residual(config):
return {"updaterule": lambda y, x: resolve_graded(
multiset([x, [-i for i in y]])), "label": "▲", "size": 18}
def complement(config):
return {"updaterule": lambda y, x: resolve_graded(
multiset([y, [-i for i in x]])), "label": "▽", "size": 20}
def difference(config):
return {"updaterule": lambda y, x: sorted(
[abs(i) for i in multiset([y, [-i for i in x]])]), "label": "△", "size": 20}
def augmentation(config):
return {"updaterule": lambda y, x: multiset(
[y, x]), "label": "∪", "size": 14}
def coincidence(config):
def multiset_intersection(y, x):
counts_y = Counter(y)
counts_x = Counter(x)
res = []
for k, v in counts_y.items():
res.extend([k] * min(v, counts_x.get(k, 0)))
return sorted(res)
return {"updaterule": multiset_intersection, "label": "∩", "size": 14}
def permutation(config):
dims = sum(b[0] for b in config.get("receive_blocks", []))
dim = dims if dims else 0
perm = rng.choice(
range(1, dim + 1),
size = dim,
replace=False).tolist() if dim > 0 else []
def updaterule(y, x):
mapped_x = sorted([(1 if i > 0 else -1 if i < 0 else 0)
* perm[abs(i) - 1] for i in x if i != 0])
return multiset([y, mapped_x])
return {"updaterule": updaterule, "label": "π", "size": 16}
"""
Auto-associative memory component .
Note: There is a wide range of possible learning, subsampling, retrieval
and update rules for auto-associative memory . This prototype captures the
geneneric cases . Modify as needed .
"""
def auto(config):
plugin_factory = config.get("plugin", replacement)
if isinstance(plugin_factory, str):
# Resolve string name to function dynamically
import sys
plugin_factory = getattr(
sys.modules[__name__],
plugin_factory,
replacement)
plugin = plugin_factory(config)
receive_blocks = config.get("receive_blocks", [])
dims = [b[0] for b in receive_blocks]
# params = Plus @@ config["receive_blocks"] (Sums Ns and Ps respectively)
params = [sum(b[0] for b in receive_blocks), sum(b[1]
for b in receive_blocks)]
pop = params[1] if len(params) > 1 else 0
rate_limit_factor = config.get("rate_limit", float('inf'))
absratelimit = round(
rate_limit_factor *
pop) if rate_limit_factor != float('inf') else float('inf')
decimation = config.get("decimate", 1.0)
# Learning thresholds
min_val = pop
max_val = pop
learn_val = config.get("learn", False)
if isinstance(learn_val, (int, float)) and not isinstance(learn_val, bool):
min_val = max_val = round(learn_val * pop)
elif isinstance(learn_val, (list, tuple)) and len(learn_val) == 2:
min_val = round(learn_val[0] * pop)
max_val = round(learn_val[1] * pop)
# Memory with identical input and output parameters
m_config = dict(config)
m_config.update({
"A_parameters": params,
"B_parameters": params
})
# Memory backend
M = Memory(m_config)
def f(*blocks):
# Join partitions and apply inhibition
A = resolve_block_join_normal(list(blocks), dims)
X = A
# Limit the rate of incoming positives
if len(X) > absratelimit:
X = sorted(
rng.choice(
X,
size=absratelimit,
replace=False).tolist())
# Proportional decimation
if decimation < 1.0:
sample_size = math.floor(decimation * len(X))
Xdec = sorted(
rng.choice(
X,
size=sample_size,
replace=False).tolist())
else:
Xdec = X
# Always learn if "learn" is True
if learn_val is True:
M["store"](A)
# Memory retrieval with decimated and rate-limited input state
Y = M["retrieve"](Xdec)
# Learn input A if retrieval fails and it falls within thresholds
if not Y and min_val <= len(A) <= max_val:
if learn_val is not True:
M["store"](A)
Y = A
X_out = plugin["updaterule"](Y, X)
X_out = sorted(list(set(X_out)))
return tuple(multiset_block_split(X_out, dims))
# Merge plugin attributes, component defaults, and Memory closures (e.g.,
# "clear")
result = dict(plugin)
result.update({
"function": f,
"checks": ["arginp", "argout", "ident"],
"shape": "square", "fill": 8
})
result.update(M)
return result
"""
Temporal-associative memory component .
Learns higher-order sequences on the fly and predicts the next token .
A hybrid between auto-associative and hetero-associative architectures .
Uses the same temporal integration parametrization as "delay" .
"""
def temporal(config):
plugin_factory = config.get("plugin", replacement)
if isinstance(plugin_factory, str):
import sys
plugin_factory = getattr(
sys.modules[__name__],
plugin_factory,
replacement)
plugin = plugin_factory(config)
receive_blocks = config.get("receive_blocks", [])
dims = [b[0] for b in receive_blocks]
params = [sum(b[0] for b in receive_blocks), sum(b[1]
for b in receive_blocks)]
pop = params[1] if len(params) > 1 else 0
threshold_factor = config.get("threshold", 0.0)
absthreshold = round(threshold_factor * pop)
rate_limit_factor = config.get("rate_limit", float('inf'))
absratelimit = round(
rate_limit_factor *
pop) if rate_limit_factor != float('inf') else float('inf')
decay = config.get("decay", 0.0)
capacity_factor = config.get("capacity", 1.0)
abscapacity = round(capacity_factor * pop)
decimation = config.get("decimate", 1.0)
latch = config.get("latch", False)
# Memory with identical input and output parameters
m_config = dict(config)
m_config.update({
"A_parameters": params,
"B_parameters": params
})
M = Memory(m_config)
Xstate = []
prediction = []
def f(*blocks):
nonlocal Xstate, prediction
X = multiset_block_join(list(blocks), dims)
# Enforce minimum population
if len(X) < absthreshold:
X = []
# Rate limiting equally applies to positive and negative elements
if len(X) > absratelimit:
X = sorted(
rng.choice(
X,
size=absratelimit,
replace=False).tolist())
# Pre-integration stochastic decay
if decay > 0.0:
keep_size = math.floor((1.0 - decay) * len(Xstate))
Xstate = sorted(
rng.choice(
Xstate,
size=keep_size,
replace=False).tolist())
# Learn state -> current input if prediction was incorrect.
if prediction != X:
M["store"](resolve_normal(Xstate), resolve_normal(X))
# Multiset subsampling applied to previous state
if len(Xstate) > abscapacity:
Xstate = sorted(
rng.choice(
Xstate,
size=abscapacity,
replace=False).tolist())
# Temporal integration via update rules
if not (latch and len(X) == 0):
Xstate = plugin["updaterule"](X, Xstate)
# Proportional multiset subsampling applied to the integrated state
Xdec = Xstate
if decimation < 1.0:
sample_size = math.floor(decimation * len(Xdec))
Xdec = sorted(
rng.choice(
Xdec,
size=sample_size,
replace=False).tolist())
# Predict next token
prediction = M["retrieve"](resolve_normal(Xdec))
return tuple(multiset_block_split(prediction, dims))
result = dict(plugin)
result.update({
"function": f,
"checks": ["arginp", "argout", "ident"],
"shape": "square", "fill": 14
})
result.update(M)
return result
def associator(config):
"""
Vanilla hetero-associative node for supervised learning A -> B.
B is the first input argument.
A is given by the rest of the input arguments.
Can be block-coded. Always learns if B != [].
"""
receive_blocks = config.get("receive_blocks", [])
Adims = [b[0] for b in receive_blocks[1:]]
params_A = [sum(b[0] for b in receive_blocks[1:]), sum(b[1]
for b in receive_blocks[1:])]
params_B = receive_blocks[0]
m_config = dict(config)
m_config.update({
"A_parameters": params_A,
"B_parameters": params_B
})
M = Memory(m_config)
def f(B, *blocks):
X = resolve_block_join_normal(list(blocks), Adims)
Y = resolve_normal(B)
if not Y:
return (M["retrieve"](X),)
M["store"](X, Y)
return ([],)
result = {
"function": f,
"checks": ["dimfirst", "oneout"],
"shape": "square", "fill": 11, "size": 18, "label": "▶●"
}
result.update(M)
return result
def heteroencoder(config):
"""
Heteroencoder A -> B. A may be partitioned. Has no input for B.
"""
receive_blocks = config.get("receive_blocks", [])
dims = [b[0] for b in receive_blocks]
params_A = [sum(b[0] for b in receive_blocks), sum(b[1]
for b in receive_blocks)]
params_B = config.get("send_blocks", [[0, 0]])[0]
m_config = dict(config)
m_config.update({
"A_parameters": params_A,
"B_parameters": params_B
})
M = Memory(m_config)
def f(*blocks):
X = resolve_block_join_normal(list(blocks), dims)
Y = M["retrieve"](X)
if not Y:
n, p = params_B
Y = sorted(
rng.choice(
range(1, n + 1),
size=p, replace=False).tolist())
M["store"](X, Y)
return (Y,)
result = {
"function": f,
"checks": ["arginp", "oneout"],
"shape": "square",
"fill": 11,
"size": 18,
"label": "◀▶"
}
result.update(M)
return result
def predictor(config):
"""
Generates a prediction based on the current input (slot #1)
and context (slots #2,...).
Automatically learns the correct prediction in the following cycle.
"""
receive_blocks = config.get("receive_blocks", [])
itemconfig = receive_blocks[0]
contextconfig = receive_blocks[1:]
# Extract decimation parameter, defaulting to 1.0
decimation = config.get("decimate", 1.0)
params_A = [sum(b[0] for b in contextconfig), sum(b[1]
for b in contextconfig)]
params_B = itemconfig
m_config = dict(config)
m_config.update({
"A_parameters": params_A,
"B_parameters": params_B
})
M = Memory(m_config)
X = []
prediction = []
def f(item, *blocks):
nonlocal X, prediction
Y = resolve_normal(item)
if prediction != X:
M["store"](X, Y)
X = resolve_block_join_normal(
list(blocks), [b[0] for b in contextconfig])
# Apply stochastic subsampling to X before retrieval
if decimation < 1.0:
sample_size = math.floor(decimation * len(X))
Xdec = sorted(
rng.choice(
X,
size=sample_size,
replace=False).tolist())
else:
Xdec = X
prediction = M["retrieve"](Xdec)
return (prediction,)
result = {
"function": f,
"checks": ["dimfirst", "oneout"],
"shape": "square", "fill": 11, "size": 18, "label": "▶▶"
}
result.update(M)
return result
def noise(config):
n, p = config.get("send_blocks", [[0, 0]])[0]
def f(*blocks):
return (
sorted(
rng.choice(range(1, n + 1),
size=p,
replace=False).tolist()),
)
return {
"function": f,
"checks": ["input", "oneout"],
"label": "~", "fill": 13, "size": 18
}
def file(config):
file_path = config.get("name")
if not file_path:
raise CircuitError(f"Missing 'name' property in {config}")
if not file_path.endswith(".json"):
file_path += ".json"
# Search current working directory first, then sys.path
search_paths = [""] + sys.path
full_path = None
for base in search_paths:
target = os.path.join(base, file_path) if base else file_path
if os.path.exists(target):
full_path = target
break
if not full_path:
raise CircuitError(
f"File '{file_path}' not found in current directory or sys.path.")
with open(full_path, "r") as f:
circ_expr = json.load(f)
# Rescale hyperparameters of embedded circuit
scale = config.get("scale")
default_hp = circ_expr.get("hyperparameters", {}).get("default")
if (isinstance(scale, (list, tuple)) and len(scale) == 2 and
isinstance(default_hp, (list, tuple)) and len(default_hp) == 2):
# Calculate separate scaling factors
rescale_factor_n = scale[0] / default_hp[0]
rescale_factor_p = scale[1] / default_hp[1]
# Apply scaling to all hyperparameters in the embedded circuit
for k, v in circ_expr["hyperparameters"].items():
if isinstance(v, (list, tuple)) and len(v) == 2:
circ_expr["hyperparameters"][k] = [
round(v[0] * rescale_factor_n),
round(v[1] * rescale_factor_p)
]
# Compile the imported circuit
sub = Circuit(circ_expr)
# Extract up to 5 characters from the filename for the label
base_name = os.path.splitext(os.path.basename(file_path))[0]
sub["label"] = base_name[:5]
return sub
# Registry for compiled embedded circuits
circuit_registry = {}
def shared(config):
"""Plug-in for embedding a precompiled circuit."""
name = config.get("name")
if not name:
raise ValueError(
f"Missing 'name' property in shared circuit: {config}")
sub = circuit_registry.get(name)
if not sub:
raise ValueError(f"Unknown circuit '{name}'.")
result = dict(sub)
result["label"] = name
result.pop("clear", None)
return result
def circuit(config):
"""
Embedded circuit component.
Multiple circuit components can reference the same embedded instance.
"""
plugin_factory = config.get("plugin", shared)
if isinstance(plugin_factory, str):
import sys
plugin_factory = getattr(sys.modules[__name__], plugin_factory, shared)
plugin = plugin_factory(config)
if not plugin:
return {}
params = [plugin.get("receive_blocks", []), plugin.get("send_blocks", [])]
if [config.get("receive_blocks", []), config.get(
"send_blocks", [])] != params:
raise ValueError(
f"Hyperparameters of embedded circuit do not match: {params}")
def f(*blocks):
return plugin["function"](*blocks)
result = dict(plugin)
result.update({
"function": f,
"checks": ["arginp", "argout"],
"shape": "square", "size": 9
})
return result
# Circuit Error Handling
class CircuitError(Exception):
"""Custom exception for Circuit-related errors."""
pass
circuit_messages = {
"ident": "{0}: Inputs must match outputs.",
"identdim": "{0}: Input and output dimensions must be identical.",
"totaldim": "{0}: Total input and output dimensions must match.",
"dimfirst": "{0}: Output must match first input slot.",
"input": "No input slots allowed in {0}.",
"oneinp": "Expecting one input slot in {0}.",
"arginp": "{0}: Missing input.",
"output": "No output slots allowed in {0}.",
"oneout": "{0}: Expecting one output slot.",
"argout": "{0}: Missing output."
}
circuit_checks = {
"ident": lambda r,
s: [b[0] for b in r] == [b[0] for b in s],
"identdim": lambda r,
s: sorted(list(set(b[0] for b in r))) == sorted(list(set(b[0] for b in s))),
"totaldim": lambda r,
s: sum(b[0] for b in r) == sum(b[0] for b in s),
"dimfirst": lambda r,
s: len(r) > 0 and len(s) > 0 and r[0] == s[0],
"input": lambda r,
s: len(r) == 0,
"oneinp": lambda r,
s: len(r) == 1,
"arginp": lambda r,
s: len(r) > 0,
"output": lambda r,
s: len(s) == 0,
"oneout": lambda r,
s: len(s) == 1,
"argout": lambda r,
s: len(s) > 0}
# Circuit Visualization
# 16-color Nord hex palette as defined in the Mathematica source
NORD_PALETTE = [
"#2E3440", "#3B4252", "#434C5E", "#4C566A",
"#D8DEE9", "#E5E9F0", "#ECEFF4", "#8FBCBB",
"#88C0D0", "#81A1C1", "#5E81AC", "#BF616A",
"#D08770", "#EBCB8B", "#A3BE8C", "#B48EAD"
]
# Tag mapping for edge labels
TAG_MAP = {
"multiset": "+", "inhibit": "-", "permute": "π",
"noise": "~", "rate_limit": "R", "threshold": "T",
"veto": "X", "mandatory": "*", "priority": "!",
"dependency": "&", "fallback": "|", "barrier": "=",
"kwta_excitatory": "K", "kwta_absolute": "k",
"log": "?", "show_slot": "#", "show_dimension": "$",
"show_population": "%"
}
def Circuit(expr):
"""
Parses a circuit dataflow dictionary or JSON string and returns
a compiled circuit interface.
Implements data-driven execution flow and state isolation.
"""
if isinstance(expr, str):
expr = json.loads(expr)
nodes = {}
pathways = {}
preprocess = []
postprocess = []
inputslots = []
outputedges = []
receiveparams = []
sendparams = []
permutations = {}
# Isolated runtime state buffers
tick = 0
nextstate = {}
currentstate = {}
nodeid = 0
edgeid = 0
def slotparams(slot):
hyperparameters = expr.get("hyperparameters", {})
default = hyperparameters.get("default", [100, 3])
# Accommodate both int and string keys from JSON
np_val = hyperparameters.get(
slot, hyperparameters.get(
str(slot), default))
if not (isinstance(np_val, list) and len(np_val) == 2):
return [100, 3]
return np_val
def fillparams(x):
if isinstance(x, list):
lists = [fillparams(i) for i in x]
dims = [l[0] for l in lists]
return [dims[0], min(l[1] for l in lists)]
elif isinstance(x, int):
return slotparams(x)
elif isinstance(x, dict):
return slotparams(x["slot"])
return None
def compilepathway(receive):
nonlocal edgeid
if isinstance(receive, list):
return [compilepathway(r) for r in receive]
edgeid += 1
current_id = edgeid
e = {
"to_node_id": nodeid,
"label": "",
"tags": []
}
if isinstance(receive, int):
e["slot"] = receive
elif isinstance(receive, dict):
e.update(receive)
e["hyperparameters"] = slotparams(e.get("slot"))
if "inhibit" in e["tags"] and "multiset" not in e["tags"]:
e["tags"].append("multiset")
pathways[current_id] = e
currentstate[e.get("slot")] = []
# Initialize 1-based permutation array
n = e["hyperparameters"][0]
perm = rng.choice(range(1, n + 1), size=n, replace=False).tolist()
permutations[current_id] = perm
return current_id
def compile_node(node):
nonlocal nodeid
nodeid += 1
current_node_id = nodeid
rec_paths = [compilepathway(r) for r in node.get("receive", [])]
# Flatten send array and extract slots
raw_sends = node.get("send", [])
flat_sends = []
def _flat(items):
for i in items:
if isinstance(i, list):
_flat(i)
else:
flat_sends.append(i)
_flat(raw_sends)
send_slots = []
for s in flat_sends:
if isinstance(s, int):
send_slots.append(s)
elif isinstance(s, dict) and "slot" in s:
send_slots.append(s["slot"])
# Base configuration dictionary
config = dict(node)
config.update({
"receive_paths": rec_paths,
"node_id": current_node_id,
"send_slots": send_slots,
"scale": expr.get("hyperparameters", {}).get("default"),
"receive_blocks": [fillparams(r) for r in node.get("receive", [])],
"send_blocks": [fillparams(s) for s in node.get("send", [])]
})
# Resolve plugin dependency dynamically
plugin_name = config.get("plugin")
if isinstance(plugin_name, str):
plugin_func = getattr(sys.modules[__name__], plugin_name, None)
if callable(plugin_func):
config["plugin"] = plugin_func
else:
raise ValueError(f"Invalid plugin '{plugin_name}'.")
comp_name = node.get("component")
if not isinstance(comp_name, str):
return
# Dynamic module namespace lookup
comp_func = getattr(sys.modules[__name__], comp_name, None)
if not callable(comp_func):
raise ValueError(f"Invalid circuit component '{comp_name}'.")
comp_result = comp_func(config)
# Strict overriding sequence
final_config = {
"shape": "circle", "label": "", "fill": 6, "color": 0, "size": 12,
"checks": [], "function": None
}
final_config.update(config)
final_config.update(comp_result)
# Static error checking, based on component-supplied "checks"
for check in final_config.get("checks", []):
if check in circuit_checks:
if not circuit_checks[check](
final_config["receive_blocks"],
final_config["send_blocks"]):
error_msg = circuit_messages.get(
check, f"Check {check} failed.").format(comp_name)
raise CircuitError(error_msg)
if comp_name == "input":
preprocess.append(final_config["function"])
inputslots.append(final_config["send_slots"][0])
receiveparams.append(final_config["send_blocks"][0])
final_config["label"] = final_config["label"].replace(
"#", str(len(inputslots)))
if comp_name == "output":
postprocess.append(final_config["function"])
outputedges.append(final_config["receive_paths"][0])
sendparams.append(final_config["receive_blocks"][0])
final_config["label"] = final_config["label"].replace(
"#", str(len(outputedges)))
nodes[current_node_id] = final_config
# Trigger compilation
for node in expr.get("dataflow", []):
compile_node(node)
# Compile link table for from_node_id values
links = {}
for node in nodes.values():
for slot in node.get("send_slots", []):
links[slot] = node["node_id"]
for edge in pathways.values():
edge["from_node_id"] = links.get(edge.get("slot"), 0)
def patheval(id_val):
e = pathways[id_val]
tags = set(e.get("tags", []))
n, p = e["hyperparameters"]
x = currentstate.get(e.get("slot"), [])
# Upstream validation of 1-based non-zero data
if not isinstance(x, list) or 0 in x:
raise ValueError(f"Invalid data in slot {e.get('slot')}: {x}")
gating = e.get("gating", [1])
if gating[(tick - 1) % len(gating)] == 0:
return []
# The ONLY source of inhibition
if "inhibit" in tags:
x = [-abs(i) for i in x]
if "multiset" in tags:
x = multiset(x)
else:
x = resolve_normal(x)
if "permute" in tags:
perm = permutations[id_val]
x = sorted([(1 if i > 0 else -1) * perm[abs(i) - 1] for i in x])
if "rate_limit" in tags and len(x) > p:
x = sorted(rng.choice(x, size=p, replace=False).tolist())
if "threshold" in tags and len(x) < p:
x = []
if "noise" in tags:
if not x:
x = sorted(
rng.choice(
range(
1,
n + 1),
size=p,
replace=False).tolist())
else:
x = []
if "log" in tags:
print(f"Slot {e.get('slot')}: {x}")
return x
def pathmerge(ids):
if isinstance(ids, int):
ids = [ids]
x_list = [patheval(i) for i in ids]
paths = [pathways[i] for i in ids]
def has_tag(t):
return [t in p.get("tags", []) for p in paths]
veto_tags = has_tag("veto")
if any(x for i, x in enumerate(x_list) if veto_tags[i] and x):
return []
mandatory_tags = has_tag("mandatory")
if any(not x for i, x in enumerate(x_list) if mandatory_tags[i]):
return []
priority_tags = has_tag("priority")
if any(x for i, x in enumerate(x_list) if priority_tags[i] and x):
x_list = [x if priority_tags[i] else []
for i, x in enumerate(x_list)]
dependency_tags = has_tag("dependency")
if any(not x for i, x in enumerate(x_list) if not dependency_tags[i]):
x_list = [[] if dependency_tags[i]
else x for i, x in enumerate(x_list)]
fallback_tags = has_tag("fallback")
if any(x for i, x in enumerate(x_list) if not fallback_tags[i] and x):
x_list = [[] if fallback_tags[i]
else x for i, x in enumerate(x_list)]
barrier_tags = has_tag("barrier")
if any(barrier_tags) and any(not x for i,
x in enumerate(x_list) if barrier_tags[i]):
x_list = [[] if barrier_tags[i]
else x for i, x in enumerate(x_list)]
merged = multiset(x_list)
kwta_exc_tags = has_tag("kwta_excitatory")
kwta_abs_tags = has_tag("kwta_absolute")
if any(kwta_exc_tags) or any(kwta_abs_tags):
k = min(p["hyperparameters"][1] for p in paths)
U = [i for i in merged if i > 0] if any(kwta_exc_tags) else merged
tally = Counter(U)
if len(tally) >= k:
freqs = sorted(tally.values())
rankedmax = freqs[-k]
if rankedmax >= 2:
merged = sorted(
[val for val, count in tally.items() if count >= rankedmax])
else:
merged = []
else:
merged = []
return merged
def componenteval(config):
if config.get("component") in ("input", "output"):
return
inputs = [pathmerge(rp) for rp in config.get("receive_paths", [])]
func = config.get("function")
result = func(*inputs) if func else tuple([]
for _ in config.get("send_slots", []))
if not isinstance(result, tuple):
result = (result,)
for slot, res in zip(config.get("send_slots", []), result):
nextstate[slot] = res
def evaluate(x):
nonlocal tick, currentstate, nextstate
tick += 1
for slot, val in zip(inputslots, x):
currentstate[slot] = val
nextstate[slot] = val
for config in nodes.values():
componenteval(config)
# Snapshot state, preparing for extraction and next cycle
currentstate = dict(nextstate)
return [pathmerge(edge) for edge in outputedges]
def multiplex(x):
if len(x) != len(inputslots):
raise ValueError(
f"Function arguments do not match input nodes: {inputslots}")
multiplex = expr.get("options", {}).get("multiplex", [1])
if not all(m in (0, 1) for m in multiplex):
multiplex = [1]
y = []
for gate in multiplex:
if gate == 1:
y = evaluate(x)
else:
y = evaluate([[] for _ in x])
return y
def f(*blocks):
x = list(blocks)
if len(x) != len(inputslots):
raise ValueError(
f"Function arguments do not match input nodes: {inputslots}")
x_enc = [prep(val) for prep, val in zip(preprocess, x)]
y_raw = multiplex(x_enc)
y = [post(val) for post, val in zip(postprocess, y_raw)]
return tuple(y)
def schematics(filename):
"""Generates a static PNG graph visualization of the circuit."""
G = nx.MultiDiGraph()
# Build nodes
for nid, node_data in nodes.items():
fill_color = NORD_PALETTE[node_data.get("fill", 6)]
text_color = NORD_PALETTE[node_data.get("color", 0)]
label = node_data.get("label", "")
# Map node shapes and apply shape-specific area multipliers
if node_data.get("shape") == "square":
shape = "s"
size_multiplier = 100
else:
shape = "o" # Default Circle
size_multiplier = 180 # Scale circles up relative to squares
G.add_node(
nid,
label=label,
fill_color=fill_color,
text_color=text_color,
shape=shape,
size=node_data.get("size", 12) * size_multiplier
)
# Build edges
for eid, edge_data in pathways.items():
u = edge_data.get("from_node_id")
v = edge_data.get("to_node_id")
if not u or not v:
continue
tags = edge_data.get("tags", [])
# Construct edge label logic
tag_symbols = "".join([TAG_MAP.get(t, "") for t in tags])
user_label = edge_data.get("label", "")
gating = edge_data.get("gating")
gating_str = "@" + "".join(str(g)
for g in gating) if gating else ""
raw_label = f"{user_label}{tag_symbols}{gating_str}"
# Substitute dynamic variables
hyperparams = edge_data.get("hyperparameters", [0, 0])
replacements = {
"#": str(edge_data.get("slot", "")),
"$": str(hyperparams[0]),
"%": str(hyperparams[1])
}
final_label = raw_label
for old, new in replacements.items():
final_label = final_label.replace(old, new)
# Clean up default labels from UI display
display_label = final_label.replace(
"-",
"").replace(
"+",
"").replace(
"*",
"✱")
# Edge styling
edge_color = NORD_PALETTE[0]
edge_width = 1.0
if "-" in tag_symbols:
edge_color = NORD_PALETTE[11] # Thick red for inhibition
edge_width = 3.5
elif "+" in tag_symbols:
edge_color = NORD_PALETTE[8] # Thick blue for excitation
edge_width = 3.5
G.add_edge(
u, v,
label=display_label,
color=edge_color,
width=edge_width
)
# Render graph
fig, ax = plt.subplots(figsize=(12, 8))
# Create a clean graph for layout calculation
G_layout = nx.MultiDiGraph()
G_layout.add_nodes_from(G.nodes())
G_layout.add_edges_from(G.edges(keys=True))
# Assign attributes (respected by AGraph, often dropped by PyDot)
G_layout.graph['rankdir'] = 'LR'
G_layout.graph['ranksep'] = '0.85'
G_layout.graph['nodesep'] = '0.85'
# Left-to-right hierarchical layout using Graphviz (dot)
try:
from networkx.drawing.nx_agraph import graphviz_layout
pos = graphviz_layout(G_layout, prog='dot', args='-Grankdir=LR')
except ImportError:
try:
from networkx.drawing.nx_pydot import pydot_layout
# PyDot wrapper drops the rankdir graph attribute.
# Calculate standard Top-to-Bottom and rotate mathematically to
# enforce Left-to-Right.
pos_tb = pydot_layout(G_layout, prog='dot')
pos = {n: (-y, x) for n, (x, y) in pos_tb.items()}
except ImportError:
print(
"Warning: Install 'pygraphviz' or 'pydot' (and OS-level Graphviz) for left-to-right layout.")
pos = nx.spring_layout(G_layout, seed=42)
# Render graph on a slightly larger canvas
fig, ax = plt.subplots(figsize=(11, 7))
# Draw edges individually to calculate curve routing for bidirectional
# and parallel overlaps
edges = G.edges(data=True, keys=True)
for u, v, key, data in edges:
rad = 0.0
if G.has_edge(v, u) or G.number_of_edges(u, v) > 1:
# Alternate the curvature radius for parallel edges: 0.2, -0.2,
# 0.4, -0.4...
magnitude = 0.2 + 0.2 * (key // 2)
direction = 1 if key % 2 == 0 else -1
rad = magnitude * direction
nx.draw_networkx_edges(
G, pos,
edgelist=[(u, v)],
edge_color=data['color'],
width=data['width'],
arrows=True,
arrowstyle='-|>',
arrowsize=18,
node_size=G.nodes[v]['size'],
# Instructs Matplotlib to calculate intersection for this
# specific shape
node_shape=G.nodes[v]['shape'],
connectionstyle=f'arc3,rad={rad}',
ax=ax
)
# Aggregate labels for parallel edges into multiline strings centered
# between the curves
edge_labels = {}
for u, v, key, data in edges:
lbl = data.get('label', '')
if lbl:
current = edge_labels.get((u, v), "")
# Prevent duplicating identical labels on perfectly mirrored
# parallel edges
if lbl not in current:
edge_labels[(u,
v)] = f"{current}\n{lbl}" if current else lbl
nx.draw_networkx_edge_labels(
G, pos, edge_labels=edge_labels,
font_family="Inter Variable",
font_color=NORD_PALETTE[0],
bbox=dict(boxstyle="round,pad=0.3", fc="white", ec="none"),
label_pos=0.5,
ax=ax
)
# Draw nodes
for node, data in G.nodes(data=True):
nx.draw_networkx_nodes(
G, pos, nodelist=[node],
node_color=data['fill_color'],
node_shape=data['shape'],
node_size=data['size'],
edgecolors=NORD_PALETTE[1],
linewidths=1.5,
ax=ax
)
# Draw node labels
node_labels = {node: data['label']
for node, data in G.nodes(data=True)}
nx.draw_networkx_labels(
G, pos, labels=node_labels,
font_family="Inter Variable",
font_color=NORD_PALETTE[0],
font_weight="bold",
ax=ax
)
plt.axis('off')
plt.tight_layout()
plt.savefig(filename, dpi=300, bbox_inches='tight')
plt.close(fig)
def clear():
for node in nodes.values():
c = node.get("clear")
if callable(c):
c()
dispatch = {
"function": f,
"schematics": schematics,
"clear": clear,
"receive_blocks": receiveparams,
"send_blocks": sendparams,
"nodes": lambda: nodes
}
name = expr.get("options", {}).get("name")
if name is not None:
dispatch["name"] = name
return dispatch
```
# Source: memory_source.md
SOURCE CODE
# Memory
The following is a reference implementation of the Topological Associative Memory algorithm. This native Python version illustrates the mechanics of the memory core and its programming interface, serving as a baseline for experimental and derived algorithms.
By default, the package uses a C extension rather than this native code, delivering a 10x performance improvement.
See also: https://creatingintelligence.org/#the-core-algorithm
## Python source code
```python
"""
Copyright (c) 2026 Peter Overmann
SPDX-License-Identifier: MIT
This file is part of the "Creating Intelligence" project. It is licensed
under the MIT License. You may obtain a copy of the License in the LICENSE
file in the root directory of this repository.
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.
"""
import math
import numpy as np
from itertools import combinations
def Memory(config: dict) -> dict:
# Extract hyperparameters
NA, PA = config["A_parameters"]
NB, PB = config["B_parameters"]
# Memory capacity, needed for default threshold calculation
if NA == NB and PA == PB:
capacity = round((math.log(2.0) * NA * (NA - 1) * (NA - 2)) /
(PA * (PA - 1) * (PA - 2)))
else:
capacity = round((math.log(2.0) * NA * (NA - 1) * NB) /
(PA * (PA - 1) * PB))
# Default pattern matching threshold
T = 1
while (T < PA and
(capacity**2 * math.comb(PA, T) *
math.comb(NA - PA, PA - T) /
math.comb(NA, PA)) >= 1):
T += 1
# User-defined (scaled) threshold via get()
if config.get("threshold") is not None:
T = round(config["threshold"] * PA)
T = max(2, T)
# Initialize enclosed memory dictionary and zero array
mem = {}
zero = np.zeros(NB + 1, dtype=np.int8)
def store(A, B=None):
if B is None:
B = A
v = zero.copy()
v[B] = 1
for pair in combinations(A, 2):
key = tuple(sorted(pair))
if key not in mem:
mem[key] = v.copy()
else:
mem[key] = np.bitwise_or(mem[key], v)
def retrieve(A):
X = list(A)
while True:
P = len(X)
if P < T:
return []
Ri = np.zeros((P, NB + 1), dtype=np.int32)
for i, j in combinations(range(P), 2):
key = tuple(sorted([X[i], X[j]]))
v = mem.get(key, zero)
Ri[i] += v
Ri[j] += v
R = np.sum(Ri, axis=0) // 2
R_valid = R[1:]
sorted_R = np.sort(R_valid)
t_val = sorted_R[-PB] if PB <= len(sorted_R) else sorted_R[0]
t = max(1, t_val)
if t < (T * (T - 1)) / 2:
return []
Y = [i for i in range(1, NB + 1) if R[i] >= t]
w = np.sum(Ri[:, Y], axis=1)
ws = np.sort(w)
h = P
while h > 0 and ws[-h] < len(Y) * (h - 1):
h -= 1
if h < T:
return []
cutoff = ws[-h]
new_X = [X[i] for i in range(P) if w[i] >= cutoff]
if h == T and h < len(new_X):
return []
if len(new_X) == P:
return Y
X = new_X
def memorycount():
return sum(np.sum(v) for v in mem.values())
def clear():
mem.clear()
return {
"A_parameters": (NA, PA),
"B_parameters": (NB, PB),
"T": T,
"store": store,
"retrieve": retrieve,
"clear": clear,
"memorycount": memorycount,
"backend": "python"
}
```