UNPKG

ai-planning-val

Version:

Javascript/typescript wrapper for VAL (AI Planning plan validation and evaluation tools from KCL Planning department and the planning community around the ICAPS conference).

209 lines 8.91 kB
"use strict"; /* -------------------------------------------------------------------------------------------- * Copyright (c) Jan Dolejsi. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. * ------------------------------------------------------------------------------------------ */ Object.defineProperty(exports, "__esModule", { value: true }); exports.PlanTimeSeriesParser = exports.FunctionsValues = exports.StateValues = exports.FunctionValues = void 0; const pddl_workspace_1 = require("pddl-workspace"); class FunctionValues { constructor(variable) { this.variable = variable; this.values = []; this.legend = variable.parameters.map(p => p.toPddlString()).join(' '); } addValue(time, value) { this.values.push([time, value]); } lastTime() { return this.values.length > 0 ? this.values[this.values.length - 1][0] : NaN; } getValueAtIndex(index) { if (index >= this.values.length) { throw new Error(`Index ${index} out of bounds of function values.`); } return this.values[index][1]; } getTimeAtIndex(index) { if (index >= this.values.length) { throw new Error(`Index ${index} out of bounds of function values.`); } return this.values[index][0]; } getValue(time) { let lastTime = 0; let lastValue = NaN; for (let index = 0; index < this.values.length; index++) { const rowTime = this.getTimeAtIndex(index); const rowValue = this.getValueAtIndex(index); if (rowTime === time) { return rowValue; } else if (rowTime < time) { lastTime = rowTime; lastValue = rowValue; } else { return lastValue + (rowValue - lastValue) / (rowTime - lastTime) * (time - lastTime); } } // the time requested is after the last value return lastValue; } getLegend() { return this.legend; } } exports.FunctionValues = FunctionValues; class StateValues { constructor(time) { this.time = time; this.values = new Map(); } setValue(variable, value) { this.values.set(variable, value); return this; } getValue(variable) { var _a; return (_a = this.values.get(variable)) !== null && _a !== void 0 ? _a : Number.NaN; } toNumbers(variables) { let output = variables.map(f => this.getValue(f)); output = [this.time].concat(output); return output; } } exports.StateValues = StateValues; /** * Structure that holds values for multiple functions */ class FunctionsValues { constructor(liftedVariable, values, functions) { this.liftedVariable = liftedVariable; this.values = values; this.functions = functions; if (functions.length === 1 && functions[0].parameters.length === 0) { // the function had no parameters this.legend = [liftedVariable.name]; } else { const objects = this.functions.map(f => f.parameters.map(p => p.toPddlString()).join(' ')); this.legend = objects; } } isConstant() { // eslint-disable-next-line @typescript-eslint/no-this-alias const that = this; return this.functions.every((_, idx) => { const firstValue = that.values[0] && that.values[0][idx + 1]; return that.values.every(values1 => values1[idx + 1] === firstValue); }); } } exports.FunctionsValues = FunctionsValues; class PlanTimeSeriesParser { constructor(functions, timeSeriesCsv, adjustDuplicatedTimeStamps = true) { this.functions = functions; this.functionValues = new Map(); const lines = timeSeriesCsv.split('\n') .map(l => l.trim()) .filter(l => l.length > 0); this.warnings = lines.filter(line => line.includes(':')); let currentFunctionValues = null; lines .filter(line => !line.includes(':')) .forEach(line => { const newFunction = this.findMatchingFunction(line, functions); if (newFunction) { if (currentFunctionValues) { this.addFunctionValues(currentFunctionValues); } currentFunctionValues = new FunctionValues(newFunction); } else { // eslint-disable-next-line prefer-const let [time, value] = line.split(',').map(v => parseFloat(v.trim())); if (currentFunctionValues === null) { throw new Error(`The ValueSeq output does not include function names ${functions.map(f => f.getFullName())}`); } if (isNaN(time) || value === undefined) { throw new Error(`The ValueSeq output does not parse: ${line}`); } else { if (adjustDuplicatedTimeStamps) { if (currentFunctionValues.lastTime() > time) { time = currentFunctionValues.lastTime() + PlanTimeSeriesParser.TIME_DELTA; } else if (currentFunctionValues.lastTime() === time) { time += PlanTimeSeriesParser.TIME_DELTA; } } currentFunctionValues.addValue(time, value); } } }); this.warnings.forEach(w => console.warn('ValueSeq: ' + w)); if (currentFunctionValues) { this.addFunctionValues(currentFunctionValues); } } findMatchingFunction(line, functions) { var _a; const existingFunction = functions.find(f => line.match(new RegExp("^\s*(;)*\s*" + f.getFullName() + "\s*$", "i"))); if (existingFunction) { return existingFunction; } const metricNameMatch = line.match(/^\s*(;)*\s*metric\s*(\d+)$/i); if (metricNameMatch) { const metricVariable = new pddl_workspace_1.Variable('metric', [new pddl_workspace_1.ObjectInstance((_a = metricNameMatch[2]) !== null && _a !== void 0 ? _a : 'unidentified', "#")]); functions.push(metricVariable); return metricVariable; } } addFunctionValues(newFunctionValues) { this.functionValues.set(newFunctionValues.variable, newFunctionValues); } getFunctionValues(variable) { return this.functionValues.get(variable); } getGroundedFunctionsValues(liftedVariable) { const groundedFunctions = [...this.functionValues.keys()] .filter(var1 => var1.name === liftedVariable.name); return groundedFunctions .map(f => this.functionValues.get(f)) .filter(f => !!f) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion .map(f => f); } getFunctionData(liftedVariable) { const functionValues = this.getGroundedFunctionsValues(liftedVariable); const groundedFunctions = functionValues.map(fv => fv.variable); if (groundedFunctions.length === 0) return new FunctionsValues(liftedVariable, [], groundedFunctions); const states = groundedFunctions.length > 1 ? // todo: this is where the duplicate timestamps (step functions) get removed; // need a smarter algorithm for combining multiple function values, while retaining step functions functionValues.reduce((previousValues, currentValues) => PlanTimeSeriesParser.join(previousValues, currentValues), new Array()) : // in case of single grounded function, let's avoid removing the duplicate timestamps functionValues[0].values.map(v => new StateValues(v[0]).setValue(functionValues[0].variable, v[1])); const data = states.map(state => state.toNumbers(groundedFunctions)); return new FunctionsValues(liftedVariable, data, groundedFunctions); } static join(previousValues, currentValues) { currentValues.values.forEach(timeAndValue => { const currentTime = timeAndValue[0]; let stateFound = previousValues.find(state => state.time === currentTime); if (!stateFound) { stateFound = new StateValues(currentTime); previousValues.push(stateFound); previousValues.sort((s1, s2) => s1.time - s2.time); } stateFound.setValue(currentValues.variable, timeAndValue[1]); }); return previousValues; } } exports.PlanTimeSeriesParser = PlanTimeSeriesParser; PlanTimeSeriesParser.TIME_DELTA = 1e-10; //# sourceMappingURL=PlanTimeSeriesParser.js.map