Skip to content

Resource resampling - #893

Open
jaredthomas68 wants to merge 21 commits into
NatLabRockies:developfrom
jaredthomas68:nonannual-resource
Open

jaredthomas68 wants to merge 21 commits into
NatLabRockies:developfrom
jaredthomas68:nonannual-resource

Conversation

@jaredthomas68

@jaredthomas68 jaredthomas68 commented Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator

Resource-resampling compatible with non-annual resource handling

The work in this PR is also included in #887. This PR splits out the resource-specific changes in #887 that are not already addressed in #897.

This PR contains changes up-sampling and down-sampling data to desired time step (using pandas methods)

Section 1: Type of Contribution

  • Feature Enhancement
    • Framework
    • New Model
    • Updated Model
    • Tools/Utilities
    • Other (please describe):
  • Bug Fix
  • Documentation Update
  • CI Changes
  • Other (please describe):

Section 2: Draft PR Checklist

  • Open draft PR
  • Describe the feature that will be added
  • Fill out TODO list steps
  • Describe requested feedback from reviewers on draft PR
  • [-] Complete Section 8: New Model Checklist (if applicable)

TODO:

  • Work with @elenya-grant to merge related work
  • Make resource resampling non-automatic and warn the user

Type of Reviewer Feedback Requested (on Draft PR)

Structural feedback:

Implementation feedback:

  • Thoughts on how resampling is being handled

Other feedback:

Section 3: General PR Checklist

  • PR description thoroughly describes the new feature, bug fix, etc.
  • Added tests for new functionality or bug fixes
  • Tests pass (If not, and this is expected, please elaborate in the Section 6: Test Results)
  • Documentation
    • Docstrings are up-to-date
    • Related docs/ files are up-to-date, or added when necessary
    • Documentation has been rebuilt successfully
    • [-] Examples have been updated (if applicable)
      • Examples will be updated in a later PR demonstrating non-annual and non-hourly timestep along with resampling.
  • CHANGELOG.md
    • At least one complete sentence has been provided to describe the changes made in this PR
    • After the above, a hyperlink has been provided to the PR using the following format:
      "A complete thought. [PR XYZ]((https://github.com/NatLabRockies/H2Integrate/pull/XYZ)", where
      XYZ should be replaced with the actual number.

Section 4: Related Issues

Resolves #651

Section 5: Impacted Areas of the Software

Section 5.1: New Files

  • None.

Section 5.2: Modified Files

  • resource_index.md
    • Documents explicit resource resampling settings and warnings.
  • validation.py
    • Retains the one-year simulation-duration restriction while formatting the seconds-per-year constant.
  • resource_baseclass.py
    • Adds opt-in up/downsampling configuration and resamples resource data before timestep clipping.
  • conftest.py
    • Explicitly selects interpolation and aggregation methods in resource integration-test configurations.
  • test_resource_api_baseclass.py
    • Updates test sampling settings and fixes synthetic timestamps to match the intended intervals.
  • test_time_tools.py
    • Covers resampling behavior, leap-day timestamp handling, opt-in errors, and user warnings.
  • time_tools.py
    • Adds timestep resampling with leap-aware timestamp regeneration, explicit method requirements, and warnings.
  • nlr_developer_wtk_api_models.py
    • Accepts filename lists for multi-year wind resource inputs.

Section 6: Additional Supporting Information

Section 7: Test Results, if applicable

Section 8 (Optional): New Model Checklist

  • Model Structure:
    • Follows established naming conventions outlined in docs/developer_guide/coding_guidelines.md
    • Used attrs class to define the Config to load in attributes for the model
      • If applicable: inherit from BaseConfig or CostModelBaseConfig
    • Added: initialize() method, setup() method, compute() method
      • If applicable: inherit from CostModelBaseClass
  • Integration: Model has been properly integrated into H2Integrate
    • Add the new model to the appropriate __init__.py file to ensure it is properly imported and used in supported_models.py
    • Added to supported_models.py
    • If a new commodity_type is added, update create_financial_model in h2integrate_model.py
  • Tests: Unit tests have been added for the new model
    • Pytest-style unit tests
    • Unit tests are in a "test" folder within the folder a new model was added to
    • If applicable add integration tests
  • Example: If applicable, a working example demonstrating the new model has been created
    • Input file comments
    • Run file comments
    • Example has been tested and runs successfully in test_all_examples.py
  • Documentation:
    • Write docstrings using the Google style
    • Model added to the main models list in docs/user_guide/model_overview.md
      • Model documentation page added to the appropriate docs/ section
      • <model_name>.md is added to the _toc.yml
    • Run generate_class_hierarchy.py to update the class hierarchy diagram in docs/developer_guide/class_structure.md

@johnjasa johnjasa changed the title Non-annual resource handling WIP: Non-annual resource handling Sep 24, 2026

@jaredthomas68 jaredthomas68 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Lots of what I'm seeing looks very similar to what I have, but with an improved interface and excluding the sampling

@jaredthomas68 jaredthomas68 added the ready for review This PR is ready for input from folks label Oct 5, 2026
@jaredthomas68 jaredthomas68 changed the title WIP: Non-annual resource handling Resource resampling Oct 6, 2026
@jaredthomas68
jaredthomas68 marked this pull request as ready for review October 8, 2026 19:06
Comment thread h2integrate/resource/test/test_resource_api_baseclass.py

@elenya-grant elenya-grant left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Hi Jared! Thanks for making this PR! I left a few small comments but nothing major. My main questions is why have resample_resource_data_to_dt() called in process_final_resource_data() instead of in get_data_for_year() before the return statements? I see a few possible benefits to calling resample_resource_data_to_dt in get_data_for_year() instead:

  1. If user provided filenames, then the files can have different timesteps. Does your approach work if part of the time index has a timestep of 1 hour and another part has a time index of 30 minutes?
  2. If a user is running non-consecutive years for a multi-year simulation, then would this still work? Like - what happens with the sampling if the 'year' goes from 2012 to 2011 at hour 8761?

I suppose the downside (pretty big one) is that it couldn't interpolate between years. But - maybe that bit could be done in process_final_resource_data()?

(Also - I'm sorry if this suggestion isn't accurate/makes sense - I'm trying to submit this review by EOD so I haven't taken a deep look at the tests you added yet - but I will later and edit this comment as needed)

I'm going to do a deeper-review shortly but wanted to give you my initial thoughts! thanks for making this PR jared!

Comment thread h2integrate/resource/test/test_resource_api_baseclass.py

API resource data may have a different native timestep from the simulation, but H2I does not resample it by default. You may explicitly choose a pandas interpolation method for upsampling or a pandas aggregation method for downsampling in the resource's `resource_parameters`.

Set `upsample_method` or `downsample_method` in `resource_parameters` to explicitly select the pandas operation. If the data timestep and simulation timestep already match, neither setting is needed. When resampling is requested, H2I emits a warning naming the source timestep, target timestep, direction, and method. If a mismatch requires resampling but no corresponding method is set, H2I raises an error. See {py:func}`h2integrate.resource.utilities.time_tools.resample_resource_data_to_dt` for method details.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

could you add a link to the pandas doc-page for the relevant methods so folks can easily see what sampling methods are available?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done

data, start_timestamp, dt_seconds, n, present_time_keys, exclude_leap_day=False
):
"""Regenerate time columns as ``n`` steps of ``dt_seconds`` starting at a timestamp.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

can you add the Args and Returns parts to this doc string?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done

Comment on lines +44 to +45
- **resource_year_setting** (*str*, optional): How resource years are selected for
API datasets. Options include ``start_year``, ``year_order``, and ``filenames``.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

remove resource_year_setting (probably left-over from merge)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done

dict: resource_data after final processing and checks
"""
# NOTE: we could up/downsample here also
# Remove leap day (if needed) on the native data, resample to the simulation

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

could you update the doctoring for process_final_resource_data to include the resample step?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done

"""
# NOTE: we could up/downsample here also
# Remove leap day (if needed) on the native data, resample to the simulation
# timestep, then clip to the number of timesteps requested by the simulation.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

if you leave this inline comment, can you move "then clip to the number of timesteps requested by the simulation." to the line above clip_data_to_n_timesteps() instead?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I just removed the comment. It is redundant with the doc string

return data


def resample_resource_data_to_dt(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm curious how this works for TMY datasets?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

idk how it works or if it works as intended but it doesnt raise any errors when I tested it...

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This works through the existing exclude_leap_day = spans_leap_year and not has_feb29 check. A TMY whose months come from leap years but has no Feb 29 is treated as "leap day removed", so the regenerated calendar skips it. Year column: for mixed-year TMY (e.g. Jan 2005, Feb 2012, …), the regenerated year column becomes the first sample's year for the whole series. The original per-month years are lost. Anything that relies on the original year labels will no longer see them. I don't think any additional tests are needed.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I also don't think the loss of the per-month years is a problem since we are not using that information anywhere.

@jaredthomas68
jaredthomas68 requested review from elenya-grant and a balanced review from Copilot October 12, 2026 21:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Copilot review overview

5 open findings
What changed in this PR

This PR adds opt-in resource data resampling so resource timeseries can be upsampled/downsampled to the simulation timestep using pandas interpolation/aggregation, including leap-day-aware timestamp regeneration.

Changes:

  • Add resample_resource_data_to_dt utility (with warnings + required explicit method selection) and integrate it into resource final-processing.
  • Update configs/tests/docs to support explicit upsample_method / downsample_method, and expand wind API config to accept filename lists.
  • Minor validation/doc/changelog updates related to timestep handling and resampling behavior.
File Description
h2integrate/​resource/​wind/​nlr_developer_wtk_api_models.py Allow resource_filename to be a list for multi-year wind inputs.
h2integrate/​resource/​utilities/​time_tools.py Implement resampling helper + leap-aware time-column regeneration and warnings.
h2integrate/​resource/​utilities/​test/​test_time_tools.py Add unit tests for resampling behavior, leap-day handling, warnings, and error paths.
h2integrate/​resource/​test/​test_resource_api_baseclass.py Adjust test configs to explicitly set resampling methods; fix synthetic timestamp minutes.
h2integrate/​resource/​test/​conftest.py Ensure integration-test configs explicitly set resampling methods.
h2integrate/​resource/​resource_baseclass.py Wire resampling into process_final_resource_data before clipping and validation.
h2integrate/​core/​inputs/​validation.py Replace magic number with formatted seconds-per-year constant.
docs/​resource/​resource_index.md Document explicit resampling configuration and warning/error behavior.
CHANGELOG.md Add unreleased entry describing resampling capability.

🧠 Review effort: Lite


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread h2integrate/resource/resource_baseclass.py
Comment thread h2integrate/resource/utilities/time_tools.py Outdated
Comment thread h2integrate/resource/wind/nlr_developer_wtk_api_models.py Outdated
Comment thread h2integrate/resource/wind/nlr_developer_wtk_api_models.py Outdated
Comment thread h2integrate/resource/utilities/test/test_time_tools.py
…ntain and use ceiling instead of round for determing the number of timesteps required

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Comment on lines +384 to +386
# Native timestep is taken from the first two samples so a calendar gap (such as a
# removed leap day) does not distort it.
native_dt = (calendar_index[1] - calendar_index[0]).total_seconds()
resource_type: str = field(default="none", init=False)
resource_data: dict | object = field(default={})
resource_filename: Path | str | list = field(default="")
resource_filename: Path | str | list[Path | str] = field(default="")
Comment on lines +307 to +312
periods = n_timesteps
new_index = pd.date_range(start=start_timestamp, periods=periods, freq=freq)
new_index = new_index[~((new_index.month == 2) & (new_index.day == 29))]
while len(new_index) < n_timesteps:
periods += (n_timesteps - len(new_index)) + 1
new_index = pd.date_range(start=start_timestamp, periods=periods, freq=freq)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready for review This PR is ready for input from folks

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants