BPMN-OS
BPMN for optimization and simulation
Loading...
Searching...
No Matches
CPModel.cpp
Go to the documentation of this file.
1#ifdef USE_CP
2
3#include "CPModel.h"
11#include <cp/limex_handle.h>
12#include <iostream>
13
14using namespace BPMNOS::Execution;
15
16CPModel::CPModel(const BPMNOS::Execution::FlattenedGraph* flattenedGraph, Config config)
17 : scenario(flattenedGraph->scenario)
18 , config(std::move(config))
19 , flattenedGraph(flattenedGraph)
20 , model(CP::Model::ObjectiveSense::MAXIMIZE)
21{
22 if ( !dynamic_cast<const BPMNOS::Model::StaticScenario*>(scenario) ) {
23 throw std::runtime_error("CPModel: scenario must be static");
24 }
25 if ( this->config.instantEntry ) {
26 throw std::runtime_error("CPModel: instant entry is not supported");
27 }
28 if ( !this->config.instantChoices ) {
29 throw std::runtime_error("CPModel: non-instant choices are not supported");
30 }
31 if ( !this->config.instantExit ) {
32 throw std::runtime_error("CPModel: non-instant exit is not supported");
33 }
34//std::cerr << "Flattened graph: " << flattenedGraph->jsonify().dump() << std::endl;
35 // Set collection lookup on model (caller responsible for bounds)
36 model.setCollectionLookup(
37 [](size_t key) -> const std::vector<double>& {
38 return collectionRegistry[key];
39 },
41 );
42
43 // add callables for lookup tables
44 for ( auto& lookupTable : scenario->model->lookupTables ) {
45 limexHandle.add(
46 lookupTable->name,
47 [&lookupTable](const std::vector<CP::Expression>& args) -> CP::Expression
48 {
49 std::vector<CP::Operand> operands = { CP::Expression::getCustomIndex(lookupTable->name) };
50 operands.insert(operands.end(), args.begin(), args.end());
51 return CP::Expression(CP::Expression::Operator::custom, std::move(operands));
52 }
53 );
54 }
55
56//std::cerr << "Limex handles: ";
57//for ( auto name : limexHandle.getNames() ) {
58// std::cerr << name << ", ";
59//}
60//std::cerr << std::endl;
61
62 createCP();
63}
64
65std::optional< BPMN::Activity::LoopCharacteristics> CPModel::getLoopCharacteristics(const Vertex* vertex) const {
66 auto activity = vertex->node->represents<BPMN::Activity>();
67 if ( !activity ) {
68 return std::nullopt;
69 }
70 return activity->loopCharacteristics;
71}
72
73
74void CPModel::createCP() {
75 vertices = flattenedGraph->sortVertices();
76
77//std::cerr << "create sequence position variables" << std::endl;
78 // create sequence position variables for all vertices
79 auto& sequence = model.addSequence( "position", vertices.size() );
80 for ( size_t i = 0; i < vertices.size(); i++ ) {
81 position.emplace(vertices[i], sequence.variables[i]);
82 indexMap.emplace(vertices[i], i);
83 }
84
85//std::cerr << "createMessageFlowVariables" << std::endl;
86 createMessageFlowVariables();
87
88//std::cerr << "createGlobalVariables" << std::endl;
89 createGlobalVariables();
90
91//std::cerr << "createVertexVariables:" << flattenedGraph->vertices.size() << std::endl;
92 // create vertex and message variables
93 for ( auto vertex : vertices ) {
94 createVertexVariables(vertex);
95 }
96
97//std::cerr << "constrainGlobalVariables" << std::endl;
98 constrainGlobalVariables();
99
100 for ( auto vertex : vertices ) {
101 if ( vertex->entry<BPMN::Scope>() ) {
102//std::cerr << "constrainDataVariables " << vertex->reference() << std::endl;
103 constrainDataVariables(vertex);
104 }
105 if ( vertex->exit<BPMN::EventBasedGateway>() ) {
106//std::cerr << "constrainEventBasedGateway " << vertex->reference() << std::endl;
107 constrainEventBasedGateway(vertex);
108 }
109 if ( vertex->exit<BPMN::TypedStartEvent>() ) {
110 constrainTypedStartEvent(vertex);
111 }
112 }
113
114//std::cerr << "constrainSequentialActivities" << std::endl;
115 constrainSequentialActivities();
116
117//std::cerr << "createMessagingConstraints" << std::endl;
118 createMessagingConstraints();
119//std::cerr << "Done" << std::endl;
120//std::cerr << model.stringify() << std::endl;
121}
122
123void CPModel::createGlobalVariables() {
124 for ( auto attribute : scenario->model->attributeRegistry.globalAttributes ) {
125 assert( attribute->index == globals.size() ); // ensure that the order of attributes is correct
126 globals.emplace_back(
127 model.addIndexedVariables(CP::Variable::Type::BOOLEAN, "defined_" + attribute->id ),
128 model.addIndexedVariables(CP::Variable::Type::REAL, "value_" + attribute->id)
129 );
130 auto& [defined,value] = globals.back();
131 // add variables holding initial values
132 auto& initialValue = scenario->globals[attribute->index];
133 if ( initialValue.has_value() ) {
134 // defined initial value
135 model.addIndexedVariable(defined, true, true);
136 model.addIndexedVariable(value, (double)initialValue.value(), (double)initialValue.value());
137 }
138 else {
139 // undefined initial value
140 model.addIndexedVariable(defined, false, false);
141 model.addIndexedVariable(value, 0.0, 0.0);
142 }
143
144 if ( attribute->isImmutable ) {
145 // deduced variables
146 for ( [[maybe_unused]] auto _ : flattenedGraph->globalModifiers ) {
147 // use initial value for all data states
148 model.addIndexedVariable(defined, defined[0]);
149 model.addIndexedVariable(value, value[0]);
150 }
151 }
152 else {
153 for ( [[maybe_unused]] auto _ : flattenedGraph->globalModifiers ) {
154 // unconstrained variables for all data states
155 model.addIndexedVariable(defined);
156 model.addIndexedVariable(value);
157 }
158 }
159 addToObjective( attribute, value[ value.size() -1 ] );
160 }
161}
162
163void CPModel::createMessageFlowVariables() {
164 for ( auto& vertex : flattenedGraph->vertices ) {
165 if ( vertex->exit<BPMN::MessageCatchEvent>() ) {
166 messageRecipients.push_back(vertex.get());
167 CP::reference_vector<const CP::Variable> messages;
168 for ( auto sender : vertex->senders ) {
169 assert( sender->entry<BPMN::MessageThrowEvent>() );
170 // create binary decision variable for a message from sender to recipient
171 messages.emplace_back( model.addBinaryVariable("message_{" + sender->reference() + " → " + vertex->reference() + "}" ) );
172//std::cerr << messages.back().get().stringify() << std::endl;
173 messageFlow.emplace( std::make_pair(sender,vertex.get()), messages.back() );
174 }
175 }
176 if ( vertex->entry<BPMN::MessageThrowEvent>() ) {
177 messageSenders.push_back(vertex.get());
178 }
179 }
180}
181
182void CPModel::createMessagingConstraints() {
183 for ( auto recipient : messageRecipients ) {
184 assert( recipient->exit<BPMN::MessageCatchEvent>() );
185 CP::Expression messagesDelivered(0);
186
187 for ( auto sender : recipient->senders ) {
188 assert( sender->entry<BPMN::MessageThrowEvent>() );
189
190 auto& message = messageFlow.at({sender,recipient});
191 messagesDelivered = messagesDelivered + message;
192
193 model.addConstraint( message <= visit.at(recipient) );
194 model.addConstraint( message <= visit.at(sender) );
195
196 model.addConstraint( message.implies( position.at(sender) < position.at(recipient) ) );
197
198 // this should not be necessary as status variables are respectively deduced
199 model.addConstraint(
200 // if a message is sent from a sender to a recipient, the recipient's timestamp must not
201 // be before the sender's timestamp
202 message.implies (
204 <= status.at(recipient)[BPMNOS::Model::ExtensionElements::Index::Timestamp].value
205 )
206 );
207
208 if ( sender->node->represents<BPMN::SendTask>() ) {
209 // exit at send task must be after message delivery
210 auto& recipientStatus =
211 ( recipient->node->represents<BPMN::ReceiveTask>() || recipient->node->represents<BPMN::MessageStartEvent>() ) ?
212 std::get<0>(locals.at(recipient)[0]) :
213 status.at(recipient)
214 ;
215 model.addConstraint(
216 message.implies (
217 status.at(exit(sender))[BPMNOS::Model::ExtensionElements::Index::Timestamp].value
218 >=
220 )
221 );
222 }
223
224 // add message header constraints
225 assert( messageHeader.contains(entry(recipient)) );
226 assert( messageHeader.contains(sender) );
227 auto& recipientHeader = messageHeader.at(entry(recipient));
228 auto& senderHeader = messageHeader.at(sender);
229 for ( auto& [key, recipientHeaderVariables] : recipientHeader ) {
230 if ( !senderHeader.contains(key) ) {
231 throw std::runtime_error("CPModel: illegal message flow from '" + sender->node->id + "' to '" + recipient->node->id + "'");
232 }
233 model.addConstraint(
234 ( message && recipientHeaderVariables.defined && senderHeader.at(key).defined ).implies (
235 recipientHeaderVariables.value == senderHeader.at(key).value
236 )
237 );
238 }
239
240
241 // add message content constraints
242 auto& recipientContent = messageContent.at(recipient);
243 auto& senderContent = messageContent.at(sender);
244 for ( auto& [key, recipientContentVariables] : recipientContent ) {
245 if ( !senderContent.contains(key) ) {
246 throw std::runtime_error("CPModel: illegal message flow from '" + sender->node->id + "' to '" + recipient->node->id + "'");
247 }
248 model.addConstraint( message.implies ( recipientContentVariables.defined == senderContent.at(key).defined ) );
249 model.addConstraint( message.implies ( recipientContentVariables.value == senderContent.at(key).value ) );
250 }
251 }
252
253 // every visited message catch event must receive exactly one message
254 model.addConstraint( visit.at(recipient) == messagesDelivered );
255 }
256
257 for ( auto sender : messageSenders ) {
258 assert( sender->entry<BPMN::MessageThrowEvent>() );
259 CP::Expression messagesDelivered(0);
260 for ( auto recipient : sender->recipients ) {
261 assert( recipient->exit<BPMN::MessageCatchEvent>() );
262 auto& message = messageFlow.at({sender,recipient});
263 messagesDelivered = messagesDelivered + message;
264 }
265 if ( sender->entry<BPMN::SendTask>() ) {
266 // a message thrown at a send task must be delivered to at exactly one recipient
267 model.addConstraint( visit.at(sender) == messagesDelivered );
268 }
269 else {
270 // a message thrown at other message throw events can be delivered to at most one recipient
271 model.addConstraint( visit.at(sender) >= messagesDelivered );
272 }
273 }
274}
275
276void CPModel::createMessageHeader(const Vertex* vertex) {
277 auto extensionElements = vertex->node->extensionElements->as<BPMNOS::Model::ExtensionElements>();
278 assert( extensionElements );
279
280 if ( extensionElements->messageDefinitions.size() > 1 ) {
281 assert(!"Not yet implemented");
282 }
283
284 auto messageDefinition = extensionElements->messageDefinitions.size() == 1 ? extensionElements->messageDefinitions[0].get() : nullptr;
285 assert( messageDefinition );
286 auto& messageHeaderVariables = messageHeader[vertex];
287
288 for (size_t i = 0; i < messageDefinition->header.size(); i++ ) {
290 // skip message name
291 continue;
292 }
293 auto& header = messageDefinition->header[i];
294 if (
297 ) {
298 // set sender or recipient instance
299 messageHeaderVariables.emplace(
300 header,
301 AttributeVariables{
302 model.addVariable(CP::Variable::Type::BOOLEAN, "header_defined_{" + vertex->shortReference() + "}," + header, true ),
303 model.addVariable(CP::Variable::Type::REAL, "header_value_{" + vertex->shortReference() + "}," + header, (double)vertex->instanceId )
304 }
305 );
306 continue;
307 }
308
309//std::cerr << "header: " << header << "/" << messageDefinition->parameterMap.contains(header) << std::endl;
310 if ( messageDefinition->parameterMap.contains(header) ) {
311 const BPMNOS::Model::Attribute* attribute =
312 messageDefinition->parameterMap.at(header)->expression ?
313 messageDefinition->parameterMap.at(header)->expression->isAttribute() :
314 nullptr
315 ;
316 if ( attribute ) {
317 auto [defined,value] = getAttributeVariables(vertex, attribute);
318 messageHeaderVariables.emplace(
319 header,
320 AttributeVariables{
321 model.addVariable(CP::Variable::Type::BOOLEAN, "header_defined_{" + vertex->shortReference() + "}," + header, defined ),
322 model.addVariable(CP::Variable::Type::REAL, "header_value_{" + vertex->shortReference() + "}," + header, value )
323 }
324 );
325 continue;
326 }
327
328 if ( messageDefinition->parameterMap.at(header)->expression ) {
329 auto headerValue = createExpression( vertex, *messageDefinition->parameterMap.at(header)->expression );
330 messageHeaderVariables.emplace(
331 header,
332 AttributeVariables{
333 model.addVariable(CP::Variable::Type::BOOLEAN, "header_defined_{" + vertex->shortReference() + "}," + header, visit.at(vertex) ),
334 model.addVariable(CP::Variable::Type::REAL, "header_value_{" + vertex->shortReference() + "}," + header, visit.at(vertex) * headerValue )
335 }
336 );
337 continue;
338 }
339 }
340 else {
341 messageHeaderVariables.emplace(
342 header,
343 AttributeVariables{
344 model.addVariable(CP::Variable::Type::BOOLEAN, "header_defined_{" + vertex->shortReference() + "}," + header, false ),
345 model.addVariable(CP::Variable::Type::REAL, "header_value_{" + vertex->shortReference() + "}," + header, 0.0 )
346 }
347 );
348 }
349 }
350}
351
352void CPModel::createMessageContent(const Vertex* vertex) {
353 auto extensionElements = vertex->node->extensionElements->as<BPMNOS::Model::ExtensionElements>();
354
355 if ( extensionElements->messageDefinitions.size() > 1 ) {
356 assert(!"Not yet implemented");
357 }
358
359 auto messageDefinition = extensionElements->messageDefinitions.size() == 1 ? extensionElements->messageDefinitions[0].get() : nullptr;
360 assert( messageDefinition );
361 auto& messageContentVariables = messageContent[vertex];
362
363 if ( vertex->entry<BPMN::MessageThrowEvent>() ) {
364 // deduce message content variables from status
365 for (auto& [key,content] : messageDefinition->contentMap ) {
366 auto [defined,value] = getAttributeVariables(vertex, content->attribute);
367 messageContentVariables.emplace(
368 content->key,
369 AttributeVariables{
370 model.addVariable(CP::Variable::Type::BOOLEAN, "content_defined_{" + vertex->shortReference() + "}," + content->key, defined ),
371 model.addVariable(CP::Variable::Type::REAL, "content_value_{" + vertex->shortReference() + "}," + content->key, value )
372 }
373 );
374 }
375 }
376 else {
377 // create variables for the message content (relevant constraints must be added separately)
378 for (auto& [key,content] : messageDefinition->contentMap ) {
379 messageContentVariables.emplace(
380 content->key,
381 AttributeVariables{
382 model.addBinaryVariable("content_defined_{" + vertex->shortReference() + "}," + content->key ),
383 model.addRealVariable("content_value_{" + vertex->shortReference() + "}," + content->key )
384 }
385 );
386 }
387 }
388}
389
390void CPModel::createDataVariables(const FlattenedGraph::Vertex* vertex) {
391 auto extensionElements = vertex->node->extensionElements->as<BPMNOS::Model::ExtensionElements>();
392 for ( auto& attribute : extensionElements->data ) {
393 IndexedAttributeVariables variables(
394 model.addIndexedVariables(CP::Variable::Type::BOOLEAN, "defined_{" + vertex->shortReference() + "}," + attribute->id ),
395 model.addIndexedVariables(CP::Variable::Type::REAL, "value_{" + vertex->shortReference() + "}," + attribute->id )
396 );
397 // add variables holding initial values
398 auto given = scenario->getKnownValue(vertex->rootId, attribute.get(), scenario->getEarliestInstantiationTime());
399 if ( given.has_value() ) {
400 // defined initial value
401 model.addIndexedVariable(variables.defined, visit.at(vertex));
402 model.addIndexedVariable(variables.value, CP::if_then_else( visit.at(vertex), (double)given.value(), 0.0));
403 }
404 else if ( attribute->expression ) {
405 // initial assignment
406 assert( attribute->expression->type == Model::Expression::Type::ASSIGN );
407 CP::Expression assignment = createExpression( vertex, *attribute->expression );
408 model.addIndexedVariable(variables.defined, visit.at(vertex));
409 model.addIndexedVariable(variables.value, CP::if_then_else( visit.at(vertex), assignment, 0.0));
410 }
411 else {
412 // undefined initial value
413 model.addIndexedVariable(variables.defined, false, false);
414 model.addIndexedVariable(variables.value, 0.0, 0.0);
415 }
416
417 assert( flattenedGraph->dataModifiers.contains(vertex) );
418 if ( attribute->isImmutable ) {
419 // deducible variables
420 for ( [[maybe_unused]] auto _ : flattenedGraph->dataModifiers.at(vertex) ) {
421 // use initial value for all data states
422 model.addIndexedVariable(variables.defined, variables.defined[0]);
423 model.addIndexedVariable(variables.value, variables.value[0]);
424 }
425 }
426 else {
427 for ( [[maybe_unused]] auto _ : flattenedGraph->dataModifiers.at(vertex) ) {
428 // unconstrained variables for all data states
429 model.addIndexedVariable(variables.defined);
430 model.addIndexedVariable(variables.value);
431 }
432 }
433 addToObjective( attribute.get(), variables.value[ variables.value.size() -1 ] );
434 data.emplace( std::make_pair(vertex, attribute.get()), std::move(variables) );
435 }
436}
437
438void CPModel::constrainDataVariables(const FlattenedGraph::Vertex* vertex) {
439 auto extensionElements = vertex->node->extensionElements->as<BPMNOS::Model::ExtensionElements>();
440 if ( extensionElements->data.empty() ) return;
441
442//std::cerr << "modifiers of " << vertex->reference() << std::endl;
443 auto& dataModifiers = flattenedGraph->dataModifiers.at(vertex);
444 for ( auto& [entry,exit] : dataModifiers ) {
445//std::cerr << "modifier: " << entry.reference() << std::endl;
446 auto& [localStatus,localData,localGlobals] = locals.at(exit).back();
447 for ( unsigned int i = 0; i < dataModifiers.size(); i++ ) {
448 for ( auto& attribute : extensionElements->data ) {
449 if (
450 entry->node->represents<BPMN::TypedStartEvent>() ||
451 entry->node->represents<BPMN::ReceiveTask>() ||
453 ) {
454 // operators are applied after message delivery or choice
455 // ASSUMPTION: exit decision immediately follows message delivery or choice
456 auto& index = dataIndex.at(exit)[ exit->dataOwnerIndex(attribute.get()) ];
457 // if global index at modifier exit equals i then the i-th global item equals the final globals of the modifier
458 model.addConstraint(
459 ( index == i ).implies(
460 data.at({vertex,attribute.get()}).defined[i] == localData[attribute->index].defined
461 )
462 );
463 model.addConstraint(
464 ( index == i ).implies(
465 data.at({vertex,attribute.get()}).value[i] == localData[attribute->index].value
466 )
467 );
468 }
469 else {
470 assert( entry->node->represents<BPMN::Task>() );
471 // operators are applied upon entry
472 auto& index = dataIndex.at(entry)[ entry->dataOwnerIndex(attribute.get()) ];
473 // if data index at modifier entry equals i then the i+1-th data item equals the final data of the modifier
474 model.addConstraint(
475 ( index == i ).implies(
476 data.at({vertex,attribute.get()}).defined[i + 1] == localData[attribute->index].defined
477 )
478 );
479 model.addConstraint(
480 ( index == i ).implies(
481 data.at({vertex,attribute.get()}).value[i + 1] == localData[attribute->index].value
482 )
483 );
484 }
485 }
486 }
487 }
488}
489
490void CPModel::constrainGlobalVariables() {
491 for ( auto& [entry,exit] : flattenedGraph->globalModifiers ) {
492 auto& [localStatus,localData,localGlobals] = locals.at(exit).back();
493 for ( unsigned int i = 0; i < flattenedGraph->globalModifiers.size(); i++ ) {
494 for ( auto attribute : scenario->model->attributeRegistry.globalAttributes ) {
495 if (
496 entry->node->represents<BPMN::TypedStartEvent>() ||
497 entry->node->represents<BPMN::ReceiveTask>() ||
499 ) {
500 // operators are applied after message delivery or choice
501 // ASSUMPTION: exit decision immediately follows message delivery or choice
502 // if global index at modifier exit equals i then the i-th global item equals the final globals of the modifier
503 model.addConstraint(
504 ( globalIndex.at(exit) == i && visit.at(exit) ).implies(
505 globals[attribute->index].defined[i] == localGlobals[attribute->index].defined
506 )
507 );
508 model.addConstraint(
509 ( globalIndex.at(exit) == i && visit.at(exit) ).implies(
510 globals[attribute->index].value[i] == localGlobals[attribute->index].value
511 )
512 );
513 }
514 else {
515 assert( entry->node->represents<BPMN::Task>() );
516 // operators are applied upon entry
517 // if global index at modifier entry equals i then the i+1-th global item equals the final globals of the modifier
518 model.addConstraint(
519 ( globalIndex.at(entry) == i && visit.at(entry) ).implies(
520 globals[attribute->index].defined[i + 1] == localGlobals[attribute->index].defined
521 )
522 );
523 model.addConstraint(
524 ( globalIndex.at(entry) == i && visit.at(entry) ).implies(
525 globals[attribute->index].value[i + 1] == localGlobals[attribute->index].value
526 )
527 );
528 }
529 }
530 }
531 }
532}
533
534void CPModel::constrainEventBasedGateway(const FlattenedGraph::Vertex* gateway) {
535 assert( gateway->node->represents<BPMN::EventBasedGateway>() );
536 // ensure a single outflow and determine all timers
537 CP::Expression outflows(0);
538 std::vector<CP::Expression> timers;
539 for ( auto& [sequenceFlow,event] : gateway->outflows ) {
540 assert( event->node->represents<BPMN::CatchEvent>() );
541 outflows = outflows + tokenFlow.at({gateway,event});
542 if ( event->node->represents<BPMN::TimerCatchEvent>() ) {
543 timers.push_back( createExpression(gateway,*event->node->extensionElements->as<BPMNOS::Model::Timer>()->trigger->expression) );
544 }
545 }
546 model.addConstraint( outflows == visit.at(gateway) );
547
548 // make sure that triggered event does not succeed any timer
549 for ( auto& [sequenceFlow,event] : gateway->outflows ) {
550 for ( auto& timer : timers ) {
551 model.addConstraint(
552 visit.at(event).implies(
553 status.at(exit(event))[BPMNOS::Model::ExtensionElements::Index::Timestamp].value
554 <=
555 timer
556// CP::max(timer,status.at(entry(gateway))[BPMNOS::Model::ExtensionElements::Index::Timestamp].value)
557 )
558 );
559 }
560 }
561}
562
563void CPModel::constrainSequentialActivities() {
564 for ( auto& [performer,sequentialActivities] : flattenedGraph->sequentialActivities ) {
565 for ( size_t i = 0; i + 1 < sequentialActivities.size(); i++ ) {
566 for ( size_t j = i + 1; j < sequentialActivities.size(); j++ ) {
567 auto& [entry1,exit1] = sequentialActivities[i];
568 auto& [entry2,exit2] = sequentialActivities[j];
569 model.addConstraint(
570 ( position.at(entry1) < position.at(entry2)).implies( position.at(exit1) < position.at(entry2) )
571 );
572 }
573 }
574 }
575}
576
577void CPModel::createStatus(const Vertex* vertex) {
578//std::cerr << "createStatus: " << vertex->reference() << std::endl;
579 if ( vertex->type == Vertex::Type::ENTRY ) {
580 createEntryStatus(vertex);
581 }
582 else {
583 createExitStatus(vertex);
584 addObjectiveCoefficients( vertex );
585 }
586
587 // Add constraints for all status variables: defined <= visit and !defined implies value == 0
588 assert( visit.contains(vertex) );
589 assert( status.contains(vertex) );
590 for ( auto& attributeVars : status.at(vertex) ) {
591 model.addConstraint( attributeVars.defined <= visit.at(vertex) );
592 model.addConstraint( (!attributeVars.defined).implies( attributeVars.value == 0.0 ) );
593 }
594}
595
596void CPModel::addAttributes(const Vertex* vertex, std::vector<AttributeVariables>& variables, const BPMNOS::Model::Attribute* loopIndex) {
597 // add new attributes if necessary
598 if ( auto extensionElements = vertex->node->extensionElements->represents<BPMNOS::Model::ExtensionElements>() ) {
599 variables.reserve(extensionElements->attributeRegistry.statusAttributes.size());
600 for ( size_t i = variables.size(); i < extensionElements->attributeRegistry.statusAttributes.size(); i++) {
601 auto attribute = extensionElements->attributeRegistry.statusAttributes[i];
602//std::cerr << "Add: " << attribute->id << std::endl;
603 // add variables holding given values
604 if ( auto given = scenario->getKnownValue(vertex->rootId, attribute, scenario->getEarliestInstantiationTime()); given.has_value() ) {
605 // defined initial value
606 variables.emplace_back(
607 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_{" + vertex->reference() + "}," + attribute->id, visit.at(vertex) ),
608 model.addVariable(CP::Variable::Type::REAL, "value_{" + vertex->reference() + "}," + attribute->id, CP::if_then_else( visit.at(vertex), (double)given.value(), 0.0))
609 );
610 }
611 else if ( attribute == loopIndex ) {
612//std::cerr << ( loopIndex ? loopIndex->id : "XXX") << " - Loop index: " << getLoopIndex(vertex).stringify() << std::endl;
613 // set or increment loop index (initial value is assumed to be undefined, and therefore has a value of zero)
614 variables.emplace_back(
615 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_{" + vertex->reference() + "}," + attribute->id, visit.at(vertex) ),
616 model.addVariable(CP::Variable::Type::REAL,"value_{" + vertex->reference() + "}," + attribute->id, CP::if_then_else( visit.at(vertex), getLoopIndex(vertex), 0 ) )
617 );
618 }
619 else if ( attribute->expression ) {
620 // initial assignment
621 assert( attribute->expression->type == Model::Expression::Type::ASSIGN );
622 CP::Expression assignment = createExpression( vertex, *attribute->expression );
623 variables.emplace_back(
624 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_{" + vertex->reference() + "}," + attribute->id, visit.at(vertex) ),
625 model.addVariable(CP::Variable::Type::REAL, "value_{" + vertex->reference() + "}," + attribute->id, CP::if_then_else( visit.at(vertex), assignment, 0.0) )
626 );
627 }
628 else {
629 // no given value
630 bool defined = ( attribute->index == BPMNOS::Model::ExtensionElements::Index::Timestamp );
631 variables.emplace_back(
632 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_{" + vertex->reference() + "}," + attribute->id, defined, defined ),
633 model.addVariable(CP::Variable::Type::REAL, "value_{" + vertex->reference() + "}," + attribute->id, 0.0, 0.0)
634 );
635 }
636 }
637 }
638}
639
640void CPModel::createEntryStatus(const Vertex* vertex) {
641//std::cerr << "createEntryStatus: " << vertex->reference() << std::endl;
642 status.emplace( vertex, std::vector<AttributeVariables>() );
643 auto& variables = status.at(vertex);
644 auto loopCharacteristics = getLoopCharacteristics(vertex);
645
646 if ( loopCharacteristics.has_value() && !flattenedGraph->dummies.contains(vertex) ) {
647 createLoopEntryStatus(vertex);
648/*
649 if ( loopCharacteristics.value() == BPMN::Activity::LoopCharacteristics::Standard ) {
650 createLoopEntryStatus(vertex);
651//std::cerr << model.stringify() << std::endl;
652 }
653 else {
654// createMultiInstanceEntryStatus(vertex);
655 assert(!"Not yet implemented");
656 }
657*/
658 return;
659 }
660
661 if ( vertex->parent.has_value() ) {
662 assert( vertex->node->represents<BPMN::FlowNode>() );
663 auto scope = vertex->node->as<BPMN::FlowNode>()->parent;
664 assert( scope );
665 auto extensionElements = scope->extensionElements->as<BPMNOS::Model::ExtensionElements>();
666 assert( extensionElements );
667 if ( vertex->entry<BPMN::UntypedStartEvent>() ) {
668 assert( vertex->parent.value().first->node == scope );
669 assert( status.contains(vertex->parent.value().first) );
670 variables = createUniquelyDeducedEntryStatus(vertex, extensionElements->attributeRegistry, status.at(vertex->parent.value().first) );
671 }
672 else if ( vertex->entry<BPMN::TypedStartEvent>() ) {
673 assert( scope->represents<BPMN::EventSubProcess>() );
674 assert( vertex->parent.value().first->node == scope->as<BPMN::EventSubProcess>()->parent );
675 assert( status.contains(vertex->parent.value().first) );
676 // use attribute registry of parent of event-subprocess scope
677 auto& attributeRegistry = scope->as<BPMN::EventSubProcess>()->parent->extensionElements->as<BPMNOS::Model::ExtensionElements>()->attributeRegistry;
678 variables = createUniquelyDeducedEntryStatus(vertex, attributeRegistry, status.at(vertex->parent.value().first) );
679 // attributes defined for event-subprocess are added later
680 }
681 else if ( vertex->entry<BPMN::ExclusiveGateway>() && vertex->inflows.size() > 1 ) {
682 std::vector< std::pair<const CP::Variable&, std::vector<AttributeVariables>& > > alternatives;
683 for ( auto& [sequenceFlow,predecessor] : vertex->inflows ) {
684 // add to alternatives
685 assert( statusFlow.contains({predecessor,vertex}) );
686 alternatives.emplace_back( tokenFlow.at({predecessor,vertex}), statusFlow.at({predecessor,vertex}) );
687 }
688 variables = createAlternativeEntryStatus(vertex, extensionElements->attributeRegistry, std::move(alternatives));
689 }
690 else if ( vertex->entry<BPMN::FlowNode>() && vertex->inflows.size() > 1 ) {
691 assert(vertex->entry<BPMN::ParallelGateway>() || vertex->entry<BPMN::InclusiveGateway>() );
692 assert( vertex->predecessors.size() == 1 );
693 if ( !vertex->entry<BPMN::ParallelGateway>() && !vertex->entry<BPMN::InclusiveGateway>() ) {
694 throw std::runtime_error("CPModel: illegal join at '" + vertex->node->id + "'");
695 }
696 std::vector< std::pair<const CP::Variable&, std::vector<AttributeVariables>& > > inputs;
697 for ( auto& [sequenceFlow,predecessor] : vertex->inflows ) {
698 // add to alternatives
699 inputs.emplace_back( tokenFlow.at({predecessor,vertex}), statusFlow.at({predecessor,vertex}) );
700 }
701 variables = createMergedStatus(vertex, extensionElements->attributeRegistry, std::move(inputs));
702 }
703 else if ( vertex->entry<BPMN::FlowNode>() ) {
704 assert( vertex->inflows.size() == 1 );
705 auto& [sequenceFlow,predecessor] = vertex->inflows.front();
706 if ( sequenceFlow ) {
707 assert( statusFlow.contains({predecessor,vertex}) );
708 variables = createUniquelyDeducedEntryStatus(vertex, extensionElements->attributeRegistry, statusFlow.at({predecessor,vertex}) );
709 }
710 else {
711 assert( vertex->node->represents<BPMN::Activity>() );
713 assert( status.contains(predecessor) );
714 variables = createUniquelyDeducedEntryStatus(vertex, extensionElements->attributeRegistry, status.at(predecessor) );
715 }
716 }
717 }
718
719 if (
720 flattenedGraph->dummies.contains(vertex) &&
721 loopCharacteristics.has_value() &&
722 loopCharacteristics.value() != BPMN::Activity::LoopCharacteristics::Standard
723 ) {
724 // for multi-instance activities, attributes are only added to vertices representing and instantiation
725 return;
726 }
727 // add new attributes where applicable
728 addAttributes(vertex,variables);
729}
730
731void CPModel::createExitStatus(const Vertex* vertex) {
732//std::cerr << "createExitStatus" << std::endl;
733 auto extensionElements = vertex->node->extensionElements->represents<BPMNOS::Model::ExtensionElements>();
734
735 if ( auto loopCharacteristics = getLoopCharacteristics(vertex);
736 loopCharacteristics.has_value() &&
737 flattenedGraph->dummies.contains(vertex)
738 ) {
739 if ( loopCharacteristics.value() == BPMN::Activity::LoopCharacteristics::Standard ) {
740 // use final loop exit status
741 status.emplace( vertex, createLoopExitStatus(vertex) );
742 }
743 else {
744 // merge multi-instance exit statuses
745 std::vector< std::pair<const CP::Variable&, std::vector<AttributeVariables>& > > inputs;
746 for ( auto& [_,multiInstanceExit] : vertex->inflows ) {
747 inputs.emplace_back( visit.at(multiInstanceExit), status.at(multiInstanceExit) );
748 }
749 status.emplace( vertex, createMergedStatus(vertex, extensionElements->attributeRegistry, std::move(inputs)) );
750 }
751 return;
752 }
753
754 if ( vertex->node->represents<BPMN::Scope>() ) {
755 if ( vertex->inflows.empty() ) {
756 throw std::runtime_error("CPModel: empty scopes are not supported");
757 }
758
759 // scope has children
760 auto getEndVertices = [&]() {
761 std::vector< const Vertex* > endVertices;
762 for ( auto& [_,candidate] : vertex->inflows ) {
763 // ignore inflows of unreachable nodes
764 if ( visit.contains(candidate) ) {
765 endVertices.push_back(candidate);
766 }
767 }
768 if ( endVertices.empty() ) {
769 throw std::runtime_error("CPModel: unable to determine end nodes for scope '" + vertex->node->id + "'");
770 }
771 return endVertices;
772 };
773 std::vector< std::pair<const CP::Variable&, std::vector<AttributeVariables>& > > inputs;
774 for ( auto endVertex : getEndVertices() ) {
775 assert(visit.contains(endVertex));
776 assert(status.contains(endVertex));
777 inputs.emplace_back( visit.at(endVertex), status.at(endVertex) );
778 }
779
780 assert(extensionElements);
781 status.emplace( vertex, createMergedStatus(vertex, extensionElements->attributeRegistry, std::move(inputs)) );
782 return;
783 }
784
785 // no scope or empty scope
786// assert( !vertex->exit<BPMN::Scope>() || vertex->inflows.size() == 1 );
787
788//std::cerr << vertex->reference() << std::endl;
789 const Vertex* entryVertex = entry(vertex);
790 if ( vertex->node->represents<BPMN::TimerCatchEvent>() ) {
791//std::cerr << "Timer: " << vertex->reference() << std::endl;
792 assert( vertex->node->extensionElements->represents<BPMNOS::Model::Timer>() );
793 auto trigger = createExpression(entry(vertex),*vertex->node->extensionElements->as<BPMNOS::Model::Timer>()->trigger->expression);
794 auto& entryStatus = status.at(entryVertex);
795 std::vector<AttributeVariables> variables;
796//std::cerr << "timer loop" << std::endl;
797 extensionElements = vertex->parent.value().first->node->extensionElements->represents<BPMNOS::Model::ExtensionElements>();
798 for ( auto attribute : extensionElements->attributeRegistry.statusAttributes ) {
799//std::cerr << attribute->name << ": " << attribute->index << " == " << variables.size() << std::endl;
800// assert( attribute->index == variables.size() );
802 variables.emplace_back(
803 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_{" + vertex->reference() + "}," + attribute->id, entryStatus[attribute->index].defined ),
804 model.addVariable(CP::Variable::Type::REAL, "value_{" + vertex->reference() + "}," + attribute->id, entryStatus[attribute->index].defined * CP::max( entryStatus[attribute->index].value, trigger ) )
805 );
806 }
807 else {
808 variables.emplace_back(
809 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_{" + vertex->reference() + "}," + attribute->id, entryStatus[attribute->index].defined ),
810 model.addVariable(CP::Variable::Type::REAL, "value_{" + vertex->reference() + "}," + attribute->id, entryStatus[attribute->index].value )
811 );
812 }
813 }
814 status.emplace( vertex, std::move(variables) );
815 return;
816 }
817 else if ( !extensionElements ) {
818 // token just runs through the node and exit status is the same as entry status
819 extensionElements = vertex->parent.value().first->node->extensionElements->represents<BPMNOS::Model::ExtensionElements>();
820//std::cerr << entryVertex->reference() << std::endl;
821 auto& entryStatus = status.at(entryVertex);
822 std::vector<AttributeVariables> variables;
823 for ( auto attribute : extensionElements->attributeRegistry.statusAttributes ) {
824//std::cerr << attribute->name << ": " << attribute->index << " == " << variables.size() << std::endl;
825// assert( attribute->index == variables.size() );
826 variables.emplace_back(
827 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_{" + vertex->reference() + "}," + attribute->id, entryStatus[attribute->index].defined ),
828 model.addVariable(CP::Variable::Type::REAL, "value_{" + vertex->reference() + "}," + attribute->id, entryStatus[attribute->index].value )
829 );
830 }
831 status.emplace( vertex, std::move(variables) );
832 return;
833 }
834
835 if ( vertex->exit<BPMN::Task>() || vertex->exit<BPMN::TypedStartEvent>() ) {
836 // create local attribute variables considering all changes made before the token leves the node
837 createLocalAttributeVariables(vertex);
838 // use final attribute values to deduce exit status
839 auto& [ localStatus, localData, localGlobals ] = locals.at(vertex).back();
840
841 // create exit status
842 std::vector<AttributeVariables> variables;
843 for ( auto attribute : extensionElements->attributeRegistry.statusAttributes ) {
844 auto& [ defined, value ] = localStatus.at(attribute->index);
845 if ( attribute->index == BPMNOS::Model::ExtensionElements::Index::Timestamp && vertex->node->represents<BPMN::Task>() ) {
846 // exit timestamp may be later than deduced timestamp
847 variables.emplace_back(
848 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_{" + vertex->reference() + "}," + attribute->id, visit.at(vertex) ),
849 model.addRealVariable("value_{" + vertex->reference() + "}," + attribute->id )
850 );
851 auto& timestamp = variables.back().value;
852 model.addConstraint( visit.at(vertex).implies( timestamp >= value ) );
853 }
854 else {
855 variables.emplace_back(
856 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_{" + vertex->reference() + "}," + attribute->id, defined ),
857 model.addVariable(CP::Variable::Type::REAL, "value_{" + vertex->reference() + "}," + attribute->id, value )
858 );
859 }
860 }
861 status.emplace( vertex, std::move(variables) );
862 return;
863 }
864
865 // copy entry status references
866 assert( status.contains(entryVertex) );
867 std::vector<AttributeVariables> currentStatus = status.at(entryVertex);
868 //std::vector<AttributeVariables> currentData = xxx.at(entryVertex);
869 //std::vector<AttributeVariables> currentGlobal = xxx.at(entryVertex);
870
871/*
872 if ( vertex->node->represents<BPMNOS::Model::DecisionTask>() ) {
873 // choices are represented by unconstrained status (data/global) attributes
874 // TODO
875 assert(!"Not yet implemented");
876 }
877*/
878 if ( vertex->node->represents<BPMN::MessageCatchEvent>() ) {
879 assert( extensionElements );
880
881 if ( extensionElements->messageDefinitions.size() > 1 ) {
882 assert(!"Not yet implemented");
883 }
884
885 auto messageDefinition = extensionElements->messageDefinitions.size() == 1 ? extensionElements->messageDefinitions[0].get() : nullptr;
886 assert( messageContent.contains(vertex) );
887 auto& messageContentVariables = messageContent.at(vertex);
888
889 // create exit status
890 std::vector<AttributeVariables> variables;
891 auto& entryStatus = status.at( entry(vertex) );
892 for ( auto attribute : extensionElements->attributeRegistry.statusAttributes ) {
894 // timestamp is implicitly determined as the time when the message is delivered
895 variables.emplace_back(
896 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_{" + vertex->reference() + "}," + attribute->id, visit.at(vertex) ),
897 model.addRealVariable("value_{" + vertex->reference() + "}," + attribute->id )
898 );
899 }
900 else if ( auto content = findContent(messageDefinition, attribute) ) {
901 auto& [ defined, value ] = messageContentVariables.at(content->key);
902 if (
904 ) {
905 throw std::runtime_error("CPModel: timestamp must not be changed by message content");
906 }
907 // attribute set by received message content
908 variables.emplace_back(
909 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_{" + vertex->reference() + "}," + attribute->id, defined ),
910 model.addVariable(CP::Variable::Type::REAL, "value_{" + vertex->reference() + "}," + attribute->id, value )
911 );
912 }
913 else {
914 auto& [ defined, value ] = entryStatus.at(attribute->index);
915 variables.emplace_back(
916 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_{" + vertex->reference() + "}," + attribute->id, defined ),
917 model.addVariable(CP::Variable::Type::REAL, "value_{" + vertex->reference() + "}," + attribute->id, value )
918 );
919 }
920 }
921 status.emplace( vertex, std::move(variables) );
922 return;
923 }
924
925 if ( vertex->node->represents<BPMN::MessageStartEvent>() ) {
926 // add entry restrictions of event-subprocess
927 // TODO
928 }
929
930 std::vector<AttributeVariables> variables;
931 for ( auto attribute : extensionElements->attributeRegistry.statusAttributes ) {
932 assert( attribute->index == variables.size() );
933 variables.emplace_back(
934 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_{" + vertex->reference() + "}," + attribute->id, currentStatus[attribute->index].defined ),
935 model.addVariable(CP::Variable::Type::REAL, "value_{" + vertex->reference() + "}," + attribute->id, currentStatus[attribute->index].value )
936 );
937 }
938
939 status.emplace( vertex, std::move(variables) );
940}
941
942std::pair< CP::Expression, CP::Expression > CPModel::getLocalAttributeVariables( const Model::Attribute* attribute, std::tuple< std::vector<AttributeVariables>, std::vector<AttributeVariables>, std::vector<AttributeVariables> >& localVariables ) {
943 auto& [ status, data, globals ] = localVariables;
944 if ( attribute->category == Model::Attribute::Category::STATUS ) {
945 return std::make_pair<CP::Expression,CP::Expression>(
946 status[attribute->index].defined,
947 status[attribute->index].value
948 );
949 }
950 else if ( attribute->category == Model::Attribute::Category::DATA ) {
951 return std::make_pair<CP::Expression,CP::Expression>(
952 data[attribute->index].defined,
953 data[attribute->index].value
954 );
955 }
956 else {
957 assert( attribute->category == Model::Attribute::Category::GLOBAL );
958 return std::make_pair<CP::Expression,CP::Expression>(
959 globals[attribute->index].defined,
960 globals[attribute->index].value
961 );
962 }
963}
964
965CP::Expression CPModel::createOperatorExpression( const Model::Expression& operator_, std::tuple< std::vector<AttributeVariables>, std::vector<AttributeVariables>, std::vector<AttributeVariables> >& localVariables ) {
966 auto compiled = LIMEX::Expression<CP::Expression,CP::Expression>(encodeQuotedStrings(operator_.expression), limexHandle);
967
968 std::vector<CP::Expression> variables;
969 for ( auto& variableName : compiled.getVariables() ) {
970 auto attribute = operator_.attributeRegistry[variableName];
971 if( attribute->type == ValueType::COLLECTION ) {
972 throw std::runtime_error("CPModel: illegal operator expression '" + operator_.expression + "'");
973 }
974
975 auto [defined,value] = getLocalAttributeVariables(attribute,localVariables);
976 variables.push_back( value );
977 }
978
979 std::vector<CP::Expression> collectionVariables;
980 for ( auto& variableName : compiled.getCollections() ) {
981 auto attribute = operator_.attributeRegistry[variableName];
982 if( attribute->type != ValueType::COLLECTION ) {
983 throw std::runtime_error("CPModel: illegal operator expression '" + operator_.expression + "'");
984 }
985
986 auto [defined,value] = getLocalAttributeVariables(attribute,localVariables);
987 collectionVariables.push_back( value );
988 }
989
990 return compiled.evaluate(variables,collectionVariables);
991}
992
993
994const BPMNOS::Model::Choice* CPModel::findChoice(const std::vector< std::unique_ptr<BPMNOS::Model::Choice> >& choices, const BPMNOS::Model::Attribute* attribute) const {
995//std::cerr << "choices: " << choices.size() << "/" << attribute->id << std::endl;
996 if ( choices.empty() ) return nullptr;
997 auto it = std::find_if(
998 choices.begin(),
999 choices.end(),
1000 [&](const auto& choice) {
1001//std::cerr << "check: " << choice->attribute->id << std::endl;
1002 return choice->attribute == attribute;
1003 }
1004 );
1005 return ( it != choices.end() ? it->get() : nullptr );
1006}
1007
1008const BPMNOS::Model::Content* CPModel::findContent(const BPMNOS::Model::MessageDefinition* messageDefinition, const BPMNOS::Model::Attribute* attribute) const {
1009 if ( !messageDefinition ) return nullptr;
1010 auto it = std::find_if(
1011 messageDefinition->contentMap.begin(),
1012 messageDefinition->contentMap.end(),
1013 [&](const auto& mapEntry) {
1014 auto& [key, content] = mapEntry;
1015 return content->attribute == attribute;
1016 }
1017 );
1018 return ( it != messageDefinition->contentMap.end() ? it->second.get() : nullptr );
1019}
1020
1021
1022void CPModel::createLocalAttributeVariables(const Vertex* vertex) {
1023//std::cerr << "createLocalAttributeVariables" << std::endl;
1024 assert( vertex->exit<BPMN::Task>() || vertex->exit<BPMN::TypedStartEvent>() );
1025
1026 if ( auto typedStartEvent = vertex->node->represents<BPMN::TypedStartEvent>();
1027 typedStartEvent && !typedStartEvent->represents<BPMN::MessageStartEvent>()
1028 ) {
1029 throw std::runtime_error("CPModel: illegal typed start event");
1030 }
1031 auto extensionElements =
1032 vertex->exit<BPMN::Task>() ?
1034 vertex->node->as<BPMN::TypedStartEvent>()->parent->extensionElements->represents<BPMNOS::Model::ExtensionElements>();
1035 ;
1036
1037 auto& messageDefinitions = vertex->node->extensionElements->as<BPMNOS::Model::ExtensionElements>()->messageDefinitions;
1038 if ( messageDefinitions.size() > 1 ) {
1039 assert(!"Not yet implemented");
1040 }
1041
1042 auto messageDefinition =
1043 ( vertex->exit<BPMN::MessageCatchEvent>() && messageDefinitions.size() == 1 ) ?
1044 messageDefinitions[0].get() :
1045 nullptr
1046 ;
1047
1048 auto localAttributeVariables = [&](const auto& attributes) -> std::vector<AttributeVariables> {
1049 std::vector<AttributeVariables> variables;
1050//std::cerr << "Attributes: " << attributes.size() << std::endl;
1051
1052 auto initialVariables = [&](BPMNOS::Model::Attribute* attribute) -> std::pair<CP::Expression,CP::Expression> {
1053 auto entryVertex = entry(vertex);
1055 return std::make_pair<CP::Expression,CP::Expression>(
1056 status.at(entryVertex)[attribute->index].defined,
1057 status.at(entryVertex)[attribute->index].value
1058 );
1059 }
1060 else if ( attribute->category == BPMNOS::Model::Attribute::Category::DATA ) {
1061 auto& index = dataIndex.at(entryVertex)[ entryVertex->dataOwnerIndex(attribute) ];
1062 return std::make_pair<CP::Expression,CP::Expression>(
1063 data.at( {entryVertex->dataOwner(attribute).first, attribute } ).defined[ index ],
1064 data.at( {entryVertex->dataOwner(attribute).first, attribute } ).value[ index ]
1065 );
1066 }
1067 else {
1069 auto& index = globalIndex.at(entryVertex);
1070 return std::make_pair<CP::Expression,CP::Expression>(
1071 globals[attribute->index].defined[ index ],
1072 globals[attribute->index].value[ index ]
1073 );
1074 }
1075 };
1076
1077 for ( auto attribute : attributes ) {
1078//std::cerr << "Attribute: " << attribute->id << std::endl;
1079 auto [defined,value] = initialVariables(attribute);
1080
1081 if ( auto choice = findChoice(extensionElements->choices, attribute) ) {
1082 if (
1085 ) {
1086 throw std::runtime_error("CPModel: timestamp must not be a choice");
1087 }
1088
1089 // attribute set by choice
1090 variables.emplace_back(
1091 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_{" + vertex->shortReference() + ",0}," + attribute->id, visit.at(vertex) ),
1092 model.addRealVariable("value_{" + vertex->shortReference() + ",0}," + attribute->id )
1093 );
1094 auto& variable = variables.back().value;
1095 // create constraints limiting the choice
1096 // ASSUMPTION: constraints on the choices must only depend on entry status, data, or globals.
1097 // ASSUMPTION: only status attributes are allowed as choices
1098 if ( choice->lowerBound.has_value() ) {
1099 auto& [LB,strictLB] = choice->lowerBound.value();
1100 if ( strictLB ) {
1101 model.addConstraint( visit.at(vertex).implies( variable > createExpression(entry(vertex),*LB) ) );
1102 }
1103 else {
1104 model.addConstraint( visit.at(vertex).implies( variable >= createExpression(entry(vertex),*LB) ) );
1105 }
1106 }
1107 if ( choice->upperBound.has_value() ) {
1108 auto& [UB,strictUB] = choice->upperBound.value();
1109 if ( strictUB ) {
1110 model.addConstraint( visit.at(vertex).implies( variable < createExpression(entry(vertex),*UB) ) );
1111 }
1112 else {
1113 model.addConstraint( visit.at(vertex).implies( variable <= createExpression(entry(vertex),*UB) ) );
1114 }
1115 }
1116 if ( choice->multipleOf ) {
1117 discretizerMap.emplace(
1118 std::make_pair(vertex, attribute),
1119 model.addIntegerVariable("discretizer_{" + vertex->reference() + "}," + attribute->id)
1120 );
1121 auto& discretizer = discretizerMap.at({vertex, attribute});
1122 model.addConstraint( (!visit.at(vertex)).implies( discretizer == 0.0 ) );
1123 model.addConstraint( visit.at(vertex).implies( variable == discretizer * createExpression(entry(vertex),*choice->multipleOf) ) );
1124 }
1125 if ( !choice->enumeration.empty() ) {
1126 CP::Expression enumerationContainsVariable(false);
1127 for ( auto& expression : choice->enumeration ) {
1128 enumerationContainsVariable = enumerationContainsVariable || ( variable == createExpression(entry(vertex),*expression) );
1129 }
1130 model.addConstraint( visit.at(vertex).implies( enumerationContainsVariable ) );
1131 }
1132// assert(!"Not yet implemented");
1133 }
1134 else if ( auto content = findContent(messageDefinition, attribute) ) {
1135//std::cerr << "Content: " << content->key << std::endl;
1136 if (
1139 ) {
1140 throw std::runtime_error("CPModel: timestamp must not be changed by message content");
1141 }
1142 assert(messageContent.contains(vertex));
1143 assert(messageContent.at(vertex).contains(content->key));
1144 // attribute set by received message content
1145 variables.emplace_back(
1146 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_{" + vertex->shortReference() + ",0}," + attribute->id, messageContent.at(vertex).at(content->key).defined ),
1147 model.addVariable(CP::Variable::Type::REAL, "value_{" + vertex->shortReference() + ",0}," + attribute->id, messageContent.at(vertex).at(content->key).value )
1148 );
1149 }
1150 else if (
1153 vertex->exit<BPMN::MessageCatchEvent>()
1154 ) {
1155 // timestamp cannot be deduced and must be constrained
1156 variables.emplace_back(
1157 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_{" + vertex->shortReference() + ",0}," + attribute->id, visit.at(vertex) ),
1158 model.addRealVariable("value_{" + vertex->shortReference() + ",0}," + attribute->id )
1159 );
1160 auto& timestamp = variables.back().value;
1161 model.addConstraint( visit.at(vertex).implies( timestamp >= status.at(entry(vertex))[BPMNOS::Model::ExtensionElements::Index::Timestamp].value ) );
1162 }
1163 else {
1164 // all local attribute variables are deduced from initial variables upon entry
1165 variables.emplace_back(
1166 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_{" + vertex->shortReference() + ",0}," + attribute->id, defined ),
1167 model.addVariable(CP::Variable::Type::REAL, "value_{" + vertex->shortReference() + ",0}," + attribute->id, value )
1168 );
1169 }
1170//std::cerr << "Attribute: " << attribute->id << " (done)" << std::endl;
1171 }
1172
1173 return variables;
1174 }; // end of lambda
1175
1176 locals.emplace(
1177 vertex,
1178 std::vector{
1179 std::make_tuple(
1180 localAttributeVariables(extensionElements->attributeRegistry.statusAttributes),
1181 localAttributeVariables(extensionElements->attributeRegistry.dataAttributes),
1182 localAttributeVariables(extensionElements->attributeRegistry.globalAttributes)
1183 )
1184 }
1185 );
1186
1187 auto& current = locals.at(vertex);
1188 for ( auto& operator_ : extensionElements->operators ) {
1189 auto& [ currentStatus, currentData, currentGlobals ] = current.back();
1190
1191 auto updatedLocalAttributeVariables = [&](const auto& attributes, std::vector<AttributeVariables>& attributeVariables ) -> std::vector<AttributeVariables> {
1192 std::vector<AttributeVariables> variables;
1193 for ( auto attribute : attributes ) {
1194 if ( attribute == operator_->attribute ) {
1195 // determine updated variable value (only change value if vertex is visited)
1196 if ( operator_->expression.type == Model::Expression::Type::UNASSIGN ) {
1197 variables.emplace_back(
1198 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_{" + vertex->shortReference() + "," + std::to_string(current.size()) + "}," + attribute->id, CP::if_then_else( visit.at(vertex), false, attributeVariables[attribute->index].defined) ),
1199 model.addVariable(CP::Variable::Type::REAL, "value_{" + vertex->shortReference() + "," + std::to_string(current.size()) + "}," + attribute->id, CP::if_then_else( visit.at(vertex), 0.0, attributeVariables[attribute->index].value) )
1200 );
1201 }
1202 else if ( operator_->expression.type == Model::Expression::Type::ASSIGN ) {
1203 CP::Expression value = createOperatorExpression( operator_->expression, current.back() );
1204 variables.emplace_back(
1205 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_{" + vertex->shortReference() + "," + std::to_string(current.size()) + "}," + attribute->id, CP::if_then_else( visit.at(vertex), true, attributeVariables[attribute->index].defined) ),
1206 model.addVariable(CP::Variable::Type::REAL, "value_{" + vertex->shortReference() + "," + std::to_string(current.size()) + "}," + attribute->id, CP::if_then_else( visit.at(vertex), value, attributeVariables[attribute->index].value) )
1207 );
1208 // TODO: add constraints ensuring that all inputs used by expression are defined
1209 }
1210 else {
1211 throw std::runtime_error("CPModel: illegal operator expression: " + operator_->expression.expression );
1212 }
1213 }
1214 else {
1215 // variable value remains unchanged
1216 variables.emplace_back(
1217 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_{" + vertex->shortReference() + "," + std::to_string(current.size()) + "}," + attribute->id, attributeVariables[attribute->index].defined ),
1218 model.addVariable(CP::Variable::Type::REAL, "value_{" + vertex->shortReference() + "," + std::to_string(current.size()) + "}," + attribute->id, attributeVariables[attribute->index].value )
1219 );
1220 }
1221 }
1222 return variables;
1223 };
1224
1225
1226 current.emplace_back(
1227 updatedLocalAttributeVariables(extensionElements->attributeRegistry.statusAttributes,currentStatus),
1228 updatedLocalAttributeVariables(extensionElements->attributeRegistry.dataAttributes,currentData),
1229 updatedLocalAttributeVariables(extensionElements->attributeRegistry.globalAttributes,currentGlobals)
1230 );
1231 }
1232}
1233
1234std::vector<CPModel::AttributeVariables> CPModel::createUniquelyDeducedEntryStatus(const Vertex* vertex, const BPMNOS::Model::AttributeRegistry& attributeRegistry, std::vector<AttributeVariables>& inheritedStatus) {
1235//std::cerr << vertex->reference() << std::endl;
1236 assert( vertex->type == Vertex::Type::ENTRY );
1237 assert( !vertex->node->represents<BPMN::Process>() );
1238
1239 std::vector<AttributeVariables> variables;
1240 variables.reserve( attributeRegistry.statusAttributes.size() );
1241 for ( auto attribute : attributeRegistry.statusAttributes ) {
1242//std::cerr << variables.size() << "/" << attribute->index << "/" << attribute->id << "/" << attributeRegistry.statusAttributes.size() << std::endl;
1243 assert( variables.size() == attribute->index );
1244 auto& [ defined, value ] = inheritedStatus.at(attribute->index);
1245 if ( auto activity = vertex->node->represents<BPMN::Activity>();
1246 activity &&
1247 !activity->loopCharacteristics.has_value() &&
1249 ) {
1250 // entry of simple activity may be later than the deduced timestamp ( entry of loop or multi-instance actvities is deduced)
1251 variables.emplace_back(
1252 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_{" + vertex->reference() + "}," + attribute->id, visit.at(vertex) ),
1253 model.addRealVariable("value_{" + vertex->reference() + "}," + attribute->id )
1254 );
1255 auto& timestamp = variables.back().value;
1256 model.addConstraint( visit.at(vertex).implies( timestamp >= value ) );
1257 }
1258 else if ( vertex->entry<BPMN::TypedStartEvent>() ) {
1260 CP::Expression timestamp(0);
1261 for ( auto predecessor : vertex->predecessors ) {
1262 timestamp = CP::max( timestamp, status.at(predecessor)[attribute->index].value );
1263 }
1264 // deduce variable if visited
1265 variables.emplace_back(
1266 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_{" + vertex->reference() + "}," + attribute->id, visit.at(vertex) ),
1267 model.addVariable(CP::Variable::Type::REAL, "value_{" + vertex->reference() + "}," + attribute->id, visit.at(vertex) * timestamp )
1268 );
1269 }
1270 else {
1271 // deduce variable if visited
1272 variables.emplace_back(
1273 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_{" + vertex->reference() + "}," + attribute->id, visit.at(vertex) * defined ),
1274 model.addVariable(CP::Variable::Type::REAL, "value_{" + vertex->reference() + "}," + attribute->id, visit.at(vertex) * value )
1275 );
1276 }
1277 }
1278 else {
1279 // deduce variable
1280 variables.emplace_back(
1281 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_{" + vertex->reference() + "}," + attribute->id, defined ),
1282 model.addVariable(CP::Variable::Type::REAL, "value_{" + vertex->reference() + "}," + attribute->id, value )
1283 );
1284 }
1285 }
1286
1287 return variables;
1288}
1289
1290
1291std::vector<CPModel::AttributeVariables> CPModel::createAlternativeEntryStatus(const Vertex* vertex, const BPMNOS::Model::AttributeRegistry& attributeRegistry, std::vector< std::pair<const CP::Variable&, std::vector<AttributeVariables>& > > alternatives) {
1292 assert( vertex->type == Vertex::Type::ENTRY );
1293 assert( !vertex->node->represents<BPMN::Process>() );
1294 assert( !vertex->node->represents<BPMN::Activity>() );
1295 std::vector<AttributeVariables> variables;
1296 variables.reserve( attributeRegistry.statusAttributes.size() );
1297 for ( auto attribute : attributeRegistry.statusAttributes ) {
1298 // deduce variable
1299 CP::Expression defined(false);
1300 CP::Expression value(0.0);
1301 for ( auto& [ active, attributeVariables] : alternatives ) {
1302 assert( attributeVariables.size() == attributeRegistry.statusAttributes.size() );
1303 defined = defined || attributeVariables[attribute->index].defined;
1304 value = value + attributeVariables[attribute->index].defined * attributeVariables[attribute->index].value;
1305 }
1306 variables.emplace_back(
1307 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_{" + vertex->reference() + "}," + attribute->id, defined ),
1308 model.addVariable(CP::Variable::Type::REAL, "value_{" + vertex->reference() + "}," + attribute->id, value )
1309 );
1310 }
1311
1312 return variables;
1313}
1314
1315
1316std::vector<CPModel::AttributeVariables> CPModel::createMergedStatus(const Vertex* vertex, const BPMNOS::Model::AttributeRegistry& attributeRegistry, std::vector< std::pair<const CP::Variable&, std::vector<AttributeVariables>& > > inputs) {
1317// assert( ( vertex->type == Vertex::Type::ENTRY && vertex->inflows.size() > 1) || vertex->exit<BPMN::Scope>() );
1318// assert( !vertex->entry<BPMN::Activity>() );
1319 std::vector<AttributeVariables> variables;
1320 variables.reserve( attributeRegistry.statusAttributes.size() );
1321 for ( auto attribute : attributeRegistry.statusAttributes ) {
1323 std::vector<CP::Expression> terms;
1324 assert( !inputs.empty() );
1325 for ( auto& [ active, attributeVariables] : inputs ) {
1326 terms.emplace_back( attributeVariables[attribute->index].defined * attributeVariables[attribute->index].value );
1327 }
1328 if ( vertex->node->represents<BPMN::Activity>() && !flattenedGraph->dummies.contains(vertex) ) {
1329 // timestamp of vertex must be at least the maximum timestamp of all inputs
1330 variables.emplace_back(
1331 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_{" + vertex->reference() + "}," + attribute->id, visit.at(vertex) ),
1332 model.addRealVariable("value_{" + vertex->reference() + "}," + attribute->id )
1333 );
1334 model.addConstraint( visit.at(vertex).implies( variables.back().value >= CP::max(terms) ) );
1335 }
1336 else {
1337 // timestamp of vertex is the same as the maximum timestamp of all inputs
1338 variables.emplace_back(
1339 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_{" + vertex->reference() + "}," + attribute->id, visit.at(vertex) ),
1340 model.addVariable(CP::Variable::Type::REAL, "value_{" + vertex->reference() + "}," + attribute->id, CP::if_then_else( visit.at(vertex), CP::max(terms), 0.0 ) )
1341 );
1342 }
1343 }
1344 else {
1345 // deduce variable
1346
1347 // determine whether merged variable is defined
1348 CP::Expression exists(false);
1349 for ( auto& [ _, attributeVariables] : inputs ) {
1350 // check whether there is at least one input is defined
1351 exists = exists || attributeVariables[attribute->index].defined;
1352 }
1353 CP::Expression allSame(true);
1354 // check whether all defined inputs have the same value
1355 for ( size_t i = 0; i + 1 < inputs.size(); i++ ) {
1356 for ( size_t j = i + 1; j < inputs.size(); j++ ) {
1357 auto& someVariables = inputs[i].second;
1358 auto& otherVariables = inputs[j].second;
1359 allSame = allSame && CP::if_then_else( someVariables[attribute->index].defined && otherVariables[attribute->index].defined, someVariables[attribute->index].value == otherVariables[attribute->index].value, true);
1360 }
1361 }
1362 auto& defined = model.addVariable(CP::Variable::Type::BOOLEAN, "defined_{" + vertex->reference() + "}," + attribute->id, exists && allSame);
1363
1364 // determine merged variable value
1365 CP::Cases cases;
1366 for ( auto& [ active, attributeVariables] : inputs ) {
1367 cases.emplace_back( attributeVariables[attribute->index].defined, attributeVariables[attribute->index].value );
1368 }
1369 auto& value = model.addVariable(CP::Variable::Type::REAL, "value_{" + vertex->reference() + "}," + attribute->id, CP::if_then_else( defined, CP::n_ary_if( cases, 0.0 ), 0.0) );
1370
1371 variables.emplace_back( defined, value );
1372 }
1373 }
1374 return variables;
1375}
1376
1377CP::Expression CPModel::getLoopIndex(const Vertex* vertex) {
1378 auto predecessor = vertex->inflows.front().second;
1379 if ( getLoopCharacteristics(vertex).value() == BPMN::Activity::LoopCharacteristics::Standard ) {
1380 auto extensionElements = vertex->node->extensionElements->as<BPMNOS::Model::ExtensionElements>();
1381 const BPMNOS::Model::Attribute* attribute =
1382 extensionElements->loopIndex.has_value() ?
1383 extensionElements->loopIndex.value()->expression->isAttribute() :
1384 nullptr
1385 ;
1386 if ( attribute->index >= status.at(predecessor).size() ) {
1387 return 1;
1388 }
1389 auto& [ defined, value ] = status.at(predecessor).at(attribute->index);
1390 return value + 1;
1391 }
1392 else {
1393 size_t i = 0; // determine index of activity
1394 for ( ; i < predecessor->outflows.size(); i++ ) {
1395 if ( vertex == predecessor->outflows[i].second ) {
1396 break;
1397 }
1398 }
1399 return (double)i+1;
1400 }
1401}
1402
1403void CPModel::createLoopEntryStatus(const Vertex* vertex) {
1404//std::cerr << "createLoopEntryStatus " << vertex->reference() << std::endl;
1405 assert( vertex->inflows.size() == 1 );
1406 assert( status.contains(vertex) );
1407 auto& variables = status.at(vertex);
1408 auto predecessor = vertex->inflows.front().second;
1409 auto& priorStatus = status.at(predecessor);
1410//std::cerr << predecessor->reference() << "/" << priorStatus.size() << std::endl;
1411 auto extensionElements = vertex->node->extensionElements->as<BPMNOS::Model::ExtensionElements>();
1412 const BPMNOS::Model::Attribute* loopIndex =
1413 extensionElements->loopIndex.has_value() ?
1414 extensionElements->loopIndex.value()->expression->isAttribute() :
1415 nullptr
1416 ;
1417
1418
1419 // deduce status for loop visit
1420 variables.reserve( extensionElements->attributeRegistry.statusAttributes.size() );
1421
1422 for ( size_t index = 0; index < priorStatus.size(); index++ ) {
1423 auto attribute = extensionElements->attributeRegistry.statusAttributes[index];
1424 assert( variables.size() == attribute->index );
1425 // check if attribute is owned by loop activity
1426 bool ownedByLoop = (index >= extensionElements->attributeRegistry.statusAttributes.size() - extensionElements->attributes.size());
1427
1428//std::cerr << attribute->id << std::endl;
1429 auto& [ defined, value ] = priorStatus.at(attribute->index);
1430 if ( vertex->node->represents<BPMN::Activity>() && attribute->index == BPMNOS::Model::ExtensionElements::Index::Timestamp ) {
1431 // entry of activity may be later than the deduced timestamp
1432 variables.emplace_back(
1433 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_{" + vertex->reference() + "}," + attribute->id, visit.at(vertex) ),
1434 model.addRealVariable("value_{" + vertex->reference() + "}," + attribute->id )
1435 );
1436 auto& timestamp = variables.back().value;
1437 model.addConstraint( visit.at(vertex).implies( timestamp >= value ) );
1438 }
1439 else if ( ownedByLoop && attribute == loopIndex ) {
1440 // set or increment loop index (initial value is assumed to be undefined, and therefore has a value of zero)
1441 variables.emplace_back(
1442 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_{" + vertex->reference() + "}," + attribute->id, visit.at(vertex) ),
1443 model.addVariable(CP::Variable::Type::REAL,"value_{" + vertex->reference() + "}," + attribute->id, CP::if_then_else( visit.at(vertex), getLoopIndex(vertex), 0 ) )
1444 );
1445 }
1446 else if ( ownedByLoop && attribute->expression ) {
1447 // initial assignment
1448 assert( attribute->expression->type == Model::Expression::Type::ASSIGN );
1449 CP::Expression assignment = createExpression( vertex, *attribute->expression );
1450 variables.emplace_back(
1451 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_{" + vertex->reference() + "}," + attribute->id, visit.at(vertex) ),
1452 model.addVariable(CP::Variable::Type::REAL, "value_{" + vertex->reference() + "}," + attribute->id, CP::if_then_else( visit.at(vertex), assignment, 0.0) )
1453 );
1454 }
1455 else {
1456 // deduce variable
1457 variables.emplace_back(
1458 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_{" + vertex->reference() + "}," + attribute->id, CP::if_then_else( visit.at(vertex), defined, false ) ),
1459 model.addVariable(CP::Variable::Type::REAL, "value_{" + vertex->reference() + "}," + attribute->id, CP::if_then_else( visit.at(vertex), value, 0.0 ) )
1460 );
1461 }
1462 }
1463
1464 if ( priorStatus.size() < extensionElements->attributeRegistry.statusAttributes.size() ) {
1465 addAttributes(vertex,variables,loopIndex);
1466 }
1467
1468}
1469
1470std::vector<CPModel::AttributeVariables> CPModel::createLoopExitStatus(const Vertex* vertex) {
1471 assert( vertex->inflows.size() >= 1 );
1472// auto& priorStatus = status.at(&predecessor);
1473 // deduce exit status for loop vertex
1474 std::vector<AttributeVariables> variables;
1475 auto extensionElements = vertex->node->extensionElements->as<BPMNOS::Model::ExtensionElements>();
1476 variables.reserve( extensionElements->attributeRegistry.statusAttributes.size() );
1477
1478 for ( auto attribute : extensionElements->attributeRegistry.statusAttributes ) {
1479 CP::Expression defined(false);
1480 CP::Expression value(0.0);
1481 for ( size_t i = 0; i < vertex->inflows.size(); i++ ) {
1482 auto predecessor = vertex->inflows[i].second;
1483 // determine condition whether predecessor is the last visited loop vertex
1484 CP::Expression condition = ( i + 1 < vertex->inflows.size() ) ? visit.at(predecessor) && !visit.at(vertex->inflows[i+1].second) : visit.at(predecessor);
1485
1486 auto &priorStatus = status.at(predecessor);
1487 defined = defined || ( condition * priorStatus.at(attribute->index).defined );
1488 value = value + ( condition * priorStatus.at(attribute->index).value );
1489 }
1490 variables.emplace_back(
1491 model.addVariable(CP::Variable::Type::BOOLEAN, "defined_{" + vertex->reference() + "}," + attribute->id, defined ),
1492 model.addVariable(CP::Variable::Type::REAL, "value_{" + vertex->reference() + "}," + attribute->id, value )
1493 );
1494 }
1495 return variables;
1496}
1497
1498void CPModel::createVertexVariables(const FlattenedGraph::Vertex* vertex) {
1499//std::cerr << "createVertexVariables: " << vertex->reference() << std::endl;
1500
1501//std::cerr << "createGlobalIndexVariable" << std::endl;
1502 createGlobalIndexVariable(vertex);
1503//std::cerr << "createDataIndexVariables" << std::endl;
1504 createDataIndexVariables(vertex);
1505
1506 if ( vertex->type == Vertex::Type::ENTRY ) {
1507//std::cerr << "createEntryVariables" << std::endl;
1508 createEntryVariables(vertex);
1509 }
1510
1511//std::cerr << "createDataVariables: " << vertex->reference() << std::endl;
1512 if ( vertex->entry<BPMN::Scope>() ) {
1513 createDataVariables(vertex);
1514 }
1515
1516 if ( vertex->exit<BPMN::MessageCatchEvent>() ) {
1517//std::cerr << "createMessageContent" << std::endl;
1518 createMessageContent(vertex);
1519 }
1520
1521//std::cerr << "createStatus" << std::endl;
1522 createStatus(vertex);
1523
1524 if ( vertex->entry<BPMN::MessageThrowEvent>() || vertex->entry<BPMN::MessageCatchEvent>() ) {
1525//std::cerr << "createMessageHeader" << std::endl;
1526 createMessageHeader(vertex);
1527 }
1528
1529 if ( vertex->entry<BPMN::MessageThrowEvent>() ) {
1530//std::cerr << "createMessageContent" << std::endl;
1531 createMessageContent(vertex);
1532 }
1533
1534 if ( vertex->type == Vertex::Type::EXIT ) {
1535 createExitVariables(vertex);
1536 }
1537
1538//std::cerr << "createSequenceConstraints" << std::endl;
1539 createSequenceConstraints(vertex);
1540
1541//std::cerr << "createRestrictions" << std::endl;
1542 createRestrictions(vertex);
1543
1544//std::cerr << "Done(createVertexVariables)" << std::endl;
1545}
1546
1547std::pair< CP::Expression, CP::Expression > CPModel::getAttributeVariables( const Vertex* vertex, const Model::Attribute* attribute) {
1548 if ( attribute->category == Model::Attribute::Category::STATUS ) {
1549 assert( status.contains(vertex) );
1550 assert( attribute->index < status.at(vertex).size() );
1551 return std::make_pair<CP::Expression,CP::Expression>(
1552 status.at(vertex)[attribute->index].defined,
1553 status.at(vertex)[attribute->index].value
1554 );
1555 }
1556 else if ( attribute->category == Model::Attribute::Category::DATA ) {
1558 return std::make_pair<CP::Expression,CP::Expression>(
1559 true,
1560 (double)vertex->instanceId
1561 );
1562 }
1563 assert( data.contains( {vertex->dataOwner(attribute).first, attribute } ) );
1564 if ( attribute->isImmutable ) {
1565 return std::make_pair<CP::Expression,CP::Expression>(
1566 data.at( {vertex->dataOwner(attribute).first, attribute } ).defined[ 0 ],
1567 data.at( {vertex->dataOwner(attribute).first, attribute } ).value[ 0 ]
1568 );
1569 }
1570 else {
1571 auto& index = dataIndex.at(vertex)[ vertex->dataOwnerIndex(attribute) ];
1572 return std::make_pair<CP::Expression,CP::Expression>(
1573 data.at( {vertex->dataOwner(attribute).first, attribute } ).defined[ index ],
1574 data.at( {vertex->dataOwner(attribute).first, attribute } ).value[ index ]
1575 );
1576 }
1577 }
1578 else {
1579 assert( attribute->category == Model::Attribute::Category::GLOBAL );
1580 assert( globals.size() > attribute->index );
1581 if ( attribute->isImmutable ) {
1582 return std::make_pair<CP::Expression,CP::Expression>(
1583 globals[attribute->index].defined[ 0 ],
1584 globals[attribute->index].value[ 0 ]
1585 );
1586 }
1587 else {
1588 auto& index = globalIndex.at(vertex);
1589 return std::make_pair<CP::Expression,CP::Expression>(
1590 globals[attribute->index].defined[ index ],
1591 globals[attribute->index].value[ index ]
1592 );
1593 }
1594 }
1595}
1596
1597void CPModel::createEntryVariables(const FlattenedGraph::Vertex* vertex) {
1598//std::cerr << "createEntryVariables: " << vertex->reference() << std::endl;
1599 // visit variable
1600 if ( vertex->node->represents<BPMN::UntypedStartEvent>() ) {
1601 assert( vertex->inflows.size() == 1 );
1602 assert( vertex->predecessors.size() == 1 );
1603 assert( vertex->senders.empty() );
1604
1605 // deduce visit from parent
1606 auto& deducedVisit = model.addVariable(CP::Variable::Type::BOOLEAN, "visit_{" + vertex->shortReference() + "}", visit.at( vertex->parent.value().first ) );
1607 visit.emplace(vertex, deducedVisit );
1608 visit.emplace(exit(vertex), deducedVisit );
1609 }
1610 else if ( auto typedStartEvent = vertex->node->represents<BPMN::TypedStartEvent>() ) {
1611 if (
1612 !typedStartEvent->parent->represents<BPMN::EventSubProcess>() ||
1613 typedStartEvent->isInterrupting ||
1614 !typedStartEvent->represents<BPMN::MessageStartEvent>()
1615 ) {
1616 throw std::runtime_error("CPModel: typed start event '" + typedStartEvent->id + "' is not supported");
1617 }
1618 // deduce visit from incoming message flow
1619 CP::Expression messageDelivered(false);
1620 for ( auto sender : exit(vertex)->senders ) {
1621 assert( messageFlow.contains({sender,exit(vertex)}) );
1622 messageDelivered = messageDelivered || messageFlow.at({sender,exit(vertex)});
1623 }
1624 auto& deducedVisit = model.addVariable(CP::Variable::Type::BOOLEAN, "visit_{" + vertex->shortReference() + "}", messageDelivered );
1625 visit.emplace(vertex, deducedVisit );
1626 visit.emplace(exit(vertex), deducedVisit );
1627 // event-subprocess may only be triggered if all predecessors are visited
1628 for ( auto predecessor : vertex->predecessors ) {
1629 model.addConstraint( visit.at(vertex) <= visit.at(predecessor) );
1630 }
1631 }
1632 else if ( vertex->node->represents<BPMN::FlowNode>() ) {
1633//std::cerr << vertex->reference() << ": " << vertex->loopIndices.size() << "/" << flattenedGraph->loopIndexAttributes.at(vertex->node).size() << "/" << getLoopCharacteristics(vertex).has_value() << "/" << flattenedGraph->dummies.contains(vertex) << std::endl;
1634 if ( auto loopCharacteristics = getLoopCharacteristics(vertex);
1635 loopCharacteristics.has_value() &&
1636 !flattenedGraph->dummies.contains(vertex)
1637 ) {
1638//std::cerr << "Loop/MI" << std::endl;
1639 if ( loopCharacteristics.value() == BPMN::Activity::LoopCharacteristics::Standard ) {
1640 // loop vertex
1641 auto predecessor = vertex->inflows.front().second;
1642 if ( flattenedGraph->dummies.contains(predecessor) ) {
1643 // first loop vertex
1644 auto& deducedVisit = model.addVariable(CP::Variable::Type::BOOLEAN, "visit_{" + vertex->shortReference() + "}" , visit.at( predecessor ) );
1645 visit.emplace(vertex, deducedVisit );
1646 visit.emplace(exit(vertex), deducedVisit );
1647//std::cerr << deducedVisit.stringify() << std::endl;
1648 }
1649 else {
1650 auto extensionElements = vertex->node->extensionElements->as<BPMNOS::Model::ExtensionElements>();
1651 if ( extensionElements->loopCondition.has_value() ) {
1652 // vertex is visited only if predecessor is visited and loop condition is satisifed
1653 auto condition = createExpression(predecessor, *extensionElements->loopCondition.value()->expression);
1654 auto& deducedVisit = model.addVariable(CP::Variable::Type::BOOLEAN, "visit_{" + vertex->shortReference() + "}" , visit.at( predecessor ) && condition );
1655 visit.emplace(vertex, deducedVisit );
1656 visit.emplace(exit(vertex), deducedVisit );
1657//std::cerr << deducedVisit.stringify() << std::endl;
1658 }
1659 else {
1660 // no condition is given, vertex is visited if predecessor is visited
1661 auto& deducedVisit = model.addVariable(CP::Variable::Type::BOOLEAN, "visit_{" + vertex->shortReference() + "}" , visit.at( predecessor ) );
1662 visit.emplace(vertex, deducedVisit );
1663 visit.emplace(exit(vertex), deducedVisit );
1664 }
1665 }
1666 }
1667 else {
1668 // multi-instance vertex
1669//std::cerr << "multi-instance vertex" << std::endl;
1670 auto predecessor = vertex->inflows.front().second;
1671 auto extensionElements = vertex->node->extensionElements->represents<BPMNOS::Model::ExtensionElements>();
1672 assert( extensionElements->loopCardinality.has_value() );
1673 CP::Expression cardinality = createExpression( predecessor, *extensionElements->loopCardinality.value()->expression );
1674 auto index = getLoopIndex(vertex);
1675 auto& deducedVisit = model.addVariable(CP::Variable::Type::BOOLEAN, "visit_{" + vertex->shortReference() + "}" , visit.at( predecessor ) && ( index <= cardinality ) );
1676 visit.emplace(vertex, deducedVisit );
1677 visit.emplace(exit(vertex), deducedVisit );
1678//std::cerr << deducedVisit.stringify() << std::endl;
1679// assert(!"Not yet implemented");
1680 }
1681 }
1682 else if ( vertex->inflows.size() == 1 ) {
1683//std::cerr << "SINGLE INFLOW" << std::endl;
1684 auto& [sequenceFlow, predecessor] = vertex->inflows.front();
1685 if ( sequenceFlow ) {
1686 // deduce visit from unique sequence flow
1687 auto& deducedVisit = model.addVariable(CP::Variable::Type::BOOLEAN, "visit_{" + vertex->shortReference() + "}" , tokenFlow.at( std::make_pair(predecessor, vertex) ) );
1688 visit.emplace(vertex, deducedVisit );
1689 visit.emplace(exit(vertex), deducedVisit );
1690 }
1691 else {
1692 // deduce visit from unique predecessor
1693 assert( vertex->node->represents<BPMN::Activity>() );
1695
1696 auto& deducedVisit = model.addVariable(CP::Variable::Type::BOOLEAN, "visit_{" + vertex->shortReference() + "}" , visit.at( predecessor ) );
1697 visit.emplace(vertex, deducedVisit );
1698 visit.emplace(exit(vertex), deducedVisit );
1699// assert(!"Not yet implemented");
1700 }
1701 }
1702 else if ( vertex->inflows.size() > 1 ) {
1703//std::cerr << vertex->reference() << std::endl;
1704 if( !vertex->node->represents<BPMN::Gateway>() ) {
1705 throw std::runtime_error("CPModel: converging gateways must be explicit");
1706 }
1707 else {
1708 // deduce visit from incoming token flows
1709 CP::Expression incomingToken(false);
1710 for ( auto& [sequenceFlow, predecessor] : vertex->inflows ) {
1711 incomingToken = incomingToken || tokenFlow.at({predecessor,vertex});
1712 }
1713 auto& deducedVisit = model.addVariable(CP::Variable::Type::BOOLEAN, "visit_{" + vertex->shortReference() + "}" , incomingToken );
1714 visit.emplace(vertex, deducedVisit );
1715 visit.emplace(exit(vertex), deducedVisit );
1716//std::cerr << deducedVisit.stringify() << std::endl;
1717// assert(!"Not yet implemented");
1718 }
1719 }
1720 else {
1721 assert( vertex->inflows.empty() );
1722//std::cerr << vertex->jsonify().dump() << std::endl;
1723 throw std::logic_error("CPModel: Every vertex except process entry should have inflow!");
1724// assert(!"Not yet implemented");
1725 }
1726 }
1727 else if ( vertex->node->represents<BPMN::Process>() ) {
1728 // every process vertex is visited
1729 auto& knownVisit = model.addVariable(CP::Variable::Type::BOOLEAN, "visit_{" + vertex->shortReference() + "}", true, true );
1730//std::cerr << vertex << "//" << exit(vertex) << std::endl;
1731 visit.emplace(vertex, knownVisit );
1732 visit.emplace(exit(vertex), knownVisit );
1733 }
1734}
1735
1736void CPModel::createExitVariables(const Vertex* vertex) {
1737//std::cerr << "createExitVariables" << std::endl;
1738 if ( vertex->node->represents<BPMN::FlowNode>() ) {
1739 if ( auto loopCharacteristics = getLoopCharacteristics(vertex);
1740 loopCharacteristics.has_value() &&
1741 loopCharacteristics.value() == BPMN::Activity::LoopCharacteristics::Standard
1742 ) {
1743 if ( vertex->loopIndices.size() == flattenedGraph->loopIndexAttributes.at(vertex->node).size() ) {
1744 return;
1745 }
1746 }
1747
1748 // flow variables
1749 if ( vertex->outflows.size() == 1 ) {
1750 auto& [sequenceFlow,target] = vertex->outflows.front();
1751 if ( sequenceFlow ) {
1752 createSequenceFlowVariables( vertex, target );
1753 }
1754 }
1755 else if ( vertex->outflows.size() > 1 ) {
1756//std::cerr << vertex->reference() << std::endl;
1757 if( !vertex->node->represents<BPMN::Gateway>() ) {
1758 throw std::runtime_error("CPModel: diverging gateways must be explicit");
1759 }
1760 else if( vertex->node->represents<BPMN::EventBasedGateway>() ) {
1761 // create exclusive outflow
1762 for ( auto& [sequenceFlow,target] : vertex->outflows ) {
1763 tokenFlow.emplace(
1764 std::make_pair(vertex,target),
1765 model.addBinaryVariable("tokenflow_{" + vertex->reference() + " → " + target->reference() + "}" )
1766 );
1767 createStatusFlowVariables(vertex,target);
1768 }
1769 }
1770 else if( vertex->node->represents<BPMN::ComplexGateway>() ) {
1771 throw std::runtime_error("CPModel: complex gateways are not supported");
1772 }
1773 else if( vertex->node->represents<BPMN::ParallelGateway>() ) {
1774 // create unconditional outflow
1775 for ( auto& [sequenceFlow,target] : vertex->outflows ) {
1776 assert( sequenceFlow );
1777 createSequenceFlowVariables( vertex, target );
1778 }
1779 }
1780 else {
1781 // create conditional outflow
1782 for ( auto& [sequenceFlow,target] : vertex->outflows ) {
1783 assert( sequenceFlow );
1784 createSequenceFlowVariables( vertex, target, sequenceFlow->extensionElements->as<BPMNOS::Model::Gatekeeper>() );
1785 }
1786 if( vertex->node->represents<BPMN::ExclusiveGateway>() ) {
1787 CP::Expression outflows(0);
1788 for ( auto& [sequenceFlow,target] : vertex->outflows ) {
1789 outflows = outflows + tokenFlow.at({vertex,target});
1790 }
1791 model.addConstraint( outflows == visit.at(vertex) );
1792 }
1793 }
1794 }
1795 }
1796//std::cerr << "Done(exitvariables)" << std::endl;
1797}
1798
1799void CPModel::createSequenceFlowVariables(const Vertex* source, const Vertex* target, const BPMNOS::Model::Gatekeeper* gatekeeper) {
1800 if ( gatekeeper ) {
1801 CP::Expression gatekeeperCondition(true);
1802 for ( auto& condition : gatekeeper->conditions ) {
1803 gatekeeperCondition = gatekeeperCondition && createExpression(source,condition->expression);
1804 }
1805 tokenFlow.emplace(
1806 std::make_pair(source,target),
1807 model.addVariable(CP::Variable::Type::BOOLEAN, "tokenflow_{" + source->reference() + " → " + target->reference() + "}", visit.at(source) && gatekeeperCondition )
1808 );
1809 }
1810 else {
1811 tokenFlow.emplace(
1812 std::make_pair(source,target),
1813 model.addVariable(CP::Variable::Type::BOOLEAN, "tokenflow_{" + source->reference() + " → " + target->reference() + "}", visit.at(source) )
1814 );
1815 }
1816//std::cerr << "tokenFlow: " << source->reference() << " to " << target->reference() << std::endl;
1817 createStatusFlowVariables(source,target);
1818}
1819
1820void CPModel::createStatusFlowVariables(const Vertex* source, const Vertex* target) {
1821//std::cerr << "statusFlow: " << source->reference() << " to " << target->reference() << std::endl;
1822 assert( source->node->represents<BPMN::FlowNode>() );
1823 assert( source->node->as<BPMN::FlowNode>()->parent );
1824 auto extensionElements = source->node->as<BPMN::FlowNode>()->parent->extensionElements->as<BPMNOS::Model::ExtensionElements>();
1825 assert( extensionElements );
1826 std::vector<AttributeVariables> variables;
1827 variables.reserve( extensionElements->attributeRegistry.statusAttributes.size() );
1828 for ( auto attribute : extensionElements->attributeRegistry.statusAttributes ) {
1829//std::cerr << "statusAttribute: " << attribute->id << "/" << source->reference() << std::endl;
1830 assert( tokenFlow.contains({source,target}) );
1831 assert( status.contains(source) );
1832//std::cerr << "indices: " << attribute->index << " < " << status.at(source).size() << std::endl;
1833 assert( attribute->index < status.at(source).size() );
1834 // deduce variable
1835 variables.emplace_back(
1836 model.addVariable(CP::Variable::Type::BOOLEAN, "statusflow_defined_{" + source->reference() + " → " + target->reference() + "}," + attribute->id,
1837 CP::if_then_else(
1838 tokenFlow.at({source,target}),
1839 status.at(source)[attribute->index].defined,
1840 false
1841 )
1842 ),
1843 model.addVariable(CP::Variable::Type::REAL, "statusflow_value_{" + source->reference() + " → " + target->reference() + "}," + attribute->id,
1844 CP::if_then_else(
1845 tokenFlow.at({source,target}),
1846 status.at(source)[attribute->index].value,
1847 0.0
1848 )
1849 )
1850 );
1851 }
1852
1853 statusFlow.emplace(
1854 std::make_pair(source,target),
1855 std::move(variables)
1856 );
1857}
1858
1859void CPModel::createSequenceConstraints(const Vertex* vertex) {
1860 auto addConstraints = [&](const Vertex* predecessor) {
1861 assert( position.contains(predecessor) );
1862 assert( position.contains(vertex) );
1863 assert( visit.contains(vertex) );
1864 assert( status.contains(predecessor) );
1865 assert( status.at(predecessor).size() >= BPMNOS::Model::ExtensionElements::Index::Timestamp );
1866 assert( status.contains(vertex) );
1867 assert( status.at(vertex).size() >= BPMNOS::Model::ExtensionElements::Index::Timestamp );
1868 if ( vertex->exit<BPMN::TypedStartEvent>() ) {
1869 model.addConstraint( position.at(vertex) == position.at(entry(vertex)) + 1 );
1870 }
1871 else {
1872 model.addConstraint( position.at(predecessor) < position.at(vertex) );
1873 }
1874/*
1875std::cerr << predecessor->reference() << " before " << vertex->reference() << std::endl;
1876std::cerr << visit.at(vertex).stringify() << "\n implies \n";
1877std::cerr << status.at(predecessor)[BPMNOS::Model::ExtensionElements::Index::Timestamp].value.stringify() << "\n <= \n";
1878std::cerr << status.at(vertex)[BPMNOS::Model::ExtensionElements::Index::Timestamp].value.stringify() << std::endl;
1879*/
1880 if ( predecessor->node == vertex->node ) {
1881 model.addConstraint(
1882 // predecessor and vertex are at the same node
1883 ( visit.at(vertex) ).implies(
1884 status.at(predecessor)[BPMNOS::Model::ExtensionElements::Index::Timestamp].value
1885 <= status.at(vertex)[BPMNOS::Model::ExtensionElements::Index::Timestamp].value
1886 )
1887 );
1888 }
1889 else {
1890 model.addConstraint(
1891 // predecessor and vertex are at different nodes
1892 ( visit.at(predecessor) && visit.at(vertex) ).implies(
1893 status.at(predecessor)[BPMNOS::Model::ExtensionElements::Index::Timestamp].value
1894 <= status.at(vertex)[BPMNOS::Model::ExtensionElements::Index::Timestamp].value
1895 )
1896 );
1897 }
1898
1899//std::cerr << model.getConstraints().back().stringify() << std::endl;
1900 };
1901
1902 for ( auto& [_, predecessor] : vertex->inflows ) {
1903 addConstraints(predecessor);
1904 }
1905
1906 for ( auto predecessor : vertex->predecessors ) {
1907 addConstraints(predecessor);
1908 }
1909}
1910
1911void CPModel::constrainTypedStartEvent(const FlattenedGraph::Vertex* startEvent) {
1912//std::cerr << "TypedStartEvent " << startEvent->shortReference() << std::endl;
1913 auto parentExit = startEvent->parent.value().second;
1914 // the event-subprocess can be triggered until the last token of the parent is consumed
1915 for ( auto& [_, predecessor] : parentExit->inflows ) {
1916 if ( predecessor->node->as<BPMN::FlowNode>()->parent == parentExit->node ) {
1917 assert(visit.contains(startEvent));
1918//std::cerr << predecessor.reference() << std::endl;
1919 assert(visit.contains(predecessor));
1920 // if the event-subprocess is not triggered, then the position must be after the last visited end node
1921//auto& constraint =
1922 model.addConstraint(
1923 ( !visit.at(startEvent) && visit.at(predecessor) ).implies(
1924 position.at(predecessor) < position.at(startEvent)
1925 )
1926 );
1927//std::cerr << constraint.stringify() << std::endl;
1928 }
1929 }
1930
1931 std::vector<CP::Expression> visitedPositions;
1932 for ( auto predecessor : parentExit->predecessors ) {
1933//std::cerr << predecessor.reference() << std::endl;
1934 if (
1935 predecessor->type == Vertex::Type::EXIT &&
1936 ( predecessor->node->as<BPMN::FlowNode>()->parent == parentExit->node ) &&
1937 ( predecessor->node->represents<BPMN::Activity>() ||
1938 predecessor->node->represents<BPMN::MessageCatchEvent>()
1939 )
1940 ) {
1941 visitedPositions.push_back( visit.at(predecessor) * position.at(predecessor) );
1942 }
1943 }
1944 // ASSUMPTION: triggering of event-subprocesses does not depend on timer or conditional events in the main scope
1945 if ( visitedPositions.empty() ) {
1946 throw std::runtime_error("CPModel: event-subprocesses in a scope without activities and message catch events are not supported");
1947 }
1948 // if the event-subprocess is triggered, then the position must be before the last visited node that holds the token
1949//auto& constraint =
1950 model.addConstraint(
1951 visit.at(startEvent) * position.at(startEvent) <= CP::max( visitedPositions )
1952 );
1953//std::cerr << constraint.stringify() << std::endl;
1954}
1955
1956
1957void CPModel::createRestrictions(const Vertex* vertex) {
1958 auto extensionElements = vertex->node->extensionElements->represents<BPMNOS::Model::ExtensionElements>();
1959 if ( !extensionElements ) return;
1960
1961 for ( auto& restriction : extensionElements->restrictions ) {
1962 if (
1963 restriction->scope == Model::Restriction::Scope::FULL ||
1964 ( vertex->type == Vertex::Type::ENTRY && restriction->scope == Model::Restriction::Scope::ENTRY ) ||
1965 ( vertex->type == Vertex::Type::EXIT && restriction->scope != Model::Restriction::Scope::ENTRY )
1966 ) {
1967 // create constraints
1968 if ( restriction->expression.type == Model::Expression::Type::IS_NULL ) {
1969 assert( restriction->expression.variables.size() == 1 );
1970 auto attribute = restriction->expression.variables.front();
1971 auto [defined,value] = getAttributeVariables(vertex,attribute);
1972 model.addConstraint( visit.at(vertex).implies(defined == false) );
1973 }
1974 else if ( restriction->expression.type == Model::Expression::Type::IS_NOT_NULL ) {
1975 assert( restriction->expression.variables.size() == 1 );
1976 auto attribute = restriction->expression.variables.front();
1977 auto [defined,value] = getAttributeVariables(vertex,attribute);
1978 model.addConstraint( visit.at(vertex).implies(defined == true) );
1979 }
1980 else {
1981 assert( restriction->expression.type == Model::Expression::Type::OTHER );
1982 for ( auto attribute : restriction->expression.variables ) {
1983 // all variables in expression must be defined
1984 auto [defined,value] = getAttributeVariables(vertex,attribute);
1985 model.addConstraint( visit.at(vertex).implies(defined == true) );
1986 }
1987 // add restriction
1988 auto constraint = createExpression( vertex, restriction->expression );
1989 model.addConstraint( visit.at(vertex).implies(constraint) );
1990 }
1991 }
1992 }
1993}
1994
1995CP::Expression CPModel::createExpression(const Vertex* vertex, const Model::Expression& expression) {
1996 auto extensionElements = vertex->node->extensionElements->represents<BPMNOS::Model::ExtensionElements>();
1997 if ( !extensionElements ) {
1998 assert( vertex->node->represents<BPMN::FlowNode>()->parent );
1999 extensionElements = vertex->node->as<BPMN::FlowNode>()->parent->extensionElements->as<BPMNOS::Model::ExtensionElements>();
2000 }
2001
2002 auto compiled = LIMEX::Expression<CP::Expression,CP::Expression>(encodeQuotedStrings(expression.expression), limexHandle);
2003
2004 std::vector<CP::Expression> variables;
2005 for ( auto& variableName : compiled.getVariables() ) {
2006 auto attribute = extensionElements->attributeRegistry[variableName];
2007 if( attribute->type == ValueType::COLLECTION ) {
2008 throw std::runtime_error("CPModel: illegal expression '" + expression.expression + "'");
2009 }
2010
2011 auto [defined,value] = getAttributeVariables(vertex,attribute);
2012//std::cerr << value.stringify() << std::endl;
2013 variables.push_back( value );
2014 }
2015
2016 std::vector<CP::Expression> collectionVariables;
2017 for ( auto& variableName : compiled.getCollections() ) {
2018 auto attribute = extensionElements->attributeRegistry[variableName];
2019 if( attribute->type != ValueType::COLLECTION ) {
2020 throw std::runtime_error("CPModel: illegal expression '" + expression.expression + "'");
2021 }
2022
2023 auto [defined,value] = getAttributeVariables(vertex,attribute);
2024//std::cerr << value.stringify() << std::endl;
2025 collectionVariables.push_back( value );
2026 }
2027
2028 return compiled.evaluate(variables,collectionVariables);
2029}
2030
2031void CPModel::createGlobalIndexVariable(const Vertex* vertex) {
2032 CP::Expression index;
2033 for ( auto& [modifierEntry, modifierExit] : flattenedGraph->globalModifiers ) {
2034 // create auxiliary variables indicating modifiers preceding the vertex
2035 index = index + model.addVariable(CP::Variable::Type::BOOLEAN, "weakly_precedes_{" + modifierExit->reference() + " → " + vertex->reference() + "}", position.at(modifierExit) <= position.at(vertex) );
2036 }
2037 globalIndex.emplace( vertex, model.addVariable(CP::Variable::Type::INTEGER, "globals_index_{" + vertex->reference() + "}", index ) );
2038}
2039
2040
2041void CPModel::createDataIndexVariables(const Vertex* vertex) {
2042 CP::reference_vector< const CP::Variable > dataIndices;
2043 dataIndices.reserve( vertex->dataOwners.size() );
2044 for ( auto dataOwner : vertex->dataOwners ) {
2045 assert( flattenedGraph->dataModifiers.contains(dataOwner) );
2046 CP::Expression index;
2047 for ( auto& [modifierEntry, modifierExit] : flattenedGraph->dataModifiers.at(dataOwner) ) {
2048 // create auxiliary variables indicating modifiers preceding the vertex
2049 index = index + model.addVariable(CP::Variable::Type::BOOLEAN, "weakly_precedes_{" + modifierExit->reference() + " → " + vertex->reference() + "}", position.at(modifierExit) <= position.at(vertex) );
2050 }
2051 // data index for data owner represents the number of modifiers exited according to the sequence positions
2052 dataIndices.emplace_back( model.addVariable(CP::Variable::Type::INTEGER, "data_index[" + dataOwner->node->id + "]_{" + vertex->reference() + "}", index ) );
2053 }
2054 dataIndex.emplace( vertex, std::move(dataIndices) );
2055}
2056
2057void CPModel::addToObjective(const BPMNOS::Model::Attribute* attribute, const CP::Variable& variable) {
2058 if ( attribute->weight != 0.0 ) {
2059 model.setObjective( model.getObjective() + attribute->weight * variable );
2060 }
2061}
2062
2063void CPModel::addObjectiveCoefficients(const Vertex* vertex) {
2064 assert( vertex->type == Vertex::Type::EXIT );
2065 auto loopCharacteristics = getLoopCharacteristics(vertex);
2066 if (
2067 flattenedGraph->dummies.contains(vertex) &&
2068 loopCharacteristics.has_value() &&
2069 loopCharacteristics.value() != BPMN::Activity::LoopCharacteristics::Standard
2070 ) {
2071 // for multi-instance activities, attributes are only added to vertices representing and instantiation
2072 return;
2073 }
2074
2075 if ( auto extensionElements = vertex->node->extensionElements->represents<BPMNOS::Model::ExtensionElements>() ) {
2076 for ( size_t i = extensionElements->attributeRegistry.statusAttributes.size() - extensionElements->attributes.size(); i < extensionElements->attributeRegistry.statusAttributes.size(); i++) {
2077 auto attribute = extensionElements->attributeRegistry.statusAttributes[i];
2078 addToObjective( attribute, status.at(vertex)[attribute->index].value );
2079 }
2080 }
2081}
2082
2083#endif // USE_CP
2084
CollectionRegistry collectionRegistry
std::vector< std::pair< const BPMN::SequenceFlow *, Vertex * > > outflows
Container holding vertices connecting by an incoming sequence flow, for loop activities the sequence ...
std::vector< Vertex * > senders
Container holding successors according to the execution logic (excl. sequence flows)
std::vector< std::pair< const BPMN::SequenceFlow *, Vertex * > > inflows
Parent vertices.
std::string shortReference() const
Returns a unique reference of the vertex.
std::vector< Vertex * > predecessors
Container holding vertices connecting by an outgoing sequence flow, for loop activities the sequence ...
std::optional< std::pair< Vertex *, Vertex * > > parent
Represents a graph containing all BPMN nodes that may receive a token during execution of a scenario.
std::unordered_set< const Vertex * > dummies
Container holding entry and exit vertices of each possible instantiation of a node.
std::vector< const Vertex * > sortVertices() const
Container holding entry vertices of all process instances.
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...
std::unordered_map< const Vertex *, std::vector< std::pair< const Vertex *, const Vertex * > > > sequentialActivities
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::unordered_map< const BPMN::Node *, std::vector< const BPMNOS::Model::Attribute * > > loopIndexAttributes
std::vector< std::unique_ptr< Vertex > > vertices
Returns a topologically sorted vector of all vertices reachable from the initial vertices.
std::vector< Attribute * > statusAttributes
double weight
Weight to be used for objective (assuming maximization).
Definition Attribute.h:36
std::unique_ptr< const Expression > expression
Definition Attribute.h:31
size_t index
Index of attribute (is automatically set by attribute registry).
Definition Attribute.h:28
bool isImmutable
Flag indicating whether attribute value may be changed by operator, choice, or intermediate catch eve...
Definition Attribute.h:38
Class representing a choice to be made within a BPMNOS::Model::DecisionTask.
Definition Choice.h:21
Class representing a task in which one or more choices have to be made.
Class representing a mathematical expression.
Definition Expression.h:17
const AttributeRegistry & attributeRegistry
Definition Expression.h:30
const std::string expression
Definition Expression.h:32
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< MessageDefinition > > messageDefinitions
Vector containing message definition(s) provided for the node.
Class holding extension elements representing gatekeeper conditions for sequence flows.
Definition Gatekeeper.h:17
std::vector< std::unique_ptr< Restriction > > conditions
Definition Gatekeeper.h:21
ContentMap contentMap
Map allowing to look up contents by their keys.
std::unique_ptr< const Expression > expression
Definition Parameter.h:24
Class representing adhoc subprocesses with sequential ordering.
A scenario implementation where all data is known from the start.
Class holding extension elements representing the trigger of timer events.
Definition Timer.h:16
std::unique_ptr< BPMNOS::Model::Parameter > trigger
Definition Timer.h:21
std::optional< LoopCharacteristics > loopCharacteristics
Definition bpmn++.h:17411
std::unique_ptr< ExtensionElements > extensionElements
Definition bpmn++.h:16299
Scope * parent
Reference to the parent node.
Definition bpmn++.h:16592
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
Base class for BPMN elements that may contain incoming and outgoing sequence flows.
Definition bpmn++.h:16694
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:16880
std::string encodeQuotedStrings(std::string text)
STL namespace.