Implementing a bridge

The easiest way to implement a bridge is to follow an existing example. There are three locations of bridges in the source code:

  • Constraint bridges are stored in src/Bridges/Constraint/bridges
  • Objective bridges are stored in src/Bridges/Objective/bridges
  • Variable bridges are stored in src/Bridges/Variable/bridges

The Implementing a constraint bridge tutorial has a more detailed guide on what is required to implement a bridge.

When opening a pull request that adds a new bridge, use the checklist Adding a new bridge.

If you need help or advice, please contact the Developer Chatroom.

SetMap bridges

For constraint and variable bridges, a common reformulation is that $f(x) \in F$ is reformulated to $g(x) \in G$. In this case, no additional variables and constraints are added, and the bridge needs only a way to map between the functions f and g and the sets F and G.

To implementation a bridge of this form, subtype the abstract type Bridges.Constraint.SetMapBridge or Bridges.Variable.SetMapBridge and implement the API described in the docstring of each type.

final_touch

Some bridges require information from other parts of the model. One set of examples are the various combinatorial ToMILP bridges, such as Bridges.Constraint.SOS1ToMILPBridge, which require knowledge of the variable bounds.

Bridges requiring information from other parts of the model should implement Bridges.final_touch and Bridges.needs_final_touch.

During the bridge's construction, store the function and set and make no changes to the underlying model. Then, in Bridges.final_touch, query the additional information and add the reformulated problem to the model.

When implementing, you must consider that:

  • Bridges.final_touch may be called multiple times, so that your reformulation should be applied only if necessary. Sometimes the additional data will be the same, and sometimes it may be different.
  • We do not currently support final_touch bridges that introduce constraints which also require a final_touch bridge. Therefore, you should implement final_touch only if necessary, and we recommend that you contact the Developer Chatroom for advice before doing so.

Testing

Use the Bridges.runtests function to test a bridge. It takes three arguments: the type of the bridge, the input model as a string, and the output model as a string.

Here is an example:

julia> MOI.Bridges.runtests(
           MOI.Bridges.Constraint.GreaterToLessBridge,
           """
           variables: x
           x >= 1.0
           """,
           """
           variables: x
           -1.0 * x <= -1.0
           """,
       )

There are a number of other useful keyword arguments.

  • eltype can be used to specify the element type of the model (and bridge). It defaults to Float64.
  • variable_start and constraint_start are used as the values to set the VariablePrimalStart and ConstraintPrimalStart attributes to. They default to 1.2. If you use a different eltype, you must set appropriate starting values of the same type. The default 1.2 was chosen to minimize the risk that the starting point is undefined, which could happen for common situations like 0.0 and 1.0. The tests associated with the starting values do not necessarily check for correctness, only that they can be set and get to produce the same result.
  • print_inner_model can be used to print the reformulated output model from the bridge. This is especially helpful during debugging to see what the bridge is doing, and to spot mistakes. It defaults to false.

Here is an example:

julia> MOI.Bridges.runtests(
           MOI.Bridges.Constraint.GreaterToLessBridge,
           """
           variables: x
           x >= 1
           """,
           """
           variables: x
           ::Int: -1 * x <= -1
           """;
           eltype = Int,
           print_inner_model = true,
           variable_start = 2,
           constraint_start = 2,
       )
Feasibility

Subject to:

ScalarAffineFunction{Int64}-in-LessThan{Int64}
 (0) - (1) x <= (-1)