import * as _ from 'lodash'; import { PetriNet, Token, Marking, PlaceObject, TransitionObject, MarkingExtensions, TokenObject } from './coloredPetriNets.js'; import { NameSuggester } from './utils.js'; // var spy = (value) => { // console.log(JSON.stringify(value, null, 4)); // return value; // }; enum ValueExplorationStrategies { STATIC = "static", DYNAMIC = "dynamic" } class ValueExplorer { // rolls out CPN to remove variables, turning all tokens into empty objects rollOutNet(net: PetriNet, options: { strategy: ValueExplorationStrategies }): PetriNet { options = _.merge({ strategy: ValueExplorationStrategies.STATIC }, options); const seenTokens: { [placeKey: string]: Token[] } = {}; // a map from place keys to lists of tokens that may appear in that place _.each(net.places, (place) => seenTokens[place.key] = []); switch (options.strategy) { case ValueExplorationStrategies.STATIC: { type TodoItem = { placeKey: string; token: Token }; const todo: TodoItem[] = []; const seeToken = function ({ placeKey, token }: TodoItem): void { const tokenList = seenTokens[placeKey]; if (!_.some(tokenList, (t) => _.isEqual(t, token))) { tokenList.push(token); // we pushed a new token // now, search for new transitions this enables const affectedTransitions = _.filter(net.transitions, (transition) => _.some(transition.inFlows, (inFlow) => inFlow.source.key == placeKey)); const marking = Marking.fromObject(net, { tokens: seenTokens, extensions: {} }); // already seen tokens represented as a marking const enabledMoves = _.flatMap(affectedTransitions, (transition) => transition.enabledMovesForMarking(marking, { ignoreInFlowTokenCounts: true })); _.each(enabledMoves, (move) => { _.each(move.producedTokens, (tokenList, placeKey) => { _.each(tokenList, (token) => { todo.push({ placeKey, token }); }); }); }); } }; _.each(net.initialMarking.tokens, (tokenList, placeKey) => { _.each(tokenList, (token) => { seeToken({ placeKey, token }); }); }); while (todo.length > 0) { seeToken(todo.shift()!); // ! is OK: length was positive } break; } case ValueExplorationStrategies.DYNAMIC: { const todo = [net.initialMarking]; const seenMarkings = [net.initialMarking.toObject()]; const seeMarking = function (marking: Marking): void { // mark token colors in this marking as seen _.each(marking.tokens, (tokens, placeKey) => { const tokenList = seenTokens[placeKey]; _.each(tokens, (token) => { if (!_.some(tokenList, (t) => _.isEqual(t, token))) { tokenList.push(token); } }); }); const enabledMoves = marking.enabledMoves(); _.each(enabledMoves, (move) => { const newMarkingObject = move.marking!.toObject(); if (!_.some(seenMarkings, (seenMarking) => _.isEqual(seenMarking, newMarkingObject))) { // new marking todo.push(move.marking!); seenMarkings.push(newMarkingObject); } }); }; while (todo.length > 0) { seeMarking(todo.shift()!); // ! is OK: length was positive } break; } } // exploration completed // assemble new net // assemble new places const placeNameSuggester = new NameSuggester(); const newPlaceMap: { [placeKey: string]: { token: Token; place: string }[] } = {}; // a map from old place keys to list of token and corresponding new place key const places: PlaceObject[] = []; _.each(seenTokens, (tokenList, placeKey) => { const place = net.places[placeKey]; const newPlaceTuples: { token: Token; place: string }[] = []; newPlaceMap[placeKey] = newPlaceTuples; _.each(tokenList, (token) => { const newPlaceKey = placeNameSuggester.suggestName(`${place.key}/${JSON.stringify(token.color)}`); places.push({ key: newPlaceKey, displayName: place.displayName, extensions: _.assign(_.cloneDeep(place.extensions), { valueRollOut: { place: placeKey, color: token.color } }) }); newPlaceTuples.push({ token, place: newPlaceKey }); }); }); // assemble new transitions const marking = Marking.fromObject(net, { tokens: seenTokens, extensions: {} }); // already seen tokens represented as a marking const transitionNameSuggester = new NameSuggester(); const getNewPlaceKey = function (oldPlaceKey: string, token: Token): string | null { return _.get(_.find(newPlaceMap[oldPlaceKey], ({ token: t }) => _.isEqual(t, token)), 'place') ?? null; }; const transitions: TransitionObject[] = []; _.each(net.transitions, (transition) => { let transitionNumber = 1; const moves = transition.enabledMovesForMarking(marking, { ignoreInFlowTokenCounts: true }); _.each(moves, (move) => { const inFlowPlaces = _.map(_.flatMap(move.consumedTokens, (tokenList, placeKey) => _.map(tokenList, (token) => getNewPlaceKey(placeKey, token)))); const outFlowPlaces = _.map(_.flatMap(move.producedTokens, (tokenList, placeKey) => _.map(tokenList, (token) => getNewPlaceKey(placeKey, token)))); if (!(_.every(outFlowPlaces))) { // transition cannot be reached and would lead to non-generated places // no need to synthesize it return; } transitions.push({ key: transitionNameSuggester.suggestName(`${move.transition.key}-${transitionNumber++}`), displayName: move.transition.displayName, guard: "true", inFlows: _.map(inFlowPlaces, (placeKey) => ({ source: placeKey!, pattern: "{}" })), outFlows: _.map(outFlowPlaces, (placeKey) => ({ target: placeKey!, expression: "{}" })), extensions: { valueRollOut: { transition: move.transition.key, environment: move.environment } } }); }); }); // and finally, the initial marking const initialMarking: { tokens: { [placeKey: string]: TokenObject[] }; extensions: MarkingExtensions } = { tokens: {}, extensions: net.initialMarking.extensions }; _.each(net.initialMarking.tokens, (tokenList, placeKey) => { _.each(tokenList, (token) => { const newPlaceKey = getNewPlaceKey(placeKey, token); if (!initialMarking.tokens[newPlaceKey!]) initialMarking.tokens[newPlaceKey!] = []; initialMarking.tokens[newPlaceKey!].push({ color: {} }); }); }); return PetriNet.fromObject({ places, transitions, initialMarking, extensions: {} }); } } export { ValueExplorationStrategies, ValueExplorer, };