summaryrefslogtreecommitdiff
path: root/src/mind
diff options
context:
space:
mode:
authorsanine <sanine.not@pm.me>2023-11-16 14:50:00 -0600
committersanine <sanine.not@pm.me>2023-11-16 14:50:00 -0600
commitf9fc4d26ec5fca9ee175c8a6fbcdd0fa36f10947 (patch)
treef190e6e465bb563c608a916f41fc8bf686ea2897 /src/mind
parent7825b92ce3be95a0ce1dfea9388adbaadce83b1f (diff)
clear out js files
Diffstat (limited to 'src/mind')
-rw-r--r--src/mind/README.md37
-rw-r--r--src/mind/topology.js183
-rw-r--r--src/mind/topology.test.js235
3 files changed, 0 insertions, 455 deletions
diff --git a/src/mind/README.md b/src/mind/README.md
deleted file mode 100644
index 1ece125..0000000
--- a/src/mind/README.md
+++ /dev/null
@@ -1,37 +0,0 @@
-mind
-====
-
-This module is used to create arbitrary stateful neural networks.
-
-The only export is the following function:
-
-```
-network(input_count, internal_count, output_count, weight_max = 4 : number)
-```
-
-This function returns an object that represents a neural network with `input_count` input neurons,
-`internal_count` internal (and stateful) neurons, and `output_count` output neurons. `max_weight` determines
-the maximum absolute value allowed for connection weights.
-
-A network object has two methods:
-
-```
-connect(source, sink, weight : number)
-```
-
-This method returns a new network object that is an exact copy of the original, except with a new
-connection between the neuron indexed by `source` and `sink`, with weight `weight`. Neuron indices
-are zero-indexed, and span all neurons, first the inputs, then the internal neurons, and finally the outputs.
-An error will be thrown if `source` is in the range of output neurons or if `sink` is in the range of input
-neurons.
-
-
-```
-compute(inputs, state : array[number])
-```
-
-This method returns a tuple `[output, newState]`, where `output` is an array of `output_count` values
-corresponding to the output neuron's computed values, and `newState` is the new state of the internal neurons.
-
-`input` must be an array of numbers with length equal to `input_count`, and `state` must be an array of numbers
-with length equal to `internal_count` or an error will be raised.
diff --git a/src/mind/topology.js b/src/mind/topology.js
deleted file mode 100644
index 946dd86..0000000
--- a/src/mind/topology.js
+++ /dev/null
@@ -1,183 +0,0 @@
-'use strict';
-
-import { create } from '../util.js';
-
-
-const DEFAULT_WEIGHT_MAX = 4;
-
-
-// prototype for network objects
-const network_proto = {
- connect: function(source, sink, weight) {
- return network_connect(this, source, sink, weight);
- },
- compute: function(inputs, state) {
- return network_compute(this, inputs, state);
- },
-};
-
-
-// create a new network
-export function network(input_count, internal_count, output_count, weight_max = 4) {
- const count = input_count + internal_count + output_count;
- const n = create({
- input_count,
- output_count,
- adjacency: new Array(count).fill([]),
- weight: [],
- }, network_proto);
- return n;
-}
-
-
-// check index is an input
-function is_input(n, index) {
- return index < n.input_count;
-}
-// check if index is an output
-function is_output(n, index) {
- return index >= (n.adjacency.length - n.output_count);
-}
-// check if index is a hidden neuron
-function is_hidden(n, index) {
- return (!is_input(n, index)) && (!is_output(n, index));
-}
-
-
-// returns a new network with an edge between the given nodes
-// with the given weight
-export function network_connect(n, source, sink, weight) {
- if (is_input(n, sink)) {
- // inputs cannot be sinks
- throw new Error(`attempt to use input as sink (${source} -> ${sink})`);
- }
- if (is_output(n, source)) {
- // outputs cannot be sources
- throw new Error(`attempt to use output as source (${source} -> ${sink})`);
- }
-
- return create({
- ...n,
- adjacency: n.adjacency.map((row, i) => {
- if (i === source && i === sink) {
- // self-loop
- return [...row, 2];
- } else if (i === source) {
- return [...row, 1];
- } else if (i === sink) {
- return [...row, -1];
- } else {
- return [...row, 0];
- }
- }),
- weight: [...n.weight, weight],
- }, network_proto);
-}
-
-
-// gets the indices of the edges incident on the given adjacency list
-function incident_edges(n, adj) {
- const incident = adj
- .map((edge, index) => (edge < 0) || (edge === 2) ? index : null)
- .filter(index => index !== null);
-
- return incident;
-}
-
-
-// get the indices of the ends of an edge
-// in the case of self-loops, both values are the same
-function edge_ends(n, edge) {
- const ends = n.adjacency
- .map((adj, index) => adj[edge] !== 0 ? index : null)
- .filter(index => index != null);
-
- ends.sort((a, b) => n.adjacency[a][edge] < n.adjacency[b][edge] ? -1 : 1);
-
- if (ends.length === 1) {
- return { source: ends[0], sink: ends[0] };
- } else if (ends.length === 2) {
- return { source: ends[1], sink: ends[0] };
- } else {
- throw new Error("something bad happened with the ends");
- }
-}
-
-
-// recursively get the value of a node from the input nodes,
-// optionally caching the computed values
-function get_value(n, index, input, prev, cache) {
- // check if value is cached
- if (cache !== undefined && cache[index]) {
- return cache[index];
- }
- // check if value is input
- if (is_input(n, index)) {
- return input[index];
- }
-
- const adj = n.adjacency[index]; // get adjacency list
- const incident = incident_edges(n, adj); // get incident edges
- const weight = incident.map(x => n.weight[x]); // edge weights
- const sources = incident // get ancestor nodes
- .map(x => edge_ends(n, x).source);
-
- // get the value of each ancestor
- const values = sources
- .map(x => x === index // if the ancestor is this node
- ? prev[x - n.input_count] // then the value is the previous value
- : get_value(n, x, input, prev, cache)); // else recurse
-
- const sum = values // compute the weighted sum of the values
- .reduce((acc, x, i) => acc + (weight[i] * x), 0);
-
- if (sum !== sum) { // NaN test
- console.log(n);
- console.log(sources);
- console.log(input);
- throw new Error(`failed to get output for index ${index}`);
- }
-
- // compute result
- const value = Math.tanh(sum);
-
- // !!! impure caching !!!
- // cache result
- if (cache !== undefined) {
- cache[index] = value;
- }
-
- return value;
-}
-
-
-// compute a network's output and new hidden state
-// given the input and previous hidden state
-export function network_compute(n, input, state) {
- // validate input
- if (input.length !== n.input_count) {
- throw new Error("incorrect number of input elements");
- }
- // validate state
- const hidden_count = n.adjacency.length - n.input_count - n.output_count;
- if (state.length !== hidden_count) {
- throw new Error("incorrect number of state elements");
- }
-
- // !!! impure caching !!!
- const value_cache = {};
-
- const result = Object.freeze(n.adjacency
- .map((x, i) => is_output(n, i) ? i : null) // output index or null
- .filter(i => i !== null) // remove nulls
- .map(x => get_value(n, x, input, state, value_cache)) // map to computed value
- );
-
- const newstate = Object.freeze(n.adjacency
- .map((x, i) => is_hidden(n, i) ? i : null) // hidden index or null
- .filter(i => i !== null) // remove nulls
- .map(x => get_value(n, x, input, state, value_cache)) // map to computed value (using cache)
- );
-
- return Object.freeze([result, newstate]);
-}
diff --git a/src/mind/topology.test.js b/src/mind/topology.test.js
deleted file mode 100644
index 52c196f..0000000
--- a/src/mind/topology.test.js
+++ /dev/null
@@ -1,235 +0,0 @@
-'use strict';
-
-import { network } from './topology';
-
-
-test('basic network functionality', () => {
- const n = network(0, 5, 0);
- expect(n).toEqual({
- input_count: 0,
- output_count: 0,
- adjacency: [ [], [], [], [], [] ],
- weight: [],
- });
-
- expect(() => n.adjacency = []).toThrow();
- expect(() => n.weight = []).toThrow();
-
- const nn = n.connect(0, 1, -2);
- expect(nn).toEqual({
- input_count: 0,
- output_count: 0,
- adjacency: [
- [ 1 ],
- [ -1 ],
- [ 0 ],
- [ 0 ],
- [ 0 ]
- ],
- weight: [ -2 ],
- });
-
- expect(() => nn.adjacency = []).toThrow();
- expect(() => nn.weight = []).toThrow();
-
- const nnn = nn.connect(2, 4, 3);
- expect(nnn).toEqual({
- input_count: 0,
- output_count: 0,
- adjacency: [
- [ 1, 0 ],
- [ -1, 0 ],
- [ 0, 1 ],
- [ 0, 0 ],
- [ 0, -1 ]
- ],
- weight: [ -2, 3 ],
- });
-
- expect(() => nnn.adjacency = []).toThrow();
- expect(() => nnn.weight = []).toThrow();
-});
-
-
-test(
-'networks are restricted from sinking to inputs or sourcing from outputs',
-() => {
- const n = network(2, 2, 2);
-
- expect(n.connect(1,2,0)).toEqual({
- input_count: 2,
- output_count: 2,
- adjacency: [
- [ 0 ],
- [ 1 ],
- [ -1 ],
- [ 0 ],
- [ 0 ],
- [ 0 ],
- ],
- weight: [ 0 ],
- });
- expect(() => n.connect(2, 1, 0)).toThrow();
-
- expect(n.connect(3, 4, 2)).toEqual({
- input_count: 2,
- output_count: 2,
- adjacency: [
- [ 0 ],
- [ 0 ],
- [ 0 ],
- [ 1 ],
- [ -1 ],
- [ 0 ],
- ],
- weight: [ 2 ],
- });
- expect(() => n.connect(4, 3, 2)).toThrow();
-});
-
-
-test('self-connections work correctly', () => {
- const n = network(0, 1, 0).connect(0, 0, 2.0);
- expect(n).toEqual({
- input_count: 0,
- output_count: 0,
- adjacency: [
- [ 2 ],
- ],
- weight: [ 2 ],
- });
-});
-
-
-test('network computations', () => {
- const n = network(1, 0, 1).connect(0, 1, 2.0);
- const input = [ -0.5 ];
- const state = [];
- const result = n.compute(input, state);
- expect(result).toEqual([
- [ Math.tanh(-0.5 * 2.0) ],
- [],
- ]);
-
- expect(input).toEqual([ -0.5 ]);
- expect(state).toEqual([]);
-
- expect(() => result[0] = 'hi').toThrow();
- expect(() => result[0].push('hi')).toThrow();
- expect(() => result[1] = 'hi').toThrow();
- expect(() => result[1].push('hi')).toThrow();
-});
-
-
-test('multiple input network', () => {
- const n = network(4, 0, 1)
- .connect(0, 4, -1.0)
- .connect(1, 4, -2.0)
- .connect(2, 4, 1.0)
- .connect(3, 4, 2.0)
-
- expect(n.compute([1, 2, 3, 5], [])).toEqual([
- [ Math.tanh(
- (-1.0 * 1) +
- (-2.0 * 2) +
- (1.0 * 3) +
- (2.0 * 5))],
- [],
- ]);
-});
-
-
-test('multiple outputs', () => {
- const n = network(4, 0, 2)
- .connect(0, 4, -1)
- .connect(1, 4, 1)
- .connect(2, 5, -1)
- .connect(3, 5, 1);
-
- expect(n.compute([1,2,3,5], [])).toEqual([
- [ Math.tanh(2-1), Math.tanh(5-3) ],
- [],
- ]);
-});
-
-
-test('hidden neurons', () => {
- const n = network(4, 2, 1)
- .connect(0, 4, -1)
- .connect(1, 4, 1)
- .connect(2, 5, -1)
- .connect(3, 5, 1)
- .connect(4, 6, -1)
- .connect(5, 6, 1);
-
- expect(n.compute([1,2,3,5], [ 0, 0 ])).toEqual([
- [ Math.tanh( Math.tanh(5-3) - Math.tanh(2-1) ) ],
- [ Math.tanh(2-1), Math.tanh(5-3) ],
- ]);
-});
-
-
-test('arbitrary hidden neurons', () => {
- const n = network(1, 2, 1)
- .connect(0, 1, 1)
- .connect(1, 2, -1)
- .connect(2, 3, 2)
-
- const [output, state] = n.compute([1], [0, 0]);
-
- expect(output).toEqual([
- Math.tanh(
- 2*Math.tanh(
- -1*Math.tanh(1)
- )
- )
- ]);
-
- expect(state).toEqual([
- Math.tanh(1),
- Math.tanh( -Math.tanh(1) ),
- ]);
-});
-
-
-test('memory', () => {
- const n = network(0, 1, 1).connect(0, 0, -0.5).connect(0, 1, 2);
-
- expect(n.compute([], [1])).toEqual([
- [ Math.tanh( 2 * Math.tanh( -0.5 * 1 ) ) ],
- [ Math.tanh( -0.5 * 1) ],
- ]);
-});
-
-
-test('memory and input', () => {
- const n = network(1, 1, 1)
- .connect(0, 1, 1)
- .connect(1, 1, 1)
- .connect(1, 2, 1);
-
- expect(n.compute([2], [-1])).toEqual([
- [ Math.tanh( Math.tanh( 2-1 ) ) ],
- [ Math.tanh( 2-1 ) ],
- ]);
-});
-
-
-test('input and state must be the correct size', () => {
- const n = network(2, 1, 1)
- .connect(0, 2, 1)
- .connect(1, 2, 1)
- .connect(2, 3, 1);
-
- // wrong input size
- expect(() => n.compute([], [4])).toThrow();
- expect(() => n.compute([1], [4])).toThrow();
- expect(() => n.compute([1, 1, 1], [4])).toThrow();
-
- // wrong state size
- expect(() => n.compute([1, 1], [])).toThrow();
- expect(() => n.compute([1, 1], [4, 4])).toThrow();
-
- // prove correct sizes work
- n.compute([1, 1], [4]);
-});