Skip to content

Remove timeslice capacity factors from annual planned maintenance con… - #189

Merged
edwardxtg merged 7 commits into
mainfrom
cap-add-b
Sep 1, 2026
Merged

edwardxtg merged 7 commits into
mainfrom
cap-add-b

Conversation

@djwels

@djwels djwels commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Description

  • Simple PR removing sub-annual capacity factor parameter from capacity_adequacy_b constraint
  • Previously if a non-default (i.e. non-100%) sub-annual CF was being set along with the annual availability factor, a generator couldnt reach its capacity factor because the adequacy_b constraint was scaling CF (summed across timeslices) by the availability factor.
  • This was leading to infeasibilities in the Japan and India models where geothermal and bioenergy/waste plants had capacity factor profiles and non-default (i.e. < 100%) availability factors

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.

Pull request overview

This PR updates the Capacity Adequacy B (planned maintenance) constraint so that annual planned-maintenance availability (AvailabilityFactor) no longer scales a timeslice-weighted sum of sub-annual capacity factors, aligning the implementation with the documented OSeMOSYS constraint and preventing infeasibilities when both sub-annual CF profiles and non-100% availability are provided.

Changes:

  • Removes the sub-annual CapacityFactor * YearSplit aggregation from the RHS of CAb1_PlannedMaintenance.
  • Simplifies the RHS to use annual capacity (GrossCapacity) scaled only by AvailabilityFactor and CapacityToActivityUnit.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tz/osemosys/model/constraints/capacity_adequacy_b.py Outdated
Comment thread tz/osemosys/model/constraints/capacity_adequacy_b.py Outdated

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.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

tz/osemosys/model/constraints/capacity_adequacy_b.py:42

  • con is split across lines without wrapping parentheses, so line 40 starts a new (discarded) expression. As a result, con only contains the LHS sum and the constraint comparison (<= 0) is not part of the assignment, which will break or misbuild the constraint in m.add_constraints. Wrap the full expression in parentheses so the subtraction and comparison are included in con.
    con = (lex["RateOfTotalActivity"] * ds["YearSplit"]).sum(dims="TIMESLICE") 
    - (lex["GrossCapacity"]
        * ds["AvailabilityFactor"]
        * ds["CapacityToActivityUnit"]) <= 0

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.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

tz/osemosys/model/constraints/capacity_adequacy_b.py:41

  • This change alters the planned-maintenance (AvailabilityFactor) constraint semantics and fixes a known infeasibility case, but there’s no regression test exercising the combination of (a) sub-annual CapacityFactor profile and (b) AvailabilityFactor < 1. Without a solve-level test, it’s easy for future refactors to reintroduce the double-scaling bug.

Consider adding an integration test that builds a 2-timeslice model with a technology that has CapacityFactor < 1 in at least one timeslice and AvailabilityFactor < 1, and asserts the model remains feasible and that annual activity respects the AvailabilityFactor bound (i.e., LHS <= GrossCapacity * AvailabilityFactor * CapacityToActivityUnit).

    mask = ds["AvailabilityFactor"] < 1
    con = (lex["RateOfTotalActivity"] * ds["YearSplit"]).sum(dims="TIMESLICE") - (
        lex["GrossCapacity"] * ds["AvailabilityFactor"] * ds["CapacityToActivityUnit"]
    ) <= 0

@djwels
djwels requested a review from abhishek0208 July 28, 2026 14:44
abhishek0208
abhishek0208 previously approved these changes Aug 27, 2026
Resolves the conflict in capacity_adequacy_b.py: keeps this branch's RHS
(sub-annual CapacityFactor removed from CAb1_PlannedMaintenance) with
main's renamed linopy kwarg from #191 (.sum(dims=) -> .sum(dim=)).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Covers technology-level AvailabilityFactor, which previously had no test
coverage at all (only trade-level AF was exercised).

- test_availability_factor_does_not_scale_sub_annual_capacity_factor pins the
  behaviour this branch changes: with a CF profile averaging 0.8 and AF 0.9,
  annual activity reaches 10 * min(0.9, 0.8) == 8.0. Under the previous
  formulation the two were multiplied, capping activity at 7.2 and making it
  impossible for a technology to reach its own capacity factor when AF < 1.
- test_availability_factor_still_binds_when_below_capacity_factor pins the
  other side of the min (flat CF 1.0, AF 0.6 -> 6.0), guarding against the
  reading that dropping CapacityFactor from CAb1 made AvailabilityFactor inert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@edwardxtg edwardxtg 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.

thanks for this.

Just added a couple tests for this behaviour.

@edwardxtg
edwardxtg merged commit 2c95ef3 into main Sep 1, 2026
3 checks passed
@edwardxtg
edwardxtg deleted the cap-add-b branch September 1, 2026 13:32
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.

4 participants