text:
# Copyright (c) 2017: Miles Lubin and contributors
# Copyright (c) 2017: Google Inc.
#
# Use of this source code is governed by an MIT-style license that can be found
# in the LICENSE.md file or at https://opensource.org/licenses/MIT.

module TestBridgesLazyBridgeOptimizer

using Test

import MathOptInterface

const MOI = MathOptInterface

function runtests()
    for name in names(@__MODULE__; all = true)
        if startswith("$(name)", "test_")
            @testset "$(name)" begin
                getfield(@__MODULE__, name)()
            end
        end
    end
    return
end

include("utilities.jl")

function test_add_remove_has_bridges()
    T = Int
    model = MOI.Utilities.Model{T}()
    bridged = MOI.Bridges.LazyBridgeOptimizer(model)
    for BT in [
        MOI.Bridges.Variable.VectorizeBridge{T},
        MOI.Bridges.Constraint.VectorizeBridge{T},
        MOI.Bridges.Objective.FunctionizeBridge{T},
        MOI.Bridges.Constraint.ScalarFunctionizeBridge{T},
        MOI.Bridges.Variable.FreeBridge{T},
    ]
        @test !MOI.Bridges.has_bridge(bridged, BT)
        MOI.Bridges.add_bridge(bridged, BT)
        @test MOI.Bridges.has_bridge(bridged, BT)
        if BT != MOI.Bridges.Variable.FreeBridge{T}
            @test length(MOI.Bridges._bridge_types(bridged, BT)) == 1
        end
        MOI.Bridges.add_bridge(bridged, BT)
        @test MOI.Bridges.has_bridge(bridged, BT)
        if BT != MOI.Bridges.Variable.FreeBridge{T}
            @test length(MOI.Bridges._bridge_types(bridged, BT)) == 1
        end
        MOI.Bridges.remove_bridge(bridged, BT)
        @test !MOI.Bridges.has_bridge(bridged, BT)
        @test isempty(MOI.Bridges._bridge_types(bridged, BT))
        err = ErrorException(
            "Cannot remove bridge `$BT` as it was never added or was already removed.",
        )
        @test_throws err MOI.Bridges.remove_bridge(bridged, BT)
    end
    return
end

function _functionize_error(b, bridge_type, func, name)
    return ErrorException(
        "Need to apply a `$bridge_type` to a `$func` $name because the" *
        " variable is bridged but $name bridges are not supported by" *
        " `$(typeof(b))`.",
    )
end

function _lazy_functionize_error(bridge_type, func, name)
    return ErrorException(
        "Need to apply a `$bridge_type` to a `$func` $name because the variable is" *
        " bridged but no such $name bridge type was added. Add one" *
        " with `add_bridge`.",
    )
end

MOI.Utilities.@model(
    StandardLPModel,
    (),
    (MOI.EqualTo, MOI.Test.UnknownScalarSet),
    (MOI.Nonnegatives,),
    (),
    (),
    (MOI.ScalarAffineFunction,),
    (MOI.VectorOfVariables,),
    ()
)

function MOI.supports_constraint(
    ::StandardLPModel{T},
    ::Type{MOI.VariableIndex},
    ::Type{<:Union{MOI.GreaterThan{T},MOI.LessThan{T},MOI.EqualTo{T}}},
) where {T}
    return false
end

function MOI.supports_constraint(
    ::StandardLPModel,
    ::Type{MOI.VectorOfVariables},
    ::Type{MOI.Reals},
)
    return false
end

function MOI.supports(
    ::StandardLPModel{T},
    ::MOI.ObjectiveFunction{
        <:Union{MOI.VariableIndex,MOI.ScalarQuadraticFunction{T}},
    },
) where {T}
    return false
end

function _test_bridged_variable_in_VariableIndex_constraint(T)
    S = MOI.Test.UnknownScalarSet{T}
    set = S(one(T))
    model = StandardLPModel{T}()
    bridged = MOI.Bridges.Variable.Vectorize{T}(model)
    x, cx = MOI.add_constrained_variable(bridged, MOI.GreaterThan(one(T)))
    err = _functionize_error(
        bridged,
        MOI.Bridges.Constraint.ScalarFunctionizeBridge,
        "VariableIndex",
        "constraint",
    )
    @test_throws err MOI.add_constraint(bridged, x, set)
    @test_throws err MOI.add_constraints(bridged, [x], [set])
    function _bridged()
        model = StandardLPModel{T}()
        bridged = MOI.Bridges.LazyBridgeOptimizer(model)
        MOI.Bridges.add_bridge(bridged, MOI.Bridges.Variable.VectorizeBridge{T})
        x, cx = MOI.add_constrained_variable(bridged, MOI.GreaterThan(one(T)))
        return model, bridged, x
    end
    _, bridged, x = _bridged()
    err = _lazy_functionize_error(
        MOI.Bridges.Constraint.ScalarFunctionizeBridge,
        "VariableIndex",
        "constraint",
    )
    @test_throws err MOI.add_constraint(bridged, x, set)
    @test_throws err MOI.add_constraints(bridged, [x], [set])
    for i in 1:2
        model, bridged, x = _bridged()
        MOI.Bridges.add_bridge(
            bridged,
            MOI.Bridges.Constraint.ScalarFunctionizeBridge{T},
        )
        if i == 1
            cx = MOI.add_constraint(bridged, x, set)
        else
            cx = MOI.add_constraints(bridged, [x], [set])[1]
        end
        @test MOI.get(bridged, MOI.ConstraintFunction(), cx) == x
        @test MOI.get(bridged, MOI.ConstraintSet(), cx) == set
        a = MOI.get(model, MOI.ListOfVariableIndices())[1]
        ca = MOI.get(
            model,
            MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{T},S}(),
        )[1]
        @test MOI.get(model, MOI.ConstraintFunction(), ca) ≈
              convert(MOI.ScalarAffineFunction{T}, a)
        @test MOI.get(model, MOI.ConstraintSet(), ca) ==
              MOI.Utilities.shift_constant(set, -one(T))
    end
    return
end

function test_bridged_variable_in_VariableIndex_constraint()
    _test_bridged_variable_in_VariableIndex_constraint(Float64)
    _test_bridged_variable_in_VariableIndex_constraint(Int)
    return
end

function _test_bridged_variable_in_VectorOfVariables_constraint(T)
    set = MOI.Zeros(1)
    model = StandardLPModel{T}()
    bridged = MOI.Bridges.Variable.Vectorize{T}(model)
    x, cx = MOI.add_constrained_variable(bridged, MOI.GreaterThan(one(T)))
    x = MOI.VectorOfVariables([x])
    err = _functionize_error(
        bridged,
        MOI.Bridges.Constraint.VectorFunctionizeBridge,
        "VectorOfVariables",
        "constraint",
    )
    @test_throws err MOI.add_constraint(bridged, x, set)
    @test_throws err MOI.add_constraints(bridged, [x], [set])
    function _bridged()
        model = StandardLPModel{T}()
        bridged = MOI.Bridges.LazyBridgeOptimizer(model)
        MOI.Bridges.add_bridge(bridged, MOI.Bridges.Variable.VectorizeBridge{T})
        MOI.Bridges.add_bridge(
            bridged,
            MOI.Bridges.Constraint.ScalarizeBridge{T},
        )
        x, cx = MOI.add_constrained_variable(bridged, MOI.GreaterThan(one(T)))
        x = MOI.VectorOfVariables([x])
        return model, bridged, x
    end
    _, bridged, x = _bridged()
    err = _lazy_functionize_error(
        MOI.Bridges.Constraint.VectorFunctionizeBridge,
        "VectorOfVariables",
        "constraint",
    )
    @test_throws err MOI.add_constraint(bridged, x, set)
    @test_throws err MOI.add_constraints(bridged, [x], [set])
    for i in 1:2
        model, bridged, x = _bridged()
        MOI.Bridges.add_bridge(
            bridged,
            MOI.Bridges.Constraint.VectorFunctionizeBridge{T},
        )
        if i == 1
            cx = MOI.add_constraint(bridged, x, set)
        else
            cx = MOI.add_constraints(bridged, [x], [set])[1]
        end
        @test MOI.get(bridged, MOI.ConstraintFunction(), cx) == x
        @test MOI.get(bridged, MOI.ConstraintSet(), cx) == set
        a = MOI.get(model, MOI.ListOfVariableIndices())[1]
        ca = MOI.get(
            model,
            MOI.ListOfConstraintIndices{
                MOI.ScalarAffineFunction{T},
                MOI.EqualTo{T},
            }(),
        )[1]
        @test MOI.get(model, MOI.ConstraintFunction(), ca) ≈
              convert(MOI.ScalarAffineFunction{T}, a)
        @test MOI.get(model, MOI.ConstraintSet(), ca) == MOI.EqualTo(-one(T))
    end
    return
end

function test_bridged_variable_in_VectorOfVariables_constraint()
    _test_bridged_variable_in_VectorOfVariables_constraint(Float64)
    _test_bridged_variable_in_VectorOfVariables_constraint(Int)
    return
end

function _test_bridged_variable_in_VariableIndex_obiective(T)
    model = StandardLPModel{T}()
    bridged = MOI.Bridges.Variable.Vectorize{T}(model)
    x, cx = MOI.add_constrained_variable(bridged, MOI.GreaterThan(one(T)))
    err = _functionize_error(
        bridged,
        MOI.Bridges.Objective.FunctionizeBridge,
        "VariableIndex",
        "objective",
    )
    @test_throws err MOI.set(bridged, MOI.ObjectiveFunction{typeof(x)}(), x)
    model = StandardLPModel{T}()
    bridged = MOI.Bridges.LazyBridgeOptimizer(model)
    MOI.Bridges.add_bridge(bridged, MOI.Bridges.Variable.VectorizeBridge{T})
    x, cx = MOI.add_constrained_variable(bridged, MOI.GreaterThan(one(T)))
    err = _lazy_functionize_error(
        MOI.Bridges.Objective.FunctionizeBridge,
        "VariableIndex",
        "objective",
    )
    @test_throws err MOI.set(bridged, MOI.ObjectiveFunction{typeof(x)}(), x)
    MOI.Bridges.add_bridge(bridged, MOI.Bridges.Objective.FunctionizeBridge{T})
    MOI.set(bridged, MOI.ObjectiveFunction{typeof(x)}(), x)
    @test MOI.get(bridged, MOI.ObjectiveFunctionType()) == MOI.VariableIndex
    @test MOI.get(bridged, MOI.ObjectiveFunction{MOI.VariableIndex}()) ≈ x
    a = MOI.get(model, MOI.ListOfVariableIndices())[1]
    @test MOI.get(model, MOI.ObjectiveFunctionType()) ==
          MOI.ScalarAffineFunction{T}
    @test MOI.get(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}()) ≈
          a + one(T)
    return
end

function test_bridged_variable_in_VariableIndex_obiective()
    _test_bridged_variable_in_VariableIndex_obiective(Float64)
    _test_bridged_variable_in_VariableIndex_obiective(Int)
    return
end

MOI.Utilities.@model(
    LPModel,
    (),
    (MOI.EqualTo, MOI.GreaterThan, MOI.LessThan),
    (),
    (),
    (),
    (MOI.ScalarAffineFunction,),
    (),
    ()
)

function test_MOI_runtests_LPModel()
    model = LPModel{Float64}()
    bridged = MOI.Bridges.full_bridge_optimizer(model, Float64)
    MOI.Test.runtests(
        bridged,
        MOI.Test.Config(exclude = Any[MOI.optimize!]),
        include = ["test_model_", "test_constraint_"],
        exclude = [
            "test_constraint_ConstraintDualStart",
            "test_constraint_ConstraintPrimalStart",
            "test_model_default_DualStatus",
            "test_model_default_PrimalStatus",
            "test_model_default_TerminationStatus",
            "test_model_LowerBoundAlreadySet",
            "test_model_UpperBoundAlreadySet",
        ],
    )
    return
end

# Model similar to SDPA format, it gives a good example because it does not
# support a lot hence need a lot of bridges
MOI.Utilities.@model(
    SDPAModel,
    (),
    (MOI.EqualTo,),
    (MOI.Nonnegatives, MOI.PositiveSemidefiniteConeTriangle),
    (),
    (),
    (MOI.ScalarAffineFunction,),
    (MOI.VectorOfVariables,),
    ()
)

function MOI.supports_constraint(
    ::SDPAModel{T},
    ::Type{MOI.VariableIndex},
    ::Type{
        <:Union{
            MOI.GreaterThan{T},
            MOI.LessThan{T},
            MOI.EqualTo{T},
            MOI.Interval{T},
            MOI.ZeroOne,
            MOI.Integer,
        },
    },
) where {T}
    return false
end

function MOI.supports_constraint(
    ::SDPAModel{T},
    ::Type{MOI.VectorOfVariables},
    ::Type{MOI.Reals},
) where {T}
    return false
end

function MOI.supports_add_constrained_variables(
    ::SDPAModel,
    ::Type{<:Union{MOI.Nonnegatives,MOI.PositiveSemidefiniteConeTriangle}},
)
    return true
end

MOI.supports_add_constrained_variables(::SDPAModel, ::Type{MOI.Reals}) = false

function MOI.supports(
    ::SDPAModel{T},
    ::MOI.ObjectiveFunction{
        <:Union{MOI.VariableIndex,MOI.ScalarQuadraticFunction{T}},
    },
) where {T}
    return false
end

function test_MOI_runtests_SDPAModel()
    model = SDPAModel{Float64}()
    bridged = MOI.Bridges.full_bridge_optimizer(model, Float64)
    MOI.Test.runtests(
        bridged,
        MOI.Test.Config(exclude = Any[MOI.optimize!]),
        include = ["ConstraintName", "VariableName"],
    )
    return
end

function test_show_SPDA()
    model = SDPAModel{Float64}()
    model_str = sprint(MOI.Utilities.print_with_acronym, string(typeof(model)))
    bridged = MOI.Bridges.full_bridge_optimizer(model, Float64)
    # no bridges
    @test sprint(show, bridged) === """
    MOIB.LazyBridgeOptimizer{$model_str}
    with 0 variable bridges
    with 0 constraint bridges
    with 0 objective bridges
    with inner model $model_str"""

    MOI.add_constrained_variable(bridged, MOI.LessThan(1.0))
    # add variable bridges
    @test sprint(show, bridged) == """
    MOIB.LazyBridgeOptimizer{$model_str}
    with 2 variable bridges
    with 0 constraint bridges
    with 0 objective bridges
    with inner model $model_str"""
    return
end

