UNPKG

nrn-ml

Version:

Core ML library for the NRN ecosystem

1,064 lines (1,054 loc) 37 kB
// lib/math/policy-mapping.ts var probabilisticSampling = (modelOutput) => { const selectedActionsArray = modelOutput.map((currentActionProbabilities) => { const randomNumber = Math.random(); let selectedAction = 0; let counter = 0; while (selectedAction < currentActionProbabilities.length - 1) { counter += currentActionProbabilities[selectedAction]; if (randomNumber <= counter) { break; } else { selectedAction++; } } return selectedAction; }); return selectedActionsArray; }; var argmax = (array) => { return array.map((x, i) => [x, i]).reduce((r, a) => a[0] > r[0] ? a : r)[1]; }; var argmaxPolicy = (modelOutput) => { const selectedActionsArray = modelOutput.map((currentModelOutputs) => { return argmax(currentModelOutputs); }); return selectedActionsArray; }; var epsilonGreedy = (modelOutput) => { const epsilon = 0.1; const selectedActionsArray = modelOutput.map((currentModelOutputs) => { if (Math.random() < epsilon) { return Math.min( Math.floor(Math.random() * currentModelOutputs.length), currentModelOutputs.length - 1 ); } else { return argmax(currentModelOutputs); } }); return selectedActionsArray; }; var policyMapping = { probabilisticSampling, epsilonGreedy, argmaxPolicy }; // lib/inference-models/model-core.ts var ModelCore = class { outputGroups; getActionHeadOutput(actionOutput, actionMetadata, outputName, row = 0) { let action; const actionType = actionMetadata[outputName].actionType; const policyMethod = actionMetadata[outputName].policyMapping; if (actionType === "discrete") { action = policyMapping[policyMethod](actionOutput); } else { action = actionOutput; } const actionObj = {}; actionMetadata[outputName].order.forEach((actionName, idx) => { actionObj[actionName] = actionType === "continuous" ? action[row][idx] : action[row] === idx; }); return actionObj; } }; var model_core_default = ModelCore; // lib/utils/model-validation.ts var isArray1D = (x) => { const isArray = Array.isArray(x); const is1D = isArray && !Array.isArray(x[0]); return isArray && is1D; }; var isArray2D = (x) => { const isArray = Array.isArray(x); const is2D = isArray && Array.isArray(x[0]) && !Array.isArray(x[0][0]); return isArray && is2D; }; var checkSingleHeadActionParam = (param, key) => { if (param !== void 0 && typeof param !== "string" && (!isArray1D(param) || !(isArray1D(param) && param.length === 1 && typeof param[0] === "string"))) { return `'${key}' is improperly defined`; } return ""; }; var allowedActionParams = { actionTypes: ["discrete", "continuous"], actionPolicies: ["argmaxPolicy", "probabilisticSampling"], actionActivations: ["softmax", "linear", "tanh", "sigmoid"] }; var validateActionNames = (modelData) => { const config = modelData.config; const { actionNames } = config; if (config.multiheadBool) { if (!isArray1D(actionNames)) { return "'actionNames' for multi-head models must be a 1D array"; } else if (actionNames?.length < 2) { return "'multiheadBool' is true, but multiple action head names were not detected"; } } else { const msg = checkSingleHeadActionParam(actionNames, "actionNames"); if (msg !== "") return msg; if (actionNames === void 0) { config.actionNames = ["Actions"]; } else if (typeof actionNames === "string") { config.actionNames = [actionNames]; } } return ""; }; var validateActionOrder = (modelData) => { const config = modelData.config; const { actionOrder } = config; if (config.multiheadBool) { const numActionNames = config.actionNames?.length; if (!isArray2D(actionOrder)) { return "'actionOrder' for multi-head models must be a 2D array"; } else if (actionOrder.length !== numActionNames) { return `Mistmatch between 'actionOrder' length and 'actionNames': ${actionOrder.length} vs ${numActionNames}`; } } else { if (!isArray1D(actionOrder) && !isArray2D(actionOrder)) { return "'actionOrder' for single-head models should be an array of actions"; } else if (isArray2D(actionOrder) && actionOrder.length > 1) { return "'actionOrder' for single-head is a 2D array with too many rows"; } } return ""; }; var validateSupplementaryActionData = (modelConfig, key) => { const actionData = modelConfig[key]; if (modelConfig.multiheadBool) { if (actionData !== void 0) { if (!isArray1D(actionData)) { return `'${key}' for multi-head models must be a 1D array`; } else if (actionData.length !== modelConfig.actionNames.length) { return `Mistmatch between ${key} length and 'actionNames': ${actionData.length} vs ${modelConfig.actionNames.length}`; } else { const illegalParams = actionData.filter((x) => !allowedActionParams[key].includes(x)); if (illegalParams.length > 0) { return `'${illegalParams[0]}' is not an allowed value for ${key}`; } } } } else { const msg = checkSingleHeadActionParam(actionData, key); if (msg) return msg; if (typeof actionData === "string") { if (!allowedActionParams[key].includes(actionData)) { return `'${actionData}' is not an allowed value for ${key}`; } else { modelConfig[key] = [modelConfig[key]]; } } else if (isArray1D(actionData) && !allowedActionParams[key].includes(actionData[0])) { return `'${actionData[0]}' is not an allowed value for ${key}`; } } return ""; }; var createActionMetadata = (referenceConfig, includeActivation = false) => { const actionHeads = []; const actionMetadata = {}; for (let i = 0; i < referenceConfig.actionNames.length; i++) { const name = referenceConfig.actionNames[i]; actionHeads.push(name); actionMetadata[name] = { actionType: referenceConfig.actionTypes?.[i] ?? "discrete", policyMapping: referenceConfig.actionPolicies?.[i] ?? "argmaxPolicy", order: referenceConfig.actionOrder[i] }; if (includeActivation) { actionMetadata[name].activationName = referenceConfig.actionActivations?.[i] ?? "softmax"; } } return [actionHeads, actionMetadata]; }; var validateFormattedActionHeads = (modelConfig) => { let msg = ""; if (!isArray1D(modelConfig.actionHeads)) { msg = "'actionHeads' must be a 1D array"; } else if (modelConfig.actionHeads.length !== Object.keys(modelConfig.actionMetadata).length) { msg = `Mistmatch between 'actionHeads' length and 'actionMetadata': ${modelConfig.actionHeads.length} vs ${Object.keys(modelConfig.actionMetadata).length}`; } else { modelConfig.actionHeads.forEach((actionGroup) => { if (modelConfig.actionMetadata[actionGroup] === void 0) { msg = `'${actionGroup}' is not found in 'actionMetadata'`; } else if (!isArray1D(modelConfig.actionMetadata[actionGroup].order)) { msg = `'order' must be a 1D array in 'actionMetadata' for '${actionGroup}'`; } }); } return msg; }; var checkValidActionHeads = (modelData) => { let msg = ""; const formattedConfig = modelData.config; const rawConfig = modelData.config; if (formattedConfig.actionMetadata !== void 0 && formattedConfig.actionHeads !== void 0) { msg = validateFormattedActionHeads(formattedConfig); if (msg !== "") return { success: false, msg }; } else { if (rawConfig.actionOrder === void 0) { return { success: false, msg: "Model data config must contain 'actionOrder'" }; } msg = validateActionNames(modelData); if (msg !== "") return { success: false, msg }; msg = validateActionOrder(modelData); if (msg !== "") return { success: false, msg }; msg = validateSupplementaryActionData(rawConfig, "actionTypes"); if (msg !== "") return { success: false, msg }; msg = validateSupplementaryActionData(rawConfig, "actionActivations"); if (msg !== "") return { success: false, msg }; msg = validateSupplementaryActionData(rawConfig, "actionPolicies"); if (msg !== "") return { success: false, msg }; if (!rawConfig.multiheadBool) { if (isArray1D(rawConfig.actionOrder)) { rawConfig.actionOrder = [rawConfig.actionOrder]; } } } return { success: true, msg }; }; var isSimpleModelValid = (modelData) => { if (modelData.config === void 0) { return { success: false, msg: "Model data must contain 'config'" }; } let msg = ""; let success = false; const config = modelData.config; const actionHeadRes = checkValidActionHeads(modelData); const newModel = modelData.frequencies === void 0; if (actionHeadRes.msg === "") { if (![void 0, "empty", "random"].includes(config.initializationMethod)) { msg = "initializationMethod should be either: 'empty' or 'random'"; } else if (newModel && config.inputDim === void 0 || !newModel && config.numDiscreteStates === void 0) { msg = "Number of discrete states not provided"; } else { success = true; } return { success, msg }; } else { return actionHeadRes; } }; var getExpectedNeuralNetParams = (modelConfig) => { var inDim; var outDim = modelConfig.nFeatures; const nLayers = modelConfig.neurons.length; const expectedParams = {}; for (var l = 0; l < nLayers; l++) { if (l === 0) inDim = modelConfig.nFeatures ?? modelConfig.inputDim; else inDim = modelConfig.neurons[l - 1]; outDim = modelConfig.neurons[l]; expectedParams[`biases-${l}-${l + 1}`] = { inDim: 1, outDim }; expectedParams[`weights-${l}-${l + 1}`] = { inDim, outDim }; } Object.keys(modelConfig.actionMetadata).forEach((actionGroup) => { const nActions = modelConfig.actionMetadata[actionGroup].order.length; const keyAppend = `${nLayers}-${actionGroup}`; expectedParams[`biases-${keyAppend}`] = { inDim: 1, outDim: nActions }; expectedParams[`weights-${keyAppend}`] = { inDim: outDim, outDim: nActions }; }); return expectedParams; }; var validateNeuralNetParameters = (modelConfig, parameters) => { let msg = ""; const expectedParams = getExpectedNeuralNetParams(modelConfig); const paramKeys = Object.keys(parameters); const expectedKeys = Object.keys(expectedParams); if (expectedKeys.length !== paramKeys.length) { msg = `Parameter keys mismatch in 'parameters' vs expectation: ${paramKeys.length} vs ${expectedKeys.length}`; } else { for (let i = 0; i < paramKeys.length; i++) { if (expectedParams[paramKeys[i]] === void 0) { msg = `'${paramKeys[i]}' is an invalid parameter key`; break; } else if (!isArray2D(parameters[paramKeys[i]])) { msg = `'${paramKeys[i]}' is not structured as a matrix`; } else { const paramShape = [parameters[paramKeys[i]].length, parameters[paramKeys[i]][0].length]; const expectedShape = [ expectedParams[paramKeys[i]].inDim, expectedParams[paramKeys[i]].outDim ]; if (paramShape[0] !== expectedShape[0] || paramShape[1] !== expectedShape[1]) { msg = `'${paramKeys[i]}' shape does not match expectation - ${paramShape} vs ${expectedShape}`; } } } } return { success: msg === "", msg }; }; var isNeuralNetModelValid = (modelData) => { if (modelData.config === void 0) { return { success: false, msg: "Model data must contain 'config'" }; } const actionHeadRes = checkValidActionHeads(modelData); return actionHeadRes; }; // lib/inference-models/tabular-model.ts var TabularModel = class _TabularModel extends model_core_default { config; frequencies; constructor(modelData) { super(); const modelValidity = isSimpleModelValid(modelData); if (!modelValidity.success) { throw Error(modelValidity.msg); } const createNewModel = modelData.frequencies === void 0; if (createNewModel) { let actionHeads; let actionMetadata; if (modelData.config.actionHeads === void 0 || modelData.config.actionMetadata === void 0) { const actionMetadataResult = createActionMetadata(modelData.config); actionHeads = actionMetadataResult[0]; actionMetadata = actionMetadataResult[1]; } else { actionHeads = modelData.config.actionHeads; actionMetadata = modelData.config.actionMetadata; } this.config = { initializationMethod: "empty", numDiscreteStates: modelData.config.inputDim, actionHeads, actionMetadata }; this.frequencies = this.initializeFrequencies(this.config.initializationMethod); } else { this.config = { ...modelData.config }; this.frequencies = { ...modelData.frequencies }; } this.outputGroups = Object.keys(this.config.actionMetadata); if (this.config.numDiscreteStates !== this.frequencies.length) { throw Error( `Number of discrete states (${this.config.numDiscreteStates}) does not match length of frequency array (${this.frequencies.length})` ); } } createEmptyCell() { const emptyCell = {}; Object.keys(this.config.actionMetadata).forEach((actionGroup) => { const size = this.config.actionMetadata[actionGroup].order.length; emptyCell[actionGroup] = new Array(size).fill(0); }); return emptyCell; } createRandomProbability(size) { return _TabularModel.convertFrequencyToProbability( [...Array(size).keys()].map((_) => Math.random()) ); } createRandomCell() { const randomCell = {}; Object.keys(this.config.actionMetadata).forEach((actionGroup) => { const size = this.config.actionMetadata[actionGroup].order.length; randomCell[actionGroup] = this.createRandomProbability(size); }); return randomCell; } initializeFrequencies(initializationMethod = "empty") { return [...Array(this.config.numDiscreteStates).keys()].map((_) => { if (initializationMethod === "empty") { return this.createEmptyCell(); } else if (initializationMethod === "random") { return this.createRandomCell(); } }); } static convertFrequencyToProbability(output) { const denom = output.reduce((total, current) => total + current, 0); if (denom !== 0) { return output.map((x) => x / denom); } return new Array(output.length).fill(1 / output.length); } getProbabilities(cell) { const cellFrequencies = this.frequencies[cell]; if (cellFrequencies === void 0) { throw Error("Cell out of range"); } const rawProbabilities = {}; this.outputGroups.forEach((actionGroup) => { const prob = _TabularModel.convertFrequencyToProbability(cellFrequencies[actionGroup]); rawProbabilities[actionGroup] = [prob]; }); return rawProbabilities; } selectActionOneHead(probabilities, outputName, row = 0) { return this.getActionHeadOutput( probabilities[outputName], this.config.actionMetadata, outputName, row ); } selectAction(cell, row = 0) { var actions = {}; const probabilities = this.getProbabilities(cell); this.outputGroups.forEach((actionGroup) => { actions = { ...actions, ...this.selectActionOneHead(probabilities, actionGroup, row) }; }); return actions; } }; var tabular_model_default = TabularModel; // lib/math/rand.ts function box_muller(mu, sigma) { var x, y, r, v = 0; do { x = 2 * Math.random() - 1; y = 2 * Math.random() - 1; r = x * x + y * y; } while (r >= 1); var co = Math.sqrt(-2 * Math.log(r) / r); v = x * co; return v * sigma + mu; } // lib/math/matrix-math.ts var createMatrix = (rows, cols, fill = 0) => { return new Array(rows).fill(fill).map(() => { return new Array(cols).fill(fill); }); }; var createMatrixFromRef = (referenceMatrix, fill = 0) => { return createMatrix(referenceMatrix.length, referenceMatrix[0].length, fill); }; var matrixMultiplication = (A, B) => { if (A[0].length !== B.length) { throw new Error( `Matrix shapes of (${A.length},${A[0].length}) and (${B.length},${B[0].length}) are incompatible` ); } var result = createMatrix(A.length, B[0].length); return result.map((row, i) => { return row.map((val, j) => { return A[i].reduce((sum, elm, k) => sum + elm * B[k][j], 0); }); }); }; var matrixElementwiseOperation = (A, B, operation) => { if (typeof A === "number" && isArray2D(B)) { A = createMatrix(B.length, B[0].length, A); } else if (typeof B === "number" && isArray2D(A)) { B = createMatrix(A.length, A[0].length, B); } if (!isArray2D(A) || !isArray2D(B)) { throw Error("Failed to broadcast A or B to a matrix"); } const matrixA = A; const matrixB = B; if (matrixA.length !== matrixB.length || matrixA[0].length !== matrixB[0].length) { const shapes = { "A": `(${matrixA.length},${matrixA[0].length})`, "B": `(${matrixB.length},${matrixB[0].length})` }; throw new Error( `Matrix shapes of ${shapes["A"]} and ${shapes["B"]} are incompatible` ); } var result = createMatrixFromRef(matrixA); for (var i = 0; i < matrixA.length; i++) { for (var j = 0; j < matrixA[0].length; j++) { if (operation === "Addition") { result[i][j] = matrixA[i][j] + matrixB[i][j]; } else if (operation === "Subtraction") { result[i][j] = matrixA[i][j] - matrixB[i][j]; } else if (operation === "Multiplication") { result[i][j] = matrixA[i][j] * matrixB[i][j]; } else if (operation === "Division") { result[i][j] = matrixA[i][j] / matrixB[i][j]; } } } return result; }; var linear = (x, derivativeBool) => { if (derivativeBool) { const linearMatrix = []; x.forEach((currentInstance) => { linearMatrix.push( currentInstance.map((x_i) => { return x_i; }) ); }); return linearMatrix; } return x; }; var sigmoidScalar = (x, derivativeBool) => { if (derivativeBool) { const sig = sigmoidScalar(x, false); return sig * (1 - sig); } return 1 / (1 + Math.exp(-x)); }; var sigmoid = (x, derivativeBool) => { var sigmoidMatrix = []; x.forEach((currentInstance) => { sigmoidMatrix.push( currentInstance.map((x_i) => { return sigmoidScalar(x_i, derivativeBool); }) ); }); return sigmoidMatrix; }; var tanhScalar = (x, derivativeBool) => { if (derivativeBool) { return 1 - tanhScalar(x, false) ** 2; } return (Math.exp(x) - Math.exp(-x)) / (Math.exp(x) + Math.exp(-x)); }; var tanh = (x, derivativeBool) => { var tanhMatrix = []; x.forEach((currentInstance) => { tanhMatrix.push( currentInstance.map((x_i) => { return tanhScalar(x_i, derivativeBool); }) ); }); return tanhMatrix; }; var relu = (x, derivativeBool) => { var reluMatrix = []; x.forEach((currentInstance) => { if (derivativeBool) { reluMatrix.push( currentInstance.map((x_i) => { return x_i > 0 ? 1 : 0; }) ); } else { reluMatrix.push( currentInstance.map((x_i) => { return x_i > 0 ? x_i : 0; }) ); } }); return reluMatrix; }; var elu = (x, derivativeBool) => { var eluMatrix = []; const alpha = 1; x.forEach((currentInstance) => { if (derivativeBool) { eluMatrix.push( currentInstance.map((x_i) => { return x_i > 0 ? 1 : alpha * (Math.exp(x_i) - 1) + alpha; }) ); } else { eluMatrix.push( currentInstance.map((x_i) => { return x_i > 0 ? x_i : alpha * (Math.exp(x_i) - 1); }) ); } }); return eluMatrix; }; var softmax = (x, derivativeBool) => { var xMax; var denom; var stabalizedCurrentInstance; var softmaxMatrix = []; x.forEach((currentInstance) => { if (derivativeBool) { } else { xMax = currentInstance.reduce((acc, val) => Math.max(acc, val), currentInstance[0]); stabalizedCurrentInstance = currentInstance.map((x_i) => { return x_i - xMax; }); denom = stabalizedCurrentInstance.reduce((total, current) => total + Math.exp(current), 0); softmaxMatrix.push( stabalizedCurrentInstance.map((x_i) => { return Math.exp(x_i) / denom; }) ); } }); return softmaxMatrix; }; // lib/utils/create-model-multihead.ts var defaultDecimalPrecision = 6; var roundNumber = (x, decimalPrecision) => { const factor = 10 ** decimalPrecision; return Math.round(x * factor) / factor; }; var generateRandomMatrix = (inDim, outDim, precision) => { const layerStd = Math.sqrt(2 / (inDim + outDim)); var rawRandomNumber; const randomMatrix = []; precision = precision !== void 0 ? precision : defaultDecimalPrecision; for (var i = 0; i < inDim; i++) { randomMatrix.push([]); for (var j = 0; j < outDim; j++) { rawRandomNumber = box_muller(0, layerStd); randomMatrix[i].push(roundNumber(rawRandomNumber, precision)); } } return randomMatrix; }; var createModelParameters = (modelConfig) => { try { const parameters = {}; var inDim; var outDim = modelConfig.nFeatures; const nLayers = modelConfig.neurons.length; const precision = modelConfig.decimalPrecision; for (var l = 0; l < nLayers; l++) { if (l === 0) { inDim = modelConfig.nFeatures; } else { inDim = modelConfig.neurons[l - 1]; } outDim = modelConfig.neurons[l]; parameters[`biases-${l}-${l + 1}`] = generateRandomMatrix(1, outDim, precision); parameters[`weights-${l}-${l + 1}`] = generateRandomMatrix(inDim, outDim, precision); } Object.keys(modelConfig.actionMetadata).forEach((actionGroup) => { const nActions = modelConfig.actionMetadata[actionGroup].order.length; const keyAppend = `${nLayers}-${actionGroup}`; parameters[`biases-${keyAppend}`] = generateRandomMatrix(1, nActions, precision); parameters[`weights-${keyAppend}`] = generateRandomMatrix(outDim, nActions, precision); }); return parameters; } catch { throw Error("Model was not configured correctly"); } }; // lib/inference-models/multihead-neural-net.ts var activationMapping = { relu, elu, sigmoid, tanh, softmax, linear }; var defaultActivations = { hidden: relu, output: softmax }; var NeuralNetworkMultihead = class extends model_core_default { config; parameters; outputActivations; activationFunction; constructor(modelData) { super(); const modelValidity = isNeuralNetModelValid(modelData); if (!modelValidity.success) { throw Error(modelValidity.msg); } if (modelData.parameters === void 0) { let actionHeads; let actionMetadata; if (modelData.config.actionHeads === void 0 || modelData.config.actionMetadata === void 0) { const actionMetadataResult = createActionMetadata(modelData.config, true); actionHeads = actionMetadataResult[0]; actionMetadata = actionMetadataResult[1]; } else { actionHeads = modelData.config.actionHeads; actionMetadata = modelData.config.actionMetadata; } this.config = { actionHeads, actionMetadata, neurons: modelData.config.neurons ?? [8, 6], movingAverageType: "Simple", activationFunctionName: modelData.config.activationFunctionName ?? "elu", nFeatures: modelData.config.inputDim, decimalPrecision: 6 }; this.parameters = createModelParameters(this.config); } else { this.config = { ...modelData.config }; if (this.config.actionMetadata === void 0) { const actionMetadataResult = createActionMetadata(modelData.config, true); this.config.actionHeads = actionMetadataResult[0]; this.config.actionMetadata = actionMetadataResult[1]; } const paramtersValid = validateNeuralNetParameters(this.config, modelData.parameters); if (!paramtersValid.success) { throw Error(paramtersValid.msg); } this.parameters = { ...modelData.parameters }; } this.activationFunction = activationMapping[this.config.activationFunctionName]; if (this.activationFunction === void 0) { this.activationFunction = defaultActivations["hidden"]; } this.outputGroups = Object.keys(this.config.actionMetadata); this.outputActivations = {}; this.outputGroups.forEach((actionGroup) => { const currentActionData = this.config.actionMetadata[actionGroup]; this.outputActivations[actionGroup] = activationMapping[currentActionData.activationName]; if (this.outputActivations[actionGroup] === void 0) { this.outputActivations[actionGroup] = defaultActivations["output"]; } }); } sliceInputs(inputs) { if (inputs.length > 0 && inputs[0].length > this.config.nFeatures) { return inputs.map((x) => x.slice(0, this.config.nFeatures)); } return inputs; } nextLayer(currentLayer, keyAppend) { const biasesKey = `biases-${keyAppend}`; const weightsKey = `weights-${keyAppend}`; return matrixElementwiseOperation( matrixMultiplication(currentLayer, this.parameters[weightsKey]), Array(currentLayer.length).fill(this.parameters[biasesKey][0]), "Addition" ); } forwardProp(inputs) { var cache = {}; var currentLayer = this.sliceInputs(inputs); for (var layer = 0; layer < this.config.neurons.length; layer++) { currentLayer = this.nextLayer(currentLayer, `${layer}-${layer + 1}`); cache["z" + layer] = currentLayer; currentLayer = this.activationFunction(currentLayer, false); cache["a" + layer] = currentLayer; } Object.keys(this.outputActivations).forEach((actionGroup) => { const outputRaw = this.nextLayer(currentLayer, `${this.config.neurons.length}-${actionGroup}`); cache["z" + actionGroup] = outputRaw; const output = this.outputActivations[actionGroup](outputRaw); cache[actionGroup] = output; }); return cache; } getProbabilities(inputs) { const cache = this.forwardProp(inputs); const probabilities = {}; this.outputGroups.forEach((actionGroup) => { probabilities[actionGroup] = cache[actionGroup]; }); return probabilities; } selectActionOneHead(cache, outputName, row = 0) { return this.getActionHeadOutput(cache[outputName], this.config.actionMetadata, outputName, row); } selectAction(inputs, row = 0) { var actions = {}; const cache = this.forwardProp(inputs); Object.keys(this.outputActivations).forEach((actionGroup) => { actions = { ...actions, ...this.selectActionOneHead(cache, actionGroup, row) }; }); return actions; } selectMultipleActions(inputs) { if (inputs.length <= 1) { throw Error("State matrix must have at least 2 rows to select multiple actions"); } const actions = []; const cache = this.forwardProp(inputs); const actionGroups = Object.keys(this.outputActivations); for (let row = 0; row < inputs.length; row++) { var currentActions = {}; actionGroups.forEach((actionGroup) => { currentActions = { ...currentActions, ...this.selectActionOneHead(cache, actionGroup, row) }; }); actions.push(currentActions); } return actions; } }; var multihead_neural_net_default = NeuralNetworkMultihead; // lib/utils/data-validation.ts var DataValidation = class { validationParams; constructor() { this.validationParams = {}; } addValidationParams(modelConfig, modelType) { if (modelType === "simple" || modelType === "neural-network") { this.validationParams = { modelType, modelConfig }; } else { throw Error("Invalid model type - must be 'simple' or 'neural-network'"); } } checkArraySizeMatch(array, targetSize, mismatchPrepend) { if (array.length !== targetSize) { throw Error( `${mismatchPrepend} mismatch compared to model config: ${array.length} vs ${targetSize}` ); } } checkFeatureSize(state1D, nFeatures) { this.checkArraySizeMatch(state1D, nFeatures, "State size"); } checkActionSize(action1D, nActions) { this.checkArraySizeMatch(action1D, nActions, "Action array length"); } checkValidNumber(action) { let numberCount = 0; action.forEach((a) => { if (typeof a === "number") { numberCount += 1; } }); if (numberCount !== action.length) { throw Error("All continuous action values must be numbers"); } } checkOneHot(action) { const counts = { zero: 0, one: 0 }; action.forEach((a) => { if (a === 0) counts.zero += 1; else if (a === 1) counts.one += 1; }); const validOneHot = counts.zero === action.length - 1 && counts.one === 1; if (!validOneHot) { throw Error("Action is not structured as a one-hot encoded vector"); } } validateState(state) { if (this.validationParams.modelType === "simple") { if (!Number.isInteger(state)) { throw Error("State for 'simple' model must be an integer"); } } else if (this.validationParams.modelType === "neural-network") { const modelConfig = this.validationParams.modelConfig; if (isArray1D(state)) { this.checkFeatureSize(state, modelConfig.nFeatures); return [state]; } else if (isArray2D(state)) { this.checkFeatureSize(state[0], modelConfig.nFeatures); } else { throw Error("State for 'neural-network' must be 1D or 2D array"); } } return state; } validateFrameInterval(interval) { if (typeof interval !== "number" || !Number.isInteger(interval)) { throw Error("Frame interval must be an integer"); } else if (interval <= 0) { throw Error("Frame interval must be greater than 0"); } } validateCustomActionIntervals(customIntervals) { const modelConfig = this.validationParams.modelConfig; if (!modelConfig) { throw Error("Validation parameters must be set up to use custom action intervals"); } const formattedCustomIntervals = {}; Object.keys(customIntervals).forEach((intervalAction) => { let validAction = false; modelConfig.actionHeads.forEach((actionGroup) => { const { order } = modelConfig.actionMetadata[actionGroup]; if (order.includes(intervalAction)) { validAction = true; if (formattedCustomIntervals[actionGroup] === void 0) { formattedCustomIntervals[actionGroup] = {}; } formattedCustomIntervals[actionGroup][order.indexOf(intervalAction)] = customIntervals[intervalAction]; } }); if (!validAction) { throw Error(`'${intervalAction}' is not a valid action`); } else { this.validateFrameInterval(customIntervals[intervalAction]); } }); return formattedCustomIntervals; } validateAction(action) { const modelConfig = this.validationParams.modelConfig; if (!modelConfig) return; if (isArray1D(action)) { const { order, actionType } = modelConfig.actionMetadata[modelConfig.actionHeads[0]]; if (modelConfig.multiheadBool) { throw Error("Models specified with multiple heads, but a 1D array of actions is provided"); } const action1D = action; this.checkActionSize(action1D, order?.length); if (actionType === "continuous") { this.checkValidNumber(action1D); } else { this.checkOneHot(action1D); } return { [modelConfig.actionHeads[0]]: [action1D] }; } else if (isArray2D(action)) { if (modelConfig.actionHeads.length === action.length) { const formattedAction = {}; modelConfig.actionHeads.forEach((actionGroup, row) => { this.checkActionSize(action[row], modelConfig.actionMetadata[actionGroup]?.order?.length); if (modelConfig.actionMetadata[actionGroup]?.actionType === "continuous") { this.checkValidNumber(action[row]); } else { this.checkOneHot(action[row]); } formattedAction[actionGroup] = [action[row]]; }); return formattedAction; } else { throw Error( `Mismatch between number of heads and number of rows in action matrix: ${modelConfig.actionHeads.length} vs ${action.length}` ); } } else { try { const formattedAction = {}; modelConfig.actionHeads.forEach((actionGroup) => { const a = action[actionGroup]; const arrayBools = { "1D": isArray1D(a), "2D": isArray2D(a) }; if (arrayBools["1D"] || arrayBools["2D"]) { this.checkActionSize( arrayBools["1D"] ? a : a[0], modelConfig.actionMetadata[actionGroup].order.length ); formattedAction[actionGroup] = arrayBools["1D"] ? [a] : a; } else { throw Error(); } }); return formattedAction; } catch { throw Error("Invalid configuration for action data"); } } } validateInstance(dataInstance) { if (dataInstance.state !== void 0 && dataInstance.action !== void 0) { if (this.validationParams.modelType) { dataInstance.state = this.validateState(dataInstance.state); dataInstance.action = this.validateAction(dataInstance.action); } return dataInstance; } else { throw Error("Invalid data instance - it should consist of a 'state' and 'action' object"); } } }; var data_validation_default = DataValidation; // lib/utils/data-collection.ts var DataCollector = class extends data_validation_default { _currentFrame; _prevAction; frameInterval; rewardThreshold; customIntervalActions; usingCustomIntervals; trainingData; constructor(frameInterval = 10) { super(); this.frameInterval = frameInterval; this.rewardThreshold = void 0; this.customIntervalActions = {}; this.usingCustomIntervals = false; this.reset(); } _incrementFrame() { this._currentFrame += 1; } setFrameInterval(interval) { this.validateFrameInterval(interval); this.frameInterval = interval; } setRewardThreshold(threshold) { if (typeof threshold !== "number") { throw Error("Reward threshold must be a number"); } else if (threshold <= 0) { throw Error("Reward threshold must be a positive number"); } this.rewardThreshold = threshold; } setCustomIntervalActions(customIntervals = {}) { this.customIntervalActions = this.validateCustomActionIntervals(customIntervals); this.usingCustomIntervals = true; } checkFrame(action) { if (this.usingCustomIntervals) { let customInterval = Infinity; Object.keys(action).forEach((actionGroup) => { for (let i = 0; i < action[actionGroup][0].length; i++) { const tempInterval = this.customIntervalActions[actionGroup]?.[i]; if (tempInterval !== void 0 && action[actionGroup][0][i] && tempInterval < customInterval) { customInterval = tempInterval; } } }); if (customInterval !== Infinity) { return this._currentFrame >= customInterval - 1; } } return this._currentFrame >= this.frameInterval - 1; } dotProduct(v1, v2) { return v1.reduce((total, x, idx) => x * v2[idx] + total, 0); } checkAction(action) { if (!this._prevAction) { return true; } else { let actionChanged = false; Object.keys(action).forEach((actionGroup) => { for (let i = 0; i < action[actionGroup][0].length; i++) { if (action[actionGroup][0][i] !== this._prevAction[actionGroup][0][i]) { actionChanged = true; break; } } }); return actionChanged; } } checkReward(reward) { if (reward !== void 0 && this.rewardThreshold !== void 0) { return Math.abs(reward) > this.rewardThreshold; } return false; } checkEligibility(dataInstance, adjustFrameBool = false) { const { action, reward } = dataInstance; if (this.checkFrame(action) || this.checkAction(action) || this.checkReward(reward)) { if (adjustFrameBool) { this._currentFrame = 0; } return true; } else { if (adjustFrameBool) { this._incrementFrame(); } return false; } } collect(dataInstance) { const validDataInstance = this.validateInstance(dataInstance); const eligible = this.checkEligibility(validDataInstance, true); if (eligible) { this.trainingData.push(validDataInstance); this._prevAction = validDataInstance.action; } return eligible; } reset() { this._currentFrame = this.frameInterval; this.trainingData = []; this._prevAction = null; } }; var data_collection_default = DataCollector; export { data_collection_default as DataCollector, multihead_neural_net_default as NeuralNetworkMultihead, tabular_model_default as TabularModel };