BPMN-OS
BPMN for optimization and simulation
Loading...
Searching...
No Matches
StochasticDataProvider.cpp
Go to the documentation of this file.
7#include <algorithm>
8#include <ranges>
9
10using namespace BPMNOS::Model;
11
13 const std::string& instanceFileOrString,
14 unsigned int seed)
15 : StochasticDataProvider(modelFile, {}, instanceFileOrString, seed)
16{
17}
18
20 const std::vector<std::string>& folders,
21 const std::string& instanceFileOrString,
22 unsigned int seed)
23 : DataProvider(modelFile, folders)
24 , reader(CSVReader(instanceFileOrString))
25 , seed(seed)
26 , columnCount(0)
27{
29
30 // Evaluate global attributes
31 for (auto& [attributeId, attribute] : attributes[nullptr]) {
32 if (attribute->expression) {
33 Values globals(model->attributes.size());
34 for (auto& [globalAttribute, globalValue] : globalValueMap) {
35 globals[globalAttribute->index] = globalValue;
36 }
37 auto value = attribute->expression->execute(Values{}, Values{}, globals);
38 if (!value.has_value()) {
39 throw std::runtime_error("StochasticDataProvider: failed to evaluate global attribute '" + attribute->id + "'");
40 }
41 globalValueMap[attribute] = value.value();
42 }
43 }
44
45 earliestInstantiation = std::numeric_limits<BPMNOS::number>::max();
46 latestInstantiation = std::numeric_limits<BPMNOS::number>::min();
48}
49
51 // Copy lookup tables from model
52 for (auto& lookupTable : model->lookupTables) {
53 auto* table = lookupTable.get();
54 stochasticHandle.add(table->name, [table](const std::vector<double>& args) {
55 return table->at(args);
56 });
57 }
58
59 // Register random functions (skips if name already taken by lookup table)
61}
62
65 if (table.empty()) {
66 throw std::runtime_error("StochasticDataProvider: table '" + reader.instanceFileOrString + "' is empty");
67 }
68
69 if (table.size() < 2) {
70 throw std::runtime_error("StochasticDataProvider: table must have at least a header and one data row");
71 }
72
73 columnCount = table[0].size();
74 if (columnCount != 3 && columnCount != 4 && columnCount != 6) {
75 throw std::runtime_error("StochasticDataProvider: expected 3, 4, or 6 columns, got " + std::to_string(columnCount));
76 }
77
78 // Column indices
79 enum { INSTANCE_ID, NODE_ID, INITIALIZATION, DISCLOSURE, ARRIVAL, COMPLETION };
80
81 for (auto& row : table | std::views::drop(1)) {
82 if (row.empty()) {
83 continue;
84 }
85 if (row.size() != columnCount) {
86 throw std::runtime_error("StochasticDataProvider: inconsistent number of cells");
87 }
88
89 // Get instance ID
90 if (!std::holds_alternative<std::string>(row.at(INSTANCE_ID))) {
91 throw std::runtime_error("StochasticDataProvider: illegal instance id");
92 }
93 std::string instanceIdentifier = std::get<std::string>(row.at(INSTANCE_ID));
94
95 // Get node ID
96 if (!std::holds_alternative<std::string>(row.at(NODE_ID))) {
97 throw std::runtime_error("StochasticDataProvider: illegal node id");
98 }
99 std::string nodeId = std::get<std::string>(row.at(NODE_ID));
100
101 // Get initialization expression
102 if (!std::holds_alternative<std::string>(row.at(INITIALIZATION))) {
103 throw std::runtime_error("StochasticDataProvider: illegal initialization");
104 }
105 std::string initialization = std::get<std::string>(row.at(INITIALIZATION));
106
107 // Get disclosure (if 4+ columns)
108 std::string disclosureExpression;
109 if (columnCount >= 4) {
110 if (!std::holds_alternative<std::string>(row.at(DISCLOSURE))) {
111 throw std::runtime_error("StochasticDataProvider: illegal disclosure");
112 }
113 disclosureExpression = std::get<std::string>(row.at(DISCLOSURE));
114 }
115
116 // Get arrival (if 6 columns)
117 std::string arrivalExpression;
118 if (columnCount == 6) {
119 if (!std::holds_alternative<std::string>(row.at(ARRIVAL))) {
120 throw std::runtime_error("StochasticDataProvider: illegal arrival");
121 }
122 arrivalExpression = std::get<std::string>(row.at(ARRIVAL));
123 }
124
125 // Get completion (if 6 columns)
126 std::string completionExpression;
127 if (columnCount == 6) {
128 if (!std::holds_alternative<std::string>(row.at(COMPLETION))) {
129 throw std::runtime_error("StochasticDataProvider: illegal completion");
130 }
131 completionExpression = std::get<std::string>(row.at(COMPLETION));
132 }
133
134 // Handle global attributes
135 if (instanceIdentifier.empty() && nodeId.empty()) {
136 if (!disclosureExpression.empty()) {
137 throw std::runtime_error("StochasticDataProvider: global attributes must not have disclosure");
138 }
139 if (!arrivalExpression.empty()) {
140 throw std::runtime_error("StochasticDataProvider: global attributes must not have arrival");
141 }
142 if (!completionExpression.empty()) {
143 throw std::runtime_error("StochasticDataProvider: global attributes must not have completion");
144 }
145 if (initialization.empty()) {
146 continue;
147 }
148
149 auto [attributeName, expressionString] = parseInitialization(initialization);
150 const Attribute* attribute = nullptr;
151 for (auto& [id, globalAttribute] : attributes[nullptr]) {
152 if (globalAttribute->name == attributeName) {
153 attribute = globalAttribute;
154 break;
155 }
156 }
157 if (!attribute) {
158 throw std::runtime_error("StochasticDataProvider: unknown global attribute '" + attributeName + "'");
159 }
160
161 Values globals(model->attributes.size());
162 for (auto& [globalAttribute, globalValue] : globalValueMap) {
163 globals[globalAttribute->index] = globalValue;
164 }
165
166 Expression expression(stochasticHandle, expressionString, model->attributeRegistry);
167 auto value = expression.execute(Values{}, Values{}, globals);
168 if (!value.has_value()) {
169 throw std::runtime_error("StochasticDataProvider: failed to evaluate global '" + attributeName + "'");
170 }
171 globalValueMap[attribute] = value.value();
172 }
173 else if (instanceIdentifier.empty()) {
174 throw std::runtime_error("StochasticDataProvider: instance id required when node id is provided");
175 }
176 else {
177 auto instanceId = (size_t)BPMNOS::to_number(instanceIdentifier, STRING);
178 BPMN::Node* node = findNode(nodeId);
179
180 // First occurrence of instance must have node = process
181 if (!instances.contains(instanceId)) {
182 if (!node->represents<BPMN::Process>()) {
183 throw std::runtime_error("StochasticDataProvider: first row for instance '" + instanceIdentifier +
184 "' must reference a process node, got '" + nodeId + "'");
185 }
186 auto process = dynamic_cast<BPMN::Process*>(node);
187 instances[instanceId] = StochasticInstanceData{process, instanceId,
188 std::numeric_limits<BPMNOS::number>::max(), {}};
189 disclosure[instanceId][process] = 0;
190 }
191
192 auto& instance = instances[instanceId];
193
194 // Handle COMPLETION expression (only valid for Tasks, not SendTask/ReceiveTask/DecisionTask)
195 if (!completionExpression.empty()) {
196 if (!node->represents<BPMN::Task>() ||
197 node->represents<BPMN::SendTask>() ||
198 node->represents<BPMN::ReceiveTask>() ||
199 node->represents<DecisionTask>()) {
200 throw std::runtime_error("StochasticDataProvider: COMPLETION only valid for Task nodes, not '" +
201 nodeId + "'");
202 }
203
204 auto [attributeName, expressionString] = parseInitialization(completionExpression);
205 auto extensionElements = node->extensionElements->as<BPMNOS::Model::ExtensionElements>();
206 if (!extensionElements->attributeRegistry.contains(attributeName)) {
207 throw std::runtime_error("StochasticDataProvider: node '" + nodeId +
208 "' has no attribute '" + attributeName + "'");
209 }
210
211 auto attribute = extensionElements->attributeRegistry[attributeName];
212 auto expression = std::make_unique<Expression>(stochasticHandle, expressionString,
213 extensionElements->attributeRegistry);
214 completionExpressions[instanceId][node].push_back({attribute, std::move(expression)});
215 }
216
217 // Handle ARRIVAL expression (valid for all Activity types)
218 if (!arrivalExpression.empty()) {
219 if (!node->represents<BPMN::Activity>()) {
220 throw std::runtime_error("StochasticDataProvider: ARRIVAL only valid for Activity nodes, not '" +
221 nodeId + "'");
222 }
223
224 auto [attributeName, expressionString] = parseInitialization(arrivalExpression);
225 auto extensionElements = node->extensionElements->as<BPMNOS::Model::ExtensionElements>();
226 if (!extensionElements->attributeRegistry.contains(attributeName)) {
227 throw std::runtime_error("StochasticDataProvider: node '" + nodeId +
228 "' has no attribute '" + attributeName + "'");
229 }
230
231 auto attribute = extensionElements->attributeRegistry[attributeName];
232 if (attribute->expression) {
233 throw std::runtime_error("StochasticDataProvider: attribute '" + attributeName +
234 "' has model expression and cannot use ARRIVAL");
235 }
236
237 // Check mutual exclusivity with INITIALIZATION
238 if (instance.data.contains(attribute)) {
239 throw std::runtime_error("StochasticDataProvider: attribute '" + attributeName +
240 "' cannot use both ARRIVAL and INITIALIZATION");
241 }
242 if (pendingDisclosures.contains(instanceId)) {
243 for (auto& pending : pendingDisclosures.at(instanceId)) {
244 if (pending.attribute == attribute) {
245 throw std::runtime_error("StochasticDataProvider: attribute '" + attributeName +
246 "' cannot use both ARRIVAL and INITIALIZATION");
247 }
248 }
249 }
250
251 auto expression = std::make_unique<Expression>(stochasticHandle, expressionString,
252 extensionElements->attributeRegistry);
253 arrivalExpressions[instanceId][node].push_back({attribute, std::move(expression)});
254 }
255
256 // Handle INITIALIZATION
257 if (initialization.empty()) {
258 continue;
259 }
260
261 auto [attributeName, expressionString] = parseInitialization(initialization);
262 auto extensionElements = node->extensionElements->as<BPMNOS::Model::ExtensionElements>();
263 if (!extensionElements->attributeRegistry.contains(attributeName)) {
264 throw std::runtime_error("StochasticDataProvider: node '" + nodeId +
265 "' has no attribute '" + attributeName + "'");
266 }
267
268 auto attribute = extensionElements->attributeRegistry[attributeName];
269 if (attribute->expression) {
270 throw std::runtime_error("StochasticDataProvider: value of attribute '" + attributeName +
271 "' is initialized by expression and must not be provided explicitly");
272 }
273
274 // Check mutual exclusivity with ARRIVAL
275 if (arrivalExpressions.contains(instanceId) && arrivalExpressions.at(instanceId).contains(node)) {
276 for (auto& arrival : arrivalExpressions.at(instanceId).at(node)) {
277 if (arrival.attribute == attribute) {
278 throw std::runtime_error("StochasticDataProvider: attribute '" + attributeName +
279 "' cannot use both INITIALIZATION and ARRIVAL");
280 }
281 }
282 }
283
284 // Parse disclosure time
285 BPMNOS::number ownDisclosure = 0;
286 if (!disclosureExpression.empty()) {
287 ownDisclosure = evaluateExpression(disclosureExpression);
288 }
289
290 BPMNOS::number disclosureTime = getEffectiveDisclosure(instanceId, node, ownDisclosure);
291
292 // Evaluate expression at parse time (globals only)
293 BPMNOS::number value = evaluateExpression(expressionString);
294
295 if (disclosureTime == 0) {
296 // Immediate disclosure: store value directly
297 instance.data[attribute] = value;
298 }
299 else {
300 // Deferred disclosure: store pre-computed value for later reveal
301 pendingDisclosures[instanceId].push_back({attribute, disclosureTime, value});
302 }
303 }
304 }
305
306 // Finalize instances
307 for (auto& [id, instance] : instances) {
310 instance.instantiation = instance.data.at(attributes[instance.process][Keyword::Timestamp]);
311
312 BPMNOS::number effectiveInstantiation = instance.instantiation;
313 if (disclosure.contains(id) && disclosure.at(id).contains(instance.process)) {
314 effectiveInstantiation = std::max(effectiveInstantiation, disclosure.at(id).at(instance.process));
315 }
316
317 if (earliestInstantiation > effectiveInstantiation) {
318 earliestInstantiation = effectiveInstantiation;
319 }
320 if (latestInstantiation < effectiveInstantiation) {
321 latestInstantiation = effectiveInstantiation;
322 }
323 }
324}
325
326std::pair<std::string, std::string> StochasticDataProvider::parseInitialization(
327 const std::string& initialization) const {
328 auto pos = initialization.find(":=");
329 if (pos == std::string::npos) {
330 throw std::runtime_error("StochasticDataProvider: initialization must be 'attribute := expression', got '" +
331 initialization + "'");
332 }
333
334 std::string attributeName = initialization.substr(0, pos);
335 std::string expression = initialization.substr(pos + 2);
336
337 auto trimStart = attributeName.find_first_not_of(" \t");
338 auto trimEnd = attributeName.find_last_not_of(" \t");
339 if (trimStart == std::string::npos) {
340 throw std::runtime_error("StochasticDataProvider: empty attribute name in '" + initialization + "'");
341 }
342 attributeName = attributeName.substr(trimStart, trimEnd - trimStart + 1);
343
344 trimStart = expression.find_first_not_of(" \t");
345 trimEnd = expression.find_last_not_of(" \t");
346 if (trimStart == std::string::npos) {
347 throw std::runtime_error("StochasticDataProvider: empty expression in '" + initialization + "'");
348 }
349 expression = expression.substr(trimStart, trimEnd - trimStart + 1);
350
351 return {attributeName, expression};
352}
353
354BPMNOS::number StochasticDataProvider::evaluateExpression(const std::string& expressionString) const {
355 Values globals(model->attributes.size());
356 for (auto& [attribute, value] : globalValueMap) {
357 globals[attribute->index] = value;
358 }
359 Expression expression(stochasticHandle, expressionString, model->attributeRegistry);
360 for (auto* attribute : expression.variables) {
361 if (attribute->category != Attribute::Category::GLOBAL) {
362 throw std::runtime_error("StochasticDataProvider: expression '" + expressionString +
363 "' references non-global attribute '" + attribute->name + "'");
364 }
365 }
366 auto value = expression.execute(Values{}, Values{}, globals);
367 if (!value.has_value()) {
368 throw std::runtime_error("StochasticDataProvider: failed to evaluate expression '" + expressionString + "'");
369 }
370 return value.value();
371}
372
374 BPMNOS::number ownDisclosure) {
375 BPMNOS::number effectiveDisclosure = ownDisclosure;
376
377 if (auto childNode = node->represents<BPMN::ChildNode>()) {
378 auto parentNode = childNode->parent;
379 if (!disclosure[instanceId].contains(parentNode)) {
380 throw std::runtime_error("StochasticDataProvider: disclosure for '" + node->id +
381 "' given before parent '" + parentNode->id + "'");
382 }
383 effectiveDisclosure = std::max(effectiveDisclosure, disclosure[instanceId][parentNode]);
384 }
385
386 if (!disclosure[instanceId].contains(node)) {
387 disclosure[instanceId][node] = effectiveDisclosure;
388 }
389 else {
390 disclosure[instanceId][node] = std::max(disclosure[instanceId][node], effectiveDisclosure);
391 }
392
393 return effectiveDisclosure;
394}
395
397 const std::string attributeId,
398 std::optional<BPMNOS::number> value) {
399 assert(attributes.contains(instance.process));
400 auto it1 = attributes.at(instance.process).find(attributeId);
401 if (it1 == attributes.at(instance.process).end()) {
402 throw std::runtime_error("StochasticDataProvider: unable to find required attribute '" +
403 attributeId + "' for process '" + instance.process->id + "'");
404 }
405
406 auto attribute = it1->second;
407 if (auto it2 = instance.data.find(attribute); it2 == instance.data.end()) {
408 if (attribute->expression) {
409 throw std::runtime_error("StochasticDataProvider: initial value of default attribute '" +
410 attribute->id + "' must not be provided by expression");
411 }
412
413 if (value.has_value()) {
414 instance.data[attribute] = value.value();
415 }
416 else if (attributeId == BPMNOS::Keyword::Timestamp) {
417 instance.data[attribute] = 0;
418 }
419 else {
420 throw std::runtime_error("StochasticDataProvider: attribute '" + attribute->id + "' has no default value");
421 }
422 }
423}
424
425std::unique_ptr<Scenario> StochasticDataProvider::createScenario(unsigned int scenarioId) {
426 auto scenario = std::make_unique<StochasticScenario>(model.get(), earliestInstantiation,
428 seed + scenarioId);
429
430 // Set the random factory reference
431 scenario->randomFactory = &randomFactory;
432
433 for (auto& [id, instance] : instances) {
434 auto& timestampAttribute = attributes[instance.process][Keyword::Timestamp];
435 auto instantiationTime = instance.data[timestampAttribute];
436 scenario->addInstance(instance.process, id, instantiationTime);
437 for (auto& [attribute, value] : instance.data) {
438 scenario->setValue(id, attribute, value);
439 }
440 }
441
442 // Set node disclosure times
443 for (auto& [instanceId, nodes] : disclosure) {
444 for (auto& [node, disclosureTime] : nodes) {
445 scenario->setDisclosure(instanceId, node, disclosureTime);
446 }
447 }
448
449 // Add pending disclosures (pre-computed values)
450 for (auto& [instanceId, pendings] : pendingDisclosures) {
451 for (auto& pending : pendings) {
452 scenario->addPendingDisclosure(instanceId, {pending.attribute, pending.disclosureTime, pending.value});
453 }
454 }
455
456 // Add completion expressions
457 for (auto& [instanceId, tasks] : completionExpressions) {
458 for (auto& [task, expressions] : tasks) {
459 for (auto& sourceExpression : expressions) {
460 auto expression = std::make_unique<Expression>(stochasticHandle,
461 sourceExpression.expression->expression,
462 sourceExpression.expression->attributeRegistry);
463 scenario->addCompletionExpression(instanceId, task, {sourceExpression.attribute, std::move(expression)});
464 }
465 }
466 }
467
468 // Add arrival expressions
469 for (auto& [instanceId, nodes] : arrivalExpressions) {
470 for (auto& [node, expressions] : nodes) {
471 for (auto& sourceExpression : expressions) {
472 auto expression = std::make_unique<Expression>(stochasticHandle,
473 sourceExpression.expression->expression,
474 sourceExpression.expression->attributeRegistry);
475 scenario->addArrivalExpression(instanceId, node, {sourceExpression.attribute, std::move(expression)});
476 }
477 }
478 }
479
480 // Reveal data disclosed at time 0
481 scenario->revealData(0);
482
483 return scenario;
484}
std::vector< Row > Table
Definition CSVReader.h:17
const std::string instanceFileOrString
Definition CSVReader.h:21
Abstract base class representing a data provider for BPMN instance data.
DataInput attributes
Map holding all attributes in the model with keys being the process (or nullptr for global attributes...
BPMN::Node * findNode(const std::string &nodeId) const
std::unique_ptr< Model > model
Pointer to the BPMN model.
Class representing a task in which one or more choices have to be made.
Class representing a mathematical expression.
Definition Expression.h:17
std::vector< const Attribute * > variables
Vector containing all input attributes used by the expression.
Definition Expression.h:37
std::optional< BPMNOS::number > execute(const BPMNOS::Values &status, const DataType &data, const BPMNOS::Values &globals) const
Class holding extension elements representing execution data for nodes.
AttributeRegistry attributeRegistry
Registry allowing to look up all status and data attributes by their names.
Data provider supporting stochastic behavior.
void ensureDefaultValue(StochasticInstanceData &instance, const std::string attributeId, std::optional< BPMNOS::number > value=std::nullopt)
std::unordered_map< size_t, std::unordered_map< const BPMN::Node *, BPMNOS::number > > disclosure
Node disclosure times.
StochasticDataProvider(const std::string &modelFile, const std::string &instanceFileOrString, unsigned int seed=0)
BPMNOS::number evaluateExpression(const std::string &expression) const
std::unordered_map< size_t, std::unordered_map< const BPMN::Node *, std::vector< CompletionExpression > > > completionExpressions
Completion expressions per (instance, node)
std::unordered_map< long unsigned int, StochasticInstanceData > instances
std::unordered_map< size_t, std::unordered_map< const BPMN::Node *, std::vector< ArrivalExpression > > > arrivalExpressions
Arrival expressions per (instance, node)
std::pair< std::string, std::string > parseInitialization(const std::string &initialization) const
BPMNOS::number getEffectiveDisclosure(size_t instanceId, const BPMN::Node *node, BPMNOS::number ownDisclosure)
LIMEX::Handle< double > stochasticHandle
Dedicated LIMEX handle with lookup tables + random functions.
RandomDistributionFactory randomFactory
Random distribution factory.
std::unordered_map< const Attribute *, BPMNOS::number > globalValueMap
std::unique_ptr< Scenario > createScenario(unsigned int scenarioId=0) override
std::unordered_map< size_t, std::vector< StochasticPendingDisclosure > > pendingDisclosures
Pending disclosures with expressions to evaluate at disclosure time.
void registerFunctions(LIMEX::Handle< double > &handle)
Register all random distribution functions with the given LIMEX handle.
std::unique_ptr< ExtensionElements > extensionElements
Definition bpmn++.h:16299
std::string id
Id of element.
Definition bpmn++.h:16298
Base class for BPMN elements within a Scope.
Definition bpmn++.h:16562
T * represents()
Attempts to cast the element to the specified type T.
Definition bpmn++.h:16236
Base class for all nodes in a BPMN model.
Definition bpmn++.h:16444
const std::string Instance
Definition Keywords.h:13
const std::string Timestamp
Definition Keywords.h:12
number to_number(const std::string &valueString, const ValueType &type)
Converts a string to a number.
Definition Number.cpp:59
BPMNOS_NUMBER_TYPE number
Definition Number.h:50
@ STRING
Definition Value.h:9
std::unordered_map< const Attribute *, BPMNOS::number > data