Skip to content

Port hybrid systems#104

Open
acostarelli wants to merge 4 commits intomainfrom
ac/hybrid
Open

Port hybrid systems#104
acostarelli wants to merge 4 commits intomainfrom
ac/hybrid

Conversation

@acostarelli
Copy link
Copy Markdown
Member

Thanks for opening a PR to PowerOperationsModels.jl, please take note of the following when making a PR:

Check the contributor guidelines

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Remaining comments which cannot be posted as a review comment to avoid GitHub Rate Limit

JuliaFormatter

[JuliaFormatter] reported by reviewdog 🐶


[JuliaFormatter] reported by reviewdog 🐶

con_ub = add_constraints_container!(container, HybridRenewableReserveLimitConstraint, V, names, time_steps; meta = "ub")
con_lb = add_constraints_container!(container, HybridRenewableReserveLimitConstraint, V, names, time_steps; meta = "lb")


[JuliaFormatter] reported by reviewdog 🐶

mult = get_multiplier_value(HybridRenewableActivePowerTimeSeriesParameter(), d, get_formulation(model)())


[JuliaFormatter] reported by reviewdog 🐶

constraint = add_constraints_container!(container, HybridStatusOutOnConstraint, V, names, time_steps)


[JuliaFormatter] reported by reviewdog 🐶

r_up = has_reserves ? get_expression(container, HybridTotalReserveOutUpExpression, V) : nothing


[JuliaFormatter] reported by reviewdog 🐶

constraint = add_constraints_container!(container, HybridStatusInOnConstraint, V, names, time_steps)


[JuliaFormatter] reported by reviewdog 🐶

r_dn = has_reserves ? get_expression(container, HybridTotalReserveInDownExpression, V) : nothing


[JuliaFormatter] reported by reviewdog 🐶

constraint = add_constraints_container!(container, HybridEnergyAssetBalanceConstraint, V, names, time_steps)


[JuliaFormatter] reported by reviewdog 🐶

p_th = haskey(IOM.get_variables(container), VariableKey(HybridThermalActivePower, V)) ?
get_variable(container, HybridThermalActivePower, V) : nothing
p_re = haskey(IOM.get_variables(container), VariableKey(HybridRenewableActivePower, V)) ?
get_variable(container, HybridRenewableActivePower, V) : nothing
p_ch = haskey(IOM.get_variables(container), VariableKey(HybridStorageChargePower, V)) ?
get_variable(container, HybridStorageChargePower, V) : nothing
p_ds = haskey(IOM.get_variables(container), VariableKey(HybridStorageDischargePower, V)) ?
get_variable(container, HybridStorageDischargePower, V) : nothing
load_param = haskey(IOM.get_parameters(container), ParameterKey(HybridElectricLoadTimeSeriesParameter, V)) ?
get_parameter_array(container, HybridElectricLoadTimeSeriesParameter, V) : nothing


[JuliaFormatter] reported by reviewdog 🐶

mult = get_multiplier_value(HybridElectricLoadTimeSeriesParameter(), d, get_formulation(model)())


[JuliaFormatter] reported by reviewdog 🐶

) where {V <: PSY.HybridSystem, W <: AbstractHybridFormulationWithReserves, X <: AbstractPowerModel}


[JuliaFormatter] reported by reviewdog 🐶

constraint = add_constraints_container!(container, HybridReserveAssignmentConstraint, V, names, time_steps;
meta = "$(s_type)_$s_name")


[JuliaFormatter] reported by reviewdog 🐶

) where {V <: PSY.HybridSystem, W <: AbstractHybridFormulationWithReserves, X <: AbstractPowerModel}


[JuliaFormatter] reported by reviewdog 🐶

constraint = add_constraints_container!(container, HybridReserveBalanceConstraint, V, names, time_steps;
meta = "$(s_type)_$s_name")


[JuliaFormatter] reported by reviewdog 🐶

HybridChargingReserveVariable, HybridDischargingReserveVariable)


[JuliaFormatter] reported by reviewdog 🐶

hybrids_with_thermal = [d for d in devices_vec if PSY.get_thermal_unit(d) !== nothing]
hybrids_with_renewable = [d for d in devices_vec if PSY.get_renewable_unit(d) !== nothing]
hybrids_with_storage = [d for d in devices_vec if PSY.get_storage(d) !== nothing]