function _test_SDPA_format(T)
    model = SDPAModel{T}()
    bridged = MOI.Bridges.LazyBridgeOptimizer(model)
    @test !MOI.supports_constraint(
        model,
        MOI.VectorOfVariables,
        MOI.Nonpositives,
    )
    @test !MOI.supports_constraint(
        bridged,
        MOI.VectorOfVariables,
        MOI.Nonpositives,
    )
    @test !MOI.supports_add_constrained_variables(bridged, MOI.Nonpositives)
    MOI.Bridges.add_bridge(
        bridged,
        MOI.Bridges.Variable.NonposToNonnegBridge{T},
    )
    @test !MOI.supports_constraint(
        bridged,
        MOI.VectorOfVariables,
        MOI.Nonpositives,
    )
    @test MOI.supports_add_constrained_variables(bridged, MOI.Nonpositives)
    @test MOI.Bridges.bridge_type(bridged, MOI.Nonpositives) ==
          MOI.Bridges.Variable.NonposToNonnegBridge{T}
    @test !MOI.supports_constraint(model, MOI.VectorOfVariables, MOI.Zeros)
    @test !MOI.supports_add_constrained_variables(bridged, MOI.Zeros)
    @test !MOI.supports_constraint(bridged, MOI.VectorOfVariables, MOI.Zeros)
    MOI.Bridges.add_bridge(bridged, MOI.Bridges.Variable.ZerosBridge{T})
    @test !MOI.supports_constraint(bridged, MOI.VectorOfVariables, MOI.Zeros)
    @test MOI.supports_add_constrained_variables(bridged, MOI.Zeros)
    @test MOI.Bridges.bridge_type(bridged, MOI.Zeros) ==
          MOI.Bridges.Variable.ZerosBridge{T}
    @test !MOI.supports_constraint(model, MOI.VectorOfVariables, MOI.Reals)
    @test !MOI.supports_constraint(bridged, MOI.VectorOfVariables, MOI.Reals)
    @test !MOI.supports_add_constrained_variables(bridged, MOI.Reals)
    @test_throws MOI.UnsupportedConstraint{MOI.VectorOfVariables,MOI.Reals} MOI.add_variable(
        bridged,
    )
    @test_throws MOI.UnsupportedConstraint{MOI.VectorOfVariables,MOI.Reals} MOI.add_variables(
        bridged,
        2,
    )
    MOI.Bridges.add_bridge(bridged, MOI.Bridges.Variable.FreeBridge{T})
    @test !MOI.supports_constraint(bridged, MOI.VectorOfVariables, MOI.Reals)
    @test MOI.supports_add_constrained_variables(bridged, MOI.Reals)
    @test MOI.Bridges.bridge_type(bridged, MOI.Reals) ==
          MOI.Bridges.Variable.FreeBridge{T}
    @test !MOI.supports_constraint(model, MOI.VariableIndex, MOI.GreaterThan{T})
    @test !MOI.supports_constraint(
        bridged,
        MOI.VariableIndex,
        MOI.GreaterThan{T},
    )
    @test !MOI.supports_add_constrained_variable(bridged, MOI.GreaterThan{T})
    @test !MOI.supports_constraint(model, MOI.VariableIndex, MOI.LessThan{T})
    @test !MOI.supports_constraint(bridged, MOI.VariableIndex, MOI.LessThan{T})
    @test !MOI.supports_add_constrained_variable(bridged, MOI.LessThan{T})
    @test !MOI.supports_constraint(model, MOI.VariableIndex, MOI.EqualTo{T})
    @test !MOI.supports_constraint(bridged, MOI.VariableIndex, MOI.EqualTo{T})
    @test !MOI.supports_add_constrained_variable(bridged, MOI.EqualTo{T})
    MOI.Bridges.add_bridge(bridged, MOI.Bridges.Variable.VectorizeBridge{T})
    @test !MOI.supports_constraint(
        bridged,
        MOI.VariableIndex,
        MOI.GreaterThan{T},
    )
    @test MOI.supports_add_constrained_variable(bridged, MOI.GreaterThan{T})
    @test MOI.Bridges.bridge_type(bridged, MOI.GreaterThan{T}) ==
          MOI.Bridges.Variable.VectorizeBridge{T,MOI.Nonnegatives}
    @test !MOI.supports_constraint(bridged, MOI.VariableIndex, MOI.LessThan{T})
    @test MOI.supports_add_constrained_variable(bridged, MOI.LessThan{T})
    @test MOI.Bridges.bridge_type(bridged, MOI.LessThan{T}) ==
          MOI.Bridges.Variable.VectorizeBridge{T,MOI.Nonpositives}
    @test !MOI.supports_constraint(bridged, MOI.VariableIndex, MOI.EqualTo{T})
    @test MOI.supports_add_constrained_variable(bridged, MOI.EqualTo{T})
    @test MOI.Bridges.bridge_type(bridged, MOI.EqualTo{T}) ==
          MOI.Bridges.Variable.VectorizeBridge{T,MOI.Zeros}
    @test !MOI.supports_constraint(
        model,
        MOI.VectorOfVariables,
        MOI.RotatedSecondOrderCone,
    )
    @test !MOI.supports_constraint(
        bridged,
        MOI.VectorOfVariables,
        MOI.RotatedSecondOrderCone,
    )
    @test !MOI.supports_add_constrained_variables(
        bridged,
        MOI.RotatedSecondOrderCone,
    )
    MOI.Bridges.add_bridge(bridged, MOI.Bridges.Variable.RSOCtoPSDBridge{T})
    @test !MOI.supports_constraint(
        bridged,
        MOI.VectorOfVariables,
        MOI.RotatedSecondOrderCone,
    )
    @test !MOI.supports_add_constrained_variables(
        bridged,
        MOI.RotatedSecondOrderCone,
    )
    MOI.Bridges.add_bridge(
        bridged,
        MOI.Bridges.Constraint.ScalarFunctionizeBridge{T},
    )
    @test !MOI.supports_constraint(
        bridged,
        MOI.VectorOfVariables,
        MOI.RotatedSecondOrderCone,
    )
    @test MOI.supports_add_constrained_variables(
        bridged,
        MOI.RotatedSecondOrderCone,
    )
    @test MOI.Bridges.bridge_type(bridged, MOI.RotatedSecondOrderCone) ==
          MOI.Bridges.Variable.RSOCtoPSDBridge{T}
    x, cx = MOI.add_constrained_variable(bridged, MOI.LessThan(one(T)))
    _test_delete_bridged_variable(
        bridged,
        x,
        MOI.LessThan{T},
        1,
        (
            (MOI.VectorOfVariables, MOI.Nonnegatives, 0),
            (MOI.VectorOfVariables, MOI.Nonpositives, 0),
        ),
        used_bridges = 2,
    )
    @test !MOI.supports_constraint(
        bridged,
        MOI.VectorAffineFunction{T},
        MOI.RotatedSecondOrderCone,
    )
    MOI.Bridges.add_bridge(bridged, MOI.Bridges.Constraint.VectorSlackBridge{T})
    @test !MOI.supports_constraint(
        bridged,
        MOI.VectorAffineFunction{T},
        MOI.RotatedSecondOrderCone,
    )
    MOI.Bridges.add_bridge(bridged, MOI.Bridges.Constraint.ScalarizeBridge{T})
    @test MOI.supports_constraint(
        bridged,
        MOI.VectorAffineFunction{T},
        MOI.RotatedSecondOrderCone,
    )
    @test MOI.Bridges.bridge_type(
        bridged,
        MOI.VectorAffineFunction{T},
        MOI.RotatedSecondOrderCone,
    ) == MOI.Bridges.Constraint.VectorSlackBridge{
        T,
        MOI.VectorAffineFunction{T},
        MOI.RotatedSecondOrderCone,
    }
    @test MOI.supports_constraint(
        bridged,
        MOI.VectorAffineFunction{T},
        MOI.Zeros,
    )
    @test MOI.Bridges.bridge_type(
        bridged,
        MOI.VectorAffineFunction{T},
        MOI.Zeros,
    ) == MOI.Bridges.Constraint.ScalarizeBridge{
        T,
        MOI.ScalarAffineFunction{T},
        MOI.EqualTo{T},
    }
    @test !MOI.supports_constraint(
        model,
        MOI.VectorOfVariables,
        MOI.PositiveSemidefiniteConeSquare,
    )
    @test !MOI.supports_constraint(
        bridged,
        MOI.VectorOfVariables,
        MOI.PositiveSemidefiniteConeSquare,
    )
    MOI.Bridges.add_bridge(bridged, MOI.Bridges.Constraint.SquareBridge{T})
    @test_throws MOI.UnsupportedConstraint{
        MOI.VectorOfVariables,
        MOI.PositiveSemidefiniteConeSquare,
    } MOI.Bridges.bridge_type(bridged, MOI.PositiveSemidefiniteConeSquare)
    @test MOI.Bridges._dist(
        bridged.graph,
        MOI.Bridges.node(bridged, MOI.PositiveSemidefiniteConeSquare),
    ) == MOI.Bridges.INFINITY
    @test sprint(MOI.Bridges.print_graph, bridged) == """
Bridge graph with 1 variable nodes, 0 constraint nodes and 0 objective nodes.
 [1] constrained variables in `MOI.PositiveSemidefiniteConeSquare` are not supported
"""
    @test MOI.supports_constraint(
        bridged,
        MOI.VectorOfVariables,
        MOI.PositiveSemidefiniteConeSquare,
    )
    @test sprint(MOI.Bridges.print_graph, bridged) ==
          MOI.Utilities.replace_acronym(
        """
Bridge graph with 1 variable nodes, 1 constraint nodes and 0 objective nodes.
 [1] constrained variables in `MOI.PositiveSemidefiniteConeSquare` are not supported
 (1) `MOI.VectorOfVariables`-in-`MOI.PositiveSemidefiniteConeSquare` constraints are bridged (distance 1) by $(MOI.Bridges.Constraint.SquareBridge{T,MOI.VectorOfVariables,MOI.ScalarAffineFunction{T},MOI.PositiveSemidefiniteConeTriangle,MOI.PositiveSemidefiniteConeSquare}).
""",
    )
    MOI.Bridges.add_bridge(
        bridged,
        MOI.Bridges.Constraint.VectorFunctionizeBridge{T},
    )
    @test_throws MOI.UnsupportedConstraint{
        MOI.VectorOfVariables,
        MOI.PositiveSemidefiniteConeSquare,
    } MOI.Bridges.bridge_type(bridged, MOI.PositiveSemidefiniteConeSquare)
    @test MOI.Bridges._dist(
        bridged.graph,
        MOI.Bridges.node(bridged, MOI.PositiveSemidefiniteConeSquare),
    ) == 6
    @test sprint(MOI.Bridges.print_graph, bridged) ==
          MOI.Utilities.replace_acronym(
        """
Bridge graph with 1 variable nodes, 3 constraint nodes and 0 objective nodes.
 [1] constrained variables in `MOI.PositiveSemidefiniteConeSquare` are supported (distance 6) by adding free variables and then constrain them, see (1).
 (1) `MOI.VectorAffineFunction{$T}`-in-`MOI.PositiveSemidefiniteConeSquare` constraints are bridged (distance 3) by $(MOI.Bridges.Constraint.SquareBridge{T,MOI.VectorAffineFunction{T},MOI.ScalarAffineFunction{T},MOI.PositiveSemidefiniteConeTriangle,MOI.PositiveSemidefiniteConeSquare}).
 (2) `MOI.VectorAffineFunction{$T}`-in-`MOI.Zeros` constraints are bridged (distance 1) by $(MOI.Bridges.Constraint.ScalarizeBridge{T,MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}).
 (3) `MOI.VectorAffineFunction{$T}`-in-`MOI.PositiveSemidefiniteConeTriangle` constraints are bridged (distance 2) by $(MOI.Bridges.Constraint.VectorSlackBridge{T,MOI.VectorAffineFunction{T},MOI.PositiveSemidefiniteConeTriangle}).
""",
    )
    @test !MOI.supports_constraint(
        bridged,
        MOI.ScalarAffineFunction{T},
        MOI.GreaterThan{T},
    )
    MOI.Bridges.add_bridge(bridged, MOI.Bridges.Constraint.VectorizeBridge{T})
    @test MOI.supports_constraint(
        bridged,
        MOI.ScalarAffineFunction{T},
        MOI.GreaterThan{T},
    )
    @test MOI.Bridges.bridge_type(
        bridged,
        MOI.ScalarAffineFunction{T},
        MOI.GreaterThan{T},
    ) == MOI.Bridges.Constraint.VectorizeBridge{
        T,
        MOI.VectorAffineFunction{T},
        MOI.Nonnegatives,
        MOI.ScalarAffineFunction{T},
    }
    @test !MOI.supports_constraint(
        bridged,
        MOI.ScalarQuadraticFunction{T},
        MOI.GreaterThan{T},
    )
    @test !MOI.supports_constraint(
        bridged,
        MOI.ScalarQuadraticFunction{T},
        MOI.LessThan{T},
    )
    MOI.Bridges.add_bridge(bridged, MOI.Bridges.Constraint.QuadtoSOCBridge{T})
    @test MOI.supports_constraint(
        bridged,
        MOI.ScalarQuadraticFunction{T},
        MOI.GreaterThan{T},
    )
    @test MOI.Bridges.bridge_type(
        bridged,
        MOI.ScalarQuadraticFunction{T},
        MOI.GreaterThan{T},
    ) == MOI.Bridges.Constraint.QuadtoSOCBridge{T}
    @test MOI.supports_constraint(
        bridged,
        MOI.ScalarQuadraticFunction{T},
        MOI.LessThan{T},
    )
    @test MOI.Bridges.bridge_type(
        bridged,
        MOI.ScalarQuadraticFunction{T},
        MOI.LessThan{T},
    ) == MOI.Bridges.Constraint.QuadtoSOCBridge{T}
    F = MOI.ScalarQuadraticFunction{T}
    attr = MOI.ObjectiveFunction{F}()
    @test !MOI.supports(bridged, MOI.ObjectiveFunction{MOI.VariableIndex}())
    @test !MOI.supports(bridged, attr)
    err = MOI.UnsupportedAttribute(attr)
    @test_throws err MOI.Bridges.bridge_type(bridged, F)
    MOI.Bridges.add_bridge(bridged, MOI.Bridges.Objective.SlackBridge{T})
    @test !MOI.supports(bridged, MOI.ObjectiveFunction{MOI.VariableIndex}())
    @test !MOI.supports(bridged, attr)
    MOI.Bridges.add_bridge(bridged, MOI.Bridges.Objective.FunctionizeBridge{T})
    @test MOI.supports(bridged, MOI.ObjectiveFunction{MOI.VariableIndex}())
    @test MOI.Bridges.bridge_type(bridged, MOI.VariableIndex) ==
          MOI.Bridges.Objective.FunctionizeBridge{T}
    @test MOI.supports(bridged, attr)
    @test MOI.Bridges.bridge_type(bridged, F) ==
          MOI.Bridges.Objective.SlackBridge{T,F,F}
    return
