prosimos 2.0.3__tar.gz → 2.0.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. {prosimos-2.0.3 → prosimos-2.0.5}/PKG-INFO +1 -1
  2. prosimos-2.0.5/prosimos/all_attributes.py +79 -0
  3. prosimos-2.0.5/prosimos/branch_condition_parser.py +38 -0
  4. prosimos-2.0.5/prosimos/branch_condition_rules.py +106 -0
  5. {prosimos-2.0.3 → prosimos-2.0.5}/prosimos/case_attributes.py +10 -5
  6. {prosimos-2.0.3 → prosimos-2.0.5}/prosimos/control_flow_manager.py +56 -31
  7. prosimos-2.0.5/prosimos/event_attributes.py +245 -0
  8. prosimos-2.0.5/prosimos/event_attributes_parser.py +23 -0
  9. {prosimos-2.0.3 → prosimos-2.0.5}/prosimos/exceptions.py +11 -1
  10. prosimos-2.0.5/prosimos/gateway_condition_choice.py +15 -0
  11. prosimos-2.0.5/prosimos/global_attributes.py +73 -0
  12. prosimos-2.0.5/prosimos/global_attributes_parser.py +18 -0
  13. prosimos-2.0.5/prosimos/outgoing_flow_selector.py +121 -0
  14. {prosimos-2.0.3 → prosimos-2.0.5}/prosimos/simulation_engine.py +85 -50
  15. prosimos-2.0.5/prosimos/simulation_execution_stats.py +131 -0
  16. {prosimos-2.0.3 → prosimos-2.0.5}/prosimos/simulation_properties_parser.py +113 -4
  17. {prosimos-2.0.3 → prosimos-2.0.5}/prosimos/simulation_setup.py +13 -6
  18. prosimos-2.0.5/prosimos/warning_logger.py +30 -0
  19. {prosimos-2.0.3 → prosimos-2.0.5}/pyproject.toml +1 -1
  20. {prosimos-2.0.3 → prosimos-2.0.5}/README.md +0 -0
  21. {prosimos-2.0.3 → prosimos-2.0.5}/bpdfr_discovery/__init__.py +0 -0
  22. {prosimos-2.0.3 → prosimos-2.0.5}/bpdfr_discovery/emd_metric.py +0 -0
  23. {prosimos-2.0.3 → prosimos-2.0.5}/bpdfr_discovery/exceptions.py +0 -0
  24. {prosimos-2.0.3 → prosimos-2.0.5}/bpdfr_discovery/inter_arrival_cases_discovery.py +0 -0
  25. {prosimos-2.0.3 → prosimos-2.0.5}/bpdfr_discovery/log_comparison_metrics.py +0 -0
  26. {prosimos-2.0.3 → prosimos-2.0.5}/bpdfr_discovery/log_parser.py +0 -0
  27. {prosimos-2.0.3 → prosimos-2.0.5}/cli/__init__.py +0 -0
  28. {prosimos-2.0.3 → prosimos-2.0.5}/cli/diff_res_bpsim.py +0 -0
  29. {prosimos-2.0.3 → prosimos-2.0.5}/prosimos/__init__.py +0 -0
  30. {prosimos-2.0.3 → prosimos-2.0.5}/prosimos/batch_processing.py +0 -0
  31. {prosimos-2.0.3 → prosimos-2.0.5}/prosimos/batch_processing_parser.py +0 -0
  32. {prosimos-2.0.3 → prosimos-2.0.5}/prosimos/execution_info.py +0 -0
  33. {prosimos-2.0.3 → prosimos-2.0.5}/prosimos/file_manager.py +0 -0
  34. {prosimos-2.0.3 → prosimos-2.0.5}/prosimos/fuzzy_engine/event_log_analyser.py +0 -0
  35. {prosimos-2.0.3 → prosimos-2.0.5}/prosimos/fuzzy_engine/fuzzy_calendar.py +0 -0
  36. {prosimos-2.0.3 → prosimos-2.0.5}/prosimos/histogram_distribution.py +0 -0
  37. {prosimos-2.0.3 → prosimos-2.0.5}/prosimos/prioritisation.py +0 -0
  38. {prosimos-2.0.3 → prosimos-2.0.5}/prosimos/prioritisation_parser.py +0 -0
  39. {prosimos-2.0.3 → prosimos-2.0.5}/prosimos/prioritisation_rules.py +0 -0
  40. {prosimos-2.0.3 → prosimos-2.0.5}/prosimos/probability_distributions.py +0 -0
  41. {prosimos-2.0.3 → prosimos-2.0.5}/prosimos/resource_profile.py +0 -0
  42. {prosimos-2.0.3 → prosimos-2.0.5}/prosimos/simulation_queues_ds.py +0 -0
  43. {prosimos-2.0.3 → prosimos-2.0.5}/prosimos/simulation_stats.py +0 -0
  44. {prosimos-2.0.3 → prosimos-2.0.5}/prosimos/simulation_stats_calculator.py +0 -0
  45. {prosimos-2.0.3 → prosimos-2.0.5}/prosimos/weekday_helper.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: prosimos
