-
Notifications
You must be signed in to change notification settings - Fork 1
Add tolerance-driven bilinear approximation config API #122
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
0bf1ff5
001b82a
f559b20
9bc4add
a73d189
57bc7ae
147ec06
b938cbd
8363d0b
f03f49f
de5735d
b4e1a6c
0414f81
14dbbfd
ccca499
475769b
5ebe8f6
3919d47
9d930d6
d954775
1f3d6f7
da98c62
1b1aa08
43465c2
40a7cc2
d86ae34
ac483ac
5fc3007
153b281
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,237 @@ | ||
| # Bilinear-approximation configuration for `HydroTurbineMILPBilinearDispatch`. | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is not just for hydro stuff. Make sure none of this is hydro-specific. Yes that's the name of the branch, but it's not the only use-case for this stuff. |
||
| # | ||
| # These POM-owned types let a user select the bilinear approximation scheme (and | ||
| # its inner quadratic method) for the turbined-flow × head product *by type*, | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also hydro specific |
||
| # through the single `"bilinear_config"` `DeviceModel` attribute — without | ||
| # depending on `InfrastructureOptimizationModels` (IOM). The accuracy of each | ||
| # scheme is driven by a `tolerance`; the discretization depth is derived per | ||
| # device at constraint-build time from the tolerance and the device's flow / head | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hydro specific |
||
| # ranges (see `_iom_config`), so the user never sets a manual depth / segment | ||
| # count. | ||
| # | ||
| # `_iom_config` translates these descriptors into the corresponding IOM config | ||
| # value used by `IOM._add_bilinear_approx!`. The approximation math itself lives | ||
| # entirely in IOM; this file is only the tolerance → IOM-config bridge. | ||
|
|
||
| ############################ Inner quadratic methods ####################################### | ||
|
|
||
| """ | ||
| Abstract supertype for the inner quadratic-approximation method used by the | ||
| [`Bin2Config`](@ref) and [`HybSConfig`](@ref) bilinear schemes (those schemes | ||
| approximate `f × h` via squared terms like `(f+h)²`, which each need a quadratic | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. f and h are short for flow and head which, again, are hydro specific |
||
| PWL method). The marker types carry no data: the discretization depth is derived | ||
| from the bilinear config's `tolerance` per device. | ||
| """ | ||
| abstract type AbstractQuadApproxMethod end | ||
|
|
||
| """ | ||
| Solver-handled SOS2 piecewise-linear quadratic approximation (default inner | ||
| method). Worst-case gap `Δ²/(4·d²)`, so depth scales with `Δ/(2·√tolerance)`. | ||
| """ | ||
| struct SolverSOS2 <: AbstractQuadApproxMethod end | ||
|
|
||
| """ | ||
| Manually-formulated SOS2 piecewise-linear quadratic approximation. Same error | ||
| bound as [`SolverSOS2`](@ref); does not rely on solver SOS2 support. | ||
| """ | ||
| struct ManualSOS2 <: AbstractQuadApproxMethod end | ||
|
|
||
| """ | ||
| Sawtooth (binary-logarithmic) quadratic approximation. Worst-case gap | ||
| `Δ²·2^{-2L-2}`. | ||
| """ | ||
| struct Sawtooth <: AbstractQuadApproxMethod end | ||
|
|
||
| """ | ||
| Epigraph (one-sided-under) quadratic approximation. Valid only as an internal | ||
| cross-term method; it is *not* a permitted inner quad for [`Bin2Config`](@ref) | ||
| or [`HybSConfig`](@ref) (the tolerance derivation requires a one-sided-over | ||
| inner quad), and is therefore excluded from their `quad` field types. | ||
| """ | ||
| struct Epigraph <: AbstractQuadApproxMethod end | ||
|
|
||
| """ | ||
| NMDT (Normalized Multiparametric Disaggregation) quadratic approximation used as | ||
| an inner quad for [`Bin2Config`](@ref). Built at the IOM default `epigraph_depth`; | ||
| `IOM.tolerance_depth(Bin2Config{NMDTQuadConfig})` accounts for its two-sidedness. | ||
| Distinct from the top-level [`NMDTConfig`](@ref) bilinear scheme. | ||
| """ | ||
| struct NMDTQuad <: AbstractQuadApproxMethod end | ||
|
|
||
| """ | ||
| DNMDT (Double NMDT) quadratic approximation used as an inner quad for | ||
| [`Bin2Config`](@ref) (see [`NMDTQuad`](@ref)). Distinct from the top-level | ||
| [`DNMDTConfig`](@ref) bilinear scheme. | ||
| """ | ||
| struct DNMDTQuad <: AbstractQuadApproxMethod end | ||
|
|
||
| """ | ||
| Inner quadratic methods valid for [`Bin2Config`](@ref): everything except | ||
| [`Epigraph`](@ref), which is one-sided-under and breaks the Bin2 tolerance | ||
| derivation. | ||
| """ | ||
| const Bin2Quad = Union{SolverSOS2, ManualSOS2, Sawtooth, NMDTQuad, DNMDTQuad} | ||
|
|
||
| """ | ||
| Inner quadratic methods valid for [`HybSConfig`](@ref): only the SOS2 variants | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please make sure these unions and their doc-strings are up-to-date with what IOM says. |
||
| and [`Sawtooth`](@ref). The HybS sandwich requires a one-sided-over inner quad | ||
| with no epigraph tightening, which rules out the NMDT/DNMDT inner quads as well | ||
| as [`Epigraph`](@ref). | ||
| """ | ||
| const HybSQuad = Union{SolverSOS2, ManualSOS2, Sawtooth} | ||
|
|
||
| ############################ Bilinear approximation configs ################################ | ||
|
|
||
| """ | ||
| Abstract supertype for the bilinear-approximation scheme selected through the | ||
| `"bilinear_config"` attribute of a [`HydroTurbineMILPBilinearDispatch`](@ref) | ||
| `DeviceModel`. | ||
| """ | ||
| abstract type AbstractBilinearApproxConfig end | ||
|
|
||
| """ | ||
| Bin2 bilinear approximation (default scheme). Linearizes `f × h` via the identity | ||
| `f·h = ½((f+h)² − f² − h²)`, approximating each square with the inner quadratic | ||
| method `quad`. | ||
|
|
||
| # Fields | ||
| - `tolerance::Float64` (default `1e-2`): maximum approximation gap. The | ||
| discretization depth is derived per device from this tolerance and the | ||
| device's flow / head ranges via `IOM.tolerance_depth` (no manual depth knob). | ||
| - `quad::`[`Bin2Quad`](@ref) (default [`SolverSOS2`](@ref)`()`): inner quadratic | ||
| method. [`Epigraph`](@ref) is intentionally not assignable. | ||
| """ | ||
| Base.@kwdef struct Bin2Config <: AbstractBilinearApproxConfig | ||
| tolerance::Float64 = 1e-2 | ||
| quad::Bin2Quad = SolverSOS2() | ||
| end | ||
|
|
||
| """ | ||
| HybS (Hybrid Separable) bilinear approximation. Sandwiches `f·h` between a Bin2 | ||
| lower bound and a Bin3 upper bound, using the inner quadratic method `quad` for | ||
| the shared `f²`, `h²` terms and an internal epigraph approximation (sized from | ||
| the same `tolerance`) for the cross terms. | ||
|
|
||
| # Fields | ||
| - `tolerance::Float64` (default `1e-2`): maximum approximation gap. Both the | ||
| inner-quad depth and the cross-term epigraph depth are derived per device from | ||
| this tolerance via `IOM.tolerance_depth` / `IOM.tolerance_epigraph_depth`. | ||
| - `quad::`[`HybSQuad`](@ref) (default [`SolverSOS2`](@ref)`()`): inner quadratic | ||
| method. Only the SOS2 variants and [`Sawtooth`](@ref) are assignable. | ||
| """ | ||
| Base.@kwdef struct HybSConfig <: AbstractBilinearApproxConfig | ||
| tolerance::Float64 = 1e-2 | ||
| quad::HybSQuad = SolverSOS2() | ||
| end | ||
|
acostarelli marked this conversation as resolved.
Outdated
|
||
|
|
||
| """ | ||
| NMDT (Normalized Multiparametric Disaggregation) bilinear approximation | ||
| (discretizes `f` only). Worst-case relaxation gap `Δf·Δh·2^{-L-2}`. | ||
|
|
||
| # Fields | ||
| - `tolerance::Float64` (default `1e-2`): maximum approximation gap; the depth `L` | ||
| is derived per device from it and the flow / head ranges via | ||
| `IOM.tolerance_depth`. | ||
| """ | ||
| Base.@kwdef struct NMDTConfig <: AbstractBilinearApproxConfig | ||
| tolerance::Float64 = 1e-2 | ||
| end | ||
|
acostarelli marked this conversation as resolved.
Outdated
|
||
|
|
||
| """ | ||
| DNMDT (Double NMDT) bilinear approximation (discretizes both `f` and `h`). | ||
| Worst-case relaxation gap `Δf·Δh·2^{-2L-2}`. | ||
|
|
||
| # Fields | ||
| - `tolerance::Float64` (default `1e-2`): maximum approximation gap; the depth `L` | ||
| is derived per device from it and the flow / head ranges via | ||
| `IOM.tolerance_depth`. | ||
| """ | ||
| Base.@kwdef struct DNMDTConfig <: AbstractBilinearApproxConfig | ||
| tolerance::Float64 = 1e-2 | ||
| end | ||
|
acostarelli marked this conversation as resolved.
Outdated
|
||
|
|
||
| """ | ||
| Pass the quadratic `f × h` term to the solver directly, with no MILP | ||
| linearization. Use this with a nonlinear-capable solver; the resulting model is | ||
| not a MILP. | ||
| """ | ||
| struct NoBilinearApprox <: AbstractBilinearApproxConfig end | ||
|
|
||
| ############################ Translation to IOM configs #################################### | ||
|
|
||
| # Map a POM inner-quad marker to the corresponding IOM quadratic-approx config TYPE. | ||
| _iom_quad_config_type(::SolverSOS2) = IOM.SolverSOS2QuadConfig | ||
| _iom_quad_config_type(::ManualSOS2) = IOM.ManualSOS2QuadConfig | ||
| _iom_quad_config_type(::Sawtooth) = IOM.SawtoothQuadConfig | ||
| _iom_quad_config_type(::Epigraph) = IOM.EpigraphQuadConfig | ||
| _iom_quad_config_type(::NMDTQuad) = IOM.NMDTQuadConfig | ||
| _iom_quad_config_type(::DNMDTQuad) = IOM.DNMDTQuadConfig | ||
|
|
||
| # TODO: McCormick cuts (`add_mccormick`) are dropped for now — we always defer to | ||
| # the IOM config's own default. Decide when they should be enabled and surface | ||
| # that through the `tolerance_depth` helper (so it stays a tolerance-driven | ||
| # decision) rather than re-exposing a raw knob here. | ||
|
|
||
| """ | ||
| Translate a POM [`AbstractBilinearApproxConfig`](@ref) into the IOM bilinear | ||
| config consumed by `IOM._add_bilinear_approx!`, sizing the discretization from | ||
| the config's `tolerance` and the per-device domain widths (`delta_x`, `delta_y`). | ||
|
|
||
| Each IOM `tolerance_depth` / `tolerance_epigraph_depth` helper inverts its | ||
| method's worst-case-gap bound and allocates the error budget across the inner | ||
| quadratic, so POM never sizes the inner quad by hand — it just builds the inner | ||
| quad at the returned `depth` (with the IOM-default `epigraph_depth`). Per-scheme | ||
| inner-quad validity is enforced statically by the `quad` field types | ||
| ([`Bin2Quad`](@ref) / [`HybSQuad`](@ref)). | ||
| """ | ||
| function _iom_config end | ||
|
|
||
| _iom_config(::NoBilinearApprox, ::Float64, ::Float64) = IOM.NoBilinearApproxConfig() | ||
|
|
||
| function _iom_config(config::Bin2Config, delta_x::Float64, delta_y::Float64) | ||
| Q = _iom_quad_config_type(config.quad) | ||
| depth = IOM.tolerance_depth( | ||
| IOM.Bin2Config{Q}; | ||
| tolerance = config.tolerance, | ||
| max_delta_x = delta_x, | ||
| max_delta_y = delta_y, | ||
| ) | ||
| return IOM.Bin2Config(Q(; depth)) | ||
| end | ||
|
|
||
| function _iom_config(config::HybSConfig, delta_x::Float64, delta_y::Float64) | ||
| Q = _iom_quad_config_type(config.quad) | ||
| depth = IOM.tolerance_depth( | ||
| IOM.HybSConfig{Q}; | ||
| tolerance = config.tolerance, | ||
| max_delta_x = delta_x, | ||
| max_delta_y = delta_y, | ||
| ) | ||
| epigraph_depth = IOM.tolerance_epigraph_depth( | ||
| IOM.HybSConfig{Q}; | ||
| tolerance = config.tolerance, | ||
| max_delta_x = delta_x, | ||
| max_delta_y = delta_y, | ||
| ) | ||
| return IOM.HybSConfig(Q(; depth); epigraph_depth) | ||
| end | ||
|
|
||
| function _iom_config(config::NMDTConfig, delta_x::Float64, delta_y::Float64) | ||
| depth = IOM.tolerance_depth( | ||
| IOM.NMDTBilinearConfig; | ||
| tolerance = config.tolerance, | ||
| max_delta_x = delta_x, | ||
| max_delta_y = delta_y, | ||
| ) | ||
| return IOM.NMDTBilinearConfig(; depth) | ||
| end | ||
|
|
||
| function _iom_config(config::DNMDTConfig, delta_x::Float64, delta_y::Float64) | ||
| depth = IOM.tolerance_depth( | ||
| IOM.DNMDTBilinearConfig; | ||
| tolerance = config.tolerance, | ||
| max_delta_x = delta_x, | ||
| max_delta_y = delta_y, | ||
| ) | ||
| return IOM.DNMDTBilinearConfig(; depth) | ||
| end | ||
Uh oh!
There was an error while loading. Please reload this page.