end

function test_SDPA_format()
    _test_SDPA_format(Float64)
    _test_SDPA_format(Int)
    return
end

function test_SDPA_debug()
    _test_SDPA_debug(Float64)
    _test_SDPA_debug(Int)
    return
end

function _test_SDPA_debug(T)
    model = SDPAModel{T}()
    bridged = MOI.Bridges.LazyBridgeOptimizer(model)
    function debug_string(f, args...)
        s = IOBuffer()
        f(bridged, args...; io = s)
        return String(resize!(s.data, s.size))
    end
    @testset "LessThan variables" begin
        S = MOI.LessThan{T}
        @test debug_string(
            MOI.Bridges.debug_supports_add_constrained_variable,
            S,
        ) == """
Constrained variables in `MOI.LessThan{$T}` are not supported and cannot be bridged into supported constrained variables and constraints. See details below:
 [1] constrained variables in `MOI.LessThan{$T}` are not supported because no added bridge supports bridging it.
   Cannot add free variables and then constrain them because free variables are bridged but no functionize bridge was added.
"""
        @test sprint(MOI.Bridges.print_graph, bridged) == """
Bridge graph with 1 variable nodes, 0 constraint nodes and 0 objective nodes.
 [1] constrained variables in `MOI.LessThan{$T}` are not supported
"""
        MOI.Bridges.add_bridge(bridged, MOI.Bridges.Variable.VectorizeBridge{T})
        @test debug_string(
            MOI.Bridges.debug_supports_add_constrained_variable,
            S,
        ) == MOI.Utilities.replace_acronym(
            """
Constrained variables in `MOI.LessThan{$T}` are not supported and cannot be bridged into supported constrained variables and constraints. See details below:
 [1] constrained variables in `MOI.LessThan{$T}` are not supported because:
   Cannot use `$(MOI.Bridges.Variable.VectorizeBridge{T,MOI.Nonpositives})` because:
   [2] constrained variables in `MOI.Nonpositives` are not supported
   Cannot add free variables and then constrain them because free variables are bridged but no functionize bridge was added.
 [2] constrained variables in `MOI.Nonpositives` are not supported because no added bridge supports bridging it.
   Cannot add free variables and then constrain them because free variables are bridged but no functionize bridge was added.
""",
        )
        MOI.Bridges.add_bridge(
            bridged,
            MOI.Bridges.Variable.NonposToNonnegBridge{T},
        )
        @test debug_string(
            MOI.Bridges.debug_supports_add_constrained_variable,
            S,
        ) == "Constrained variables in `MOI.LessThan{$T}` are supported.\n"
        @test sprint(MOI.Bridges.print_graph, bridged) ==
              MOI.Utilities.replace_acronym(
            """
Bridge graph with 2 variable nodes, 0 constraint nodes and 0 objective nodes.
 [1] constrained variables in `MOI.LessThan{$T}` are bridged (distance 2) by $(MOI.Bridges.Variable.VectorizeBridge{T,MOI.Nonpositives}).
 [2] constrained variables in `MOI.Nonpositives` are bridged (distance 1) by $(MOI.Bridges.Variable.NonposToNonnegBridge{T}).
""",
        )
    end
    bridged = MOI.Bridges.LazyBridgeOptimizer(model)
    @testset "LessThan variables with ScalarFunctionizeBridge" begin
        MOI.Bridges.add_bridge(
            bridged,
            MOI.Bridges.Constraint.ScalarFunctionizeBridge{T},
        )
        S = MOI.LessThan{T}
        @test debug_string(
            MOI.Bridges.debug_supports_add_constrained_variable,
            S,
        ) == """
Constrained variables in `MOI.LessThan{$T}` are not supported and cannot be bridged into supported constrained variables and constraints. See details below:
 [1] constrained variables in `MOI.LessThan{$T}` are not supported because no added bridge supports bridging it.
   Cannot add free variables and then constrain them because:
   (1) `MOI.ScalarAffineFunction{$T}`-in-`MOI.LessThan{$T}` constraints are not supported
 (1) `MOI.ScalarAffineFunction{$T}`-in-`MOI.LessThan{$T}` constraints are not supported because no added bridge supports bridging it.
"""
        @test sprint(MOI.Bridges.print_graph, bridged) == """
Bridge graph with 1 variable nodes, 1 constraint nodes and 0 objective nodes.
 [1] constrained variables in `MOI.LessThan{$T}` are not supported
 (1) `MOI.ScalarAffineFunction{$T}`-in-`MOI.LessThan{$T}` constraints are not supported
"""
        MOI.Bridges.add_bridge(bridged, MOI.Bridges.Variable.VectorizeBridge{T})
        @test debug_string(
            MOI.Bridges.debug_supports_add_constrained_variable,
            S,
        ) == MOI.Utilities.replace_acronym(
            """
Constrained variables in `MOI.LessThan{$T}` are not supported and cannot be bridged into supported constrained variables and constraints. See details below:
 [1] constrained variables in `MOI.LessThan{$T}` are not supported because:
   Cannot use `$(MOI.Bridges.Variable.VectorizeBridge{T,MOI.Nonpositives})` because:
   [2] constrained variables in `MOI.Nonpositives` are not supported
   Cannot add free variables and then constrain them because:
   (1) `MOI.ScalarAffineFunction{$T}`-in-`MOI.LessThan{$T}` constraints are not supported
 [2] constrained variables in `MOI.Nonpositives` are not supported because no added bridge supports bridging it.
   Cannot add free variables and then constrain them because free variables are bridged but no functionize bridge was added.
 (1) `MOI.ScalarAffineFunction{$T}`-in-`MOI.LessThan{$T}` constraints are not supported because no added bridge supports bridging it.
""",
        )
        MOI.Bridges.add_bridge(
            bridged,
            MOI.Bridges.Variable.NonposToNonnegBridge{T},
        )
        @test debug_string(
            MOI.Bridges.debug_supports_add_constrained_variable,
            S,
        ) == "Constrained variables in `MOI.LessThan{$T}` are supported.\n"
        @test sprint(MOI.Bridges.print_graph, bridged) ==
              MOI.Utilities.replace_acronym(
            """
Bridge graph with 2 variable nodes, 1 constraint nodes and 0 objective nodes.
 [1] constrained variables in `MOI.LessThan{$T}` are bridged (distance 2) by $(MOI.Bridges.Variable.VectorizeBridge{T,MOI.Nonpositives}).
 [2] constrained variables in `MOI.Nonpositives` are bridged (distance 1) by $(MOI.Bridges.Variable.NonposToNonnegBridge{T}).
 (1) `MOI.ScalarAffineFunction{$T}`-in-`MOI.LessThan{$T}` constraints are not supported
""",
        )
    end
    bridged = MOI.Bridges.LazyBridgeOptimizer(model)
    @testset "Interval constraint" begin
        F = MOI.ScalarAffineFunction{T}
        S = MOI.Interval{T}
        @test debug_string(MOI.Bridges.debug_supports_constraint, F, S) == """
`MOI.ScalarAffineFunction{$T}`-in-`MOI.Interval{$T}` constraints are not supported and cannot be bridged into supported constrained variables and constraints. See details below:
 (1) `MOI.ScalarAffineFunction{$T}`-in-`MOI.Interval{$T}` constraints are not supported because no added bridge supports bridging it.
"""
        MOI.Bridges.add_bridge(
            bridged,
            MOI.Bridges.Constraint.SplitIntervalBridge{T},
        )
        @test debug_string(MOI.Bridges.debug_supports_constraint, F, S) ==
              MOI.Utilities.replace_acronym(
            """
`MOI.ScalarAffineFunction{$T}`-in-`MOI.Interval{$T}` constraints are not supported and cannot be bridged into supported constrained variables and constraints. See details below:
 (1) `MOI.ScalarAffineFunction{$T}`-in-`MOI.Interval{$T}` constraints are not supported because:
   Cannot use `$(MOI.Bridges.Constraint.SplitIntervalBridge{T,MOI.ScalarAffineFunction{T},MOI.Interval{T},MOI.GreaterThan{T},MOI.LessThan{T}})` because:
   (2) `MOI.ScalarAffineFunction{$T}`-in-`MOI.GreaterThan{$T}` constraints are not supported
   (3) `MOI.ScalarAffineFunction{$T}`-in-`MOI.LessThan{$T}` constraints are not supported
 (2) `MOI.ScalarAffineFunction{$T}`-in-`MOI.GreaterThan{$T}` constraints are not supported because no added bridge supports bridging it.
 (3) `MOI.ScalarAffineFunction{$T}`-in-`MOI.LessThan{$T}` constraints are not supported because no added bridge supports bridging it.
""",
        )
        MOI.Bridges.add_bridge(
            bridged,
            MOI.Bridges.Constraint.ScalarSlackBridge{T},
        )
        @test debug_string(MOI.Bridges.debug_supports_constraint, F, S) ==
              MOI.Utilities.replace_acronym(
            """
`MOI.ScalarAffineFunction{$T}`-in-`MOI.Interval{$T}` constraints are not supported and cannot be bridged into supported constrained variables and constraints. See details below:
 [1] constrained variables in `MOI.GreaterThan{$T}` are not supported because no added bridge supports bridging it.
   Cannot add free variables and then constrain them because free variables are bridged but no functionize bridge was added.
 [2] constrained variables in `MOI.LessThan{$T}` are not supported because no added bridge supports bridging it.
   Cannot add free variables and then constrain them because free variables are bridged but no functionize bridge was added.
 [3] constrained variables in `MOI.Interval{$T}` are not supported because no added bridge supports bridging it.
   Cannot add free variables and then constrain them because free variables are bridged but no functionize bridge was added.
 (1) `MOI.ScalarAffineFunction{$T}`-in-`MOI.Interval{$T}` constraints are not supported because:
   Cannot use `$(MOI.Bridges.Constraint.SplitIntervalBridge{T,MOI.ScalarAffineFunction{T},MOI.Interval{T},MOI.GreaterThan{T},MOI.LessThan{T}})` because:
   (2) `MOI.ScalarAffineFunction{$T}`-in-`MOI.GreaterThan{$T}` constraints are not supported
   (3) `MOI.ScalarAffineFunction{$T}`-in-`MOI.LessThan{$T}` constraints are not supported
   Cannot use `$(MOI.Bridges.Constraint.ScalarSlackBridge{T,MOI.ScalarAffineFunction{T},MOI.Interval{T}})` because:
   [3] constrained variables in `MOI.Interval{$T}` are not supported
 (2) `MOI.ScalarAffineFunction{$T}`-in-`MOI.GreaterThan{$T}` constraints are not supported because:
   Cannot use `$(MOI.Bridges.Constraint.ScalarSlackBridge{T,MOI.ScalarAffineFunction{T},MOI.GreaterThan{T}})` because:
   [1] constrained variables in `MOI.GreaterThan{$T}` are not supported
 (3) `MOI.ScalarAffineFunction{$T}`-in-`MOI.LessThan{$T}` constraints are not supported because:
   Cannot use `$(MOI.Bridges.Constraint.ScalarSlackBridge{T,MOI.ScalarAffineFunction{T},MOI.LessThan{T}})` because:
   [2] constrained variables in `MOI.LessThan{$T}` are not supported
""",
        )
        MOI.Bridges.add_bridge(bridged, MOI.Bridges.Variable.VectorizeBridge{T})
        @test debug_string(MOI.Bridges.debug_supports_constraint, F, S) ==
              MOI.Utilities.replace_acronym(
            """
`MOI.ScalarAffineFunction{$T}`-in-`MOI.Interval{$T}` constraints are not supported and cannot be bridged into supported constrained variables and constraints. See details below:
 [2] constrained variables in `MOI.LessThan{$T}` are not supported because:
   Cannot use `$(MOI.Bridges.Variable.VectorizeBridge{T,MOI.Nonpositives})` because:
   [3] constrained variables in `MOI.Nonpositives` are not supported
   Cannot add free variables and then constrain them because free variables are bridged but no functionize bridge was added.
 [3] constrained variables in `MOI.Nonpositives` are not supported because no added bridge supports bridging it.
   Cannot add free variables and then constrain them because free variables are bridged but no functionize bridge was added.
 [4] constrained variables in `MOI.Interval{$T}` are not supported because no added bridge supports bridging it.
   Cannot add free variables and then constrain them because free variables are bridged but no functionize bridge was added.
 (1) `MOI.ScalarAffineFunction{$T}`-in-`MOI.Interval{$T}` constraints are not supported because:
   Cannot use `$(MOI.Bridges.Constraint.SplitIntervalBridge{T,MOI.ScalarAffineFunction{T},MOI.Interval{T},MOI.GreaterThan{T},MOI.LessThan{T}})` because:
   (3) `MOI.ScalarAffineFunction{$T}`-in-`MOI.LessThan{$T}` constraints are not supported
   Cannot use `$(MOI.Bridges.Constraint.ScalarSlackBridge{T,MOI.ScalarAffineFunction{T},MOI.Interval{T}})` because:
   [4] constrained variables in `MOI.Interval{$T}` are not supported
 (3) `MOI.ScalarAffineFunction{$T}`-in-`MOI.LessThan{$T}` constraints are not supported because:
   Cannot use `$(MOI.Bridges.Constraint.ScalarSlackBridge{T,MOI.ScalarAffineFunction{T},MOI.LessThan{T}})` because:
   [2] constrained variables in `MOI.LessThan{$T}` are not supported
""",
        )
        MOI.Bridges.add_bridge(
            bridged,
            MOI.Bridges.Variable.NonposToNonnegBridge{T},
        )
        @test debug_string(MOI.Bridges.debug_supports_constraint, F, S) ==
              "`MOI.ScalarAffineFunction{$T}`-in-`MOI.Interval{$T}` constraints are supported.\n"
    end
    bridged = MOI.Bridges.LazyBridgeOptimizer(model)
    @testset "Quadratic objective" begin
        F = MOI.ScalarQuadraticFunction{T}
        attr = MOI.ObjectiveFunction{F}()
        @test debug_string(MOI.Bridges.debug_supports, attr) == """
Objective function of type `MOI.ScalarQuadraticFunction{$T}` is not supported and cannot be bridged into a supported objective function by adding only supported constrained variables and constraints. See details below:
 |1| objective function of type `MOI.ScalarQuadraticFunction{$T}` is not supported because no added bridge supports bridging it.
"""
        MOI.Bridges.add_bridge(bridged, MOI.Bridges.Objective.SlackBridge{T})
        @test debug_string(MOI.Bridges.debug_supports, attr) ==
              MOI.Utilities.replace_acronym(
            """
Objective function of type `MOI.ScalarQuadraticFunction{$T}` is not supported and cannot be bridged into a supported objective function by adding only supported constrained variables and constraints. See details below:
 (1) `MOI.ScalarQuadraticFunction{$T}`-in-`MOI.GreaterThan{$T}` constraints are not supported because no added bridge supports bridging it.
 (2) `MOI.ScalarQuadraticFunction{$T}`-in-`MOI.LessThan{$T}` constraints are not supported because no added bridge supports bridging it.
 |1| objective function of type `MOI.ScalarQuadraticFunction{$T}` is not supported because:
   Cannot use `$(MOI.Bridges.Objective.SlackBridge{T,MOI.ScalarQuadraticFunction{T},MOI.ScalarQuadraticFunction{T}})` because:
   (1) `MOI.ScalarQuadraticFunction{$T}`-in-`MOI.GreaterThan{$T}` constraints are not supported
   (2) `MOI.ScalarQuadraticFunction{$T}`-in-`MOI.LessThan{$T}` constraints are not supported
   |2| objective function of type `MOI.VariableIndex` is not supported
 |2| objective function of type `MOI.VariableIndex` is not supported because no added bridge supports bridging it.
""",
        )
        MOI.Bridges.add_bridge(
            bridged,
            MOI.Bridges.Objective.FunctionizeBridge{T},
        )
        MOI.Bridges.add_bridge(
            bridged,
            MOI.Bridges.Constraint.QuadtoSOCBridge{T},
        )
        @test debug_string(MOI.Bridges.debug_supports, attr) ==
              MOI.Utilities.replace_acronym(
            """
Objective function of type `MOI.ScalarQuadraticFunction{$T}` is not supported and cannot be bridged into a supported objective function by adding only supported constrained variables and constraints. See details below:
 (1) `MOI.ScalarQuadraticFunction{$T}`-in-`MOI.GreaterThan{$T}` constraints are not supported because:
   Cannot use `$(MOI.Bridges.Constraint.QuadtoSOCBridge{T})` because:
   (2) `MOI.VectorAffineFunction{$T}`-in-`MOI.RotatedSecondOrderCone` constraints are not supported
 (2) `MOI.VectorAffineFunction{$T}`-in-`MOI.RotatedSecondOrderCone` constraints are not supported because no added bridge supports bridging it.
 (3) `MOI.ScalarQuadraticFunction{$T}`-in-`MOI.LessThan{$T}` constraints are not supported because:
   Cannot use `$(MOI.Bridges.Constraint.QuadtoSOCBridge{T})` because:
   (2) `MOI.VectorAffineFunction{$T}`-in-`MOI.RotatedSecondOrderCone` constraints are not supported
 |1| objective function of type `MOI.ScalarQuadraticFunction{$T}` is not supported because:
   Cannot use `$(MOI.Bridges.Objective.SlackBridge{T,MOI.ScalarQuadraticFunction{T},MOI.ScalarQuadraticFunction{T}})` because:
   (1) `MOI.ScalarQuadraticFunction{$T}`-in-`MOI.GreaterThan{$T}` constraints are not supported
   (3) `MOI.ScalarQuadraticFunction{$T}`-in-`MOI.LessThan{$T}` constraints are not supported
""",
        )
        MOI.Bridges.add_bridge(
            bridged,
            MOI.Bridges.Constraint.VectorSlackBridge{T},
        )
        @test debug_string(MOI.Bridges.debug_supports, attr) ==
              MOI.Utilities.replace_acronym(
            """
Objective function of type `MOI.ScalarQuadraticFunction{$T}` is not supported and cannot be bridged into a supported objective function by adding only supported constrained variables and constraints. See details below:
 [1] constrained variables in `MOI.RotatedSecondOrderCone` are not supported because no added bridge supports bridging it.
   Cannot add free variables and then constrain them because free variables are bridged but no functionize bridge was added.
 (1) `MOI.ScalarQuadraticFunction{$T}`-in-`MOI.GreaterThan{$T}` constraints are not supported because:
   Cannot use `$(MOI.Bridges.Constraint.QuadtoSOCBridge{T})` because:
   (2) `MOI.VectorAffineFunction{$T}`-in-`MOI.RotatedSecondOrderCone` constraints are not supported
 (2) `MOI.VectorAffineFunction{$T}`-in-`MOI.RotatedSecondOrderCone` constraints are not supported because:
   Cannot use `$(MOI.Bridges.Constraint.VectorSlackBridge{T,MOI.VectorAffineFunction{T},MOI.RotatedSecondOrderCone})` because:
   [1] constrained variables in `MOI.RotatedSecondOrderCone` are not supported
   (3) `MOI.VectorAffineFunction{$T}`-in-`MOI.Zeros` constraints are not supported
 (3) `MOI.VectorAffineFunction{$T}`-in-`MOI.Zeros` constraints are not supported because no added bridge supports bridging it.
 (4) `MOI.ScalarQuadraticFunction{$T}`-in-`MOI.LessThan{$T}` constraints are not supported because:
   Cannot use `$(MOI.Bridges.Constraint.QuadtoSOCBridge{T})` because:
   (2) `MOI.VectorAffineFunction{$T}`-in-`MOI.RotatedSecondOrderCone` constraints are not supported
 |1| objective function of type `MOI.ScalarQuadraticFunction{$T}` is not supported because:
   Cannot use `$(MOI.Bridges.Objective.SlackBridge{T,MOI.ScalarQuadraticFunction{T},MOI.ScalarQuadraticFunction{T}})` because:
   (1) `MOI.ScalarQuadraticFunction{$T}`-in-`MOI.GreaterThan{$T}` constraints are not supported
   (4) `MOI.ScalarQuadraticFunction{$T}`-in-`MOI.LessThan{$T}` constraints are not supported
""",
        )
        MOI.Bridges.add_bridge(bridged, MOI.Bridges.Variable.RSOCtoPSDBridge{T})
        MOI.Bridges.add_bridge(
            bridged,
            MOI.Bridges.Constraint.ScalarFunctionizeBridge{T},
        )
        @test debug_string(MOI.Bridges.debug_supports, attr) ==
              MOI.Utilities.replace_acronym(
            """
Objective function of type `MOI.ScalarQuadraticFunction{$T}` is not supported and cannot be bridged into a supported objective function by adding only supported constrained variables and constraints. See details below:
 (1) `MOI.ScalarQuadraticFunction{$T}`-in-`MOI.GreaterThan{$T}` constraints are not supported because:
   Cannot use `$(MOI.Bridges.Constraint.QuadtoSOCBridge{T})` because:
   (2) `MOI.VectorAffineFunction{$T}`-in-`MOI.RotatedSecondOrderCone` constraints are not supported
 (2) `MOI.VectorAffineFunction{$T}`-in-`MOI.RotatedSecondOrderCone` constraints are not supported because:
   Cannot use `$(MOI.Bridges.Constraint.VectorSlackBridge{T,MOI.VectorAffineFunction{T},MOI.RotatedSecondOrderCone})` because:
   (4) `MOI.VectorAffineFunction{$T}`-in-`MOI.Zeros` constraints are not supported
 (4) `MOI.VectorAffineFunction{$T}`-in-`MOI.Zeros` constraints are not supported because no added bridge supports bridging it.
 (5) `MOI.ScalarQuadraticFunction{$T}`-in-`MOI.LessThan{$T}` constraints are not supported because:
   Cannot use `$(MOI.Bridges.Constraint.QuadtoSOCBridge{T})` because:
   (2) `MOI.VectorAffineFunction{$T}`-in-`MOI.RotatedSecondOrderCone` constraints are not supported
 |1| objective function of type `MOI.ScalarQuadraticFunction{$T}` is not supported because:
   Cannot use `$(MOI.Bridges.Objective.SlackBridge{T,MOI.ScalarQuadraticFunction{T},MOI.ScalarQuadraticFunction{T}})` because:
   (1) `MOI.ScalarQuadraticFunction{$T}`-in-`MOI.GreaterThan{$T}` constraints are not supported
   (5) `MOI.ScalarQuadraticFunction{$T}`-in-`MOI.LessThan{$T}` constraints are not supported
""",
        )
        MOI.Bridges.add_bridge(
            bridged,
            MOI.Bridges.Constraint.ScalarizeBridge{T},
        )
        @test debug_string(MOI.Bridges.debug_supports, attr) ==
              "Objective function of type `MOI.ScalarQuadraticFunction{$T}` is supported.\n"
        @test sprint(MOI.Bridges.print_graph, bridged) ==
              MOI.Utilities.replace_acronym(
            """
Bridge graph with 1 variable nodes, 5 constraint nodes and 2 objective nodes.
 [1] constrained variables in `MOI.RotatedSecondOrderCone` are bridged (distance 2) by $(MOI.Bridges.Variable.RSOCtoPSDBridge{T}).
 (1) `MOI.ScalarQuadraticFunction{$T}`-in-`MOI.GreaterThan{$T}` constraints are bridged (distance 5) by $(MOI.Bridges.Constraint.QuadtoSOCBridge{T}).
 (2) `MOI.VectorAffineFunction{$T}`-in-`MOI.RotatedSecondOrderCone` constraints are bridged (distance 4) by $(MOI.Bridges.Constraint.VectorSlackBridge{T,MOI.VectorAffineFunction{T},MOI.RotatedSecondOrderCone}).
 (3) `MOI.VariableIndex`-in-`MOI.EqualTo{$T}` constraints are bridged (distance 1) by $(MOI.Bridges.Constraint.ScalarFunctionizeBridge{T,MOI.EqualTo{T}}).
 (4) `MOI.VectorAffineFunction{$T}`-in-`MOI.Zeros` constraints are bridged (distance 1) by $(MOI.Bridges.Constraint.ScalarizeBridge{T,MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}).
 (5) `MOI.ScalarQuadraticFunction{$T}`-in-`MOI.LessThan{$T}` constraints are bridged (distance 5) by $(MOI.Bridges.Constraint.QuadtoSOCBridge{T}).
 |1| objective function of type `MOI.ScalarQuadraticFunction{$T}` is bridged (distance 12) by $(MOI.Bridges.Objective.SlackBridge{T,MOI.ScalarQuadraticFunction{T},MOI.ScalarQuadraticFunction{T}}).
 |2| objective function of type `MOI.VariableIndex` is bridged (distance 1) by $(MOI.Bridges.Objective.FunctionizeBridge{T}).
""",
        )
    end
    bridged = MOI.Bridges.LazyBridgeOptimizer(model)
    @testset "Exponential constraint" begin
        F = MOI.VectorAffineFunction{T}
        S = MOI.ExponentialCone
        @test debug_string(MOI.Bridges.debug_supports_constraint, F, S) == """
                                                                    `MOI.VectorAffineFunction{$T}`-in-`MOI.ExponentialCone` constraints are not supported and cannot be bridged into supported constrained variables and constraints. See details below:
                                                                     (1) `MOI.VectorAffineFunction{$T}`-in-`MOI.ExponentialCone` constraints are not supported because no added bridge supports bridging it.
                                                                    """
        MOI.Bridges.add_bridge(
            bridged,
            MOI.Bridges.Constraint.VectorSlackBridge{T},
        )
        @test debug_string(MOI.Bridges.debug_supports_constraint, F, S) ==
              MOI.Utilities.replace_acronym(
            """
`MOI.VectorAffineFunction{$T}`-in-`MOI.ExponentialCone` constraints are not supported and cannot be bridged into supported constrained variables and constraints. See details below:
 [1] constrained variables in `MOI.ExponentialCone` are not supported because no added bridge supports bridging it.
   Cannot add free variables and then constrain them because free variables are bridged but no functionize bridge was added.
 (1) `MOI.VectorAffineFunction{$T}`-in-`MOI.ExponentialCone` constraints are not supported because:
   Cannot use `$(MOI.Bridges.Constraint.VectorSlackBridge{T,MOI.VectorAffineFunction{T},MOI.ExponentialCone})` because:
   [1] constrained variables in `MOI.ExponentialCone` are not supported
   (2) `MOI.VectorAffineFunction{$T}`-in-`MOI.Zeros` constraints are not supported
 (2) `MOI.VectorAffineFunction{$T}`-in-`MOI.Zeros` constraints are not supported because no added bridge supports bridging it.
""",
        )
        MOI.Bridges.add_bridge(
            bridged,
            MOI.Bridges.Constraint.VectorFunctionizeBridge{T},
        )
        @test debug_string(MOI.Bridges.debug_supports_constraint, F, S) ==
              MOI.Utilities.replace_acronym(
            """
`MOI.VectorAffineFunction{$T}`-in-`MOI.ExponentialCone` constraints are not supported and cannot be bridged into supported constrained variables and constraints. See details below:
 [1] constrained variables in `MOI.ExponentialCone` are not supported because no added bridge supports bridging it.
   Cannot add free variables and then constrain them because:
   (1) `MOI.VectorAffineFunction{$T}`-in-`MOI.ExponentialCone` constraints are not supported
 (1) `MOI.VectorAffineFunction{$T}`-in-`MOI.ExponentialCone` constraints are not supported because:
   Cannot use `$(MOI.Bridges.Constraint.VectorSlackBridge{T,MOI.VectorAffineFunction{T},MOI.ExponentialCone})` because:
   [1] constrained variables in `MOI.ExponentialCone` are not supported
   (2) `MOI.VectorAffineFunction{$T}`-in-`MOI.Zeros` constraints are not supported
 (2) `MOI.VectorAffineFunction{$T}`-in-`MOI.Zeros` constraints are not supported because no added bridge supports bridging it.
""",
        )
    end