[JuliaFormatter] reported by reviewdog 🐶

HybridTotalReserveInUpExpression, HybridTotalReserveInDownExpression,


[JuliaFormatter] reported by reviewdog 🐶

HybridServedReserveInUpExpression, HybridServedReserveInDownExpression,


[JuliaFormatter] reported by reviewdog 🐶

HybridServedReserveOutUpExpression, HybridServedReserveOutDownExpression)


[JuliaFormatter] reported by reviewdog 🐶

HybridServedReserveInUpExpression, HybridServedReserveInDownExpression)


[JuliaFormatter] reported by reviewdog 🐶

lazy_container_addition!(container, E, T, PSY.get_name.(hybrids_with_storage), time_steps)


[JuliaFormatter] reported by reviewdog 🐶

add_to_expression!(container, E, HybridDischargingReserveVariable, hybrids_with_storage, model)


[JuliaFormatter] reported by reviewdog 🐶

add_to_expression!(container, E, HybridChargingReserveVariable, hybrids_with_storage, model)


[JuliaFormatter] reported by reviewdog 🐶

add_to_expression!(container, TotalReserveOffering, v, hybrids_with_storage, model)


[JuliaFormatter] reported by reviewdog 🐶

add_to_expression!(container, ActivePowerBalance, ActivePowerInVariable, devices, model, network_model)
add_to_expression!(container, ActivePowerBalance, ActivePowerOutVariable, devices, model, network_model)
add_to_expression!(container, ReactivePowerBalance, ReactivePowerVariable, devices, model, network_model)


[JuliaFormatter] reported by reviewdog 🐶

add_parameters!(container, HybridRenewableActivePowerTimeSeriesParameter, grouped.with_renewable, model)


[JuliaFormatter] reported by reviewdog 🐶

add_parameters!(container, HybridElectricLoadTimeSeriesParameter, grouped.with_load, model)


[JuliaFormatter] reported by reviewdog 🐶

add_constraints!(container, ReactivePowerVariableLimitsConstraint, ReactivePowerVariable,


[JuliaFormatter] reported by reviewdog 🐶

add_constraints!(container, HybridStatusInOnConstraint, devices, model, network_model)
add_constraints!(container, HybridEnergyAssetBalanceConstraint, devices, model, network_model)


[JuliaFormatter] reported by reviewdog 🐶

add_constraints!(container, HybridThermalReserveLimitConstraint, grouped.with_thermal, model, network_model)


[JuliaFormatter] reported by reviewdog 🐶

add_constraints!(container, HybridThermalOnVariableUbConstraint, grouped.with_thermal, model, network_model)
add_constraints!(container, HybridThermalOnVariableLbConstraint, grouped.with_thermal, model, network_model)


[JuliaFormatter] reported by reviewdog 🐶

add_constraints!(container, HybridRenewableActivePowerLimitConstraint, grouped.with_renewable, model, network_model)


[JuliaFormatter] reported by reviewdog 🐶

add_constraints!(container, HybridRenewableReserveLimitConstraint, grouped.with_renewable, model, network_model)


[JuliaFormatter] reported by reviewdog 🐶

add_constraints!(container, HybridStorageBalanceConstraint, grouped.with_storage, model, network_model)


[JuliaFormatter] reported by reviewdog 🐶

add_constraints!(container, StateofChargeTargetConstraint, grouped.with_storage, model, network_model)


[JuliaFormatter] reported by reviewdog 🐶

add_constraints!(container, ReserveCoverageConstraint, grouped.with_storage, model, network_model)
add_constraints!(container, ReserveCoverageConstraintEndOfPeriod, grouped.with_storage, model, network_model)
add_constraints!(container, HybridStorageChargingReservePowerLimitConstraint, grouped.with_storage, model, network_model)
add_constraints!(container, HybridStorageDischargingReservePowerLimitConstraint, grouped.with_storage, model, network_model)


[JuliaFormatter] reported by reviewdog 🐶

add_constraints!(container, HybridStorageStatusChargeOnConstraint, grouped.with_storage, model, network_model)
add_constraints!(container, HybridStorageStatusDischargeOnConstraint, grouped.with_storage, model, network_model)


[JuliaFormatter] reported by reviewdog 🐶

add_constraints!(container, HybridReserveAssignmentConstraint, devices, model, network_model)
add_constraints!(container, HybridReserveBalanceConstraint, devices, model, network_model)


[JuliaFormatter] reported by reviewdog 🐶

) where {T <: PSY.HybridSystem, D <: HybridDispatchWithReserves, S <: AbstractActivePowerModel}