3
- Version: 2.0.3
3
+ Version: 2.0.5
4
4
  Summary:
5
5
  Author: Iryna Halenok
6
6
  Author-email: iryna.halenok@ut.ee
@@ -0,0 +1,79 @@
1
+ from prosimos.event_attributes import AllEventAttributes
2
+ from prosimos.case_attributes import AllCaseAttributes
3
+ from prosimos.global_attributes import AllGlobalAttributes
4
+
5
+
6
+ class AllAttributes:
7
+ def __init__(self, global_attributes, case_attributes, event_attributes):
8
+ global_attribute_names = {attr for attr in global_attributes.attributes}
9
+ all_case_attribute_names = {attr.name for attr in case_attributes.attributes}
10
+ all_event_attribute_names = set()
11
+ for event in event_attributes.attributes:
12
+ attrs = event_attributes.attributes[event]
13
+ all_event_attribute_names.update(attrs.keys())
14
+
15
+ global_case_attribute_names = global_attribute_names.intersection(all_case_attribute_names)
16
+ global_event_attribute_names = global_attribute_names.intersection(all_event_attribute_names)
17
+
18
+ self.global_attribute_initial_values = global_attributes.get_values_calculated()
19
+ self.global_attributes = self._extract_global_attributes(global_attributes, global_case_attribute_names, global_event_attribute_names)
20
+
21
+ self.global_case_attributes, self.case_attributes = \
22
+ self._extract_case_attributes(case_attributes, global_case_attribute_names)
23
+
24
+ self.global_event_attributes, self.event_attributes = \
25
+ self._extract_event_attributes(event_attributes, global_event_attribute_names)
26
+
27
+ self.event_attribute_names = [attr_name for inner_dict in self.event_attributes.attributes.values() for attr_name in inner_dict.keys()]
28
+
29
+ local_case_attribute_names = all_case_attribute_names - global_case_attribute_names
30
+ local_event_attribute_names = all_event_attribute_names - global_event_attribute_names
31
+ attributes_intersection = local_case_attribute_names.intersection(local_event_attribute_names)
32
+ if attributes_intersection:
33
+ raise ValueError(f"Case attributes cannot be changed by event attributes. Check {attributes_intersection} attributes")
34
+
35
+ def get_all_columns_generated(self):
36
+ attributes_collections = [
37
+ self.global_attributes,
38
+ self.global_case_attributes,
39
+ self.global_event_attributes,
40
+ self.case_attributes,
41
+ self.event_attributes
42
+ ]
43
+ columns = [attr.get_columns_generated() for attr in attributes_collections]
44
+ return [item for sublist in columns for item in sorted(sublist)]
45
+
46
+ def _extract_case_attributes(self, case_attributes, global_case_attribute_names):
47
+ global_case_attributes = list()
48
+ local_case_attributes = list()
49
+
50
+ for attr in case_attributes.attributes:
51
+ if attr.name in global_case_attribute_names:
52
+ global_case_attributes.append(attr)
53
+ else:
54
+ local_case_attributes.append(attr)
55
+
56
+ return AllCaseAttributes(global_case_attributes), AllCaseAttributes(local_case_attributes)
57
+
58
+ def _extract_event_attributes(self, event_attributes, global_event_attribute_names):
59
+ global_event_attributes = {}
60
+ local_event_attributes = {}
61
+
62
+ for event, attrs in event_attributes.attributes.items():
63
+ for attr, value in attrs.items():
64
+ if attr in global_event_attribute_names:
65
+ global_event_attributes.setdefault(event, {})[attr] = value
66
+ else:
67
+ local_event_attributes.setdefault(event, {})[attr] = value
68
+ return AllEventAttributes(global_event_attributes), AllEventAttributes(local_event_attributes)
69
+
70
+ def _extract_global_attributes(self, global_attributes, global_case_attribute_names, global_event_attribute_names):
71
+ attributes_to_remove = global_case_attribute_names.union(global_event_attribute_names)
72
+
73
+ filtered_attributes_dict = {
74
+ attr_name: attr_object
75
+ for attr_name, attr_object in global_attributes.attributes.items()
76
+ if attr_name not in attributes_to_remove
77
+ }
78
+
79
+ return AllGlobalAttributes(filtered_attributes_dict)
@@ -0,0 +1,38 @@
1
+ from typing import List
2
+ from prosimos.branch_condition_rules import (
3
+ AllBranchConditionRules,
4
+ AndBranchConditionRule,
5
+ OrBranchConditionRule,
6
+ BranchConditionRule,
7
+ BranchConditionWithRule
8
+ )
9
+
10
+
11
+ class BranchConditionParser:
12
+ def __init__(self, json_data_with_branching_conditions):
13
+ self.data = json_data_with_branching_conditions
14
+
15
+ def parse(self):
16
+ branching_conditions: List[BranchConditionWithRule] = []
17
+ for curr_branch_condition_rule in self.data:
18
+ or_rules_json = curr_branch_condition_rule["rules"]
19
+ branch_id = curr_branch_condition_rule["id"]
20
+
21
+ or_rules: List[AndBranchConditionRule] = []
22
+ for or_rule in or_rules_json:
23
+ and_rules: List[BranchConditionRule] = []
24
+ for and_rule_json in or_rule:
25
+ rule: BranchConditionRule = BranchConditionRule(
26
+ and_rule_json["attribute"],
27
+ and_rule_json["comparison"],
28
+ and_rule_json["value"],
29
+ )
30
+ and_rules.append(rule)
31
+ all_and_rules = AndBranchConditionRule(and_rules)
32
+ or_rules.append(all_and_rules)
33
+
34
+ all_or_rules = OrBranchConditionRule(or_rules)
35
+
36
+ branching_conditions.append(BranchConditionWithRule(all_or_rules, branch_id))
37
+
38
+ return AllBranchConditionRules(branching_conditions)
@@ -0,0 +1,106 @@
1
+ import sys
2
+ from typing import List, Tuple
3
+
4
+
5
+ class InOperatorEvaluator:
6
+ def __init__(self, range: Tuple[float, float], case_value: float):
7
+ self.min, self.max = range # including the edges
8
+ self.value = case_value
9
+
10
+ def eval(self):
11
+ if self.min == self.max:
12
+ # equal operator
13
+ return self.min == self.value
14
+ else:
15
+ # check whether in the range
16
+ return self.min <= self.value <= self.max
17
+
18
+
19
+ class BranchConditionRule:
20
+ def __init__(self, attribute, condition, value):
21
+ self.attribute = attribute
22
+ self.condition = condition
23
+ self.value = self._parse_value(value)
24
+
25
+ def _parse_value(self, value):
26
+ if self.condition == "in":
27
+ min_boundary = float(value[0])
28
+ max_boundary = sys.maxsize if value[1] == "inf" else float(value[1])
29
+ return min_boundary, max_boundary
30
+ else:
31
+ return value
32
+
33
+ def is_rule_true(self, all_case_values):
34
+ if self.attribute not in all_case_values:
35
+ return False
36
+
37
+ raw_case_value = all_case_values[self.attribute]
38
+ if self.condition in [">", ">=", "<", "<=", "!="]:
39
+ try:
40
+ case_value = float(raw_case_value)
41
+ comparison_value = float(self.value)
42
+ except ValueError:
43
+ case_value = raw_case_value
44
+ comparison_value = self.value
45
+ else:
46
+ case_value = raw_case_value
47
+ comparison_value = self.value
48
+
49
+ if self.condition == "in":
50
+ min_value, max_value = comparison_value
51
+ return min_value <= case_value <= max_value
52
+ elif self.condition == ">":
53
+ return case_value > comparison_value
54
+ elif self.condition == ">=":
55
+ return case_value >= comparison_value
56
+ elif self.condition == "<":
57
+ return case_value < comparison_value
58
+ elif self.condition == "<=":
59
+ return case_value <= comparison_value
60
+ elif self.condition == "!=":
61
+ return case_value != comparison_value
62
+ else:
63
+ return case_value == comparison_value
64
+
65
+
66
+
67
+ class AndBranchConditionRule:
68
+ def __init__(self, and_rules: List[BranchConditionRule]):
69
+ self.and_rules = and_rules
70
+
71
+ def is_and_rule_true(self, all_case_values):
72
+ init_val = True
73
+ for item in self.and_rules:
74
+ init_val = init_val and item.is_rule_true(all_case_values)
75
+ return init_val
76
+
77
+
78
+ class OrBranchConditionRule:
79
+ def __init__(self, or_rules: List[AndBranchConditionRule]):
80
+ self.or_rules = or_rules
81
+
82
+ def is_or_rule_true(self, all_case_values):
83
+ init_val = False
84
+ for item in self.or_rules:
85
+ init_val = init_val or item.is_and_rule_true(all_case_values)
86
+ return init_val
87
+
88
+
89
+ class BranchConditionWithRule:
90
+ def __init__(self, or_rule: OrBranchConditionRule, id: str):
91
+ self.or_rule = or_rule
92
+ self.id = id
93
+
94
+ def is_true(self, all_case_values):
95
+ return self.or_rule.is_or_rule_true(all_case_values)
96
+
97
+
98
+ class AllBranchConditionRules:
99
+ def __init__(self, rules_array: List[BranchConditionWithRule]):
100
+ self.all_rules = rules_array
101
+
102
+ def get_branch_condition_by_id(self, branch_condition_id):
103
+ for rule in self.all_rules:
104
+ if rule.id == branch_condition_id:
105
+ return rule
106
+ return None
@@ -12,6 +12,7 @@ class CASE_ATTR_TYPE(Enum):
12
12
  DISCRETE = "discrete"
