Skip to content
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

Allow SetMapBridge to use bridge value #2509

Merged
merged 15 commits into from
Jun 22, 2024
1 change: 1 addition & 0 deletions docs/src/submodules/Bridges/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ Bridges.debug_supports
## [SetMap API](@id constraint_set_map)

```@docs
Bridges.MapNotInvertible
Bridges.map_set
Bridges.inverse_map_set
Bridges.map_function
Expand Down
53 changes: 46 additions & 7 deletions src/Bridges/Constraint/set_map.jl
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,33 @@

# Attributes, Bridge acting as a constraint

# MapNotInvertible is thrown if the bridge does not support inverting the
# function. The user doesn't need to know this, only that they cannot get the
# attribute. Throwing `GetAttributeNotAllowed` allows `CachingOptimizer` to fall
# back to using the cache.
function _not_invertible_error_message(attr, message)
s = "Cannot get `$attr` as the constraint is reformulated through a linear transformation that is not invertible."
if isempty(message)
return s

Check warning on line 106 in src/Bridges/Constraint/set_map.jl

View check run for this annotation

Codecov / codecov/patch

src/Bridges/Constraint/set_map.jl#L106

Added line #L106 was not covered by tests
end
return s * " " * message
end

function MOI.get(
model::MOI.ModelLike,
attr::MOI.ConstraintFunction,
bridge::MultiSetMapBridge{T,S1,G},
) where {T,S1,G}
mapped_func = MOI.get(model, attr, bridge.constraint)
func = MOI.Bridges.inverse_map_function(typeof(bridge), mapped_func)
func = try
MOI.Bridges.inverse_map_function(bridge, mapped_func)
catch err
if err isa MOI.Bridges.MapNotInvertible
msg = _not_invertible_error_message(attr, err.message)
throw(MOI.GetAttributeNotAllowed(attr, msg))
end
rethrow(err)
end
return MOI.Utilities.convert_approx(G, func)
end

Expand All @@ -123,7 +143,7 @@
bridge::MultiSetMapBridge,
)
set = MOI.get(model, attr, bridge.constraint)
return MOI.Bridges.inverse_map_set(typeof(bridge), set)
return MOI.Bridges.inverse_map_set(bridge, set)
end

function MOI.set(
Expand All @@ -132,7 +152,7 @@
bridge::MultiSetMapBridge{T,S1},
set::S1,
) where {T,S1}
new_set = MOI.Bridges.map_set(typeof(bridge), set)
new_set = MOI.Bridges.map_set(bridge, set)
MOI.set(model, attr, bridge.constraint, new_set)
return
end
Expand All @@ -146,7 +166,18 @@
if value === nothing
return nothing
end
return MOI.Bridges.inverse_map_function(typeof(bridge), value)
try
return MOI.Bridges.inverse_map_function(bridge, value)
catch err
# MapNotInvertible is thrown if the bridge does not support inverting
# the function. The user doesn't need to know this, only that they
# cannot get the attribute.
if err isa MOI.Bridges.MapNotInvertible
msg = _not_invertible_error_message(attr, err.message)
throw(MOI.GetAttributeNotAllowed(attr, msg))
end
rethrow(err)
end
end

function MOI.set(
Expand All @@ -158,7 +189,7 @@
if value === nothing
MOI.set(model, attr, bridge.constraint, nothing)
else
mapped_value = MOI.Bridges.map_function(typeof(bridge), value)
mapped_value = MOI.Bridges.map_function(bridge, value)
MOI.set(model, attr, bridge.constraint, mapped_value)
end
return
Expand All @@ -173,7 +204,7 @@
if value === nothing
return nothing
end
return MOI.Bridges.adjoint_map_function(typeof(bridge), value)
return MOI.Bridges.adjoint_map_function(bridge, value)
end

function MOI.set(
Expand All @@ -185,7 +216,15 @@
if value === nothing
MOI.set(model, attr, bridge.constraint, nothing)
else
mapped_value = MOI.Bridges.inverse_adjoint_map_function(BT, value)
mapped_value = try
MOI.Bridges.inverse_adjoint_map_function(bridge, value)
catch err
if err isa MOI.Bridges.MapNotInvertible
msg = _not_invertible_error_message(attr, err.message)
throw(MOI.SetAttributeNotAllowed(attr, msg))
end
rethrow(err)
end
MOI.set(model, attr, bridge.constraint, mapped_value)
end
return
Expand Down
118 changes: 90 additions & 28 deletions src/Bridges/set_map.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,74 @@
# in the LICENSE.md file or at https://opensource.org/licenses/MIT.

"""
struct MapNotInvertible <: Exception
message::String
end

An error thrown by [`inverse_map_function`](@ref) or
[`inverse_adjoint_map_function`](@ref) indicating that the linear map `A`
defined in [`Variable.SetMapBridge`](@ref) and [`Constraint.SetMapBridge`](@ref)
is not invertible.
"""
struct MapNotInvertible <: Exception
message::String
end

"""
map_set(bridge::MOI.Bridges.AbstractBridge, set)
map_set(::Type{BT}, set) where {BT}

Return the image of `set` through the linear map `A` defined in
[`Variable.SetMapBridge`](@ref) and [`Constraint.SetMapBridge`](@ref). This is
used for bridging the constraint and setting
the [`MOI.ConstraintSet`](@ref).
[`Variable.SetMapBridge`](@ref) and [`Constraint.SetMapBridge`](@ref).

This function is used for bridging the constraint and setting the
[`MOI.ConstraintSet`](@ref).

The default implementation of [`Variable.bridge_constraint`](@ref) uses
[`map_set`](@ref) with the bridge type so if this function is defined
on the bridge type, [`Variable.bridge_constraint`](@ref) does not need
to be implemented.
Copy link
Member

Choose a reason for hiding this comment

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

I don't understand this comment. Variable.bridge_constraint does not exist.

Copy link
Member

Choose a reason for hiding this comment

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

Do you mean

function bridge_constraint(
BT::Type{<:MultiSetMapBridge{T,S1,G}},
model::MOI.ModelLike,
func::G,
set::S1,
) where {T,S1,G}
mapped_func = MOI.Bridges.map_function(BT, func)
mapped_set = MOI.Bridges.map_set(BT, set)
constraint = MOI.add_constraint(model, mapped_func, mapped_set)

Copy link
Member

Choose a reason for hiding this comment

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

I just removed the comment

"""
function map_set end
map_set(bridge::AbstractBridge, set) = map_set(typeof(bridge), set)

"""
inverse_map_set(bridge::MOI.Bridges.AbstractBridge, set)
inverse_map_set(::Type{BT}, set) where {BT}

Return the preimage of `set` through the linear map `A` defined in
[`Variable.SetMapBridge`](@ref) and [`Constraint.SetMapBridge`](@ref). This is
used for getting the [`MOI.ConstraintSet`](@ref).
[`Variable.SetMapBridge`](@ref) and [`Constraint.SetMapBridge`](@ref).

This function is used for getting the [`MOI.ConstraintSet`](@ref).

The method can alternatively be defined on the bridge type. This legacy
interface is kept for backward compatibility.
"""
function inverse_map_set end
function inverse_map_set(bridge::AbstractBridge, set)
return inverse_map_set(typeof(bridge), set)
end

"""
map_function(bridge::MOI.Bridges.AbstractBridge, func)
map_function(::Type{BT}, func) where {BT}

Return the image of `func` through the linear map `A` defined in
[`Variable.SetMapBridge`](@ref) and [`Constraint.SetMapBridge`](@ref). This is
used for getting the [`MOI.ConstraintPrimal`](@ref) of variable
[`Variable.SetMapBridge`](@ref) and [`Constraint.SetMapBridge`](@ref).

This function is used for getting the [`MOI.ConstraintPrimal`](@ref) of variable
bridges. For constraint bridges, this is used for bridging the constraint,
setting the [`MOI.ConstraintFunction`](@ref) and
[`MOI.ConstraintPrimalStart`](@ref) and
modifying the function with [`MOI.modify`](@ref).
setting the [`MOI.ConstraintFunction`](@ref) and [`MOI.ConstraintPrimalStart`](@ref)
and modifying the function with [`MOI.modify`](@ref).

The default implementation of [`Constraint.bridge_constraint`](@ref) uses
[`map_function`](@ref) with the bridge type so if this function is defined
on the bridge type, [`Constraint.bridge_constraint`](@ref) does not need
to be implemented.
"""
function map_function(bridge::AbstractBridge, func)
return map_function(typeof(bridge), func)
end

"""
map_function(::Type{BT}, func, i::IndexInVector) where {BT}

Return the scalar function at the `i`th index of the vector function that
Expand All @@ -42,42 +81,65 @@ would be returned by `map_function(BT, func)` except that it may compute the
the [`MOI.VariablePrimal`](@ref) and
[`MOI.VariablePrimalStart`](@ref) of variable bridges.
"""
function map_function end

function map_function(::Type{BT}, func, i::IndexInVector) where {BT}
odow marked this conversation as resolved.
Show resolved Hide resolved
return MOI.Utilities.eachscalar(map_function(BT, func))[i.value]
end

"""
inverse_map_function(bridge::MOI.Bridges.AbstractBridge, func)
inverse_map_function(::Type{BT}, func) where {BT}

Return the image of `func` through the inverse of the linear map `A` defined in
[`Variable.SetMapBridge`](@ref) and [`Constraint.SetMapBridge`](@ref). This is
used by [`Variable.unbridged_map`](@ref) and for setting the
[`MOI.VariablePrimalStart`](@ref) of variable bridges
and for getting the [`MOI.ConstraintFunction`](@ref),
the [`MOI.ConstraintPrimal`](@ref) and the
[`Variable.SetMapBridge`](@ref) and [`Constraint.SetMapBridge`](@ref).

This function is used by [`Variable.unbridged_map`](@ref) and for setting the
[`MOI.VariablePrimalStart`](@ref) of variable bridges and for getting the
[`MOI.ConstraintFunction`](@ref), the [`MOI.ConstraintPrimal`](@ref) and the
[`MOI.ConstraintPrimalStart`](@ref) of constraint bridges.

If the linear map `A` is not invertible, the error [`MapNotInvertible`](@ref) is
thrown.

The method can alternatively be defined on the bridge type. This legacy
interface is kept for backward compatibility.
"""
function inverse_map_function end
function inverse_map_function(bridge::AbstractBridge, func)
return inverse_map_function(typeof(bridge), func)
end

"""
adjoint_map_function(bridge::MOI.Bridges.AbstractBridge, func)
adjoint_map_function(::Type{BT}, func) where {BT}

Return the image of `func` through the adjoint of the linear map `A` defined in
[`Variable.SetMapBridge`](@ref) and [`Constraint.SetMapBridge`](@ref). This is
used for getting the [`MOI.ConstraintDual`](@ref) and
[`Variable.SetMapBridge`](@ref) and [`Constraint.SetMapBridge`](@ref).

This function is used for getting the [`MOI.ConstraintDual`](@ref) and
[`MOI.ConstraintDualStart`](@ref) of constraint bridges.

The method can alternatively be defined on the bridge type. This legacy
interface is kept for backward compatibility.
"""
function adjoint_map_function end
function adjoint_map_function(bridge::AbstractBridge, func)
return adjoint_map_function(typeof(bridge), func)
end

"""
inverse_adjoint_map_function(bridge::MOI.Bridges.AbstractBridge, func)
inverse_adjoint_map_function(::Type{BT}, func) where {BT}

Return the image of `func` through the inverse of the adjoint of the linear map
`A` defined in [`Variable.SetMapBridge`](@ref) and
[`Constraint.SetMapBridge`](@ref). This is used for getting the
[`MOI.ConstraintDual`](@ref) of variable bridges and setting the
[`MOI.ConstraintDualStart`](@ref) of constraint bridges.
`A` defined in [`Variable.SetMapBridge`](@ref) and [`Constraint.SetMapBridge`](@ref).

This function is used for getting the [`MOI.ConstraintDual`](@ref) of variable
bridges and setting the [`MOI.ConstraintDualStart`](@ref) of constraint bridges.

If the linear map `A` is not invertible, the error [`MapNotInvertible`](@ref) is
thrown.

The method can alternatively be defined on the bridge type. This legacy
interface is kept for backward compatibility.
"""
function inverse_adjoint_map_function end
function inverse_adjoint_map_function(bridge::AbstractBridge, func)
odow marked this conversation as resolved.
Show resolved Hide resolved
return inverse_adjoint_map_function(typeof(bridge), func)
end
Loading
Loading