[JuliaFormatter] reported by reviewdog 🐶

add_to_expression!(container, ActivePowerBalance, ActivePowerInVariable, devices, model, network_model)
add_to_expression!(container, ActivePowerBalance, ActivePowerOutVariable, devices, model, network_model)


[JuliaFormatter] reported by reviewdog 🐶

add_parameters!(container, HybridRenewableActivePowerTimeSeriesParameter, grouped.with_renewable, model)


[JuliaFormatter] reported by reviewdog 🐶

add_parameters!(container, HybridElectricLoadTimeSeriesParameter, grouped.with_load, model)


[JuliaFormatter] reported by reviewdog 🐶

) where {T <: PSY.HybridSystem, D <: HybridDispatchWithReserves, S <: AbstractActivePowerModel}


[JuliaFormatter] reported by reviewdog 🐶

add_constraints!(container, HybridStatusInOnConstraint, devices, model, network_model)
add_constraints!(container, HybridEnergyAssetBalanceConstraint, devices, model, network_model)


[JuliaFormatter] reported by reviewdog 🐶

add_constraints!(container, HybridThermalReserveLimitConstraint, grouped.with_thermal, model, network_model)


[JuliaFormatter] reported by reviewdog 🐶

add_constraints!(container, HybridThermalOnVariableUbConstraint, grouped.with_thermal, model, network_model)
add_constraints!(container, HybridThermalOnVariableLbConstraint, grouped.with_thermal, model, network_model)


[JuliaFormatter] reported by reviewdog 🐶

add_constraints!(container, HybridRenewableActivePowerLimitConstraint, grouped.with_renewable, model, network_model)


[JuliaFormatter] reported by reviewdog 🐶

add_constraints!(container, HybridRenewableReserveLimitConstraint, grouped.with_renewable, model, network_model)


[JuliaFormatter] reported by reviewdog 🐶

add_constraints!(container, HybridStorageBalanceConstraint, grouped.with_storage, model, network_model)


[JuliaFormatter] reported by reviewdog 🐶

add_constraints!(container, StateofChargeTargetConstraint, grouped.with_storage, model, network_model)


[JuliaFormatter] reported by reviewdog 🐶

add_constraints!(container, ReserveCoverageConstraint, grouped.with_storage, model, network_model)
add_constraints!(container, ReserveCoverageConstraintEndOfPeriod, grouped.with_storage, model, network_model)
add_constraints!(container, HybridStorageChargingReservePowerLimitConstraint, grouped.with_storage, model, network_model)
add_constraints!(container, HybridStorageDischargingReservePowerLimitConstraint, grouped.with_storage, model, network_model)


[JuliaFormatter] reported by reviewdog 🐶

add_constraints!(container, HybridStorageStatusChargeOnConstraint, grouped.with_storage, model, network_model)
add_constraints!(container, HybridStorageStatusDischargeOnConstraint, grouped.with_storage, model, network_model)


[JuliaFormatter] reported by reviewdog 🐶

add_constraints!(container, HybridReserveAssignmentConstraint, devices, model, network_model)
add_constraints!(container, HybridReserveBalanceConstraint, devices, model, network_model)

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 30, 2026

Performance Results

Version Precompile Time
Main 2.791720029
This Branch 2.780480675
Version Build Time
Main-Build Time Precompile 93.139721003
Main-Build Time Postcompile 14.936840596
This Branch-Build Time Precompile 93.293282646
This Branch-Build Time Postcompile 14.881578305
Version Solve Time
Main-Solve Time Precompile 409.538186946
Main-Solve Time Postcompile 378.418116699
This Branch-Solve Time Precompile 169.564918863
This Branch-Solve Time Postcompile 138.487782779

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Remaining comments which cannot be posted as a review comment to avoid GitHub Rate Limit

