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,269 @@
|
|
|
1
|
+
# Parameters names are defined in this file
|
|
2
|
+
# and DataProvider file provides the default
|
|
3
|
+
# values for each parameter
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import Dict, Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ParameterName(str, Enum):
|
|
9
|
+
"""
|
|
10
|
+
Parameter enumerations used in settings file
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
# ***********************
|
|
14
|
+
# * Required parameters *
|
|
15
|
+
# ***********************
|
|
16
|
+
|
|
17
|
+
# Process related
|
|
18
|
+
SheetNameProcesses: str = "sheet_name_processes"
|
|
19
|
+
SkipNumRowsProcesses: str = "skip_num_rows_processes"
|
|
20
|
+
IgnoreColumnsProcesses: str = "ignore_columns_processes"
|
|
21
|
+
|
|
22
|
+
# Flow related
|
|
23
|
+
SheetNameFlows: str = "sheet_name_flows"
|
|
24
|
+
SkipNumRowsFlows: str = "skip_num_rows_flows"
|
|
25
|
+
IgnoreColumnsFlows: str = "ignore_columns_flows"
|
|
26
|
+
|
|
27
|
+
# Model parameters
|
|
28
|
+
StartYear: str = "start_year"
|
|
29
|
+
EndYear: str = "end_year"
|
|
30
|
+
DetectYearRange: str = "detect_year_range"
|
|
31
|
+
UseVirtualFlows: str = "use_virtual_flows"
|
|
32
|
+
VirtualFlowsEpsilon: str = "virtual_flows_epsilon"
|
|
33
|
+
|
|
34
|
+
# ***********************
|
|
35
|
+
# * Optional parameters *
|
|
36
|
+
# ***********************
|
|
37
|
+
ConversionFactorCToCO2: str = "conversion_factor_c_to_co2"
|
|
38
|
+
FillMissingAbsoluteFlows: str = "fill_missing_absolute_flows"
|
|
39
|
+
FillMissingRelativeFlows: str = "fill_missing_relative_flows"
|
|
40
|
+
FillMethod: str = "fill_method"
|
|
41
|
+
|
|
42
|
+
# Scenarios related
|
|
43
|
+
UseScenarios: str = "use_scenarios"
|
|
44
|
+
SheetNameScenarios: str = "sheet_name_scenarios"
|
|
45
|
+
SkipNumRowsScenarios: str = "skip_num_rows_scenarios"
|
|
46
|
+
IgnoreColumnsScenarios: str = "ignore_columns_scenarios"
|
|
47
|
+
ScenarioType: str = "scenario_type"
|
|
48
|
+
|
|
49
|
+
# Sheet name for colors
|
|
50
|
+
SheetNameColors: str = "sheet_name_colors"
|
|
51
|
+
SkipNumRowsColors: str = "skip_num_rows_colors"
|
|
52
|
+
IgnoreColumnsColors: str = "ignore_columns_colors"
|
|
53
|
+
|
|
54
|
+
# Network graph
|
|
55
|
+
CreateNetworkGraphs: str = "create_network_graphs"
|
|
56
|
+
CreateSankeyCharts: str = "create_sankey_charts"
|
|
57
|
+
|
|
58
|
+
# Output path
|
|
59
|
+
OutputPath: str = "output_path"
|
|
60
|
+
|
|
61
|
+
# Show plots
|
|
62
|
+
ShowPlots: str = "show_plots"
|
|
63
|
+
|
|
64
|
+
# Visualize inflows to process IDs
|
|
65
|
+
VisualizeInflowsToProcesses: str = "visualize_inflows_to_processes"
|
|
66
|
+
|
|
67
|
+
# Baseline unit
|
|
68
|
+
# BaseLineValueName: Name of the baseline value e.g. "Solid wood equivalent"
|
|
69
|
+
# BaselineUnitName: Name of the baseline unit e.g. Mm3 SWE
|
|
70
|
+
BaselineValueName: str = "baseline_value_name"
|
|
71
|
+
BaselineUnitName: str = "baseline_unit_name"
|
|
72
|
+
|
|
73
|
+
# Flow prioritization
|
|
74
|
+
PrioritizeTransformationStages: str = "prioritize_transformation_stages"
|
|
75
|
+
PrioritizeLocations: str = "prioritize_locations"
|
|
76
|
+
|
|
77
|
+
# Sheet name for process positions
|
|
78
|
+
SheetNameProcessPositions: str = "sheet_name_process_positions"
|
|
79
|
+
|
|
80
|
+
# Check errors
|
|
81
|
+
CheckErrors: str = "check_errors"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class ParameterFillMethod(str, Enum):
|
|
85
|
+
"""
|
|
86
|
+
Valid values for parameter FillMethod
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
Zeros: str = "Zeros"
|
|
90
|
+
Previous: str = "Previous"
|
|
91
|
+
Next: str = "Next"
|
|
92
|
+
Interpolate: str = "Interpolate"
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class ParameterScenarioType(str, Enum):
|
|
96
|
+
"""
|
|
97
|
+
Valid values for parameter ScenarioType
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
Constrained: str = "Constrained"
|
|
101
|
+
Unconstrained: str = "Unconstrained"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# Parameters used for Process/Flows/Stocks in settings file
|
|
105
|
+
class StockDistributionType(str, Enum):
|
|
106
|
+
"""
|
|
107
|
+
Stock distribution types and decay functions
|
|
108
|
+
"""
|
|
109
|
+
Fixed: str = "Fixed"
|
|
110
|
+
Normal: str = "Normal"
|
|
111
|
+
LogNormal: str = "LogNormal"
|
|
112
|
+
FoldedNormal: str = "FoldedNormal"
|
|
113
|
+
Weibull: str = "Weibull"
|
|
114
|
+
Simple: str = "Simple"
|
|
115
|
+
LandfillDecayWood: str = "LandfillDecayWood"
|
|
116
|
+
LandfillDecayPaper: str = "LandfillDecayPaper"
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class LandfillDecayParameter(str, Enum):
|
|
120
|
+
"""
|
|
121
|
+
Parameter names for LandFillDecay* types
|
|
122
|
+
"""
|
|
123
|
+
Condition: str = "condition"
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
# *********************************
|
|
127
|
+
# * Stock distribution parameters *
|
|
128
|
+
# *********************************
|
|
129
|
+
|
|
130
|
+
class StockDistributionParameter(str, Enum):
|
|
131
|
+
"""
|
|
132
|
+
Stock distribution parameters.
|
|
133
|
+
"""
|
|
134
|
+
StdDev: str = "stddev"
|
|
135
|
+
Shape: str = "shape"
|
|
136
|
+
Scale: str = "scale"
|
|
137
|
+
Condition: str = "condition"
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class StockDistributionParameterValueType(object):
|
|
141
|
+
"""
|
|
142
|
+
Storage class for stock distribution parameters.
|
|
143
|
+
Emulates Dictionary behaviour allowing to get value using
|
|
144
|
+
the [] notation e.g.
|
|
145
|
+
StockDistributionParameterValueType[StockDistributionParameter.StdDev]
|
|
146
|
+
StockDistributionParameterValueType["stddev"]
|
|
147
|
+
"""
|
|
148
|
+
parameter_to_value_type = {
|
|
149
|
+
StockDistributionParameter.StdDev: float,
|
|
150
|
+
StockDistributionParameter.Shape: float,
|
|
151
|
+
StockDistributionParameter.Scale: float,
|
|
152
|
+
StockDistributionParameter.Condition: str,
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
def __class_getitem__(cls, item):
|
|
156
|
+
"""
|
|
157
|
+
Get value type for StockDistributionParameter
|
|
158
|
+
|
|
159
|
+
:param item: Item (str or StockDistributionParameter)
|
|
160
|
+
:return:
|
|
161
|
+
"""
|
|
162
|
+
item_enum = None
|
|
163
|
+
if isinstance(item, str):
|
|
164
|
+
try:
|
|
165
|
+
item_enum = StockDistributionParameter(item)
|
|
166
|
+
except ValueError:
|
|
167
|
+
pass
|
|
168
|
+
|
|
169
|
+
if item_enum is None:
|
|
170
|
+
#raise Exception("{} is not valid StockDistributionParameter".format(item_enum))
|
|
171
|
+
pass
|
|
172
|
+
|
|
173
|
+
value_type = cls.parameter_to_value_type.get(item_enum, None)
|
|
174
|
+
if value_type is None:
|
|
175
|
+
#raise Exception("No value type for {}!".format(item_enum))
|
|
176
|
+
pass
|
|
177
|
+
|
|
178
|
+
return value_type
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class RequiredStockDistributionParameters(object):
|
|
182
|
+
"""
|
|
183
|
+
Storage class for required stock distribution parameters.
|
|
184
|
+
Emulates Dictionary behaviour allowing to get value using
|
|
185
|
+
the [] notation e.g.
|
|
186
|
+
RequiredStockDistributionParameters[StockDistributionType.Fixed]
|
|
187
|
+
RequiredStockDistributionParameters["Fixed"]
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
# NOTE: value must be dictionary
|
|
191
|
+
# NOTE: Key = StockDistributionType, Value: Dictionary (str -> required type)
|
|
192
|
+
stock_distribution_to_required_params = {
|
|
193
|
+
StockDistributionType.Fixed: {},
|
|
194
|
+
StockDistributionType.Normal: {"stddev": float},
|
|
195
|
+
StockDistributionType.LogNormal: {"stddev": float},
|
|
196
|
+
StockDistributionType.FoldedNormal: {"stddev": float},
|
|
197
|
+
StockDistributionType.Weibull: {"shape": float, "scale": float},
|
|
198
|
+
StockDistributionType.Simple: {},
|
|
199
|
+
StockDistributionType.LandfillDecayWood: {"condition": str},
|
|
200
|
+
StockDistributionType.LandfillDecayPaper: {"condition": str},
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
def __class_getitem__(cls, item) -> Dict[str, Any]:
|
|
204
|
+
"""
|
|
205
|
+
Get required parameters for StockDistributionType.
|
|
206
|
+
Parameter 'item' can be either string or StockDistributionType-instance.
|
|
207
|
+
|
|
208
|
+
:param item: String or instance of StockDistributionType
|
|
209
|
+
:return: Dictionary of required parameters (key = parameter name, value = type of required parameter e.g. float)
|
|
210
|
+
"""
|
|
211
|
+
|
|
212
|
+
# Convert string to StockDistributionType Enum
|
|
213
|
+
item_enum = None
|
|
214
|
+
if isinstance(item, str):
|
|
215
|
+
try:
|
|
216
|
+
item_enum = StockDistributionType(item)
|
|
217
|
+
except ValueError:
|
|
218
|
+
pass
|
|
219
|
+
|
|
220
|
+
if item_enum is None:
|
|
221
|
+
# raise Exception("{} is not in RequiredStockDistributionParameters".format(item_name))
|
|
222
|
+
pass
|
|
223
|
+
|
|
224
|
+
params = cls.stock_distribution_to_required_params.get(item_enum, None)
|
|
225
|
+
if params is None:
|
|
226
|
+
# raise Exception("{} is not valid StockDistributionType!".format(item_enum))
|
|
227
|
+
pass
|
|
228
|
+
|
|
229
|
+
return params
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class AllowedStockDistributionParameterValues(object):
|
|
233
|
+
"""
|
|
234
|
+
Storage class for required stock distribution parameter values.
|
|
235
|
+
Emulates Dictionary behaviour allowing to get value using
|
|
236
|
+
the [] notation e.g.
|
|
237
|
+
AllowedStockDistributionParameterValues[StockDistributionType.Fixed]
|
|
238
|
+
AllowedStockDistributionParameterValues["Fixed"]
|
|
239
|
+
"""
|
|
240
|
+
|
|
241
|
+
# NOTE: value must be dictionary
|
|
242
|
+
# NOTE: Key = StockDistributionType, Value: Dictionary (str -> required type)
|
|
243
|
+
stock_distribution_param_to_allowed_values = {
|
|
244
|
+
StockDistributionParameter.Condition: {"Dry": str, "Wet": str, "Managed": str}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
def __class_getitem__(cls, item) -> Dict[str, Any]:
|
|
248
|
+
"""
|
|
249
|
+
Get allowed parameters for StockDistributionParameter.
|
|
250
|
+
Parameter 'item' can be either string or StockDistributionParameter-instance.
|
|
251
|
+
|
|
252
|
+
:param item: String or instance of StockDistributionParameter
|
|
253
|
+
:return: Dictionary of allowed entries (key: StockDistributionParameter-instance, value: type)
|
|
254
|
+
"""
|
|
255
|
+
|
|
256
|
+
# Convert string to StockDistributionParameter Enum
|
|
257
|
+
item_enum = None
|
|
258
|
+
if isinstance(item, str):
|
|
259
|
+
try:
|
|
260
|
+
item_enum = StockDistributionParameter(item)
|
|
261
|
+
except ValueError:
|
|
262
|
+
pass
|
|
263
|
+
|
|
264
|
+
# If item_enum is not found in list of allowed values then return empty Dictionary
|
|
265
|
+
params = cls.stock_distribution_param_to_allowed_values.get(item_enum, None)
|
|
266
|
+
if params is None:
|
|
267
|
+
params = {}
|
|
268
|
+
|
|
269
|
+
return params
|
aiphoria/core/types.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# Flow modifier parameters
|
|
5
|
+
class FunctionType(str, Enum):
|
|
6
|
+
Constant: str = "constant"
|
|
7
|
+
Linear: str = "linear"
|
|
8
|
+
Exponential: str = "exponential"
|
|
9
|
+
Sigmoid: str = "sigmoid"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ChangeType(str, Enum):
|
|
13
|
+
Value: str = "Value"
|
|
14
|
+
Proportional: str = "%"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class FlowType(str, Enum):
|
|
18
|
+
Absolute: str = "ABS"
|
|
19
|
+
Relative: str = "REL"
|
|
20
|
+
|
aiphoria/core/utils.py
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
import sys
|
|
4
|
+
import re
|
|
5
|
+
from typing import List, Dict, Any, Union, Optional
|
|
6
|
+
import numpy as np
|
|
7
|
+
import pandas as pd
|
|
8
|
+
from IPython import get_ipython
|
|
9
|
+
from .datastructures import Scenario, Flow
|
|
10
|
+
import aiphoria.lib.odym.modules.ODYM_Classes as msc
|
|
11
|
+
|
|
12
|
+
global_path_to_output_dir: Union[str, None] = None
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def show_model_parameters(model_params: Dict[str, Any]):
|
|
16
|
+
"""
|
|
17
|
+
Show model parameters (= prints the model parameters to console).
|
|
18
|
+
|
|
19
|
+
:param model_params: Dictionary of model parameters
|
|
20
|
+
"""
|
|
21
|
+
print("Using following parameters for running the model:")
|
|
22
|
+
max_param_len = max([len(name) for name in model_params])
|
|
23
|
+
for param_name, param_value in model_params.items():
|
|
24
|
+
print("\t{:{}} = {}".format(param_name, max_param_len, param_value))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def show_exception_errors(exception: Exception, msg: str = ""):
|
|
28
|
+
"""
|
|
29
|
+
Show Exception errors.
|
|
30
|
+
|
|
31
|
+
:param msg: Message about exception.
|
|
32
|
+
:param exception: Exception
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
if msg:
|
|
36
|
+
print(msg)
|
|
37
|
+
|
|
38
|
+
errors = exception.args[0]
|
|
39
|
+
if type(errors) is str:
|
|
40
|
+
print(errors)
|
|
41
|
+
|
|
42
|
+
if type(errors) is list:
|
|
43
|
+
for error in errors:
|
|
44
|
+
print("\t{}".format(error))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def setup_current_working_directory():
|
|
48
|
+
"""
|
|
49
|
+
Setup current working directory.
|
|
50
|
+
"""
|
|
51
|
+
if get_ipython() is None:
|
|
52
|
+
# Running in terminal
|
|
53
|
+
os.chdir(os.path.dirname(os.path.abspath(sys.argv[0])))
|
|
54
|
+
else:
|
|
55
|
+
# Running Notebook, cwd is already set
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def create_output_directory(output_dir_path: str):
|
|
60
|
+
"""
|
|
61
|
+
Create output directory.
|
|
62
|
+
Deletes existing directory.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
# If exists then delete existing directory and create new
|
|
66
|
+
if os.path.exists(output_dir_path):
|
|
67
|
+
try:
|
|
68
|
+
shutil.rmtree(output_dir_path)
|
|
69
|
+
except Exception as ex:
|
|
70
|
+
raise ex
|
|
71
|
+
|
|
72
|
+
# Create output directory and directories for all scenarios
|
|
73
|
+
os.makedirs(output_dir_path)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def setup_odym_directories():
|
|
77
|
+
"""
|
|
78
|
+
Setup ODYM directories. Appends to path to ODYM files to sys.path.
|
|
79
|
+
"""
|
|
80
|
+
sys.path.insert(0, os.path.join(
|
|
81
|
+
os.getcwd(), '.', 'lib', 'odym', 'modules'))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def setup_scenario_output_directories(output_dir_path: str, scenario_names: List[str]) -> Dict[str, str]:
|
|
85
|
+
"""
|
|
86
|
+
Setup scenario output directories. Deletes existing scenario directory if exists.
|
|
87
|
+
Returns Dictionary of scenario name to scenario directory (absolute path)
|
|
88
|
+
|
|
89
|
+
:param output_dir_name: Output directory name
|
|
90
|
+
:param scenario_names: List of scenario names
|
|
91
|
+
:return: Dictionary [str, str]: Scenario name to scenario directory (absolute path)
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
scenario_name_to_abs_scenario_output_path = {}
|
|
95
|
+
for scenario_name in scenario_names:
|
|
96
|
+
abs_scenario_output_path = os.path.join(output_dir_path, scenario_name)
|
|
97
|
+
try:
|
|
98
|
+
shutil.rmtree(abs_scenario_output_path)
|
|
99
|
+
except FileNotFoundError as ex:
|
|
100
|
+
pass
|
|
101
|
+
except Exception as ex:
|
|
102
|
+
raise ex
|
|
103
|
+
|
|
104
|
+
os.makedirs(abs_scenario_output_path)
|
|
105
|
+
scenario_name_to_abs_scenario_output_path[scenario_name] = abs_scenario_output_path
|
|
106
|
+
|
|
107
|
+
return scenario_name_to_abs_scenario_output_path
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def build_mfa_system_for_scenario(scenario: Scenario):
|
|
111
|
+
"""
|
|
112
|
+
Build MFA system for scenario.
|
|
113
|
+
|
|
114
|
+
:param scenario: Scenario-object
|
|
115
|
+
:param progress_bar: Progress bar instance (optional)
|
|
116
|
+
:return: ODYM MFASystem
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
# Track solid wood equivalent and carbon.
|
|
120
|
+
# Dictionary of classifications enters the index table defined for the system.
|
|
121
|
+
# The index table lists all aspects needed and assigns a classification and index letter to each aspect.
|
|
122
|
+
scenario_data = scenario.scenario_data
|
|
123
|
+
flow_solver = scenario.flow_solver
|
|
124
|
+
years = scenario_data.years
|
|
125
|
+
|
|
126
|
+
# Baseline value and unit name: e.g. name = "Solid wood equivalent and unit = "Mm3"
|
|
127
|
+
baseline_value_name = scenario_data.baseline_value_name
|
|
128
|
+
baseline_unit_name = scenario_data.baseline_unit_name
|
|
129
|
+
|
|
130
|
+
# Indicator names are used as Elements in ODYM MFASystem
|
|
131
|
+
indicator_names = flow_solver.get_indicator_names()
|
|
132
|
+
|
|
133
|
+
model_time_start = scenario_data.start_year
|
|
134
|
+
model_time_end = scenario_data.end_year
|
|
135
|
+
model_elements = [baseline_value_name] + indicator_names
|
|
136
|
+
model_years = years
|
|
137
|
+
|
|
138
|
+
model_classifications = {
|
|
139
|
+
'Time': msc.Classification(Name='Time', Dimension='Time', ID=1, Items=model_years),
|
|
140
|
+
'Cohort': msc.Classification(Name='Age-cohort', Dimension='Time', ID=2, Items=model_years),
|
|
141
|
+
'Element': msc.Classification(Name='Elements', Dimension='Element', ID=3, Items=model_elements),
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
index_table = pd.DataFrame({'Aspect': ['Time', 'Age-cohort', 'Element'], # 'Time' and 'Element' must be present!
|
|
145
|
+
'Description': ['Model aspect "time"', 'Model aspect "age-cohort"', 'Model aspect "Element"'],
|
|
146
|
+
# 'Time' and 'Element' are also dimensions
|
|
147
|
+
'Dimension': ['Time', 'Time', 'Element'],
|
|
148
|
+
'Classification': [model_classifications[Aspect] for Aspect in ['Time', 'Cohort', 'Element']],
|
|
149
|
+
# Unique one letter (upper or lower case) indices to be used later for calculations.
|
|
150
|
+
'IndexLetter': ['t', 'c', 'e']})
|
|
151
|
+
|
|
152
|
+
# Default indexing of IndexTable, other indices are produced on the fly
|
|
153
|
+
index_table.set_index('Aspect', inplace=True)
|
|
154
|
+
|
|
155
|
+
# ****************************
|
|
156
|
+
# Initialize ODYM MFA system *
|
|
157
|
+
# ****************************
|
|
158
|
+
flow_solver = scenario.flow_solver
|
|
159
|
+
mfa_system = msc.MFAsystem(Name='MFA System',
|
|
160
|
+
Geogr_Scope='Not defined',
|
|
161
|
+
Unit=baseline_unit_name,
|
|
162
|
+
ProcessList=[], FlowDict={}, StockDict={}, ParameterDict={},
|
|
163
|
+
Time_Start=model_time_start, Time_End=model_time_end, IndexTable=index_table,
|
|
164
|
+
Elements=index_table.loc['Element'].Classification.Items)
|
|
165
|
+
|
|
166
|
+
# Get inflow values to stock
|
|
167
|
+
year_index_to_year = dict(enumerate(model_years))
|
|
168
|
+
unique_processes = flow_solver.get_unique_processes()
|
|
169
|
+
unique_flows = flow_solver.get_unique_flows()
|
|
170
|
+
|
|
171
|
+
# Create ODYM objects
|
|
172
|
+
# print("Building ODYM processes...")
|
|
173
|
+
odym_processes = []
|
|
174
|
+
process_id_to_index = {}
|
|
175
|
+
for process_id, process in unique_processes.items():
|
|
176
|
+
process_index = len(odym_processes)
|
|
177
|
+
process_id_to_index[process_id] = process_index
|
|
178
|
+
new_process = msc.Process(ID=process_index, Name=process.name)
|
|
179
|
+
odym_processes.append(new_process)
|
|
180
|
+
|
|
181
|
+
# print("Building ODYM flows...")
|
|
182
|
+
odym_flows = {}
|
|
183
|
+
for flow_id, flow in unique_flows.items():
|
|
184
|
+
source_process_index = process_id_to_index[flow.source_process_id]
|
|
185
|
+
target_process_index = process_id_to_index[flow.target_process_id]
|
|
186
|
+
new_flow = msc.Flow(ID=flow.id, P_Start=source_process_index,
|
|
187
|
+
P_End=target_process_index, Indices='t,e', Values=None)
|
|
188
|
+
odym_flows[flow.id] = new_flow
|
|
189
|
+
|
|
190
|
+
# print("Building ODYM stocks...")
|
|
191
|
+
odym_stocks = {}
|
|
192
|
+
for stock in flow_solver.get_all_stocks():
|
|
193
|
+
process_index = process_id_to_index[stock.id]
|
|
194
|
+
new_stock = msc.Stock(ID=stock.id, Name=stock.name,
|
|
195
|
+
P_Res=process_index, Indices='t,e', Type=0, Values=None)
|
|
196
|
+
odym_stocks[stock.id] = new_stock
|
|
197
|
+
|
|
198
|
+
mfa_system.ProcessList = odym_processes
|
|
199
|
+
mfa_system.FlowDict = odym_flows
|
|
200
|
+
mfa_system.StockDict = odym_stocks
|
|
201
|
+
mfa_system.Initialize_FlowValues()
|
|
202
|
+
mfa_system.Initialize_StockValues()
|
|
203
|
+
mfa_system.Consistency_Check()
|
|
204
|
+
|
|
205
|
+
# Update ODYM flow values from flow values DataFrame
|
|
206
|
+
for flow_id, flow in mfa_system.FlowDict.items():
|
|
207
|
+
for year_index, value in enumerate(flow.Values):
|
|
208
|
+
# Skip to next year if FlowSolver does not have data for this year
|
|
209
|
+
# This is possible because ODYM flow and stock values are already initialized to 0.0
|
|
210
|
+
flow_has_data_for_year = flow_solver.has_flow(
|
|
211
|
+
year=year_index_to_year[year_index], flow_id=flow_id)
|
|
212
|
+
if not flow_has_data_for_year:
|
|
213
|
+
continue
|
|
214
|
+
|
|
215
|
+
# NOTE: Virtual flows use default value defined in Flow for carbon content (now 1.0).
|
|
216
|
+
# Get all evaluated values and set those to ODYM flow
|
|
217
|
+
solved_flow = flow_solver.get_flow(
|
|
218
|
+
year=year_index_to_year[year_index], flow_id=flow_id)
|
|
219
|
+
|
|
220
|
+
# If flow is not present this year, use 0.0 value
|
|
221
|
+
if not isinstance(solved_flow, Flow):
|
|
222
|
+
for index, indicator_name in enumerate(indicator_names):
|
|
223
|
+
flow.Values[year_index, index] = 0.0
|
|
224
|
+
continue
|
|
225
|
+
|
|
226
|
+
for index, evaluated_value in enumerate(solved_flow.get_all_evaluated_values()):
|
|
227
|
+
flow.Values[year_index, index] = evaluated_value
|
|
228
|
+
|
|
229
|
+
# Process stocks (fill with data)
|
|
230
|
+
for stock_id, stock in odym_stocks.items():
|
|
231
|
+
# Baseline DSM
|
|
232
|
+
# These are needed for triggering DSM calculations
|
|
233
|
+
baseline_dsm = flow_solver.get_baseline_dynamic_stocks()[stock_id]
|
|
234
|
+
baseline_stock_by_cohort = baseline_dsm.compute_s_c_inflow_driven()
|
|
235
|
+
baseline_outflow_by_cohort = baseline_dsm.compute_o_c_from_s_c()
|
|
236
|
+
baseline_stock_total = baseline_dsm.compute_stock_total()
|
|
237
|
+
baseline_stock_change = baseline_dsm.compute_stock_change()
|
|
238
|
+
stock.Values[:, 0] = baseline_stock_change
|
|
239
|
+
|
|
240
|
+
# Indicator DSMs
|
|
241
|
+
for indicator_index, indicator_name in enumerate(indicator_names):
|
|
242
|
+
# These are needed for triggering DSM calculations
|
|
243
|
+
indicator_dsm = flow_solver.get_indicator_dynamic_stocks()[
|
|
244
|
+
stock_id][indicator_name]
|
|
245
|
+
indicator_stock_by_cohort = indicator_dsm.compute_s_c_inflow_driven()
|
|
246
|
+
indicator_outflow_by_cohort = indicator_dsm.compute_o_c_from_s_c()
|
|
247
|
+
indicator_stock_total = indicator_dsm.compute_stock_total()
|
|
248
|
+
indicator_stock_change = indicator_dsm.compute_stock_change()
|
|
249
|
+
|
|
250
|
+
# Baseline values are at index 0 so offset by +1
|
|
251
|
+
stock.Values[:, indicator_index + 1] = indicator_stock_change
|
|
252
|
+
|
|
253
|
+
return mfa_system
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def calculate_scenario_mass_balance(mfa_system: msc.MFAsystem) -> pd.DataFrame:
|
|
257
|
+
"""
|
|
258
|
+
Get scenario mass balance difference per year.
|
|
259
|
+
|
|
260
|
+
:param mfa_system: MFASystem-object
|
|
261
|
+
:param model_years: List of years
|
|
262
|
+
:return: DataFrame
|
|
263
|
+
"""
|
|
264
|
+
|
|
265
|
+
mb = mfa_system.MassBalance()
|
|
266
|
+
# print("Mass balance result shape: {}".format(mb.shape))
|
|
267
|
+
df_mass_balance = pd.DataFrame(
|
|
268
|
+
columns=["Year", "Process 0", "Rest", "Difference"])
|
|
269
|
+
for year_index, year in enumerate(mfa_system.Time_L):
|
|
270
|
+
# Calculate mass balance using the first element in MFA system (= base element)
|
|
271
|
+
# Negative value in process 0 means that process 0 has no inflows so this mass
|
|
272
|
+
# is coming from outside system boundaries
|
|
273
|
+
p0 = np.sum(mb[year_index][0][0])
|
|
274
|
+
rest = np.sum(mb[year_index][1:, 0])
|
|
275
|
+
abs_diff = abs(p0) - abs(rest)
|
|
276
|
+
df_mass_balance.loc[year_index] = np.array([year, p0, rest, abs_diff])
|
|
277
|
+
df_mass_balance = df_mass_balance.astype({"Year": "int32"})
|
|
278
|
+
# df_mass_balance.set_index(["Year"], inplace=True)
|
|
279
|
+
return df_mass_balance
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def shorten_sheet_name(name, max_length=31):
|
|
283
|
+
"""Shorten and sanitize Excel sheet names to comply with the 31-character limit."""
|
|
284
|
+
# Sanitize name
|
|
285
|
+
sanitized_name = re.sub(r'[:/\\]', '_', name)
|
|
286
|
+
|
|
287
|
+
# Split by underscores to maintain structure
|
|
288
|
+
parts = sanitized_name.split('_')
|
|
289
|
+
|
|
290
|
+
# Identify fixed parts: these are short words (<= 3 chars) or common connectors
|
|
291
|
+
fixed_parts = {i: p for i, p in enumerate(
|
|
292
|
+
parts) if len(p) <= 3 or not p.isalnum()}
|
|
293
|
+
variable_parts = [p for i, p in enumerate(parts) if i not in fixed_parts]
|
|
294
|
+
|
|
295
|
+
# Determine available length for variable parts (accounting for underscores and fixed parts)
|
|
296
|
+
num_underscores = len(parts) - 1
|
|
297
|
+
fixed_length = sum(len(p) for p in fixed_parts.values())
|
|
298
|
+
available_length = max_length - num_underscores - fixed_length
|
|
299
|
+
|
|
300
|
+
# Reduce variable parts proportionally
|
|
301
|
+
total_variable_length = sum(len(p) for p in variable_parts)
|
|
302
|
+
if total_variable_length <= available_length:
|
|
303
|
+
return sanitized_name[:max_length] # Trim if needed
|
|
304
|
+
|
|
305
|
+
reduced_parts = []
|
|
306
|
+
for part in variable_parts:
|
|
307
|
+
reduced_length = max(
|
|
308
|
+
1, len(part) * available_length // total_variable_length)
|
|
309
|
+
reduced_parts.append(part[:reduced_length])
|
|
310
|
+
|
|
311
|
+
# Reconstruct the name, preserving fixed parts
|
|
312
|
+
final_parts = []
|
|
313
|
+
variable_index = 0
|
|
314
|
+
for i in range(len(parts)):
|
|
315
|
+
if i in fixed_parts:
|
|
316
|
+
final_parts.append(fixed_parts[i])
|
|
317
|
+
else:
|
|
318
|
+
final_parts.append(reduced_parts[variable_index])
|
|
319
|
+
variable_index += 1
|
|
320
|
+
|
|
321
|
+
# Ensure final length
|
|
322
|
+
shortened_name = "_".join(final_parts)
|
|
323
|
+
return shortened_name[:max_length]
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def get_output_directory() -> str:
|
|
327
|
+
"""
|
|
328
|
+
Get output path directory.
|
|
329
|
+
NOTE: Must be using the set_output_directory(), otherwise raises Exception
|
|
330
|
+
"""
|
|
331
|
+
|
|
332
|
+
if global_path_to_output_dir is None:
|
|
333
|
+
raise Exception("Output path not set")
|
|
334
|
+
|
|
335
|
+
return global_path_to_output_dir
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def set_output_directory(path_to_output_dir: str) -> None:
|
|
339
|
+
"""
|
|
340
|
+
Set output directory for results.
|
|
341
|
+
Path is converted to absolute path (e.g. .., . and ~ are converted to
|
|
342
|
+
actual directory names).
|
|
343
|
+
|
|
344
|
+
:param path_to_output_dir: Path to directory
|
|
345
|
+
|
|
346
|
+
"""
|
|
347
|
+
# NOTE: os.path.realpath follows symlinks where os.path.abspath does not
|
|
348
|
+
# NOTE: ~ is not expanded to user home directory in Linux
|
|
349
|
+
global_path_to_output_dir = os.path.realpath(path_to_output_dir)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def make_output_directory(path_to_output: str = "", remove_existing: bool = True) -> None:
|
|
353
|
+
"""
|
|
354
|
+
Make output directory.
|
|
355
|
+
If path to output directory is not set then creates directory "output"
|
|
356
|
+
by default to the directory of the running script.
|
|
357
|
+
|
|
358
|
+
:param remove_existing: If True then removes existing directory.
|
|
359
|
+
"""
|
|
360
|
+
# TODO: Implement creating output directory
|
|
361
|
+
|
|
362
|
+
os.makedirs(exist_ok=True)
|
|
Binary file
|