• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rl-institut / multi-vector-simulator / 8870538658

28 Apr 2024 09:31PM UTC coverage: 75.582% (-1.4%) from 76.96%
8870538658

push

github

web-flow
Merge pull request #971 from rl-institut/fix/black-vulnerability

Fix/black vulnerability

26 of 29 new or added lines in 15 files covered. (89.66%)

826 existing lines in 21 files now uncovered.

5977 of 7908 relevant lines covered (75.58%)

0.76 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.67
/src/multi_vector_simulator/D2_model_constraints.py
1
"""
2
Module D2 - Model constraints
3
=============================
4

5
This module gathers all constraints that can be added to the MVS optimisation. 
6
we will probably require another input CSV file or further parameters in simulation_settings.
7

8
Future constraints are discussed in issue #133 (https://github.com/rl-institut/multi-vector-simulator/issues/133)
9

10
constraints should be tested in-code (examples) and by comparing the lp file generated.
11
"""
12

13
import logging
1✔
14
import pyomo.environ as po
1✔
15
from oemof.solph import constraints
1✔
16

17
from multi_vector_simulator.utils.constants import DEFAULT_WEIGHTS_ENERGY_CARRIERS
1✔
18

19
from multi_vector_simulator.utils.constants_json_strings import (
1✔
20
    OEMOF_SOURCE,
21
    OEMOF_SINK,
22
    OEMOF_BUSSES,
23
    ENERGY_PRODUCTION,
24
    ENERGY_PROVIDERS,
25
    ENERGY_CONSUMPTION,
26
    ENERGY_VECTOR,
27
    VALUE,
28
    LABEL,
29
    INFLOW_DIRECTION,
30
    OUTFLOW_DIRECTION,
31
    DSO_CONSUMPTION,
32
    RENEWABLE_SHARE_DSO,
33
    RENEWABLE_ASSET_BOOL,
34
    EXCESS_SINK,
35
    CONSTRAINTS,
36
    MINIMAL_RENEWABLE_FACTOR,
37
    MAXIMUM_EMISSIONS,
38
    MINIMAL_DEGREE_OF_AUTONOMY,
39
    DSO_FEEDIN,
40
    NET_ZERO_ENERGY,
41
)
42

43
# Keys for dicts renewable_assets and non_renewable_assets
44
OEMOF_SOLPH_OBJECT_ASSET = "object"
1✔
45
WEIGHTING_FACTOR_ENERGY_CARRIER = "weighting_factor_energy_carrier"
1✔
46
RENEWABLE_SHARE_ASSET_FLOW = "renewable_share_asset_flow"
1✔
47
OEMOF_SOLPH_OBJECT_BUS = "oemof_solph_object_bus"
1✔
48

49

50
def add_constraints(local_energy_system, dict_values, dict_model):
1✔
51
    r"""
52
    Adds all constraints activated in constraints.csv to the energy system model.
53

54
    Possible constraints:
55
    - Minimal renewable factor constraint
56
    Parameters
57
    ----------
58
    local_energy_system:  :oemof-solph: <oemof.solph.model>
59
        Energy system model generated from oemof-solph for the energy system scenario, including all energy system assets.
60

61
    dict_values: dict
62
        All simulation parameters
63

64
    dict_model: dict of :oemof-solph: <oemof.solph.assets>
65
        Dictionary including the oemof-solph component assets, which need to be connected with constraints
66

67
    Returns
68
    -------
69
    local_energy_system: :oemof-solph: <oemof.solph.model>
70
        Updated object local_energy_system with the additional constraints and bounds.
71

72
    Notes
73
    -----
74
    The constraints can be validated by evaluating the LP file.
75
    Additionally, there are validation tests in `E4_verification_of_constraints`.
76

77
    Tested with:
78
    - D2.test_add_constraints_maximum_emissions()
79
    - D2.test_add_constraints_maximum_emissions_None()
80
    - D2.test_add_constraints_minimal_renewable_share()
81
    - D2.test_add_constraints_minimal_renewable_share_is_0()
82
    - D2.test_add_constraints_net_zero_energy_requirement_is_true()
83
    - D2.test_add_constraints_net_zero_energy_requirement_is_false()
84
    """
85