JuliaFormatter

[JuliaFormatter] reported by reviewdog 🐶

add_to_expression!(container, ActivePowerBalance, ActivePowerInVariable, devices, model, network_model)
add_to_expression!(container, ActivePowerBalance, ActivePowerOutVariable, devices, model, network_model)


[JuliaFormatter] reported by reviewdog 🐶

add_parameters!(container, HybridRenewableActivePowerTimeSeriesParameter, grouped.with_renewable, model)


[JuliaFormatter] reported by reviewdog 🐶

add_parameters!(container, HybridElectricLoadTimeSeriesParameter, grouped.with_load, model)


[JuliaFormatter] reported by reviewdog 🐶

add_constraints!(container, HybridStatusInOnConstraint, devices, model, network_model)
add_constraints!(container, HybridEnergyAssetBalanceConstraint, devices, model, network_model)


[JuliaFormatter] reported by reviewdog 🐶

add_constraints!(container, HybridThermalReserveLimitConstraint, grouped.with_thermal, model, network_model)


[JuliaFormatter] reported by reviewdog 🐶

add_constraints!(container, HybridThermalOnVariableUbConstraint, grouped.with_thermal, model, network_model)
add_constraints!(container, HybridThermalOnVariableLbConstraint, grouped.with_thermal, model, network_model)


[JuliaFormatter] reported by reviewdog 🐶

add_constraints!(container, HybridRenewableActivePowerLimitConstraint, grouped.with_renewable, model, network_model)


[JuliaFormatter] reported by reviewdog 🐶

add_constraints!(container, HybridRenewableReserveLimitConstraint, grouped.with_renewable, model, network_model)


[JuliaFormatter] reported by reviewdog 🐶

add_constraints!(container, HybridStorageBalanceConstraint, grouped.with_storage, model, network_model)


[JuliaFormatter] reported by reviewdog 🐶

add_constraints!(container, StateofChargeTargetConstraint, grouped.with_storage, model, network_model)


[JuliaFormatter] reported by reviewdog 🐶

add_constraints!(container, ReserveCoverageConstraint, grouped.with_storage, model, network_model)
add_constraints!(container, ReserveCoverageConstraintEndOfPeriod, grouped.with_storage, model, network_model)
add_constraints!(container, HybridStorageChargingReservePowerLimitConstraint, grouped.with_storage, model, network_model)
add_constraints!(container, HybridStorageDischargingReservePowerLimitConstraint, grouped.with_storage, model, network_model)


[JuliaFormatter] reported by reviewdog 🐶

add_constraints!(container, HybridStorageStatusChargeOnConstraint, grouped.with_storage, model, network_model)
add_constraints!(container, HybridStorageStatusDischargeOnConstraint, grouped.with_storage, model, network_model)


[JuliaFormatter] reported by reviewdog 🐶

add_constraints!(container, HybridReserveAssignmentConstraint, devices, model, network_model)
add_constraints!(container, HybridReserveBalanceConstraint, devices, model, network_model)

@acostarelli acostarelli marked this pull request as ready for review May 5, 2026 02:59
@jd-lara jd-lara requested review from kdayday and rodrigomha May 5, 2026 04:19
Copy link
Copy Markdown
Contributor

@rodrigomha rodrigomha left a comment

Choose a reason for hiding this comment

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

We need to add a couple of extra tests.

First, we need to test different attributes, that is using reservation = false, and energy target = true. Also, what happened with storage_reservation and regularization attributes?

Also, it would be good to test that the hybrid system builds when there is no thermal, or no storage, or no renewable.

Finally, it would be good to test when the hybrid does not participate in reserves.

Overall, the PR looks good. Once we have the tests, I will QA/QC the model for the different tests to ensure that the constraints are looking good.

@@ -0,0 +1,1828 @@
#! format: off
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.

I think we can enable format on every line here. The methods are quite large and probably need format anyways

Comment thread src/core/parameters.jl
# Hybrid System Parameters
#################################################################################

"Time-series parameter for the maximum active power available from a hybrid system's
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.

