BPMN-OS
BPMN for optimization and simulation
Loading...
Searching...
No Matches
CPController.cpp
Go to the documentation of this file.
1#include "CPController.h"
4
5#include <iostream>
6
7using namespace BPMNOS::Execution;
8
10 : scenario(scenario)
11 , flattenedGraph(FlattenedGraph(scenario))
12 , model(CP::Model::ObjectiveSense::MAXIMIZE)
13{
14 createCP();
15}
16
18 Controller::connect(mediator);
19}
20
21std::shared_ptr<Event> CPController::dispatchEvent(const SystemState* systemState) {
22 std::shared_ptr<Decision> best = nullptr;
23 // TODO
24 return best;
25}
26
28 vertices.reserve( flattenedGraph.vertices.size() );
29std::cerr << "initializeVertices" << std::endl;
30 // determine relevant vertices of all process instances
31 for ( auto initialVertex : flattenedGraph.initialVertices ) {
32 initializeVertices(initialVertex);
33 }
34
35std::cerr << "create sequence position variables" << std::endl;
36 // create sequence position variables for all vertices
37 auto sequence = model.addSequence( "sequence", vertices.size() );
38 for ( size_t i = 0; i < vertices.size(); i++ ) {
39 position.emplace(vertices[i], sequence[i]);
40 }
41
42std::cerr << "createGlobalVariables" << std::endl;
44
45std::cerr << "createVertexVariables:" << flattenedGraph.vertices.size() << std::endl;
46 // create vertex and message variables
47 for ( auto vertex : vertices ) {
48 createVertexVariables(*vertex);
49 }
50
51std::cerr << "createMessageVariables" << std::endl;
53std::cerr << "Done" << std::endl;
54//std::cerr << model.stringify() << std::endl;
55}
56
58 for ( auto& [name,attribute] : scenario->model->attributeRegistry.globalAttributes ) {
59 assert( attribute->index == globals.size() ); // ensure that the order of attributes is correct
60 globals.emplace_back(
61 model.addIndexedVariables(CP::Variable::Type::BOOLEAN, "defined_" + attribute->id ),
62 model.addIndexedVariables(CP::Variable::Type::REAL, "value_" + attribute->id)
63 );
64 auto& [defined,value] = globals.back();
65 // add variables holding initial values
66 auto& initialValue = scenario->globals[attribute->index];
67 if ( initialValue.has_value() ) {
68 // defined initial value
69 defined.emplace_back((double)true,(double)true);
70 value.emplace_back((double)initialValue.value(), (double)initialValue.value());
71 }
72 else {
73 // undefined initial value
74 defined.emplace_back((double)false,(double)false);
75 value.emplace_back(0.0, 0.0);
76 }
77
78 if ( attribute->isImmutable ) {
79 // deduced variables
80 for ( [[maybe_unused]] auto _ : flattenedGraph.globalModifiers ) {
81 defined.emplace_back(defined[0]);
82 value.emplace_back(value[0]);
83 }
84 }
85 else {
86 for ( [[maybe_unused]] auto _ : flattenedGraph.globalModifiers ) {
87 // unconstrained variables
88 defined.emplace_back();
89 value.emplace_back();
90 }
91 }
92 }
93}
94
96 for ( auto recipient : messageRecipients ) {
97 assert( recipient->exit<BPMN::MessageCatchEvent>() );
98 CP::reference_vector<const CP::Variable> messages;
99 for ( Vertex& sender : recipient->senders ) {
100 assert( sender.exit<BPMN::MessageThrowEvent>() );
101 messages.emplace_back( model.addBinaryVariable("message_" + sender.reference() + "→" + recipient->reference() ) );
102 const CP::Variable& message = messages.back();
103 messageFlow.emplace( std::make_pair(&sender,recipient), message );
104 model.addConstraint( message <= visit.at(recipient) );
105 model.addConstraint( message <= visit.at(&sender) );
106
107 model.addConstraint( message.implies( position.at(&sender) <= position.at(recipient) ) );
108 model.addConstraint(
109 // if a message is sent from a sender to a recipient, the recipient's timestamp must not
110 // be before the sender's timestamp
111 message.implies (
114 )
115 );
116 if ( sender.node->represents<BPMN::SendTask>() ) {
117 auto senderExit = &sender + 1;
118 model.addConstraint(
119 message.implies (
122 )
123 );
124 }
125
126 // TODO: add message header constraints
127 }
128 CP::Expression sum;
129 for ( const CP::Variable& message : messages ) {
130 sum = sum + message;
131 }
132 model.addConstraint( sum == visit.at(recipient) );
133 }
134}
135
137 // TODO
138 // every visited recipient must receive a message
139 // message precedence constraints
140
141 // message content
142}
143
145 auto extensionElements = vertex.node->extensionElements->as<BPMNOS::Model::ExtensionElements>();
146
147 std::vector< IndexedAttributeVariables > variables; /// CAN I MOVE THIS; SHOULD THIS BE REFERENCES?
148
149 for ( auto& attribute : extensionElements->data ) {
150 assert( attribute->index == variables.size() ); // ensure that the order of attributes is correct
151 variables.emplace_back(
152 model.addIndexedVariables(CP::Variable::Type::BOOLEAN, "defined_" + BPMNOS::to_string(vertex.instanceId,STRING) + "," + attribute->id ),
153 model.addIndexedVariables(CP::Variable::Type::REAL, "value_" + BPMNOS::to_string(vertex.instanceId,STRING) + "," + attribute->id)
154 );
155 auto& [defined,value] = variables.back();
156 // add variables holding initial values
157 auto initialValue = scenario->getKnownValue(vertex.rootId, attribute.get(), 0);
158 if ( initialValue.has_value() ) {
159 // defined initial value
160 defined.emplace_back((double)true,(double)true);
161 value.emplace_back((double)initialValue.value(), (double)initialValue.value());
162 }
163 else {
164 // undefined initial value
165 defined.emplace_back((double)false,(double)false);
166 value.emplace_back(0.0, 0.0);
167 }
168
169 assert( flattenedGraph.dataModifiers.contains(&vertex) );
170 if ( attribute->isImmutable ) {
171 // deducible variables
172 for ( [[maybe_unused]] auto _ : flattenedGraph.dataModifiers.at(&vertex) ) {
173 defined.emplace_back(defined[0]);
174 value.emplace_back(value[0]);
175 }
176 }
177 else {
178 for ( [[maybe_unused]] auto _ : flattenedGraph.dataModifiers.at(&vertex) ) {
179 // unconstrained variables
180 defined.emplace_back();
181 value.emplace_back();
182 }
183 }
184 }
185
186 data.emplace( &vertex, std::move(variables) );
187}
188
190std::cerr << "createStatus: " << vertex.reference() << std::endl;
191 if ( vertex.type == Vertex::Type::ENTRY ) {
192 createEntryStatus(vertex);
193 }
194 else {
195 createExitStatus(vertex);
196 }
197
198 // TODO: sequential activity or multi-instance sequential activity
199}
200
202 std::vector<AttributeVariables> variables;
203 if ( vertex.parent.has_value() ) {
204 auto scope = vertex.parent.value().first.node;
205 auto extensionElements = scope->extensionElements->as<BPMNOS::Model::ExtensionElements>();
206 if ( vertex.entry<BPMN::UntypedStartEvent>() ) {
207 // TODO: start-event
208 assert( vertex.predecessors.size() == 1 );
209 variables = createAlternativeEntryStatus(vertex, extensionElements->attributeRegistry, {{visit.at(&vertex.predecessors.front().get()), status.at(&vertex.predecessors.front().get())}} ); // TODO: add new attributes!
210 }
211 else if ( vertex.entry<BPMN::TypedStartEvent>() ) {
212 // TODO: start-event
213 }
214 else if ( vertex.entry<BPMN::ExclusiveGateway>() && vertex.inflows.size() > 1 ) {
215 std::vector< std::pair<const CP::Variable&, std::vector<AttributeVariables>& > > alternatives;
216 for ( auto& [sequenceFlow,predecessor] : vertex.inflows ) {
217 // add to alternatives
218 alternatives.emplace_back( tokenFlow.at({&predecessor,&vertex}), statusFlow.at({&predecessor,&vertex}) );
219 }
220 variables = createAlternativeEntryStatus(vertex, extensionElements->attributeRegistry, std::move(alternatives)); // TODO: add new attributes!
221 }
222 else if ( vertex.entry<BPMN::FlowNode>() && vertex.inflows.size() > 1 ) {
223 assert(vertex.entry<BPMN::ParallelGateway>() || vertex.entry<BPMN::InclusiveGateway>() );
224 assert( vertex.predecessors.size() == 1 );
225 if ( !vertex.entry<BPMN::ParallelGateway>() && !vertex.entry<BPMN::InclusiveGateway>() ) {
226 throw std::runtime_error("CPController: illegal join at '" + vertex.node->id + "'");
227 }
228 std::vector< std::pair<const CP::Variable&, std::vector<AttributeVariables>& > > inputs;
229 for ( auto& [sequenceFlow,predecessor] : vertex.inflows ) {
230 // add to alternatives
231 inputs.emplace_back( tokenFlow.at({&predecessor,&vertex}), statusFlow.at({&predecessor,&vertex}) );
232 }
233 variables = createMergedStatus(vertex, extensionElements->attributeRegistry, std::move(inputs)); // TODO: add new attributes!
234 }
235 else if ( vertex.entry<BPMN::FlowNode>() ) {
236 assert( vertex.inflows.size() == 1 );
237 auto& [sequenceFlow,predecessor] = vertex.inflows.front();
238 variables = createAlternativeEntryStatus(vertex, extensionElements->attributeRegistry, {{tokenFlow.at({&predecessor,&vertex}), statusFlow.at({&predecessor,&vertex})}} ); // TODO: add new attributes!
239 }
240 }
241
242 if ( auto extensionElements = vertex.node->extensionElements->represents<BPMNOS::Model::ExtensionElements>() ) {
243 variables.reserve(extensionElements->attributeRegistry.statusAttributes.size());
244 // add new attributes
245 auto it = extensionElements->attributeRegistry.statusAttributes.begin();
246 std::advance(it, variables.size());
247 for ( ; it != extensionElements->attributeRegistry.statusAttributes.end(); it++ ) {
248 auto attribute = it->second;
249 // add variables holding given values
250 auto value = scenario->getKnownValue(vertex.rootId, attribute, 0);
251
252 if ( value.has_value() ) {
253 // defined initial value
254 variables.emplace_back(
255 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_" + vertex.reference() + "," + attribute->id, (double)true,(double)true ),
256 model.addVariable(CP::Variable::Type::REAL, "value_" + vertex.reference() + "," + attribute->id, (double)value.value(), (double)value.value())
257 );
258 }
259 else {
260 // no given value
261 bool defined = ( attribute->index == BPMNOS::Model::ExtensionElements::Index::Timestamp );
262 variables.emplace_back(
263 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_" + vertex.reference() + "," + attribute->id, (double)defined,(double)defined ),
264 model.addVariable(CP::Variable::Type::REAL, "value_" + vertex.reference() + "," + attribute->id, 0.0, 0.0)
265 );
266 }
267 }
268 }
269
270 status.emplace( &vertex, std::move(variables) );
271}
272
274std::cerr << "createExitStatus" << std::endl;
275 std::vector<AttributeVariables> variables;
276 auto extensionElements = vertex.node->extensionElements->represents<BPMNOS::Model::ExtensionElements>();
277
278 if ( vertex.node->represents<BPMN::Scope>() && vertex.predecessors.size() > 1 ) {
279 // scope has children
280 auto getEndVertices = [&]() {
281 std::vector< std::reference_wrapper<const Vertex> > endVertices;
282 for ( const auto& candidate : vertex.predecessors ) {
283 if (
284 candidate.get().parent.has_value() &&
285 &candidate.get().parent.value().second == &vertex &&
286 candidate.get().parent.value().second.outflows.empty()
287 ) {
288 endVertices.push_back( candidate );
289 }
290 }
291 return endVertices;
292 };
293 std::vector< std::pair<const CP::Variable&, std::vector<AttributeVariables>& > > inputs;
294 for ( const Vertex& endVertex : getEndVertices() ) {
295 assert(visit.contains(&endVertex));
296 assert(status.contains(&endVertex));
297 inputs.emplace_back( visit.at(&endVertex), status.at(&endVertex) );
298 }
299
300 assert(extensionElements);
301 variables = createMergedStatus(vertex, extensionElements->attributeRegistry, std::move(inputs));
302
303 status.emplace( &vertex, std::move(variables) );
304 return;
305 }
306
307 // no scope or empty scope
308 assert( !vertex.exit<BPMN::Scope>() || vertex.predecessors.size() == 1 );
309
310 const Vertex& entry = *(&vertex - 1);
311
312 if ( !extensionElements ) {
313 // exit status is the same as entry status
314 extensionElements = vertex.parent.value().first.node->extensionElements->represents<BPMNOS::Model::ExtensionElements>();
315 auto& entryStatus = status.at(&entry);
316 for ( auto& [name,attribute] : extensionElements->attributeRegistry.statusAttributes ) {
317 assert( attribute->index == variables.size() );
318 variables.emplace_back(
319 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_" + vertex.reference() + "," + attribute->id, entryStatus[attribute->index].defined ),
320 model.addVariable(CP::Variable::Type::REAL, "value_" + vertex.reference() + "," + attribute->id, entryStatus[attribute->index].value )
321 );
322 }
323 status.emplace( &vertex, std::move(variables) );
324 return;
325 }
326
327 // From Token.cpp:
328 // - process operators are applied upon entry
329 // - subprocess operators are applied upon entry
330 // - operators of receive task and decision task are applied on completion
331 // - event subprocess operators are applied when exiting typed start event
332
333 // copy entry status references
334 std::vector<AttributeVariables> currentStatus = status.at(&entry);
335 //std::vector<AttributeVariables> currentData = status.at(&entry);
336 //std::vector<AttributeVariables> currentGlobla = status.at(&entry);
337
339 // apply choices
340 // TODO
341 }
342
343 if ( vertex.node->represents<BPMN::MessageCatchEvent>() ) {
344 // receive message content (globals and data attributes must not be changed)
345 if ( extensionElements->messageDefinitions.size() == 1 ) {
346 std::vector< std::tuple< std::string_view, size_t, AttributeVariables> > contentVariables;
347 auto& messageDefinition = extensionElements->messageDefinitions.front();
348 for (auto& [key,content] : messageDefinition->contentMap ) {
349 auto attribute = content->attribute;
350
351 // create variables for the message content (relevant constraints must be added separately)
352 AttributeVariables attributeVariables = {
353 model.addBinaryVariable("content[" + content->key + "]_defined_" + vertex.reference() + "," + attribute->id ),
354 model.addRealVariable("content[" + content->key + "]_value_" + vertex.reference() + "," + attribute->id )
355 };
356
357 // use message content variables in current status
358 currentStatus.at(attribute->index) = attributeVariables;
359
360 contentVariables.emplace_back( content->key, attribute->index, std::move(attributeVariables) );
361 }
362
363 messageContent.emplace( &vertex, std::move(contentVariables) );
364 }
365 else if ( extensionElements->messageDefinitions.size() > 1 ) {
366 // multi-instance receive task
367 assert(!"Not yet supported");
368 }
369 }
370
371 if ( vertex.node->represents<BPMN::MessageStartEvent>() ) {
372 // add entry restrictions of event-subprocess
373 // TODO
374 }
375
376 if ( vertex.node->represents<BPMN::Task>() ) {
377 // apply operators
378 // TODO
379 }
380
381 for ( auto& [name,attribute] : extensionElements->attributeRegistry.statusAttributes ) {
382 assert( attribute->index == variables.size() );
383 variables.emplace_back(
384 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_" + vertex.reference() + "," + attribute->id, currentStatus[attribute->index].defined ),
385 model.addVariable(CP::Variable::Type::REAL, "value_" + vertex.reference() + "," + attribute->id, currentStatus[attribute->index].value )
386 );
387
388 // ensure that attribute only has a value if vertex is visited and the attribute ifis defined
389 assert( visit.contains(&vertex) );
390 model.addConstraint( variables[attribute->index].defined <= visit.at(&vertex) );
391 model.addConstraint( (!variables[attribute->index].defined).implies( variables[attribute->index].value == 0.0 ) );
392 }
393
394 status.emplace( &vertex, std::move(variables) );
395}
396
397std::vector<CPController::AttributeVariables> CPController::createAlternativeEntryStatus(const Vertex& vertex, const BPMNOS::Model::AttributeRegistry& attributeRegistry, std::vector< std::pair<const CP::Variable&, std::vector<AttributeVariables>& > > alternatives) {
398 assert( vertex.type == Vertex::Type::ENTRY );
399 assert( !vertex.node->represents<BPMN::Process>() );
400 std::vector<AttributeVariables> variables;
401 variables.reserve( attributeRegistry.statusAttributes.size() );
402 for ( auto& [name,attribute] : attributeRegistry.statusAttributes ) {
403 // deduce variable
404 CP::Expression defined(false);
405 CP::Expression value = 0.0;
406 for ( auto& [ active, attributeVariables] : alternatives ) {
407 assert( attributeVariables.size() == attributeRegistry.statusAttributes.size() );
408 defined = defined || attributeVariables[attribute->index].defined;
409 value = value + attributeVariables[attribute->index].value;
410 }
411 variables.emplace_back(
412 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_" + vertex.reference() + "," + attribute->id, defined ),
413 model.addVariable(CP::Variable::Type::REAL, "value_" + vertex.reference() + "," + attribute->id, value )
414 );
415 }
416
417 return variables;
418}
419
420std::vector<CPController::AttributeVariables> CPController::createMergedStatus(const Vertex& vertex, const BPMNOS::Model::AttributeRegistry& attributeRegistry, std::vector< std::pair<const CP::Variable&, std::vector<AttributeVariables>& > > inputs) {
421 assert( ( vertex.type == Vertex::Type::ENTRY && vertex.inflows.size() > 1) || vertex.exit<BPMN::Scope>() );
422 std::vector<AttributeVariables> variables;
423 variables.reserve( attributeRegistry.statusAttributes.size() );
424 for ( auto& [name,attribute] : attributeRegistry.statusAttributes ) {
425 if ( attribute->index == BPMNOS::Model::ExtensionElements::Index::Timestamp ) {
426// TODO
427/*
428 CP::MaxExpression timestamp;
429 for ( auto& [ active, attributeVariables] : inputs ) {
430 timestamp = CP::max( timestamp, attributeVariables[attribute->index].value );
431 }
432 variables.emplace_back(
433 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_" + vertex.reference() + "," + attribute->id, (double)true, (double)true ),
434 model.addVariable(CP::Variable::Type::REAL, "value_" + vertex.reference() + "," + attribute->id, timestamp )
435 );
436*/
437 }
438 else {
439 // deduce variable
440 CP::Expression defined(false);
441 CP::Cases cases;
442 for ( auto& [ active, attributeVariables] : inputs ) {
443 defined = defined || attributeVariables[attribute->index].defined;
444 cases.emplace_back( attributeVariables[attribute->index].defined, attributeVariables[attribute->index].value );
445 }
446 variables.emplace_back(
447 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_" + vertex.reference() + "," + attribute->id, defined ),
448 model.addVariable(CP::Variable::Type::REAL, "value_" + vertex.reference() + "," + attribute->id, CP::n_ary_if( cases, 0.0 ))
449 );
450
451 // add constraints that all defined inputs have the same value
452 auto& mergedValue = variables.back().value;
453 for ( auto& [ _, attributeVariables] : inputs ) {
454 auto& [ hasValue, value ] = attributeVariables[attribute->index];
455 model.addConstraint( hasValue.implies( value == mergedValue ) );
456 }
457 }
458 }
459
460 return variables;
461}
462
463
464std::vector< std::reference_wrapper<const FlattenedGraph::Vertex> > CPController::getReachableVertices(const FlattenedGraph::Vertex& initialVertex) {
465 std::vector< std::reference_wrapper<const FlattenedGraph::Vertex> > reachableVertices;
466 std::unordered_map<const FlattenedGraph::Vertex*, size_t> inDegree;
467
468 std::deque<const FlattenedGraph::Vertex*> queue;
469 queue.push_back(&initialVertex);
470
471 // determine vertices in topological order
472 while ( !queue.empty() ) {
473std::cerr << "Queue " << queue.size() << std::endl;
474 const FlattenedGraph::Vertex* current = queue.front();
475 queue.pop_front();
476 reachableVertices.push_back(*current);
477 inDegree.erase(current);
478
479 for ( auto& [_,vertex] : current->outflows ) {
480 if ( !inDegree.contains(&vertex) ) {
481 // initialize in degree
482 inDegree[&vertex] = vertex.inflows.size() + vertex.predecessors.size();
483 }
484 // decrement in degree and add vertex to queue if zero
485 if ( --inDegree.at(&vertex) == 0 ) {
486 queue.push_back(&vertex);
487 }
488 }
489 for ( const FlattenedGraph::Vertex& vertex : current->successors ) {
490 if ( !inDegree.contains(&vertex) ) {
491 // initialize in degree
492 inDegree[&vertex] = vertex.inflows.size() + vertex.predecessors.size();
493 }
494std::cerr << "Vertex " << vertex.reference() << " has inDegree " << inDegree.at(&vertex) << "/" << vertex.inflows.size() << "/" << vertex.predecessors.size() << std::endl;
495 // decrement in degree and add vertex to queue if zero
496 if ( --inDegree.at(&vertex) == 0 ) {
497 queue.push_back(&vertex);
498 }
499 }
500 }
501
502 if ( inDegree.size() ) {
503 throw std::runtime_error("CPController: cycle detected in '" + BPMNOS::to_string(initialVertex.rootId, STRING) + "'");
504 }
505std::cerr << "reachableVertices:" << reachableVertices.size() << std::endl;
506 return reachableVertices;
507}
508
510 for ( const Vertex& vertex : getReachableVertices(initialVertex) ) {
511 vertices.push_back(&vertex);
512 }
513}
514
516std::cerr << "createVertexVariables: " << vertex.reference() << std::endl;
517
518std::cerr << "createGlobalIndexVariable" << std::endl;
520std::cerr << "createDataIndexVariables" << std::endl;
522
523 if ( vertex.type == Vertex::Type::ENTRY ) {
524std::cerr << "createEntryVariables" << std::endl;
525 createEntryVariables(vertex);
526 }
527 else {
528std::cerr << "createExitVariables" << std::endl;
529 createExitVariables(vertex);
530 }
531
532std::cerr << "createDataVariables: " << vertex.reference() << std::endl;
533 if ( vertex.entry<BPMN::Scope>() ) {
534 createDataVariables(vertex);
535 }
536
537std::cerr << "createStatus" << std::endl;
538 createStatus(vertex);
539
540std::cerr << "createSequenceConstraints" << std::endl;
542std::cerr << "Done" << std::endl;
543}
544
546 // visit variable
547 if ( vertex.node->represents<BPMN::UntypedStartEvent>() ) {
548 assert( vertex.inflows.empty() );
549 assert( vertex.predecessors.size() == 1 );
550 assert( vertex.senders.empty() );
551
552 // deduce visit from parent
553 auto& deducedVisit = model.addVariable(CP::Variable::Type::BOOLEAN, "visit_" + BPMNOS::to_string(vertex.instanceId, STRING) + "," + vertex.node->id, visit.at( &vertex.parent.value().first ) );
554 visit.emplace(&vertex, deducedVisit );
555 visit.emplace(&vertex+1, deducedVisit );
556 }
557 else if ( vertex.node->represents<BPMN::TypedStartEvent>() ) {
558 assert(!"Not yet implemented");
559 }
560 else if ( vertex.node->represents<BPMN::FlowNode>() ) {
561 if ( vertex.inflows.size() == 1 ) {
562 // deduce visit from unique sequence flow
563 auto& deducedVisit = model.addVariable(CP::Variable::Type::BOOLEAN, "visit_" + BPMNOS::to_string(vertex.instanceId, STRING) + "," + vertex.node->id , tokenFlow.at( std::make_pair(&vertex.inflows.front().second, &vertex) ) );
564 visit.emplace(&vertex, deducedVisit );
565 visit.emplace(&vertex+1, deducedVisit );
566 }
567 else {
568 assert(!"Not yet implemented");
569 }
570 }
571 else if ( vertex.node->represents<BPMN::Process>() ) {
572 // every process vertex is visited
573 auto& knownVisit = model.addVariable(CP::Variable::Type::BOOLEAN, "visit_" + BPMNOS::to_string(vertex.instanceId, STRING) + "," + vertex.node->id, (double)true, (double)true );
574 visit.emplace(&vertex, knownVisit );
575 visit.emplace(&vertex+1, knownVisit );
576 }
577
578 // status attributes
579 // message content
580}
581
583 if ( vertex.node->represents<BPMN::FlowNode>() ) {
584 // flow variables
585 if ( vertex.outflows.size() == 1 ) {
586 tokenFlow.emplace(
587 std::make_pair(&vertex,&vertex.outflows.front().second),
588 model.addVariable(CP::Variable::Type::BOOLEAN, "tokenflow_" + vertex.reference() + "→" + vertex.outflows.front().second.reference(), visit.at(&vertex) )
589 );
590 auto extensionElements = vertex.parent.value().first.node->extensionElements->as<BPMNOS::Model::ExtensionElements>();
591 std::vector<AttributeVariables> variables;
592 variables.reserve( extensionElements->attributeRegistry.statusAttributes.size() );
593 for ( auto& [name,attribute] : extensionElements->attributeRegistry.statusAttributes ) {
594 // deduce variable
595 variables.emplace_back(
596 model.addVariable(CP::Variable::Type::BOOLEAN, "statusflow_defined_" + vertex.reference() + "→" + vertex.outflows.front().second.reference() + "," + attribute->id,
597 CP::if_then_else(
598 tokenFlow.at({&vertex,&vertex.outflows.front().second}),
599 status.at(&vertex)[attribute->index].defined,
600 (double)false
601 )
602 ),
603 model.addVariable(CP::Variable::Type::REAL, "statusflow_value_" + vertex.reference() + "→" + vertex.outflows.front().second.reference() + "," + attribute->id,
604 CP::if_then_else(
605 tokenFlow.at({&vertex,&vertex.outflows.front().second}),
606 status.at(&vertex)[attribute->index].value,
607 0.0
608 )
609 )
610 );
611 }
612
613 statusFlow.emplace(
614 std::make_pair(&vertex,&vertex.outflows.front().second),
615 std::move(variables)
616 );
617 }
618 else if ( vertex.outflows.size() > 1 ) {
619 assert(!"Not yet implemented");
620 }
621 }
622}
623
625 auto addConstraints = [&](const Vertex& predecessor) {
626 assert( position.contains(&predecessor) );
627 assert( position.contains(&vertex) );
628 assert( visit.contains(&vertex) );
629 assert( status.contains(&predecessor) );
630 assert( status.contains(&vertex) );
631 model.addConstraint( position.at(&predecessor) + 1 <= position.at(&vertex) );
632 model.addConstraint(
633 // if a vertex is visited, its timestamp must not be before the predecessors timestamp
634 // as all timestamps are non-negative and zero if not visited, no condition on a visit
635 // of the predecessor is required
636 visit.at(&vertex).implies (
639 )
640 );
641 };
642
643 for ( auto& [sequenceFlow, predecessor] : vertex.inflows ) {
644 addConstraints(predecessor);
645 }
646
647 for ( auto& predecessor : vertex.predecessors ) {
648 addConstraints(predecessor);
649 }
650}
651
652
654 CP::Expression index;
655 for ( auto& [modifierEntry, modifierExit] : flattenedGraph.globalModifiers ) {
656 // create auxiliary variables indicating modifiers preceding the vertex
657 index = index + model.addVariable(CP::Variable::Type::BOOLEAN, "precedes_" + modifierExit.reference() + "" + vertex.reference(), position.at(&modifierExit) <= position.at(&vertex) );
658 }
659 globalIndex.emplace( &vertex, model.addVariable(CP::Variable::Type::INTEGER, "globals_index_" + vertex.reference(), index ) );
660}
661
662
664 std::vector< CP::reference_vector< const CP::Variable > > dataIndices;
665 dataIndices.resize( vertex.dataOwners.size() );
666 for ( size_t i = 0; i < vertex.dataOwners.size(); i++ ) {
667 auto& dataOwner = vertex.dataOwners[i].get();
668 assert( flattenedGraph.dataModifiers.contains(&dataOwner) );
669
670 CP::Expression index;
671 for ( auto& [modifierEntry, modifierExit] : flattenedGraph.dataModifiers.at(&dataOwner) ) {
672 // create auxiliary variables indicating modifiers preceding the vertex
673 index = index + model.addVariable(CP::Variable::Type::BOOLEAN, "precedes_" + modifierExit.reference() + "→" + vertex.reference(), position.at(&modifierExit) <= position.at(&vertex) );
674 }
675 dataIndices[i].emplace_back( model.addVariable(CP::Variable::Type::INTEGER, "data_index[" + dataOwner.node->id + "]_" + vertex.reference(), index ) );
676 }
677 dataIndex.emplace( &vertex, std::move(dataIndices) );
678}
679
680/*
681bool CPController::checkEntryDependencies(NodeReference reference) {
682 auto& [instance,node] = reference;
683 if ( auto startEvent = node->represents<BPMN::UntypedStartEvent>() ) {
684 if ( !entryStatus.contains({instance,startEvent->parent}) ) {
685 return false;
686 }
687 }
688 else if ( auto startEvent = node->represents<BPMN::TypedStartEvent>() ) {
689 if ( !entryStatus.contains({instance,startEvent->parent->as<BPMN::EventSubProcess>()->parent}) ) {
690 return false;
691 }
692 // TODO: check trigger dependencies?
693 if ( startEvent->isInterrupting ) {
694 throw std::runtime_error("CPController: unsupported interrupting start event '" + node->id + "'");
695 }
696
697 if ( auto messageStartEvent = node->represents<BPMN::MessageStartEvent>() ) {
698 auto originCandidates = messageStartEvent->extensionElements->as<BPMNOS::Model::ExtensionElements>()->messageCandidates;
699 for ( auto candidate : originCandidates ) {
700 for ( auto originInstance : originInstances[candidate->as<BPMN::MessageThrowEvent>()] ) {
701 if ( !nodeVariables.contains({(size_t)originInstance,candidate}) ) {
702 return false;
703 }
704 }
705 }
706 }
707 else {
708 throw std::runtime_error("CPController: unsupported start event type for '" + node->id + "'");
709 }
710 }
711 else if ( auto flowNode = node->represents<BPMN::FlowNode>() ) {
712 for ( const BPMN::SequenceFlow* sequenceFlow : flowNode->incoming ) {
713 if ( !sequenceFlowVariables.contains({instance,sequenceFlow}) ) {
714 return false;
715 }
716 }
717 }
718 else {
719 assert(!"Unexpected node reference");
720 }
721 return true;
722}
723
724bool CPController::checkExitDependencies(NodeReference reference) {
725 auto& [instance,node] = reference;
726 if ( node->represents<BPMN::SendTask>() ) {
727 // check all possible recipients
728 }
729 else if ( node->represents<BPMN::MessageCatchEvent>() ) {
730 // check all possible senders
731 }
732 else if ( auto scope = node->represents<BPMN::Scope>() ) {
733 // check that all exit variables of end flow nodes have been created
734 for ( auto endNode : scope->flowNodes | std::ranges::views::filter([](const BPMN::FlowNode* node) { return node->outgoing.empty(); }) ) {
735 if ( !exitStatus.contains({instance,endNode}) ) {
736 return false;
737 }
738 }
739 }
740
741 return true;
742}
743
744
745void CPController::addEntryVariables(NodeReference reference) {
746 auto& [instance,node] = reference;
747
748 auto extensionElements = node->extensionElements->as<BPMNOS::Model::ExtensionElements>();
749 size_t newDataIndex = extensionElements->attributeRegistry.dataAttributes.size() - extensionElements->data.size();
750 createNodeTraversalVariables(reference);
751
752 if ( auto scope = node->represents<BPMN::Scope>() ) {
753 // it is assumed that modified data states are only propagated upon entry and exit
754
755 // determine sequential activities of sequential performers
756 sequentialActivities[scope] = extensionElements->hasSequentialPerformer ? std::vector<const BPMN::Node* >() : scope->find_all(
757 [scope](const BPMN::Node* candidate) {
758 if ( auto activity = candidate->represents<BPMN::Activity>() ) {
759 if ( auto adhocSubprocess = activity->parent->represents<BPMNOS::Model::SequentialAdHocSubProcess>() ) {
760 return adhocSubprocess->performer == scope;
761 }
762 }
763 return false;
764 }
765 );
766
767 for ( auto& attribute : extensionElements->data ) {
768 // store scope in which data attributes live
769 dataOwner[attribute.get()] = scope;
770 }
771 }
772
773 // determine set holding the scopes for which entry data state variables are required
774 for ( auto& [_,attribute] : extensionElements->attributeRegistry.dataAttributes ) {
775 if ( auto flowNode = node->represents<BPMN::FlowNode>();
776 flowNode && flowNode->parent != dataOwner.at(attribute)
777 ) {
778 auto scopeReference = ScopeReference{instance,dataOwner.at(attribute)};
779 if ( flowNode->incoming.empty() ) {
780 if ( auto activity = flowNode->parent->represents<BPMN::Activity>() ) {
781 if ( auto adhoc = activity->parent->represents<BPMNOS::Model::SequentialAdHocSubProcess>();
782 adhoc && adhoc->performer == dataOwner.at(attribute)
783 ) {
784 // flow node is start event of sequentially executed activity and data cannot be changed by any other activity
785 continue;
786 }
787 }
788 else {
789 if (!entryDataState[NodeReference{instance,flowNode->parent}].contains(scopeReference) ) {
790 // parent has no entry data state variable for attribute
791 continue;
792 }
793 }
794 }
795 else {
796 auto& predecessor = flowNode->incoming.front()->source;
797 if (!entryDataState[NodeReference{instance,predecessor}].contains(scopeReference) ) {
798 // predecessor has no entry data state variable for attribute
799 continue;
800 }
801 }
802 }
803 scopesOwningData[reference].insert(dataOwner.at(attribute));
804 }
805
806 createEntryDataStateVariables(reference);
807
808 // create entry data and new data state variables
809 auto dataVariables = std::vector<AttributeVariables>();
810 for ( auto& [name,attribute] : extensionElements->attributeRegistry.dataAttributes ) {
811 assert( attribute->index == dataVariables.size() );
812 if ( attribute->index >= newDataIndex ) {
813 // create new entry data variable
814 auto value = scenario->getKnownValue(instance, attribute, 0); // get value known at time zero
815 if ( value.has_value() ) {
816 dataVariables.emplace_back(
817 model.addVariable(CP::Variable::Type::BOOLEAN, "defined^entry_{" + identifier(reference) + "," + name + "}", (double)true,(double)true ),
818 model.addVariable(CP::Variable::Type::REAL, "value^entry_{" + identifier(reference) + "," + name + "}", (double)value.value(), (double)value.value())
819 );
820 }
821 else {
822 dataVariables.emplace_back(
823 model.addVariable(CP::Variable::Type::BOOLEAN, "defined^entry_{" + identifier(reference) + "," + name + "}", (double)false,(double)false ),
824 model.addVariable(CP::Variable::Type::REAL, "value^entry_{" + identifier(reference) + "," + name + "}", 0.0, 0.0)
825 );
826 }
827 // create initial data state variables
828 dataState[DataReference{instance,attribute}].emplace_back(
829 model.addVariable(CP::Variable::Type::BOOLEAN,std::string("defined^") + std::to_string(0) + "_{" + identifier(reference) + "}", dataVariables.back().defined ),
830 model.addVariable(CP::Variable::Type::BOOLEAN,std::string("value^") + std::to_string(0) + "_{" + identifier(reference) + "}", dataVariables.back().value )
831 );
832 // create subsequent data state variables
833 for ( size_t i = 1; i <= sequentialActivities[node->as<BPMN::Scope>()].size(); i++ ) {
834 dataState[DataReference{instance,attribute}].emplace_back(
835 model.addBinaryVariable(std::string("defined^") + std::to_string(i) + "_{" + identifier(reference) + "}"),
836 model.addBinaryVariable(std::string("value^") + std::to_string(i) + "_{" + identifier(reference) + "}")
837 );
838 }
839 }
840 else {
841 // deduce exisiting entry data variables
842 auto scopeReference = ScopeReference{instance,dataOwner.at(attribute)};
843 auto it = entryDataState[reference].find(scopeReference);
844 if ( it == entryDataState.at(reference).end() ) {
845 // deduce from parent or predecessor
846 if ( auto flowNode = node->represents<BPMN::FlowNode>() ) {
847 deduceEntryAttributeVariable(dataVariables, attribute, reference, entryData, exitData);
848 }
849 }
850 else {
851 auto& entryDataStateVariables = it->second;
852 CP::Cases defined_cases;
853 CP::Cases value_cases;
854 for ( size_t i = 0; i <= entryDataStateVariables.size(); i++ ) {
855 auto& [defined,value] = dataState[DataReference{instance,attribute}][i];
856 defined_cases.emplace_back( entryDataStateVariables[i], defined );
857 value_cases.emplace_back( entryDataStateVariables[i], value );
858 }
859 dataVariables.emplace_back(
860 model.addVariable(CP::Variable::Type::BOOLEAN, "defined^entry_[" + identifier(reference) + "," + name+ "}", CP::n_ary_if(defined_cases,0) ),
861 model.addVariable(CP::Variable::Type::REAL, "value^entry_{" + identifier(reference) + "," + name+ "}", CP::n_ary_if(value_cases,0))
862 );
863 }
864 }
865 }
866 entryData.emplace( reference , std::move(dataVariables) );
867
868 // deduce exisiting entry status variables
869 auto statusVariables = std::vector<AttributeVariables>();
870 size_t newStatusIndex = extensionElements->attributeRegistry.statusAttributes.size() - extensionElements->attributes.size();
871 for ( auto& [name,attribute] : node->extensionElements->as<BPMNOS::Model::ExtensionElements>()->attributeRegistry.statusAttributes ) {
872 assert( attribute->index == statusVariables.size() );
873 if ( attribute->index >= newStatusIndex ) {
874 // create new entry status variable
875 auto value = scenario->getKnownValue(instance, attribute, 0); // get value known at time zero
876 if ( value.has_value() ) {
877 statusVariables.emplace_back(
878 model.addVariable(CP::Variable::Type::BOOLEAN, "defined^entry_{" + identifier(reference) + "," + name + "}", (double)true,(double)true ),
879 model.addVariable(CP::Variable::Type::REAL, "value^entry_{" + identifier(reference) + "," + name + "}", (double)value.value(), (double)value.value())
880 );
881 }
882 else {
883 statusVariables.emplace_back(
884 model.addVariable(CP::Variable::Type::BOOLEAN, "defined^entry_{" + identifier(reference) + "," + name + "}", (double)false,(double)false ),
885 model.addVariable(CP::Variable::Type::REAL, "value^entry_{" + identifier(reference) + "," + name + "}", 0.0, 0.0)
886 );
887 }
888 }
889 else {
890 // deduce exisiting entry status variables
891 if ( auto flowNode = node->represents<BPMN::FlowNode>() ) {
892 deduceEntryAttributeVariable(statusVariables, attribute, reference, entryStatus, exitStatus);
893//// BEGIN COMMENT
894 if ( flowNode->incoming.empty() ) {
895 statusVariables.emplace_back(
896 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_entry_" + identifier(reference) + "_" + name, entryStatus.at(NodeReference{instance,flowNode->parent})[attribute->index].defined ),
897 model.addVariable(CP::Variable::Type::REAL, "value_entry_" + identifier(reference) + "_" + name, entryStatus.at(NodeReference{instance,flowNode->parent})[attribute->index].value )
898 );
899 }
900 else if ( flowNode->incoming.size() == 1 ) {
901 auto& predecessor = flowNode->incoming.front()->source;
902 statusVariables.emplace_back(
903 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_entry_" + identifier(reference) + "_" + name, exitStatus.at(NodeReference{instance,predecessor})[attribute->index].defined ),
904 model.addVariable(CP::Variable::Type::REAL, "value_entry_" + identifier(reference) + "_" + name, exitStatus.at(NodeReference{instance,predecessor})[attribute->index].value )
905 );
906 }
907 else if ( node->represents<BPMN::ExclusiveGateway>() ) {
908 assert(!"Exclusive join not yet supported");
909 }
910 else {
911 assert(!"Non-exclusive join not yet supported");
912 }
913//// END COMMENT
914 }
915 }
916 }
917 entryStatus.emplace( reference , std::move(statusVariables) );
918}
919
920void CPController::deduceEntryAttributeVariable(std::vector<AttributeVariables>& attributeVariables, const BPMNOS::Model::Attribute* attribute, NodeReference reference, variable_map<NodeReference, std::vector<AttributeVariables> >& entryAttributes, variable_map<NodeReference, std::vector<AttributeVariables> >& exitAttributes) {
921 auto& [instance,node] = reference;
922 auto flowNode = node->as<BPMN::FlowNode>();
923
924 if ( flowNode->incoming.empty() ) {
925 // attribute values of start node is deduced from entry value of parent
926 attributeVariables.emplace_back(
927 model.addVariable(CP::Variable::Type::BOOLEAN, "defined^entry_{" + identifier(reference) + "," + attribute->name + "}", entryAttributes.at(NodeReference{instance,flowNode->parent})[attribute->index].defined ),
928 model.addVariable(CP::Variable::Type::REAL, "value^entry_{" + identifier(reference) + "," + attribute->name + "}", entryAttributes.at(NodeReference{instance,flowNode->parent})[attribute->index].value )
929 );
930 }
931 else if ( flowNode->incoming.size() == 1 ) {
932 // attribute values of flow node with exactle one predecessor is deduced from exit value of predecessor
933 auto& predecessor = flowNode->incoming.front()->source;
934 attributeVariables.emplace_back(
935 model.addVariable(CP::Variable::Type::BOOLEAN, "defined^entry_{" + identifier(reference) + "," + attribute->name, exitAttributes.at(NodeReference{instance,predecessor})[attribute->index].defined ),
936 model.addVariable(CP::Variable::Type::REAL, "value^entry_{" + identifier(reference) + "," + attribute->name + "}", exitAttributes.at(NodeReference{instance,predecessor})[attribute->index].value )
937 );
938 }
939 else if ( flowNode->represents<BPMN::ExclusiveGateway>() ) {
940 assert(!"Exclusive join not yet supported");
941 }
942 else {
943 assert(!"Non-exclusive join not yet supported");
944 }
945}
946
947
948void CPController::createNodeTraversalVariables(NodeReference reference) {
949 auto& [instance,node] = reference;
950
951 // create node variables
952 if ( node->represents<BPMN::Process>() ) {
953 // process must be executed
954 nodeVariables.emplace(
955 NodeReference{instance, node},
956 model.addVariable(CP::Variable::Type::BOOLEAN, std::string("x_{") + identifier(reference) + "}", (double)true, (double)true)
957 );
958 }
959 else if ( auto startEvent = node->represents<BPMN::UntypedStartEvent>() ) {
960 // start event must be executed if parent is executed
961 nodeVariables.emplace(
962 NodeReference{instance, node},
963 model.addVariable(
964 CP::Variable::Type::BOOLEAN,
965 std::string("x_{") + identifier(reference) + "}",
966 nodeVariables.at(NodeReference{instance,startEvent->parent})
967 )
968 );
969 }
970 else if ( auto startEvent = node->represents<BPMN::TypedStartEvent>() ) {
971 if ( startEvent->isInterrupting ) {
972 throw std::runtime_error("CPController: unsupported interrupting start event '" + node->id + "'");
973 }
974
975 if ( auto messageStartEvent = node->represents<BPMN::MessageStartEvent>() ) {
976 CP::OrExpression messageReceived;
977 auto originCandidates = messageStartEvent->extensionElements->as<BPMNOS::Model::ExtensionElements>()->messageCandidates;
978 for ( auto candidate : originCandidates ) {
979 for ( auto originInstance : originInstances[candidate->as<BPMN::MessageThrowEvent>()] ) {
980 messageReceived = messageReceived || messageFlowVariables.at(MessageCatchReference{instance,node->as<BPMN::MessageCatchEvent>()}).at(MessageThrowReference{(size_t)originInstance,candidate->as<BPMN::MessageThrowEvent>()});
981 }
982 }
983 nodeVariables.emplace(
984 NodeReference{instance, node},
985 model.addVariable(
986 CP::Variable::Type::BOOLEAN,
987 std::string("x_{") + identifier(reference) +"}",
988 messageReceived
989 )
990 );
991 }
992 else {
993 throw std::runtime_error("CPController: unsupported start event type for '" + node->id + "'");
994 }
995 }
996 else if ( auto flowNode = node->represents<BPMN::FlowNode>() ) {
997 assert( flowNode->incoming.size() );
998 CP::OrExpression tokenArrives;
999 for (auto sequenceFlow : flowNode->incoming ) {
1000 tokenArrives = tokenArrives || sequenceFlowVariables.at(SequenceFlowReference{instance,sequenceFlow});
1001 }
1002 nodeVariables.emplace(
1003 NodeReference{instance, node},
1004 model.addVariable(
1005 CP::Variable::Type::BOOLEAN,
1006 std::string("x_{") + identifier(reference) + "}",
1007 tokenArrives
1008 )
1009 );
1010 }
1011}
1012
1013
1014void CPController::createEntryDataStateVariables(NodeReference reference) {
1015 // create entry data states
1016 auto& [instance,node] = reference;
1017// auto extensionElements = node->extensionElements->as<BPMNOS::Model::ExtensionElements>();
1018
1019
1020 // the entry data state is non-decreasing along sequence flows and subprocesses
1021 for ( auto scopeOwningData : scopesOwningData.at(reference) ) {
1022 auto scopeReference = ScopeReference{instance,node->as<BPMN::Scope>()};
1023 std::vector< std::reference_wrapper< const CP::Variable > > variables;
1024 CP::LinearExpression sumVariables(0);
1025 for ( size_t i = 0; i <= sequentialActivities[scopeOwningData].size(); i++ ) {
1026 if ( scopeOwningData == node ) {
1027 // use first entry data state
1028 variables.emplace_back(
1029 model.addVariable( CP::Variable::Type::BOOLEAN, std::string("y^{entry,") + std::to_string(i) + "," + scopeOwningData->id + "}_{" + identifier(reference) + "}", (double)(i == 0), (double)(i == 0) )
1030 );
1031 }
1032 else {
1033 if ( auto activity = node->represents<BPMN::Activity>() ) {
1034 // for each activity, the entry data state is a decision (with constraints)
1035 if ( activity->incoming.size() > 1 ) {
1036 throw std::runtime_error("CPController: implicit join for activity '" + activity->id + "'");
1037 }
1038 else if ( activity->incoming.empty() && !activity->parent->represents<BPMNOS::Model::SequentialAdHocSubProcess>() ) {
1039 throw std::runtime_error("CPController: activitiy '" + activity->id + "' has no incoming sequence flow");
1040 }
1041
1042 if ( activity->incoming.size() == 1 ) {
1043 if ( !activity->parent->represents<BPMNOS::Model::SequentialAdHocSubProcess>() ) {
1044 variables.emplace_back(
1045 model.addBinaryVariable( std::string("y^{entry,") + std::to_string(i) + "," + scopeOwningData->id + "}_{" + identifier(reference) + "}" )
1046 );
1047 }
1048 // ensure that first i entry data states are smaller or equal first i exit data states of predecessor
1049 auto& predecessor = activity->incoming.front()->source;
1050 CP::LinearExpression sumFirstExitDataStatesOfPredecessor(0);
1051 CP::LinearExpression sumFirstEntryDataStatesOfActivity(0);
1052 for ( size_t j = 0; j <= i ; j++ ) {
1053 sumFirstExitDataStatesOfPredecessor += exitDataState.at(NodeReference{instance,predecessor}).at(scopeReference)[j].get();
1054 sumFirstEntryDataStatesOfActivity += variables[j].get();
1055 }
1056 model.addConstraint( sumFirstEntryDataStatesOfActivity <= sumFirstExitDataStatesOfPredecessor );
1057 }
1058
1059 }
1060 else if ( auto startEvent = node->represents<BPMN::UntypedStartEvent>() ) {
1061 // for each untyped start event, the entry data state must be the same as for its parent
1062 auto& parentEntryDataState = entryDataState.at(NodeReference{instance,startEvent->parent}).at( scopeReference )[i].get();
1063 variables.emplace_back(
1064 model.addVariable( CP::Variable::Type::BOOLEAN, std::string("y^{entry,") + std::to_string(i) + "," + scopeOwningData->id + "}_{" + identifier(reference) + "}", parentEntryDataState )
1065 );
1066 }
1067 else if ( auto flowNode = node->represents<BPMN::FlowNode>() ) {
1068 assert( flowNode->incoming.size() );
1069 assert(!node->represents<BPMN::Activity>());
1070
1071 if ( flowNode->incoming.size() == 1 ) {
1072 // entry data states must be the same as exit state of predecessor
1073 auto& predecessor = flowNode->incoming.front()->source;
1074 auto& predecessorExitDataState = exitDataState.at(NodeReference{instance,predecessor}).at( scopeReference )[i].get();
1075 variables.emplace_back(
1076 model.addVariable( CP::Variable::Type::BOOLEAN, std::string("y^{entry,") + std::to_string(i) + "," + scopeOwningData->id + "}_{" + identifier(reference) + "}", predecessorExitDataState )
1077 );
1078 }
1079 else if ( node->represents<BPMN::ExclusiveGateway>() ) {
1080 assert(!"Exclusive join not yet supported");
1081 }
1082 else {
1083 assert(!"Non-exclusive join not yet supported");
1084 }
1085 }
1086 }
1087 sumVariables += variables.back().get();
1088 }
1089 entryDataState[reference].emplace( scopeReference, std::move(variables) );
1090
1091 // if and only if the node is visited, exactly one of the variables must be true
1092 model.addConstraint( sumVariables == nodeVariables.at(reference) );
1093
1094 }
1095
1096}
1097
1098void CPController::addChildren(ScopeReference reference) {
1099 auto& [instance,scope] = reference;
1100 if ( scope->startNodes.empty() ) {
1101 // no flow nodes within scope
1102 return;
1103 }
1104 else if ( scope->startNodes.size() > 1 ) {
1105 throw std::runtime_error("CPController: multiple start nodes for scope '" + scope->id + "'");
1106 }
1107 pendingEntry.insert(NodeReference{instance,scope->startNodes.front()});
1108
1109 for ( auto eventSubProcess : scope->eventSubProcesses ) {
1110 assert(!"Event-subprocesses not yet supported");
1111 }
1112
1113}
1114
1115void CPController::addSuccessors(NodeReference reference) {
1116 auto& [instance,node] = reference;
1117 assert(node->represents<BPMN::FlowNode>());
1118 auto flowNode = node->as<BPMN::FlowNode>();
1119 if ( flowNode->outgoing.size() == 1 || node->represents<BPMN::ParallelGateway>() ) {
1120 for ( auto sequenceFlow : flowNode->outgoing ) {
1121 auto& successor = sequenceFlow->target;
1122 // token traverses sequence flow when node is visited
1123 sequenceFlowVariables.emplace(
1124 SequenceFlowReference{instance, sequenceFlow},
1125 model.addVariable(
1126 CP::Variable::Type::BOOLEAN,
1127 std::string("x^sequence_{") + identifier(reference) + "," + successor->id + "}",
1128 nodeVariables.at(reference)
1129 )
1130 );
1131 pendingEntry.insert(NodeReference{instance,successor});
1132 }
1133 }
1134 else if ( node->represents<BPMN::ExclusiveGateway>() || node->represents<BPMN::InclusiveGateway>() ) {
1135 assert(!"Exclusive and inclusive fork not yet supported");
1136 }
1137 else if ( node->represents<BPMN::EventBasedGateway>() ) {
1138 assert(!"Event-based gateways not yet supported");
1139 }
1140}
1141
1142void CPController::addExitVariables(NodeReference reference) {
1143 auto& [instance,node] = reference;
1144 auto extensionElements = node->extensionElements->as<BPMNOS::Model::ExtensionElements>();
1145
1146 createExitDataStateVariables(reference);
1147
1148 // create exit data variables
1149 auto dataVariables = std::vector<AttributeVariables>();
1150 for ( auto& [name,attribute] : extensionElements->attributeRegistry.dataAttributes ) {
1151 auto scopeOwningData = dataOwner.at(attribute);
1152 assert( attribute->index == dataVariables.size() );
1153
1154
1155 auto scopeReference = ScopeReference{instance,dataOwner.at(attribute)};
1156 auto it = exitDataState[reference].find(scopeReference);
1157 if ( it != exitDataState.at(reference).end() ) {
1158 auto& exitDataStateVariables = it->second;
1159 CP::Cases defined_cases;
1160 CP::Cases value_cases;
1161 for ( size_t i = 0; i < exitDataStateVariables.size(); i++ ) {
1162 auto& [defined,value] = dataState[DataReference{instance,attribute}][i];
1163 defined_cases.emplace_back( exitDataStateVariables[i], defined );
1164 value_cases.emplace_back( exitDataStateVariables[i], value );
1165 }
1166 dataVariables.emplace_back(
1167 model.addVariable(CP::Variable::Type::BOOLEAN, "defined^exit_[" + identifier(reference) + "," + name+ "}", CP::n_ary_if(defined_cases,0) ),
1168 model.addVariable(CP::Variable::Type::REAL, "value^exit_{" + identifier(reference) + "," + name+ "}", CP::n_ary_if(value_cases,0))
1169 );
1170 }
1171 }
1172 exitData.emplace( reference , std::move(dataVariables) );
1173
1174 // create exit status variables
1175 // TODO
1176 auto statusVariables = std::vector<AttributeVariables>();
1177 for ( auto& [name,attribute] : extensionElements->attributeRegistry.statusAttributes ) {
1178 assert( attribute->index == statusVariables.size() );
1179 if ( auto scope = node->represents<BPMN::Scope>() ) {
1180 // create exit status variables
1181 if ( scope->flowNodes.empty() ) {
1182 // exit status is the same as entry status
1183 statusVariables.emplace_back(
1184 model.addVariable(CP::Variable::Type::BOOLEAN, "defined^exit_[" + identifier(reference) + "," + name+ "}", entryStatus.at(reference)[attribute->index].defined ),
1185 model.addVariable(CP::Variable::Type::REAL, "value^exit_{" + identifier(reference) + "," + name+ "}", entryStatus.at(reference)[attribute->index].value )
1186 );
1187 }
1188 else {
1189 // assume an exclusive end node
1190 }
1191 }
1192 else if ( auto event = node->represents<BPMN::CatchEvent>() ) {
1193 }
1194 else if ( auto gateway = node->represents<BPMN::Gateway>();
1195 gateway && gateway->incoming.size() > 1
1196 ) {
1197 }
1198 else if ( auto task = node->represents<BPMN::Task>() ) {
1199 }
1200 else {
1201 }
1202 }
1203}
1204
1205
1206void CPController::createExitDataStateVariables(NodeReference reference) {
1207 auto& [instance,node] = reference;
1208 for ( auto scopeOwningData : scopesOwningData.at(reference) ) {
1209 std::vector< std::reference_wrapper< const CP::Variable > > variables;
1210 CP::LinearExpression sumVariables(0);
1211 for ( size_t i = 0; i <= sequentialActivities[scopeOwningData].size(); i++ ) {
1212 // TODO
1213 if ( auto activity = node->represents<BPMN::Activity>() ) {
1214 if ( auto adhoc = activity->parent->represents<BPMNOS::Model::SequentialAdHocSubProcess>();
1215 adhoc && adhoc->performer == scopeOwningData
1216 ) {
1217 // TODO: exit data state must be exactly one higher than entry data state of each child
1218 }
1219 else {
1220 // for all other activities, the exit data state is a decision (with constraints)
1221 variables.emplace_back(
1222 model.addBinaryVariable( std::string("y^{exit_{") + std::to_string(i) + "," + scopeOwningData->id + "}_{" + identifier(reference) + "}" )
1223 );
1224
1225 if ( auto scope = node->represents<BPMN::Scope>() ) {
1226 constrainExitDataStateVariables(ScopeReference{instance,scope});
1227 }
1228 }
1229 }
1230 else if ( auto flowNode = node->represents<BPMN::CatchEvent>() ) {
1231 // TODO: exit data state must be higher than exit data state of each child
1232 }
1233 else if ( auto process = node->represents<BPMN::Process>() ) {
1234 // the exit data state is a decision (with constraints)
1235 variables.emplace_back(
1236 model.addBinaryVariable( std::string("y^{exit,") + std::to_string(i) + "," + scopeOwningData->id + "}_{" + identifier(reference) + "}" )
1237 );
1238 constrainExitDataStateVariables(ScopeReference{instance,process});
1239 }
1240 else {
1241 // TODO: exit data state must be the same as entry data state
1242 }
1243 sumVariables += variables.back().get();
1244
1245 // ensure that first i exit data states are smaller or equal first i entry data states
1246 CP::LinearExpression sumFirstEntryDataStates(0);
1247 CP::LinearExpression sumFirstExitDataStates(0);
1248 for ( size_t j = 0; j <= i ; j++ ) {
1249 sumFirstEntryDataStates += entryDataState.at(reference).at(ScopeReference{instance,scopeOwningData})[j].get();
1250 sumFirstExitDataStates += variables[j].get();
1251 }
1252 model.addConstraint( sumFirstExitDataStates <= sumFirstEntryDataStates );
1253 }
1254 exitDataState[reference].emplace( ScopeReference{instance,scopeOwningData}, std::move(variables) );
1255 // if and only if the node is visited, exactly one of the variables must be true
1256 model.addConstraint( sumVariables == nodeVariables.at(reference) );
1257 }
1258}
1259
1260void CPController::constrainExitDataStateVariables(ScopeReference reference) {
1261 // TODO: exit data state of scope must be higher or equal than exit data state of each descendant
1262}
1263
1264void CPController::createSequenceFlowTraversalVariables(SequenceFlowReference reference) {
1265}
1266*/
1267
1268/// TODO: ensure that the highest entry data state remains smaller than the sum of sequential activities beeing used
1269/// TODO: ensure that the highest exit data state remains smaller or equal than the sum of sequential activities beeing used
1270/// TODO: add constraint that the the sum of entry data state variables for each sequential activity and each data state is one
1271
std::vector< std::reference_wrapper< const Vertex > > getReachableVertices(const Vertex &initialVertex)
std::unordered_map< const Vertex *, const CP::Variable & > visit
Variables holding sequence positions for all vertices.
std::unordered_map< const Vertex *, const CP::Variable & > globalIndex
Variables representing global attributes after i-th modification.
void createEntryVariables(const Vertex &vertex)
void connect(Mediator *mediator)
std::unordered_map< const Vertex *, const CP::Variable & > position
Container of all vertices catching a message.
void createGlobalVariables()
Method creating the constraint program.
std::shared_ptr< Event > dispatchEvent(const SystemState *systemState)
const BPMNOS::Model::Scenario * scenario
std::unordered_map< std::pair< const Vertex *, const Vertex * >, std::vector< AttributeVariables >, pair_hash > statusFlow
Variables representing status attributes of a vertex.
void createEntryStatus(const Vertex &vertex)
std::unordered_map< const Vertex *, std::vector< CP::reference_vector< const CP::Variable > > > dataIndex
Variables representing data attributes owned by an entry vertex after i-th modification.
void createGlobalIndexVariable(const Vertex &vertex)
std::unordered_map< const Vertex *, std::vector< AttributeVariables > > status
Variables representing an index representing the state of the data attributes for each data owner.
std::unordered_map< const Vertex *, std::vector< IndexedAttributeVariables > > data
Variables representing an index representing the state of the global attributes.
void createDataIndexVariables(const Vertex &vertex)
CPController(const BPMNOS::Model::Scenario *scenario)
void createVertexVariables(const Vertex &vertex)
void createMessageContent(const Vertex &vertex)
std::vector< IndexedAttributeVariables > globals
Variables indicating whether the a token flows from one vertex to another.
std::vector< const Vertex * > messageRecipients
Container of all vertices considered.
std::vector< AttributeVariables > createMergedStatus(const Vertex &vertex, const BPMNOS::Model::AttributeRegistry &attributeRegistry, std::vector< std::pair< const CP::Variable &, std::vector< AttributeVariables > & > > inputs)
void createExitVariables(const Vertex &vertex)
std::unordered_map< const Vertex *, std::vector< std::tuple< std::string_view, size_t, AttributeVariables > > > messageContent
Variables representing status attributes flowing from one vertex to another.
void createSequenceConstraints(const Vertex &vertex)
std::unordered_map< std::pair< const Vertex *, const Vertex * >, const CP::Variable &, pair_hash > messageFlow
Variables indicating whether the a token flows from one vertex to another.
std::vector< AttributeVariables > createAlternativeEntryStatus(const Vertex &vertex, const BPMNOS::Model::AttributeRegistry &attributeRegistry, std::vector< std::pair< const CP::Variable &, std::vector< AttributeVariables > & > > alternatives)
void initializeVertices(const Vertex &initialVertex)
Returns a topologically sorted vector of all vertices reachable from the given vertex.
std::unordered_map< std::pair< const Vertex *, const Vertex * >, const CP::Variable &, pair_hash > tokenFlow
Variables indicating whether the a token enters or leaves a vertex.
void createDataVariables(const Vertex &vertex)
void createStatus(const Vertex &vertex)
void createExitStatus(const Vertex &vertex)
const FlattenedGraph flattenedGraph
std::vector< const Vertex * > vertices
virtual void connect(Mediator *mediator)
Definition Controller.cpp:9
std::optional< std::pair< Vertex &, Vertex & > > parent
std::vector< std::pair< const BPMN::SequenceFlow *, Vertex & > > inflows
Parent vertices.
std::vector< std::reference_wrapper< Vertex > > successors
Container holding predecessors according to the execution logic (excl. sequence flows)
std::vector< std::reference_wrapper< Vertex > > dataOwners
Container holding all possible vertices receiving a message (or the message delivery notfication for ...
bool entry() const
Returns a unique reference of the vertex.
std::vector< std::pair< const BPMN::SequenceFlow *, Vertex & > > outflows
Container holding vertices connecting by an incoming sequence flow.
std::string reference() const
Returns the vertices of the owner of a data attribute.
std::vector< std::reference_wrapper< Vertex > > predecessors
Container holding vertices connecting by an outgoing sequence flow.
std::vector< std::reference_wrapper< Vertex > > senders
Container holding successors according to the execution logic (excl. sequence flows)
Represents a graph containing all BPMN nodes that may receive a token during execution of a scenario.
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::vector< std::reference_wrapper< Vertex > > initialVertices
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...
A class representing the state that the execution or simulation of a given scenario is in.
Definition SystemState.h:21
std::map< std::string, Attribute * > globalAttributes
std::map< std::string, Attribute * > statusAttributes
Class representing a task in which one or more choices have to be made.
Class holding extension elements representing execution data for nodes.
AttributeRegistry attributeRegistry
Registry allowing to look up all status and data attributes by their names.
Definition Model.h:68
The Scenario class holds data for all BPMN instances.
Definition Scenario.h:20
std::optional< BPMNOS::number > getKnownValue(const Scenario::InstanceData *instance, const BPMNOS::Model::Attribute *attribute, const BPMNOS::number currentTime) const
Method returning a known value of an attribute.
Definition Scenario.cpp:194
const Model * model
Pointer to the BPMN model.
Definition Scenario.h:182
BPMNOS::Values globals
Definition Scenario.h:181
std::unique_ptr< ExtensionElements > extensionElements
Definition bpmn++.h:16299
std::string id
Id of element.
Definition bpmn++.h:16298
T * represents()
Attempts to cast the element to the specified type T.
Definition bpmn++.h:16236
Base class for BPMN elements that may contain incoming and outgoing sequence flows.
Definition bpmn++.h:16670
Base class for BPMN elements that may contain a ChildNode elements.
Definition bpmn++.h:16510
Base class for all start events with an event definition.
Definition bpmn++.h:16856
std::string to_string(number numericValue, const ValueType &type)
Converts a number to a string.
Definition Number.cpp:150
@ STRING
Definition Value.h:9
Represents an abstract base class for a class that is an event listener and notifier.
Definition Mediator.h:13