BPMN-OS
BPMN for optimization and simulation
Loading...
Searching...
No Matches
DynamicDataProvider.cpp
Go to the documentation of this file.
2#include "DynamicScenario.h"
9#include <unordered_map>
10#include <algorithm>
11#include <ranges>
12
13using namespace BPMNOS::Model;
14
15DynamicDataProvider::DynamicDataProvider(const std::string& modelFile, const std::string& instanceFileOrString)
16 : DynamicDataProvider(modelFile,{},instanceFileOrString)
17{
18}
19
20DynamicDataProvider::DynamicDataProvider(const std::string& modelFile, const std::vector<std::string>& folders, const std::string& instanceFileOrString)
21 : DataProvider(modelFile,folders)
22 , reader( CSVReader(instanceFileOrString) )
23{
24 for ( auto& [ attributeId, attribute ] : attributes[nullptr] ) {
25 if ( attribute->expression ) {
26 // Build globals vector from current globalValueMap
27 Values globals(model->attributes.size());
28 for ( auto& [attr, value] : globalValueMap ) {
29 globals[attr->index] = value;
30 }
31 auto value = attribute->expression->execute(Values{}, Values{}, globals);
32 if ( !value.has_value() ) {
33 throw std::runtime_error("DynamicDataProvider: failed to evaluate global attribute '" + attribute->id + "'");
34 }
35 globalValueMap[ attribute ] = value.value();
36 }
37 }
38 earliestInstantiation = std::numeric_limits<BPMNOS::number>::max();
39 latestInstantiation = std::numeric_limits<BPMNOS::number>::min();
41
42}
43
46 if ( table.empty() ) {
47 throw std::runtime_error("DynamicDataProvider: table '" + reader.instanceFileOrString + "' is empty");
48 }
49
50 if ( table.size() < 2 ) {
51 throw std::runtime_error("DynamicDataProvider: table must have at least a header and one data row");
52 }
53
54 size_t columnCount = table[0].size();
55 if ( columnCount != 4 ) {
56 throw std::runtime_error("DynamicDataProvider: expected 4 columns (INSTANCE_ID, NODE_ID, INITIALIZATION, DISCLOSURE), got " + std::to_string(columnCount));
57 }
58
59 // Parse table with format: INSTANCE_ID, NODE_ID, INITIALIZATION, DISCLOSURE
60 enum {INSTANCE_ID, NODE_ID, INITIALIZATION, DISCLOSURE};
61
62 for (auto &row : table | std::views::drop(1)) { // assume a single header line
63 if ( row.empty() ) {
64 continue;
65 }
66 if ( row.size() != 4 ) {
67 throw std::runtime_error("DynamicDataProvider: illegal number of cells");
68 }
69
70 // Get instance ID (may be empty for globals)
71 if ( !std::holds_alternative<std::string>(row.at(INSTANCE_ID)) ) {
72 throw std::runtime_error("DynamicDataProvider: illegal instance id");
73 }
74 std::string instanceIdStr = std::get<std::string>(row.at(INSTANCE_ID));
75
76 // Get node ID (may be empty for globals)
77 if ( !std::holds_alternative<std::string>(row.at(NODE_ID)) ) {
78 throw std::runtime_error("DynamicDataProvider: illegal node id");
79 }
80 std::string nodeId = std::get<std::string>(row.at(NODE_ID));
81
82 // Get initialization expression
83 if ( !std::holds_alternative<std::string>(row.at(INITIALIZATION)) ) {
84 throw std::runtime_error("DynamicDataProvider: illegal initialization");
85 }
86 std::string initialization = std::get<std::string>(row.at(INITIALIZATION));
87
88 // Get disclosure expression
89 if ( !std::holds_alternative<std::string>(row.at(DISCLOSURE)) ) {
90 throw std::runtime_error("DynamicDataProvider: illegal disclosure");
91 }
92 std::string disclosureStr = std::get<std::string>(row.at(DISCLOSURE));
93
94 if ( instanceIdStr.empty() && nodeId.empty() ) {
95 // Global attribute
96 if ( !disclosureStr.empty() ) {
97 throw std::runtime_error("DynamicDataProvider: global attributes must not have disclosure expression");
98 }
99 if ( initialization.empty() ) {
100 continue;
101 }
102 auto [attributeName, expressionString] = parseInitialization(initialization);
103 // Find global attribute by name
104 const Attribute* attribute = nullptr;
105 for ( auto& [id, globalAttribute] : attributes[nullptr] ) {
106 if ( globalAttribute->name == attributeName ) {
107 attribute = globalAttribute;
108 break;
109 }
110 }
111 if ( !attribute ) {
112 throw std::runtime_error("DynamicDataProvider: unknown global attribute '" + attributeName + "'");
113 }
114 // Build globals vector from current globalValueMap
115 Values globals(model->attributes.size());
116 for ( auto& [globalAttribute, value] : globalValueMap ) {
117 globals[globalAttribute->index] = value;
118 }
119 // Compile and evaluate using Expression
120 Expression expression(expressionString, model->attributeRegistry);
121 auto value = expression.execute(Values{}, Values{}, globals);
122 if ( !value.has_value() ) {
123 throw std::runtime_error("DynamicDataProvider: failed to evaluate global '" + attributeName + "'");
124 }
125 globalValueMap[attribute] = value.value();
126 }
127 else if ( instanceIdStr.empty() ) {
128 throw std::runtime_error("DynamicDataProvider: instance id required when node id is provided");
129 }
130 else {
131 auto instanceId = (size_t)BPMNOS::to_number(instanceIdStr, STRING);
132
133 // Find the node
134 BPMN::Node* node = findNode(nodeId);
135
136 // First occurrence of instance must have node = process
137 if ( !instances.contains(instanceId) ) {
138 // Check that node is a process
139 if ( !node->represents<BPMN::Process>() ) {
140 throw std::runtime_error("DynamicDataProvider: first row for instance '" + instanceIdStr + "' must reference a process node, got '" + nodeId + "'");
141 }
142 auto process = dynamic_cast<BPMN::Process*>(node);
143 instances[instanceId] = DynamicInstanceData({process, instanceId, std::numeric_limits<BPMNOS::number>::max(), {}});
144 // Initialize process disclosure to 0 (may be updated later if process attributes have disclosure)
145 disclosure[instanceId][process] = 0;
146 }
147
148 // If no initialization, just create instance (already done above)
149 if ( initialization.empty() ) {
150 continue;
151 }
152
153 auto& instance = instances[instanceId];
154 auto [attributeName, expressionString] = parseInitialization(initialization);
155
156 // Look up attribute in the node's extension elements
157 auto extensionElements = node->extensionElements->as<BPMNOS::Model::ExtensionElements>();
158 if ( !extensionElements->attributeRegistry.contains(attributeName) ) {
159 throw std::runtime_error("DynamicDataProvider: node '" + nodeId + "' has no attribute '" + attributeName + "'");
160 }
161
162 auto attribute = extensionElements->attributeRegistry[attributeName];
163 if ( attribute->expression ) {
164 throw std::runtime_error("DynamicDataProvider: value of attribute '" + attributeName + "' is initialized by expression and must not be provided explicitly");
165 }
166
167 // Parse disclosure time (must be constant)
168 BPMNOS::number ownDisclosure = 0;
169 if ( !disclosureStr.empty() ) {
170 ownDisclosure = evaluateExpression(disclosureStr);
171 }
172
173 // Compute effective disclosure = max(own, parent_scope_disclosure)
174 BPMNOS::number disclosureTime = getEffectiveDisclosure(instanceId, node, ownDisclosure);
175
176 // Evaluate expression at parse time (globals only)
177 BPMNOS::number value = evaluateExpression(expressionString);
178
179 if (disclosureTime == 0) {
180 // Immediate disclosure: store value directly
181 instance.data[attribute] = value;
182 }
183 else {
184 // Deferred disclosure: store pre-computed value for later reveal
185 deferredInitializations[instanceId].push_back({attribute, disclosureTime, value});
186 }
187 }
188 }
189
190 for (auto& [id, instance] : instances) {
191 // ensure that default attributes are available
194 // set time of instantiation
195 instance.instantiation = instance.data.at( attributes[instance.process][Keyword::Timestamp] );
196
197 // Effective instantiation time is max(instantiation, processDisclosure)
198 BPMNOS::number effectiveInstantiation = instance.instantiation;
199 if ( disclosure.contains(id) && disclosure.at(id).contains(instance.process) ) {
200 effectiveInstantiation = std::max(effectiveInstantiation, disclosure.at(id).at(instance.process));
201 }
202
203 if ( earliestInstantiation > effectiveInstantiation ) {
204 earliestInstantiation = effectiveInstantiation;
205 }
206 if ( latestInstantiation < effectiveInstantiation ) {
207 latestInstantiation = effectiveInstantiation;
208 }
209 }
210}
211
212std::pair<std::string, std::string> DynamicDataProvider::parseInitialization(const std::string& initialization) const {
213 // Parse "attributeName := expression"
214 auto pos = initialization.find(":=");
215 if ( pos == std::string::npos ) {
216 throw std::runtime_error("DynamicDataProvider: initialization must be in format 'attribute := expression', got '" + initialization + "'");
217 }
218
219 std::string attributeName = initialization.substr(0, pos);
220 std::string expression = initialization.substr(pos + 2);
221
222 // Trim whitespace
223 auto trimStart = attributeName.find_first_not_of(" \t");
224 auto trimEnd = attributeName.find_last_not_of(" \t");
225 if ( trimStart == std::string::npos ) {
226 throw std::runtime_error("DynamicDataProvider: empty attribute name in initialization '" + initialization + "'");
227 }
228 attributeName = attributeName.substr(trimStart, trimEnd - trimStart + 1);
229
230 trimStart = expression.find_first_not_of(" \t");
231 trimEnd = expression.find_last_not_of(" \t");
232 if ( trimStart == std::string::npos ) {
233 throw std::runtime_error("DynamicDataProvider: empty expression in initialization '" + initialization + "'");
234 }
235 expression = expression.substr(trimStart, trimEnd - trimStart + 1);
236
237 return {attributeName, expression};
238}
239
240BPMNOS::number DynamicDataProvider::evaluateExpression(const std::string& expressionString) const {
241 Values globals(model->attributes.size());
242 for (auto& [attribute, value] : globalValueMap) {
243 globals[attribute->index] = value;
244 }
245 Expression expression(expressionString, model->attributeRegistry);
246 for (auto* attribute : expression.variables) {
247 if (attribute->category != Attribute::Category::GLOBAL) {
248 throw std::runtime_error("DynamicDataProvider: expression '" + expressionString +
249 "' references non-global attribute '" + attribute->name + "'");
250 }
251 }
252 auto value = expression.execute(Values{}, Values{}, globals);
253 if (!value.has_value()) {
254 throw std::runtime_error("DynamicDataProvider: failed to evaluate expression '" + expressionString + "'");
255 }
256 return value.value();
257}
258
260 BPMNOS::number effectiveDisclosure = ownDisclosure;
261
262 // Check parent scope's disclosure time
263 if ( auto childNode = node->represents<BPMN::ChildNode>() ) {
264 auto parentNode = childNode->parent;
265 if ( !disclosure[instanceId].contains(parentNode) ) {
266 throw std::runtime_error("DynamicDataProvider: disclosure for '" + node->id + "' given before parent '" + parentNode->id + "'");
267 }
268 effectiveDisclosure = std::max(effectiveDisclosure, disclosure[instanceId][parentNode]);
269 }
270
271 // Update this scope's disclosure time (track maximum seen for this scope)
272 if ( !disclosure[instanceId].contains(node) ) {
273 disclosure[instanceId][node] = effectiveDisclosure;
274 }
275 else {
276 disclosure[instanceId][node] = std::max(disclosure[instanceId][node], effectiveDisclosure);
277 }
278
279 return effectiveDisclosure;
280}
281
282void DynamicDataProvider::ensureDefaultValue(DynamicInstanceData& instance, const std::string attributeId, std::optional<BPMNOS::number> value) {
283 assert( attributes.contains(instance.process) );
284 auto it1 = attributes.at(instance.process).find(attributeId);
285 if ( it1 == attributes.at(instance.process).end() ) {
286 throw std::runtime_error("DynamicDataProvider: unable to find required attribute '" + attributeId + "' for process '" + instance.process->id + "'");
287 }
288 auto attribute = it1->second;
289 if ( auto it2 = instance.data.find( attribute );
290 it2 == instance.data.end()
291 ) {
292 if ( attribute->expression ) {
293 throw std::runtime_error("DynamicDataProvider: initial value of default attribute '" + attribute->id + "' must not be provided by expression");
294 }
295
296 // set attribute value if available
297 if ( value.has_value() ) {
298 instance.data[ attribute ] = value.value();
299 }
300 else if ( attributeId == BPMNOS::Keyword::Timestamp ) {
301 // use 0 as fallback
302 instance.data[ attribute ] = 0;
303 }
304 else {
305 throw std::runtime_error("DynamicDataProvider: attribute '" + attribute->id + "' has no default value");
306 }
307 }
308}
309
310std::unique_ptr<Scenario> DynamicDataProvider::createScenario([[maybe_unused]] unsigned int scenarioId) {
311 auto scenario = std::make_unique<DynamicScenario>(model.get(), earliestInstantiation, latestInstantiation, globalValueMap);
312 for ( auto& [id, instance] : instances ) {
313 auto& timestampAttribute = attributes[instance.process][Keyword::Timestamp];
314 auto instantiationTime = instance.data[timestampAttribute];
315 scenario->addInstance(instance.process, id, instantiationTime);
316 for ( auto& [attribute, value] : instance.data ) {
317 scenario->setValue(id, attribute, value);
318 }
319 }
320 // Set node disclosure times (effective disclosure = max of own and parent scope)
321 for ( auto& [instanceId, nodes] : disclosure ) {
322 for ( auto& [node, disclosureTime] : nodes ) {
323 scenario->setDisclosure(instanceId, node, disclosureTime);
324 }
325 }
326 // Add deferred initializations (pre-computed values)
327 for (auto& [instanceId, pendings] : deferredInitializations) {
328 for (auto& pending : pendings) {
329 scenario->addPendingDisclosure(instanceId, {pending.attribute, pending.disclosureTime, pending.value});
330 }
331 }
332 // Reveal data disclosed at time 0
333 scenario->revealData(0);
334 return scenario;
335}
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 data provider for dynamic BPMN instance data.
std::unordered_map< const Attribute *, BPMNOS::number > globalValueMap
DynamicDataProvider(const std::string &modelFile, const std::string &instanceFileOrString)
Constructor for DynamicDataProvider.
std::unique_ptr< Scenario > createScenario(unsigned int scenarioId=0) override
void ensureDefaultValue(DynamicInstanceData &instance, const std::string attributeId, std::optional< BPMNOS::number > value=std::nullopt)
std::unordered_map< size_t, std::vector< DeferredInitialization > > deferredInitializations
Instance ID -> deferred inits.
std::unordered_map< long unsigned int, DynamicInstanceData > instances
BPMNOS::number getEffectiveDisclosure(size_t instanceId, const BPMN::Node *node, BPMNOS::number ownDisclosure)
std::unordered_map< size_t, std::unordered_map< const BPMN::Node *, BPMNOS::number > > disclosure
Instance ID -> Node -> time when node's data is disclosed.
BPMNOS::number evaluateExpression(const std::string &expression) const
std::pair< std::string, std::string > parseInitialization(const std::string &initialization) const
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.
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