end

function _test_continuous_SDPAModel(T)
    model = SDPAModel{T}()
    bridged = MOI.Bridges.full_bridge_optimizer(model, T)
    # For `ScalarAffineFunction`-in-`GreaterThan`,
    # `Constraint.ScalarSlackBridge` -> `Variable.VectorizeBridge`
    # is equivalent to
    # `Constraint.VectorizeBridge` -> `Constraint.VectorSlackBridge`
    # however, `Variable.VectorizeBridge` do not support modification of the
    # set hence it makes some tests of `contlineartest` fail so we disable it.
    MOI.Bridges.remove_bridge(
        bridged,
        MOI.Bridges.Constraint.ScalarSlackBridge{T},
    )
    MOI.Test.runtests(
        bridged,
        MOI.Test.Config(T; exclude = Any[MOI.optimize!]),
        include = ["test_linear_"],
        exclude = [
            "test_linear_Indicator",
            "test_linear_Semi",
            "test_linear_SOS",
            "test_linear_integer",
        ],
    )
    return
end

function test_continuous_SDPAModel()
    _test_continuous_SDPAModel(Float64)
    _test_continuous_SDPAModel(Rational{Int})
    return
end

function test_SDPAModel_with_bridges_and_caching()
    # This tests that the computation of the reverse dict in the
    # caching optimizer works with negative indices
    cached = MOI.Utilities.CachingOptimizer(
        MOI.Utilities.Model{Float64}(),
        MOI.Utilities.MANUAL,
    )
    vi_cache = MOI.add_variable(cached)
    f(vi) = 1.0 * vi
    ci_cache = MOI.add_constraint(cached, f(vi_cache), MOI.EqualTo(1.0))
    model = SDPAModel{Float64}()
    bridged = MOI.Bridges.full_bridge_optimizer(model, Float64)
    MOI.Utilities.reset_optimizer(cached, bridged)
    MOI.Utilities.attach_optimizer(cached)
    vi_bridged = first(MOI.get(bridged, MOI.ListOfVariableIndices()))
    @test vi_bridged == MOI.VariableIndex(-1)
    @test cached.model_to_optimizer_map[vi_cache] == vi_bridged
    @test cached.optimizer_to_model_map[vi_bridged] == vi_cache
    vis_sdpa = MOI.get(model, MOI.ListOfVariableIndices())
    F = typeof(f(vi_cache))
    S = MOI.EqualTo{Float64}
    ci_bridged = first(MOI.get(model, MOI.ListOfConstraintIndices{F,S}()))
    attr = MOI.ConstraintFunction()
    @test MOI.get(bridged, attr, ci_bridged) ≈ f(vi_bridged)
    cis = MOI.get(model, MOI.ListOfConstraintIndices{F,S}())
    @test length(cis) == 1
    ci_sdpa = first(cis)
    func = [1.0, -1.0]' * vis_sdpa
    @test MOI.get(model, attr, ci_sdpa) ≈ func
    return
