BPMN-OS
BPMN for optimization and simulation
Loading...
Searching...
No Matches
FlattenedGraph.cpp
Go to the documentation of this file.
1#include "FlattenedGraph.h"
6#include <ranges>
7#include <iostream>
8
9using namespace BPMNOS::Execution;
10
11FlattenedGraph::Vertex::Vertex(size_t index, BPMNOS::number rootId, BPMNOS::number instanceId, const BPMN::Node* node, Type type, std::optional< std::pair<Vertex&, Vertex&> > parent)
12 : index(index)
13 , rootId(rootId)
14 , instanceId(instanceId)
15 , node(node)
16 , type(type)
17 , parent(parent)
18{
19 if ( parent.has_value() ) {
20 dataOwners = parent.value().first.dataOwners;
21 }
22}
23
24/*
25std::pair<const FlattenedGraph::Vertex&, const FlattenedGraph::Vertex&> FlattenedGraph::Vertex::parent() const {
26 if( !node->represents<BPMN::ChildNode>() ) {
27 throw std::logic_error("FlattenedGraph::Vertex: only child nodes have a parent");
28 }
29
30 assert( predecessors.size() );
31 assert( predecessors.back().get().node->represents<BPMN::Scope>() );
32 assert( predecessors.back().get().node == node->as<BPMN::ChildNode>()->parent );
33
34 assert( successors.size() );
35 assert( successors.back().get().node->represents<BPMN::Scope>() );
36 assert( successors.back().get().node == node->as<BPMN::ChildNode>()->parent );
37
38 return std::pair<Vertex&, Vertex&>(predecessors.back().get(), successors.back().get() );
39}
40*/
41
42std::pair<const FlattenedGraph::Vertex&, const FlattenedGraph::Vertex&> FlattenedGraph::Vertex::performer() const {
43 assert( node->represents<BPMN::Activity>() );
45
46 auto performer = node->as<BPMN::Activity>()->parent->represents<BPMNOS::Model::SequentialAdHocSubProcess>()->performer;
47 const Vertex* entry = (type == Type::ENTRY ? this : this - 1);
48 const Vertex* exit = (type == Type::EXIT ? this : this + 1);
49 do {
50 assert( entry->parent.has_value() );
51 auto parentVertices = entry->parent.value();
52 entry = &parentVertices.first;
53 exit = &parentVertices.second;
54 } while ( entry->node != performer );
55
56 return { *entry, *exit };
57}
58
59std::pair< const FlattenedGraph::Vertex&, const FlattenedGraph::Vertex&> FlattenedGraph::Vertex::dataOwner( const BPMNOS::Model::Attribute* attribute ) const {
61 const Vertex* entry = (type == Type::ENTRY ? this : this - 1);
62 const Vertex* exit = (type == Type::EXIT ? this : this + 1);
63 const BPMNOS::Model::ExtensionElements* extensionElements = node->extensionElements->as<BPMNOS::Model::ExtensionElements>();
64 while ( extensionElements->attributeRegistry.dataAttributes.size() - extensionElements->data.size() > attribute->index ) {
65 assert( entry->parent.has_value() );
66 auto parentVertices = entry->parent.value();
67 entry = &parentVertices.first;
68 exit = &parentVertices.second;
69 extensionElements = entry->node->extensionElements->as<BPMNOS::Model::ExtensionElements>();
70 }
71
72 return { *entry, *exit };
73}
74
76 return BPMNOS::to_string(instanceId, STRING) + "," + node->id + "," + ( type == Type::ENTRY ? "entry" : "exit" );
77}
78
79
81 // get all known instances
82 auto instances = scenario->getKnownInstances(0);
83 for ( auto& instance : instances ) {
84 addInstance( instance );
85 }
86}
87
89 // create process vertices
90 auto [ entry, exit ] = createVertexPair(instance->id, instance->id, instance->process, std::nullopt);
91 initialVertices.push_back(entry);
92 flatten( instance->id, instance->process, entry, exit );
93}
94
95void FlattenedGraph::addNonInterruptingEventSubProcess( const BPMN::EventSubProcess* eventSubProcess, Vertex& parentEntry, Vertex& parentExit ) {
96 nonInterruptingEventSubProcesses.emplace_back(eventSubProcess, parentEntry, parentExit, 0, nullptr);
97 // iterate through all known trigger and flatten event-subprocess instantiations
98 auto& counter = std::get<unsigned int>( nonInterruptingEventSubProcesses.back() );
99 auto& lastStart = std::get<Vertex*>( nonInterruptingEventSubProcesses.back() );
100 assert( eventSubProcess->startEvent->represents<BPMN::MessageStartEvent>() );
101 assert( eventSubProcess->startEvent->extensionElements );
102 assert( eventSubProcess->startEvent->extensionElements->represents<BPMNOS::Model::ExtensionElements>() );
103 auto& candidates = eventSubProcess->startEvent->extensionElements->as<BPMNOS::Model::ExtensionElements>()->messageCandidates;
104 for ( auto candidate : candidates ) {
105 for ( [[maybe_unused]] auto& _ : sendingVertices[candidate] ) {
106 // create and flatten next event-subprocess
107 counter++;
108 BPMNOS::number id = BPMNOS::to_number( BPMNOS::to_string(parentEntry.instanceId,STRING) + BPMNOS::Model::Scenario::delimiters[0] + eventSubProcess->id + BPMNOS::Model::Scenario::delimiters[1] + std::to_string(counter), STRING);
109 flatten( id, eventSubProcess, parentEntry, parentExit );
110 // newly created vertices at start event must succeed previous vertices at start event
111 auto& container = vertexMap.at(eventSubProcess->startEvent).at(id);
112 assert( container.size() >= 2 );
113 Vertex& startExit = container[container.size()-1];
114 if ( lastStart ) {
115 Vertex& startEntry = container[container.size()-2];
116 lastStart->successors.push_back(startEntry);
117 startEntry.predecessors.push_back(*lastStart);
118 }
119 lastStart = &startExit;
120 }
121 }
122}
123
124void FlattenedGraph::addSender( const BPMN::MessageThrowEvent* messageThrowEvent, Vertex& senderEntry, Vertex& senderExit ) {
125 // flatten event subprocesses if applicable
126 assert( messageThrowEvent->extensionElements );
127 assert( messageThrowEvent->extensionElements->represents<BPMNOS::Model::ExtensionElements>() );
128 auto& candidates = messageThrowEvent->extensionElements->as<BPMNOS::Model::ExtensionElements>()->messageCandidates;
129 for ( auto& [eventSubProcess, parentEntry, parentExit, counter, lastStart] : nonInterruptingEventSubProcesses ) {
130 if (std::find(candidates.begin(), candidates.end(), eventSubProcess->startEvent) != candidates.end()) {
131 // eventSubProcess may be triggered by message throw event, create and flatten next event-subprocess
132 counter++;
133 BPMNOS::number id = BPMNOS::to_number( BPMNOS::to_string(parentEntry.instanceId,STRING) + BPMNOS::Model::Scenario::delimiters[0] + eventSubProcess->id + BPMNOS::Model::Scenario::delimiters[1] + std::to_string(counter), STRING);
134 flatten( id, eventSubProcess, parentEntry, parentExit );
135 }
136 }
137
138 // set precedences for all recipients
139 for ( auto candidate : candidates ) {
140 for ( auto& [ recipientEntry, recipientExit] : receivingVertices[candidate] ) {
141 senderEntry.recipients.push_back(recipientExit);
142 recipientExit.senders.push_back(senderEntry);
143 if ( messageThrowEvent->represents<BPMN::SendTask>() ) {
144 recipientExit.recipients.push_back(senderExit);
145 senderExit.senders.push_back(recipientExit);
146 }
147 }
148 }
149
150 sendingVertices[messageThrowEvent].emplace_back(senderEntry, senderExit);
151}
152
153void FlattenedGraph::addRecipient( const BPMN::MessageCatchEvent* messageCatchEvent, Vertex& recipientEntry, Vertex& recipientExit ) {
154 // set precedences for all senders
155 assert( messageCatchEvent->extensionElements );
156 assert( messageCatchEvent->extensionElements->represents<BPMNOS::Model::ExtensionElements>() );
157 auto& candidates = messageCatchEvent->extensionElements->as<BPMNOS::Model::ExtensionElements>()->messageCandidates;
158 for ( auto candidate : candidates ) {
159 for ( auto& [ senderEntry, senderExit] : sendingVertices[candidate] ) {
160 senderEntry.recipients.push_back(recipientExit);
161 recipientExit.senders.push_back(senderEntry);
162 if ( candidate->represents<BPMN::SendTask>() ) {
163 recipientExit.recipients.push_back(senderExit);
164 senderExit.senders.push_back(recipientExit);
165 }
166 }
167 }
168
169 receivingVertices[messageCatchEvent].emplace_back(recipientEntry, recipientExit);
170}
171
172std::pair<FlattenedGraph::Vertex&, FlattenedGraph::Vertex&> FlattenedGraph::createVertexPair(BPMNOS::number rootId, BPMNOS::number instanceId, const BPMN::Node* node, std::optional< std::pair<Vertex&, Vertex&> > parent) {
173 vertices.emplace_back(vertices.size(), rootId, instanceId, node, Vertex::Type::ENTRY, parent);
174 auto& entry = vertices.back();
175 vertices.emplace_back(vertices.size(), rootId, instanceId, node, Vertex::Type::EXIT, parent);
176 auto& exit = vertices.back();
177
178 entry.successors.push_back(exit);
179 exit.predecessors.push_back(entry);
180
181 if ( parent.has_value() ) {
182 entry.predecessors.push_back( parent.value().first );
183 exit.successors.push_back( parent.value().second );
184 parent.value().first.successors.push_back( entry );
185 parent.value().second.predecessors.push_back( exit );
186 }
187
188 auto& container = vertexMap[node][instanceId]; // get or create container
189 container.emplace_back( entry );
190 container.emplace_back( exit );
191
192 assert( node->extensionElements );
193 auto extensionElements = node->extensionElements->represents<BPMNOS::Model::ExtensionElements>();
194
195 if ( !extensionElements ) {
196 return { entry, exit };
197 }
198
199 if ( auto messageThrowEvent = node->represents<BPMN::MessageThrowEvent>() ) {
200 addSender( messageThrowEvent, entry, exit );
201 }
202 else if ( auto messageCatchEvent = node->represents<BPMN::MessageCatchEvent>() ) {
203 addRecipient( messageCatchEvent, entry, exit );
204 }
205
206 if ( extensionElements->data.size() ) {
207 entry.dataOwners.push_back( entry );
208 exit.dataOwners.push_back( entry );
209 }
210
211 // populate lookup maps of sequential activities for each performer
212 if ( extensionElements->hasSequentialPerformer ) {
213 sequentialActivities.emplace(&entry,std::vector< std::pair<const Vertex&, const Vertex&> >());
214 }
215 else if ( auto sequentialAdHocSubProcess = node->represents<BPMNOS::Model::SequentialAdHocSubProcess>();
216 sequentialAdHocSubProcess && !sequentialAdHocSubProcess->performer
217 ) {
218 sequentialActivities.emplace(&entry,std::vector< std::pair<const Vertex&, const Vertex&> >());
219 }
220
222 auto performerVertices = entry.performer();
223 sequentialActivities.at( &performerVertices.first ).push_back( { entry, exit } );
224 }
225
226 // populate lookup maps of data modifiers for data owner
227 if ( extensionElements->data.size() ) {
228 dataModifiers.emplace(&entry,std::vector< std::pair<const Vertex&, const Vertex&> >());
229// dataModifiers.emplace(&exit,std::vector< std::pair<const Vertex&, const Vertex&> >()); // ISTHISNEEDED
230 }
231
232 if ( node->represents<BPMN::Task>() ) {
233 for ( auto& operator_ : extensionElements->operators ) {
234 if ( operator_->attribute->category == BPMNOS::Model::Attribute::Category::DATA ) {
235 auto dataOwnerVertices = entry.dataOwner(operator_->attribute);
236 dataModifiers.at( &dataOwnerVertices.first ).push_back( { entry, exit } );
237 }
238 else if ( operator_->attribute->category == BPMNOS::Model::Attribute::Category::GLOBAL ) {
239 globalModifiers.push_back( { entry, exit } );
240 }
241 }
242 }
243
244 return { entry, exit };
245}
246
247void FlattenedGraph::createLoopVertices(BPMNOS::number rootId, BPMNOS::number instanceId, const BPMN::Activity* activity, std::optional< std::pair<Vertex&, Vertex&> > parent) {
248 // loop & multi-instance activties
249
250 // lambda returning parameter value known at time zero
251 auto getValue = [&](BPMNOS::Model::Parameter* parameter, BPMNOS::ValueType type) -> std::optional<BPMNOS::number> {
252/*
253 if ( parameter->attribute.has_value() ) {
254// TODO:
255 BPMNOS::Model::Attribute& attribute = parameter->attribute->get();
256 if ( !attribute.isImmutable ) {
257 throw std::runtime_error("FlattenedGraph: Loop parameter '" + parameter->name + "' for activity '" + activity->id +"' must be immutable" );
258 }
259 auto value = scenario->getKnownValue(rootId, &attribute, 0);
260 if ( value.has_value() ) {
261 return value.value();
262 }
263 }
264
265 if ( parameter->value.has_value() ) {
266 return BPMNOS::to_number( parameter->value.value().get(), type);
267 }
268*/
269
270 return std::nullopt;
271 };
272
273 int n = 0;
274 auto extensionElements = activity->extensionElements->represents<BPMNOS::Model::ExtensionElements>();
275 assert(extensionElements);
276
278 if ( extensionElements->loopMaximum.has_value() ) {
279 auto value = getValue( extensionElements->loopMaximum.value().get(), INTEGER );
280 n = value.has_value() ? (int)value.value() : 0;
281 }
282 }
283 else {
284 if ( extensionElements->loopMaximum.has_value() ) {
285 auto value = getValue( extensionElements->loopCardinality.value().get(), INTEGER );
286 n = value.has_value() ? (int)value.value() : 0;
287 }
288// TODO
289/*
290 // determine implicit cardinality from collection size
291 auto attributes = extensionElements->attributes | std::views::filter([](auto& attribute) {
292 return (attribute->collection != nullptr);
293 });
294
295 for ( auto& attribute : attributes ) {
296 auto collectionValue = getValue( attribute->collection.get(), COLLECTION );
297 if ( !collectionValue.has_value() ) {
298 throw std::runtime_error("FlattenedGraph: unable to determine collection for attribute '" + attribute->name + "'");
299 }
300 auto& collection = collectionRegistry[(size_t)collectionValue.value()].values;
301 if ( n > 0 && n != (int)collection.size() ) {
302 throw std::runtime_error("FlattenedGraph: inconsistent number of values provided for multi-instance activity '" + activity->id +"'" );
303 }
304 n = (int)collection.size();
305 }
306*/
307 }
308
309 if ( n <= 0 ) {
310 throw std::runtime_error("FlattenedGraph: cannot determine loop maximum/cardinality for activity '" + activity->id +"'" );
311 }
312
314 // create vertices for loop activity
315 for ( int i = 1; i <= n; i++ ) {
316 createVertexPair(rootId, instanceId, activity, parent);
317 }
318 }
319 else {
320 std::string baseName = BPMNOS::to_string(instanceId,STRING) + BPMNOS::Model::Scenario::delimiters[0] + activity->id + BPMNOS::Model::Scenario::delimiters[1] ;
321 // create vertices for multi-instance activity
322 for ( int i = 1; i <= n; i++ ) {
323 createVertexPair(rootId, BPMNOS::to_number(baseName + std::to_string(i),STRING), activity, parent);
324 }
325 }
326
328 // create sequential precedences
329 auto& container = vertexMap.at(activity).at(instanceId);
330 for ( size_t i = 1; i < container.size(); i += 2 ) {
331 Vertex& predecessor = container[i]; // exit vertex
332 Vertex& successor = container[i+1]; // entry vertex
333 predecessor.successors.push_back(successor);
334 successor.predecessors.push_back(predecessor);
335 }
336 }
337
338}
339
340void FlattenedGraph::flatten(BPMNOS::number instanceId, const BPMN::Scope* scope, Vertex& scopeEntry, Vertex& scopeExit) {
341 std::pair<Vertex&, Vertex&> parent = {scopeEntry,scopeExit};
342 for ( auto& flowNode : scope->flowNodes ) {
343
344 // create vertices for flow node
345 auto activity = flowNode->represents<BPMN::Activity>();
346 if ( activity && activity->loopCharacteristics.has_value() ) {
347 createLoopVertices(scopeEntry.rootId, instanceId, activity, parent);
348 }
349 else {
350 createVertexPair(scopeEntry.rootId, instanceId, flowNode, parent);
351 }
352
353 auto& container = vertexMap.at(flowNode).at(instanceId);
354/*
355 // add predecessors and successors for vertices
356 for ( Vertex& vertex : container ) {
357 vertex.predecessors.push_back(scopeEntry);
358 vertex.successors.push_back(scopeExit);
359 }
360*/
361 // flatten child scopes
362 if ( auto childScope = flowNode->represents<BPMN::Scope>() ) {
363 assert( container.size() % 2 == 0 );
364 for ( size_t i = 0; i < container.size(); i += 2 ) {
365 Vertex& entry = container[i];
366 Vertex& exit = container[i+1];
367 flatten( entry.instanceId, childScope, entry, exit );
368 }
369 }
370 }
371
372 // sequence flows
373 for ( auto& sequenceFlow : scope->sequenceFlows ) {
374 Vertex& origin = vertexMap.at(sequenceFlow->source).at(instanceId).front();
375 Vertex& destination = vertexMap.at(sequenceFlow->target).at(instanceId).back();
376 origin.outflows.emplace_back(sequenceFlow.get(),destination);
377 destination.inflows.emplace_back(sequenceFlow.get(),origin);
378 }
379
380 // boundary events
381 for ( auto& flowNode : scope->flowNodes ) {
382 if ( auto boundaryEvent = flowNode->represents<BPMN::BoundaryEvent>() ) {
383 throw std::runtime_error("FlattenedGraph: Boundary event '" + boundaryEvent->id + "' is not yet supported");
384 }
385 }
386
387 // event-subprocesses
388 for ( auto& eventSubProcess : scope->eventSubProcesses ) {
389 if ( eventSubProcess->startEvent->isInterrupting ) {
390 // interrupting event-subprocesses
391 flatten( instanceId, eventSubProcess, scopeEntry, scopeExit );
392 }
393 else {
394 // non-interrupting event-subprocesses
395 if ( eventSubProcess->startEvent->represents<BPMN::MessageStartEvent>() ) {
396 addNonInterruptingEventSubProcess(eventSubProcess, scopeEntry, scopeExit);
397 }
398 else {
399 throw std::runtime_error("FlattenedGraph: Type of non-interrupting event-subprocess '" + eventSubProcess->id + "' is not supported");
400 }
401 }
402 }
403
404 // compensation nodes
405 for ( auto& flowNode : scope->flowNodes ) {
406 auto activity = flowNode->represents<BPMN::Activity>();
407 if ( activity && activity->isForCompensation ) {
408 throw std::runtime_error("FlattenedGraph: Compensation activity '" + activity->id + "' is not yet supported");
409 }
410 }
411}
412
std::pair< const Vertex &, const Vertex & > performer() const
Container holding all entry vertices of nodes owning at least one data attribute.
std::optional< std::pair< Vertex &, Vertex & > > parent
std::pair< const Vertex &, const Vertex & > dataOwner(const BPMNOS::Model::Attribute *attribute) const
Returns the vertices of the performer of a sequential activity vertex.
std::string reference() const
Returns the vertices of the owner of a data attribute.
void addInstance(const BPMNOS::Model::Scenario::InstanceData *instance)
Map holding entry and exit vertices of each possible instantiation of a node.
std::unordered_map< const Vertex *, std::vector< std::pair< const Vertex &, const Vertex & > > > dataModifiers
Container allowing to look up vertices of sequential activities given a pointer to the entry vertex o...
std::deque< Vertex > vertices
Container holding entry vertices of all process instances.
std::unordered_map< const Vertex *, std::vector< std::pair< const Vertex &, const Vertex & > > > sequentialActivities
const BPMNOS::Model::Scenario * scenario
std::vector< std::reference_wrapper< Vertex > > initialVertices
std::unordered_map< const BPMN::Node *, std::unordered_map< BPMNOS::number, std::vector< std::reference_wrapper< Vertex > > > > vertexMap
Container holding entry and exit vertices of each possible instantiation of a node.
std::vector< std::pair< const Vertex &, const Vertex & > > globalModifiers
Container allowing to look up vertices of tasks modifying data attributes given a pointer to the entr...
FlattenedGraph(const BPMNOS::Model::Scenario *scenario)
std::map< std::string, Attribute * > dataAttributes
size_t index
Index of attribute (is automatically set by attribute registry).
Definition Attribute.h:28
Class holding extension elements representing execution data for nodes.
AttributeRegistry attributeRegistry
Registry allowing to look up all status and data attributes by their names.
std::vector< std::unique_ptr< Attribute > > data
Vector containing data attributes declared for data objects within the node's scope.
The Scenario class holds data for all BPMN instances.
Definition Scenario.h:20
static constexpr char delimiters[]
Delimiters used for disambiguation of identifiers of non-interrupting event subprocesses and multi-in...
Definition Scenario.h:53
std::vector< const InstanceData * > getKnownInstances(const BPMNOS::number currentTime) const
Method returning a vector of all instances that have been created or are known for sure until the giv...
Definition Scenario.cpp:97
Class representing adhoc subprocesses with sequential ordering.
bool isForCompensation
Definition bpmn++.h:17385
std::optional< LoopCharacteristics > loopCharacteristics
Definition bpmn++.h:17387
std::unique_ptr< ExtensionElements > extensionElements
Definition bpmn++.h:16299
std::string id
Id of element.
Definition bpmn++.h:16298
Base class for all boundary events attached to an Activity.
Definition bpmn++.h:17200
Scope * parent
Reference to the parent node.
Definition bpmn++.h:16568
T * as()
Casts the element to the specified type T.
Definition bpmn++.h:16253
T * represents()
Attempts to cast the element to the specified type T.
Definition bpmn++.h:16236
TypedStartEvent * startEvent
Definition bpmn++.h:16610
Base class for all nodes in a BPMN model.
Definition bpmn++.h:16444
Base class for BPMN elements that may contain a ChildNode elements.
Definition bpmn++.h:16510
std::vector< EventSubProcess * > eventSubProcesses
Vector containing pointers to all event subprocesses within the scope of the nodes.
Definition bpmn++.h:16522
std::vector< FlowNode * > flowNodes
Vector containing pointers to all flow nodes within the scope of the nodes.
Definition bpmn++.h:16519
std::vector< std::unique_ptr< SequenceFlow > > sequenceFlows
Vector containing all sequence flows within the scope.
Definition bpmn++.h:16528
std::string to_string(number numericValue, const ValueType &type)
Converts a number to a string.
Definition Number.cpp:150
number to_number(const std::string &valueString, const ValueType &type)
Converts a string to a number.
Definition Number.cpp:57
BPMNOS_NUMBER_TYPE number
Definition Number.h:42
ValueType
Definition Value.h:9
@ INTEGER
Definition Value.h:9
@ STRING
Definition Value.h:9
const BPMN::Process * process
Definition Scenario.h:33
size_t id
Instance identifier.
Definition Scenario.h:34