13
13
  CONTINUOUS = "continuous"
14
14
 
15
+
15
16
  def parse_discrete_value(value_info_arr):
16
17
  prob_arr = []
17
18
  options_arr = []
@@ -24,11 +25,12 @@ def parse_discrete_value(value_info_arr):
24
25
  "probabilities": prob_arr
25
26
  }
26
27
 
28
+
27
29
  def parse_continuous_value(value_info) -> "DurationDistribution":
28
30
  return DurationDistribution.from_dict(value_info)
29
31
 
30
32
 
31
- class CaseAttribute():
33
+ class CaseAttribute:
32
34
  def __init__(self, name, case_atrr_type, value):
33
35
  self.name: str = name
34
36
  self.case_atrr_type: CASE_ATTR_TYPE = CASE_ATTR_TYPE(case_atrr_type)
@@ -50,16 +52,19 @@ class CaseAttribute():
50
52
  return self.value.generate_sample(1)[0]
51
53
 
52
54
  def validate(self):
55
+ epsilon = 1e-6
56
+
53
57
  if self.case_atrr_type == CASE_ATTR_TYPE.DISCRETE:
54
58
  actual_sum_probabilities = reduce(lambda acc, item: acc + item, self.value["probabilities"], 0)
55
59
 
56
- if actual_sum_probabilities != 1:
57
- raise InvalidCaseAttributeException(f"Case attribute ${self.name}: probabilities' sum should be equal to 1")
58
-
60
+ if not (1 - epsilon <= actual_sum_probabilities <= 1 + epsilon):
61
+ raise InvalidCaseAttributeException(
62
+ f"Case attribute {self.name}: probabilities' sum should be equal to 1")
63
+
59
64
  return True