end

function test_conic_SDPAModel()
    model = SDPAModel{Float64}()
    bridged = MOI.Bridges.full_bridge_optimizer(model, Float64)
    MOI.Test.test_conic_PositiveSemidefiniteConeSquare_VectorOfVariables(
        bridged,
        MOI.Test.Config(exclude = Any[MOI.optimize!]),
    )
    return
end

# Model not supporting RotatedSecondOrderCone
MOI.Utilities.@model(
    NoRSOCModel,
    (),
    (MOI.EqualTo, MOI.GreaterThan, MOI.LessThan),
    (
        MOI.Zeros,
        MOI.Nonnegatives,
        MOI.Nonpositives,
        MOI.SecondOrderCone,
        MOI.NormInfinityCone,
        MOI.NormOneCone,
        MOI.ExponentialCone,
        MOI.PositiveSemidefiniteConeTriangle,
    ),
    (MOI.PowerCone,),
    (),
    (MOI.ScalarAffineFunction, MOI.ScalarQuadraticFunction),
    (MOI.VectorOfVariables,),
    (MOI.VectorAffineFunction, MOI.VectorQuadraticFunction)
)

function _test_constrained_variables_in_RSOC(T)
    bridged = MOI.Bridges.full_bridge_optimizer(NoRSOCModel{T}(), T)
    @test MOI.supports_constraint(
        bridged,
        MOI.VectorOfVariables,
        MOI.RotatedSecondOrderCone,
    )
    # It should be selected over `MOI.Bridges.Variable.RSOCtoPSDBridge` even if they
    # are tied in terms of number of bridges because it is added first in
    # `MOI.Bridges.full_bridge_optimizer`.
    @test MOI.Bridges.bridge_type(bridged, MOI.RotatedSecondOrderCone) ==
          MOI.Bridges.Variable.RSOCtoSOCBridge{T}
    x, cx =
        MOI.add_constrained_variables(bridged, MOI.RotatedSecondOrderCone(3))
    for i in 1:3
        @test MOI.Bridges.bridge(bridged, x[i]) isa
              MOI.Bridges.Variable.RSOCtoSOCBridge{T}
    end
    @test MOI.Bridges.bridge(bridged, cx) isa
          MOI.Bridges.Variable.RSOCtoSOCBridge{T}
    return
end

function test_constrained_variables_in_RSOC()
    # We only use floating point types as there is √2
    _test_constrained_variables_in_RSOC(Float64)
    _test_constrained_variables_in_RSOC(BigFloat)
    return
end

# Model not supporting VectorOfVariables and VariableIndex
MOI.Utilities.@model(
    NoVariableModel,
    (MOI.ZeroOne, MOI.Integer),
    (MOI.EqualTo, MOI.GreaterThan, MOI.LessThan),
    (MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives, MOI.SecondOrderCone),
    (),
    (),
    (MOI.ScalarAffineFunction,),
    (),
    (MOI.VectorAffineFunction,)
)
function MOI.supports_constraint(
    ::NoVariableModel{T},
    ::Type{MOI.VariableIndex},
    ::Type{<:MOI.Utilities.SUPPORTED_VARIABLE_SCALAR_SETS{T}},
) where {T}
    return false
end

function _test_continuous_conic_with_NoVariableModel(T)
    model = NoVariableModel{T}()
    bridged = MOI.Bridges.full_bridge_optimizer(model, T)
    # The best variable bridge for SOC and RSOC are respectively `SOCtoRSOC` and `RSOCtoSOC`.
    # This forms a cycle because using the variable bridges is not in the shortest path.
    # Therefore they should not be used when calling `add_constrained_variables`.
    # Moreover, the printing should say that the variable bridge is not used.
    @test MOI.supports_constraint(
        bridged,
        MOI.VectorOfVariables,
        MOI.SecondOrderCone,
    )
    @test MOI.Bridges.bridge_type(bridged, MOI.SecondOrderCone) ==
          MOI.Bridges.Variable.SOCtoRSOCBridge{T}
    @test !MOI.Bridges.is_variable_bridged(bridged, MOI.RotatedSecondOrderCone)
    @test MOI.supports_constraint(
        bridged,
        MOI.VectorOfVariables,
        MOI.RotatedSecondOrderCone,
    )
    @test MOI.Bridges.bridge_type(bridged, MOI.RotatedSecondOrderCone) ==
          MOI.Bridges.Variable.RSOCtoSOCBridge{T}
    @test !MOI.Bridges.is_variable_bridged(bridged, MOI.RotatedSecondOrderCone)
    x, cx = MOI.add_constrained_variables(bridged, MOI.SecondOrderCone(3))
    @test !any(v -> MOI.Bridges.is_bridged(bridged, v), x)
    @test !MOI.Bridges.is_variable_bridged(bridged, cx)
    @test MOI.Bridges.bridge(bridged, cx) isa
          MOI.Bridges.Constraint.VectorFunctionizeBridge{T,MOI.SecondOrderCone}
    y, cy =
        MOI.add_constrained_variables(bridged, MOI.RotatedSecondOrderCone(4))
    @test !any(v -> MOI.Bridges.is_bridged(bridged, v), y)
    @test !MOI.Bridges.is_variable_bridged(bridged, cy)
    @test MOI.Bridges.bridge(bridged, cy) isa
          MOI.Bridges.Constraint.RSOCtoSOCBridge{T}
    @test sprint(MOI.Bridges.print_graph, bridged) ==
          MOI.Utilities.replace_acronym(
        """
Bridge graph with 5 variable nodes, 11 constraint nodes and 0 objective nodes.
 [1] constrained variables in `MOI.RotatedSecondOrderCone` are supported (distance 2) by adding free variables and then constrain them, see (3).
 [2] constrained variables in `MOI.PositiveSemidefiniteConeTriangle` are not supported
 [3] constrained variables in `MOI.SecondOrderCone` are supported (distance 2) by adding free variables and then constrain them, see (1).
 [4] constrained variables in `MOI.Nonnegatives` are supported (distance 2) by adding free variables and then constrain them, see (6).
 [5] constrained variables in `MOI.Interval{$T}` are supported (distance 3) by adding free variables and then constrain them, see (8).
 (1) `MOI.VectorOfVariables`-in-`MOI.SecondOrderCone` constraints are bridged (distance 1) by $(MOI.Bridges.Constraint.VectorFunctionizeBridge{T,MOI.SecondOrderCone}).
 (2) `MOI.VectorAffineFunction{$T}`-in-`MOI.RotatedSecondOrderCone` constraints are bridged (distance 1) by $(MOI.Bridges.Constraint.RSOCtoSOCBridge{T,MOI.VectorAffineFunction{T},MOI.VectorAffineFunction{T}}).
 (3) `MOI.VectorOfVariables`-in-`MOI.RotatedSecondOrderCone` constraints are bridged (distance 1) by $(MOI.Bridges.Constraint.RSOCtoSOCBridge{T,MOI.VectorAffineFunction{T},MOI.VectorOfVariables}).
 (4) `MOI.VectorAffineFunction{$T}`-in-`MOI.PositiveSemidefiniteConeTriangle` constraints are not supported
 (5) `MOI.VectorOfVariables`-in-`MOI.PositiveSemidefiniteConeTriangle` constraints are not supported
 (6) `MOI.VectorOfVariables`-in-`MOI.Nonnegatives` constraints are bridged (distance 1) by $(MOI.Bridges.Constraint.NonnegToNonposBridge{T,MOI.VectorAffineFunction{T},MOI.VectorOfVariables}).
 (7) `MOI.VariableIndex`-in-`MOI.GreaterThan{$T}` constraints are bridged (distance 1) by $(MOI.Bridges.Constraint.GreaterToLessBridge{T,MOI.ScalarAffineFunction{T},MOI.VariableIndex}).
 (8) `MOI.VariableIndex`-in-`MOI.Interval{$T}` constraints are bridged (distance 2) by $(MOI.Bridges.Constraint.ScalarFunctionizeBridge{T,MOI.Interval{T}}).
 (9) `MOI.ScalarAffineFunction{$T}`-in-`MOI.Interval{$T}` constraints are bridged (distance 1) by $(MOI.Bridges.Constraint.SplitIntervalBridge{T,MOI.ScalarAffineFunction{T},MOI.Interval{T},MOI.GreaterThan{T},MOI.LessThan{T}}).
 (10) `MOI.VariableIndex`-in-`MOI.LessThan{$T}` constraints are bridged (distance 1) by $(MOI.Bridges.Constraint.LessToGreaterBridge{T,MOI.ScalarAffineFunction{T},MOI.VariableIndex}).
 (11) `MOI.VariableIndex`-in-`MOI.EqualTo{$T}` constraints are bridged (distance 1) by $(MOI.Bridges.Constraint.VectorizeBridge{T,MOI.VectorAffineFunction{T},MOI.Zeros,MOI.VariableIndex}).
""",
    )
    return
end

function test_continuous_conic_with_NoVariableModel()
    _test_continuous_conic_with_NoVariableModel(Float64)
    _test_continuous_conic_with_NoVariableModel(Float32)
    return
end

MOI.Utilities.@model(
    OnlyNonnegVAF,
    (),
    (),
    (MOI.Nonnegatives,),
    (),
    (),
    (),
    (),
    (MOI.VectorAffineFunction,)
)

function MOI.supports_constraint(
    ::OnlyNonnegVAF{T},
    ::Type{MOI.VariableIndex},
    ::Type{<:MOI.Utilities.SUPPORTED_VARIABLE_SCALAR_SETS{T}},
) where {T}
    return false
end

struct InvariantUnderFunctionConversionAttribute <:
       MOI.AbstractConstraintAttribute end

function MOI.Bridges.Constraint.invariant_under_function_conversion(
    ::InvariantUnderFunctionConversionAttribute,
)
    return true
end

function _dict(model::OnlyNonnegVAF)
    if !haskey(model.ext, :InvariantUnderFunctionConversionAttribute)
        model.ext[:InvariantUnderFunctionConversionAttribute] =
            Dict{MOI.ConstraintIndex,Any}()
    end
    return model.ext[:InvariantUnderFunctionConversionAttribute]
end

function MOI.set(
    model::OnlyNonnegVAF,
    ::InvariantUnderFunctionConversionAttribute,
    ci::MOI.ConstraintIndex,
    value,
)
    return _dict(model)[ci] = value
end

function MOI.get(
    model::OnlyNonnegVAF,
    ::InvariantUnderFunctionConversionAttribute,
    ci::MOI.ConstraintIndex,
)
    return _dict(model)[ci]
end

function _test_context_substitution(T)
    # Two Variable bridge in context
    model = OnlyNonnegVAF{T}()
    bridged = MOI.Bridges.LazyBridgeOptimizer(model)
    MOI.Bridges.add_bridge(
        bridged,
        MOI.Bridges.Constraint.VectorFunctionizeBridge{T},
    )
    MOI.Bridges.add_bridge(
        bridged,
        MOI.Bridges.Variable.NonposToNonnegBridge{T},
    )
    MOI.Bridges.add_bridge(bridged, MOI.Bridges.Variable.VectorizeBridge{T})
    x, cx = MOI.add_constrained_variable(bridged, MOI.LessThan(one(T)))
    vectorize = MOI.Bridges.bridge(bridged, x)
    @test vectorize isa MOI.Bridges.Variable.VectorizeBridge
    y = vectorize.variable
    flip = MOI.Bridges.bridge(bridged, y)
    @test flip isa MOI.Bridges.Variable.NonposToNonnegBridge
    Z = flip.variables
    z = Z[1]
    vov_ci = flip.constraint
    functionize = MOI.Bridges.bridge(bridged, vov_ci)
    @test functionize isa MOI.Bridges.Constraint.VectorFunctionizeBridge
    aff_ci = functionize.constraint
    function f(vi)
        return MOI.Bridges.Variable.unbridged_function(
            MOI.Bridges.Variable.bridges(bridged),
            vi,
        )
    end
    @test f(y) ≈ one(T) * x - one(T)
    @test MOI.Bridges.call_in_context(bridged, x, bridge -> f(y)) === nothing
    @test MOI.Bridges.call_in_context(bridged, y, bridge -> f(y)) === nothing
    @test f(z) ≈ -one(T) * y
    @test MOI.Bridges.call_in_context(bridged, x, bridge -> f(z)) ≈ f(z)
    @test MOI.Bridges.call_in_context(bridged, y, bridge -> f(z)) === nothing
    attr = InvariantUnderFunctionConversionAttribute()
    for func in [T(2) * x, T(2) * y, T(2) * z]
        MOI.set(model, attr, aff_ci, func)
        @test MOI.get(model, attr, aff_ci) ≈ func
        @test MOI.Bridges.call_in_context(
            bridged,
            vov_ci,
            bridge -> MOI.get(bridged, attr, vov_ci),
        ) ≈ func
        @test MOI.Bridges.call_in_context(
            bridged,
            y,
            bridge -> MOI.get(bridged, attr, vov_ci),
        ) ≈ func
        #@test MOI.Bridges.call_in_context(bridged, z, bridge -> MOI.get(bridged, attr, vov_ci)) ≈ func
    end
    # One Variable bridge in context
    model = NoVariableModel{T}()
    bridged = MOI.Bridges.LazyBridgeOptimizer(model)
    MOI.Bridges.add_bridge(bridged, MOI.Bridges.Variable.RSOCtoSOCBridge{T})
    MOI.Bridges.add_bridge(
        bridged,
        MOI.Bridges.Constraint.VectorFunctionizeBridge{T},
    )
    x, cx =
        MOI.add_constrained_variables(bridged, MOI.RotatedSecondOrderCone(4))
    y = MOI.add_variable(bridged)
    @test MOI.get(bridged, MOI.NumberOfVariables()) == 5
    @test MOI.is_valid(bridged, y)
    MOI.delete(bridged, y)
    @test MOI.get(bridged, MOI.NumberOfVariables()) == 4
    @test !MOI.is_valid(bridged, y)
    _test_delete_bridged_variables(
        bridged,
        x,
        MOI.RotatedSecondOrderCone,
        4,
        (
            (MOI.VectorOfVariables, MOI.SecondOrderCone, 0),
            (MOI.VectorAffineFunction{T}, MOI.SecondOrderCone, 0),
        ),
    )
    return
