Skip to content

Update MT-HVDC quadratic converter model#103

Open
acostarelli wants to merge 9 commits into
mainfrom
ac/hvdc
Open

Update MT-HVDC quadratic converter model#103
acostarelli wants to merge 9 commits into
mainfrom
ac/hvdc

Conversation

@acostarelli
Copy link
Copy Markdown
Member

No description provided.

Comment thread src/mt_hvdc_models/hvdcsystems_constructor.jl Outdated
Comment thread test/test_device_hvdc.jl Outdated
@test solve!(model) == IOM.RunStatus.SUCCESSFULLY_FINALIZED
end

@testset "HVDC System with Losses Network (QuadraticLossConverter NLP)" begin
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.

We should add a test that have both together and check that they are in the same order of magnitude for current and dc power probably.

You can run the same system and run without linear loss for both.

@rodrigomha rodrigomha changed the title Cleaner HVDC Update MT-HVDC quadratic converter model Apr 29, 2026
@acostarelli acostarelli marked this pull request as ready for review April 30, 2026 15:34
@jd-lara jd-lara requested review from Copilot and removed request for luke-kiernan April 30, 2026 15:52
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

Updates the MT-HVDC quadratic-loss converter modeling to support both a MIP-friendly Bin2/PWL approximation and an exact (nonlinear) formulation, and expands tests to validate both paths.

Changes:

  • Introduces AbstractQuadraticLossConverter with two concrete formulations: Bin2QuadraticLossConverter (PWL/SOS2+Bin2) and QuadraticLossConverter (exact bilinear/quadratic, NLP-capable).
  • Refactors MT-HVDC converter construction/constraints to use shared bilinear/quadratic expression builders instead of bespoke “squared”/interpolation variables.
  • Updates HVDC tests and dependency source pins to exercise both the MIP and NLP variants and compare objective agreement.

Reviewed changes

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

Show a summary per file
File Description
test/test_device_hvdc.jl Updates the lossy HVDC test to use Bin2 formulation; adds NLP test and an objective agreement check.
test/Project.toml Updates [sources] URLs and removes the InfrastructureOptimizationModels source pin.
src/operation/emulation_model.jl Changes warning message interpolation to use model.name.
src/operation/decision_model.jl Changes warning message interpolation to use model.name.
src/mt_hvdc_models/hvdcsystems_constructor.jl Refactors converter construction; adds Bin2-based model build stage and reworks QuadraticLossConverter stage to use IOM approximation helpers.
src/mt_hvdc_models/HVDCsystems.jl Generalizes quadratic-loss converter support; adds helper functions and updates loss/current constraints to use new expressions.
src/core/variables.jl Removes legacy HVDC squared/aux/interpolation variable type definitions.
src/core/formulations.jl Adds AbstractQuadraticLossConverter and new Bin2QuadraticLossConverter; updates QuadraticLossConverter docstring/intent.
src/core/constraints.jl Removes legacy converter power/McCormick/interpolation constraint type definitions.
src/PowerOperationsModels.jl Updates exports to reflect removed legacy HVDC variable types; exports new converter formulation types.
Project.toml Moves/updates [sources] to Sienna-Platform URLs and pins InfrastructureOptimizationModels to main; tightens compat formatting.

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

logger = IS.configure_logging(get_internal(model), IOM.PROBLEM_LOG_FILENAME, file_mode)
if store_system_in_results
@warn "store_system_in_results for $(model) is set to true. This will do nothing unless a Simulation is being built."
@warn "store_system_in_results for $(model.name) is set to true. This will do nothing unless a Simulation is being built."
Comment thread src/operation/decision_model.jl
Comment thread src/operation/emulation_model.jl
Comment thread src/operation/emulation_model.jl
Comment thread test/Project.toml Outdated
Comment thread src/PowerOperationsModels.jl
@acostarelli acostarelli removed the request for review from jd-lara April 30, 2026 18:19
@acostarelli acostarelli marked this pull request as draft April 30, 2026 18:19
combine ModelConstruct stages for both formulations
only add linear loss formulations for devices with non-zero linear loss
Comment thread src/mt_hvdc_models/hvdcsystems_constructor.jl Outdated
Comment thread src/mt_hvdc_models/hvdcsystems_constructor.jl Outdated
Comment thread src/mt_hvdc_models/hvdcsystems_constructor.jl Outdated
@acostarelli acostarelli marked this pull request as ready for review May 12, 2026 18:53
@jd-lara jd-lara requested a review from rodrigomha May 18, 2026 04:51
* Add HVDCTwoTerminalVSC two-terminal HVDC formulation

Add two new formulations for PSY.TwoTerminalVSCLine: HVDCTwoTerminalVSC (NLP)
and HVDCTwoTerminalVSCBin2 (SOS2 MILP). Both model independent active/reactive
power control per terminal under a PQ capability circle, quadratic+linear
converter losses, and explicit cable resistance via v_f - v_t = (1/g)*I.
Reactive variables/constraints are added only on full AC networks.

Refactor shared converter-loss helpers (loss expression builder, abs-value
decomposition, linear-loss detection) out of mt_hvdc_models into
src/common_models/quadratic_converter_loss.jl so the multi-terminal and
two-terminal VSC paths reuse the same primitives. Rename the
ConverterPositiveCurrent / NegativeCurrent / CurrentDirection variable types
to generic PositiveCurrent / NegativeCurrent / CurrentDirection.

Point Project.toml at the InfrastructureOptimizationModels ac/hvdc-vsc
branch (which carries the per-device-bounds API used by these formulations)
and restore runtests.jl to run the full test list.

* Address PR #119 review on HVDCTwoTerminalVSC

- Rename Bin2 → MIP for VSC + IPC formulations and update tests
- Make HVDCReactivePowerVariable parametric on From/To with const aliases
- Wire HVDCReactivePower{From,To}Variable into ReactivePowerBalance on AC
- Fix g==0 cable Ohm's law to force I==0 (open circuit, not v_f == v_t)
- Add axis-aligned half-spaces to the MIP PQ-capability octagon
- Default use_linear_loss=true for MIP and false for NLP via
  get_default_attributes; warn when NLP runs with use_linear_loss=true
- Collapse abs-value decomposition into a single ModelConstructStage helper
- Skip zero-coefficient terms in the quadratic converter loss expression
- Fix docstring sign convention on HVDCVSCConverterPowerConstraint
- Drop brittle log-file-regex linear-loss warning test

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

* Address PR #119 Review 2 on HVDCTwoTerminalVSC

- Add HVDCTwoTerminalVSCLP formulation that owns the octagonal linear
  outer-approximation of the per-terminal PQ disk; document the octagon
  geometry and its tightness tradeoffs vs the PWL path.
- Refactor HVDCTwoTerminalVSC (NLP) and HVDCTwoTerminalVSCMIP (PWL) to
  share a single PQ-capability add_constraints! method that writes
  `p_sq + q_sq <= s^2` over IOM._add_quadratic_approx! expressions.
- Collapse the HVDCReactivePowerVariable{From}/{To} add_to_expression
  methods into one parametric method via a new `_vsc_arc_bus` helper;
  collapse `_vsc_v_from_bounds`/`_vsc_v_to_bounds` similarly.
- Dispatch `_quadratic_converter_loss_expr` on i_sq_t (QuadExpr vs
  AffExpr) and accumulate via JuMP.add_to_expression!; degrade to
  AffExpr when a == 0 even on the QuadExpr branch.
- Drop the explicit-type-check MINLP warnings on both VSC and MT HVDC
  paths; leave brief comments in their place.
- Document the use_linear_loss defaults on all four
  get_default_attributes methods.
- Reorder construct_device! to add the abs-value decomposition variables
  before the constraints that consume them (PositiveCurrent /
  NegativeCurrent were previously requested before they were added).
- Replace `filter(_has_linear_loss, devices)` with a comprehension so
  it works on FlattenIteratorWrapper as well as Vector.
- Omit the VSCMIP/VSCLP on ACPPowerModel build tests: HiGHS cannot
  handle the ACP network's trigonometric branch constraints.

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

* Address PR #119 Review 3 on HVDCTwoTerminalVSC

- Drop parametric From/To types in favor of two concrete reactive-power
  variable structs (HVDCReactivePowerFromVariable / ToVariable); unroll the
  side-parametric _vsc_* accessors into two named methods each.
- Rename HVDCTwoTerminalVSCMIP -> HVDCTwoTerminalVSCMILP (forward-looking
  for future MINLP support) and HVDCVSCReactiveCapabilityConstraint ->
  HVDCVSCApparentPowerLimitConstraint with a docstring covering NLP/MILP/LP
  paths accurately.
- Split _add_abs_value_decomposition! into separate variables (ArgumentStage)
  and constraints (ModelStage) helpers; move the variable-creation call to
  ArgumentConstructStage in both the VSC and MT converter constructors.
- Move _maybe_add_reactive_power_variables! / _constraints! to a new
  common_models/network_conditional.jl as generic helpers parameterized by
  variable types and constraint type, addressing issue #120.

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

* Address PR #119 Review 3 follow-up on HVDCTwoTerminalVSC

Drop the HVDCTwoTerminalVSCMILP formulation: the PWL surrogate for the
loss model is already MILP-grade, so a separate MILP-on-disk path didn't
buy anything we can solve. The LP path now carries a `use_octagon`
attribute (default `true`) that toggles the four diagonal half-planes on
top of the always-on axis-aligned box, so users can pick either the
octagon (≤8.2% loose linear outer-approximation of the disk) or just the
box without adding new formulations.

Refactors driven by the review feedback:
- Rename MIPQuadraticLossConverter -> MILPQuadraticLossConverter
  (forward-looking for potential MINLP variants).
- Delete _maybe_add_pq_sq_approx! and the per-formulation
  _uses_pq_sq_approx trait; the p_sq/q_sq IOM expressions are now
  registered by _register_pq_sq_expressions!, resolved by dispatch on
  (formulation, network) so only HVDCTwoTerminalVSC on an AC network
  registers them.
- Drop the VSC PSY wrappers (_vsc_v_limits_*, _vsc_rating_*,
  _vsc_v_bounds_*, _vsc_i_bounds) and call PSY.get_voltage_limits_* /
  get_rating_* directly via broadcast.
- Consolidate _vsc_shared_i_max into _linear_loss_i_max with method
  overloads for TwoTerminalVSCLine and InterconnectingConverter.
- Collapse the From/To HVDCReactivePower* add_to_expression! pair into
  one method dispatching on the variable type via _vsc_q_terminal_bus,
  and flip the coefficient from -1.0 to +1.0 (VSC injects/consumes Q
  freely, unlike LCC which is strictly a consumer).
- Replace the runtime `if N <: AbstractActivePowerModel` guards in
  network_conditional.jl with explicit no-op methods dispatched on
  NetworkModel{<:AbstractActivePowerModel}.
- Combine the two _quadratic_converter_loss_expr methods via a
  JuMP-type-dispatched _loss_seed helper.
- Drop the i_max_getter::Function argument from
  _add_abs_value_decomposition_constraints!; the getter is now picked
  by dispatch on the device type.
- Switch the four VSC add_constraints! signatures to the parameterized
  Union{Vector{U}, IS.FlattenIteratorWrapper{U}} where {U <: ...} form
  used elsewhere in the file.

Tests:
- Delete the HVDCTwoTerminalVSCMILP testsets and rename the
  MILPQuadraticLossConverter testset accordingly.
- Switch the MIP-vs-NLP agreement test to LP-vs-NLP isapprox at 5%
  tolerance (on DCP the PQ disk is inactive, so the LP and NLP differ
  only by the i² loss surrogate; the SOS2 PWL can be slightly above the
  exact i², so a strict ordering doesn't hold).
- Add a new testset that asserts use_octagon=true on the LP path gives
  an objective ≥ the box-only case, pinning the new attribute's
  semantics.

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

* formatting

* Address PR #119 Review 4 on HVDCTwoTerminalVSC

---------

Co-authored-by: Anthony Costarelli <acostare@nrel.gov>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
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

Copilot reviewed 17 out of 18 changed files in this pull request and generated 5 comments.

Comments suppressed due to low confidence (2)

src/common_models/quadratic_converter_loss.jl:45

  • When use_linear_loss is false, a nonzero proportional loss term is silently dropped instead of being modeled. That means any converter loss curve with b != 0 is solved with understated losses (the new NLP/default paths set this flag to false), so this should reject/warn on such data or use a representation that preserves the b * |I| term.
    if use_linear_loss && !iszero(b)
        JuMP.add_to_expression!(expr, b, i_pos_t)
        JuMP.add_to_expression!(expr, b, i_neg_t)
    end

test/test_device_hvdc.jl:305

  • This test uses DCPPowerModel, which dispatches through the active-power-only no-op path for reactive-power constraints, so HVDCVSCApparentPowerLimitConstraint is never added and the use_octagon attribute has no effect. The assertion can pass with identical models and does not exercise the octagon-vs-box behavior it is meant to cover; use an AC network/formulation or otherwise verify the constraint containers are present.
    function _solve_lp(use_octagon)
        sys = _generate_test_vsc_sys()
        template = OperationsProblemTemplate(NetworkModel(DCPPowerModel))

Comment thread src/mt_hvdc_models/HVDCsystems.jl Outdated
Comment on lines +583 to +584
i_pos_t = use_linear_loss ? i_pos_var[name, t] : nothing
i_neg_t = use_linear_loss ? i_neg_var[name, t] : nothing
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.

Good catch. Also add a test case for this

Comment on lines +1638 to +1647
for t in time_steps
i_pos_t = use_linear_loss ? i_pos_var[name, t] : nothing
i_neg_t = use_linear_loss ? i_neg_var[name, t] : nothing
loss_ft = _quadratic_converter_loss_expr(
a_f, b_f, c_f, i_sq_expr[name, t], i_pos_t, i_neg_t;
use_linear_loss = use_linear_loss,
)
loss_tf = _quadratic_converter_loss_expr(
a_t, b_t, c_t, i_sq_expr[name, t], i_pos_t, i_neg_t;
use_linear_loss = use_linear_loss,
Comment thread src/core/variables.jl Outdated
Comment on lines +392 to +399
Used by `HVDCTwoTerminalVSCNLP` formulations.
Docs abbreviation: ``v_f^{dc}``
"""
struct HVDCFromDCVoltage <: VariableType end

"""
DC-side voltage at the to-terminal of a two-terminal HVDC link.
Used by `HVDCTwoTerminalVSCNLP` formulations.
# loss(I) = a * I^2 + b * |I| + c
# Used by multi-terminal InterconnectingConverter formulations
# (MILPQuadraticLossConverter, QuadraticLossConverter) and two-terminal
# HVDCTwoTerminalVSCNLP formulations.
Comment thread test/test_device_hvdc.jl Outdated
Comment on lines +277 to +278
# ACP network's trigonometric (cos/sin) branch ohms-law constraints; we'd
# need an MINLP-capable solver (e.g. Gurobi) for that combination.
Copy link
Copy Markdown
Collaborator

@luke-kiernan luke-kiernan left a comment

Choose a reason for hiding this comment

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

Took a brief look--a few small things. But you should have someone more familiar with these formulations also take a look

Comment thread src/mt_hvdc_models/HVDCsystems.jl Outdated
time_steps = get_time_steps(container)
for d in devices
name = PSY.get_name(d)
bus_no = PNM.get_mapped_bus_number(
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 a little surprised to see this get_mapped_bus_number call here. What's the status of our support and testing for network reductions? Problem is that anything bus-associated gets weird...

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

LCC does something similar.

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.

Is needed for network reductions I think

Comment thread src/core/variables.jl Outdated
Docs abbreviation: ``i^{+,dc}``
"""
struct AuxBilinearConverterVariable <: VariableType end
struct PositiveCurrent <: VariableType end
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.

Generic name for something with a pretty narrow use case. Call it PositiveAbsDecompCurrent instead? Oh but we like to use full words for public interface where possible...

Comment thread src/core/variables.jl Outdated
"""
struct AuxBilinearSquaredConverterVariable <: VariableType end

Negative part of an absolute-value current decomposition (``i = i^+ - i^-``).
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.

Negative part of an absolute-value current decomposition (i = i^+ - i^-)

Is this "absolute value decomposition" thing standard terminology? I had to go pull up _add_abs_value_decomposition_constraints! in order to figure out what's going on. Namely, only one of i^+ and i^- can be nonzero, and whichever one is nonzero equals abs(i).

Comment thread src/core/formulations.jl Outdated
Quadratic Loss InterconnectingConverter using the separable bilinear approximation
(`v·i = ½((v+i)² − v² − i²)`) with a SOS2-based PWL approximation for x². Stays MILP.
"""
struct MILPQuadraticLossConverter <: AbstractQuadraticLossConverter end
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.

nitpick: it'd be more consistent to put MILP at the end of the name, since we have HVDCTwoTerminalVSCNLP. Similarly I'd maybe add a MINLP to the end of QuadraticLossConverter.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Will be in later issue

Comment thread src/mt_hvdc_models/HVDCsystems.jl Outdated
Comment on lines +583 to +584
i_pos_t = use_linear_loss ? i_pos_var[name, t] : nothing
i_neg_t = use_linear_loss ? i_neg_var[name, t] : nothing
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.

Good catch. Also add a test case for this

Replace the abs-value decomposition (PositiveCurrent + NegativeCurrent +
CurrentDirection binary, gated by use_linear_loss attribute) with a single
linear AbsoluteValueCurrent variable bounded by abs_i >= ±i. The LP
relaxation pins abs_i = |i| at the optimum because the loss term b*abs_i is
minimized via the generation-cost objective, so no binary or
complementarity constraint is needed. With the binary gone, NLP solvers
(Ipopt) can also handle the linear loss term, so use_linear_loss collapses
into the default codepath and is deleted on all four formulations.

_quadratic_converter_loss_expr becomes two type-dispatched methods on
typeof(i_sq_t): AffExpr for the MILP/PWL path, QuadExpr for the NLP/exact
bilinear path. Each method is type-stable and returns the natural
expression type for its formulation.

Rename MILPQuadraticLossConverter -> QuadraticLossConverterMILP and
QuadraticLossConverter -> QuadraticLossConverterNLP for NLP/MILP suffix
consistency.

Consolidate test/test_device_hvdc.jl: drop two redundant standalone MT
build+solve tests, three redundant standalone VSC build+solve tests, and
the octagon test (which silently passed on DCPPowerModel because
HVDCVSCApparentPowerLimitConstraint is a no-op on active-power-only
networks). Add one new test that verifies AbsoluteValueCurrent ≈
|ConverterCurrent| at the MILP optimum — direct evidence that the
binary-free abs-value formulation is tight.

Also addresses PR #103 review comments:
- Drop misleading "NLP-only" wording from HVDCFromDCVoltage /
  HVDCToDCVoltage docstrings.
- New header docstring on quadratic_converter_loss.jl explains both NLP
  and LP paths.
- Mixed-loss-device KeyError bug (Copilot's comment) disappears by
  construction: AbsoluteValueCurrent is now registered for every device.
- Silent-drop bug (use_linear_loss=false dropping nonzero b) disappears
  too: there's no toggle and b*abs_i is always modeled.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
######## Absolute-value surrogate #######
#########################################

function _add_abs_value_variables!(
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.

Why do we need this method? Can we just call add_variables! directly in the constructor?

return expr
end

function _quadratic_converter_loss_expr(
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.

This methods are good. We should add docstrings to explain here that we skip the terms.

Also, can we specify that abs_i_t is a JuMP something here so we avoid compiling an Any for abs_i_t?

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.

This is looking good. Just minor comments and we are good to go.

Anthony Costarelli and others added 2 commits May 19, 2026 10:46
Rename AbsoluteValueCurrent → CurrentAbsoluteValueVariable to match its
sister CurrentAbsoluteValueConstraint. Inline the one-line
_add_abs_value_variables! wrapper at its two call sites. Add a docstring
to _quadratic_converter_loss_expr explaining why the iszero guards drop
zero coefficient terms, and annotate abs_i_t::JuMP.VariableRef so the
methods don't infer Any.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@acostarelli acostarelli requested a review from jd-lara May 19, 2026 18:37
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.

I'm happy with this if tests are passing locally

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