60
65
 
61
66
 
62
- class AllCaseAttributes():
67
+ class AllCaseAttributes:
63
68
  def __init__(self, case_attr_arr: List[CaseAttribute]):
64
69
  self.attributes = case_attr_arr
65
70
 
@@ -12,6 +12,8 @@ from prosimos.batch_processing import (BATCH_TYPE, AndFiringRule,
12
12
  BatchConfigPerTask)
13
13
  from prosimos.exceptions import InvalidBpmnModelException
14
14
  from prosimos.weekday_helper import CustomDatetimeAndSeconds
15
+ from prosimos.simulation_execution_stats import SimulationExecutionStats
16
+ from prosimos.warning_logger import warning_logger
15
17
 
16
18
  seconds_per_unit = {"s": 1, "m": 60, "h": 3600, "d": 86400, "w": 604800}
17
19
 
@@ -24,7 +26,7 @@ class BatchInfoForExecution:
24
26
 
25
27
  def is_sequential(self):
26
28
  return self.task_batch_info.type == BATCH_TYPE.SEQUENTIAL
27
-
29
+
28
30
  def is_concurrent(self):
29
31
  return self.task_batch_info.type == BATCH_TYPE.CONCURRENT
30
32
 
@@ -36,7 +38,7 @@ class EnabledTask:
36
38
  def __init__(self, task_id, batch_info_exec: BatchInfoForExecution = None, duration_sec = None, is_event = False):
