Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
bd868e0
Add stub of replacement for processing.results
p-snft May 26, 2025
ae31e60
Finalise compatible result re-sorter
p-snft May 27, 2025
4c18b7d
Adhere to Black
p-snft May 27, 2025
152127a
Fix import order
p-snft May 28, 2025
1a62c52
Use new result handling
p-snft May 28, 2025
055ff84
Merge branch 'dev' into feature/result_dict_wrapper
p-snft Jun 3, 2025
097a350
Merge branch 'dev' into feature/result_dict_wrapper
p-snft Jun 5, 2025
b69aed3
Refactor (Invest)NonConvexFlowBlock
p-snft Jun 5, 2025
beb1833
Revert "Refactor (Invest)NonConvexFlowBlock"
p-snft Jun 5, 2025
2cd12b2
Refactor NonConvexFlowBlock
p-snft Jun 5, 2025
87fc4a5
Fix limit_active_flow_count_by_keyword
p-snft Jun 5, 2025
0a2766c
Fix costs of NonConvexFlowBlock
p-snft Jun 5, 2025
ffbbe77
Fix limit_active_flow_count_by_keyword (really!)
p-snft Jun 5, 2025
59c36ce
Fix test_numeric_index in NonEquidistantTimeStep
p-snft Jun 5, 2025
96c3f35
Remove outdated test
p-snft Jun 5, 2025
e00b36e
Fix ES initialisation using timeincrement
p-snft Jun 5, 2025
185d0ea
Allow local variable names for Results
p-snft Jun 5, 2025
4071f92
Merge branch 'fix/result-object_same-name-vars' into feature/result_d…
p-snft Jun 5, 2025
a46b442
Fix import formating
p-snft Jun 5, 2025
bb6f778
Re-add dual-extraction to processing
p-snft Jun 5, 2025
00abe83
Use Results object in test_connect_invest
p-snft Jun 5, 2025
b905e7b
Fix import order
p-snft Jun 5, 2025
1c160e7
Merge branch 'master' into feature/result_dict_wrapper
p-snft Jun 12, 2025
46c7def
Add changes to changelog
p-snft Jun 12, 2025
cc98058
Merge branch 'dev' into feature/result_dict_wrapper
p-snft Oct 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions docs/whatsnew/v0-6-1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ v0.6.1

API changes
###########
* GenericStorage requires explicitly set `Investment` objects if the `invest_relation`
keywords are used. Automatic creation of the `Investment` objects is removed.


New features
############
* GenericStorage requires explicitly set `Investment` objects if the `invest_relation`
keywords are used. Automatic creation of the `Investment` objects is removed.
* Two new keys added to `Results` class (experimental) to calculate variable costs and
investment costs via `results["variable_costs"]` and `results["investment_costs"]`.

Expand All @@ -22,10 +23,19 @@ Bug fixes
Other changes
#############

* Implement a backward-compatibility module for processing.results that uses
the new solph.Results object.
* Experimental component `SinkDSM` (unmaintained) has been removed
* Unmaintained, non-working functionality for three-phase electricity has been
removed.

Known issues
############

* Backward-compatibile result extraction (processing.results)
of mutli-period-models fails.
* Results of TSAM-models do not have original time axis reconstructed.

Contributors
############

Expand Down
2 changes: 1 addition & 1 deletion examples/check_examples.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import importlib
import os
import subprocess
import tempfile
import warnings
import importlib
from datetime import datetime

import matplotlib
Expand Down
2 changes: 1 addition & 1 deletion examples/result_object/result_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,12 @@

from oemof.solph import EnergySystem
from oemof.solph import Model
from oemof.solph import Results
from oemof.solph import buses
from oemof.solph import components
from oemof.solph import create_time_index
from oemof.solph import flows
from oemof.solph import processing
from oemof.solph import Results
from oemof.solph import views


Expand Down
135 changes: 135 additions & 0 deletions src/oemof/solph/_processing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# -*- coding: utf-8 -*-

"""Compatibility wrapper of solph.Results for providing solph results in the
structure of the output of old processing.results(model).

SPDX-FileCopyrightText: Patrik Schönfeldt <patrik.schoenfeldt@dlr.de>

SPDX-License-Identifier: MIT
"""

import warnings
from itertools import groupby

import pandas as pd
from oemof.tools import debugging

from ._models import Model
from ._results import Results


def results(
model: Model,
remove_last_time_point: bool = False,
scalar_data: list[str] | None = None,
):
"""Create a nested result dictionary from the result DataFrame

The results from Pyomo from the Results object are
transferred into a nested dictionary of pandas objects.
The first level key of that dictionary is a node
(denoting the respective flow or component).

The second level keys are "sequences" and "scalars":

* A pd.DataFrame holds all results that are time-dependent, i.e. given as
a sequence and can be indexed with the energy system's timeindex.
* A pd.Series holds all scalar values which are applicable for timestep 0
(i.e. investments).

Models with more than one time for investments are not supported.
In these models, investments are sequential data,
but with a second time imdex. As this is a compatibility layer,
we did not add support for this new feature.
Instead, use of the Results object is advised.

Parameters
----------
model : oemof.solph.Model
A solved oemof.solph model.
remove_last_time_point : bool
The last time point of all TIMEPOINT variables is removed to get the
same length as the TIMESTEP (interval) variables without getting
nan-values. By default, the last time point is removed if it has not
been defined by the user in the EnergySystem but inferred. If all
time points have been defined explicitly by the user the last time
point will not be removed by default. In that case all interval
variables will get one row with nan-values to have the same index
for all variables.
scalar_data : list[str]
List of variables to be treated as scalar data (see above).
sequence_data: list[str]
List of variables to be treated as sequential data (see above).
"""

