oemof/oemof-solph

The `OffsetConverter` is broken in the new implementation

Closed this issue · 4 comments

Describe the bug
With the current formulation, the OffsetConverter can produce the outputs at the level of each respective offset without consuming anything from the input.

I am preparing a PR to solve the issue.

To Reproduce
Consider the following example of an electrolyzer producing hydrogen and heat. The electrolyzer follows a fixed hydrogen demand, a slack source is available in case the demand cannot be supplied due to part load restrictions.

from oemof import solph


es = solph.EnergySystem(timeindex=solph.create_time_index(year=2023, number=5))

eta_h2_min = 0.6                 # efficiency at minimal operation point
eta_h2_max = 0.5                 # efficiency at nominal operation point
P_in_min = 20                   # absolute minimal output power
P_in_max = 100                  # absolute nominal output power

# calculate limits of input power flow
P_out_min_h2 = P_in_min * eta_h2_min
P_out_max_h2 = P_in_max * eta_h2_max

# calculate coefficients of input-output line equation
slope_h2 = (P_out_max_h2 - P_out_min_h2) / (P_in_max - P_in_min)
offset_h2 = (P_out_max_h2 - slope_h2 * P_in_max) / P_out_max_h2


eta_heat_min = 0.3                 # efficiency at minimal operation point
eta_heat_max = 0.4                 # efficiency at nominal operation point

# calculate limits of input power flow
P_out_min = P_in_min * eta_heat_min
P_out_max = P_in_max * eta_heat_max

# calculate coefficients of input-output line equation
slope_heat = (P_out_max - P_out_min) / (P_in_max - P_in_min)
offset_heat = (P_out_max - slope_heat * P_in_max) / P_out_max

b_h2 = solph.Bus("hydrogen bus")
b_heat = solph.Bus("heat bus")
b_el = solph.Bus("electricity bus")

b_electrolyzer_h2 = solph.Bus("electrolyzer virtual input for h2")
b_electrolyzer_heat = solph.Bus("electrolyzer virtual input for heat")

source_el = solph.components.Source("electricity import", outputs={b_el: solph.Flow()})
source_slack_h2 = solph.components.Source("hydrogen slack", outputs={b_h2: solph.Flow(variable_costs=1000)})
sink_heat = solph.components.Sink("heat export", inputs={b_heat: solph.Flow()})
sink_h2 = solph.components.Sink("hydrogen export", inputs={b_h2: solph.Flow(fix=[10, 20, 30, 40, 50, 60], nominal_value=1)})

electrolyzer = solph.components.OffsetConverter(
    label="electrolyzer",
    inputs={b_el: solph.Flow(nominal_value=P_in_max)},
    outputs={
        b_heat: solph.Flow(
            nominal_value=P_out_max,
            nonconvex=solph.NonConvex(),
            min=P_out_min / P_out_max
        ),
        b_h2: solph.Flow(
            nominal_value=P_out_max_h2,
            nonconvex=solph.NonConvex()
        )
    },
    coefficients={
        b_heat: [offset_heat, slope_heat],
        b_h2: [offset_h2, slope_h2],
    }
)

es.add(
    b_el, b_heat, b_h2,
    source_el, sink_h2, sink_heat, source_slack_h2,
    electrolyzer
)

om = solph.Model(es)

om.solve("gurobi")

result = solph.views.convert_keys_to_strings(om.results())
result[("electrolyzer", "hydrogen bus")]["sequences"]

The minimum hydrogen production should be at 12, but due to the offset it is at 2.5.

flow status status_nominal
2023-01-01 00:00:00 2.5 1 50
2023-01-01 01:00:00 20 1 50
2023-01-01 02:00:00 30 1 50
2023-01-01 03:00:00 40 1 50
2023-01-01 04:00:00 50 1 50
2023-01-01 05:00:00 50 1 50

The reason for that is, that the status_nominal is multiplied with the output binary variable instead of the input binary variable.

Never mind, I think my example is just missing the min attribute of the H2 output Flow. Still, I would like to make the cleanup with enforcing a single NonConvex flow as the reference for all other flow's slopes and offsets.

Never mind, I think my example is just missing the min attribute of the H2 output Flow. Still, I would like to make the cleanup with enforcing a single NonConvex flow as the reference for all other flow's slopes and offsets.

Does that mean this issue is closed, then?

Does that mean this issue is closed, then?

Kind of yes, the linked pull requests is still valid. Now it is more of a complete rework of the OffsetConverter and might close #1010 instead?

So, this was not a real bug, only the API was very inviting to put unreasonable parameters. I will close this, as it can be seen as a duplicate of the "mislieading documentation" issue.