37
39
  self.task_id = task_id
38
40
  self.batch_info_exec = batch_info_exec
39
-
41
+
40
42
  # is filled only for event-based gateways
41
43
  # (when we already know the duration at the control flow step)
42
44
  self.duration_sec = duration_sec
@@ -61,6 +63,8 @@ class BPMN(Enum):
61
63
  else:
62
64
  return False
63
65
 
66
+ from prosimos.outgoing_flow_selector import OutgoingFlowSelector
67
+
64
68
  class EVENT_TYPE(Enum):
65
69
  MESSAGE = 'MESSAGE'
66
70
  TIMER = 'TIMER'
@@ -90,7 +94,7 @@ class ElementInfo:
90
94
 
91
95
  def is_start_or_end_event(self):
92
96
  return self.type in [BPMN.START_EVENT, BPMN.END_EVENT]
93
-
97
+
94
98
  def is_event(self):
95
99
  return self.type in [BPMN.START_EVENT, BPMN.END_EVENT, BPMN.INTERMEDIATE_EVENT]
96
100
 
@@ -153,13 +157,20 @@ class BPMNGraph:
153
157
  self.batch_count = dict()
154
158
  self.batch_waiting_processes = dict()
155
159
  self.last_datetime = dict()
156
-
157
- def set_additional_fields_from_json(self, element_probability, task_resource_probability, \
158
- event_distribution, batch_processing):
160
+ self.all_attributes = None
161
+ self.gateway_conditions = None
162
+ self.gateway_execution_limit = 1000
163
+ self.simulation_execution_stats = SimulationExecutionStats()
164
+
165
+ def set_additional_fields_from_json(self, element_probability, task_resource_probability,
166
+ event_distribution, batch_processing, gateway_conditions,
167
+ gateway_execution_limit):
159
168
  self.element_probability = element_probability
160
169
  self.task_resource_probability = task_resource_probability
161
170
  self.event_distribution = event_distribution
162
171
  self.batch_info = batch_processing
172
+ self.gateway_conditions = gateway_conditions
173
+ self.gateway_execution_limit = gateway_execution_limit
163
174
 
164
175
  def add_bpmn_element(self, element_id, element_info):
165
176
  if element_info.type == BPMN.START_EVENT:
@@ -171,6 +182,7 @@ class BPMNGraph:
171
182
  self.from_name[element_info.name] = element_id
172
183
  self.nodes_bitset[element_id] = (1 << len(self.element_info))
173
184
  self.last_datetime[element_id] = dict()
185
+ self.simulation_execution_stats.add_element(element_info, element_id)
174
186
 
175
187
  def add_flow_arc(self, flow_id, source_id, target_id):
176
188
  for node_id in [source_id, target_id]:
@@ -328,18 +340,19 @@ class BPMNGraph:
328
340
  f_arcs = [(flow, None) for flow in flows]
329
341
 
330
342
  if len(f_arcs) > 1:
331
- if e_info.type is BPMN.EXCLUSIVE_GATEWAY:
332
- f_arcs = [(self.element_probability[e_info.id].get_outgoing_flow(), None)]
333
- elif e_info.type is BPMN.EVENT_BASED_GATEWAY:
343
+ if self.is_gateway_execution_limit_exceeded(case_id, e_info.id):
344
+ break
345
+
346
+ if e_info.type is BPMN.EVENT_BASED_GATEWAY:
334
347
  f_arcs = [self.get_event_gateway_choice(e_info, last_enabled.datetime)]
335
348
  else:
336
- if e_info.type in \
337
- [BPMN.TASK, BPMN.PARALLEL_GATEWAY, BPMN.START_EVENT, BPMN.INTERMEDIATE_EVENT]:
338
- flows = copy.deepcopy(e_info.outgoing_flows)
339
- f_arcs = [(flow, None) for flow in flows]
340
- elif e_info.type is BPMN.INCLUSIVE_GATEWAY:
341
- f_arcs = self.element_probability[e_info.id].get_multiple_flows()
349
+ all_curr_attributes = self.get_all_attributes(case_id)
350
+ f_arcs = OutgoingFlowSelector.choose_outgoing_flow(e_info, self.element_probability,
351
+ all_curr_attributes, self.gateway_conditions)
342
352
  random.shuffle(f_arcs)
353
+
354
+ self.simulation_execution_stats.update_element_execution(case_id, e_info, f_arcs)
355
+
343
356
  for f_arc in f_arcs:
344
357
  self._find_next(f_arc, case_id, p_state, enabled_tasks, to_execute, last_enabled, visited_at)
345
358
  current += 1
@@ -347,9 +360,21 @@ class BPMNGraph:
347
360
  random.shuffle(enabled_tasks)
348
361
  return enabled_tasks, visited_at
349
362
 
363
+ def get_all_attributes(self, case_id):
364
+ all_current_attributes = {}
365
+ all_current_attributes.update(self.all_attributes["global"])
366
+ all_current_attributes.update(self.all_attributes[case_id])
367
+ return all_current_attributes
368
+
350
369
  def is_task_batched(self, task_id):
351
370
  return self.batch_info.get(task_id, None) != None
352
371
 
372
+ def is_gateway_execution_limit_exceeded(self, case_id, e_id):
373
+ element_executions = self.simulation_execution_stats.get_element_executions(case_id, e_id)
374
+ if element_executions >= self.gateway_execution_limit:
375
+ warning_logger.add_warning(f"Infinite loop was detected in case #{case_id} on element {e_id}. Case has been terminated")
376
+ return True
377
+ return False
353
378
 
354
379
  def is_batched_task_enabled(self, task_id: str, enabled_at: CustomDatetimeAndSeconds):
355
380
  """
@@ -371,8 +396,8 @@ class BPMNGraph:
371
396
  # not enough items for batch execution
372
397
  return False, None, None
373
398
 
374
- waiting_time = [ (enabled_at.datetime - v.datetime).total_seconds() for (_, v) in self.batch_waiting_processes[task_id].items() ]
375
-
399
+ waiting_time = [ (enabled_at.datetime - v.datetime).total_seconds() for (_, v) in self.batch_waiting_processes[task_id].items() ]
400
+
376
401
  spec = {
377
402
  "size": size_count,
378
403
  "waiting_times": waiting_time,
@@ -397,7 +422,7 @@ class BPMNGraph:
397
422
 
398
423
  """
399
424
  spec["is_only_one_batch_return"] = True
400
-
425
+
401
426
  final_is_true, final_batch_spec, final_start_time = False, None, None
402
427
 
403
428
  while True:
@@ -415,7 +440,7 @@ class BPMNGraph:
415
440
  final_batch_spec.append(num_items)
416
441
  final_is_true = is_true
417
442
  final_start_time = start_time
418
-
443
+
419
444
  # regenerate the rule based on the size distribution
420
445
  task_batch_info.update_firing_rules_from_distr()
421
446
 
@@ -427,7 +452,7 @@ class BPMNGraph:
427
452
  if new_num_tasks_in_queue == 0:
428
453
  # all items were included in the batches
429
454
  break
430
-
455
+
431
456
  spec["size"] = spec["size"] - num_items
432
457
 
433
458
  return final_is_true, final_batch_spec, final_start_time
@@ -438,10 +463,10 @@ class BPMNGraph:
438
463
  firing_rules: List[AndFiringRule] = task_batch_info.firing_rules
439
464
  enabled_times = list(self.batch_waiting_processes[task_id].items())
440
465
  batch_enabled_time = firing_rules.get_enabled_time(
441
- enabled_times,
466
+ enabled_times,
442
467
  last_task_enabled_time,
443
468
  )
444
-
469
+
445
470
  if batch_enabled_time != None:
446
471
  enabled_time = batch_enabled_time
447
472
 
@@ -466,7 +491,7 @@ class BPMNGraph:
466
491
  event_id = self.flow_arcs[outgoing_flow][1]
467
492
  [duration] = self.event_duration(event_id)
468
493
  all_gateway_choices[outgoing_flow] = duration
469
-
494
+
470
495
  min_value = min(all_gateway_choices.values())
471
496
  res = [(key, value) for key, value in all_gateway_choices.items() if value == min_value]
472
497
 
@@ -840,9 +865,9 @@ class BPMNGraph:
840
865
  enabled_time = self._check_and_update_enabling_time(case_id, next_e, enabled_time)
841
866
  visited_at[next_e] = enabled_time
842
867
  if self.is_enabled(next_e, p_state):
843
- next_el_type = self.element_info[next_e].type
868
+ next_el_type = self.element_info[next_e].type
844
869
  if next_el_type == BPMN.TASK and self.is_task_batched(next_e):
845
- self.enable_batch_or_task(next_e, case_id, enabled_time, enabled_tasks, duration_sec)
870
+ self.enable_batch_or_task(next_e, case_id, enabled_time, enabled_tasks, duration_sec)
846
871
  elif next_el_type == BPMN.TASK:
847
872
  enabled_tasks.append(EnabledTask(next_e, None, duration_sec))
848
873
  elif next_el_type == BPMN.INTERMEDIATE_EVENT:
@@ -939,13 +964,13 @@ class BPMNGraph:
939
964
  # keep track of case_id which are waiting for the firing rule to be true
940
965
  if task_id not in self.batch_waiting_processes:
941
966
  self.batch_waiting_processes[task_id] = dict()
942
-
967
+
943
968
  # in case we execute the batch, there is no need to increase the total num
944
969
  if case_id in self.batch_waiting_processes[task_id]:
945
970
  return
946
-
971
+
947
972
  self.batch_waiting_processes[task_id][case_id] = enabled_time
948
-
973
+
949
974
  if task_id in self.batch_count:
950
975
  self.batch_count[task_id] += 1
951
976
  else:
@@ -990,7 +1015,7 @@ class BPMNGraph:
990
1015
  num_tasks_wait_batch = len(waiting_tasks)
991
1016
  if num_tasks_wait_batch == 0:
992
1017
  continue
993
-
1018
+
994
1019
  if self.is_or_rule_invalid(waiting_tasks, task_id, num_tasks_wait_batch, current_point_of_time):
995
1020
  # if the rule become invlaid, we execute everything we have
996
1021
  # invalid = the one that cannot be satisfied in the future anymore
@@ -1008,7 +1033,7 @@ class BPMNGraph:
1008
1033
  task_id, batch_spec, start_time_from_rule
1009
1034
  )
1010
1035
  continue
1011
-
1036
+
1012
1037
  return enabled_task_batch
1013
1038
 
1014
1039
  def create_batch_info_and_clear_from_queue(self, task_id: str, batch_spec, start_time_from_rule):
@@ -1024,7 +1049,7 @@ class BPMNGraph:
1024
1049
 
1025
1050
  def is_or_rule_invalid(self, waiting_tasks, task_id: str, num_tasks_wait_batch: int, current_point_of_time: CustomDatetimeAndSeconds):
1026
1051
  all_keys = list(waiting_tasks.keys())
1027
-
1052
+
1028
1053
  first_key = all_keys[0]
1029
1054
  first_wt = (current_point_of_time.datetime - waiting_tasks[first_key].datetime).seconds
1030
1055