end

function test_context_substitution()
    _test_context_substitution(Float64)
    _test_context_substitution(Float32)
    return
end

# Only supports GreaterThan and Nonnegatives
MOI.Utilities.@model(
    GreaterNonnegModel,
    (),
    (MOI.GreaterThan,),
    (MOI.Nonnegatives,),
    (),
    (),
    (MOI.ScalarAffineFunction, MOI.ScalarQuadraticFunction),
    (MOI.VectorOfVariables,),
    (MOI.VectorAffineFunction, MOI.VectorQuadraticFunction)
)

function MOI.supports_constraint(
    ::GreaterNonnegModel{T},
    ::Type{MOI.VariableIndex},
    ::Type{<:Union{MOI.EqualTo{T},MOI.LessThan{T},MOI.Interval{T}}},
) where {T}
    return false
end

MOI.Utilities.@model(
    ModelNoVAFinSOC,
    (),
    (MOI.EqualTo, MOI.GreaterThan, MOI.LessThan, MOI.Interval),
    (
        MOI.Zeros,
        MOI.Nonnegatives,
        MOI.Nonpositives,
        MOI.SecondOrderCone,
        MOI.NormInfinityCone,
        MOI.NormOneCone,
        MOI.RotatedSecondOrderCone,
        MOI.GeometricMeanCone,
        MOI.PositiveSemidefiniteConeTriangle,
        MOI.ExponentialCone,
    ),
    (MOI.PowerCone, MOI.DualPowerCone),
    (),
    (MOI.ScalarAffineFunction, MOI.ScalarQuadraticFunction),
    (MOI.VectorOfVariables,),
    (MOI.VectorAffineFunction, MOI.VectorQuadraticFunction)
)

function MOI.supports_constraint(
    ::ModelNoVAFinSOC{Float64},
    ::Type{MOI.VectorAffineFunction{Float64}},
    ::Type{MOI.SecondOrderCone},
)
    return false
end

# Model supporting nothing
MOI.Utilities.@model NothingModel () () () () () () () ()

function MOI.supports_constraint(
    ::NothingModel{T},
    ::Type{MOI.VariableIndex},
    ::Type{
        <:Union{
            MOI.EqualTo{T},
            MOI.GreaterThan{T},
            MOI.LessThan{T},
            MOI.Interval{T},
            MOI.Integer,
            MOI.ZeroOne,
        },
    },
) where {T}
    return false
end

struct BridgeAddingNoConstraint{T} <: MOI.Bridges.Constraint.AbstractBridge end

function MOI.Bridges.added_constrained_variable_types(
    ::Type{<:BridgeAddingNoConstraint},
)
    return Tuple{Type}[]
end

function MOI.Bridges.added_constraint_types(::Type{<:BridgeAddingNoConstraint})
    return Tuple{Type,Type}[]
end

function MOI.supports_constraint(
    ::Type{<:BridgeAddingNoConstraint},
    ::Type{MOI.VariableIndex},
    ::Type{MOI.Integer},
)
    return true
end

function MOI.Bridges.Constraint.concrete_bridge_type(
    ::Type{<:BridgeAddingNoConstraint{T}},
    ::Type{MOI.VariableIndex},
    ::Type{MOI.Integer},
) where {T}
    return BridgeAddingNoConstraint{T}
end

const LessThanIndicatorOne{T} =
    MOI.Indicator{MOI.ACTIVATE_ON_ONE,MOI.LessThan{T}}

MOI.Utilities.@model(
    ModelNoZeroIndicator,
    (MOI.ZeroOne, MOI.Integer),
    (
        MOI.EqualTo,
        MOI.GreaterThan,
        MOI.LessThan,
        MOI.Interval,
        MOI.Semicontinuous,
        MOI.Semiinteger,
    ),
    (
        MOI.Reals,
        MOI.Zeros,
        MOI.Nonnegatives,
        MOI.Nonpositives,
        MOI.NormInfinityCone,
        MOI.NormOneCone,
        MOI.SecondOrderCone,
        MOI.RotatedSecondOrderCone,
        MOI.GeometricMeanCone,
        MOI.ExponentialCone,
        MOI.DualExponentialCone,
        MOI.RelativeEntropyCone,
        MOI.NormSpectralCone,
        MOI.NormNuclearCone,
        MOI.PositiveSemidefiniteConeTriangle,
        MOI.PositiveSemidefiniteConeSquare,
        MOI.RootDetConeTriangle,
        MOI.RootDetConeSquare,
        MOI.LogDetConeTriangle,
        MOI.LogDetConeSquare,
    ),
    (
        MOI.PowerCone,
        MOI.DualPowerCone,
        MOI.SOS1,
        MOI.SOS2,
        LessThanIndicatorOne,
    ),
    (),
    (MOI.ScalarAffineFunction, MOI.ScalarQuadraticFunction),
    (MOI.VectorOfVariables,),
    (MOI.VectorAffineFunction, MOI.VectorQuadraticFunction)
)

MOI.Utilities.@model(
    ModelNoIndicator,
    (MOI.ZeroOne, MOI.Integer),
    (
        MOI.EqualTo,
        MOI.GreaterThan,
        MOI.LessThan,
        MOI.Interval,
        MOI.Semicontinuous,
        MOI.Semiinteger,
    ),
    (
        MOI.Reals,
        MOI.Zeros,
        MOI.Nonnegatives,
        MOI.Nonpositives,
        MOI.NormInfinityCone,
        MOI.NormOneCone,
        MOI.SecondOrderCone,
        MOI.RotatedSecondOrderCone,
        MOI.GeometricMeanCone,
        MOI.ExponentialCone,
        MOI.DualExponentialCone,
        MOI.RelativeEntropyCone,
        MOI.NormSpectralCone,
        MOI.NormNuclearCone,
        MOI.PositiveSemidefiniteConeTriangle,
        MOI.PositiveSemidefiniteConeSquare,
        MOI.RootDetConeTriangle,
        MOI.RootDetConeSquare,
        MOI.LogDetConeTriangle,
        MOI.LogDetConeSquare,
    ),
    (MOI.PowerCone, MOI.DualPowerCone, MOI.SOS1, MOI.SOS2),
    (),
    (MOI.ScalarAffineFunction, MOI.ScalarQuadraticFunction),
    (MOI.VectorOfVariables,),
    (MOI.VectorAffineFunction, MOI.VectorQuadraticFunction)
)

function test_bridge_adding_no_constraint()
    mock = MOI.Utilities.MockOptimizer(NothingModel{Int}())
    bridged = MOI.Bridges.LazyBridgeOptimizer(mock)
    MOI.Bridges.add_bridge(bridged, BridgeAddingNoConstraint{Float64})
    @test MOI.Bridges.supports_bridging_constraint(
        bridged,
        MOI.VariableIndex,
        MOI.Integer,
    )
end

function test_unsupported_constraint_with_cycles()
    # Test that `supports_constraint` works correctly when it is not
    # supported but the bridges forms a cycle
    mock = MOI.Utilities.MockOptimizer(NothingModel{Float64}())
    bridged = MOI.Bridges.full_bridge_optimizer(mock, Float64)
    @test !MOI.supports_constraint(
        bridged,
        MOI.VariableIndex,
        MOI.GreaterThan{Float64},
    )
    @test !MOI.supports_constraint(
        bridged,
        MOI.VectorAffineFunction{Float64},
        MOI.Nonpositives,
    )
end

function test_UnsupportedConstraint_when_it_cannot_be_bridged()
    mock = MOI.Utilities.MockOptimizer(NoRSOCModel{Float64}())
    bridged_mock = MOI.Bridges.LazyBridgeOptimizer(mock)
    x = MOI.add_variables(bridged_mock, 4)
    err = MOI.UnsupportedConstraint{
        MOI.VectorOfVariables,
        MOI.RotatedSecondOrderCone,
    }()
    @test_throws err begin
        MOI.add_constraint(
            bridged_mock,
            MOI.VectorOfVariables(x),
            MOI.RotatedSecondOrderCone(4),
        )
    end
end

function test_MOI_runtests_No_RSOCModel()
    mock = MOI.Utilities.MockOptimizer(NoRSOCModel{Float64}())
    bridged_mock = MOI.Bridges.LazyBridgeOptimizer(mock)

    MOI.Bridges.add_bridge(
        bridged_mock,
        MOI.Bridges.Constraint.SplitIntervalBridge{Float64},
    )
    MOI.Bridges.add_bridge(
        bridged_mock,
        MOI.Bridges.Constraint.RSOCtoPSDBridge{Float64},
    )
    MOI.Bridges.add_bridge(
        bridged_mock,
        MOI.Bridges.Constraint.SOCtoPSDBridge{Float64},
    )
    MOI.Bridges.add_bridge(
        bridged_mock,
        MOI.Bridges.Constraint.RSOCtoSOCBridge{Float64},
    )
    MOI.Test.runtests(
        bridged_mock,
        MOI.Test.Config(),
        include = ["ConstraintName", "VariableName"],
    )
    return
end

# Test that RSOCtoPSD is used instead of RSOC+SOCtoPSD as it is a shortest path.
function test_bridge_selection()
    mock = MOI.Utilities.MockOptimizer(NoRSOCModel{Float64}())
    bridged_mock = MOI.Bridges.LazyBridgeOptimizer(mock)

    MOI.Bridges.add_bridge(
        bridged_mock,
        MOI.Bridges.Constraint.SplitIntervalBridge{Float64},
    )
    MOI.Bridges.add_bridge(
        bridged_mock,
        MOI.Bridges.Constraint.RSOCtoPSDBridge{Float64},
    )
    MOI.Bridges.add_bridge(
        bridged_mock,
        MOI.Bridges.Constraint.SOCtoPSDBridge{Float64},
    )
    MOI.Bridges.add_bridge(
        bridged_mock,
        MOI.Bridges.Constraint.RSOCtoSOCBridge{Float64},
    )
    @test !(MOI.supports_constraint(
        bridged_mock,
        MOI.VectorAffineFunction{Float64},
        MOI.LogDetConeTriangle,
    ))
    x = MOI.add_variables(bridged_mock, 3)
    err = MOI.UnsupportedConstraint{
        MOI.VectorAffineFunction{Float64},
        MOI.LogDetConeTriangle,
    }()
    @test_throws err begin
        MOI.Bridges.bridge_type(
            bridged_mock,
            MOI.VectorAffineFunction{Float64},
            MOI.LogDetConeTriangle,
        )
    end
    c = MOI.add_constraint(
        bridged_mock,
        MOI.VectorOfVariables(x),
        MOI.RotatedSecondOrderCone(3),
    )
    @test MOI.Bridges.bridge_type(
        bridged_mock,
        MOI.VectorOfVariables,
        MOI.RotatedSecondOrderCone,
    ) == MOI.Bridges.Constraint.RSOCtoPSDBridge{
        Float64,
        MOI.VectorAffineFunction{Float64},
        MOI.VectorOfVariables,
    }
    @test MOI.Bridges.bridge(bridged_mock, c) isa
          MOI.Bridges.Constraint.RSOCtoPSDBridge
    @test bridged_mock.graph.constraint_dist[MOI.Bridges.node(
        bridged_mock,
        MOI.VectorOfVariables,
        MOI.RotatedSecondOrderCone,
    ).index] == 1
    return
end

