-
Notifications
You must be signed in to change notification settings - Fork 33
Remove system-level outputs from storage and replace with demand component (follow-on to 631) #666
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
base: develop
Are you sure you want to change the base?
Changes from 47 commits
1b9f88d
2b3d0c1
2af6329
c59a20e
eab6d09
1e54ed2
1852db4
b1b62c9
a97292c
2d6b0c5
0e6b3e8
2986dff
1f71c9d
9672ecc
7ad7b59
9ace0ad
8444cdf
2093b81
1510bd2
d116ad8
7c5b9e5
1046419
65dc07b
89a4ced
5032e41
24cb283
92d9854
3001732
572a020
2bd17d8
c71ba77
7596e27
f47966d
9c8b7c1
90e56be
8988ae8
140c368
7e3b3f3
d9ad39e
a0202a7
8094ee8
738173c
eaa17e8
75fa5f5
a01b5af
5a510aa
21082b7
9ecd357
6ebd865
ed710a5
46116b6
0c9c665
6308329
6720139
34f5a8c
41a177b
864b124
ff1b98c
62347b5
98047ec
0704c24
15e7573
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think these figures really help illustrate what the calculations are doing :) thank you! |
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a very nice demo, well done putting this together! |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,213 @@ | ||
| --- | ||
| jupytext: | ||
| text_representation: | ||
| extension: .md | ||
| format_name: myst | ||
| format_version: 0.13 | ||
| jupytext_version: 1.18.1 | ||
| kernelspec: | ||
| display_name: native-ard-h2i | ||
| language: python | ||
| name: python3 | ||
| --- | ||
|
|
||
| # Demand Demonstration | ||
| Different usage of demand components is shown in the following examples: | ||
| - `13_dispatch_for_electrolyzer`: sets the battery demand differently than the demand profile of the demand component | ||
| - `23_solar_wind_ng_demand`: compares usage of the `GenericDemandComponent` and `FlexibleDemandComponent` | ||
| - `24_solar_battery_grid`: sells excess electricity to grid | ||
elenya-grant marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| This demonstration will focus on the `13_dispatch_for_electrolyzer` example | ||
|
|
||
| ## Electrolyzer load demand | ||
|
|
||
| The following example is an expanded form of `examples/13_dispatch_for_electrolyzer`. | ||
|
|
||
| The technology interconnections: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the user would benefit from an xdsm diagram here.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. heard - how do I make that?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
We could use the built-in XDSM-maker in H2I, callable via
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that is exactly what I was thinking. It would be nice to use the built-in method as part of the example, but all I was asking for is including the figure to visualize the connections.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thanks! I added it in |
||
|
|
||
| ```{literalinclude} ../../examples/13_dispatch_for_electrolyzer/plant_config.yaml | ||
| :language: yaml | ||
| :lineno-start: 16 | ||
| :linenos: true | ||
| :lines: 16-27 | ||
| ``` | ||
|
|
||
|
|
||
| The electrolyzer system is comprised of 6 stacks, each rated at 10 MW, resulting in a total capacity of 60 MW. The minimum operating point of the electrolyzer (`turndown_ratio`) is 10% of the rated capacity, meaning that the electrolyzer is turned off if there is less than 6 MW of input electricity. | ||
|
|
||
| ```{literalinclude} ../../examples/13_dispatch_for_electrolyzer/tech_config.yaml | ||
| :language: yaml | ||
| :lineno-start: 121 | ||
| :linenos: true | ||
| :lines: 121-123,126-127,130,131,135 | ||
| ``` | ||
|
|
||
| We want to dispatch the battery to *keep the electrolyzer on*. We set the demand profile for the battery as the minimum power required to keep the electrolyzer on, 6 MW. | ||
|
|
||
| ```{note} | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Helpful note! |
||
| Note: the demand profile for the battery is only used by the battery controller and is not used in the battery performance model for any calculations. | ||
| ``` | ||
|
|
||
|
|
||
| ```{literalinclude} ../../examples/13_dispatch_for_electrolyzer/tech_config.yaml | ||
| :language: yaml | ||
| :lineno-start: 79 | ||
| :linenos: true | ||
| :lines: 79-90, 100 | ||
| ``` | ||
|
|
||
| We don't want to send more electricity to the electrolyzer than the electrolyzer can use. We use the demand component to saturate the electricity generation to the electrolyzer's rated capacity (equal to 60 MW). | ||
|
|
||
| ```{literalinclude} ../../examples/13_dispatch_for_electrolyzer/tech_config.yaml | ||
| :language: yaml | ||
| :lineno-start: 113 | ||
| :linenos: true | ||
| :lines: 113-120 | ||
| ``` | ||
|
|
||
|
|
||
|
|
||
| We initialize and setup the H2I model | ||
|
|
||
| ```{code-cell} ipython3 | ||
| from matplotlib import pyplot as plt | ||
| from h2integrate.core.h2integrate_model import H2IntegrateModel | ||
| from h2integrate import EXAMPLE_DIR | ||
|
|
||
| # Create an H2Integrate model | ||
| h2i = H2IntegrateModel(EXAMPLE_DIR/"13_dispatch_for_electrolyzer"/"inputs"/"h2i_wind_to_h2_storage.yaml") | ||
|
|
||
| # Setup the model | ||
| h2i.setup() | ||
| ``` | ||
|
|
||
| If we wanted to change the demand profiles for the battery (`battery`) or the demand component (`elec_load_demand`) to be different than the demand profiles specified in the technology config, we can do that using `set_val`: | ||
|
|
||
| ```{code-cell} ipython3 | ||
| electrolyzer_capacity_MW = 60 | ||
|
|
||
| # Set the battery demand equal to the minimum electricity needed to keep the electrolyzer on | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wait, didn't you already set this in the
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes - I may have pushed up changes since your comment (the line numbers are confusing me). Before the part where I call set_val() now - I have this line:
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. commented out the python code in that section but left the note so folks understand. |
||
| h2i.prob.set_val("battery.electricity_demand", 0.1 * electrolyzer_capacity_MW, units="MW") | ||
|
|
||
| # Set the demand of the demand component equal to the rated electrical capacity of the electrolyzer | ||
| h2i.prob.set_val("elec_load_demand.electricity_demand", electrolyzer_capacity_MW, units="MW") | ||
| ``` | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since you want to show the code but not run the code, just render the code in the standard non-running code block. electrolyzer_capacity_MW = 60
# Set the battery demand equal to the minimum electricity needed to keep the electrolyzer on
h2i.prob.set_val("battery.electricity_demand", 0.1 * electrolyzer_capacity_MW, units="MW")
# Set the demand of the demand component equal to the rated electrical capacity of the electrolyzer
h2i.prob.set_val("elec_load_demand.electricity_demand", electrolyzer_capacity_MW, units="MW")Which, non-rendered, looks like this:
|
||
|
|
||
| We then run the model: | ||
| ```{code-cell} ipython3 | ||
| # Run the model | ||
| h2i.run() | ||
| h2i.prob.get_val("finance_subgroup_hydrogen.LCOH", units="USD/kg")[0] | ||
| ``` | ||
|
|
||
|
|
||
| ### Plotting outputs | ||
|
|
||
| First, we get the main inputs and outputs of the `GenericDemandComponent` technology: | ||
|
|
||
| ```{code-cell} ipython3 | ||
| generation_with_battery = h2i.prob.get_val("elec_load_demand.electricity_in", units="MW") | ||
| full_demand = h2i.prob.get_val("elec_load_demand.electricity_demand", units="MW") | ||
| unmet_demand = h2i.prob.get_val("elec_load_demand.unmet_electricity_demand_out", units="MW") | ||
| excess_electricity = h2i.prob.get_val("elec_load_demand.unused_electricity_out", units="MW") | ||
| ``` | ||
|
|
||
| Plot the inputs to the `GenericDemandComponent` and outputs calculated in the `GenericDemandComponent`: | ||
|
|
||
| ```{code-cell} ipython3 | ||
| import matplotlib.pyplot as plt | ||
| fig, ax = plt.subplots(1, 1, figsize=[6.4, 2.4]) | ||
|
|
||
| start_hour = 800 | ||
| end_hour = 1000 | ||
|
|
||
| x = list(range(start_hour, end_hour)) | ||
| where_unmet_demand = [True if d>0 else False for d in unmet_demand[start_hour:end_hour]] | ||
| where_excess_commodity = [True if d>0 else False for d in excess_electricity[start_hour:end_hour]] | ||
|
|
||
| ax.fill_between(x, full_demand[start_hour:end_hour], generation_with_battery[start_hour:end_hour], where=where_unmet_demand, color="tab:red", alpha=0.5, zorder=0, label="unmet_commodity_demand_out") | ||
| ax.fill_between(x, full_demand[start_hour:end_hour], generation_with_battery[start_hour:end_hour], where=where_excess_commodity, color="tab:blue", alpha=0.5, zorder=1, label="unused_commodity_out") | ||
| ax.plot(x, full_demand[start_hour:end_hour], color="tab:green", lw=2.0, ls='solid', zorder=4, label="commodity_demand") | ||
| ax.plot(x, generation_with_battery[start_hour:end_hour], color="tab:pink", lw=2.0, ls='solid', zorder=3, label="commodity_in") | ||
|
|
||
| ax.set_xlim([start_hour,end_hour]) | ||
| ax.spines[['right', 'top']].set_visible(False) | ||
| ax.legend(bbox_to_anchor=(0.10, 0.95), loc="lower left", borderaxespad=0.0, framealpha=0.0, ncols=2) | ||
| ax.set_ylabel("GenericDemandComponent \nElectricity (MW)") | ||
| ax.set_xlabel("Time (hours)") | ||
| ``` | ||
|
|
||
|
|
||
| Plot the main inputs and outputs of the `GenericDemandComponent`: | ||
|
|
||
| ```{code-cell} ipython3 | ||
| fig, ax = plt.subplots(1, 1, figsize=[6.4, 2.4]) | ||
| ax.fill_between(x, full_demand[start_hour:end_hour], generation_with_battery[start_hour:end_hour], where=where_excess_commodity, color="tab:grey", alpha=0.25, zorder=1, label="_unused_commodity_out") | ||
| ax.plot(x, generation_with_battery[start_hour:end_hour], color="tab:grey", lw=2.0, ls='solid', zorder=2, label="commodity_in", alpha=0.25) | ||
|
|
||
| ax.plot(x, full_demand[start_hour:end_hour], color="tab:green", alpha=0.5, lw=1.5, ls='-.', zorder=3, label="commodity_demand") | ||
|
|
||
| ax.plot(x, h2i.prob.get_val("elec_load_demand.electricity_out", units="MW")[start_hour:end_hour], color="tab:purple", lw=2.0, ls='solid', zorder=4, label="commodity_out") | ||
| ax.spines[['right', 'top']].set_visible(False) | ||
| ax.set_xlim([start_hour, end_hour]) | ||
| ax.set_ylabel("GenericDemandComponent \nElectricity (MW)") | ||
| ax.set_xlabel("Time (hours)") | ||
| ax.legend(bbox_to_anchor=(0.0, 0.95), loc="lower left", ncols=3, borderaxespad=0.0, framealpha=0.0) | ||
| ``` | ||
|
|
||
|
|
||
| Plot the battery performance: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sick plot!!!! |
||
|
|
||
| ```{code-cell} ipython3 | ||
| fig, ax = plt.subplots(1, 1, figsize=[7.2, 2.4]) | ||
|
|
||
| start_hour = 900 | ||
| end_hour = 1000 | ||
| x = list(range(start_hour, end_hour)) | ||
|
|
||
| generation = h2i.prob.get_val("battery.electricity_in", units="MW") | ||
| battery_demand = h2i.prob.get_val("battery.electricity_demand", units="MW") | ||
| battery_charge_discharge = h2i.prob.get_val("battery.electricity_out", units="MW") | ||
|
|
||
| where_charge = [True if d<0 else False for d in battery_charge_discharge[start_hour:end_hour]] | ||
| where_discharge = [True if d>0 else False for d in battery_charge_discharge[start_hour:end_hour]] | ||
|
|
||
| ax.plot(x, battery_demand[start_hour:end_hour], color="tab:green", alpha=0.5, lw=1.5, ls='-.', zorder=2, label="battery.electricity_demand") | ||
| ax.plot(x, generation[start_hour:end_hour], color="tab:blue", alpha=1.0, lw=1.5, ls='--', zorder=3, label="battery.electricity_in") | ||
| ax.plot(x, generation_with_battery[start_hour:end_hour], color="tab:pink", alpha=1.0, lw=1.5, ls='-', zorder=3, label="elec_combiner.electricity_out") | ||
| ax.fill_between(x, generation[start_hour:end_hour], generation_with_battery[start_hour:end_hour], where=where_charge, color="tab:cyan", alpha=0.5, zorder=0, label="battery charging") | ||
| ax.fill_between(x, generation[start_hour:end_hour], generation_with_battery[start_hour:end_hour], where=where_discharge, color="tab:orange", alpha=0.5, zorder=0, label="battery discharging") | ||
|
|
||
|
|
||
| ax.spines[['right', 'top']].set_visible(False) | ||
| ax.set_xlim([start_hour, end_hour]) | ||
| ax.set_ylim([0, 70]) | ||
| ax.legend(bbox_to_anchor=(1.0, 0.5), loc="center left", borderaxespad=0, framealpha=0.0) | ||
| ax.set_ylabel("Electricity (MW)") | ||
| ax.set_xlabel("Time (hours)") | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You did a very nice job with the figures in this page. Clean and clear. Non-blocking suggestion here, but I would like to see the storage SOC and charge and discharge plots directly. I think it would make the storage operation a little more clear.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done! |
||
| ``` | ||
|
|
||
|
|
||
| ### Changing the battery demand | ||
|
|
||
| Lets see what the LCOH is when the battery is used to keep the electrolyzer on: | ||
| ```{code-cell} ipython3 | ||
| h2i.prob.get_val("finance_subgroup_hydrogen.LCOH", units="USD/kg")[0] | ||
| ``` | ||
|
|
||
| If we re-run H2I and set the battery demand equal to the electrolyzer capacity instead, we can see that the LCOH increases: | ||
|
|
||
| ```{code-cell} ipython3 | ||
|
|
||
| # Set the battery demand equal to the rated electrical capacity of the electrolyzer | ||
| h2i.prob.set_val("battery.electricity_demand",electrolyzer_capacity_MW, units="MW") | ||
|
|
||
| # Set the demand of the demand component equal to the rated electrical capacity of the electrolyzer | ||
| h2i.prob.set_val("elec_load_demand.electricity_demand", electrolyzer_capacity_MW, units="MW") | ||
|
|
||
| # Re-run H2I | ||
| h2i.run() | ||
|
|
||
| # Get the LCOH | ||
| h2i.prob.get_val("finance_subgroup_hydrogen.LCOH", units="USD/kg")[0] | ||
| ``` | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. are the added figures color-blind friendly?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure...could you suggest some colors that you'd like instead?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Noted in Issue #667 |
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looking at this figure, the red is really hard to see even for a color seeing person. I realize this isn't a new addition in this PR but do we have to code to maybe change to color and that wouldn't be too hard? If the answer is no then don't do it.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't remember where the code is that I had to make this figure initially - so - I can try to re-make it but it'd take 30min+ |


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.
I was looking at the docs for this and I think it could make sense to update example 14 and the outputs in this demonstration to show how the demand to the
h2_storageand theh2_load_demandcan be different and how that impacts the analysis. I would also recommend updating thetech_configexecutable block because it doesn't include theh2_load_demandand I think that would be helpful to showcase.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.
That's a good idea - I will work on that!
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.
I think that example 1 or 2 may be a better candidate. Those ones have a battery and we could set the battery demand profile to 10% of the electrolyzer rated capacity and then add/set an electricity component demand profile equal to the electrolyzer rated capacity. There is nothing that hydrogen is going to in example 14 which makes it hard to come up with a storage why the storage demand profile would be different than the demand component demand profile.
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.
I made a new example (example 13) and used that to make a demand_demo.md page in the
demanddocs folder.