86
    constraint_functions = {
1✔
87
        MINIMAL_RENEWABLE_FACTOR: constraint_minimal_renewable_share,
88
        MAXIMUM_EMISSIONS: constraint_maximum_emissions,
89
        MINIMAL_DEGREE_OF_AUTONOMY: constraint_minimal_degree_of_autonomy,
90
        NET_ZERO_ENERGY: constraint_net_zero_energy,
91
    }
92

93
    count_added_constraints = 0
1✔
94

95
    for constraint in dict_values[CONSTRAINTS]:
1✔
96
        # if the constraint is not within its proper range of admissible values, None is returned
97
        les = constraint_functions[constraint](
1✔
98
            local_energy_system, dict_values, dict_model
99
        )
100
        # if the contraint is not applied (because not defined by the user, or with a value which
101
        # is not in the acceptable range the constraint function will return None
102
        if les is not None:
1✔
103
            local_energy_system = les
1✔
104
            count_added_constraints += 1
1✔
105

106
    if count_added_constraints == 0:
1✔
107
        logging.info("No modelling constraint to be introduced.")
1✔
108
    else:
109
        logging.debug(f"Number of added constraints: {count_added_constraints}")
1✔
110

111
    return local_energy_system
1✔
112

113

114
def constraint_maximum_emissions(model, dict_values, dict_model=None):
1✔
115
    r"""
116
    Resulting in an energy system adhering to a maximum amount of emissions.
117

118
    Parameters
119
    ----------
120
    model: :oemof-solph: <oemof.solph.model>
121
        Model to which constraint is added.
122

123
    dict_values: dict
124
        All simulation parameters
125

126
    dict_model: None
127
        To match other constraint function's format
128

129
    Notes
130
    -----
131
    Tested with:
132
    - D2.test_constraint_maximum_emissions()
133

134
    """
135
    maximum_emissions = dict_values[CONSTRAINTS][MAXIMUM_EMISSIONS][VALUE]
1✔
136
    if maximum_emissions is not None:
1✔
137
        # Updates the model with the constraint for maximum amount of emissions
138
        constraints.emission_limit(model, limit=maximum_emissions)
1✔
139
        logging.info("Added maximum emission constraint.")
1✔
140
        answer = model
1✔
141
    else:
142
        answer = None
1✔
143

144
    return answer
1✔
145

146

147
def constraint_minimal_renewable_share(model, dict_values, dict_model):
1✔
148
    r"""
149
    Resulting in an energy system adhering to a minimal renewable factor.
150

151
    Please be aware that the renewable factor that has to adhere to the minimal renewable factor is not the one of one specific sector,
152
    but of the overall energy system. This means that eg. 1 kg H2 is produced renewably, it goes into account with a heavier
153
    weighting factor then one renewably produced electricity unit.
154

155
    Parameters
156
    ----------
157
    model: :oemof-solph: <oemof.solph.model>
158
        Model to which constraint is added.
159

160
    dict_values: dict
161
        All simulation parameters
162

163
    dict_model: dict of :oemof-solph: <oemof.solph.assets>
164
        Dictionary including the oemof-solph component assets, which need to be connected with constraints
165

166
    Notes
167
    -----
168
    The renewable factor of the whole energy system has to adhere for following constraint:
169

170
    .. math::
171
        minimal renewable factor <= \frac{\sum renewable generation \cdot weighting factor}{\sum renewable generation \cdot weighting factor + \sum non-renewable generation \cdot weighting factor}
172

173
    Tested with:
174
    - Test_Constraints.test_benchmark_minimal_renewable_share_constraint()
175
    """
176

177
    if dict_values[CONSTRAINTS][MINIMAL_RENEWABLE_FACTOR][VALUE] > 0:
1✔
178

179
        (
1✔
180
            renewable_assets,
181
            non_renewable_assets,
182
        ) = prepare_constraint_minimal_renewable_share(
183
            dict_values,
184
            dict_model,
185
        )
186

187
        def renewable_share_rule(model):
1✔
188
            renewable_generation = 0
1✔
189
            total_generation = 0
1✔
190

191
            # Get the flows from all renewable assets
192
            for asset in renewable_assets:
1✔
193
                generation = (
1✔
194
                    sum(
195
                        model.flow[
196
                            renewable_assets[asset][OEMOF_SOLPH_OBJECT_ASSET],
197
                            renewable_assets[asset][OEMOF_SOLPH_OBJECT_BUS],
198
                            :,
199
                            :,
200
                        ]
201
                    )
202
                    * renewable_assets[asset][WEIGHTING_FACTOR_ENERGY_CARRIER]
203
                    * renewable_assets[asset][RENEWABLE_SHARE_ASSET_FLOW]
204
                )
205
                total_generation += generation
1✔
206
                renewable_generation += generation
1✔
207

208
            # Get the flows from all non renewable assets
209
            for asset in non_renewable_assets:
1✔
210
                generation = (
1✔
211
                    sum(
212
                        model.flow[
213
                            non_renewable_assets[asset][OEMOF_SOLPH_OBJECT_ASSET],
214
                            non_renewable_assets[asset][OEMOF_SOLPH_OBJECT_BUS],
215
                            :,
216
                            :,
217
                        ]
218
                    )
219
                    * non_renewable_assets[asset][WEIGHTING_FACTOR_ENERGY_CARRIER]
220
                    * (1 - non_renewable_assets[asset][RENEWABLE_SHARE_ASSET_FLOW])
221
                )
222
                total_generation += generation
1✔
223

224
            expr = (
1✔
225
                renewable_generation
226
                - (dict_values[CONSTRAINTS][MINIMAL_RENEWABLE_FACTOR][VALUE])
227
                * total_generation
228
            )
229
            return expr >= 0
1✔
230

231
        model.constraint_minimal_renewable_share = po.Constraint(
1✔
232
            rule=renewable_share_rule
233
        )
234

235
        logging.info("Added minimal renewable factor constraint.")
1✔
236
        answer = model
1✔
237
    else:
238
        answer = None
1✔
239

240
    return answer
1✔
241

242

243
def prepare_constraint_minimal_renewable_share(
1✔
244
    dict_values,
245
    dict_model,
246
):
247
    r"""
248
    Prepare creating the minimal renewable factor constraint by processing dict_values
249

250
    Parameters
251
    ----------
252
    dict_values: dict
253
        All simulation parameters
254

255
    dict_model: dict of :oemof-solph: <oemof.solph.assets>
256
        Dictionary including the oemof-solph component assets, which need to be connected with constraints
257

258
    Returns
259
    -------
260
    renewable_assets: dict
261
        Dictionary of all assets with renewable generation in the energy system.
262
        Defined by: oemof_solph_object_asset, weighting_factor_energy_carrier, renewable_share_asset_flow, oemof_solph_object_bus
263

264
    non_renewable_assets: dict
265
        Dictionary of all assets with renewable generation in the energy system.
266
        Defined by: oemof_solph_object_asset, weighting_factor_energy_carrier, renewable_share_asset_flow, oemof_solph_object_bus
267

268
    """
269
    # dicts for processing
270
    renewable_assets = {}
1✔
271
    non_renewable_assets = {}
1✔
272

273
    # Determine which energy sources are of renewable origin and which are fossil-fuelled.
274
    # DSO sources are added separately (as they do not have parameter "RENEWABLE_ASSET_BOOL".
275
    assets_without_renewable_asset_bool = []
1✔
276
    for asset in dict_values[ENERGY_PRODUCTION]:
1✔
277
        if RENEWABLE_ASSET_BOOL in dict_values[ENERGY_PRODUCTION][asset]:
1✔
278
            if (
1✔
279
                dict_values[ENERGY_PRODUCTION][asset][RENEWABLE_ASSET_BOOL][VALUE]
280
                is True
281
            ):
282
                renewable_assets.update(
1✔
283
                    {
284
                        asset: {
285
                            OEMOF_SOLPH_OBJECT_ASSET: dict_model[OEMOF_SOURCE][
286
                                dict_values[ENERGY_PRODUCTION][asset][LABEL]
287
                            ],
288
                            OEMOF_SOLPH_OBJECT_BUS: dict_model[OEMOF_BUSSES][
289
                                dict_values[ENERGY_PRODUCTION][asset][OUTFLOW_DIRECTION]
290
                            ],
291
                            WEIGHTING_FACTOR_ENERGY_CARRIER: DEFAULT_WEIGHTS_ENERGY_CARRIERS[
292
                                dict_values[ENERGY_PRODUCTION][asset][ENERGY_VECTOR]
293
                            ][
294
                                VALUE
295
                            ],
296
                            RENEWABLE_SHARE_ASSET_FLOW: 1,
297
                        }
298
                    }
299
                )
300
            elif (
1✔
301
                dict_values[ENERGY_PRODUCTION][asset][RENEWABLE_ASSET_BOOL][VALUE]
302
                is False
303
            ):
304
                non_renewable_assets.update(
1✔
305
                    {
306
                        asset: {
307
                            OEMOF_SOLPH_OBJECT_ASSET: dict_model[OEMOF_SOURCE][
308
                                dict_values[ENERGY_PRODUCTION][asset][LABEL]
309
                            ],
310
                            OEMOF_SOLPH_OBJECT_BUS: dict_model[OEMOF_BUSSES][
311
                                dict_values[ENERGY_PRODUCTION][asset][OUTFLOW_DIRECTION]
312
                            ],
313
                            WEIGHTING_FACTOR_ENERGY_CARRIER: DEFAULT_WEIGHTS_ENERGY_CARRIERS[
314
                                dict_values[ENERGY_PRODUCTION][asset][ENERGY_VECTOR]
315
                            ][
316
                                VALUE
317
                            ],
318
                            RENEWABLE_SHARE_ASSET_FLOW: 0,
319
                        }
320
                    }
321
                )
322
            else:
UNCOV
323
                logging.warning(
×
324
                    f"Value of parameter {RENEWABLE_ASSET_BOOL} of asset {asset} is {dict_values[ENERGY_PRODUCTION][asset][RENEWABLE_ASSET_BOOL][VALUE]}, but should be True/False."
325
                )
326
        else:
327
            assets_without_renewable_asset_bool.append(asset)
1✔
328

329
    # This message is printed so that errors can be identified easier.
330
    assets_without_renewable_asset_bool_string = ", ".join(
1✔
331
        map(str, assets_without_renewable_asset_bool)
332
    )
333
    logging.debug(
1✔
334
        f"Following assets do not have key RENEWABLE_ASSET_BOOL: {assets_without_renewable_asset_bool_string}. "
335
        f"They should be all DSO consumption sources."
336
    )
337

338
    dso_sources = []
1✔
339
    for dso in dict_values[ENERGY_PROVIDERS]:
1✔
340
        # Get source connected to the specific DSO in question
341
        DSO_source_name = dso + DSO_CONSUMPTION
1✔
342
        # Add DSO to both renewable and nonrenewable assets (as only a share of their supply may be renewable)
343
        dso_sources.append(DSO_source_name)
1✔
344

345
        renewable_assets.update(
1✔
346
            {
347
                DSO_source_name: {
348
                    OEMOF_SOLPH_OBJECT_ASSET: dict_model[OEMOF_SOURCE][
349
                        dict_values[ENERGY_PRODUCTION][DSO_source_name][LABEL]
350
                    ],
351
                    OEMOF_SOLPH_OBJECT_BUS: dict_model[OEMOF_BUSSES][
352
                        dict_values[ENERGY_PRODUCTION][DSO_source_name][
353
                            OUTFLOW_DIRECTION
354
                        ]
355
                    ],
356
                    WEIGHTING_FACTOR_ENERGY_CARRIER: DEFAULT_WEIGHTS_ENERGY_CARRIERS[
357
                        dict_values[ENERGY_PRODUCTION][DSO_source_name][ENERGY_VECTOR]
358
                    ][VALUE],
359
                    RENEWABLE_SHARE_ASSET_FLOW: dict_values[ENERGY_PROVIDERS][dso][
360
                        RENEWABLE_SHARE_DSO
361
                    ][VALUE],
362
                }
363
            }
364
        )
365
        non_renewable_assets.update(
1✔
366
            {
367
                DSO_source_name: {
368
                    OEMOF_SOLPH_OBJECT_ASSET: dict_model[OEMOF_SOURCE][
369
                        dict_values[ENERGY_PRODUCTION][DSO_source_name][LABEL]
370
                    ],
371
                    OEMOF_SOLPH_OBJECT_BUS: dict_model[OEMOF_BUSSES][
372
                        dict_values[ENERGY_PRODUCTION][DSO_source_name][
373
                            OUTFLOW_DIRECTION
374
                        ]
375
                    ],
376
                    WEIGHTING_FACTOR_ENERGY_CARRIER: DEFAULT_WEIGHTS_ENERGY_CARRIERS[
377
                        dict_values[ENERGY_PRODUCTION][DSO_source_name][ENERGY_VECTOR]
378
                    ][VALUE],
379
                    RENEWABLE_SHARE_ASSET_FLOW: dict_values[ENERGY_PROVIDERS][dso][
380
                        RENEWABLE_SHARE_DSO
381
                    ][VALUE],
382
                }
383
            }
384
        )
385

386
    for k in assets_without_renewable_asset_bool:
1✔
387
        if k not in dso_sources:
1✔
UNCOV
388
            logging.warning(
×
389
                "There is an asset {k} that does not have any information whether it is of renewable origin or not. "
390
                "It is neither a DSO source nor a renewable or fuel source."
391
            )
392

393
    renewable_asset_string = ", ".join(map(str, renewable_assets.keys()))
1✔
394
    non_renewable_asset_string = ", ".join(map(str, non_renewable_assets.keys()))
1✔
395

396
    logging.debug(f"Data considered for the minimal renewable factor constraint:")
1✔
397
    logging.debug(f"Assets connected to renewable generation: {renewable_asset_string}")
1✔
398
    logging.debug(
1✔
399
        f"Assets connected to non-renewable generation: {non_renewable_asset_string}"
400
    )
401
    return renewable_assets, non_renewable_assets
1✔
402

403

404
def constraint_minimal_degree_of_autonomy(model, dict_values, dict_model):
1✔
405
    r"""
406
    Resulting in an energy system adhering to a minimal degree of autonomy.
407

408
    Please be aware that the minimal degree of autonomy is not applied to each sector individually,
409
    but to the overall energy system (via energy carrier weighting).
410

411
    Parameters
412
    ----------
413
    model: :oemof-solph: <oemof.solph.model>
414
        Model to which constraint is added.
415

416
    dict_values: dict
417
        All simulation parameters
418

419
    dict_model: dict of :oemof-solph: <oemof.solph.assets>
420
        Dictionary including the oemof-solph component assets, which need to be connected with constraints
421

422
    Notes
423
    -----
424
    The renewable factor of the whole energy system has to adhere for following constraint:
425

426
    .. math::
427
        minimal degree of autonomy \cdot (\sum local demand  \cdot weighting factor) <= \sum local demand \cdot weighting factor - \sum consumtion from energy providers \cdot weighting factor
428

429
    Tested with:
430
    - Test_Constraints.test_benchmark_minimal_degree_of_autonomy()
431
    """
432

433
    logging.debug(f"Data considered for the minimal degree of autonomy constraint:")
1✔
434

435
    if dict_values[CONSTRAINTS][MINIMAL_DEGREE_OF_AUTONOMY][VALUE] > 0:
1✔
436

437
        demands = prepare_demand_assets(
1✔
438
            dict_values,
439
            dict_model,
440
        )
441

442
        energy_provider_consumption_sources = (
1✔
443
            prepare_energy_provider_consumption_sources(
444
                dict_values,
445
                dict_model,
446
            )
447
        )
448

449
        def degree_of_autonomy_rule(model):
1✔
450
            total_demand = 0
1✔
451
            total_consumption_from_energy_provider = 0
1✔
452

453
            # Get the flows from demands and add weighing
454
            for asset in demands:
1✔
455

456
                demand_one_asset = (
1✔
457
                    sum(
458
                        model.flow[
459
                            demands[asset][OEMOF_SOLPH_OBJECT_BUS],
460
                            demands[asset][OEMOF_SOLPH_OBJECT_ASSET],
461
                            :,
462
                            :,
463
                        ]
464
                    )
465
                    * demands[asset][WEIGHTING_FACTOR_ENERGY_CARRIER]
466
                )
467
                total_demand += demand_one_asset
1✔
468

469
            # Get the flows from providers and add weighing
470
            for asset in energy_provider_consumption_sources:
1✔
471
                consumption_of_one_provider = (
1✔
472
                    sum(
473
                        model.flow[
474
                            energy_provider_consumption_sources[asset][
475
                                OEMOF_SOLPH_OBJECT_ASSET
476
                            ],
477
                            energy_provider_consumption_sources[asset][
478
                                OEMOF_SOLPH_OBJECT_BUS
479
                            ],
480
                            :,
481
                            :,
482
                        ]
483
                    )
484
                    * energy_provider_consumption_sources[asset][
485
                        WEIGHTING_FACTOR_ENERGY_CARRIER
486
                    ]
487
                )
488
                total_consumption_from_energy_provider += consumption_of_one_provider
1✔
489

490
            expr = (
1✔
491
                1 - dict_values[CONSTRAINTS][MINIMAL_DEGREE_OF_AUTONOMY][VALUE]
492
            ) * total_demand - total_consumption_from_energy_provider
493
            return expr >= 0
1✔
494

495
        model.constraint_minimal_degree_of_autonomy = po.Constraint(
1✔
496
            rule=degree_of_autonomy_rule
497
        )
498

499
        logging.info("Added minimal degree of autonomy constraint.")
1✔
500
        answer = model
1✔
501
    else:
502
        answer = None
1✔
503

504
    return answer
1✔
505

506

507
def prepare_demand_assets(
1✔
508
    dict_values,
509
    dict_model,
510
):
511
    r"""
512
    Perpare demand assets by processing `dict_values`
513

514
    Used for the following constraints:
515
    - minimal degree of autonomy
516

517
    Parameters
518
    ----------
519
    dict_values: dict
520
        All simulation parameters
521

522
    dict_model: dict of :oemof-solph: <oemof.solph.assets>
523
        Dictionary including the oemof-solph component assets, which need to be connected with constraints
524

525
    Notes
526
    -----
527
    Tested with:
528
    - test_prepare_demand_assets()
529

530
    Returns
531
    -------
532
    demands: dict
533
        Dictionary of all assets with all demands in the energy system.
534
        Defined by: oemof_solph_object_asset, weighting_factor_energy_carrier, oemof_solph_object_bus
535

536
    """
537
    # dicts for processing
538
    demands = {}
1✔
539
    demand_list = []
1✔
540

541
    # Determine energy demands
542
    for asset in dict_values[ENERGY_CONSUMPTION]:
1✔
543
        # Do not add flows into excess sink of feedin sink to the demands to be supplied
544
        if EXCESS_SINK not in asset and DSO_FEEDIN not in asset:
1✔
545
            demands.update(
1✔
546
                {
547
                    asset: {
548
                        OEMOF_SOLPH_OBJECT_ASSET: dict_model[OEMOF_SINK][
549
                            dict_values[ENERGY_CONSUMPTION][asset][LABEL]
550
                        ],
551
                        OEMOF_SOLPH_OBJECT_BUS: dict_model[OEMOF_BUSSES][
552
                            dict_values[ENERGY_CONSUMPTION][asset][INFLOW_DIRECTION]
553
                        ],
554
                        WEIGHTING_FACTOR_ENERGY_CARRIER: DEFAULT_WEIGHTS_ENERGY_CARRIERS[
555
                            dict_values[ENERGY_CONSUMPTION][asset][ENERGY_VECTOR]
556
                        ][
557
                            VALUE
558
                        ],
559
                    }
560
                }
561
            )
562
            demand_list.append(asset)
1✔
563

564
    # Preprocessing for log messages
565
    demand_string = ", ".join(map(str, demand_list))
1✔
566

567
    logging.debug(f"Demands: {demand_string}")
1✔
568
    return demands
1✔
569

570

571
def prepare_energy_provider_consumption_sources(
1✔
572
    dict_values,
573
    dict_model,
574
):
575
    r"""
576
    Prepare energy provider consumption sources by processing `dict_values`.
577

578
    Used for the following constraints:
579
    - minimal degree of autonomy
580
    - net zero energy (NZE)
581

582
    Parameters
583
    ----------
584
    dict_values: dict
585
        All simulation parameters
586

587
    dict_model: dict of :oemof-solph: <oemof.solph.assets>
588
        Dictionary including the oemof-solph component assets, which need to be connected with constraints
589

590
    Notes
591
    -----
592
    Tested with:
593
    - test_prepare_energy_provider_consumption_sources()
594

595
    Returns
596
    -------
597
    energy_provider_consumption_sources: dict
598
        Dictionary of all assets that are sources for the energy consumption from energy providers in the energy system.
599
        Defined by: oemof_solph_object_asset, weighting_factor_energy_carrier, oemof_solph_object_bus
600

601
    """