function test_supports()
    mock = MOI.Utilities.MockOptimizer(NoRSOCModel{Float64}())
    full_bridged_mock = MOI.Bridges.full_bridge_optimizer(mock, Float64)
    for S in [MOI.Nonnegatives, MOI.Nonpositives, MOI.Zeros]
        @test !MOI.supports_constraint(full_bridged_mock, MOI.VariableIndex, S)
    end
    for S in
        [MOI.GreaterThan{Float64}, MOI.LessThan{Float64}, MOI.EqualTo{Float64}]
        @test !MOI.supports_constraint(
            full_bridged_mock,
            MOI.VectorOfVariables,
            S,
        )
    end
    greater_nonneg_mock =
        MOI.Utilities.MockOptimizer(GreaterNonnegModel{Float64}())
    full_bridged_greater_nonneg =
        MOI.Bridges.full_bridge_optimizer(greater_nonneg_mock, Float64)
    for F in [
        MOI.VariableIndex,
        MOI.ScalarAffineFunction{Float64},
        MOI.ScalarQuadraticFunction{Float64},
    ]
        @test MOI.supports_constraint(
            full_bridged_mock,
            F,
            MOI.Interval{Float64},
        )
        @test !MOI.supports_constraint(
            greater_nonneg_mock,
            F,
            MOI.LessThan{Float64},
        )
        @test MOI.supports_constraint(
            full_bridged_greater_nonneg,
            F,
            MOI.LessThan{Float64},
        )
    end
    for F in [
        MOI.VectorOfVariables,
        MOI.VectorAffineFunction{Float64},
        MOI.VectorQuadraticFunction{Float64},
    ]
        @test MOI.supports_constraint(
            full_bridged_mock,
            F,
            MOI.PositiveSemidefiniteConeSquare,
        )
        @test MOI.supports_constraint(
            full_bridged_mock,
            F,
            MOI.GeometricMeanCone,
        )
        @test MOI.supports_constraint(
            full_bridged_mock,
            F,
            MOI.RelativeEntropyCone,
        )
        @test !MOI.supports_constraint(greater_nonneg_mock, F, MOI.Nonpositives)
        @test MOI.supports_constraint(
            full_bridged_greater_nonneg,
            F,
            MOI.Nonnegatives,
        )
    end
    for F in [MOI.VectorOfVariables, MOI.VectorAffineFunction{Float64}]
        # The bridges in this for loop do not support yet
        # VectorQuadraticFunction. See TODO's for the reason.
        # TODO: Missing vcat for quadratic for supporting quadratic.
        @test MOI.supports_constraint(
            full_bridged_mock,
            F,
            MOI.RotatedSecondOrderCone,
        )
        # TODO: Det bridges need to use MOI.Utilities.operate to support quadratic.
        @test MOI.supports_constraint(
            full_bridged_mock,
            F,
            MOI.LogDetConeTriangle,
        )
        @test MOI.supports_constraint(
            full_bridged_mock,
            F,
            MOI.RootDetConeTriangle,
        )
    end
    mock2 = MOI.Utilities.MockOptimizer(ModelNoVAFinSOC{Float64}())
    @test !MOI.supports_constraint(
        mock2,
        MOI.VectorAffineFunction{Float64},
        MOI.SecondOrderCone,
    )
    full_bridged_mock2 = MOI.Bridges.full_bridge_optimizer(mock2, Float64)
    @test MOI.supports_constraint(
        full_bridged_mock2,
        MOI.VectorAffineFunction{Float64},
        MOI.SecondOrderCone,
    )
    mock_indicator =
        MOI.Utilities.MockOptimizer(ModelNoZeroIndicator{Float64}())
    full_bridged_mock_indicator =
        MOI.Bridges.full_bridge_optimizer(mock_indicator, Float64)
    @test !MOI.supports_constraint(
        mock_indicator,
        MOI.VectorAffineFunction{Float64},
        MOI.Indicator{MOI.ACTIVATE_ON_ZERO,MOI.LessThan{Float64}},
    )
    @test MOI.supports_constraint(
        full_bridged_mock_indicator,
        MOI.VectorAffineFunction{Float64},
        MOI.Indicator{MOI.ACTIVATE_ON_ZERO,MOI.LessThan{Float64}},
    )

    mock_sos_indicator =
        MOI.Utilities.MockOptimizer(ModelNoIndicator{Float64}())
    full_bridged_mock_sos_indicator =
        MOI.Bridges.full_bridge_optimizer(mock_sos_indicator, Float64)
    @test !MOI.supports_constraint(
        mock_sos_indicator,
        MOI.VectorAffineFunction{Float64},
        MOI.Indicator{MOI.ACTIVATE_ON_ONE,MOI.LessThan{Float64}},
    )
    @test !MOI.supports_constraint(
        mock_sos_indicator,
        MOI.VectorAffineFunction{Float64},
        MOI.Indicator{MOI.ACTIVATE_ON_ONE,MOI.EqualTo{Float64}},
    )
    @test MOI.supports_constraint(
        full_bridged_mock_sos_indicator,
        MOI.VectorAffineFunction{Float64},
        MOI.Indicator{MOI.ACTIVATE_ON_ONE,MOI.LessThan{Float64}},
    )
    @test MOI.supports_constraint(
        full_bridged_mock_sos_indicator,
        MOI.VectorAffineFunction{Float64},
        MOI.Indicator{MOI.ACTIVATE_ON_ONE,MOI.EqualTo{Float64}},
    )
    for T in [Float64, Int]
        no_variable_mock = MOI.Utilities.MockOptimizer(NoVariableModel{T}())
        full_bridged_no_variable =
            MOI.Bridges.full_bridge_optimizer(no_variable_mock, T)
        for S in [
            MOI.LessThan{T},
            MOI.GreaterThan{T},
            MOI.EqualTo{T},
            MOI.ZeroOne,
            MOI.Integer,
        ]
            @test MOI.supports_constraint(
                full_bridged_no_variable,
                MOI.VariableIndex,
                S,
            )
        end
        for S in
            [MOI.Nonpositives, MOI.Nonnegatives, MOI.Zeros, MOI.SecondOrderCone]
            @test MOI.supports_constraint(
                full_bridged_no_variable,
                MOI.VectorOfVariables,
                S,
            )
        end
    end
    return
end

struct CustomVectorSet <: MOI.AbstractVectorSet
    dimension::Int
end

struct CustomScalarSet <: MOI.AbstractScalarSet end

function test_wrong_coefficient()
    for (S, T) in [(Complex{Float64}, Float64), (Float64, Int)]
        model = MOI.Utilities.Model{T}()
        bridged = MOI.Bridges.full_bridge_optimizer(model, T)
        x = MOI.add_variable(bridged)
        f_scalar = one(S) * x
        f_vector = MOI.Utilities.vectorize([f_scalar])
        function _test(func, set)
            @test_throws(
                MOI.UnsupportedConstraint{typeof(func),typeof(set)},
                MOI.add_constraint(bridged, func, set),
            )
            @test !MOI.supports_constraint(bridged, typeof(func), typeof(set))
            return
        end
        _test(f_scalar, MOI.EqualTo(one(S)))
        _test(f_vector, MOI.Zeros(1))
        _test(f_scalar, CustomScalarSet())
        _test(f_vector, CustomVectorSet(1))
    end
    return
end

struct OptimizerWithBridgeListOfNonstandardBridges <: MOI.AbstractOptimizer end
struct BridgeListOfNonstandardBridges{T} <:
       MOI.Bridges.Constraint.AbstractBridge end
function MOI.get(
    ::OptimizerWithBridgeListOfNonstandardBridges,
    ::MOI.Bridges.ListOfNonstandardBridges{T},
) where {T}
    return [BridgeListOfNonstandardBridges{T}]
end

function test_toadd()
    b = MOI.Bridges.full_bridge_optimizer(
        OptimizerWithBridgeListOfNonstandardBridges(),
        Int,
    )
    @test MOI.Bridges.has_bridge(b, BridgeListOfNonstandardBridges{Int})
    @test !MOI.Bridges.has_bridge(b, BridgeListOfNonstandardBridges{Float64})
end

end  # module

TestBridgesLazyBridgeOptimizer.runtests()

max_stars_repo_name: JuliaOpt/MathOptInterface

max_issues_repo_name: JuliaOpt/MathOptInterface

max_forks_repo_name: JuliaOpt/MathOptInterface


text:
using .Zygote

Zygote.@adjoint function (ev::Eval)(args...)
    Z = ev.fwd(args...)
    Z, Δ -> begin
        isnothing(ev.rev) && error("no gradient definition here!")
        tuple(nothing, ev.rev(Δ, Z, args...)...)
    end
end

Tullio.promote_storage(::Type{T}, ::Type{F}) where {T, F<:Zygote.Fill} = T
Tullio.promote_storage(::Type{F}, ::Type{T}) where {T, F<:Zygote.Fill} = T

ext: jl


text:
using NiceNumbers
using Test, LinearAlgebra

