1 | # -*- coding: utf-8 - |
||
2 | |||
3 | """Tests the processing module of solph. |
||
4 | |||
5 | This file is part of project oemof (github.com/oemof/oemof). It's copyrighted |
||
6 | by the contributors recorded in the version control history of the file, |
||
7 | available from its original location oemof/tests/test_processing.py |
||
8 | |||
9 | SPDX-License-Identifier: MIT |
||
10 | """ |
||
11 | |||
12 | import pandas |
||
13 | import pytest |
||
14 | from pandas.testing import assert_frame_equal |
||
15 | from pandas.testing import assert_series_equal |
||
16 | |||
17 | from oemof.solph import EnergySystem |
||
18 | from oemof.solph import Investment |
||
19 | from oemof.solph import Model |
||
20 | from oemof.solph import processing |
||
21 | from oemof.solph import views |
||
22 | from oemof.solph.buses import Bus |
||
23 | from oemof.solph.components import Converter |
||
24 | from oemof.solph.components import GenericStorage |
||
25 | from oemof.solph.components import Sink |
||
26 | from oemof.solph.flows import Flow |
||
27 | |||
28 | |||
29 | class TestParameterResult: |
||
30 | @classmethod |
||
31 | def setup_class(cls): |
||
32 | cls.period = 24 |
||
33 | cls.es = EnergySystem( |
||
34 | timeindex=pandas.date_range( |
||
35 | "2016-01-01", |
||
36 | periods=cls.period, |
||
37 | freq="h", |
||
38 | ), |
||
39 | infer_last_interval=True, |
||
40 | ) |
||
41 | |||
42 | # BUSSES |
||
43 | b_el1 = Bus(label="b_el1") |
||
44 | b_el2 = Bus(label="b_el2") |
||
45 | b_diesel = Bus(label="b_diesel", balanced=False) |
||
46 | cls.es.add(b_el1, b_el2, b_diesel) |
||
47 | |||
48 | # TEST DIESEL: |
||
49 | dg = Converter( |
||
50 | label="diesel", |
||
51 | inputs={b_diesel: Flow(variable_costs=2)}, |
||
52 | outputs={ |
||
53 | b_el1: Flow( |
||
54 | variable_costs=1, nominal_capacity=Investment(ep_costs=0.5) |
||
55 | ) |
||
56 | }, |
||
57 | conversion_factors={b_el1: 2}, |
||
58 | ) |
||
59 | |||
60 | batt = GenericStorage( |
||
61 | label="storage", |
||
62 | inputs={b_el1: Flow(variable_costs=3)}, |
||
63 | outputs={b_el2: Flow(variable_costs=2.5)}, |
||
64 | loss_rate=0.00, |
||
65 | initial_storage_level=0, |
||
66 | invest_relation_input_capacity=1 / 6, |
||
67 | invest_relation_output_capacity=1 / 6, |
||
68 | inflow_conversion_factor=1, |
||
69 | outflow_conversion_factor=0.8, |
||
70 | nominal_capacity=Investment(ep_costs=0.4), |
||
71 | ) |
||
72 | |||
73 | cls.demand_values = [0.0] + [100] * 23 |
||
74 | demand = Sink( |
||
75 | label="demand_el", |
||
76 | inputs={ |
||
77 | b_el2: Flow( |
||
78 | nominal_capacity=1, |
||
79 | fix=cls.demand_values, |
||
80 | ) |
||
81 | }, |
||
82 | ) |
||
83 | cls.es.add(dg, batt, demand) |
||
84 | cls.om = Model(cls.es) |
||
85 | cls.om.receive_duals() |
||
86 | cls.om.solve() |
||
87 | cls.mod = Model(cls.es) |
||
88 | cls.mod.solve() |
||
89 | |||
90 | def test_flows_with_none_exclusion(self): |
||
91 | b_el2 = self.es.groups["b_el2"] |
||
92 | demand = self.es.groups["demand_el"] |
||
93 | param_results = processing.parameter_as_dict( |
||
94 | self.es, exclude_none=True |
||
95 | ) |
||
96 | assert_series_equal( |
||
97 | param_results[(b_el2, demand)]["scalars"].sort_index(), |
||
98 | pandas.Series( |
||
99 | { |
||
100 | "bidirectional": False, |
||
101 | "integer": False, |
||
102 | "nominal_capacity": 1, |
||
103 | "max": 1, |
||
104 | "min": 0, |
||
105 | "variable_costs": 0, |
||
106 | "label": str(b_el2.outputs[demand].label), |
||
107 | } |
||
108 | ).sort_index(), |
||
109 | ) |
||
110 | assert_frame_equal( |
||
111 | param_results[(b_el2, demand)]["sequences"], |
||
112 | pandas.DataFrame({"fix": self.demand_values}), |
||
113 | check_like=True, |
||
114 | ) |
||
115 | |||
116 | def test_flows_without_none_exclusion(self): |
||
117 | b_el2 = self.es.groups["b_el2"] |
||
118 | demand = self.es.groups["demand_el"] |
||
119 | param_results = processing.parameter_as_dict( |
||
120 | self.es, exclude_none=False |
||
121 | ) |
||
122 | default_attributes = { |
||
123 | "age": None, |
||
124 | "lifetime": None, |
||
125 | "integer": False, |
||
126 | "investment": None, |
||
127 | "nominal_capacity": 1, |
||
128 | "nonconvex": None, |
||
129 | "bidirectional": False, |
||
130 | "full_load_time_max": None, |
||
131 | "full_load_time_min": None, |
||
132 | "max": 1, |
||
133 | "min": 0, |
||
134 | "negative_gradient_limit": None, |
||
135 | "positive_gradient_limit": None, |
||
136 | "variable_costs": 0, |
||
137 | "fixed_costs": None, |
||
138 | "flow": None, |
||
139 | "values": None, |
||
140 | "label": str(b_el2.outputs[demand].label), |
||
141 | } |
||
142 | assert_series_equal( |
||
143 | param_results[(b_el2, demand)]["scalars"].sort_index(), |
||
144 | pandas.Series(default_attributes).sort_index(), |
||
145 | ) |
||
146 | sequences_attributes = { |
||
147 | "fix": self.demand_values, |
||
148 | } |
||
149 | |||
150 | assert_frame_equal( |
||
151 | param_results[(b_el2, demand)]["sequences"], |
||
152 | pandas.DataFrame(sequences_attributes), |
||
153 | check_like=True, |
||
154 | ) |
||
155 | |||
156 | View Code Duplication | def test_nodes_with_none_exclusion(self): |
|
0 ignored issues
–
show
Duplication
introduced
by
![]() |
|||
157 | param_results = processing.parameter_as_dict( |
||
158 | self.es, exclude_none=True |
||
159 | ) |
||
160 | param_results = processing.convert_keys_to_strings(param_results) |
||
161 | assert_series_equal( |
||
162 | param_results[("storage", "None")]["scalars"], |
||
163 | pandas.Series( |
||
164 | { |
||
165 | "balanced": True, |
||
166 | "initial_storage_level": 0, |
||
167 | "investment_age": 0, |
||
168 | "investment_existing": 0, |
||
169 | "investment_nonconvex": False, |
||
170 | "investment_ep_costs": 0.4, |
||
171 | "investment_maximum": float("inf"), |
||
172 | "investment_minimum": 0, |
||
173 | "investment_nonconvex": False, |
||
174 | "investment_offset": 0, |
||
175 | "label": "storage", |
||
176 | "fixed_costs": 0, |
||
177 | "fixed_losses_absolute": 0, |
||
178 | "fixed_losses_relative": 0, |
||
179 | "inflow_conversion_factor": 1, |
||
180 | "invest_relation_input_capacity": 1 / 6, |
||
181 | "invest_relation_output_capacity": 1 / 6, |
||
182 | "loss_rate": 0, |
||
183 | "max_storage_level": 1, |
||
184 | "min_storage_level": 0, |
||
185 | "outflow_conversion_factor": 0.8, |
||
186 | } |
||
187 | ), |
||
188 | ) |
||
189 | assert_frame_equal( |
||
190 | param_results[("storage", "None")]["sequences"], pandas.DataFrame() |
||
191 | ) |
||
192 | |||
193 | View Code Duplication | def test_nodes_with_none_exclusion_old_name(self): |
|
0 ignored issues
–
show
|
|||
194 | param_results = processing.parameter_as_dict( |
||
195 | self.es, exclude_none=True |
||
196 | ) |
||
197 | param_results = processing.convert_keys_to_strings( |
||
198 | param_results, keep_none_type=True |
||
199 | ) |
||
200 | assert_series_equal( |
||
201 | param_results[("storage", None)]["scalars"], |
||
202 | pandas.Series( |
||
203 | { |
||
204 | "balanced": True, |
||
205 | "initial_storage_level": 0, |
||
206 | "investment_age": 0, |
||
207 | "investment_existing": 0, |
||
208 | "investment_nonconvex": False, |
||
209 | "investment_ep_costs": 0.4, |
||
210 | "investment_maximum": float("inf"), |
||
211 | "investment_minimum": 0, |
||
212 | "investment_nonconvex": False, |
||
213 | "investment_offset": 0, |
||
214 | "label": "storage", |
||
215 | "fixed_costs": 0, |
||
216 | "fixed_losses_absolute": 0, |
||
217 | "fixed_losses_relative": 0, |
||
218 | "inflow_conversion_factor": 1, |
||
219 | "invest_relation_input_capacity": 1 / 6, |
||
220 | "invest_relation_output_capacity": 1 / 6, |
||
221 | "loss_rate": 0, |
||
222 | "max_storage_level": 1, |
||
223 | "min_storage_level": 0, |
||
224 | "outflow_conversion_factor": 0.8, |
||
225 | } |
||
226 | ), |
||
227 | ) |
||
228 | assert_frame_equal( |
||
229 | param_results[("storage", None)]["sequences"], pandas.DataFrame() |
||
230 | ) |
||
231 | |||
232 | def test_nodes_without_none_exclusion(self): |
||
233 | diesel = self.es.groups["diesel"] |
||
234 | param_results = processing.parameter_as_dict( |
||
235 | self.es, exclude_none=False |
||
236 | ) |
||
237 | assert_series_equal( |
||
238 | param_results[(diesel, None)]["scalars"], |
||
239 | pandas.Series( |
||
240 | { |
||
241 | "label": "diesel", |
||
242 | "conversion_factors_b_el1": 2, |
||
243 | "conversion_factors_b_diesel": 1, |
||
244 | } |
||
245 | ), |
||
246 | ) |
||
247 | assert_frame_equal( |
||
248 | param_results[(diesel, None)]["sequences"], pandas.DataFrame() |
||
249 | ) |
||
250 | |||
251 | def test_nodes_with_excluded_attrs(self): |
||
252 | diesel = self.es.groups["diesel"] |
||
253 | param_results = processing.parameter_as_dict( |
||
254 | self.es, exclude_attrs=["conversion_factors"] |
||
255 | ) |
||
256 | assert_series_equal( |
||
257 | param_results[(diesel, None)]["scalars"], |
||
258 | pandas.Series( |
||
259 | { |
||
260 | "label": "diesel", |
||
261 | } |
||
262 | ), |
||
263 | ) |
||
264 | assert_frame_equal( |
||
265 | param_results[(diesel, None)]["sequences"], pandas.DataFrame() |
||
266 | ) |
||
267 | |||
268 | def test_parameter_with_node_view(self): |
||
269 | param_results = processing.parameter_as_dict( |
||
270 | self.es, exclude_none=True |
||
271 | ) |
||
272 | bel1 = views.node(param_results, "b_el1") |
||
273 | assert ( |
||
274 | bel1["scalars"][[(("b_el1", "storage"), "variable_costs")]].values |
||
275 | == 3 |
||
276 | ) |
||
277 | |||
278 | bel1_m = views.node(param_results, "b_el1", multiindex=True) |
||
279 | assert bel1_m["scalars"][("b_el1", "storage", "variable_costs")] == 3 |
||
280 | |||
281 | def test_multiindex_sequences(self): |
||
282 | results = processing.results(self.om) |
||
283 | bel1 = views.node(results, "b_el1", multiindex=True) |
||
284 | assert ( |
||
285 | int(bel1["sequences"][("diesel", "b_el1", "flow")].sum()) == 2875 |
||
286 | ) |
||
287 | |||
288 | def test_error_from_nan_values(self): |
||
289 | trsf = self.es.groups["diesel"] |
||
290 | bus = self.es.groups["b_el1"] |
||
291 | self.mod.flow[trsf, bus, 5] = float("nan") |
||
292 | with pytest.raises(ValueError): |
||
293 | processing.results(self.mod) |
||
294 | |||
295 | def test_duals(self): |
||
296 | results = processing.results(self.om) |
||
297 | bel = views.node(results, "b_el1", multiindex=True) |
||
298 | assert int(bel["sequences"]["b_el1", "None", "duals"].sum()) == 48 |
||
299 | |||
300 | def test_node_weight_by_type(self): |
||
301 | results = processing.results(self.om) |
||
302 | storage_content = views.node_weight_by_type( |
||
303 | results, node_type=GenericStorage |
||
304 | ) |
||
305 | assert ( |
||
306 | storage_content.sum().iloc[0] == pytest.approx(1437.5, abs=0.1) |
||
307 | ).all() |
||
308 | |||
309 | def test_output_by_type_view(self): |
||
310 | results = processing.results(self.om) |
||
311 | converter_output = views.node_output_by_type( |
||
312 | results, node_type=Converter |
||
313 | ) |
||
314 | compare = views.node(results, "diesel", multiindex=True)["sequences"][ |
||
315 | ("diesel", "b_el1", "flow") |
||
316 | ] |
||
317 | assert converter_output.sum().iloc[0] == pytest.approx(compare.sum()) |
||
318 | |||
319 | def test_input_by_type_view(self): |
||
320 | results = processing.results(self.om) |
||
321 | sink_input = views.node_input_by_type(results, node_type=Sink) |
||
322 | compare = views.node(results, "demand_el", multiindex=True) |
||
323 | assert sink_input.sum().iloc[0] == pytest.approx( |
||
324 | compare["sequences"][("b_el2", "demand_el", "flow")].sum() |
||
325 | ) |
||
326 | |||
327 | def test_net_storage_flow(self): |
||
328 | results = processing.results(self.om) |
||
329 | storage_flow = views.net_storage_flow( |
||
330 | results, node_type=GenericStorage |
||
331 | ) |
||
332 | |||
333 | compare = views.node(results, "storage", multiindex=True)["sequences"] |
||
334 | |||
335 | assert ( |
||
336 | ( |
||
337 | ( |
||
338 | compare[("storage", "b_el2", "flow")] |
||
339 | - compare[("b_el1", "storage", "flow")] |
||
340 | ) |
||
341 | .to_frame() |
||
342 | .fillna(0) |
||
343 | == storage_flow.values |
||
344 | ) |
||
345 | .all() |
||
346 | .iloc[0] |
||
347 | ) |
||
348 | |||
349 | def test_output_by_type_view_empty(self): |
||
350 | results = processing.results(self.om) |
||
351 | view = views.node_output_by_type(results, node_type=Flow) |
||
352 | assert view is None |
||
353 | |||
354 | def test_input_by_type_view_empty(self): |
||
355 | results = processing.results(self.om) |
||
356 | view = views.node_input_by_type(results, node_type=Flow) |
||
357 | assert view is None |
||
358 | |||
359 | def test_net_storage_flow_empty(self): |
||
360 | results = processing.results(self.om) |
||
361 | view = views.net_storage_flow(results, node_type=Sink) |
||
362 | assert view is None |
||
363 | view2 = views.net_storage_flow(results, node_type=Flow) |
||
364 | assert view2 is None |
||
365 | |||
366 | def test_node_weight_by_type_empty(self): |
||
367 | results = processing.results(self.om) |
||
368 | view = views.node_weight_by_type(results, node_type=Flow) |
||
369 | assert view is None |
||
370 |