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
JavaScript
;
/* --------------------------------------------------------------------------------------------
* 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