BPMN-OS
BPMN for optimization and simulation
Loading...
Searching...
No Matches
StaticDataProvider.cpp
Go to the documentation of this file.
2#include "StaticScenario.h"
9#include <unordered_map>
10#include <algorithm>
11#include <ranges>
12#include <set>
13#include <sstream>
14#include <fstream>
15
16using namespace BPMNOS::Model;
17
18StaticDataProvider::StaticDataProvider(const std::string& modelFile, const std::string& instanceFileOrString)
19 : StaticDataProvider(modelFile,{},instanceFileOrString)
20{
21}
22
23StaticDataProvider::StaticDataProvider(const std::string& modelFile, const std::vector<std::string>& folders)
24 : DataProvider(modelFile, folders)
25{
26 for ( auto& [ attributeId, attribute ] : attributes[nullptr] ) {
27 if ( attribute->expression ) {
28 Values globals(model->attributes.size());
29 for ( auto& [attr, value] : globalValueMap ) {
30 globals[attr->index] = value;
31 }
32 auto value = attribute->expression->execute(Values{}, Values{}, globals);
33 if ( !value.has_value() ) {
34 throw std::runtime_error("StaticDataProvider: failed to evaluate global attribute '" + attribute->id + "'");
35 }
36 globalValueMap[ attribute ] = value.value();
37 }
38 }
39 earliestInstantiation = std::numeric_limits<BPMNOS::number>::max();
40 latestInstantiation = std::numeric_limits<BPMNOS::number>::min();
41}
42
43StaticDataProvider::StaticDataProvider(const std::string& modelFile, const std::vector<std::string>& folders, const std::string& instanceFileOrString)
44 : StaticDataProvider(modelFile, folders)
45{
46 reader = std::make_unique<CSVReader>(instanceFileOrString);
48}
49
51 CSVReader::Table table = reader->read();
52 if (table.empty()) {
53 throw std::runtime_error("StaticDataProvider: table '" + reader->instanceFileOrString + "' is empty");
54 }
55
56 if (table.size() < 2) {
57 throw std::runtime_error("StaticDataProvider: table must have at least a header and one data row");
58 }
59
60 size_t columnCount = table[0].size();
61 if (columnCount != 3) {
62 throw std::runtime_error("StaticDataProvider: expected 3 columns (INSTANCE_ID, NODE_ID, INITIALIZATION), got " + std::to_string(columnCount));
63 }
64
65 enum { INSTANCE_ID, NODE_ID, INITIALIZATION };
66
67 for (auto& row : table | std::views::drop(1)) {
68 if (row.empty()) {
69 continue;
70 }
71 if (row.size() != 3) {
72 throw std::runtime_error("StaticDataProvider: inconsistent number of cells");
73 }
74
75 // Get instance ID (may be empty for globals)
76 if (!std::holds_alternative<std::string>(row.at(INSTANCE_ID))) {
77 throw std::runtime_error("StaticDataProvider: illegal instance id");
78 }
79 std::string instanceIdentifier = std::get<std::string>(row.at(INSTANCE_ID));
80
81 // Get node ID (may be empty for globals)
82 if (!std::holds_alternative<std::string>(row.at(NODE_ID))) {
83 throw std::runtime_error("StaticDataProvider: illegal node id");
84 }
85 std::string nodeId = std::get<std::string>(row.at(NODE_ID));
86
87 // Get initialization expression
88 if (!std::holds_alternative<std::string>(row.at(INITIALIZATION))) {
89 throw std::runtime_error("StaticDataProvider: illegal initialization");
90 }
91 std::string initialization = std::get<std::string>(row.at(INITIALIZATION));
92
93 if (instanceIdentifier.empty() && nodeId.empty()) {
94 // Global attribute
95 if (initialization.empty()) {
96 continue;
97 }
98 auto [attributeName, expressionString] = parseInitialization(initialization);
99 // Find global attribute by name
100 const Attribute* attribute = nullptr;
101 for (auto& [id, globalAttribute] : attributes[nullptr]) {
102 if (globalAttribute->name == attributeName) {
103 attribute = globalAttribute;
104 break;
105 }
106 }
107 if (!attribute) {
108 throw std::runtime_error("StaticDataProvider: unknown global attribute '" + attributeName + "'");
109 }
110 // Build globals vector from current globalValueMap
111 Values globals(model->attributes.size());
112 for (auto& [globalAttribute, value] : globalValueMap) {
113 globals[globalAttribute->index] = value;
114 }
115 // Compile and evaluate using Expression
116 Expression expression(expressionString, model->attributeRegistry);
117 auto value = expression.execute(Values{}, Values{}, globals);
118 if (!value.has_value()) {
119 throw std::runtime_error("StaticDataProvider: failed to evaluate global '" + attributeName + "'");
120 }
121 globalValueMap[attribute] = value.value();
122 }
123 else if (instanceIdentifier.empty()) {
124 throw std::runtime_error("StaticDataProvider: instance id required when node id is provided");
125 }
126 else {
127 auto instanceId = (size_t)BPMNOS::to_number(instanceIdentifier, STRING);
128
129 // Find the node
130 BPMN::Node* node = findNode(nodeId);
131
132 // First occurrence of instance must have node = process
133 if (!instances.contains(instanceId)) {
134 // Check that node is a process
135 if (!node->represents<BPMN::Process>()) {
136 throw std::runtime_error("StaticDataProvider: first row for instance '" + instanceIdentifier + "' must reference a process node, got '" + nodeId + "'");
137 }
138 auto process = dynamic_cast<BPMN::Process*>(node);
139 instances[instanceId] = StaticInstanceData({process, instanceId, std::numeric_limits<BPMNOS::number>::max(), {}});
140 }
141
142 // If no initialization, just create instance (already done above)
143 if (initialization.empty()) {
144 continue;
145 }
146
147 auto& instance = instances[instanceId];
148 auto [attributeName, expressionString] = parseInitialization(initialization);
149
150 // Look up attribute in the node's extension elements
151 auto extensionElements = node->extensionElements->as<BPMNOS::Model::ExtensionElements>();
152 if (!extensionElements->attributeRegistry.contains(attributeName)) {
153 throw std::runtime_error("StaticDataProvider: node '" + nodeId + "' has no attribute '" + attributeName + "'");
154 }
155
156 auto attribute = extensionElements->attributeRegistry[attributeName];
157 if (attribute->expression) {
158 throw std::runtime_error("StaticDataProvider: value of attribute '" + attributeName + "' is initialized by expression and must not be provided explicitly");
159 }
160
161 instance.data[attribute] = evaluateExpression(expressionString);
162 }
163 }
164
165 for (auto& [id, instance] : instances) {
166 // ensure that default attributes are available
169 // set time of instantiation
170 instance.instantiation = instance.data.at( attributes[instance.process][Keyword::Timestamp] );
171
172 if ( earliestInstantiation > instance.instantiation ) {
173 earliestInstantiation = instance.instantiation;
174 }
175 if ( latestInstantiation < instance.instantiation ) {
176 latestInstantiation = instance.instantiation;
177 }
178 }
179}
180
181std::pair<std::string, std::string> StaticDataProvider::parseInitialization(const std::string& initialization) const {
182 // Parse "attributeName := expression"
183 auto pos = initialization.find(":=");
184 if ( pos == std::string::npos ) {
185 throw std::runtime_error("StaticDataProvider: initialization must be in format 'attribute := expression', got '" + initialization + "'");
186 }
187
188 std::string attributeName = initialization.substr(0, pos);
189 std::string expression = initialization.substr(pos + 2);
190
191 // Trim whitespace
192 auto trimStart = attributeName.find_first_not_of(" \t");
193 auto trimEnd = attributeName.find_last_not_of(" \t");
194 if ( trimStart == std::string::npos ) {
195 throw std::runtime_error("StaticDataProvider: empty attribute name in initialization '" + initialization + "'");
196 }
197 attributeName = attributeName.substr(trimStart, trimEnd - trimStart + 1);
198
199 trimStart = expression.find_first_not_of(" \t");
200 trimEnd = expression.find_last_not_of(" \t");
201 if ( trimStart == std::string::npos ) {
202 throw std::runtime_error("StaticDataProvider: empty expression in initialization '" + initialization + "'");
203 }
204 expression = expression.substr(trimStart, trimEnd - trimStart + 1);
205
206 return {attributeName, expression};
207}
208
209BPMNOS::number StaticDataProvider::evaluateExpression(const std::string& expressionString) const {
210 Values globals(model->attributes.size());
211 for (auto& [attribute, value] : globalValueMap) {
212 globals[attribute->index] = value;
213 }
214 Expression expression(expressionString, model->attributeRegistry);
215 for (auto* attribute : expression.variables) {
216 if (attribute->category != Attribute::Category::GLOBAL) {
217 throw std::runtime_error("StaticDataProvider: expression '" + expressionString +
218 "' references non-global attribute '" + attribute->name + "'");
219 }
220 }
221 auto value = expression.execute(Values{}, Values{}, globals);
222 if (!value.has_value()) {
223 throw std::runtime_error("StaticDataProvider: failed to evaluate expression '" + expressionString + "'");
224 }
225 return value.value();
226}
227
228void StaticDataProvider::ensureDefaultValue(StaticInstanceData& instance, const std::string attributeId, std::optional<BPMNOS::number> value) {
229 assert( attributes.contains(instance.process) );
230 auto it1 = attributes.at(instance.process).find(attributeId);
231 if ( it1 == attributes.at(instance.process).end() ) {
232 throw std::runtime_error("StaticDataProvider: unable to find required attribute '" + attributeId + "' for process '" + instance.process->id + "'");
233 }
234 auto attribute = it1->second;
235 if ( auto it2 = instance.data.find( attribute );
236 it2 == instance.data.end()
237 ) {
238 if ( attribute->expression ) {
239 throw std::runtime_error("StaticDataProvider: initial value of default attribute '" + attribute->id + "' must not be provided by expression");
240 }
241
242 // set attribute value if available
243 if ( value.has_value() ) {
244 instance.data[ attribute ] = value.value();
245 }
246 else if ( attributeId == BPMNOS::Keyword::Timestamp ) {
247 // use 0 as fallback
248 instance.data[ attribute ] = 0;
249 }
250 else {
251 throw std::runtime_error("StaticDataProvider: attribute '" + attribute->id + "' has no default value");
252 }
253 }
254}
255
256std::unique_ptr<Scenario> StaticDataProvider::createScenario([[maybe_unused]] unsigned int scenarioId) {
257 auto scenario = std::make_unique<StaticScenario>(model.get(), earliestInstantiation, latestInstantiation, globalValueMap);
258 for ( auto& [id, instance] : instances ) {
259 auto& timestampAttribute = attributes[instance.process][Keyword::Timestamp];
260 auto instantiationTime = instance.data[timestampAttribute];
261 scenario->addInstance(instance.process, id, instantiationTime);
262 for ( auto& [attribute, value] : instance.data ) {
263 scenario->setValue(id, attribute, value);
264 }
265 }
266 return scenario;
267}
std::vector< Row > Table
Definition CSVReader.h:17
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 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.
Class representing a data provider for static BPMN instance data.
std::pair< std::string, std::string > parseInitialization(const std::string &initialization) const
std::unordered_map< const Attribute *, BPMNOS::number > globalValueMap
virtual BPMNOS::number evaluateExpression(const std::string &expression) const
std::unique_ptr< Scenario > createScenario(unsigned int scenarioId=0) override
StaticDataProvider(const std::string &modelFile, const std::string &instanceFileOrString)
Constructor for StaticDataProvider.
std::unique_ptr< CSVReader > reader
std::unordered_map< long unsigned int, StaticInstanceData > instances
void ensureDefaultValue(StaticInstanceData &instance, const std::string attributeId, std::optional< BPMNOS::number > value=std::nullopt)
std::unique_ptr< ExtensionElements > extensionElements
Definition bpmn++.h:16299
std::string id
Id of element.
Definition bpmn++.h:16298
T * represents()
Attempts to cast the element to the specified type T.
Definition bpmn++.h:16236
Base class for 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