Skip to content
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

Feature: heat pump cop profiles #393

Merged
merged 19 commits into from
Jul 1, 2024

Conversation

brynpickering
Copy link
Member

Fixes #80 by adding COP profiles.

Builds onchanges made to input weather data in #390.

Checklist

Any checks which are not relevant to the PR can be pre-checked by the PR creator. All others should be checked by the reviewer. You can add extra checklist items here if required by the PR.

  • CHANGELOG updated
  • Minimal workflow tests pass
  • Tests added to cover contribution
  • Documentation updated
  • Configuration schema updated

Copy link
Contributor

@irm-codebase irm-codebase left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor comments.
Things look generally good!

CHANGELOG.md Outdated Show resolved Hide resolved
config/default.yaml Show resolved Hide resolved
rules/heat.smk Outdated Show resolved Hide resolved
scripts/heat/heat_pump_cop.py Show resolved Hide resolved
scripts/heat/heat_pump_cop.py Outdated Show resolved Hide resolved
envs/default.yaml Show resolved Hide resolved
@timtroendle
Copy link
Member

I am confused. Why are you intending to merge into the feature-hot-water branch? Which PR to check first? I will now have a look at the feature-hot-water PR.

Base automatically changed from feature-hot-water to develop June 9, 2024 13:34
@brynpickering
Copy link
Member Author

I am confused. Why are you intending to merge into the feature-hot-water branch? Which PR to check first? I will now have a look at the feature-hot-water PR.

To reduce the diff that you would be reviewing. Sorry for pushing this before the hot water one was ready! I thought it would be nice to have it online so that you could review it while I was fixing up review comments on the hot water PR.

Copy link
Contributor

@irm-codebase irm-codebase left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

envs/default.yaml Show resolved Hide resolved
CHANGELOG.md Outdated Show resolved Hide resolved
Copy link
Member

@timtroendle timtroendle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great.

In the sake of implementing features that can be tested right away, would it possibly to extend this PR to actually building in the heat pump? It seems to me that only a few minor things are missing so that we'd actually have heat pumps, right?

CHANGELOG.md Outdated Show resolved Hide resolved
config/default.yaml Outdated Show resolved Hide resolved
config/default.yaml Outdated Show resolved Hide resolved
config/schema.yaml Outdated Show resolved Hide resolved
scripts/heat/heat_pump_cop.py Outdated Show resolved Hide resolved
scripts/heat/heat_pump_cop.py Outdated Show resolved Hide resolved
scripts/heat/heat_pump_cop.py Outdated Show resolved Hide resolved
scripts/heat/heat_pump_cop.py Outdated Show resolved Hide resolved
scripts/heat/heat_pump_cop.py Outdated Show resolved Hide resolved
assert (
cop_summer > cop_winter
).all(), "Found higher heating COP values in winter than in summer."
assert (cop >= 1).all(), "Found improbably low heat pump COP values (< 1)."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These would make great unit tests. I would move them there.

Copy link
Member Author

@brynpickering brynpickering Jun 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pros of moving to unit tests:

  • more clear grouping of our tests
  • availability of pytest infrastructure

cons:

  • you don't find out something is a problem until the entire workflow has been completed (should we instead be aiming for fast failure...?)
  • the test is far removed from the code that would cause test failure
  • (in this example) the test would come after the data aggregation. I'd fix this by moving the aggregation of gridcells to IDs into the next rule in the process, so this rule's outputs can be fed into a test, but then the output files are quite large.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is a more high level question of when to test in a snakemake workflow. I'm have implemented the change locally but do wonder whether the cons outweigh the pros...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see your point and I agree this may require a high-level discussion. I just want to add another important pro: you can test many more things and, because it's pytest, quite easily. For example, you can execute each test by country.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In terms of code modularity, it would be good to have tests (especially unit tests) closer to the actual scripts. In theory we could have a test runner per "module" (== ".smk" file), but it would add overhead.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved although I maintain that for intermediate datasets it is better to do the checks as part of the script. For final model files, we can then have tests. This stops us having an overcomplicated DAG where loads of intermediate data files need to be kept around until the penultimate step.

@brynpickering
Copy link
Member Author

would it possibly to extend this PR to actually building in the heat pump? It seems to me that only a few minor things are missing so that we'd actually have heat pumps, right?

Do you mean extending to having the electricity demand from heating be based on the when2heat profile & heat pump COP? Or do you mean having heat pumps as a templated technology option?

@timtroendle
Copy link
Member

Either of the two, but it seems the latter makes more sense. It wouldn't be a big effort, would it?

It's just so that this PR is not just some unconnected data processing step that'll make sense only after other PRs are merged but a feature that can be integrated and used right away.

@brynpickering
Copy link
Member Author

@timtroendle I have:

  1. added a heat pump supply template.
  2. processed the heat pump outputs into a COP timeseries file.
  3. moved data checks to tests.

It was a bit more work than expected ;)

I also found it a pain to debug tests as you can't drop into the debugger with breakpoint() in the same way as with the other scripts. It requires adding --pdb to the pytest call. I've added functionality to allow that in the test rule/runner.

@brynpickering
Copy link
Member Author

Failing tests are due to too many requests to access either the miniconda .sh file or a cached version of it. Not sure how we resolve that.

@@ -188,30 +189,39 @@ parameters:
nuts-year: 2013
heat:
space_heat:
carnot-performance: 0.36 # [Nouvel_2015]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor cleanup here to remove some unused configs and update refs (still more to do)

type: number
description: Heat pump coefficient of performance.
water_heat:
hot_water:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

water_heat -> hot_water applied everywhere for consistency


# Weather data is subset by the geographic area covered by model run (given by available population sites)
temperature_ds = xr.open_dataset(path_to_temperature).sel(site=population.site)
wind_ds = xr.open_dataset(path_to_wind_speed).sel(site=population.site)
temperature_ds = xr.open_dataset(path_to_temperature)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer doing pre-selection of sites so that this step is resolution agnostic


grouped_hourly_heat = xr.merge([grouped_hourly_space, grouped_hourly_water])
grouped_hourly_heat.to_netcdf(out_path)
grouped_hourly_heat = xr.merge([
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved sanity checks to tests

Copy link
Member

@timtroendle timtroendle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome!

A few more minor changes. More importantly: updates to the docs are changing. There should be updates here:
https://euro-calliope.readthedocs.io/en/latest/model/customisation/#importing-modules

In the sake of modularity, shouldn't we split the heat yaml template into several files? I tend to think yes. Also, I would add additional techs in additional PRs, together with the respective carriers they require.

@@ -0,0 +1,25 @@
def test_cop_monthly(cop_timeseries):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you could do that per location. Would that be useful?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's effectively being done per location anyway, right? If the test fials, you'd be able to pinpoint the offending location pretty quickly when debugging...

tests/model/test_heat.py Show resolved Hide resolved


if __name__ == "__main__":
gridded_data = xr.open_dataset(snakemake.input.gridded_timeseries_data)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First use of this approach in the repository. The line will make history. :D

Args:
gridded_data (xr.Dataset): Gridded timeseries space heat and hot water data.
grid_weight (xr.DataArray): Weighted mapping from grid (a.k.a. "site") to units.
annual_demand (xr.DataArray): Per-unit annual space heating and hot water demands.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find it hard to understand why this input is necessary. Can you explain? Is this a bias-correction? Or is it because the time series have no meaningful scale / are normed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's doing two sets of weighted averages: weighted averaging "sites" into "IDs" (using population) and weighted averaging "space heat" and "hot water" demand into a general profile for "heat" (using annual demands).

pd.DataFrame: Index: timeseries, Columns: unit IDs
"""

partial_func = partial(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe rename function to "apply_weights" or similar?

monetary:
# TODO: weight averaging per region based on dwelling ratio
energy_cap: {{ mean([5.9 / 10, 5.9 / 8, 76 / 400, 45 / 160]) * 1e6 * scaling_factors.specific_costs }} # {{ (1 / scaling_factors.specific_costs) | unit("EUR2015/MW_heat") }} | average of new/existing and single-/multi-family homes.
om_annual: {{ mean([0.42 / 10, 0.42 / 10, 1.343 / 400, 0.889 / 160]) * 1e6 * scaling_factors.specific_costs }} # {{ (1 / scaling_factors.specific_costs) | unit("EUR2015/MW_heat/year") }} | average of new/existing and single-/multi-family homes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above.

monetary:
# TODO: weight averaging per region based on dwelling ratio
energy_cap: {{ (mean([7.6 / 10, 5 / 4, 114 / 400, 57 / 160]) * heat_pump_shares.ashp + mean([12 / 10, 9 / 4, 202 / 400, 72 / 160]) * heat_pump_shares.gshp) * 1e6 * scaling_factors.specific_costs }} # {{ (1 / scaling_factors.specific_costs) | unit("EUR2015/MW_heat") }} | average of new/existing and single-/multi-family homes | weighted by assumed share of installed GSHP vs ASHP.
om_annual: {{ (mean([0.222 / 10, 0.222 / 4, 0.761 / 400, 0.761 / 160]) * heat_pump_shares.ashp + mean([0.222 / 10, 0.222 / 4, 0.761 / 400, 0.761 / 160]) * heat_pump_shares.gshp) * 1e6 * scaling_factors.specific_costs }} # {{ (1 / scaling_factors.specific_costs) | unit("EUR2015/MW_heat/year") }} | average of new/existing and single-/multi-family homes | weighted by assumed share of installed GSHP vs ASHP.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All three comments from above apply here too.

name: Heat pump
parent: conversion
carrier_in: electricity
carrier_out: hp_heat
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's this carrier necessary for? For the different temperature level?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the storage buffer to be specific to that technology

lifetime: 30
costs:
monetary:
# TODO: weight averaging per region based on dwelling ratio
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And again all three comments apply.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In terms of modularity, should this file include only heat pumps? I tend to think yes. Also, I'd prefer to include other supply technologies in other PRs, together with their carrier supplies which seem missing in this PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, sorry. A staging error. I'll revert the non-electricity ones. I'd maybe do it per type of carrier in?

@timtroendle
Copy link
Member

I wouldn't worry too much about the failing tests. @setup-miniconda's tests are failing, too, so I am assuming they will have this high up on their agenda.

@timtroendle
Copy link
Member

👆Oops, I wanted to suggest edits and now I've committed them. Must have missed the "suggest" option, sorry.

* Split out heat tech templates
* Remove boilers from heat techs
* Add locations to heat tech template
* Update DEA references
* Improve commenting for heat tech cost calcs
* Rearrange gridded timeseries script for clarity
@brynpickering
Copy link
Member Author

brynpickering commented Jun 28, 2024

@timtroendle I think that's all done now. I've opened various issues for things that need to be resolved off the back of this PR.

EDIT: nvm, still need to update docs with new file and fix a docs issue.

Copy link
Member

@timtroendle timtroendle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

Just one, I think important, comment: there is a dependency between the "storage/heat" and "supply/heat" files now which should not be the case. By moving the overrides to the "storage/heat" you should be able to get rid of the dependency. The only remaining dependency are the carrier definitions and we will not and don't have to get rid of those.

electric_heater:
{% endfor %}

overrides:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These overrides should be in the templates/models/techs/storage/heat.yaml, right? Otherwise we are creating a dependency between these two files.

I mean, this will break if users don't import the file with the storage techs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also: consider adding these overrides to tests/resources/test.yaml.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're creating a dependency either way as they also influence the output carrier of the supply technology. I think they are better off in supply/heat-from-electricity.yaml as the storage techs are actually abstract tech groups so there's nothing wrong with including that YAML file as an input by default.

As an aside: this won't be an issue in v0.7 as we can trigger a storage buffer for these kinds of techs when defining them, so I wouldn't put too much weight on whether we've got it perfect right now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok up to you. I guess you have the better overview.

I just think we should try to minimise dependencies between files as much as possible. As far as I can tell, this would be the first dependency between two files. It's also slightly surprising that storage gets added through a file in "supply" instead of "storage".

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I've decoupled them now, although this does add a bit of unnecessary model complexity when not using the storage buffers. Since this will all be swept away with v0.7 that complexity is probably fine for now.

@brynpickering brynpickering merged commit c78a5b1 into develop Jul 1, 2024
24 checks passed
@brynpickering brynpickering deleted the feature-heat-pump-cop-profiles branch July 1, 2024 14:30
@brynpickering brynpickering mentioned this pull request Jul 2, 2024
5 tasks
jnnr pushed a commit to jnnr/euro-calliope that referenced this pull request Aug 27, 2024
…-heat-pump-cop-profiles

Feature: heat pump cop profiles
jnnr pushed a commit to jnnr/euro-calliope that referenced this pull request Aug 27, 2024
…-heat-pump-cop-profiles

Feature: heat pump cop profiles
jnnr pushed a commit to jnnr/euro-calliope that referenced this pull request Sep 3, 2024
…-heat-pump-cop-profiles

Feature: heat pump cop profiles
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Model heat pumps as timeseries-influenced supply technology
3 participants