@jd-lara This is a change from how things are currently defined in HSS with the time series attached to the hybrid, not the subcomponent, but I guess that works and is simpler for this case?

Copy link
Copy Markdown
Collaborator

@kdayday kdayday left a comment

Choose a reason for hiding this comment

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

Following Rodrigo's lead on the testing, but in addition, can you port over the updated equivalent of what's in the HybridDispatchWithReserves docstring on the HSS PR and have claude add hyperlinks to exported types and functions referenced in the docstring? It looks like the POM docs are way out of date, but would like to update as we go.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 ports/introduces PSY.HybridSystem support into PowerOperationsModels.jl by adding a new HybridDispatchWithReserves device formulation, its constructor plumbing, and a basic end-to-end test that builds/solves a hybrid co-optimization on RTS-GMLC.

Changes:

  • Adds hybrid formulation types, variables/parameters/expressions/constraints, and objective plumbing for HybridDispatchWithReserves.
  • Adds a two-stage construct_device! implementation for HybridSystems (argument + model stages) and hooks it into the main module includes/exports.
  • Adds test utilities and a new test that builds and solves a model containing a HybridSystem with reserves.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/PowerOperationsModels.jl Includes hybrid model files and exports the new hybrid-related types.
src/hybrid_system_models/hybrid_systems.jl Implements hybrid variable bounds, reserve aggregation, constraints, and objective-cost plumbing.
src/hybrid_system_models/hybridsystem_constructor.jl Adds the two-stage construct_device! pipeline for HybridDispatchWithReserves.
src/core/variables.jl Introduces HybridSystem-specific variable types.
src/core/parameters.jl Introduces HybridSystem time-series parameter types and unit-conversion behavior.
src/core/expressions.jl Adds HybridSystem reserve aggregation/served-reserve expression types.
src/core/constraints.jl Adds HybridSystem constraint type definitions.
src/core/formulations.jl Adds hybrid formulation types and user-facing documentation.
test/test_utils/hybrid_test_utils.jl Adds fixtures for building a test HybridSystem in RTS-GMLC.
test/test_device_hybrid_constructors.jl Adds an integration-style test that builds and solves a hybrid dispatch-with-reserves model.
test/includes.jl Wires hybrid test utilities into the test suite.

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

end
if load_param_container !== nothing && PSY.get_electric_load(d) !== nothing
load_ref = get_parameter_column_refs(load_param_container, name)[t]
JuMP.add_to_expression!(rhs, -load_multiplier[name, t], load_ref)

# Disambiguate against the generic ReserveDemandCurve method in services_models/reserves.jl.
function get_variable_upper_bound(::Type{ActivePowerReserveVariable}, r::PSY.ReserveDemandCurve, d::PSY.HybridSystem, ::Type{<:AbstractReservesFormulation})
return PSY.get_output_active_power_limits(d).max + PSY.get_input_active_power_limits(d).max
add_variables!(container, ActivePowerOutVariable, devices, D)
add_variables!(container, ActivePowerInVariable, devices, D)
_maybe_add_reactive_power_variable!(container, devices, D, S)
add_variables!(container, ReservationVariable, devices, D)
Comment on lines +291 to +294
# PCC ↔ subcomponent plumbing
add_constraints!(container, HybridStatusOutOnConstraint, devices, model, network_model)
add_constraints!(container, HybridStatusInOnConstraint, devices, model, network_model)
add_constraints!(
"""
Energy asset balance: the hybrid's PCC injection equals the sum of subcomponent
injections (thermal + renewable + storage discharge - storage charge - load).
Reserves contribute through their *served* (deployed-fraction) expressions.
Comment on lines +1738 to +1751
time_steps = get_time_steps(container)
variable = get_variable(container, V, D)
for d in devices
sub = accessor(d)
sub === nothing && continue
cost_term = PSY.get_fixed(PSY.get_operation_cost(sub))
cost_term == 0.0 && continue
name = PSY.get_name(d)
for t in time_steps
add_to_objective_invariant_expression!(
container,
cost_term * variable[name, t],
)
end
Comment thread src/core/formulations.jl
Comment on lines +436 to +437
- `"reservation"`: forces the storage subcomponent to operate exclusively on charge or
discharge mode through the entire operation interval.
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