602
    # dicts for processing
603
    energy_provider_consumption_sources = {}
1✔
604
    dso_consumption_source_list = []
1✔
605

606
    for dso in dict_values[ENERGY_PROVIDERS]:
1✔
607
        # Get source connected to the specific DSO in question
608
        DSO_source_name = dso + DSO_CONSUMPTION
1✔
609
        # Add DSO to assets
610
        dso_consumption_source_list.append(DSO_source_name)
1✔
611

612
        energy_provider_consumption_sources.update(
1✔
613
            {
614
                DSO_source_name: {
615
                    OEMOF_SOLPH_OBJECT_ASSET: dict_model[OEMOF_SOURCE][
616
                        dict_values[ENERGY_PRODUCTION][DSO_source_name][LABEL]
617
                    ],
618
                    OEMOF_SOLPH_OBJECT_BUS: dict_model[OEMOF_BUSSES][
619
                        dict_values[ENERGY_PRODUCTION][DSO_source_name][
620
                            OUTFLOW_DIRECTION
621
                        ]
622
                    ],
623
                    WEIGHTING_FACTOR_ENERGY_CARRIER: DEFAULT_WEIGHTS_ENERGY_CARRIERS[
624
                        dict_values[ENERGY_PRODUCTION][DSO_source_name][ENERGY_VECTOR]
625
                    ][VALUE],
626
                }
627
            }
628
        )
629

630
    # Preprocessing for log messages
631
    dso_consumption_source_string = ", ".join(map(str, dso_consumption_source_list))
1✔
632

633
    logging.debug(
1✔
634
        f"Energy provider consumption sources: {dso_consumption_source_string}"
635
    )
636
    return energy_provider_consumption_sources
1✔
637

638

639
def prepare_energy_provider_feedin_sinks(
1✔
640
    dict_values,
641
    dict_model,
642
):
643
    r"""
644
    Prepare energy provider feedin sinks by processing `dict_values`.
645

646
    Used for the following constraints:
647
    - net zero energy (NZE)
648

649
    Parameters
650
    ----------
651
    dict_values: dict
652
        All simulation parameters
653

654
    dict_model: dict of :oemof-solph: <oemof.solph.assets>
655
        Dictionary including the oemof-solph component assets, which need to be connected with constraints
656

657
    Notes
658
    -----
659
    Tested with:
660
        - test_prepare_energy_provider_feedin_sinks()
661

662
    Returns
663
    -------
664
    energy_provider_feedin_sinks: dict
665
        Dictionary of all assets that are sinks for the energy feed-in to energy providers in the energy system.
666
        Defined by: oemof_solph_object_asset, weighting_factor_energy_carrier, oemof_solph_object_bus
667

668
    """
669
    # dicts for processing
670
    energy_provider_feedin_sinks = {}
1✔
671
    dso_feedin_sink_list = []
1✔
672

673
    for dso in dict_values[ENERGY_PROVIDERS]:
1✔
674
        # Get sink connected to the specific DSO in question
675
        DSO_sink_name = dso + DSO_FEEDIN
1✔
676
        # Add DSO to assets
677
        dso_feedin_sink_list.append(DSO_sink_name)
1✔
678

679
        energy_provider_feedin_sinks.update(
1✔
680
            {
681
                DSO_sink_name: {
682
                    OEMOF_SOLPH_OBJECT_ASSET: dict_model[OEMOF_SINK][
683
                        dict_values[ENERGY_CONSUMPTION][DSO_sink_name][LABEL]
684
                    ],
685
                    OEMOF_SOLPH_OBJECT_BUS: dict_model[OEMOF_BUSSES][
686
                        dict_values[ENERGY_CONSUMPTION][DSO_sink_name][INFLOW_DIRECTION]
687
                    ],
688
                    WEIGHTING_FACTOR_ENERGY_CARRIER: DEFAULT_WEIGHTS_ENERGY_CARRIERS[
689
                        dict_values[ENERGY_CONSUMPTION][DSO_sink_name][ENERGY_VECTOR]
690
                    ][VALUE],
691
                }
692
            }
693
        )