meta_result_keys = ["Solution", "Problem", "Solver"]

if scalar_data is None:
scalar_data = ["invest", "total"]

result_dict = {}
with warnings.catch_warnings():
warnings.filterwarnings(
"ignore", category=debugging.ExperimentalFeatureWarning
)
result_object = Results(model)

timeindex = model.es.timeindex

if remove_last_time_point:
timeindex = timeindex[:-1]

def _handle_scalar(data):
return data.iloc[0]

def _handle_sequence(data):
return data

for result_key in result_object.keys():
if result_key not in meta_result_keys:
if result_key in scalar_data:
result_type = "scalars"
data_handler = _handle_scalar
else:
result_type = "sequences"
data_handler = _handle_sequence

index = result_object[result_key].columns
for item in index:
if isinstance(index, pd.MultiIndex):
node_tuple = item
else:
node_tuple = (item, None)
if node_tuple not in result_dict:
result_dict[node_tuple] = {
"scalars": pd.Series(),
"sequences": pd.DataFrame(index=timeindex),
}

data = result_object[result_key][item]
result_dict[node_tuple][result_type][result_key] = (
data_handler(data)
)

if model.dual is not None:
grouped = groupby(
sorted(model.BusBlock.balance.iterkeys()), lambda t: t[0]
)
for bus, timestep in grouped:
duals = [
model.dual[model.BusBlock.balance[bus, t]] for _, t in timestep
]
if model.es.periods is None:
df = pd.DataFrame({"duals": duals}, index=timeindex[:-1])
# TODO: Align with standard model
else:
df = pd.DataFrame({"duals": duals}, index=timeindex)
if (bus, None) not in result_dict.keys():
result_dict[(bus, None)] = {
"sequences": df,
"scalars": pd.Series(dtype=float),
}
else:
result_dict[(bus, None)]["sequences"]["duals"] = duals

return result_dict
11 changes: 8 additions & 3 deletions src/oemof/solph/constraints/flow_count_limit.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,14 @@ def limit_active_flow_count_by_keyword(
limit_active_flow_count
"""
flows = []
for i, o in model.NonConvexFlowBlock.NONCONVEX_FLOWS:
if hasattr(model.flows[i, o], keyword):
flows.append((i, o))
if hasattr(model.NonConvexFlowBlock, "FIXED_CAPACITY_NONCONVEX_FLOWS"):
for i, o in model.NonConvexFlowBlock.FIXED_CAPACITY_NONCONVEX_FLOWS:
if hasattr(model.flows[i, o], keyword):
flows.append((i, o))
if hasattr(model.InvestNonConvexFlowBlock, "INVEST_NON_CONVEX_FLOWS"):
for i, o in model.InvestNonConvexFlowBlock.INVEST_NON_CONVEX_FLOWS:
if hasattr(model.flows[i, o], keyword):
flows.append((i, o))

return limit_active_flow_count(
model,
Expand Down
20 changes: 14 additions & 6 deletions src/oemof/solph/flows/_non_convex_flow_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ def _create_sets(self, group):

.. automethod:: _sets_for_non_convex_flows
"""
self.NONCONVEX_FLOWS = Set(initialize=[(g[0], g[1]) for g in group])
self.FIXED_CAPACITY_NONCONVEX_FLOWS = Set(
initialize=[(g[0], g[1]) for g in group]
)

self._sets_for_non_convex_flows(group)

Expand All @@ -86,8 +88,10 @@ def _create_variables(self):
.. automethod:: _variables_for_non_convex_flows
"""
m = self.parent_block()
self.status = Var(self.NONCONVEX_FLOWS, m.TIMESTEPS, within=Binary)
for o, i in self.NONCONVEX_FLOWS:
self.status = Var(
self.FIXED_CAPACITY_NONCONVEX_FLOWS, m.TIMESTEPS, within=Binary
)
for o, i in self.FIXED_CAPACITY_NONCONVEX_FLOWS:
if m.flows[o, i].nonconvex.initial_status is not None:
for t in range(
0, m.flows[o, i].nonconvex.first_flexible_timestep
Expand All @@ -101,7 +105,9 @@ def _create_variables(self):
# multiplication of a binary variable (`status`)
# and a continuous variable (`invest` or `nominal_capacity`)
self.status_nominal = Var(
self.NONCONVEX_FLOWS, m.TIMESTEPS, within=NonNegativeReals
self.FIXED_CAPACITY_NONCONVEX_FLOWS,
m.TIMESTEPS,
within=NonNegativeReals,
)

self._variables_for_non_convex_flows()
Expand Down Expand Up @@ -132,7 +138,7 @@ def _objective_expression(self):
.. automethod:: _activity_costs
.. automethod:: _inactivity_costs
"""
if not hasattr(self, "NONCONVEX_FLOWS"):
if not hasattr(self, "FIXED_CAPACITY_NONCONVEX_FLOWS"):
return 0

startup_costs = self._startup_costs()
Expand Down Expand Up @@ -669,7 +675,9 @@ def _status_nominal_rule(_, i, o, t):
return expr

return Constraint(
self.NONCONVEX_FLOWS, m.TIMESTEPS, rule=_status_nominal_rule
self.FIXED_CAPACITY_NONCONVEX_FLOWS,
m.TIMESTEPS,
rule=_status_nominal_rule,
)

def _shared_constraints_for_non_convex_flows(self):
Expand Down
Loading
Loading