'use strict'; const DEFAULT_WEIGHT_MAX = 4; const graph_proto = { connect: function(source, sink, weight) { return network_connect(this, source, sink, weight); }, compute: function(inputs, state) { return network_compute(this, inputs, state); }, }; export function network(input_count, internal_count, output_count, weight_max = 4) { const count = input_count + internal_count + output_count; const n = Object.create(graph_proto); n.input_count = input_count; n.output_count = output_count; n.adjacency = new Array(count).fill([]); n.weight = []; return Object.freeze(n); } function is_input(n, index) { return index < n.input_count; } function is_output(n, index) { return index >= (n.adjacency.length - n.output_count); } 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"); } if (is_output(n, source)) { // outputs cannot be sources throw new Error("attempt to use output as source"); } const nn = Object.create(graph_proto); nn.input_count = n.input_count; nn.output_count = n.output_count; nn.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]; } }); nn.weight = [...n.weight, weight]; return Object.freeze(nn); } function incident_edges(n, adj) { const incident = adj .map((edge, index) => edge < 0 ? 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"); } } function get_value(n, index, input, cache) { if (cache !== undefined && cache[index]) { return cache[index]; } if (is_input(n, index)) { return input[index]; } const adj = n.adjacency[index]; const incident = incident_edges(n, adj); const weight = incident.map(x => n.weight[x]); const sources = incident .map(x => edge_ends(n, x).source); const sum = sources .reduce((acc, x, i) => acc + (weight[i] * get_value(n, x, input, cache)), 0 ); const value = Math.tanh(sum); // !!! impure caching !!! if (cache !== undefined) { cache[index] = value; } return value; } function network_compute(n, input, state) { // !!! impure caching !!! const value_cache = {}; const hidden = n.adjacency .map((x, i) => ( (!(is_input(n, i))) && (!is_output(n, i))) ? i : null) .filter(i => i !== null); const outputs = n.adjacency .map((x, i) => is_output(n, i) ? i : null) .filter(i => i !== null); const output = Object.freeze( outputs.map(x => get_value(n, x, input, value_cache)) ); const newstate = Object.freeze( hidden.map(x => get_value(n, x, input, value_cache)) ); return Object.freeze([output, newstate]); }