@testset "NiceNumbers" begin
    @testset "Constructors" begin
        @test NiceNumber(8) === NiceNumber(8, 0, 0)
        @test NiceNumber(2, 3, 4) === NiceNumber(8)
        @test NiceNumber(2, 3, 0) === NiceNumber(2)
        @test NiceNumber(2, 0, 5) === NiceNumber(2)
        @test NiceNumber(3 // 5) === NiceNumber(3 // 5, 0, 0)
        @test NiceNumber(3 // 5, 3 // 5, 27 // 125) === NiceNumber(3 // 5, 9 // 125, 15)
        @test NiceNumber(0, 1, 1 // 5) === NiceNumber(0, 1 // 5, 5)
        @test NiceNumber(0.1) === NiceNumber(1 // 10)
    end

    n = NiceNumber(2, 3, 5)
    m = NiceNumber(5, -3, 5)
    k = NiceNumber(8, 4, 3)
    c = NiceNumber(0, 1, -5)
    z = NiceNumber(2, -3, -5)

    @testset "Basic Arithmetic" begin
        @test n + m === NiceNumber(7)
        @test_broken n + k
        @test n - m === NiceNumber(-3, 6, 5)
        @test_broken n - k
        @test n * m === NiceNumber(-35, 9, 5)
        @test_broken n * k
        @test n / m === NiceNumber(-11 // 4, -21 // 20, 5)
        @test_broken n / k
        @test c * c === NiceNumber(-5)
    end

    @testset "Scalar Arithmetic" begin
        @test +n === n
        @test -n === NiceNumber(-2, -3, 5)
        @test 4 + n === n + 4 === NiceNumber(6, 3, 5)
        @test 4 - n === -(n - 4) === NiceNumber(2, -3, 5)
        @test 3 * n === n * 3 === NiceNumber(6, 9, 5)
        @test n / 3 === inv(3 / n) === NiceNumber(2 // 3, 1, 5)

        @test sqrt(NiceNumber(9)) === NiceNumber(3)
        @test sqrt(NiceNumber(3 // 5)) === NiceNumber(0, 1 // 5, 15)
        @test NiceNumbers.nthroot(NiceNumber(81), 8) === NiceNumber(0, 1, 3)
        @test NiceNumber(4)^(3 // 2) === NiceNumber(8)
        @test 4^NiceNumber(3 // 2) === NiceNumber(8)
    end

    @testset "Rational Arithmetic" begin
        @test n // 3 === n / 3
        @test 3 // n === 3 / n
        @test n // n === n / n === NiceNumber(1)
    end

    @testset "Comparisons" begin
        @test NiceNumber(7) < NiceNumber(42)
        @test NiceNumber(7) < NiceNumber(7, 1, 2)
        @test NiceNumber(7) <= NiceNumber(7, 1, 2)
        @test NiceNumber(7) <= NiceNumber(7)
        @test NiceNumber(5, 1, 2) < 7
        @test NiceNumber(5, 1, 2) > 5
        @test NiceNumber(13, 3, 7) == 13 + 3 * √7
        @test 13 + 3 * √7 == NiceNumber(13, 3, 7)
    end

    @testset "Conversion" begin
        @test NiceNumber(n) == n
        @test float(NiceNumber(13, 3, 7)) == 13 + 3 * √7
        @test Float64(NiceNumber(13, 3, 7)) == 13 + 3 * √7
    end

    @testset "Promotion" begin
        @test n + 18 // 5 === NiceNumber(28 // 5, 3, 5)
        @test n + 3.6 === NiceNumber(28 // 5, 3, 5)
        @test NiceNumber(3,3,-1) + 7im === NiceNumber(3,10,-1) # issue #5
        @test NiceNumber(3,3,-1) * 7im === NiceNumber(-21,21,-1) # issue #5
    end

    @testset "Complex stuff" begin
        @test abs(n) === n
        @test abs(m) === -m
        @test abs(c) === NiceNumber(0,1,5)
        @test conj(z) === NiceNumber(2,3,-5)
        @test abs(z) === NiceNumber(7)
    end

    u = NiceNumber[4, 12, 3] * sqrt(NiceNumber(2))
    @testset "Linear Algebra" begin
        @test norm(n) === n
        @test norm(m) === -m
        @test norm(u) === NiceNumber(0, 13, 2)
    end

    @testset "Misc" begin
        @test isrational(NiceNumber(139 // 7, 0, 17))
        @test isinteger(NiceNumber(12))
        @test isfinite(NiceNumber(12))
        @test !isfinite(NiceNumber(1 // 0, 7, 7))
        @test !isfinite(NiceNumber(7, 1 // 0, 7))
        @test one(n) === one(NiceNumber) === NiceNumber(1)
        @test zero(n) === zero(NiceNumber) === NiceNumber(0)
    end

    @testset "Macro" begin
        @test @nice 4^1.5 === NiceNumber(8)
        @test @nice sqrt(9//4)+7*sqrt(3)*im === NiceNumber(3//2,7,-3)
    end

    @testset "Show Methods" begin
        io = IOBuffer()
        @test sprint(io -> show(io, n)) == "2+3⋅√5"
        @test sprint(io -> show(io, NiceNumber(0))) == "0"
        @test sprint(io -> show(io, NiceNumber(7))) == "7"
        @test sprint(io -> show(io, NiceNumber(7 // 2))) == "7//2"
        @test sprint(io -> show(io, NiceNumber(7, 1, 5))) == "7+√5"
        @test sprint(io -> show(io, NiceNumber(7, -1, 5))) == "7-√5"
        @test sprint(io -> show(io, NiceNumber(7, 5, 5))) == "7+5⋅√5"
        @test sprint(io -> show(io, NiceNumber(7, -5, 5))) == "7-5⋅√5"
        @test sprint(io -> show(io, NiceNumber(7, 1 // 2, 5))) == "7+1//2⋅√5"
        @test sprint(io -> show(io, NiceNumber(7 // 2, 1, 5))) == "7//2+√5"
        @test sprint(io -> show(io, NiceNumber(7 // 2, 5, 5))) == "7//2+5⋅√5"
        @test sprint(io -> show(io, NiceNumber(7 // 2, 1 // 2, 5))) == "7//2+1//2⋅√5"
        @test sprint(io -> show(io, NiceNumber(0, 1, 5))) == "√5"
        @test sprint(io -> show(io, NiceNumber(0, 5, 5))) == "5⋅√5"
        @test sprint(io -> show(io, NiceNumber(0, -5, 5))) == "-5⋅√5"
        @test sprint(io -> show(io, NiceNumber(0, 1 // 2, 5))) == "1//2⋅√5"
    end

    @testset "Factorizations" begin
        @testset "Cholesky" begin
            @nice L = [2 0 0 0;0 1 0 0;4 6*sqrt(-1) 9 0;0 4 0 2]
            @test L == cholesky(L*L').L
        end

        @testset "LU" begin
            @nice L = [1 0 0;im 1 0; 1/2 -3im 1]
            @nice U = [1 1 1;0 1 1; 0 0 1]
            LUL, LUU = lu(L*U, Val(false))
            @test LUL == L
            @test LUU == U

            @nice A = [2 2 0;2 2 1; 2 3 5]
            L, U, p = lu(A, Val(true))
            @test L == @nice [1 0 0;1 1 0;1 0 1]
            @test U == @nice [2 2 0;0 1 5;0 0 1]
            @test p == [1,3,2]
        end

        @testset "QR" begin
            @nice A = [4 2;0 3//5;0 -4//5;0 0]
            Q, R = qr(A)
            @test Q == @nice [-1 0 0 0;0 -3/5 4/5 0;0 4/5 3/5 0;0 0 0 1]
            @test R == @nice [-4 -2;0 -1]
        end
    end
end


text:
# compute PCA using proPCA software
global propca_exe = "/scratch/users/bbchu/ProPCA/build/propca"
seed = 1

for chr in 1:22
    println("computing PCA for chr $chr")
    plinkname = "/scratch/users/bbchu/ukb_SHAPEIT/subset/ukb.10k.chr$chr"
    outfile = "/scratch/users/bbchu/ukb_SHAPEIT/subset_pca/ukb.10k.chr$chr."
    run(`$propca_exe -g $plinkname -k 10 -o $outfile -nt $(Threads.nthreads()) -seed $seed`)
end


text:
module CLIWrappers

using AutoMLPipeline
using AutoMLPipeline.AbsTypes
using AutoMLPipeline.Utils

using TSML.ValDateFilters
using TSML: CSVDateValReader
using TSML: CSVDateValWriter
using TSML.Statifiers
using TSML.Monotonicers

using Dates
using DataFrames
using CSV

export tsmlrun

const COMMONARG = Dict(:dateformat=>"dd/mm/yyyy HH:MM",
                        :dateinterval=>Dates.Hour(1),
                        :processmissing=>true
                       )

function tsmlrun(inputname::AbstractString,outputname::AbstractString="",datefmt::AbstractString="dd/mm/yyyy HH:MM",otype::AbstractString="table")
    if otype == "table"
        imputedoutput(inputname,outputname,datefmt)
    elseif otype == "stat"
        imputedstat(inputname,outputname,datefmt)
    else
        error("wrong output type")
    end
end

function rawstat(inputname::AbstractString,outputname::AbstractString="",datefmt::AbstractString="dd/mm/yyyy HH:MM")
    isfile(inputname) || error("input file name does not exist")
    csvreader = CSVDateValReader(Dict(:filename=>inputname,:dateformat=>datefmt))
    stfier = Statifier()
    lpipe = @pipeline csvreader |> stfier
    res=fit_transform!(lpipe)
    if outputname != ""
        res |> CSV.write(outputname)
    end
    return res
end

function aggregatedstat(inputname::AbstractString,outputname::AbstractString="",datefmt::AbstractString="dd/mm/yyyy HH:MM")
    isfile(inputname) || error("input file name does not exist")
    csvreader = CSVDateValReader(Dict(:filename=>inputname,:dateformat=>datefmt))
    valgator = DateValgator(COMMONARG)
    stfier = Statifier()
    lpipe = @pipeline csvreader |> valgator |> stfier
    res=fit_transform!(lpipe)
    if outputname != ""
        res |> CSV.write(outputname)
    end
    return res
end

function aggregatedoutput(inputname::AbstractString,outputname::AbstractString="",datefmt::AbstractString="dd/mm/yyyy HH:MM")
    isfile(inputname) || error("input file name does not exist")
    csvreader = CSVDateValReader(Dict(:filename=>inputname,:dateformat=>datefmt))
    valgator = DateValgator(COMMONARG)
    lpipe = @pipeline csvreader |> valgator
    res=fit_transform!(lpipe)
    if outputname != ""
        res |> CSV.write(outputname)
    end
    return res
end

function imputedstat(inputname::AbstractString,outputname::AbstractString="",datefmt::AbstractString="dd/mm/yyyy HH:MM")
    isfile(inputname) || error("input file name does not exist")
    csvreader = CSVDateValReader(Dict(:filename=>inputname,:dateformat=>datefmt))
    valgator = DateValgator(COMMONARG)
    valnner = DateValNNer(COMMONARG)
    mononicer = Monotonicer()
    stfier = Statifier()
    lpipe = @pipeline csvreader |> valgator |> valnner |> mononicer |> stfier
    res=fit_transform!(lpipe)
    if outputname != ""
        res |> CSV.write(outputname)
    end
    return res
end

function imputedoutput(inputname::AbstractString,outputname::AbstractString="",datefmt::AbstractString="dd/mm/yyyy HH:MM")
    isfile(inputname) || error("input file name does not exist")
    csvreader = CSVDateValReader(Dict(:filename=>inputname,:dateformat=>datefmt))
    valgator = DateValgator(COMMONARG)
    valnner = DateValNNer(COMMONARG)
    mononicer = Monotonicer()
    stfier = Statifier()
    lpipe = @pipeline csvreader |> valgator |> valnner |> mononicer
    res=fit_transform!(lpipe)
    if outputname != ""
        res |> CSV.write(outputname)
    end
    return res
end

end


text:
using Test
using Statistics

function bin2dec(b)
    return sum(bit * 2^(ex-1) for (ex, bit) in enumerate(reverse(b)))
end

"In a vector of 0s and 1s, return the more frequent symbol."
function morefrequent(vec)
    nones = sum(vec)
    n = length(vec)
    return nones >= length(vec)-nones ? 1 : 0
end

function gamma_epsilon(bm)
    binary_gamma = [morefrequent(c) for c in eachcol(bm)]
    binary_epsilon = [1-bit for bit in binary_gamma]
    return bin2dec(binary_gamma), bin2dec(binary_epsilon)
end

function day03a(bm)
    gam, eps = gamma_epsilon(bm)
    return gam*eps
end

function filterrows(f::Function, bm)
    i = 0
    while size(bm)[1] > 1
        i += 1
        bit = f(bm[:,i])
        bm = bm[ bm[:,i].==bit , :]
    end
    return bin2dec(bm[1,:])
end

function oxygen(bm)
    result = filterrows(bm) do col
        morefrequent(col)
    end
    return result
end

function co2(bm)
    result = filterrows(bm) do col
        1-morefrequent(col)
    end
    return result
end

function day03b(bm)
    ox = oxygen(bm)
    co = co2(bm)
    return ox * co
end

function day03(fpath)
    bm = parsebinary(fpath)
    return (day03a(bm), day03b(bm))
end


text:
module LevelJump

const lib = "libleveldb"

type LevelDB
    db::Ptr
    #it::Ptr
    path::AbstractString
end

function LevelDB(path::AbstractString, create_if_missing::Bool=true)
    options = ccall( (:leveldb_options_create, lib), Ptr{Void}, ())
    if create_if_missing
        ccall(
              (:leveldb_options_set_create_if_missing, lib),
              Void,
              (Ptr{Void}, UInt8),
              options, 1)
    end
    err = Ptr{UInt8}[0]
    db = ccall(
               (:leveldb_open, lib),
               Ptr{Void},
               (Ptr{Void}, Ptr{UInt8}, Ptr{Ptr{UInt8}}),
               options,
               path,
               err)
    if db == C_NULL
        error(bytestring(err[1]))
    end
    #it = ccall(
    #           (:leveldb_create_iterator, lib),
    #           Ptr{Void},
    #           (Ptr{Void}, Ptr{Void}),
    #           db, options,
    #           )
    ldb = LevelDB(db, path)
    return ldb
end

function open(path::AbstractString, create_if_missing::Bool=true)
    return LevelDB(path, create_if_missing)
end

function close(ldb::LevelDB)
    ccall(
          (:leveldb_close, lib),
          Void,
          (Ptr{Void},),
          ldb.db)
end

function push!(ldb::LevelDB, key::AbstractString, val::AbstractString)
    options = ccall(
                    (:leveldb_writeoptions_create, lib),
                    Ptr{Void},
                    ())
    err = Ptr{UInt8}[0]
    ccall(
          (:leveldb_put, lib),
          Void,
          (Ptr{Void}, Ptr{Void}, Cstring, UInt, Cstring, UInt, Ptr{Ptr{UInt8}}),
          ldb.db, options, key, length(key), val, length(val), err,
          )
    if err[1] != C_NULL
        error(bytestring(err[1]))
    end
end

function setindex!(ldb::LevelDB, key::AbstractString, val::AbstractString)
    push!(ldb, key, val)
end

function get(ldb::LevelDB, key::AbstractString, default)
    return getindex(ldb, key)
end

function getintex(ldb::LevelDB, key::AbstractString)
    options = ccall( (:leveldb_readoptions_create, lib), Ptr{Void}, ())
    err = Ptr{UInt8}[0]
    val_len = Csize_t[0]
    value = ccall(
                  (:leveldb_get, lib),
                  Cstring,
                  (Ptr{Void}, Ptr{Void}, Cstring, UInt, Ptr{Csize_t}, Ptr{Ptr{UInt8}}),
                  ldb.db, options, key, length(key), val_len, err
                  )
    if err[1] != C_NULL
        error(bytestring(err[1]))
    else
        ret = bytestring(value)
        return ret
    end
end

function delete!(ldb::LevelDB, key::AbstractString)
    options = ccall(
                    (:leveldb_writeoptions_create, lib),
                    Ptr{Void},
                    ()
                    )
    err = Ptr{UInt8}[0]
    ccall(
          (:leveldb_delete, lib),
          Void,
          (Ptr{Void}, Ptr{Void}, Cstring, UInt, Ptr{Ptr{UInt8}} ),
          ldb.db, options, key, length(key), err
          )
    if err[1] !=C_NULL
        error(bytestring(err[1]))
    end
end

function pop!(ldb::LevelDB, key::AbstractString, default::Any=Nothing)
    ret = get(ldb, key, default)
    delete!(ldb, key)
    return ret
end

function batch(ldb::LevelDB, array::Array)
    w_batch = ccall((:leveldb_writebatch_create, lib), Ptr{Void},())
    for set in array
        ccall((:leveldb_writebatch_put, lib), Void,
          (Ptr{UInt8}, Cstring, UInt, Cstring, UInt),
          w_batch, set["key"], length(set["key"]), set["value"], length(set["value"]))
    end
    options = ccall(
                    (:leveldb_writeoptions_create, lib),
                    Ptr{Void},
                    ())
    err = Ptr{UInt8}[0]
    ccall((:leveldb_write, libleveldbjl), Void,
          (Ptr{Void}, Ptr{Void}, Ptr{Void},  Ptr{Ptr{UInt8}} ),
          ldb, options, w_batch, err)
    if err[1] != C_NULL
        error(bytestring(err[1]))
    end
end

function iter_valid(ldb::LevelDB)
    return ccall(
                 (:leveldb_iter_valid, lib),
                 UInt8,
                 (Ptr{Void},),
                 ldb.it) == 1
end

function iter_value(ldb::LevelDB)
    v_len = Csize_t[0]
    value = ccall(
                  (:leveldb_iter_value, libleveldbjl),
                  Ptr{UInt8},
                  (Ptr{Void}, Ptr{Csize_t}),
                  ldb.it, v_len)
    return pointer_to_array(value, (v_len[1],), false)
end

function iter_key(ldb::LevelDB)
    k_len = Csize_t[0]
    key = ccall((:leveldb_iter_key, lib),
                Cstring,
                (Ptr{Void}, Ptr{Csize_t}),
                ldb.it, k_len)
    return bytestring(key, k_len[1])
end

function iter_seek(ldb::LevelDB, key::AbstractString)
    ccall(
          (:leveldb_iter_seek, lib),
          Void,
          (Ptr{Void}, Cstring, UInt),
          ldb.it, key, length(key))
end

function iter_next(ldb::LevelDB)
    ccall(
          (:leveldb_iter_next, lib),
          Void,
          (Ptr{Void},),
          ldb.it)
end

# module end
end


text:
@testset "FAD" begin

    @testset "FAD" begin
        out1, out2 = FAD(vcat([reverse(nl43env) for i in 1:10], [nl43env for i in 1:10]))
        @test (sort(out1) == sort([nl43env, reverse(nl43env)]) && out2 == [10.0, 10.0])
    end

end


text:
module MonteCarloIntegration

include("vegas.jl")

export vegas

end # module


text:
struct PINningSynapseParameter
end

@snn_kw mutable struct PINningSynapse{MFT=Matrix{Float32},VFT=Vector{Float32}}
    param::PINningSynapseParameter = PINningSynapseParameter()
    W::MFT  # synaptic weight
    rI::VFT # postsynaptic rate
    rJ::VFT # presynaptic rate
    g::VFT  # postsynaptic conductance
    P::MFT  # <rᵢrⱼ>⁻¹
    q::VFT  # P * r
    f::VFT  # postsynaptic traget
    records::Dict = Dict()
end

"""
[PINing Sparse Synapse](https://www.ncbi.nlm.nih.gov/pubmed/26971945)
"""
PINningSynapse

function PINningSynapse(pre, post; σ = 1.5, p = 0.0, α = 1, kwargs...)
    rI, rJ, g = post.r, pre.r, post.g
    W = σ * 1 / √pre.N * randn(post.N, pre.N) # normalized recurrent weight
    P = α * I(post.N) # initial inverse of C = <rr'>
    f, q = zeros(post.N), zeros(post.N)
    PINningSynapse(;@symdict(W, rI, rJ, g, P, q, f)..., kwargs...)
end

function forward!(c::PINningSynapse, param::PINningSynapseParameter)
    @unpack W, rI, rJ, g, P, q = c
    mul!(q, P, rJ)
    mul!(g, W, rJ)
end

function plasticity!(c::PINningSynapse, param::PINningSynapseParameter, dt::Float32, t::Float32)
    @unpack W, rI, g, P, q, f = c
    C = 1 / (1 + dot(q, rI))
    BLAS.ger!(C, f - g, q, W)
    BLAS.ger!(-C, q, q, P)
end


text:
# FINDING, STORING, LOADING DATA

function lookup(modname::Module, srchterm::Regex)
    vars = names(modname, true)
    indx = map((n)->ismatch(srchterm, string(n)), vars);
    vars[indx]
end

function filterpaths(dir, srch)
    srchr = Regex(srch)
    absdir = abspath(dir)
    fpths = readdir(absdir)
    matches = filter((name)->ismatch(srchr,name), fpths)
    map((nm)->absdir*"/"*nm, matches)
end

function dictify(syms::Symbol...; mod=Main)
    Dict(s => eval(mod, s) for s in syms)
end

function save(fp::AbstractString, A::Any)
    f = open(fp, "w+")
    serialize(f, A)
    close(f)
end

function save(fp::AbstractString, syms::Symbol...)
    D = dictify(syms...)
    save(fp, D)
end

function load(fp)
    f = open(fp, "r")
    O = deserialize(f)
    close(f)
    O
end

# PERFMORMANCE TESTING

macro timeit(reps, expr)
   quote
       mean([@elapsed $expr for i in 1:$reps])
   end
end

function checksame(thing::Symbol)
    @assert length(Set(values(reap(thing))))==1
end