694

695
    # Preprocessing for log messages
696
    dso_feedin_sink_string = ", ".join(map(str, dso_feedin_sink_list))
1✔
697

698
    logging.debug(f"Energy provider feedin sinks: {dso_feedin_sink_string}")
1✔
699
    return energy_provider_feedin_sinks
1✔
700

701

702
def constraint_net_zero_energy(model, dict_values, dict_model):
1✔
703
    r"""
704
    Resulting in an energy system that is a net zero energy (NZE) or plus energy system.
705

706
    Please be aware that the NZE constraint is not applied to each sector individually,
707
    but to the overall energy system (via energy carrier weighting).
708

709
    Parameters
710
    ----------
711
    model: :oemof-solph: <oemof.solph.model>
712
        Model to which constraint is added.
713

714
    dict_values: dict
715
        All simulation parameters
716

717
    dict_model: dict of :oemof-solph: <oemof.solph.assets>
718
        Dictionary including the oemof-solph component assets, which need to be connected with constraints
719

720
    Notes
721
    -----
722
    The constraint reads as follows:
723

724
    .. math::
725
        \sum_{i} {E_{feedin, DSO} (i) \cdot w_i - E_{consumption, DSO} (i) \cdot w_i} >= 0
726

727
    Tested with:
728
    - Test_Constraints.test_net_zero_energy_constraint()
729
    """
730

731
    logging.debug(f"Data considered for the net zero energy (NZE) constraint:")
1✔
732

733
    if dict_values[CONSTRAINTS][NET_ZERO_ENERGY][VALUE] == True:
1✔
734

735
        energy_provider_feedin_sinks = prepare_energy_provider_feedin_sinks(
1✔
736
            dict_values,
737
            dict_model,
738
        )
739

740
        energy_provider_consumption_sources = (
1✔
741
            prepare_energy_provider_consumption_sources(
742
                dict_values,
743
                dict_model,
744
            )
745
        )
746

747
        def net_zero_energy(model):
1✔
748
            total_feedin_to_energy_provider = 0
1✔
749
            total_consumption_from_energy_provider = 0
1✔
750

751
            # Get the flows from provider sources and add weighing
752
            for asset in energy_provider_consumption_sources:
1✔
753
                consumption_of_one_provider = (
1✔
754
                    sum(
755
                        model.flow[
756
                            energy_provider_consumption_sources[asset][
757
                                OEMOF_SOLPH_OBJECT_ASSET
758
                            ],
759
                            energy_provider_consumption_sources[asset][
760
                                OEMOF_SOLPH_OBJECT_BUS
761
                            ],
762
                            :,
763
                            :,
764
                        ]
765
                    )
766
                    * energy_provider_consumption_sources[asset][
767
                        WEIGHTING_FACTOR_ENERGY_CARRIER
768
                    ]
769
                )
770
                total_consumption_from_energy_provider += consumption_of_one_provider
1✔
771

772
            # Get the flows from provider sources and add weighing
773
            for asset in energy_provider_feedin_sinks:
1✔
774
                feedin_of_one_provider = (
1✔
775
                    sum(
776
                        model.flow[
777
                            energy_provider_feedin_sinks[asset][OEMOF_SOLPH_OBJECT_BUS],
778
                            energy_provider_feedin_sinks[asset][
779
                                OEMOF_SOLPH_OBJECT_ASSET
780
                            ],
781
                            :,
782
                            :,
783
                        ]
784
                    )
785
                    * energy_provider_feedin_sinks[asset][
786
                        WEIGHTING_FACTOR_ENERGY_CARRIER
787
                    ]
788
                )
789
                total_feedin_to_energy_provider += feedin_of_one_provider
1✔
790

791
            expr = (
1✔
792
                total_feedin_to_energy_provider - total_consumption_from_energy_provider
793
            )
794
            return expr >= 0
1✔
795

796
        model.constraint_net_zero_energy = po.Constraint(rule=net_zero_energy)
1✔
797

798
        logging.info("Added net zero energy (NZE) constraint.")
1✔
799
        answer = model
1✔
800
    else:
801
        answer = None
1✔
802

803
    return answer
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc