Skip to content

Nonannual - #936

Open
jaredthomas68 wants to merge 38 commits into
nonannual-frameworkfrom
nonannual
Open

jaredthomas68 wants to merge 38 commits into
nonannual-frameworkfrom
nonannual

Conversation

@jaredthomas68

@jaredthomas68 jaredthomas68 commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

Enable non-annual solar, storage, grid, and demand simulations

Adapt the models used by the solar/battery/grid example to support sub-annual and multi-year
simulation horizons and non-hourly timesteps. The updated models explicitly declare compatible
simulation durations and use the shared annualization helpers introduced by the framework PR (#934).

PySAM solar supports timesteps from 15 minutes to 4 hours and computes projected annual
production/capacity factors for non-annual horizons without relying on PySAM's unavailable
ac_annual output. The existing annual-output path is retained for a nominal one-year run.
Grid and demand models project annual production and capacity factors across plant life;
grid costs annualize per-timestep purchases and sales. Storage dispatch converts between rates
and stored amounts using the timestep, and storage outputs distinguish net capacity factor from
the discharge-only standard capacity factor.

Example 24 now runs three configurations and prints a comparison of energy, capacity factors,
load service, curtailment, and LCOE:

Variant Simulation Duration Timestep Steps Resource Processing
half_year 0.5 nominal years 15 minutes 17,520 Time interpolation, then clipping
annual 1 nominal year 1 hour 8,760 Native hourly data
two_year 2 nominal years 3 hours 5,840 Mean aggregation of 2012/2013 data

This is PR #936, the third PR in the
nonannual-resource -> nonannual-framework -> nonannual stack. It depends on
PR #934 and is reviewed relative to
nonannual-framework. Resource resampling belongs to
PR #933; shared duration validation,
annualization helpers, and ProFAST conversions belong to #934. This PR applies those capabilities
to concrete models and demonstrates them together.

Section 1: Type of Contribution

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

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): N/A; no new model.

TODO:

  • Step 1: Adapt models, update Example 24, and add non-annual regression/integration coverage.
  • Step 2: Verify model tests, both Example 24 tests, all three demo variants, and a docs build.

Type of Reviewer Feedback Requested (on Draft PR)

Structural feedback:

Implementation feedback: PySAM non-annual output handling, grid costs and annual energy,
demand rated-production/zero-demand handling, and timestep-aware storage charge/discharge limits.

Other feedback: Clarity of Example 24's three variants, reported storage metrics, and the
distinction between simulated-horizon energy and projected annual financial inputs.

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: N/A; example-led changes.
    • Documentation has been rebuilt successfully
    • Examples have been updated (if applicable)
  • 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.

New-model checklist: N/A. The checked test item refers to the local
checks in Section 7; full repository CI and remote documentation checks remain unverified.

Section 4: Related Issues

Related to #204 through concrete model
support for variable simulation durations. This PR enables the solar/battery/grid workflow;
it does not claim all models now support arbitrary durations or independent timesteps.

Depends on PR #934, which in turn depends
on PR #933.

Section 5: Impacted Areas of the Software

Section 5.1: New Files

  • resource_files/solar/30.6617_-101.7096_psmv3_60_2014.csv: Add hourly solar resource data for
    the extended solar regression scenarios.

Section 5.2: Modified Files

  • examples/24_solar_battery_grid/plant_config.yaml: Update resource and simulation settings
    used by the non-annual demonstration.
  • examples/24_solar_battery_grid/run_solar_battery_grid.py: Run half-year, annual, and two-year
    variants with isolated configuration copies and print a comparison table.
  • examples/test/test_all_examples.py: Extend annual storage checks and add an end-to-end
    non-annual Example 24 test.
  • h2integrate/converters/solar/solar_pysam.py: Widen timestep/duration bounds and compute
    non-annual production/capacity factors using the shared framework projection.
  • h2integrate/converters/solar/atb_utility_pv_cost.py: Declare compatible non-annual durations
    and 15-minute to 4-hour timesteps.
  • h2integrate/converters/solar/test/test_pysam_solar.py: Add non-annual PySAM horizon coverage.
  • h2integrate/converters/grid/grid.py: Widen model bounds and annualize production, sales,
    purchase costs, and sale revenues across plant life.
  • h2integrate/demand/demand_baseclass.py: Use peak demand as rated production, handle zero
    demand, and project annual production/capacity factors with commodity-unit conversion.
  • h2integrate/demand/generic_demand.py: Opt the generic demand component into non-annual runs.
  • h2integrate/storage/storage_baseclass.py: Extend timestep bounds and project net versus
    discharge-only capacity factors and annual discharge.
  • h2integrate/storage/storage_performance_model.py: Opt into non-annual runs and permit
    timesteps up to one day.
  • h2integrate/storage/battery/atb_battery_cost.py: Widen timestep and duration compatibility
    for the capacity/rate-based battery cost model.
  • h2integrate/control/control_strategies/storage/demand_openloop_storage_controller.py: Opt
    into non-annual runs and use timestep-aware stored amounts and charge/discharge limits.
  • h2integrate/transporters/cable.py: Declare non-annual duration compatibility.
  • h2integrate/transporters/generic_combiner.py: Declare non-annual duration compatibility.
  • h2integrate/resource/utilities/time_tools.py: Clarify explicit interpolation/aggregation
    method examples in the resampling docstring.
  • h2integrate/resource/utilities/test/test_time_tools.py: Extend resampling test compatibility.

Section 6: Additional Supporting Information

  • Compatibility: Only models explicitly opted in support non-annual runs. Other models retain
    the framework's annual-only fallback; duration support does not override timestep bounds.
  • Annualization: Nominal 8760-hour years are used for framework projection and financial
    inputs. Resource calendar-year length and leap-day handling remain separate concerns.
  • Energy accounting: Simulated totals integrate rates over dt. Projected annual outputs
    use the inherited framework helpers rather than treating the entire simulated horizon as
    a single repeated year.
  • Storage metrics: capacity_factor reflects net contribution, while
    standard_capacity_factor reflects discharge-only storage service.
  • Resources: Resampling remains opt-in. Example 24 explicitly selects time interpolation
    for the half-year variant and mean aggregation for the two-year variant.

Section 7: Test Results, if applicable

Local verification on nonannual:

  • Model regressions: 137 tests and 335 subtests passed, covering PySAM solar, grid, demand,
    storage performance/sizing, battery costs, open-loop storage control/integration, and
    resource timestep utilities.
  • Example 24 annual and non-annual integration tests: 2 tests and 6 subtests passed.
  • The standalone demo completed all three variants:
Variant LCOE ($/kWh)
half_year 0.0866
annual 0.0893
two_year 0.0917

Verification commands:

Section 8 (Optional): New Model Checklist

N/A; existing models are adapted, with no new registered model. The checklist is retained.

  • Model Structure:
    • Follows established naming conventions outlined in docs/developer_guide/coding_guidelines.md
    • Configuration class is named {ModelName}Config, where {ModelName} is the complete model class name registered with @register
    • 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 PerformanceModelBaseClass and CostModelBaseClass
  • Integration: Model has been properly integrated into H2Integrate
    • Add the new model to the appropriate __init__.py file so it can be imported from its subpackage
    • Decorated the model class with @register from h2integrate/core/supported_models.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 documentation page added to the appropriate docs/ section
      • Path to model documentation page is added to the _toc.yml
    • Update the class hierarchy diagram docs/_static/class_hierarchy.html by doing one of the below options:
      • Navigate to docs folder and run sh build_book.sh
      • Run generate_class_hierarchy.py to update the class hierarchy diagram in docs/developer_guide/class_structure.md. You may need to delete the _autosummary folder before doing this.

@jaredthomas68
jaredthomas68 added this pull request to stack #935 October 14, 2026 15:49
@jaredthomas68
jaredthomas68 marked this pull request as ready for review October 14, 2026 18:43
@jaredthomas68 jaredthomas68 self-assigned this Oct 14, 2026
@jaredthomas68 jaredthomas68 added the ready for review This PR is ready for input from folks label Oct 14, 2026
@coveralls

coveralls commented Oct 15, 2026 •

Copy link
Copy Markdown

Coverage Status

Coverage is 90.68% — nonannual into nonannual-framework. No base build found for nonannual-framework.

@@ -512,16 +511,35 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs):
)

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.

Preliminary thoughts I wanted to share real quick (I've only looked at this file):

I've thought about how the multi-year resource could work with pysam solar when it is using lifetime outputs - and I think I attempted here. Should we enable that? Or throw an error if it's using lifetime performance (includes degradation) with multi-year resource?

Additionally, I've noticed that PySAM solar will remove a leap day even if it's included in the resource data. If the resource data includes leap day data (suppose it's 8784), then the pysam output data will be 8760. I'm curious about how this should be handled - because it someone has a simulation length of 8784 then the fraction of year simulated will be > 1, but the pysam output profile would only be 8760. Also - if pysam removes the leap day, I think we should see if it removes the actual leap day or if it removes the last day of the resource data.

3600,
3600,
900,
14400,

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 pysam solar really be run with 4 hour periods?

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.

3 participants