-
Notifications
You must be signed in to change notification settings - Fork 124
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enforce nonconvex for OffsetConverter on a single flow instead of outputs #1068
Conversation
Thank you. I think this issue came up as the offset is typically negative, so there would be no output without input. However, when taking into account the losses (as you did in #1067) you will always run into the issue. So, I'd also consider the current implementation broken. As you suggested in the chat, it might make sense to change the keyword argument, when we change the implementation anyway. I'd suggest |
Agree!
That sounds like a good idea. I will complete the changes in this PR then. This includes
|
On top of that, I suggest a couple more changes:
The second version would be more simple from the user perspective (you have to define a reference flow, i.e. the |
I drafted this version with my latest commit. I will readd the investment part after I have created two examples which we should discuss before putting more time into the changes. |
Example 1electricity input with hydrogen (efficiency declining with load) and heat (efficiency increasing with load) output, reference is the only input flow: from oemof import solph
import numpy as np
t_steps = 11
es = solph.EnergySystem(timeindex=solph.create_time_index(year=2023, number=t_steps - 1))
eta_h2_min = 0.6
eta_h2_max = 0.5
P_in_min = 2
P_in_max = 10
P_out_min_h2 = P_in_min * eta_h2_min
P_out_max_h2 = P_in_max * eta_h2_max
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_in_max
eta_heat_min = 0.25
eta_heat_max = 0.3
P_out_min_heat = P_in_min * eta_heat_min
P_out_max_heat = P_in_max * eta_heat_max
slope_heat = (P_out_max_heat - P_out_min_heat) / (P_in_max - P_in_min)
offset_heat = (P_out_max_heat - slope_heat * P_in_max) / P_in_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()})
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=[0] + np.linspace(P_out_min_h2, P_out_max_h2, t_steps - 1).tolist(), nominal_value=1)})
electrolyzer = solph.components.OffsetConverter(
label="electrolyzer",
inputs={b_el: solph.Flow(nominal_value=P_in_max, nonconvex=solph.NonConvex(), min=P_in_min / P_in_max)},
outputs={
b_heat: solph.Flow(),
b_h2: solph.Flow()
},
conversion_factors={
b_heat: slope_heat,
b_h2: slope_h2,
},
normed_offsets={b_heat: offset_heat, b_h2: offset_h2}
)
es.add(
b_el, b_heat, b_h2,
source_el, sink_h2, sink_heat,
electrolyzer
)
om = solph.Model(es)
om.solve("gurobi")
result = solph.views.convert_keys_to_strings(om.results()) heat = result[("electrolyzer", "heat bus")]["sequences"]["flow"]
el = result[("electricity bus", "electrolyzer")]["sequences"]["flow"]
h2 = result[("electrolyzer", "hydrogen bus")]["sequences"]["flow"]
plot_lines_and_conversion_efficiencies(
{"flows": el, "min": P_in_min, "max": P_in_max, "commodity": "electricity input"},
{"flows": heat, "offset": offset_heat, "slope": slope_heat, "commodity": "heat output"},
{"flows": h2, "offset": offset_h2, "slope": slope_h2, "commodity": "h2 output"},
) Example 2gas input and electricity output with electricity (output commodity) as reference, might not make that much sense, but to show, that it works. from oemof import solph
import numpy as np
t_steps = 11
es = solph.EnergySystem(timeindex=solph.create_time_index(year=2023, number=t_steps - 1))
eta_el_min = 0.3
eta_el_max = 0.4
P_in_min = 2
P_in_max = 10
P_out_min_el = P_in_min * eta_el_min
P_out_max_el = P_in_max * eta_el_max
slope_gas = (P_in_max - P_in_min) / (P_out_max_el - P_out_min_el)
offset_gas = (P_in_max - slope_gas * P_out_max_el) / P_out_max_el
b_el = solph.Bus("electricity bus")
b_gas = solph.Bus("gas bus")
source_gas = solph.components.Source("gas import", outputs={b_gas: solph.Flow()})
sink_el = solph.components.Sink("electricity export", inputs={b_el: solph.Flow(fix=[0] + np.linspace(P_out_min_el, P_out_max_el, t_steps - 1).tolist(), nominal_value=1)})
chp = solph.components.OffsetConverter(
label="chp",
inputs={b_gas: solph.Flow()},
outputs={
b_el: solph.Flow(nominal_value=P_out_max_el, nonconvex=solph.NonConvex(), min=P_out_min_el / P_out_max_el)
},
conversion_factors={b_gas: slope_gas},
normed_offsets={b_gas: offset_gas}
)
es.add(
b_gas, b_el,
source_gas, sink_el,
chp
)
om = solph.Model(es)
om.solve("gurobi")
result = solph.views.convert_keys_to_strings(om.results())
el = result[("chp", "electricity bus")]["sequences"]["flow"]
gas = result[("gas bus", "chp")]["sequences"]["flow"]
plot_lines_and_conversion_efficiencies(
{"flows": el, "min": P_out_min_el, "max": P_out_max_el, "commodity": "electricity output"},
{"flows": gas, "offset": offset_gas, "slope": slope_gas, "commodity": "gas input"},
) Plotting code
|
The lp file of last failing test ( With the new implementation we have to reference both, the slope (0.9) and the offset (17) to one of both flows. That means, that either the slope or the offset has to be recalculated. I therefore suggest, hand-checking the new implementation's lp and replacing the old one. On a side note: the |
…when provided to the reference flow
I added two more tests, one for double inputs and double outputs with the With that, I think this PR is ready for final review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for this implementation. It really fills a gap. At the moment, there are few issues left to be fixed before merge.
One has investment optimization, the other one does not.
@p-snft, @Bachibouzouk, I think, we are finally at it. I implemented a small plotting function for the new figures. I did not reference it anywhere in the docs. Although it is helpful, it could be refined a little bit maybe, I did not want to waste too much time on it. The fail of the Have a nice weekend! |
The doc error reports that the linked page https://shop.vde.com/en/vde-study-the-cellular-approach has problems. This is no problem to be solved here. There is little to do, yet. I will contribute that myself. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my opinion, this is ready to be merged.
There are two remaining problems found by the automated process:
- Docs: Not all linked pages are available. (Not to be solved here.)
- Coverage: Test coverage decreases because of plotting functionality. As, however, this is not an advertised feature and nobody will rely on it, I think this is in order.
Thank you for the review @p-snft and @Bachibouzouk, I will merge the branch although the tests are crashing here. The reason for that is that some parts of the already existing code are incompatible with numpy. I did not touch the code since the approval, where the tests were still successful. |
Resolve #1010