aiphoria 0.0.1__py3-none-any.whl → 0.8.0__py3-none-any.whl
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.
- aiphoria/__init__.py +59 -0
- aiphoria/core/__init__.py +55 -0
- aiphoria/core/builder.py +305 -0
- aiphoria/core/datachecker.py +1808 -0
- aiphoria/core/dataprovider.py +806 -0
- aiphoria/core/datastructures.py +1686 -0
- aiphoria/core/datavisualizer.py +431 -0
- aiphoria/core/datavisualizer_data/LICENSE +21 -0
- aiphoria/core/datavisualizer_data/datavisualizer_plotly.html +5561 -0
- aiphoria/core/datavisualizer_data/pako.min.js +2 -0
- aiphoria/core/datavisualizer_data/plotly-3.0.0.min.js +3879 -0
- aiphoria/core/flowmodifiersolver.py +1754 -0
- aiphoria/core/flowsolver.py +1472 -0
- aiphoria/core/logger.py +113 -0
- aiphoria/core/network_graph.py +136 -0
- aiphoria/core/network_graph_data/ECHARTS_LICENSE +202 -0
- aiphoria/core/network_graph_data/echarts_min.js +45 -0
- aiphoria/core/network_graph_data/network_graph.html +76 -0
- aiphoria/core/network_graph_data/network_graph.js +1391 -0
- aiphoria/core/parameters.py +269 -0
- aiphoria/core/types.py +20 -0
- aiphoria/core/utils.py +362 -0
- aiphoria/core/visualizer_parameters.py +7 -0
- aiphoria/data/example_scenario.xlsx +0 -0
- aiphoria/example.py +66 -0
- aiphoria/lib/docs/dynamic_stock.py +124 -0
- aiphoria/lib/odym/modules/ODYM_Classes.py +362 -0
- aiphoria/lib/odym/modules/ODYM_Functions.py +1299 -0
- aiphoria/lib/odym/modules/__init__.py +1 -0
- aiphoria/lib/odym/modules/dynamic_stock_model.py +808 -0
- aiphoria/lib/odym/modules/test/DSM_test_known_results.py +762 -0
- aiphoria/lib/odym/modules/test/ODYM_Classes_test_known_results.py +107 -0
- aiphoria/lib/odym/modules/test/ODYM_Functions_test_known_results.py +136 -0
- aiphoria/lib/odym/modules/test/__init__.py +2 -0
- aiphoria/runner.py +678 -0
- aiphoria-0.8.0.dist-info/METADATA +119 -0
- aiphoria-0.8.0.dist-info/RECORD +40 -0
- {aiphoria-0.0.1.dist-info → aiphoria-0.8.0.dist-info}/WHEEL +1 -1
- aiphoria-0.8.0.dist-info/licenses/LICENSE +21 -0
- aiphoria-0.0.1.dist-info/METADATA +0 -5
- aiphoria-0.0.1.dist-info/RECORD +0 -5
- {aiphoria-0.0.1.dist-info → aiphoria-0.8.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,1686 @@
|
|
|
1
|
+
from typing import Tuple, List, Union, Dict, Any
|
|
2
|
+
from builtins import float
|
|
3
|
+
import copy
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from aiphoria.lib.odym.modules.ODYM_Classes import MFAsystem
|
|
6
|
+
from .parameters import StockDistributionParameterValueType
|
|
7
|
+
from .types import FunctionType, ChangeType
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ObjectBase(object):
|
|
11
|
+
"""
|
|
12
|
+
Base class for Process, Flow and Stock.
|
|
13
|
+
Keeps track of row number, validity of read row and virtual state.
|
|
14
|
+
"""
|
|
15
|
+
def __init__(self):
|
|
16
|
+
self._id: Union[str, any] = -1
|
|
17
|
+
self._row_number: int = -1
|
|
18
|
+
self._is_valid: bool = False
|
|
19
|
+
self._is_virtual: bool = False
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def is_valid(self) -> bool:
|
|
23
|
+
"""
|
|
24
|
+
Return validity of object.
|
|
25
|
+
|
|
26
|
+
:return: Boolean
|
|
27
|
+
"""
|
|
28
|
+
return False
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def id(self) -> Union[str, any]:
|
|
32
|
+
"""
|
|
33
|
+
Get ID of the object.
|
|
34
|
+
|
|
35
|
+
:return: Object ID (string)
|
|
36
|
+
"""
|
|
37
|
+
return self._id
|
|
38
|
+
|
|
39
|
+
@id.setter
|
|
40
|
+
def id(self, new_id: str) -> None:
|
|
41
|
+
"""
|
|
42
|
+
Set Object ID.
|
|
43
|
+
|
|
44
|
+
:param new_id: New ID
|
|
45
|
+
:return: None
|
|
46
|
+
"""
|
|
47
|
+
self._id = new_id
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def row_number(self) -> int:
|
|
51
|
+
"""
|
|
52
|
+
Get row number where Object was defined.
|
|
53
|
+
This is valid only for the Flows and Processes defined in settings file.
|
|
54
|
+
|
|
55
|
+
:return: Row number where Object was defined
|
|
56
|
+
"""
|
|
57
|
+
return self._row_number
|
|
58
|
+
|
|
59
|
+
@row_number.setter
|
|
60
|
+
def row_number(self, value: int) -> None:
|
|
61
|
+
"""
|
|
62
|
+
Set row number where Object was defined.
|
|
63
|
+
|
|
64
|
+
:param value: Target row number
|
|
65
|
+
:return: None
|
|
66
|
+
"""
|
|
67
|
+
self._row_number = value
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def is_virtual(self) -> bool:
|
|
71
|
+
"""
|
|
72
|
+
Check if Object is virtual.
|
|
73
|
+
|
|
74
|
+
:return: True if Object is virtual, False otherwise.
|
|
75
|
+
"""
|
|
76
|
+
return self._is_virtual
|
|
77
|
+
|
|
78
|
+
@is_virtual.setter
|
|
79
|
+
def is_virtual(self, value: bool) -> None:
|
|
80
|
+
"""
|
|
81
|
+
Set Object virtual state.
|
|
82
|
+
|
|
83
|
+
:param value: State
|
|
84
|
+
:return: None
|
|
85
|
+
"""
|
|
86
|
+
self._is_virtual = value
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class Indicator(object):
|
|
90
|
+
def __init__(self, name: str = None, conversion_factor: float = 1.0, comment: str = None, unit: str = None):
|
|
91
|
+
super().__init__()
|
|
92
|
+
self._name: Union[str, None] = name
|
|
93
|
+
self._conversion_factor: Union[float, None] = conversion_factor
|
|
94
|
+
self._comment: Union[str, None] = comment
|
|
95
|
+
self._unit: Union[str, None] = unit
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def name(self) -> str:
|
|
99
|
+
"""
|
|
100
|
+
Get the indicator name.
|
|
101
|
+
|
|
102
|
+
:return: Indicator name (str)
|
|
103
|
+
"""
|
|
104
|
+
return self._name
|
|
105
|
+
|
|
106
|
+
@name.setter
|
|
107
|
+
def name(self, new_name: str):
|
|
108
|
+
"""
|
|
109
|
+
Set the indicator name.
|
|
110
|
+
|
|
111
|
+
:param new_name: New indicator name (str)
|
|
112
|
+
"""
|
|
113
|
+
self._name = new_name
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def conversion_factor(self) -> float:
|
|
117
|
+
"""
|
|
118
|
+
Get the indicator conversion factor.
|
|
119
|
+
|
|
120
|
+
:return: Conversion factor (float)
|
|
121
|
+
"""
|
|
122
|
+
return self._conversion_factor
|
|
123
|
+
|
|
124
|
+
@conversion_factor.setter
|
|
125
|
+
def conversion_factor(self, new_conversion_factor: float):
|
|
126
|
+
"""
|
|
127
|
+
Set the indicator conversion factor.
|
|
128
|
+
|
|
129
|
+
:param new_value: New conversion factor (float)
|
|
130
|
+
"""
|
|
131
|
+
self._conversion_factor = new_conversion_factor
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def comment(self) -> str:
|
|
135
|
+
"""
|
|
136
|
+
Get indicator comment.
|
|
137
|
+
|
|
138
|
+
:return: Indicator comment (str)
|
|
139
|
+
"""
|
|
140
|
+
return self._comment
|
|
141
|
+
|
|
142
|
+
@comment.setter
|
|
143
|
+
def comment(self, new_comment: str):
|
|
144
|
+
"""
|
|
145
|
+
Set the indicator comment.
|
|
146
|
+
|
|
147
|
+
:param new_comment: New indicator comment (str)
|
|
148
|
+
"""
|
|
149
|
+
self._comment = new_comment
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def unit(self) -> str:
|
|
153
|
+
"""
|
|
154
|
+
Get the indicator unit.
|
|
155
|
+
|
|
156
|
+
:return: Indicator unit (str)
|
|
157
|
+
"""
|
|
158
|
+
return self._unit
|
|
159
|
+
|
|
160
|
+
@unit.setter
|
|
161
|
+
def unit(self, new_unit: str):
|
|
162
|
+
"""
|
|
163
|
+
Set the indicator unit.
|
|
164
|
+
|
|
165
|
+
:param new_unit: New Indicator unit (str)
|
|
166
|
+
"""
|
|
167
|
+
self._unit = new_unit
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class Process(ObjectBase):
|
|
171
|
+
"""
|
|
172
|
+
aiphoria Process-object:
|
|
173
|
+
|
|
174
|
+
Used to store data for Process.
|
|
175
|
+
"""
|
|
176
|
+
def __init__(self, params: pd.Series = None, row_number=-1):
|
|
177
|
+
super().__init__()
|
|
178
|
+
|
|
179
|
+
self._name = None
|
|
180
|
+
self._location = None
|
|
181
|
+
self._id = None
|
|
182
|
+
self._transformation_stage = None
|
|
183
|
+
self._stock_lifetime = None
|
|
184
|
+
self._stock_lifetime_source = None
|
|
185
|
+
self._stock_distribution_type = None
|
|
186
|
+
self._stock_distribution_params = None
|
|
187
|
+
self._wood_content = None
|
|
188
|
+
self._wood_content_source = None
|
|
189
|
+
self._density = None
|
|
190
|
+
self._density_source = None
|
|
191
|
+
self._modelling_status = None
|
|
192
|
+
self._comment = None
|
|
193
|
+
self._row_number = -1
|
|
194
|
+
self._depth = -1
|
|
195
|
+
self._position_x = None
|
|
196
|
+
self._position_y = None
|
|
197
|
+
self._label_in_graph = None
|
|
198
|
+
|
|
199
|
+
# Leave instance to default state if params is not provided
|
|
200
|
+
if params is None:
|
|
201
|
+
return
|
|
202
|
+
|
|
203
|
+
# Skip totally empty row
|
|
204
|
+
if params.isna().all():
|
|
205
|
+
return
|
|
206
|
+
|
|
207
|
+
self._name = params.iloc[0]
|
|
208
|
+
self._location = params.iloc[1]
|
|
209
|
+
self._id = params.iloc[2]
|
|
210
|
+
self._transformation_stage = params.iloc[3]
|
|
211
|
+
|
|
212
|
+
# Parse stock lifetime, default to zero if None
|
|
213
|
+
self._stock_lifetime = self._parse_stock_lifetime(params.iloc[4], row_number)
|
|
214
|
+
|
|
215
|
+
self._stock_lifetime_source = params.iloc[5]
|
|
216
|
+
self._stock_distribution_type = params.iloc[6]
|
|
217
|
+
self._stock_distribution_params = params.iloc[7]
|
|
218
|
+
|
|
219
|
+
# Parse stock distribution parameters
|
|
220
|
+
# NOTE: Event invalid key-value -pairs are stored to _stock_distribution_params after parsin
|
|
221
|
+
# and those are checked in datachecker
|
|
222
|
+
self._parse_and_set_distribution_params(params.iloc[7])
|
|
223
|
+
|
|
224
|
+
self._wood_content = params.iloc[8]
|
|
225
|
+
self._wood_content_source = params.iloc[9]
|
|
226
|
+
self._density = params.iloc[10]
|
|
227
|
+
self._density_source = params.iloc[11]
|
|
228
|
+
self._modelling_status = params.iloc[12]
|
|
229
|
+
self._comment = params.iloc[13]
|
|
230
|
+
self._position_x = params.iloc[14]
|
|
231
|
+
self._position_y = params.iloc[15]
|
|
232
|
+
self._label_in_graph = params.iloc[16]
|
|
233
|
+
self._row_number = row_number
|
|
234
|
+
self._meta = {}
|
|
235
|
+
|
|
236
|
+
def __str__(self) -> str:
|
|
237
|
+
s = "Process '{}': Lifetime: {}".format(self.id, self.stock_lifetime)
|
|
238
|
+
return s
|
|
239
|
+
|
|
240
|
+
def __hash__(self):
|
|
241
|
+
return hash(self.id)
|
|
242
|
+
|
|
243
|
+
def __eq__(self, other) -> bool:
|
|
244
|
+
if not isinstance(other, Process):
|
|
245
|
+
return NotImplemented
|
|
246
|
+
|
|
247
|
+
return self.id == other.id
|
|
248
|
+
|
|
249
|
+
def is_valid(self) -> bool:
|
|
250
|
+
is_valid = True
|
|
251
|
+
is_valid = is_valid and self.name is not None
|
|
252
|
+
is_valid = is_valid and self.location is not None
|
|
253
|
+
is_valid = is_valid and self.id is not None
|
|
254
|
+
return is_valid
|
|
255
|
+
|
|
256
|
+
@property
|
|
257
|
+
def name(self) -> str:
|
|
258
|
+
return self._name
|
|
259
|
+
|
|
260
|
+
@name.setter
|
|
261
|
+
def name(self, value: str) -> None:
|
|
262
|
+
self._name = value
|
|
263
|
+
|
|
264
|
+
@name.setter
|
|
265
|
+
def name(self, value) -> None:
|
|
266
|
+
self._name = value
|
|
267
|
+
|
|
268
|
+
@property
|
|
269
|
+
def location(self) -> str:
|
|
270
|
+
return self._location
|
|
271
|
+
|
|
272
|
+
@location.setter
|
|
273
|
+
def location(self, value: str):
|
|
274
|
+
self._location = value
|
|
275
|
+
|
|
276
|
+
@property
|
|
277
|
+
def transformation_stage(self) -> str:
|
|
278
|
+
return self._transformation_stage
|
|
279
|
+
|
|
280
|
+
@transformation_stage.setter
|
|
281
|
+
def transformation_stage(self, value: str):
|
|
282
|
+
self._transformation_stage = value
|
|
283
|
+
|
|
284
|
+
@property
|
|
285
|
+
def stock_lifetime(self) -> int:
|
|
286
|
+
return self._stock_lifetime
|
|
287
|
+
|
|
288
|
+
@stock_lifetime.setter
|
|
289
|
+
def stock_lifetime(self, value: int) -> None:
|
|
290
|
+
self._stock_lifetime = value
|
|
291
|
+
|
|
292
|
+
@property
|
|
293
|
+
def stock_lifetime_source(self) -> str:
|
|
294
|
+
return self._stock_lifetime_source
|
|
295
|
+
|
|
296
|
+
@stock_lifetime_source.setter
|
|
297
|
+
def stock_lifetime_source(self, value: str):
|
|
298
|
+
self._stock_lifetime_source = value
|
|
299
|
+
|
|
300
|
+
@property
|
|
301
|
+
def stock_distribution_type(self) -> str:
|
|
302
|
+
return self._stock_distribution_type
|
|
303
|
+
|
|
304
|
+
@stock_distribution_type.setter
|
|
305
|
+
def stock_distribution_type(self, value: str):
|
|
306
|
+
self._stock_distribution_type = value
|
|
307
|
+
|
|
308
|
+
@property
|
|
309
|
+
def stock_distribution_params(self) -> str:
|
|
310
|
+
return self._stock_distribution_params
|
|
311
|
+
|
|
312
|
+
@stock_distribution_params.setter
|
|
313
|
+
def stock_distribution_params(self, value: str):
|
|
314
|
+
self._stock_distribution_params = value
|
|
315
|
+
|
|
316
|
+
@property
|
|
317
|
+
def wood_content(self) -> float:
|
|
318
|
+
return self._wood_content
|
|
319
|
+
|
|
320
|
+
@wood_content.setter
|
|
321
|
+
def wood_content(self, value: float):
|
|
322
|
+
self._wood_content = value
|
|
323
|
+
|
|
324
|
+
@property
|
|
325
|
+
def wood_content_source(self) -> str:
|
|
326
|
+
return self._wood_content_source
|
|
327
|
+
|
|
328
|
+
@wood_content_source.setter
|
|
329
|
+
def wood_content_source(self, value: str):
|
|
330
|
+
self._wood_content_source = value
|
|
331
|
+
|
|
332
|
+
@property
|
|
333
|
+
def density(self) -> float:
|
|
334
|
+
return self._density
|
|
335
|
+
|
|
336
|
+
@density.setter
|
|
337
|
+
def density(self, value: float):
|
|
338
|
+
self._density = value
|
|
339
|
+
|
|
340
|
+
@property
|
|
341
|
+
def density_source(self) -> str:
|
|
342
|
+
return self._density_source
|
|
343
|
+
|
|
344
|
+
@density_source.setter
|
|
345
|
+
def density_source(self, value: str):
|
|
346
|
+
self._density_source = value
|
|
347
|
+
|
|
348
|
+
@property
|
|
349
|
+
def modelling_status(self) -> str:
|
|
350
|
+
return self._modelling_status
|
|
351
|
+
|
|
352
|
+
@modelling_status.setter
|
|
353
|
+
def modelling_status(self, value: str):
|
|
354
|
+
self._modelling_status = value
|
|
355
|
+
|
|
356
|
+
@property
|
|
357
|
+
def comment(self) -> str:
|
|
358
|
+
return self._comment
|
|
359
|
+
|
|
360
|
+
@comment.setter
|
|
361
|
+
def comment(self, value: str):
|
|
362
|
+
self._comment = value
|
|
363
|
+
|
|
364
|
+
@property
|
|
365
|
+
def depth(self) -> int:
|
|
366
|
+
return self._depth
|
|
367
|
+
|
|
368
|
+
@depth.setter
|
|
369
|
+
def depth(self, value: int):
|
|
370
|
+
self._depth = value
|
|
371
|
+
|
|
372
|
+
@property
|
|
373
|
+
def position_x(self) -> float:
|
|
374
|
+
return self._position_x
|
|
375
|
+
|
|
376
|
+
@position_x.setter
|
|
377
|
+
def position_x(self, value: float):
|
|
378
|
+
self._position_x = value
|
|
379
|
+
|
|
380
|
+
@property
|
|
381
|
+
def position_y(self) -> float:
|
|
382
|
+
return self._position_y
|
|
383
|
+
|
|
384
|
+
@position_y.setter
|
|
385
|
+
def position_y(self, value: float):
|
|
386
|
+
self._position_y = value
|
|
387
|
+
|
|
388
|
+
@property
|
|
389
|
+
def label_in_graph(self) -> str:
|
|
390
|
+
return self._label_in_graph
|
|
391
|
+
|
|
392
|
+
@label_in_graph.setter
|
|
393
|
+
def label_in_graph(self, value: str):
|
|
394
|
+
self._label_in_graph = value
|
|
395
|
+
|
|
396
|
+
@property
|
|
397
|
+
def meta(self) -> Dict[Any, Any]:
|
|
398
|
+
return self._meta
|
|
399
|
+
|
|
400
|
+
@meta.setter
|
|
401
|
+
def meta(self, value: Dict[Any, Any]) -> None:
|
|
402
|
+
self._meta = value
|
|
403
|
+
|
|
404
|
+
def _parse_stock_lifetime(self, s: str, row_number: int = -1):
|
|
405
|
+
"""
|
|
406
|
+
Parse stock lifetime from string.
|
|
407
|
+
|
|
408
|
+
:param s: String
|
|
409
|
+
:return: Stock lifetime (int)
|
|
410
|
+
"""
|
|
411
|
+
lifetime = 0
|
|
412
|
+
if s is None:
|
|
413
|
+
return lifetime
|
|
414
|
+
|
|
415
|
+
try:
|
|
416
|
+
lifetime = int(s)
|
|
417
|
+
except (ValueError, TypeError) as ex:
|
|
418
|
+
raise Exception("Stock lifetime must be number (row {})".format(row_number))
|
|
419
|
+
|
|
420
|
+
return lifetime
|
|
421
|
+
|
|
422
|
+
def _parse_and_set_distribution_params(self, s: str):
|
|
423
|
+
"""
|
|
424
|
+
Parse keys from string for distribution parameters.
|
|
425
|
+
"""
|
|
426
|
+
|
|
427
|
+
try:
|
|
428
|
+
# Check if cell contains only value
|
|
429
|
+
self._stock_distribution_params = float(s)
|
|
430
|
+
return
|
|
431
|
+
except (ValueError, TypeError):
|
|
432
|
+
if s is None:
|
|
433
|
+
return
|
|
434
|
+
|
|
435
|
+
params = {}
|
|
436
|
+
has_multiple_params = s.find(',') > 0
|
|
437
|
+
if not has_multiple_params:
|
|
438
|
+
# Single parameter
|
|
439
|
+
entry = s
|
|
440
|
+
k = None
|
|
441
|
+
v = None
|
|
442
|
+
if entry.find("=") >= 0:
|
|
443
|
+
# Has key=value
|
|
444
|
+
k, v = entry.split("=")
|
|
445
|
+
k = k.strip()
|
|
446
|
+
v = v.strip()
|
|
447
|
+
else:
|
|
448
|
+
# Only key, no value
|
|
449
|
+
k = entry.strip()
|
|
450
|
+
v = None
|
|
451
|
+
|
|
452
|
+
# Convert to target value type if definition exists
|
|
453
|
+
value_type = StockDistributionParameterValueType[k]
|
|
454
|
+
if value_type is not None:
|
|
455
|
+
v = value_type(v)
|
|
456
|
+
|
|
457
|
+
params[k] = v
|
|
458
|
+
|
|
459
|
+
else:
|
|
460
|
+
# Multiple parameters, separated by ','
|
|
461
|
+
for entry in s.split(","):
|
|
462
|
+
k = None
|
|
463
|
+
v = None
|
|
464
|
+
if entry.find("=") >= 0:
|
|
465
|
+
k, v = entry.split("=")
|
|
466
|
+
k = k.strip()
|
|
467
|
+
v = v.strip()
|
|
468
|
+
else:
|
|
469
|
+
k = entry.strip()
|
|
470
|
+
v = None
|
|
471
|
+
|
|
472
|
+
# Convert to target value type if definition exists
|
|
473
|
+
value_type = StockDistributionParameterValueType[k]
|
|
474
|
+
if value_type is not None:
|
|
475
|
+
v = value_type(v)
|
|
476
|
+
|
|
477
|
+
params[k] = v
|
|
478
|
+
|
|
479
|
+
self._stock_distribution_params = params
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
class Flow(ObjectBase):
|
|
483
|
+
"""
|
|
484
|
+
aiphoria Flow-object:
|
|
485
|
+
|
|
486
|
+
Used to store data for Flow.
|
|
487
|
+
"""
|
|
488
|
+
def __init__(self, params: pd.Series = None, row_number=-1):
|
|
489
|
+
super().__init__()
|
|
490
|
+
|
|
491
|
+
self._source_process = None
|
|
492
|
+
self._source_process_transformation_stage = None
|
|
493
|
+
self._source_process_location = None
|
|
494
|
+
self._target_process = None
|
|
495
|
+
self._target_process_transformation_stage = None
|
|
496
|
+
self._target_process_location = None
|
|
497
|
+
self._source_process_id = None
|
|
498
|
+
self._target_process_id = None
|
|
499
|
+
self._value = None
|
|
500
|
+
self._unit = None
|
|
501
|
+
self._year = None
|
|
502
|
+
self._data_source = None
|
|
503
|
+
self._data_source_comment = None
|
|
504
|
+
self._comment = None
|
|
505
|
+
|
|
506
|
+
# Evaluated per timestep
|
|
507
|
+
self._is_evaluated = False
|
|
508
|
+
self._evaluated_share = 0.0
|
|
509
|
+
self._evaluated_value = 0.0
|
|
510
|
+
|
|
511
|
+
# Indicator name to Indicator
|
|
512
|
+
self._indicator_name_to_indicator = {}
|
|
513
|
+
self._indicator_name_to_evaluated_value = {}
|
|
514
|
+
|
|
515
|
+
# Flow prioritization
|
|
516
|
+
self._is_prioritized = False
|
|
517
|
+
|
|
518
|
+
if params is None:
|
|
519
|
+
return
|
|
520
|
+
|
|
521
|
+
# Skip totally empty row
|
|
522
|
+
if params.isna().all():
|
|
523
|
+
return
|
|
524
|
+
|
|
525
|
+
self._source_process = params.iloc[0]
|
|
526
|
+
self._source_process_transformation_stage = params.iloc[1]
|
|
527
|
+
self._source_process_location = params.iloc[2]
|
|
528
|
+
self._target_process = params.iloc[3]
|
|
529
|
+
self._target_process_transformation_stage = params.iloc[4]
|
|
530
|
+
self._target_process_location = params.iloc[5]
|
|
531
|
+
self._source_process_id = params.iloc[6]
|
|
532
|
+
self._target_process_id = params.iloc[7]
|
|
533
|
+
self._value = params.iloc[8]
|
|
534
|
+
self._unit = params.iloc[9]
|
|
535
|
+
self._year = int(params.iloc[10])
|
|
536
|
+
self._data_source = params.iloc[11]
|
|
537
|
+
self._data_source_comment = params.iloc[12]
|
|
538
|
+
|
|
539
|
+
# Rest of the elements except last element are indicators
|
|
540
|
+
# There should be even number of indicators because each indicator has value and comment
|
|
541
|
+
first_indicator_index = 13
|
|
542
|
+
indicators = params[first_indicator_index:]
|
|
543
|
+
if len(indicators) % 2:
|
|
544
|
+
s = "Not even number of indicator columns in settings file.\n"
|
|
545
|
+
s += "Each indicator needs two columns (value and comment) in this order."
|
|
546
|
+
raise Exception(s)
|
|
547
|
+
|
|
548
|
+
# Build indicator name to Indicator mappings
|
|
549
|
+
for i in range(0, len(indicators), 2):
|
|
550
|
+
indicator_name = indicators.index[i]
|
|
551
|
+
conversion_factor = indicators.iloc[i]
|
|
552
|
+
comment = indicators.iloc[i+1]
|
|
553
|
+
|
|
554
|
+
# Strip substring inside characters '(' and ')'
|
|
555
|
+
# and use that as a unit
|
|
556
|
+
indicator_unit = ""
|
|
557
|
+
start_index = indicator_name.find("(")
|
|
558
|
+
end_index = indicator_name.find(")")
|
|
559
|
+
if start_index >= 0 and end_index >= 0:
|
|
560
|
+
unit_name = indicator_name[start_index:end_index + 1]
|
|
561
|
+
indicator_name = indicator_name.replace(unit_name, '').strip()
|
|
562
|
+
indicator_unit = unit_name[1:-1].strip()
|
|
563
|
+
|
|
564
|
+
# NOTE: Set indicator conversion factor to 0.0
|
|
565
|
+
# if not defined in the settings file
|
|
566
|
+
if conversion_factor is None:
|
|
567
|
+
conversion_factor = 0.0
|
|
568
|
+
|
|
569
|
+
new_indicator = Indicator(indicator_name, conversion_factor, comment, indicator_unit)
|
|
570
|
+
self._indicator_name_to_indicator[indicator_name] = new_indicator
|
|
571
|
+
self._indicator_name_to_evaluated_value[indicator_name] = 0.0
|
|
572
|
+
|
|
573
|
+
self._row_number = row_number # Track Excel file row number
|
|
574
|
+
|
|
575
|
+
def __str__(self):
|
|
576
|
+
s = "Flow '{}' -> '{}': Value={}, Unit={}, " \
|
|
577
|
+
"is_evaluated={}, evaluated_share={}, evaluated_value={}, " \
|
|
578
|
+
"year={}, is_virtual={}".format(
|
|
579
|
+
self.source_process_id, self.target_process_id, self.value, self.unit,
|
|
580
|
+
self.is_evaluated, self.evaluated_share, self.evaluated_value, self.year,
|
|
581
|
+
self.is_virtual)
|
|
582
|
+
return s
|
|
583
|
+
|
|
584
|
+
def __hash__(self):
|
|
585
|
+
return hash(self.id)
|
|
586
|
+
|
|
587
|
+
def __eq__(self, other):
|
|
588
|
+
if not isinstance(other, Flow):
|
|
589
|
+
return NotImplemented
|
|
590
|
+
|
|
591
|
+
return self.id == other.id
|
|
592
|
+
|
|
593
|
+
@staticmethod
|
|
594
|
+
def make_flow_id(source_process_id: str, target_process_id: str) -> str:
|
|
595
|
+
"""
|
|
596
|
+
Make Flow ID from source Process ID and target Process ID.
|
|
597
|
+
|
|
598
|
+
:param source_process_id: Source Process ID (string)
|
|
599
|
+
:param target_process_id: Target Process ID (string)
|
|
600
|
+
:return: Flow ID (string)
|
|
601
|
+
"""
|
|
602
|
+
return "{} {}".format(source_process_id, target_process_id)
|
|
603
|
+
|
|
604
|
+
@property
|
|
605
|
+
def id(self) -> str:
|
|
606
|
+
"""
|
|
607
|
+
Returns Flow ID.
|
|
608
|
+
|
|
609
|
+
:return: Flow ID (string)
|
|
610
|
+
"""
|
|
611
|
+
return Flow.make_flow_id(self.source_process_id, self.target_process_id)
|
|
612
|
+
|
|
613
|
+
def is_valid(self):
|
|
614
|
+
is_valid = True
|
|
615
|
+
is_valid = is_valid and self.value is not None
|
|
616
|
+
is_valid = is_valid and (self.source_process is not None)
|
|
617
|
+
is_valid = is_valid and (self.target_process is not None)
|
|
618
|
+
is_valid = is_valid and (self.source_process_id is not None)
|
|
619
|
+
is_valid = is_valid and (self.target_process_id is not None)
|
|
620
|
+
return is_valid
|
|
621
|
+
|
|
622
|
+
@property
|
|
623
|
+
def is_unit_absolute_value(self):
|
|
624
|
+
# Default to absolute value if unit is missing
|
|
625
|
+
unit_str = self.unit
|
|
626
|
+
if unit_str is None:
|
|
627
|
+
return True
|
|
628
|
+
|
|
629
|
+
unit_str = unit_str.strip()
|
|
630
|
+
if unit_str == "%":
|
|
631
|
+
return False
|
|
632
|
+
|
|
633
|
+
return True
|
|
634
|
+
|
|
635
|
+
@property
|
|
636
|
+
def source_process(self) -> str:
|
|
637
|
+
"""
|
|
638
|
+
Get source Process name.
|
|
639
|
+
|
|
640
|
+
:return: Source Process name (str)
|
|
641
|
+
"""
|
|
642
|
+
return self._source_process
|
|
643
|
+
|
|
644
|
+
@property
|
|
645
|
+
def source_process_transformation_stage(self) -> str:
|
|
646
|
+
return self._source_process_transformation_stage
|
|
647
|
+
|
|
648
|
+
@property
|
|
649
|
+
def source_process_location(self) -> str:
|
|
650
|
+
return self._source_process_location
|
|
651
|
+
|
|
652
|
+
@property
|
|
653
|
+
def target_process(self) -> str:
|
|
654
|
+
"""
|
|
655
|
+
Get target Process name.
|
|
656
|
+
:return: Target Process name (str)
|
|
657
|
+
"""
|
|
658
|
+
return self._target_process
|
|
659
|
+
|
|
660
|
+
@property
|
|
661
|
+
def target_process_transformation_stage(self) -> str:
|
|
662
|
+
return self._target_process_transformation_stage
|
|
663
|
+
|
|
664
|
+
@property
|
|
665
|
+
def target_process_location(self) -> str:
|
|
666
|
+
return self._target_process_location
|
|
667
|
+
|
|
668
|
+
@property
|
|
669
|
+
def source_process_id(self) -> str:
|
|
670
|
+
"""
|
|
671
|
+
Get source Process ID.
|
|
672
|
+
:return: Source Process ID (str)
|
|
673
|
+
"""
|
|
674
|
+
return self._source_process_id
|
|
675
|
+
|
|
676
|
+
@source_process_id.setter
|
|
677
|
+
def source_process_id(self, source_process_id: str):
|
|
678
|
+
"""
|
|
679
|
+
Set source Process ID.
|
|
680
|
+
|
|
681
|
+
:param source_process_id: Source Process ID (str)
|
|
682
|
+
"""
|
|
683
|
+
self._source_process_id = source_process_id
|
|
684
|
+
|
|
685
|
+
@property
|
|
686
|
+
def target_process_id(self) -> str:
|
|
687
|
+
"""
|
|
688
|
+
Get target Process ID.
|
|
689
|
+
|
|
690
|
+
:return: Target Process ID (str)
|
|
691
|
+
"""
|
|
692
|
+
return self._target_process_id
|
|
693
|
+
|
|
694
|
+
@target_process_id.setter
|
|
695
|
+
def target_process_id(self, target_process_id: str):
|
|
696
|
+
"""
|
|
697
|
+
Set target Process ID.
|
|
698
|
+
|
|
699
|
+
:param target_process_id: New target Process ID
|
|
700
|
+
"""
|
|
701
|
+
self._target_process_id = target_process_id
|
|
702
|
+
|
|
703
|
+
# Original value from Excel row
|
|
704
|
+
@property
|
|
705
|
+
def value(self) -> float:
|
|
706
|
+
"""
|
|
707
|
+
Get original baseline value.
|
|
708
|
+
|
|
709
|
+
:return: Original baseline value (float)
|
|
710
|
+
"""
|
|
711
|
+
return self._value
|
|
712
|
+
|
|
713
|
+
@value.setter
|
|
714
|
+
def value(self, value: float):
|
|
715
|
+
"""
|
|
716
|
+
Set original base value.
|
|
717
|
+
|
|
718
|
+
:param value: New original base value (float)
|
|
719
|
+
"""
|
|
720
|
+
self._value = value
|
|
721
|
+
|
|
722
|
+
@property
|
|
723
|
+
def unit(self) -> str:
|
|
724
|
+
return self._unit
|
|
725
|
+
|
|
726
|
+
@unit.setter
|
|
727
|
+
def unit(self, unit: str):
|
|
728
|
+
self._unit = unit
|
|
729
|
+
|
|
730
|
+
@property
|
|
731
|
+
def year(self) -> int:
|
|
732
|
+
return self._year
|
|
733
|
+
|
|
734
|
+
@year.setter
|
|
735
|
+
def year(self, value: int):
|
|
736
|
+
self._year = value
|
|
737
|
+
|
|
738
|
+
@property
|
|
739
|
+
def data_source(self) -> str:
|
|
740
|
+
return self._data_source
|
|
741
|
+
|
|
742
|
+
@property
|
|
743
|
+
def data_source_comment(self) -> str:
|
|
744
|
+
return self._data_source_comment
|
|
745
|
+
|
|
746
|
+
@property
|
|
747
|
+
def comment(self) -> str:
|
|
748
|
+
return self._comment
|
|
749
|
+
|
|
750
|
+
@property
|
|
751
|
+
def is_evaluated(self) -> bool:
|
|
752
|
+
"""
|
|
753
|
+
Get flow evaluated state.
|
|
754
|
+
|
|
755
|
+
:return: True if Flow is evaluated, False otherwise.
|
|
756
|
+
"""
|
|
757
|
+
return self._is_evaluated
|
|
758
|
+
|
|
759
|
+
@is_evaluated.setter
|
|
760
|
+
def is_evaluated(self, value: bool):
|
|
761
|
+
"""
|
|
762
|
+
Set flow evaluated state.
|
|
763
|
+
|
|
764
|
+
:param value: New evaluated state (bool)
|
|
765
|
+
"""
|
|
766
|
+
self._is_evaluated = value
|
|
767
|
+
|
|
768
|
+
@property
|
|
769
|
+
def evaluated_value(self) -> float:
|
|
770
|
+
"""
|
|
771
|
+
Get evaluated base value.
|
|
772
|
+
|
|
773
|
+
:return: Evaluated base value (float)
|
|
774
|
+
"""
|
|
775
|
+
return self._evaluated_value
|
|
776
|
+
|
|
777
|
+
@evaluated_value.setter
|
|
778
|
+
def evaluated_value(self, value: float):
|
|
779
|
+
"""
|
|
780
|
+
Set evaluated base value.
|
|
781
|
+
|
|
782
|
+
:param value: New evaluated base value (float)
|
|
783
|
+
"""
|
|
784
|
+
self._evaluated_value = value
|
|
785
|
+
|
|
786
|
+
@property
|
|
787
|
+
def evaluated_share(self) -> float:
|
|
788
|
+
return self._evaluated_share
|
|
789
|
+
|
|
790
|
+
@evaluated_share.setter
|
|
791
|
+
def evaluated_share(self, value: float):
|
|
792
|
+
self._evaluated_share = value
|
|
793
|
+
|
|
794
|
+
@property
|
|
795
|
+
def is_prioritized(self) -> bool:
|
|
796
|
+
return self._is_prioritized
|
|
797
|
+
|
|
798
|
+
@is_prioritized.setter
|
|
799
|
+
def is_prioritized(self, is_prioritized: bool):
|
|
800
|
+
self._is_prioritized = is_prioritized
|
|
801
|
+
|
|
802
|
+
@property
|
|
803
|
+
def indicator_name_to_indicator(self) -> Dict[str, Indicator]:
|
|
804
|
+
return self._indicator_name_to_indicator
|
|
805
|
+
|
|
806
|
+
@property
|
|
807
|
+
def indicator_name_to_evaluated_value(self) -> Dict[str, float]:
|
|
808
|
+
return self._indicator_name_to_evaluated_value
|
|
809
|
+
|
|
810
|
+
def get_indicator_names(self) -> List[str]:
|
|
811
|
+
"""
|
|
812
|
+
Get list of Indicator names (including baseline indicator name).
|
|
813
|
+
|
|
814
|
+
:return: List of Indicator names
|
|
815
|
+
"""
|
|
816
|
+
return list(self._indicator_name_to_indicator.keys())
|
|
817
|
+
|
|
818
|
+
def get_indicator_units(self) -> List[str]:
|
|
819
|
+
"""
|
|
820
|
+
Get list of Indicator unit names (including baseline indicator unit).
|
|
821
|
+
|
|
822
|
+
:return: List of Indicator unit names
|
|
823
|
+
"""
|
|
824
|
+
indicator_units = []
|
|
825
|
+
for indicator_name, indicator in self._indicator_name_to_indicator.items():
|
|
826
|
+
indicator_units.append(indicator.unit)
|
|
827
|
+
|
|
828
|
+
return indicator_units
|
|
829
|
+
|
|
830
|
+
def get_indicator_conversion_factor(self, indicator_name: str) -> float:
|
|
831
|
+
"""
|
|
832
|
+
|
|
833
|
+
:param indicator_name:
|
|
834
|
+
:return:
|
|
835
|
+
"""
|
|
836
|
+
return self._indicator_name_to_indicator[indicator_name].conversion_factor
|
|
837
|
+
|
|
838
|
+
def get_evaluated_value_for_indicator(self, indicator_name: str) -> float:
|
|
839
|
+
"""
|
|
840
|
+
Get evaluated value for Indicator.
|
|
841
|
+
|
|
842
|
+
:param indicator_name: Target Indicator name (str)
|
|
843
|
+
:return: Evaluated value for Indicator (float)
|
|
844
|
+
"""
|
|
845
|
+
return self._indicator_name_to_evaluated_value[indicator_name]
|
|
846
|
+
|
|
847
|
+
def set_evaluated_value_for_indicator(self, indicator_name: str, value: float):
|
|
848
|
+
"""
|
|
849
|
+
Set evaluated value for Indicator.
|
|
850
|
+
|
|
851
|
+
:param indicator_name: Target Indicator name (str)
|
|
852
|
+
:param value: New evaluated value for Indicator (float)
|
|
853
|
+
"""
|
|
854
|
+
self._indicator_name_to_evaluated_value[indicator_name] = value
|
|
855
|
+
|
|
856
|
+
def evaluate_indicator_values_from_baseline_value(self):
|
|
857
|
+
"""
|
|
858
|
+
Evaluated indicator evaluated value from baseline value.
|
|
859
|
+
"""
|
|
860
|
+
for indicator_name, indicator in self._indicator_name_to_indicator.items():
|
|
861
|
+
evaluated_value = self.evaluated_value * indicator.conversion_factor
|
|
862
|
+
self.set_evaluated_value_for_indicator(indicator_name, evaluated_value)
|
|
863
|
+
|
|
864
|
+
def get_all_evaluated_values(self) -> List[float]:
|
|
865
|
+
"""
|
|
866
|
+
Get list of all evaluated values.
|
|
867
|
+
First index is always the evaluated baseline value.
|
|
868
|
+
Other indices are evaluated indicator values
|
|
869
|
+
|
|
870
|
+
:return: List of evaluated values (list of float)
|
|
871
|
+
"""
|
|
872
|
+
|
|
873
|
+
return [self.evaluated_value] + [value for name, value in self.indicator_name_to_evaluated_value.items()]
|
|
874
|
+
|
|
875
|
+
|
|
876
|
+
# Stock is created for each process that has lifetime
|
|
877
|
+
class Stock(ObjectBase):
|
|
878
|
+
"""
|
|
879
|
+
aiphoria Stock-object:
|
|
880
|
+
|
|
881
|
+
Used to store data for Stock.
|
|
882
|
+
"""
|
|
883
|
+
def __init__(self, params: Process = None, row_number=-1):
|
|
884
|
+
super().__init__()
|
|
885
|
+
self._process = None
|
|
886
|
+
self._id = -1
|
|
887
|
+
|
|
888
|
+
if params is None:
|
|
889
|
+
return
|
|
890
|
+
|
|
891
|
+
self._process = params
|
|
892
|
+
self._id = params.id
|
|
893
|
+
self._row_number = row_number
|
|
894
|
+
|
|
895
|
+
def __str__(self):
|
|
896
|
+
if not self.is_valid():
|
|
897
|
+
return "Stock: no process"
|
|
898
|
+
|
|
899
|
+
s = "Stock: Process='{}', lifetime={}".format(self.id, self.stock_lifetime)
|
|
900
|
+
return s
|
|
901
|
+
|
|
902
|
+
def is_valid(self):
|
|
903
|
+
if not self._process:
|
|
904
|
+
return False
|
|
905
|
+
|
|
906
|
+
return True
|
|
907
|
+
|
|
908
|
+
def __hash__(self):
|
|
909
|
+
return hash(self._process.id)
|
|
910
|
+
|
|
911
|
+
def __eq__(self, other):
|
|
912
|
+
return self.id == other.id
|
|
913
|
+
|
|
914
|
+
@property
|
|
915
|
+
def name(self):
|
|
916
|
+
return self._process.name
|
|
917
|
+
|
|
918
|
+
@property
|
|
919
|
+
def stock_lifetime(self):
|
|
920
|
+
return self._process.stock_lifetime
|
|
921
|
+
|
|
922
|
+
@property
|
|
923
|
+
def stock_distribution_type(self):
|
|
924
|
+
return self._process.stock_distribution_type
|
|
925
|
+
|
|
926
|
+
@property
|
|
927
|
+
def stock_distribution_params(self):
|
|
928
|
+
return self._process.stock_distribution_params
|
|
929
|
+
|
|
930
|
+
|
|
931
|
+
class FlowModifier(ObjectBase):
|
|
932
|
+
"""
|
|
933
|
+
aiphoria FlowModifier-object:
|
|
934
|
+
|
|
935
|
+
Used to store data for scenario related flow modifications.
|
|
936
|
+
"""
|
|
937
|
+
def __init__(self, params: pd.Series = None):
|
|
938
|
+
super().__init__()
|
|
939
|
+
|
|
940
|
+
self._scenario_name: str = ""
|
|
941
|
+
self._source_process_id: str = ""
|
|
942
|
+
self._target_process_id: str = ""
|
|
943
|
+
self._change_in_value: Union[float, None] = None
|
|
944
|
+
self._target_value: Union[float, None] = None
|
|
945
|
+
self._change_type: str = ""
|
|
946
|
+
self._start_year: int = 0
|
|
947
|
+
self._end_year: int = 0
|
|
948
|
+
self._function_type: str = ""
|
|
949
|
+
self._apply_to_targets: bool = True
|
|
950
|
+
self._opposite_target_process_ids = []
|
|
951
|
+
|
|
952
|
+
if params is None:
|
|
953
|
+
# Invalid: no parameters
|
|
954
|
+
return
|
|
955
|
+
|
|
956
|
+
if all(not elem for elem in params):
|
|
957
|
+
# Invalid: all parameters None
|
|
958
|
+
return
|
|
959
|
+
|
|
960
|
+
# Alias parameters to more readable form
|
|
961
|
+
param_scenario_name = params.iloc[0]
|
|
962
|
+
param_source_process_id = params.iloc[1]
|
|
963
|
+
param_target_process_id = params.iloc[2]
|
|
964
|
+
param_change_in_value = params.iloc[3]
|
|
965
|
+
param_target_value = params.iloc[4]
|
|
966
|
+
param_change_type = params.iloc[5]
|
|
967
|
+
param_start_year = params.iloc[6]
|
|
968
|
+
param_end_year = params.iloc[7]
|
|
969
|
+
param_function_type = params.iloc[8]
|
|
970
|
+
param_apply_to_targets = params.iloc[9]
|
|
971
|
+
|
|
972
|
+
self._scenario_name = self._parse_as(param_scenario_name, str)[0]
|
|
973
|
+
self._source_process_id = self._parse_as(param_source_process_id, str)[0]
|
|
974
|
+
self._target_process_id = self._parse_as(param_target_process_id, str)[0]
|
|
975
|
+
|
|
976
|
+
# This is the delta change of the value and means that it's error
|
|
977
|
+
# if target flow has ABS type and the 'change in value' is REL
|
|
978
|
+
# NOTE: Either of self._change_in_value of self._target_value must be defined
|
|
979
|
+
if param_change_in_value is not None:
|
|
980
|
+
self._change_in_value = self._parse_as(param_change_in_value, float)[0]
|
|
981
|
+
|
|
982
|
+
if param_target_value is not None:
|
|
983
|
+
self._target_value = self._parse_as(param_target_value, float)[0]
|
|
984
|
+
|
|
985
|
+
# Change type
|
|
986
|
+
# NOTE: Convert change type to valid ChangeType enum if found. Otherwise use the value from parameter.
|
|
987
|
+
self._change_type = param_change_type
|
|
988
|
+
for change_type in ChangeType:
|
|
989
|
+
if self._parse_as(self._change_type, str)[0].lower() == change_type.lower():
|
|
990
|
+
self._change_type = change_type
|
|
991
|
+
|
|
992
|
+
self._start_year = self._parse_as(param_start_year, int)[0]
|
|
993
|
+
self._end_year = self._parse_as(param_end_year, int)[0]
|
|
994
|
+
|
|
995
|
+
# Function type
|
|
996
|
+
# NOTE: Convert function type to valid FunctionType enum if found. Otherwise use the value from parameter.
|
|
997
|
+
self._function_type = param_function_type
|
|
998
|
+
for function_type in FunctionType:
|
|
999
|
+
if self._parse_as(self._function_type, str)[0].lower() == function_type:
|
|
1000
|
+
self._function_type = function_type
|
|
1001
|
+
|
|
1002
|
+
# Apply to siblings
|
|
1003
|
+
if param_apply_to_targets is not None:
|
|
1004
|
+
self._apply_to_targets = self._parse_as(param_apply_to_targets, bool)[0]
|
|
1005
|
+
|
|
1006
|
+
# Check how many target nodes with opposite effect there is
|
|
1007
|
+
for process_id in list(params[10:]):
|
|
1008
|
+
if process_id is not None:
|
|
1009
|
+
self._opposite_target_process_ids.append(process_id)
|
|
1010
|
+
|
|
1011
|
+
def __str__(self):
|
|
1012
|
+
s = "Flow modifier: scenario_name='{}', source_process_id='{}', target_process_id='{}', change_in_value='{}', " \
|
|
1013
|
+
"target_value='{}', change_type='{}', start_year='{}', end_year='{}', function_type='{}'".format(
|
|
1014
|
+
self.scenario_name,
|
|
1015
|
+
self.source_process_id,
|
|
1016
|
+
self.target_process_id,
|
|
1017
|
+
self.change_in_value,
|
|
1018
|
+
self.target_value,
|
|
1019
|
+
self.change_type,
|
|
1020
|
+
self.start_year,
|
|
1021
|
+
self.end_year,
|
|
1022
|
+
self.function_type,
|
|
1023
|
+
)
|
|
1024
|
+
return s
|
|
1025
|
+
|
|
1026
|
+
def is_valid(self) -> bool:
|
|
1027
|
+
return True
|
|
1028
|
+
|
|
1029
|
+
@property
|
|
1030
|
+
def use_change_in_value(self) -> bool:
|
|
1031
|
+
return self.change_in_value is not None
|
|
1032
|
+
|
|
1033
|
+
@property
|
|
1034
|
+
def use_target_value(self) -> bool:
|
|
1035
|
+
return self.target_value is not None
|
|
1036
|
+
|
|
1037
|
+
@property
|
|
1038
|
+
def is_change_type_value(self) -> bool:
|
|
1039
|
+
"""
|
|
1040
|
+
Check if change type is value type (= absolute change in flow value or in flow share)
|
|
1041
|
+
:return: True if change type is value type, False otherwise.
|
|
1042
|
+
"""
|
|
1043
|
+
return self.change_type == ChangeType.Value
|
|
1044
|
+
|
|
1045
|
+
@property
|
|
1046
|
+
def is_change_type_proportional(self) -> bool:
|
|
1047
|
+
"""
|
|
1048
|
+
Is change type proportional (= relative change in flow value or in flow share)
|
|
1049
|
+
:return: True if change type is proportional, False otherwise.
|
|
1050
|
+
"""
|
|
1051
|
+
return self.change_type == ChangeType.Proportional
|
|
1052
|
+
|
|
1053
|
+
@property
|
|
1054
|
+
def has_target(self) -> bool:
|
|
1055
|
+
"""
|
|
1056
|
+
Does flow modifier target any flow?
|
|
1057
|
+
:return: Bool
|
|
1058
|
+
"""
|
|
1059
|
+
return self.target_process_id != ""
|
|
1060
|
+
|
|
1061
|
+
@property
|
|
1062
|
+
def has_opposite_targets(self) -> bool:
|
|
1063
|
+
"""
|
|
1064
|
+
Does flow modifier have opposite targets?
|
|
1065
|
+
:return: Bool
|
|
1066
|
+
"""
|
|
1067
|
+
return len(self.opposite_target_process_ids) > 0
|
|
1068
|
+
|
|
1069
|
+
|
|
1070
|
+
@staticmethod
|
|
1071
|
+
def _parse_as(val: any, target_type: any) -> (bool, any):
|
|
1072
|
+
"""
|
|
1073
|
+
Parse variable as type.
|
|
1074
|
+
Returns tuple (bool, value).
|
|
1075
|
+
If parsing fails then value is the original val.
|
|
1076
|
+
|
|
1077
|
+
:param val: Variable
|
|
1078
|
+
:param type: Target type
|
|
1079
|
+
:return: Tuple (bool, value)
|
|
1080
|
+
"""
|
|
1081
|
+
ok = True
|
|
1082
|
+
result = val
|
|
1083
|
+
try:
|
|
1084
|
+
result = target_type(val)
|
|
1085
|
+
except ValueError as ex:
|
|
1086
|
+
ok = False
|
|
1087
|
+
|
|
1088
|
+
return result, ok
|
|
1089
|
+
|
|
1090
|
+
@property
|
|
1091
|
+
def scenario_name(self) -> str:
|
|
1092
|
+
return self._scenario_name
|
|
1093
|
+
|
|
1094
|
+
@property
|
|
1095
|
+
def source_process_id(self) -> str:
|
|
1096
|
+
return self._source_process_id
|
|
1097
|
+
|
|
1098
|
+
@property
|
|
1099
|
+
def target_process_id(self) -> str:
|
|
1100
|
+
return self._target_process_id
|
|
1101
|
+
|
|
1102
|
+
@property
|
|
1103
|
+
def target_flow_id(self) -> str:
|
|
1104
|
+
return Flow.make_flow_id(self.source_process_id, self.target_process_id)
|
|
1105
|
+
|
|
1106
|
+
@property
|
|
1107
|
+
def change_in_value(self) -> float:
|
|
1108
|
+
return self._change_in_value
|
|
1109
|
+
|
|
1110
|
+
@property
|
|
1111
|
+
def target_value(self) -> float:
|
|
1112
|
+
return self._target_value
|
|
1113
|
+
|
|
1114
|
+
@property
|
|
1115
|
+
def change_type(self) -> str:
|
|
1116
|
+
return self._change_type
|
|
1117
|
+
|
|
1118
|
+
@property
|
|
1119
|
+
def start_year(self) -> int:
|
|
1120
|
+
return self._start_year
|
|
1121
|
+
|
|
1122
|
+
@property
|
|
1123
|
+
def end_year(self) -> int:
|
|
1124
|
+
return self._end_year
|
|
1125
|
+
|
|
1126
|
+
@property
|
|
1127
|
+
def function_type(self) -> Union[FunctionType, str]:
|
|
1128
|
+
return self._function_type
|
|
1129
|
+
|
|
1130
|
+
@property
|
|
1131
|
+
def apply_to_targets(self) -> bool:
|
|
1132
|
+
return self._apply_to_targets
|
|
1133
|
+
|
|
1134
|
+
@property
|
|
1135
|
+
def opposite_target_process_ids(self) -> List[str]:
|
|
1136
|
+
return self._opposite_target_process_ids
|
|
1137
|
+
|
|
1138
|
+
def get_year_range(self) -> List[int]:
|
|
1139
|
+
"""
|
|
1140
|
+
Get list of years FlowModifier is used.
|
|
1141
|
+
|
|
1142
|
+
:return: List of years (integers)
|
|
1143
|
+
"""
|
|
1144
|
+
return [year for year in range(self.start_year, self.end_year + 1)]
|
|
1145
|
+
|
|
1146
|
+
def get_opposite_target_flow_ids(self) -> List[str]:
|
|
1147
|
+
"""
|
|
1148
|
+
Get list of opposite target flow IDs.
|
|
1149
|
+
|
|
1150
|
+
:return: List of opposite target flow IDs.
|
|
1151
|
+
"""
|
|
1152
|
+
opposite_flow_ids = []
|
|
1153
|
+
for opposite_process_id in self.opposite_target_process_ids:
|
|
1154
|
+
flow_id = Flow.make_flow_id(self.source_process_id, opposite_process_id)
|
|
1155
|
+
opposite_flow_ids.append(flow_id)
|
|
1156
|
+
return opposite_flow_ids
|
|
1157
|
+
|
|
1158
|
+
|
|
1159
|
+
class ScenarioData(object):
|
|
1160
|
+
"""
|
|
1161
|
+
Data class for holding Scenario data.
|
|
1162
|
+
|
|
1163
|
+
DataChecker builds ScenarioData-object that can be used for FlowSolver.
|
|
1164
|
+
"""
|
|
1165
|
+
|
|
1166
|
+
def __init__(self,
|
|
1167
|
+
years: List[int] = None,
|
|
1168
|
+
year_to_process_id_to_process: Dict[int, Dict[str, Process]] = None,
|
|
1169
|
+
year_to_process_id_to_flow_ids: Dict[int, Dict[str, Dict[str, List[str]]]] = None,
|
|
1170
|
+
year_to_flow_id_to_flow: Dict[int, Dict[str, Flow]] = None,
|
|
1171
|
+
stocks: List[Stock] = None,
|
|
1172
|
+
process_id_to_stock: Dict[str, Stock] = None,
|
|
1173
|
+
unique_process_id_to_process: Dict[str, Process] = None,
|
|
1174
|
+
unique_flow_id_to_flow: Dict[str, Flow] = None,
|
|
1175
|
+
use_virtual_flows: bool = True,
|
|
1176
|
+
virtual_flows_epsilon: float = 0.1,
|
|
1177
|
+
baseline_value_name: str = "Baseline",
|
|
1178
|
+
baseline_unit_name: str = "Baseline unit",
|
|
1179
|
+
indicator_name_to_indicator: Dict[str, Indicator] = None
|
|
1180
|
+
):
|
|
1181
|
+
|
|
1182
|
+
if years is None:
|
|
1183
|
+
years = []
|
|
1184
|
+
|
|
1185
|
+
if year_to_process_id_to_process is None:
|
|
1186
|
+
year_to_process_id_to_process = {}
|
|
1187
|
+
|
|
1188
|
+
if year_to_process_id_to_flow_ids is None:
|
|
1189
|
+
year_to_process_id_to_flow_ids = {}
|
|
1190
|
+
|
|
1191
|
+
if year_to_flow_id_to_flow is None:
|
|
1192
|
+
year_to_flow_id_to_flow = {}
|
|
1193
|
+
|
|
1194
|
+
if stocks is None:
|
|
1195
|
+
stocks = []
|
|
1196
|
+
|
|
1197
|
+
if process_id_to_stock is None:
|
|
1198
|
+
process_id_to_stock = {}
|
|
1199
|
+
|
|
1200
|
+
if unique_process_id_to_process is None:
|
|
1201
|
+
unique_process_id_to_process = {}
|
|
1202
|
+
|
|
1203
|
+
if unique_flow_id_to_flow is None:
|
|
1204
|
+
unique_flow_id_to_flow = {}
|
|
1205
|
+
|
|
1206
|
+
if indicator_name_to_indicator is None:
|
|
1207
|
+
indicator_name_to_indicator = {}
|
|
1208
|
+
|
|
1209
|
+
self._year_to_flow_id_to_flow = year_to_flow_id_to_flow
|
|
1210
|
+
self._year_to_process_id_to_process = year_to_process_id_to_process
|
|
1211
|
+
self._year_to_process_id_to_flow_ids = year_to_process_id_to_flow_ids
|
|
1212
|
+
|
|
1213
|
+
self._years = years
|
|
1214
|
+
self._year_start = 0
|
|
1215
|
+
self._year_end = 0
|
|
1216
|
+
|
|
1217
|
+
if self._years:
|
|
1218
|
+
self._year_start = min(self._years)
|
|
1219
|
+
self._year_end = max(self._years)
|
|
1220
|
+
|
|
1221
|
+
self._process_id_to_stock = process_id_to_stock
|
|
1222
|
+
self._stocks = stocks
|
|
1223
|
+
self._unique_process_id_to_process = unique_process_id_to_process
|
|
1224
|
+
self._unique_flow_id_to_flow = unique_flow_id_to_flow
|
|
1225
|
+
self._use_virtual_flows = use_virtual_flows
|
|
1226
|
+
self._virtual_flows_epsilon = virtual_flows_epsilon
|
|
1227
|
+
self._baseline_value_name = baseline_value_name
|
|
1228
|
+
self._baseline_unit_name = baseline_unit_name
|
|
1229
|
+
self._indicator_name_to_indicator = indicator_name_to_indicator
|
|
1230
|
+
|
|
1231
|
+
@property
|
|
1232
|
+
def years(self) -> List[int]:
|
|
1233
|
+
"""
|
|
1234
|
+
Get list of years
|
|
1235
|
+
:return: List of years
|
|
1236
|
+
"""
|
|
1237
|
+
return self._years
|
|
1238
|
+
|
|
1239
|
+
@property
|
|
1240
|
+
def year_to_process_id_to_process(self) -> Dict[int, Dict[str, Process]]:
|
|
1241
|
+
"""
|
|
1242
|
+
Get year to Process ID to Process mappings.
|
|
1243
|
+
|
|
1244
|
+
:return: Dictionary (Year -> Process ID -> Process)
|
|
1245
|
+
"""
|
|
1246
|
+
return self._year_to_process_id_to_process
|
|
1247
|
+
|
|
1248
|
+
@property
|
|
1249
|
+
def year_to_process_id_to_flow_ids(self) -> Dict[int, Dict[str, Dict[str, List[str]]]]:
|
|
1250
|
+
"""
|
|
1251
|
+
Get year to Process ID to In/Out to -> List of Flow ID mappings.
|
|
1252
|
+
|
|
1253
|
+
:return: Dictionary (Year -> Process ID -> Dictionary(keys "in", "out") -> List of Flow IDS)
|
|
1254
|
+
"""
|
|
1255
|
+
|
|
1256
|
+
return self._year_to_process_id_to_flow_ids
|
|
1257
|
+
|
|
1258
|
+
@property
|
|
1259
|
+
def year_to_flow_id_to_flow(self) -> Dict[int, Dict[str, Flow]]:
|
|
1260
|
+
"""
|
|
1261
|
+
Get year to Flow ID to Flow mappings.
|
|
1262
|
+
|
|
1263
|
+
:return: Dictionary (Year -> Flow ID -> Flow)
|
|
1264
|
+
"""
|
|
1265
|
+
return self._year_to_flow_id_to_flow
|
|
1266
|
+
|
|
1267
|
+
@property
|
|
1268
|
+
def stocks(self) -> List[Stock]:
|
|
1269
|
+
"""
|
|
1270
|
+
Get list of Stocks
|
|
1271
|
+
:return: List of Stocks
|
|
1272
|
+
"""
|
|
1273
|
+
return self._stocks
|
|
1274
|
+
|
|
1275
|
+
@property
|
|
1276
|
+
def process_id_to_stock(self) -> Dict[str, Stock]:
|
|
1277
|
+
"""
|
|
1278
|
+
Get mapping of Process ID to Stock
|
|
1279
|
+
:return: Dictionary
|
|
1280
|
+
"""
|
|
1281
|
+
return self._process_id_to_stock
|
|
1282
|
+
|
|
1283
|
+
@property
|
|
1284
|
+
def unique_process_id_to_process(self) -> Dict[str, Process]:
|
|
1285
|
+
"""
|
|
1286
|
+
Get mapping of unique Process ID to Process
|
|
1287
|
+
:return: Dictionary
|
|
1288
|
+
"""
|
|
1289
|
+
return self._unique_process_id_to_process
|
|
1290
|
+
|
|
1291
|
+
@property
|
|
1292
|
+
def unique_flow_id_to_flow(self) -> Dict[str, Flow]:
|
|
1293
|
+
"""
|
|
1294
|
+
Get mapping of unique Flow ID to Flows
|
|
1295
|
+
:return: Dictionary
|
|
1296
|
+
"""
|
|
1297
|
+
return self._unique_flow_id_to_flow
|
|
1298
|
+
|
|
1299
|
+
@property
|
|
1300
|
+
def use_virtual_flows(self) -> bool:
|
|
1301
|
+
"""
|
|
1302
|
+
Get boolean flag if using virtual flows
|
|
1303
|
+
:return: bool
|
|
1304
|
+
"""
|
|
1305
|
+
return self._use_virtual_flows
|
|
1306
|
+
|
|
1307
|
+
@property
|
|
1308
|
+
def virtual_flows_epsilon(self) -> float:
|
|
1309
|
+
"""
|
|
1310
|
+
Get maximum allowed difference between input and output flows before creating virtual flows.
|
|
1311
|
+
This is only used if using the virtual flows.
|
|
1312
|
+
:return: Float
|
|
1313
|
+
"""
|
|
1314
|
+
return self._virtual_flows_epsilon
|
|
1315
|
+
|
|
1316
|
+
@property
|
|
1317
|
+
def start_year(self) -> int:
|
|
1318
|
+
"""
|
|
1319
|
+
Get starting year
|
|
1320
|
+
:return: Starting year (int)
|
|
1321
|
+
"""
|
|
1322
|
+
return self._year_start
|
|
1323
|
+
|
|
1324
|
+
@property
|
|
1325
|
+
def end_year(self) -> int:
|
|
1326
|
+
"""
|
|
1327
|
+
Get ending year
|
|
1328
|
+
Ending year is included in simulation.
|
|
1329
|
+
|
|
1330
|
+
:return: Ending year (int)
|
|
1331
|
+
"""
|
|
1332
|
+
return self._year_end
|
|
1333
|
+
|
|
1334
|
+
@property
|
|
1335
|
+
def baseline_value_name(self) -> str:
|
|
1336
|
+
"""
|
|
1337
|
+
Get baseline value name (e.g. "Solid wood equivalent")
|
|
1338
|
+
|
|
1339
|
+
:return: Baseline value name (str)
|
|
1340
|
+
"""
|
|
1341
|
+
return self._baseline_value_name
|
|
1342
|
+
|
|
1343
|
+
@baseline_value_name.setter
|
|
1344
|
+
def baseline_value_name(self, new_name: str):
|
|
1345
|
+
"""
|
|
1346
|
+
Set new baseline value name.
|
|
1347
|
+
|
|
1348
|
+
:param new_name: New baseline value name (str)
|
|
1349
|
+
"""
|
|
1350
|
+
self._baseline_value_name = new_name
|
|
1351
|
+
|
|
1352
|
+
@property
|
|
1353
|
+
def baseline_unit_name(self) -> str:
|
|
1354
|
+
"""
|
|
1355
|
+
Get baseline unit name (e.g. "Mm3")
|
|
1356
|
+
|
|
1357
|
+
:return: Baseline unit name (str)
|
|
1358
|
+
"""
|
|
1359
|
+
return self._baseline_unit_name
|
|
1360
|
+
|
|
1361
|
+
@baseline_unit_name.setter
|
|
1362
|
+
def baseline_unit_name(self, new_name: str):
|
|
1363
|
+
"""
|
|
1364
|
+
Set new baseline unt name.
|
|
1365
|
+
|
|
1366
|
+
:param new_name: New baseline name (str)
|
|
1367
|
+
"""
|
|
1368
|
+
self._baseline_unit_name = new_name
|
|
1369
|
+
|
|
1370
|
+
@property
|
|
1371
|
+
def indicator_name_to_indicator(self) -> Dict[str, Indicator]:
|
|
1372
|
+
"""
|
|
1373
|
+
Get dictionary of Indicator name to Indicator.
|
|
1374
|
+
|
|
1375
|
+
:return: Dictionary (indicator name (str), Indicator)
|
|
1376
|
+
"""
|
|
1377
|
+
return self._indicator_name_to_indicator
|
|
1378
|
+
|
|
1379
|
+
@indicator_name_to_indicator.setter
|
|
1380
|
+
def indicator_name_to_indicator(self, new_indicator_name_to_indicator: Dict[str, Indicator]):
|
|
1381
|
+
"""
|
|
1382
|
+
Set new Indicator name to Indicator mapping.
|
|
1383
|
+
|
|
1384
|
+
:param new_indicator_name_to_indicator: New Indicator name to Indicator dictionary
|
|
1385
|
+
"""
|
|
1386
|
+
self._indicator_name_to_indicator = new_indicator_name_to_indicator
|
|
1387
|
+
|
|
1388
|
+
|
|
1389
|
+
class ScenarioDefinition(object):
|
|
1390
|
+
"""
|
|
1391
|
+
ScenarioDefinition is wrapper object that contains scenario name and all flow modifiers that are applied
|
|
1392
|
+
for the Scenario.
|
|
1393
|
+
|
|
1394
|
+
Actual building of Scenarios happens inside DataChecker.build_scenarios()
|
|
1395
|
+
"""
|
|
1396
|
+
def __init__(self, name: str = None, flow_modifiers: List[FlowModifier] = None):
|
|
1397
|
+
if name is None:
|
|
1398
|
+
name = "Baseline scenario"
|
|
1399
|
+
|
|
1400
|
+
if flow_modifiers is None:
|
|
1401
|
+
flow_modifiers = []
|
|
1402
|
+
|
|
1403
|
+
self._name = name
|
|
1404
|
+
self._flow_modifiers = flow_modifiers
|
|
1405
|
+
|
|
1406
|
+
@property
|
|
1407
|
+
def name(self) -> str:
|
|
1408
|
+
"""
|
|
1409
|
+
Get scenario name.
|
|
1410
|
+
|
|
1411
|
+
:return: Scenario name
|
|
1412
|
+
"""
|
|
1413
|
+
return self._name
|
|
1414
|
+
|
|
1415
|
+
@property
|
|
1416
|
+
def flow_modifiers(self) -> List[FlowModifier]:
|
|
1417
|
+
"""
|
|
1418
|
+
Get list of FlowModifiers.
|
|
1419
|
+
These are the rules that are applied to Scenario.
|
|
1420
|
+
|
|
1421
|
+
:return: List of FlowModifier-objects
|
|
1422
|
+
"""
|
|
1423
|
+
return self._flow_modifiers
|
|
1424
|
+
|
|
1425
|
+
|
|
1426
|
+
class Scenario(object):
|
|
1427
|
+
"""
|
|
1428
|
+
Scenario is wrapper object that contains scenario name and all flow modifiers that are
|
|
1429
|
+
happening in the scenario
|
|
1430
|
+
"""
|
|
1431
|
+
|
|
1432
|
+
def __init__(self, definition: ScenarioDefinition = None, data: ScenarioData = None, model_params=None):
|
|
1433
|
+
if definition is None:
|
|
1434
|
+
definition = ScenarioDefinition()
|
|
1435
|
+
|
|
1436
|
+
if data is None:
|
|
1437
|
+
data = ScenarioData()
|
|
1438
|
+
|
|
1439
|
+
if model_params is None:
|
|
1440
|
+
model_params = {}
|
|
1441
|
+
|
|
1442
|
+
self._scenario_definition = definition
|
|
1443
|
+
self._scenario_data = data
|
|
1444
|
+
self._flow_solver = None
|
|
1445
|
+
self._odym_data = None
|
|
1446
|
+
self._model_params = model_params
|
|
1447
|
+
self._mfa_system = None
|
|
1448
|
+
|
|
1449
|
+
@property
|
|
1450
|
+
def name(self) -> str:
|
|
1451
|
+
return self._scenario_definition.name
|
|
1452
|
+
|
|
1453
|
+
@property
|
|
1454
|
+
def scenario_definition(self) -> ScenarioDefinition:
|
|
1455
|
+
return self._scenario_definition
|
|
1456
|
+
|
|
1457
|
+
@property
|
|
1458
|
+
def scenario_data(self) -> ScenarioData:
|
|
1459
|
+
return self._scenario_data
|
|
1460
|
+
|
|
1461
|
+
# Flow solver
|
|
1462
|
+
@property
|
|
1463
|
+
def flow_solver(self):
|
|
1464
|
+
"""
|
|
1465
|
+
Get FlowSolver that is assigned to Scenario.
|
|
1466
|
+
:return: FlowSolver (FlowSolver)
|
|
1467
|
+
"""
|
|
1468
|
+
return self._flow_solver
|
|
1469
|
+
|
|
1470
|
+
@flow_solver.setter
|
|
1471
|
+
def flow_solver(self, flow_solver):
|
|
1472
|
+
self._flow_solver = flow_solver
|
|
1473
|
+
|
|
1474
|
+
@property
|
|
1475
|
+
def model_params(self) -> Dict[str, Any]:
|
|
1476
|
+
return self._model_params
|
|
1477
|
+
|
|
1478
|
+
@property
|
|
1479
|
+
def mfa_system(self) -> MFAsystem:
|
|
1480
|
+
"""
|
|
1481
|
+
Get stored ODYM MFA system.
|
|
1482
|
+
:return: MFAsystem-object
|
|
1483
|
+
"""
|
|
1484
|
+
return self._mfa_system
|
|
1485
|
+
|
|
1486
|
+
@mfa_system.setter
|
|
1487
|
+
def mfa_system(self, mfa_system) -> None:
|
|
1488
|
+
"""
|
|
1489
|
+
Set new MFAsystem
|
|
1490
|
+
:param mfa_system: Target MFAsystem-object
|
|
1491
|
+
"""
|
|
1492
|
+
self._mfa_system = mfa_system
|
|
1493
|
+
|
|
1494
|
+
def copy_from_baseline_scenario_data(self, scenario_data: ScenarioData):
|
|
1495
|
+
"""
|
|
1496
|
+
Copy ScenarioData from baseline Scenario.
|
|
1497
|
+
Data is deep copied and is not referencing to original data anymore.
|
|
1498
|
+
|
|
1499
|
+
:param scenario_data: ScenarioData from baseline FlowSolver.
|
|
1500
|
+
"""
|
|
1501
|
+
self._scenario_data = copy.deepcopy(scenario_data)
|
|
1502
|
+
|
|
1503
|
+
|
|
1504
|
+
class Color(ObjectBase):
|
|
1505
|
+
def __init__(self, params: Union[List, pd.Series] = None, row_number=-1):
|
|
1506
|
+
super().__init__()
|
|
1507
|
+
self._name: str = ""
|
|
1508
|
+
self._value: str = ""
|
|
1509
|
+
self.row_number = row_number
|
|
1510
|
+
|
|
1511
|
+
if params is None:
|
|
1512
|
+
return
|
|
1513
|
+
|
|
1514
|
+
# Handle processing list and pd.Series differently
|
|
1515
|
+
name_val = ""
|
|
1516
|
+
value_val = ""
|
|
1517
|
+
if isinstance(params, list):
|
|
1518
|
+
name_val = str(params[0])
|
|
1519
|
+
value_val = str(params[1])
|
|
1520
|
+
|
|
1521
|
+
if isinstance(params, pd.Series):
|
|
1522
|
+
name_val = str(params.iloc[0])
|
|
1523
|
+
value_val = str(params.iloc[1])
|
|
1524
|
+
|
|
1525
|
+
self.name = name_val
|
|
1526
|
+
self.value = value_val
|
|
1527
|
+
|
|
1528
|
+
def __str__(self) -> str:
|
|
1529
|
+
"""
|
|
1530
|
+
Returns the string presentation of the Color in format:
|
|
1531
|
+
#rrggbb
|
|
1532
|
+
where
|
|
1533
|
+
rr = red component
|
|
1534
|
+
gg = green component
|
|
1535
|
+
bb = blue component
|
|
1536
|
+
|
|
1537
|
+
:return: Color as hexadecimal string, prefixed with character '#'
|
|
1538
|
+
"""
|
|
1539
|
+
return self.value.lower()
|
|
1540
|
+
|
|
1541
|
+
def is_valid(self) -> bool:
|
|
1542
|
+
return not self.name and self.value
|
|
1543
|
+
|
|
1544
|
+
@property
|
|
1545
|
+
def name(self) -> str:
|
|
1546
|
+
"""
|
|
1547
|
+
Get color name.
|
|
1548
|
+
|
|
1549
|
+
:return: Color name (str)
|
|
1550
|
+
"""
|
|
1551
|
+
return self._name
|
|
1552
|
+
|
|
1553
|
+
@name.setter
|
|
1554
|
+
def name(self, new_name: str):
|
|
1555
|
+
"""
|
|
1556
|
+
Set color name.
|
|
1557
|
+
|
|
1558
|
+
:param new_name: New color name
|
|
1559
|
+
"""
|
|
1560
|
+
self._name = new_name
|
|
1561
|
+
|
|
1562
|
+
@property
|
|
1563
|
+
def value(self) -> str:
|
|
1564
|
+
"""
|
|
1565
|
+
Color value (hexadecimal, e.g. #AABBCC) prefixed with the character '#'
|
|
1566
|
+
"""
|
|
1567
|
+
return self._value
|
|
1568
|
+
|
|
1569
|
+
@value.setter
|
|
1570
|
+
def value(self, new_value: str):
|
|
1571
|
+
"""
|
|
1572
|
+
Set new color value (hexadecimal).
|
|
1573
|
+
New color must be prefixed with the character '#'.
|
|
1574
|
+
|
|
1575
|
+
:param new_value: New color value (hexadecimal)
|
|
1576
|
+
"""
|
|
1577
|
+
self._value = new_value
|
|
1578
|
+
|
|
1579
|
+
def get_red_as_float(self) -> float:
|
|
1580
|
+
"""
|
|
1581
|
+
Get red component value in range [0, 1]
|
|
1582
|
+
:return: Red value (float)
|
|
1583
|
+
"""
|
|
1584
|
+
return self._hex_to_normalized_float(self.value[1:][0:2])
|
|
1585
|
+
|
|
1586
|
+
def get_green_as_float(self) -> float:
|
|
1587
|
+
"""
|
|
1588
|
+
Get green component value in range [0, 1]
|
|
1589
|
+
:return: Green value (float)
|
|
1590
|
+
"""
|
|
1591
|
+
return self._hex_to_normalized_float(self.value[1:][2:4])
|
|
1592
|
+
|
|
1593
|
+
def get_blue_as_float(self) -> float:
|
|
1594
|
+
"""
|
|
1595
|
+
Get blue component value in range [0, 1]
|
|
1596
|
+
:return: Blue value (float)
|
|
1597
|
+
"""
|
|
1598
|
+
return self._hex_to_normalized_float(self.value[1:][4:6])
|
|
1599
|
+
|
|
1600
|
+
def _hex_to_normalized_float(self, hex_value: str) -> float:
|
|
1601
|
+
return int(hex_value, 16) / 255.0
|
|
1602
|
+
|
|
1603
|
+
|
|
1604
|
+
class ProcessEntry(object):
|
|
1605
|
+
"""
|
|
1606
|
+
Internal storage class for Process entry (process, inflows, and outflows).
|
|
1607
|
+
Used when storing Process data in DataFrames.
|
|
1608
|
+
"""
|
|
1609
|
+
|
|
1610
|
+
KEY_IN: str = "in"
|
|
1611
|
+
KEY_OUT: str = "out"
|
|
1612
|
+
|
|
1613
|
+
def __init__(self, process: Process = None):
|
|
1614
|
+
"""
|
|
1615
|
+
Initialize ProcessEntry.
|
|
1616
|
+
Makes deep copy of target Process.
|
|
1617
|
+
|
|
1618
|
+
:param process: Target Process
|
|
1619
|
+
"""
|
|
1620
|
+
self._process = process
|
|
1621
|
+
self._flows = {self.KEY_IN: {}, self.KEY_OUT: {}}
|
|
1622
|
+
|
|
1623
|
+
@property
|
|
1624
|
+
def process(self) -> Process:
|
|
1625
|
+
return self._process
|
|
1626
|
+
|
|
1627
|
+
@property
|
|
1628
|
+
def flows(self) -> Dict[str, Dict[str, Flow]]:
|
|
1629
|
+
return self._flows
|
|
1630
|
+
|
|
1631
|
+
@flows.setter
|
|
1632
|
+
def flows(self, flows: Dict[str, Dict[str, Flow]]):
|
|
1633
|
+
self._flows = flows
|
|
1634
|
+
|
|
1635
|
+
@property
|
|
1636
|
+
def inflows(self) -> List[Flow]:
|
|
1637
|
+
return list(self._flows[self.KEY_IN].values())
|
|
1638
|
+
|
|
1639
|
+
@inflows.setter
|
|
1640
|
+
def inflows(self, flows: List[Flow]):
|
|
1641
|
+
self._flows[self.KEY_IN] = {flow.id: flow for flow in flows}
|
|
1642
|
+
|
|
1643
|
+
@property
|
|
1644
|
+
def inflows_as_dict(self) -> Dict[str, Flow]:
|
|
1645
|
+
return self._flows[self.KEY_IN]
|
|
1646
|
+
|
|
1647
|
+
@property
|
|
1648
|
+
def outflows(self) -> List[Flow]:
|
|
1649
|
+
return list(self._flows[self.KEY_OUT].values())
|
|
1650
|
+
|
|
1651
|
+
@outflows.setter
|
|
1652
|
+
def outflows(self, flows: List[Flow]):
|
|
1653
|
+
self._flows[self.KEY_OUT] = {flow.id: flow for flow in flows}
|
|
1654
|
+
|
|
1655
|
+
def outflows_as_dict(self) -> Dict[str, Flow]:
|
|
1656
|
+
return self._flows[self.KEY_OUT]
|
|
1657
|
+
|
|
1658
|
+
def add_inflow(self, flow: Flow):
|
|
1659
|
+
self._flows[self.KEY_IN][flow.id] = flow
|
|
1660
|
+
|
|
1661
|
+
def add_outflow(self, flow: Flow):
|
|
1662
|
+
self._flows[self.KEY_OUT][flow.id] = flow
|
|
1663
|
+
|
|
1664
|
+
def remove_inflow(self, flow_id: str):
|
|
1665
|
+
"""
|
|
1666
|
+
Remove inflow by Flow ID.
|
|
1667
|
+
Raises Exception if Flow ID is not found in inflows.
|
|
1668
|
+
|
|
1669
|
+
:param flow_id: Target Flow ID
|
|
1670
|
+
:raises Exception If Flow ID is not found
|
|
1671
|
+
"""
|
|
1672
|
+
removed_flow_id = self._flows[self.KEY_IN].pop(flow_id, None)
|
|
1673
|
+
if not removed_flow_id:
|
|
1674
|
+
raise Exception("No flow_id {} in inflows".format(flow_id))
|
|
1675
|
+
|
|
1676
|
+
def remove_outflow(self, flow_id: str):
|
|
1677
|
+
"""
|
|
1678
|
+
Remove outflow by Flow ID.
|
|
1679
|
+
Raises Exception if Flow ID is not found in outflows.
|
|
1680
|
+
|
|
1681
|
+
:param flow_id: Target Flow ID
|
|
1682
|
+
:raises Exception If Flow ID is not found
|
|
1683
|
+
"""
|
|
1684
|
+
removed_flow_id = self._flows[self.KEY_OUT].pop(flow_id, None)
|
|
1685
|
+
if not removed_flow_id:
|
|
1686
|
+
raise Exception("No flow_id {} in outflows".format(flow_id))
|