# 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: image

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: image
## 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 image
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]} ]} ``` image
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]} ]} ``` image
`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]} ]} ``` image
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"} ]} ``` image
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]} ]} ``` image
## 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]} ]} ``` image
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} ]} ``` image
# Source: pathways.md CIRCUIT CONFIGURATON # Pathways image
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]} ]} ``` image
The same network with a tagged pathway: ```json { "hyperparameters": {"default": [1000, 10]}, "dataflow": [ {"component": "input", "send": [1]}, {"component": "output", "receive": [{"slot": 1, "tags": ["permute"]}]} ]} ``` image
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"]}]]} ]} ``` image
## 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]}]} ]} ``` image
## 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"]}]} ]} ``` image
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"]}]} ]} ``` image
## 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]} ]} ``` image
## 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"]}]} ]} ``` image
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"]}]} ]} ``` image

# Source: input.md CIRCUIT COMPONENTS # input image
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]} ]} ``` image
## 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]]} ]} ``` image
## 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"]}]} ]} ``` image
## 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"]}]} ]} ``` image
## 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]} ]} ``` image
This uses the [`flyhash`](flyhash.md) encoder: ```json { "hyperparameters": {"default": [1000, 10]}, "dataflow": [ {"component": "input", "plugin": "flyhash", "send": [1]}, {"component": "output", "receive": [1]} ]} ``` image
## 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 image
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]} ]} ``` image
## 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]} ]} ``` image
## Multisets This circuit outputs a multiset: ```json { "hyperparameters": {"default": [1000, 10]}, "dataflow": [ {"component": "input", "send": [1]}, {"component": "output", "receive": [{"slot": 1, "tags": ["multiset"]}]} ]} ``` image
## 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]} ]} ``` image
## 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 image
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]} ]} ``` image
## 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]} ]} ``` image
## 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]} ]} ``` image
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]} ]} ``` image
## 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]} ]} ``` image
## 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]} ]} ``` image
## 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]} ]} ``` image
## 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 image
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]]} ]} ``` image
## 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"]}]]} ]} ``` image
## 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 image
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"]}]} ]} ``` image
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]} ]} ``` image
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]} ]} ``` image
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 image
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]} ]} ``` image
## 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]} ]} ``` image
## 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]} ]} ``` image
## 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]} ]} ``` image
## 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]} ]} ``` image
## 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]} ]} ``` image
## 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 image
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]} ]} ``` image
## 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]} ]} ``` image
## 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]} ]} ``` image
## 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]} ]} ``` image
## 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]} ]} ``` image
## 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 image
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]} ]} ``` image
## 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 image
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"} ]} ``` image
## 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 image
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]} ]} ``` image
## 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 image
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]} ]} ``` image
## 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]} ]} ``` image
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]} ]} ``` image
## Source code ```python def replacement(config): return {"updaterule": lambda y, x: y, "label": "▼", "size": 18} ``` # Source: residual.md UPDATE RULE PLUGINS # residual image
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]} ]} ``` image
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 image
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]} ]} ``` image
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]} ]} ``` image
## 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 image
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]} ]} ``` image
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]} ]} ``` image
## 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]} ]} ``` image
## 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 image
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]} ]} ``` image
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]} ]} ``` image
## 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]} ]} ``` image
## Source code ```python def augmentation(config): return {"updaterule": lambda y, x: multiset( [y, x]), "label": "∪", "size": 14} ``` # Source: coincidence.md UPDATE RULE PLUGINS # coincidence image
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]} ]} ``` image
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 image
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]} ]} ``` image
## 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]} ]} ``` image
## 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 image
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]} ]} ``` image
## 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 image
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} ]} ``` image
## 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 image
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]} ]} ``` image
## 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 image
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]} ]} ``` image
## 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 image
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]} ]} ``` image
## 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" } ```