text:
# Continuous linear problems

# Basic solver, query, resolve
function linear1test(model::MOI.ModelLike, config::TestConfig{T}) where {T}
    atol = config.atol
    rtol = config.rtol
    # simple 2 variable, 1 constraint problem
    # min -x
    # st   x + y <= 1   (x + y - 1 ∈ Nonpositives)
    #       x, y >= 0   (x, y ∈ Nonnegatives)

    @test MOIU.supports_default_copy_to(model, false) #=copy_names=#
    @test MOI.supports(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
    )
    @test MOI.supports(model, MOI.ObjectiveSense())
    @test MOI.supports_constraint(
        model,
        MOI.ScalarAffineFunction{T},
        MOI.EqualTo{T},
    )
    @test MOI.supports_constraint(
        model,
        MOI.ScalarAffineFunction{T},
        MOI.GreaterThan{T},
    )
    @test MOI.supports_constraint(
        model,
        MOI.ScalarAffineFunction{T},
        MOI.LessThan{T},
    )
    @test MOI.supports_constraint(model, MOI.SingleVariable, MOI.EqualTo{T})
    @test MOI.supports_constraint(model, MOI.SingleVariable, MOI.GreaterThan{T})

    #@test MOI.get(model, MOI.SupportsAddConstraintAfterSolve())
    #@test MOI.get(model, MOI.SupportsAddVariableAfterSolve())
    #@test MOI.get(model, MOI.SupportsDeleteConstraint())

    MOI.empty!(model)
    @test MOI.is_empty(model)

    v = MOI.add_variables(model, 2)
    @test MOI.get(model, MOI.NumberOfVariables()) == 2

    cf = MOI.ScalarAffineFunction{T}(
        MOI.ScalarAffineTerm{T}.([one(T), one(T)], v),
        zero(T),
    )
    c = MOI.add_constraint(model, cf, MOI.LessThan(one(T)))
    @test MOI.get(
        model,
        MOI.NumberOfConstraints{MOI.ScalarAffineFunction{T},MOI.LessThan{T}}(),
    ) == 1

    vc1 = MOI.add_constraint(
        model,
        MOI.SingleVariable(v[1]),
        MOI.GreaterThan(zero(T)),
    )
    # We test this after the creation of every `SingleVariable` constraint
    # to ensure a good coverage of corner cases.
    @test vc1.value == v[1].value
    # test fallback
    vc2 = MOI.add_constraint(model, v[2], MOI.GreaterThan(zero(T)))
    @test vc2.value == v[2].value
    @test MOI.get(
        model,
        MOI.NumberOfConstraints{MOI.SingleVariable,MOI.GreaterThan{T}}(),
    ) == 2

    # note: adding some redundant zero coefficients to catch solvers that don't handle duplicate coefficients correctly:
    objf = MOI.ScalarAffineFunction{T}(
        MOI.ScalarAffineTerm{
            T,
        }.(
            [zero(T), zero(T), -one(T), zero(T), zero(T), zero(T)],
            [v; v; v],
        ),
        zero(T),
    )
    MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(), objf)
    MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)

    @test MOI.get(model, MOI.ObjectiveSense()) == MOI.MIN_SENSE

    if config.query
        vrs = MOI.get(model, MOI.ListOfVariableIndices())
        @test vrs == v || vrs == reverse(v)

        @test objf ≈ MOI.get(
            model,
            MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
        )

        @test cf ≈ MOI.get(model, MOI.ConstraintFunction(), c)

        s = MOI.get(model, MOI.ConstraintSet(), c)
        @test s == MOI.LessThan(one(T))

        s = MOI.get(model, MOI.ConstraintSet(), vc1)
        @test s == MOI.GreaterThan(zero(T))

        s = MOI.get(model, MOI.ConstraintSet(), vc2)
        @test s == MOI.GreaterThan(zero(T))
    end

    if config.solve
        @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED

        MOI.optimize!(model)

        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status

        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT

        @test MOI.get(model, MOI.ObjectiveValue()) ≈ -1 atol = atol rtol = rtol
        if config.dual_objective_value
            @test MOI.get(model, MOI.DualObjectiveValue()) ≈ -1 atol = atol rtol =
                rtol
        end

        @test MOI.get(model, MOI.VariablePrimal(), v) ≈ [1, 0] atol = atol rtol =
            rtol

        @test MOI.get(model, MOI.ConstraintPrimal(), c) ≈ 1 atol = atol rtol =
            rtol

        if config.duals
            @test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT
            @test MOI.get(model, MOI.ConstraintDual(), c) ≈ -1 atol = atol rtol =
                rtol

            # reduced costs
            @test MOI.get(model, MOI.ConstraintDual(), vc1) ≈ 0 atol = atol rtol =
                rtol
            @test MOI.get(model, MOI.ConstraintDual(), vc2) ≈ 1 atol = atol rtol =
                rtol
        end
    end

    # change objective to Max +x

    objf = MOI.ScalarAffineFunction{T}(
        MOI.ScalarAffineTerm{T}.([one(T), zero(T)], v),
        zero(T),
    )
    MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(), objf)
    MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)

    if config.query
        @test objf ≈ MOI.get(
            model,
            MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
        )
    end

    @test MOI.get(model, MOI.ObjectiveSense()) == MOI.MAX_SENSE

    if config.solve
        MOI.optimize!(model)

        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status

        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT

        @test MOI.get(model, MOI.ObjectiveValue()) ≈ 1 atol = atol rtol = rtol
        if config.dual_objective_value
            @test MOI.get(model, MOI.DualObjectiveValue()) ≈ 1 atol = atol rtol =
                rtol
        end

        @test MOI.get(model, MOI.VariablePrimal(), v) ≈ [1, 0] atol = atol rtol =
            rtol

        if config.duals
            @test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT
            @test MOI.get(model, MOI.ConstraintDual(), c) ≈ -1 atol = atol rtol =
                rtol

            @test MOI.get(model, MOI.ConstraintDual(), vc1) ≈ 0 atol = atol rtol =
                rtol
            @test MOI.get(model, MOI.ConstraintDual(), vc2) ≈ 1 atol = atol rtol =
                rtol
        end
    end

    # add new variable to get :
    # max x + 2z
    # s.t. x + y + z <= 1
    # x,y,z >= 0

    z = MOI.add_variable(model)
    push!(v, z)
    @test v[3] == z

    if config.query
        # Test that the modification of v has not affected the model
        vars = map(
            t -> t.variable_index,
            MOI.get(model, MOI.ConstraintFunction(), c).terms,
        )
        @test vars == [v[1], v[2]] || vars == [v[2], v[1]]
        @test MOI.ScalarAffineFunction{T}(
            [MOI.ScalarAffineTerm{T}(one(T), v[1])],
            zero(T),
        ) ≈ MOI.get(
            model,
            MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
        )
    end

    vc3 = MOI.add_constraint(
        model,
        MOI.SingleVariable(v[3]),
        MOI.GreaterThan(zero(T)),
    )
    @test vc3.value == v[3].value
    @test MOI.get(
        model,
        MOI.NumberOfConstraints{MOI.SingleVariable,MOI.GreaterThan{T}}(),
    ) == 3

    if config.modify_lhs
        MOI.modify(model, c, MOI.ScalarCoefficientChange{T}(z, one(T)))
    else
        MOI.delete(model, c)
        cf = MOI.ScalarAffineFunction{T}(
            MOI.ScalarAffineTerm{T}.([one(T), one(T), one(T)], v),
            zero(T),
        )
        c = MOI.add_constraint(model, cf, MOI.LessThan(one(T)))
    end

    MOI.modify(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
        MOI.ScalarCoefficientChange{T}(z, T(2)),
    )

    @test MOI.get(
        model,
        MOI.NumberOfConstraints{MOI.ScalarAffineFunction{T},MOI.LessThan{T}}(),
    ) == 1
    @test MOI.get(
        model,
        MOI.NumberOfConstraints{MOI.SingleVariable,MOI.GreaterThan{T}}(),
    ) == 3

    if config.solve
        MOI.optimize!(model)

        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status

        @test MOI.get(model, MOI.ResultCount()) >= 1

        @test MOI.get(model, MOI.PrimalStatus(1)) == MOI.FEASIBLE_POINT

        @test MOI.get(model, MOI.ObjectiveValue()) ≈ 2 atol = atol rtol = rtol
        if config.dual_objective_value
            @test MOI.get(model, MOI.DualObjectiveValue()) ≈ 2 atol = atol rtol =
                rtol
        end

        @test MOI.get(model, MOI.VariablePrimal(), v) ≈ [0, 0, 1] atol = atol rtol =
            rtol

        @test MOI.get(model, MOI.ConstraintPrimal(), c) ≈ 1 atol = atol rtol =
            rtol

        if config.duals
            @test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT
            @test MOI.get(model, MOI.ConstraintDual(), c) ≈ -2 atol = atol rtol =
                rtol

            @test MOI.get(model, MOI.ConstraintDual(), vc1) ≈ 1 atol = atol rtol =
                rtol
            @test MOI.get(model, MOI.ConstraintDual(), vc2) ≈ 2 atol = atol rtol =
                rtol
            @test MOI.get(model, MOI.ConstraintDual(), vc3) ≈ 0 atol = atol rtol =
                rtol
        end
    end

    # setting lb of x to -1 to get :
    # max x + 2z
    # s.t. x + y + z <= 1
    # x >= -1
    # y,z >= 0
    MOI.set(model, MOI.ConstraintSet(), vc1, MOI.GreaterThan(-one(T)))

    if config.solve
        MOI.optimize!(model)

        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status

        @test MOI.get(model, MOI.ResultCount()) >= 1

        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT

        @test MOI.get(model, MOI.ObjectiveValue()) ≈ 3 atol = atol rtol = rtol
        if config.dual_objective_value
            @test MOI.get(model, MOI.DualObjectiveValue()) ≈ 3 atol = atol rtol =
                rtol
        end

        @test MOI.get(model, MOI.VariablePrimal(), v) ≈ [-1, 0, 2] atol = atol rtol =
            rtol

        if config.duals
            @test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT
        end
    end

    # put lb of x back to 0 and fix z to zero to get :
    # max x + 2z
    # s.t. x + y + z <= 1
    # x, y >= 0, z = 0 (vc3)
    MOI.set(model, MOI.ConstraintSet(), vc1, MOI.GreaterThan(zero(T)))

    MOI.delete(model, vc3)

    vc3 = MOI.add_constraint(
        model,
        MOI.SingleVariable(v[3]),
        MOI.EqualTo(zero(T)),
    )
    @test vc3.value == v[3].value
    @test MOI.get(
        model,
        MOI.NumberOfConstraints{MOI.SingleVariable,MOI.GreaterThan{T}}(),
    ) == 2

    if config.solve
        MOI.optimize!(model)

        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status

        @test MOI.get(model, MOI.ResultCount()) >= 1

        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT

        @test MOI.get(model, MOI.ObjectiveValue()) ≈ 1 atol = atol rtol = rtol

        @test MOI.get(model, MOI.VariablePrimal(), v) ≈ [1, 0, 0] atol = atol rtol =
            rtol
    end

    # modify affine linear constraint set to be == 2 to get :
    # max x + 2z
    # s.t. x + y + z == 2 (c)
    # x,y >= 0, z = 0
    MOI.delete(model, c)
    # note: adding some redundant zero coefficients to catch solvers that don't handle duplicate coefficients correctly:
    cf = MOI.ScalarAffineFunction{T}(
        MOI.ScalarAffineTerm{
            T,
        }.(
            [
                zero(T),
                zero(T),
                zero(T),
                one(T),
                one(T),
                one(T),
                zero(T),
                zero(T),
                zero(T),
            ],
            [v; v; v],
        ),
        zero(T),
    )
    c = MOI.add_constraint(model, cf, MOI.EqualTo(T(2)))
    @test MOI.get(
        model,
        MOI.NumberOfConstraints{MOI.ScalarAffineFunction{T},MOI.LessThan{T}}(),
    ) == 0
    @test MOI.get(
        model,
        MOI.NumberOfConstraints{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}(),
    ) == 1

    if config.solve
        MOI.optimize!(model)

        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status

        @test MOI.get(model, MOI.ResultCount()) >= 1

        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT

        @test MOI.get(model, MOI.ObjectiveValue()) ≈ 2 atol = atol rtol = rtol

        @test MOI.get(model, MOI.VariablePrimal(), v) ≈ [2, 0, 0] atol = atol rtol =
            rtol
    end

    # modify objective function to x + 2y to get :
    # max x + 2y
    # s.t. x + y + z == 2 (c)
    # x,y >= 0, z = 0

    objf = MOI.ScalarAffineFunction{T}(
        MOI.ScalarAffineTerm{T}.([one(T), T(2), zero(T)], v),
        zero(T),
    )
    MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(), objf)
    MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)

    if config.query
        @test objf ≈ MOI.get(
            model,
            MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
        )
    end

    if config.solve
        MOI.optimize!(model)

        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status

        @test MOI.get(model, MOI.ResultCount()) >= 1

        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT

        @test MOI.get(model, MOI.ObjectiveValue()) ≈ 4 atol = atol rtol = rtol

        @test MOI.get(model, MOI.VariablePrimal(), v) ≈ [0, 2, 0] atol = atol rtol =
            rtol
    end

    # add constraint x - y >= 0 (c2) to get :
    # max x+2y
    # s.t. x + y + z == 2 (c)
    # x - y >= 0 (c2)
    # x,y >= 0 (vc1,vc2), z = 0 (vc3)

    cf2 = MOI.ScalarAffineFunction{T}(
        MOI.ScalarAffineTerm{T}.([one(T), -one(T), zero(T)], v),
        zero(T),
    )
    c2 = MOI.add_constraint(model, cf2, MOI.GreaterThan(zero(T)))
    @test MOI.get(
        model,
        MOI.NumberOfConstraints{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}(),
    ) == 1
    @test MOI.get(
        model,
        MOI.NumberOfConstraints{
            MOI.ScalarAffineFunction{T},
            MOI.GreaterThan{T},
        }(),
    ) == 1
    @test MOI.get(
        model,
        MOI.NumberOfConstraints{MOI.ScalarAffineFunction{T},MOI.LessThan{T}}(),
    ) == 0
    @test MOI.get(
        model,
        MOI.NumberOfConstraints{MOI.SingleVariable,MOI.EqualTo{T}}(),
    ) == 1
    @test MOI.get(
        model,
        MOI.NumberOfConstraints{MOI.SingleVariable,MOI.GreaterThan{T}}(),
    ) == 2
    @test MOI.get(
        model,
        MOI.NumberOfConstraints{MOI.SingleVariable,MOI.LessThan{T}}(),
    ) == 0

    if config.solve
        MOI.optimize!(model)

        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status

        @test MOI.get(model, MOI.ResultCount()) >= 1

        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT

        @test MOI.get(model, MOI.ObjectiveValue()) ≈ 3 atol = atol rtol = rtol
        if config.dual_objective_value
            @test MOI.get(model, MOI.DualObjectiveValue()) ≈ 3 atol = atol rtol =
                rtol
        end

        @test MOI.get(model, MOI.VariablePrimal(), v) ≈ [1, 1, 0] atol = atol rtol =
            rtol

        @test MOI.get(model, MOI.ConstraintPrimal(), c) ≈ 2 atol = atol rtol =
            rtol

        @test MOI.get(model, MOI.ConstraintPrimal(), c2) ≈ 0 atol = atol rtol =
            rtol

        @test MOI.get(model, MOI.ConstraintPrimal(), vc1) ≈ 1 atol = atol rtol =
            rtol

        @test MOI.get(model, MOI.ConstraintPrimal(), vc2) ≈ 1 atol = atol rtol =
            rtol

        @test MOI.get(model, MOI.ConstraintPrimal(), vc3) ≈ 0 atol = atol rtol =
            rtol

        if config.duals
            @test MOI.get(model, MOI.DualStatus(1)) == MOI.FEASIBLE_POINT

            @test MOI.get(model, MOI.ConstraintDual(), c) ≈ -T(3 // 2) atol =
                atol rtol = rtol
            @test MOI.get(model, MOI.ConstraintDual(), c2) ≈ T(1 // 2) atol =
                atol rtol = rtol

            @test MOI.get(model, MOI.ConstraintDual(), vc1) ≈ 0 atol = atol rtol =
                rtol
            @test MOI.get(model, MOI.ConstraintDual(), vc2) ≈ 0 atol = atol rtol =
                rtol
            @test MOI.get(model, MOI.ConstraintDual(), vc3) ≈ T(3 // 2) atol =
                atol rtol = rtol
        end
    end

    if config.query
        @test MOI.get(model, MOI.ConstraintFunction(), c2) ≈ cf2
    end

    # delete variable x to get :
    # max 2y
    # s.t. y + z == 2
    # - y >= 0
    # y >= 0, z = 0

    @test MOI.get(
        model,
        MOI.NumberOfConstraints{MOI.SingleVariable,MOI.GreaterThan{T}}(),
    ) == 2
    MOI.delete(model, v[1])
    @test MOI.get(
        model,
        MOI.NumberOfConstraints{MOI.SingleVariable,MOI.GreaterThan{T}}(),
    ) == 1

    if config.query
        err = MOI.InvalidIndex(vc1)
        # vc1 should have been deleted with `v[1]`.
        @test_throws err MOI.get(model, MOI.ConstraintFunction(), vc1)
        @test_throws err MOI.get(model, MOI.ConstraintSet(), vc1)

        @test MOI.get(model, MOI.ConstraintFunction(), c2) ≈
              MOI.ScalarAffineFunction{T}(
            MOI.ScalarAffineTerm{T}.([-one(T), zero(T)], [v[2], z]),
            zero(T),
        )

        vrs = MOI.get(model, MOI.ListOfVariableIndices())
        @test vrs == [v[2], z] || vrs == [z, v[2]]
        @test MOI.get(
            model,
            MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
        ) ≈ MOI.ScalarAffineFunction{T}(
            MOI.ScalarAffineTerm{T}.([T(2), zero(T)], [v[2], z]),
            zero(T),
        )
    end
end

# add_variable (one by one)
function linear2test(model::MOI.ModelLike, config::TestConfig{T}) where {T}
    atol = config.atol
    rtol = config.rtol
    # Min -x
    # s.t. x + y <= 1
    # x, y >= 0

    @test MOIU.supports_default_copy_to(model, false) #=copy_names=#
    @test MOI.supports(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
    )
    @test MOI.supports(model, MOI.ObjectiveSense())
    @test MOI.supports_constraint(
        model,
        MOI.ScalarAffineFunction{T},
        MOI.LessThan{T},
    )
    @test MOI.supports_constraint(model, MOI.SingleVariable, MOI.GreaterThan{T})

    MOI.empty!(model)
    @test MOI.is_empty(model)

    x = MOI.add_variable(model)
    y = MOI.add_variable(model)

    @test MOI.get(model, MOI.NumberOfVariables()) == 2

    cf = MOI.ScalarAffineFunction{T}(
        MOI.ScalarAffineTerm{T}.([one(T), one(T)], [x, y]),
        zero(T),
    )
    c = MOI.add_constraint(model, cf, MOI.LessThan(one(T)))
    @test MOI.get(
        model,
        MOI.NumberOfConstraints{MOI.ScalarAffineFunction{T},MOI.LessThan{T}}(),
    ) == 1

    vc1 = MOI.add_constraint(
        model,
        MOI.SingleVariable(x),
        MOI.GreaterThan(zero(T)),
    )
    @test vc1.value == x.value
    vc2 = MOI.add_constraint(
        model,
        MOI.SingleVariable(y),
        MOI.GreaterThan(zero(T)),
    )
    @test vc2.value == y.value
    @test MOI.get(
        model,
        MOI.NumberOfConstraints{MOI.SingleVariable,MOI.GreaterThan{T}}(),
    ) == 2

    objf = MOI.ScalarAffineFunction{T}(
        MOI.ScalarAffineTerm{T}.([-one(T), zero(T)], [x, y]),
        zero(T),
    )
    MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(), objf)
    MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)

    @test MOI.get(model, MOI.ObjectiveSense()) == MOI.MIN_SENSE

    if config.solve
        @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED

        MOI.optimize!(model)

        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status

        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT

        @test MOI.get(model, MOI.ObjectiveValue()) ≈ -1 atol = atol rtol = rtol
        if config.dual_objective_value
            @test MOI.get(model, MOI.DualObjectiveValue()) ≈ -1 atol = atol rtol =
                rtol
        end

        @test MOI.get(model, MOI.VariablePrimal(), x) ≈ 1 atol = atol rtol =
            rtol

        @test MOI.get(model, MOI.VariablePrimal(), y) ≈ 0 atol = atol rtol =
            rtol

        @test MOI.get(model, MOI.ConstraintPrimal(), c) ≈ 1 atol = atol rtol =
            rtol

        if config.duals
            @test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT
            @test MOI.get(model, MOI.ConstraintDual(), c) ≈ -1 atol = atol rtol =
                rtol

            # reduced costs
            @test MOI.get(model, MOI.ConstraintDual(), vc1) ≈ 0 atol = atol rtol =
                rtol
            @test MOI.get(model, MOI.ConstraintDual(), vc2) ≈ 1 atol = atol rtol =
                rtol
        end

        if config.basis
            @test MOI.get(model, MOI.ConstraintBasisStatus(), vc1) == MOI.BASIC
            @test MOI.get(model, MOI.ConstraintBasisStatus(), vc2) ==
                  MOI.NONBASIC
            @test MOI.get(model, MOI.ConstraintBasisStatus(), c) == MOI.NONBASIC
        end
    end
end

# Issue #40 from Gurobi.jl
function linear3test(model::MOI.ModelLike, config::TestConfig{T}) where {T}
    atol = config.atol
    rtol = config.rtol
    # min  x
    # s.t. x >= 0
    #      x >= 3

    @test MOIU.supports_default_copy_to(model, false) #=copy_names=#
    @test MOI.supports(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
    )
    @test MOI.supports(model, MOI.ObjectiveSense())
    @test MOI.supports_constraint(
        model,
        MOI.ScalarAffineFunction{T},
        MOI.GreaterThan{T},
    )
    @test MOI.supports_constraint(
        model,
        MOI.ScalarAffineFunction{T},
        MOI.LessThan{T},
    )
    @test MOI.supports_constraint(model, MOI.SingleVariable, MOI.GreaterThan{T})
    @test MOI.supports_constraint(model, MOI.SingleVariable, MOI.LessThan{T})

    MOI.empty!(model)
    @test MOI.is_empty(model)

    x = MOI.add_variable(model)
    @test MOI.get(model, MOI.NumberOfVariables()) == 1

    vc = MOI.add_constraint(
        model,
        MOI.SingleVariable(x),
        MOI.GreaterThan(zero(T)),
    )
    @test vc.value == x.value
    cf = MOI.ScalarAffineFunction{T}(
        [MOI.ScalarAffineTerm{T}(one(T), x)],
        zero(T),
    )
    c = MOI.add_constraint(model, cf, MOI.GreaterThan(T(3)))

    @test MOI.get(
        model,
        MOI.NumberOfConstraints{MOI.SingleVariable,MOI.GreaterThan{T}}(),
    ) == 1
    @test MOI.get(
        model,
        MOI.NumberOfConstraints{
            MOI.ScalarAffineFunction{T},
            MOI.GreaterThan{T},
        }(),
    ) == 1

    objf = MOI.ScalarAffineFunction{T}(
        [MOI.ScalarAffineTerm{T}(one(T), x)],
        zero(T),
    )
    MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(), objf)
    MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)

    if config.solve
        MOI.optimize!(model)

        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status

        @test MOI.get(model, MOI.ResultCount()) >= 1

        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT

        @test MOI.get(model, MOI.ObjectiveValue()) ≈ 3 atol = atol rtol = rtol

        @test MOI.get(model, MOI.VariablePrimal(), x) ≈ 3 atol = atol rtol =
            rtol

        if config.basis
            @test MOI.get(model, MOI.ConstraintBasisStatus(), vc) == MOI.BASIC
            @test MOI.get(model, MOI.ConstraintBasisStatus(), c) == MOI.NONBASIC
        end
    end

    # max  x
    # s.t. x <= 0
    #      x <= 3

    MOI.empty!(model)
    @test MOI.is_empty(model)

    x = MOI.add_variable(model)
    @test MOI.get(model, MOI.NumberOfVariables()) == 1

    vc = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.LessThan(zero(T)))
    @test vc.value == x.value
    cf = MOI.ScalarAffineFunction{T}(
        [MOI.ScalarAffineTerm{T}(one(T), x)],
        zero(T),
    )
    c = MOI.add_constraint(model, cf, MOI.LessThan(T(3)))

    @test MOI.get(
        model,
        MOI.NumberOfConstraints{MOI.SingleVariable,MOI.LessThan{T}}(),
    ) == 1
    @test MOI.get(
        model,
        MOI.NumberOfConstraints{MOI.ScalarAffineFunction{T},MOI.LessThan{T}}(),
    ) == 1

    objf = MOI.ScalarAffineFunction{T}(
        [MOI.ScalarAffineTerm{T}(one(T), x)],
        zero(T),
    )
    MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(), objf)
    MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)

    if config.solve
        @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED

        MOI.optimize!(model)

        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status

        @test MOI.get(model, MOI.ResultCount()) >= 1

        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT

        @test MOI.get(model, MOI.ObjectiveValue()) ≈ 0 atol = atol rtol = rtol

        @test MOI.get(model, MOI.VariablePrimal(), x) ≈ 0 atol = atol rtol =
            rtol

        if config.basis
            @test MOI.get(model, MOI.ConstraintBasisStatus(), vc) ==
                  MOI.NONBASIC
            @test MOI.get(model, MOI.ConstraintBasisStatus(), c) == MOI.BASIC
        end
    end
end

# Modify GreaterThan{T} and LessThan{T} sets as bounds
function linear4test(model::MOI.ModelLike, config::TestConfig{T}) where {T}
    atol = config.atol
    rtol = config.rtol

    @test MOIU.supports_default_copy_to(model, false) #=copy_names=#
    @test MOI.supports(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
    )
    @test MOI.supports(model, MOI.ObjectiveSense())
    @test MOI.supports_constraint(model, MOI.SingleVariable, MOI.GreaterThan{T})
    @test MOI.supports_constraint(model, MOI.SingleVariable, MOI.LessThan{T})

    MOI.empty!(model)
    @test MOI.is_empty(model)

    x = MOI.add_variable(model)
    y = MOI.add_variable(model)

    # Min  x - y
    # s.t. zero(T) <= x          (c1)
    #             y <= zero(T)   (c2)

    MOI.set(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
        MOI.ScalarAffineFunction{T}(
            MOI.ScalarAffineTerm{T}.([one(T), -one(T)], [x, y]),
            zero(T),
        ),
    )
    MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)

    c1 = MOI.add_constraint(
        model,
        MOI.SingleVariable(x),
        MOI.GreaterThan(zero(T)),
    )
    @test c1.value == x.value
    c2 = MOI.add_constraint(model, MOI.SingleVariable(y), MOI.LessThan(zero(T)))
    @test c2.value == y.value

    if config.solve
        @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED

        MOI.optimize!(model)

        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status
        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT

        @test MOI.get(model, MOI.ObjectiveValue()) ≈ zero(T) atol = atol rtol =
            rtol
        @test MOI.get(model, MOI.VariablePrimal(), x) ≈ zero(T) atol = atol rtol =
            rtol
        @test MOI.get(model, MOI.VariablePrimal(), y) ≈ zero(T) atol = atol rtol =
            rtol
    end

    # Min  x - y
    # s.t. T(100) <= x
    #               y <= zero(T)
    MOI.set(model, MOI.ConstraintSet(), c1, MOI.GreaterThan(T(100)))
    if config.solve
        MOI.optimize!(model)
        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status
        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT

        @test MOI.get(model, MOI.ObjectiveValue()) ≈ T(100) atol = atol rtol =
            rtol
        @test MOI.get(model, MOI.VariablePrimal(), x) ≈ T(100) atol = atol rtol =
            rtol
        @test MOI.get(model, MOI.VariablePrimal(), y) ≈ zero(T) atol = atol rtol =
            rtol
    end

    # Min  x - y
    # s.t. T(100) <= x
    #               y <= -T(100)
    MOI.set(model, MOI.ConstraintSet(), c2, MOI.LessThan(-T(100)))
    if config.solve
        MOI.optimize!(model)
        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status
        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT

        @test MOI.get(model, MOI.ObjectiveValue()) ≈ T(200) atol = atol rtol =
            rtol
        @test MOI.get(model, MOI.VariablePrimal(), x) ≈ T(100) atol = atol rtol =
            rtol
        @test MOI.get(model, MOI.VariablePrimal(), y) ≈ -T(100) atol = atol rtol =
            rtol
    end
end

# Change coeffs, del constr, del var
function linear5test(model::MOI.ModelLike, config::TestConfig{T}) where {T}
    atol = config.atol
    rtol = config.rtol
    #@test MOI.get(model, MOI.SupportsDeleteVariable())
    #####################################
    # Start from simple LP
    # Solve it
    # Copy and solve again
    # Chg coeff, solve, change back solve
    # del constr and solve
    # del var and solve

    #   maximize x + y
    #
    #   s.t. 2 x + 1 y <= 4
    #        1 x + 2 y <= 4
    #        x >= 0, y >= 0
    #
    #   solution: x = 1.3333333, y = 1.3333333, objv = 2.66666666

    @test MOIU.supports_default_copy_to(model, false) #=copy_names=#
    @test MOI.supports(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
    )
    @test MOI.supports(model, MOI.ObjectiveSense())
    @test MOI.supports_constraint(
        model,
        MOI.ScalarAffineFunction{T},
        MOI.GreaterThan{T},
    )
    @test MOI.supports_constraint(
        model,
        MOI.ScalarAffineFunction{T},
        MOI.LessThan{T},
    )

    MOI.empty!(model)
    @test MOI.is_empty(model)

    x = MOI.add_variable(model)
    y = MOI.add_variable(model)

    @test MOI.get(model, MOI.NumberOfVariables()) == 2

    cf1 = MOI.ScalarAffineFunction{T}(
        MOI.ScalarAffineTerm{T}.([T(2), one(T)], [x, y]),
        zero(T),
    )
    cf2 = MOI.ScalarAffineFunction{T}(
        MOI.ScalarAffineTerm{T}.([one(T), T(2)], [x, y]),
        zero(T),
    )

    c1 = MOI.add_constraint(model, cf1, MOI.LessThan(T(4)))
    c2 = MOI.add_constraint(model, cf2, MOI.LessThan(T(4)))

    @test MOI.get(
        model,
        MOI.NumberOfConstraints{MOI.ScalarAffineFunction{T},MOI.LessThan{T}}(),
    ) == 2

    vc1 = MOI.add_constraint(
        model,
        MOI.SingleVariable(x),
        MOI.GreaterThan(zero(T)),
    )
    @test vc1.value == x.value
    vc2 = MOI.add_constraint(
        model,
        MOI.SingleVariable(y),
        MOI.GreaterThan(zero(T)),
    )
    @test vc2.value == y.value

    @test MOI.get(
        model,
        MOI.NumberOfConstraints{MOI.SingleVariable,MOI.GreaterThan{T}}(),
    ) == 2

    objf = MOI.ScalarAffineFunction{T}(
        MOI.ScalarAffineTerm{T}.([one(T), one(T)], [x, y]),
        zero(T),
    )
    MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(), objf)
    MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)

    if config.solve
        @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED

        MOI.optimize!(model)

        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status

        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT

        @test MOI.get(model, MOI.ObjectiveValue()) ≈ T(8 // 3) atol = atol rtol =
            rtol

        @test MOI.get(model, MOI.VariablePrimal(), [x, y]) ≈
              [T(4 // 3), T(4 // 3)] atol = atol rtol = rtol
    end

    # copy and solve again
    # missing test

    # change coeff
    #   maximize x + y
    #
    #   s.t. 2 x + 3 y <= 4
    #        1 x + 2 y <= 4
    #        x >= 0, y >= 0
    #
    #   solution: x = 2, y = 0, objv = 2

    if config.modify_lhs
        MOI.modify(model, c1, MOI.ScalarCoefficientChange(y, T(3)))
    else
        MOI.delete(model, c1)
        cf1 = MOI.ScalarAffineFunction{T}(
            MOI.ScalarAffineTerm{T}.([T(2), T(3)], [x, y]),
            zero(T),
        )
        c1 = MOI.add_constraint(model, cf1, MOI.LessThan(T(4)))
    end

    if config.solve
        MOI.optimize!(model)

        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status

        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT

        @test MOI.get(model, MOI.ObjectiveValue()) ≈ 2 atol = atol rtol = rtol

        @test MOI.get(model, MOI.VariablePrimal(), [x, y]) ≈ [T(2), zero(T)] atol =
            atol rtol = rtol
    end

    # delconstrs and solve
    #   maximize x + y
    #
    #   s.t. 1 x + 2 y <= 4
    #        x >= 0, y >= 0
    #
    #   solution: x = 4, y = 0, objv = 4
    MOI.delete(model, c1)

    if config.solve
        MOI.optimize!(model)

        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status

        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT

        @test MOI.get(model, MOI.ObjectiveValue()) ≈ 4 atol = atol rtol = rtol

        @test MOI.get(model, MOI.VariablePrimal(), [x, y]) ≈ [T(4), zero(T)] atol =
            atol rtol = rtol
    end

    # delvars and solve
    #   maximize y
    #
    #   s.t.  2 y <= 4
    #           y >= 0
    #
    #   solution: y = 2, objv = 2
    MOI.delete(model, x)

    if config.solve
        MOI.optimize!(model)

        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status

        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT

        @test MOI.get(model, MOI.ObjectiveValue()) ≈ 2 atol = atol rtol = rtol

        @test MOI.get(model, MOI.VariablePrimal(), y) ≈ T(2) atol = atol rtol =
            rtol
    end
end

# Modify GreaterThan{T} and LessThan{T} sets as linear constraints
function linear6test(model::MOI.ModelLike, config::TestConfig{T}) where {T}
    atol = config.atol
    rtol = config.rtol

    @test MOIU.supports_default_copy_to(model, false) #=copy_names=#
    @test MOI.supports(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
    )
    @test MOI.supports(model, MOI.ObjectiveSense())
    @test MOI.supports_constraint(
        model,
        MOI.ScalarAffineFunction{T},
        MOI.GreaterThan{T},
    )
    @test MOI.supports_constraint(
        model,
        MOI.ScalarAffineFunction{T},
        MOI.LessThan{T},
    )

    MOI.empty!(model)
    @test MOI.is_empty(model)

    x = MOI.add_variable(model)
    y = MOI.add_variable(model)

    # Min  x - y
    # s.t. zero(T) <= x          (c1)
    #             y <= zero(T)   (c2)

    MOI.set(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
        MOI.ScalarAffineFunction{T}(
            MOI.ScalarAffineTerm{T}.([one(T), -one(T)], [x, y]),
            zero(T),
        ),
    )
    MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)

    fx = convert(MOI.ScalarAffineFunction{T}, MOI.SingleVariable(x))
    c1 = MOI.add_constraint(model, fx, MOI.GreaterThan(zero(T)))
    fy = convert(MOI.ScalarAffineFunction{T}, MOI.SingleVariable(y))
    c2 = MOI.add_constraint(model, fy, MOI.LessThan(zero(T)))

    if config.query
        @test MOI.get(model, MOI.ConstraintFunction(), c1) ≈ fx
        @test MOI.get(model, MOI.ConstraintSet(), c1) ==
              MOI.GreaterThan(zero(T))
        @test MOI.get(model, MOI.ConstraintFunction(), c2) ≈ fy
        @test MOI.get(model, MOI.ConstraintSet(), c2) == MOI.LessThan(zero(T))
    end

    if config.solve
        @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED

        MOI.optimize!(model)

        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status
        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT

        @test MOI.get(model, MOI.ObjectiveValue()) ≈ zero(T) atol = atol rtol =
            rtol
        @test MOI.get(model, MOI.VariablePrimal(), x) ≈ zero(T) atol = atol rtol =
            rtol
        @test MOI.get(model, MOI.VariablePrimal(), y) ≈ zero(T) atol = atol rtol =
            rtol
    end

    # Min  x - y
    # s.t. T(100) <= x
    #               y <= zero(T)
    MOI.set(model, MOI.ConstraintSet(), c1, MOI.GreaterThan(T(100)))

    if config.query
        @test MOI.get(model, MOI.ConstraintFunction(), c1) ≈ fx
        @test MOI.get(model, MOI.ConstraintSet(), c1) == MOI.GreaterThan(T(100))
        @test MOI.get(model, MOI.ConstraintFunction(), c2) ≈ fy
        @test MOI.get(model, MOI.ConstraintSet(), c2) == MOI.LessThan(zero(T))
    end

    if config.solve
        MOI.optimize!(model)
        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status
        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT

        @test MOI.get(model, MOI.ObjectiveValue()) ≈ T(100) atol = atol rtol =
            rtol
        @test MOI.get(model, MOI.VariablePrimal(), x) ≈ T(100) atol = atol rtol =
            rtol
        @test MOI.get(model, MOI.VariablePrimal(), y) ≈ zero(T) atol = atol rtol =
            rtol
    end

    # Min  x - y
    # s.t. T(100) <= x
    #               y <= -T(100)
    MOI.set(model, MOI.ConstraintSet(), c2, MOI.LessThan(-T(100)))

    if config.query
        @test MOI.get(model, MOI.ConstraintFunction(), c1) ≈ fx
        @test MOI.get(model, MOI.ConstraintSet(), c1) == MOI.GreaterThan(T(100))
        @test MOI.get(model, MOI.ConstraintFunction(), c2) ≈ fy
        @test MOI.get(model, MOI.ConstraintSet(), c2) == MOI.LessThan(-T(100))
    end

    if config.solve
        MOI.optimize!(model)
        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status
        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT

        @test MOI.get(model, MOI.ObjectiveValue()) ≈ T(200) atol = atol rtol =
            rtol
        @test MOI.get(model, MOI.VariablePrimal(), x) ≈ T(100) atol = atol rtol =
            rtol
        @test MOI.get(model, MOI.VariablePrimal(), y) ≈ -T(100) atol = atol rtol =
            rtol
    end
end

# Modify constants in Nonnegatives and Nonpositives
function linear7test(model::MOI.ModelLike, config::TestConfig{T}) where {T}
    atol = config.atol
    rtol = config.rtol

    # Min  x - y
    # s.t. bx <= x          (c1)
    #             y <= by   (c2)
    #
    # or, in more detail,
    #
    # Min    1 x - 1 y
    # s.t. - 1 x       <= - bx  (z)   (c1)
    #              1 y <=   by  (w)   (c2)
    #
    # with generic dual
    #
    # Max  - bx z + by w
    # s.t. -    z        == - 1     (c1)
    #                  w ==   1     (c2)
    # i.e. z == w == 1

    @test MOIU.supports_default_copy_to(model, false) #=copy_names=#
    @test MOI.supports(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
    )
    @test MOI.supports(model, MOI.ObjectiveSense())
    @test MOI.supports_constraint(
        model,
        MOI.VectorAffineFunction{T},
        MOI.Nonnegatives,
    )
    @test MOI.supports_constraint(
        model,
        MOI.VectorAffineFunction{T},
        MOI.Nonpositives,
    )

    MOI.empty!(model)
    @test MOI.is_empty(model)

    x = MOI.add_variable(model)
    y = MOI.add_variable(model)

    # Min  x - y
    # s.t. zero(T) <= x          (c1)
    #             y <= zero(T)   (c2)

    MOI.set(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
        MOI.ScalarAffineFunction{T}(
            MOI.ScalarAffineTerm{T}.([one(T), -one(T)], [x, y]),
            zero(T),
        ),
    )
    MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)

    c1 = MOI.add_constraint(
        model,
        MOI.VectorAffineFunction{T}(
            [MOI.VectorAffineTerm{T}(1, MOI.ScalarAffineTerm{T}(one(T), x))],
            [zero(T)],
        ),
        MOI.Nonnegatives(1),
    )
    c2 = MOI.add_constraint(
        model,
        MOI.VectorAffineFunction{T}(
            [MOI.VectorAffineTerm{T}(1, MOI.ScalarAffineTerm{T}(one(T), y))],
            [zero(T)],
        ),
        MOI.Nonpositives(1),
    )

    if config.solve
        @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED

        MOI.optimize!(model)

        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status
        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT

        @test MOI.get(model, MOI.ObjectiveValue()) ≈ zero(T) atol = atol rtol =
            rtol
        @test MOI.get(model, MOI.VariablePrimal(), x) ≈ zero(T) atol = atol rtol =
            rtol
        @test MOI.get(model, MOI.VariablePrimal(), y) ≈ zero(T) atol = atol rtol =
            rtol
    end

    # Min  x - y
    # s.t. T(100) <= x
    #               y <= zero(T)

    if config.modify_lhs
        MOI.modify(model, c1, MOI.VectorConstantChange([-T(100)]))
    else
        MOI.delete(model, c1)
        c1 = MOI.add_constraint(
            model,
            MOI.VectorAffineFunction{T}(
                [
                    MOI.VectorAffineTerm{T}(
                        1,
                        MOI.ScalarAffineTerm{T}(one(T), x),
                    ),
                ],
                [-T(100)],
            ),
            MOI.Nonnegatives(1),
        )
    end

    if config.solve
        MOI.optimize!(model)
        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status
        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT

        @test MOI.get(model, MOI.ObjectiveValue()) ≈ T(100) atol = atol rtol =
            rtol
        @test MOI.get(model, MOI.VariablePrimal(), x) ≈ T(100) atol = atol rtol =
            rtol
        @test MOI.get(model, MOI.VariablePrimal(), y) ≈ zero(T) atol = atol rtol =
            rtol
    end

    # Min  x - y
    # s.t. T(100) <= x
    #               y <= -T(100)

    if config.modify_lhs
        MOI.modify(model, c2, MOI.VectorConstantChange([T(100)]))
    else
        MOI.delete(model, c2)
        c2 = MOI.add_constraint(
            model,
            MOI.VectorAffineFunction{T}(
                [
                    MOI.VectorAffineTerm{T}(
                        1,
                        MOI.ScalarAffineTerm{T}(one(T), y),
                    ),
                ],
                [T(100)],
            ),
            MOI.Nonpositives(1),
        )
    end

    if config.solve
        MOI.optimize!(model)
        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status
        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT

        @test MOI.get(model, MOI.ObjectiveValue()) ≈ T(200) atol = atol rtol =
            rtol
        @test MOI.get(model, MOI.VariablePrimal(), x) ≈ T(100) atol = atol rtol =
            rtol
        @test MOI.get(model, MOI.VariablePrimal(), y) ≈ -T(100) atol = atol rtol =
            rtol
    end
end

# infeasible problem
function linear8atest(model::MOI.ModelLike, config::TestConfig{T}) where {T}
    atol = config.atol
    rtol = config.rtol
    # min x
    # s.t. 2x+y <= -1
    # x,y >= 0

    @test MOIU.supports_default_copy_to(model, false) #=copy_names=#
    @test MOI.supports(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
    )
    @test MOI.supports(model, MOI.ObjectiveSense())
    @test MOI.supports_constraint(
        model,
        MOI.ScalarAffineFunction{T},
        MOI.LessThan{T},
    )
    @test MOI.supports_constraint(model, MOI.SingleVariable, MOI.GreaterThan{T})

    MOI.empty!(model)
    @test MOI.is_empty(model)

    x = MOI.add_variable(model)
    y = MOI.add_variable(model)
    c = MOI.add_constraint(
        model,
        MOI.ScalarAffineFunction{T}(
            MOI.ScalarAffineTerm{T}.([T(2), one(T)], [x, y]),
            zero(T),
        ),
        MOI.LessThan(-one(T)),
    )
    bndx = MOI.add_constraint(
        model,
        MOI.SingleVariable(x),
        MOI.GreaterThan(zero(T)),
    )
    @test bndx.value == x.value
    bndy = MOI.add_constraint(
        model,
        MOI.SingleVariable(y),
        MOI.GreaterThan(zero(T)),
    )
    @test bndy.value == y.value

    MOI.set(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
        MOI.ScalarAffineFunction{T}(
            [MOI.ScalarAffineTerm{T}(one(T), x)],
            zero(T),
        ),
    )
    MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)

    if config.solve
        @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED

        MOI.optimize!(model)

        @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE ||
              MOI.get(model, MOI.TerminationStatus()) ==
              MOI.INFEASIBLE_OR_UNBOUNDED
        if config.duals && config.infeas_certificates
            # solver returned an infeasibility ray
            @test MOI.get(model, MOI.ResultCount()) >= 1

            @test MOI.get(model, MOI.DualStatus()) ==
                  MOI.INFEASIBILITY_CERTIFICATE
            cd = MOI.get(model, MOI.ConstraintDual(), c)
            @test cd < -atol
            # TODO: farkas dual on bounds - see #127
            # xd = MOI.get(model, MOI.ConstraintDual(), bndx)
            # yd = MOI.get(model, MOI.ConstraintDual(), bndy)
            # @test xd > atol
            # @test yd > atol
            # @test yd ≈ -cd atol=atol rtol=rtol
            # @test xd ≈ -2cd atol=atol rtol=rtol
        else
            # solver returned nothing
            @test MOI.get(model, MOI.ResultCount()) == 0
        end
    end
end

# unbounded problem
function linear8btest(model::MOI.ModelLike, config::TestConfig{T}) where {T}
    atol = config.atol
    rtol = config.rtol
    # min -x-y
    # s.t. -x+2y <= 0
    # x,y >= 0

    @test MOIU.supports_default_copy_to(model, false) #=copy_names=#
    @test MOI.supports(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
    )
    @test MOI.supports(model, MOI.ObjectiveSense())
    @test MOI.supports_constraint(
        model,
        MOI.ScalarAffineFunction{T},
        MOI.LessThan{T},
    )
    @test MOI.supports_constraint(model, MOI.SingleVariable, MOI.GreaterThan{T})

    MOI.empty!(model)
    @test MOI.is_empty(model)

    x = MOI.add_variable(model)
    y = MOI.add_variable(model)
    MOI.add_constraint(
        model,
        MOI.ScalarAffineFunction{T}(
            MOI.ScalarAffineTerm{T}.([-one(T), T(2)], [x, y]),
            zero(T),
        ),
        MOI.LessThan(zero(T)),
    )
    vc1 = MOI.add_constraint(
        model,
        MOI.SingleVariable(x),
        MOI.GreaterThan(zero(T)),
    )
    @test vc1.value == x.value
    vc2 = MOI.add_constraint(
        model,
        MOI.SingleVariable(y),
        MOI.GreaterThan(zero(T)),
    )
    @test vc2.value == y.value

    MOI.set(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
        MOI.ScalarAffineFunction{T}(
            MOI.ScalarAffineTerm{T}.([-one(T), -one(T)], [x, y]),
            zero(T),
        ),
    )
    MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)

    if config.solve
        @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED

        MOI.optimize!(model)

        @test MOI.get(model, MOI.TerminationStatus()) == MOI.DUAL_INFEASIBLE ||
              MOI.get(model, MOI.TerminationStatus()) ==
              MOI.INFEASIBLE_OR_UNBOUNDED
        if config.infeas_certificates
            # solver returned an unbounded ray
            @test MOI.get(model, MOI.ResultCount()) >= 1
            @test MOI.get(model, MOI.PrimalStatus()) ==
                  MOI.INFEASIBILITY_CERTIFICATE
        else
            # solver returned nothing
            @test MOI.get(model, MOI.ResultCount()) == 0
        end
    end
end

# unbounded problem with unique ray
function linear8ctest(model::MOI.ModelLike, config::TestConfig{T}) where {T}
    atol = config.atol
    rtol = config.rtol
    # min -x-y
    # s.t. x-y == 0
    # x,y >= 0

    @test MOIU.supports_default_copy_to(model, false) #=copy_names=#
    @test MOI.supports(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
    )
    @test MOI.supports(model, MOI.ObjectiveSense())
    @test MOI.supports_constraint(
        model,
        MOI.ScalarAffineFunction{T},
        MOI.EqualTo{T},
    )
    @test MOI.supports_constraint(model, MOI.SingleVariable, MOI.GreaterThan{T})

    MOI.empty!(model)
    @test MOI.is_empty(model)

    x = MOI.add_variable(model)
    y = MOI.add_variable(model)
    MOI.add_constraint(
        model,
        MOI.ScalarAffineFunction{T}(
            MOI.ScalarAffineTerm{T}.([one(T), -one(T)], [x, y]),
            zero(T),
        ),
        MOI.EqualTo(zero(T)),
    )
    vc1 = MOI.add_constraint(
        model,
        MOI.SingleVariable(x),
        MOI.GreaterThan(zero(T)),
    )
    @test vc1.value == x.value
    vc2 = MOI.add_constraint(
        model,
        MOI.SingleVariable(y),
        MOI.GreaterThan(zero(T)),
    )
    @test vc2.value == y.value

    MOI.set(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
        MOI.ScalarAffineFunction{T}(
            MOI.ScalarAffineTerm{T}.([-one(T), -one(T)], [x, y]),
            zero(T),
        ),
    )
    MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)

    if config.solve
        @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED

        MOI.optimize!(model)

        @test MOI.get(model, MOI.TerminationStatus()) == MOI.DUAL_INFEASIBLE ||
              MOI.get(model, MOI.TerminationStatus()) ==
              MOI.INFEASIBLE_OR_UNBOUNDED
        if config.infeas_certificates
            # solver returned an unbounded ray
            @test MOI.get(model, MOI.ResultCount()) >= 1
            @test MOI.get(model, MOI.PrimalStatus()) ==
                  MOI.INFEASIBILITY_CERTIFICATE
            ray = MOI.get(model, MOI.VariablePrimal(), [x, y])
            @test ray[1] ≈ ray[2] atol = atol rtol = rtol
        else
            # solver returned nothing
            @test MOI.get(model, MOI.ResultCount()) == 0
        end
    end
end

# add_constraints
function linear9test(model::MOI.ModelLike, config::TestConfig{T}) where {T}
    atol = config.atol
    rtol = config.rtol
    #   maximize 1000 x + 350 y
    #
    #       s.t.                x >= 30
    #                           y >= 0
    #                 x -   1.5 y >= 0
    #            12   x +   8   y <= 1000
    #            1000 x + 300   y <= 70000
    #
    #   solution: (59.0909, 36.3636)
    #   objv: 71818.1818

    @test MOIU.supports_default_copy_to(model, false) #=copy_names=#
    @test MOI.supports(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
    )
    @test MOI.supports(model, MOI.ObjectiveSense())
    @test MOI.supports_constraint(
        model,
        MOI.ScalarAffineFunction{T},
        MOI.GreaterThan{T},
    )
    @test MOI.supports_constraint(
        model,
        MOI.ScalarAffineFunction{T},
        MOI.LessThan{T},
    )
    @test MOI.supports_constraint(model, MOI.SingleVariable, MOI.GreaterThan{T})

    MOI.empty!(model)
    @test MOI.is_empty(model)

    x = MOI.add_variable(model)
    y = MOI.add_variable(model)

    vc12 = MOI.add_constraints(
        model,
        [MOI.SingleVariable(x), MOI.SingleVariable(y)],
        [MOI.GreaterThan(T(30)), MOI.GreaterThan(zero(T))],
    )
    @test vc12[1].value == x.value
    @test vc12[2].value == y.value

    c1 = MOI.add_constraints(
        model,
        [
            MOI.ScalarAffineFunction{T}(
                MOI.ScalarAffineTerm{T}.([one(T), -T(3 // 2)], [x, y]),
                zero(T),
            ),
        ],
        [MOI.GreaterThan(zero(T))],
    )

    c23 = MOI.add_constraints(
        model,
        [
            MOI.ScalarAffineFunction{T}(
                MOI.ScalarAffineTerm{T}.([T(12), T(8)], [x, y]),
                zero(T),
            ),
            MOI.ScalarAffineFunction{T}(
                MOI.ScalarAffineTerm{T}.([T(1_000), T(300)], [x, y]),
                zero(T),
            ),
        ],
        [MOI.LessThan(T(1_000)), MOI.LessThan(T(70_000))],
    )

    MOI.set(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
        MOI.ScalarAffineFunction{T}(
            MOI.ScalarAffineTerm{T}.([T(1_000), T(350)], [x, y]),
            zero(T),
        ),
    )
    MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)

    if config.solve
        @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED

        MOI.optimize!(model)

        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status
        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT

        @test MOI.get(model, MOI.ObjectiveValue()) ≈ 790_000 / T(11) atol = atol rtol =
            rtol
        @test MOI.get(model, MOI.VariablePrimal(), x) ≈ 650 / T(11) atol = atol rtol =
            rtol
        @test MOI.get(model, MOI.VariablePrimal(), y) ≈ 400 / T(11) atol = atol rtol =
            rtol

        if config.basis
            @test MOI.get(model, MOI.ConstraintBasisStatus(), vc12[1]) ==
                  MOI.BASIC
            @test MOI.get(model, MOI.ConstraintBasisStatus(), vc12[2]) ==
                  MOI.BASIC
            @test MOI.get(model, MOI.ConstraintBasisStatus(), c1[1]) ==
                  MOI.BASIC
            @test MOI.get(model, MOI.ConstraintBasisStatus(), c23[1]) ==
                  MOI.NONBASIC
            @test MOI.get(model, MOI.ConstraintBasisStatus(), c23[2]) ==
                  MOI.NONBASIC
        end
    end
end

# ranged constraints
function linear10test(model::MOI.ModelLike, config::TestConfig{T}) where {T}
    atol = config.atol
    rtol = config.rtol
    #   maximize x + y
    #
    #       s.t.  5 <= x + y <= 10
    #                  x,  y >= 0

    @test MOIU.supports_default_copy_to(model, false) #=copy_names=#
    @test MOI.supports(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
    )
    @test MOI.supports(model, MOI.ObjectiveSense())
    @test MOI.supports_constraint(
        model,
        MOI.ScalarAffineFunction{T},
        MOI.Interval{T},
    )
    @test MOI.supports_constraint(model, MOI.SingleVariable, MOI.GreaterThan{T})

    MOI.empty!(model)
    @test MOI.is_empty(model)

    x = MOI.add_variable(model)
    y = MOI.add_variable(model)

    vc = MOI.add_constraints(
        model,
        [MOI.SingleVariable(x), MOI.SingleVariable(y)],
        [MOI.GreaterThan(zero(T)), MOI.GreaterThan(zero(T))],
    )
    @test vc[1].value == x.value
    @test vc[2].value == y.value

    c = MOI.add_constraint(
        model,
        MOI.ScalarAffineFunction{T}(
            MOI.ScalarAffineTerm{T}.([one(T), one(T)], [x, y]),
            zero(T),
        ),
        MOI.Interval(T(5), T(10)),
    )

    MOI.set(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
        MOI.ScalarAffineFunction{T}(
            MOI.ScalarAffineTerm{T}.([one(T), one(T)], [x, y]),
            zero(T),
        ),
    )
    MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)

    if config.solve
        @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED

        MOI.optimize!(model)

        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status
        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT
        @test MOI.get(model, MOI.ObjectiveValue()) ≈ T(10) atol = atol rtol =
            rtol
        if config.dual_objective_value
            @test MOI.get(model, MOI.DualObjectiveValue()) ≈ T(10) atol = atol rtol =
                rtol
        end
        @test MOI.get(model, MOI.ConstraintPrimal(), c) ≈ 10 atol = atol rtol =
            rtol

        if config.duals
            @test MOI.get(model, MOI.ResultCount()) >= 1
            @test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT
            @test MOI.get(model, MOI.ConstraintDual(), c) ≈ -1 atol = atol rtol =
                rtol
        end

        if config.basis
            # There are multiple optimal bases. Either x or y can be in the optimal basis.
            @test (
                MOI.get(model, MOI.ConstraintBasisStatus(), vc[1]) ==
                MOI.BASIC ||
                MOI.get(model, MOI.ConstraintBasisStatus(), vc[2]) == MOI.BASIC
            )
            @test MOI.get(model, MOI.ConstraintBasisStatus(), c) ==
                  MOI.NONBASIC_AT_UPPER
        end
    end

    MOI.set(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
        MOI.ScalarAffineFunction{T}(
            MOI.ScalarAffineTerm{T}.([one(T), one(T)], [x, y]),
            zero(T),
        ),
    )
    MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)

    if config.solve
        MOI.optimize!(model)

        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status
        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT
        @test MOI.get(model, MOI.ObjectiveValue()) ≈ T(5) atol = atol rtol =
            rtol
        if config.dual_objective_value
            @test MOI.get(model, MOI.DualObjectiveValue()) ≈ T(5) atol = atol rtol =
                rtol
        end
        @test MOI.get(model, MOI.ConstraintPrimal(), c) ≈ 5 atol = atol rtol =
            rtol

        if config.duals
            @test MOI.get(model, MOI.ResultCount()) >= 1
            @test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT
            @test MOI.get(model, MOI.ConstraintDual(), c) ≈ 1 atol = atol rtol =
                rtol
        end

        if config.basis
            # There are multiple optimal bases. Either x or y can be in the optimal basis."
            @test (
                MOI.get(model, MOI.ConstraintBasisStatus(), vc[1]) ==
                MOI.BASIC ||
                MOI.get(model, MOI.ConstraintBasisStatus(), vc[2]) == MOI.BASIC
            )
            @test MOI.get(model, MOI.ConstraintBasisStatus(), c) ==
                  MOI.NONBASIC_AT_LOWER
        end
    end

    MOI.set(model, MOI.ConstraintSet(), c, MOI.Interval(T(2), T(12)))

    if config.query
        @test MOI.get(model, MOI.ConstraintSet(), c) ==
              MOI.Interval(T(2), T(12))
    end

    if config.solve
        MOI.optimize!(model)

        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status
        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT
        @test MOI.get(model, MOI.ObjectiveValue()) ≈ T(2) atol = atol rtol =
            rtol
        if config.dual_objective_value
            @test MOI.get(model, MOI.DualObjectiveValue()) ≈ T(2) atol = atol rtol =
                rtol
        end
        @test MOI.get(model, MOI.ConstraintPrimal(), c) ≈ 2 atol = atol rtol =
            rtol

        if config.basis
            # There are multiple optimal bases. Either x or y can be in the optimal basis.
            @test (
                MOI.get(model, MOI.ConstraintBasisStatus(), vc[1]) ==
                MOI.BASIC ||
                MOI.get(model, MOI.ConstraintBasisStatus(), vc[2]) == MOI.BASIC
            )
            @test MOI.get(model, MOI.ConstraintBasisStatus(), c) ==
                  MOI.NONBASIC_AT_LOWER
        end

        if config.duals
            @test MOI.get(model, MOI.ResultCount()) >= 1
            @test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT
            @test MOI.get(model, MOI.ConstraintDual(), c) ≈ 1 atol = atol rtol =
                rtol
        end
    end

    MOI.set(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
        MOI.ScalarAffineFunction{T}(
            MOI.ScalarAffineTerm{T}.([one(T), one(T)], [x, y]),
            zero(T),
        ),
    )
    MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)

    if config.solve
        MOI.optimize!(model)

        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status
        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT
        @test MOI.get(model, MOI.ObjectiveValue()) ≈ T(12) atol = atol rtol =
            rtol
        @test MOI.get(model, MOI.ConstraintPrimal(), c) ≈ 12 atol = atol rtol =
            rtol

        if config.basis
            # There are multiple optimal bases. Either x or y can be in the optimal basis.
            @test (
                MOI.get(model, MOI.ConstraintBasisStatus(), vc[1]) ==
                MOI.BASIC ||
                MOI.get(model, MOI.ConstraintBasisStatus(), vc[2]) == MOI.BASIC
            )
            @test MOI.get(model, MOI.ConstraintBasisStatus(), c) ==
                  MOI.NONBASIC_AT_UPPER
        end
    end
end

# inactive ranged constraints
function linear10btest(model::MOI.ModelLike, config::TestConfig{T}) where {T}
    atol = config.atol
    rtol = config.rtol
    #   minimize x + y
    #
    #       s.t.  -1 <= x + y <= 10
    #                   x,  y >= 0

    @test MOIU.supports_default_copy_to(model, false) #=copy_names=#
    @test MOI.supports(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
    )
    @test MOI.supports(model, MOI.ObjectiveSense())
    @test MOI.supports_constraint(
        model,
        MOI.ScalarAffineFunction{T},
        MOI.Interval{T},
    )
    @test MOI.supports_constraint(model, MOI.SingleVariable, MOI.GreaterThan{T})

    MOI.empty!(model)
    @test MOI.is_empty(model)

    x = MOI.add_variable(model)
    y = MOI.add_variable(model)

    vc = MOI.add_constraints(
        model,
        [MOI.SingleVariable(x), MOI.SingleVariable(y)],
        [MOI.GreaterThan(zero(T)), MOI.GreaterThan(zero(T))],
    )
    @test vc[1].value == x.value
    @test vc[2].value == y.value

    c = MOI.add_constraint(
        model,
        MOI.ScalarAffineFunction{T}(
            MOI.ScalarAffineTerm{T}.([one(T), one(T)], [x, y]),
            zero(T),
        ),
        MOI.Interval(-one(T), T(10)),
    )

    MOI.set(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
        MOI.ScalarAffineFunction{T}(
            MOI.ScalarAffineTerm{T}.([one(T), one(T)], [x, y]),
            zero(T),
        ),
    )
    MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)

    if config.solve
        @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED

        MOI.optimize!(model)

        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status
        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT
        @test MOI.get(model, MOI.ObjectiveValue()) ≈ zero(T) atol = atol rtol =
            rtol
        @test MOI.get(model, MOI.ConstraintPrimal(), c) ≈ zero(T) atol = atol rtol =
            rtol

        if config.duals
            @test MOI.get(model, MOI.ResultCount()) >= 1
            @test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT
            @test MOI.get(model, MOI.ConstraintDual(), c) ≈ zero(T) atol = atol rtol =
                rtol
            @test MOI.get(model, MOI.ConstraintDual(), vc[1]) ≈ one(T) atol =
                atol rtol = rtol
            @test MOI.get(model, MOI.ConstraintDual(), vc[2]) ≈ one(T) atol =
                atol rtol = rtol
        end

        if config.basis
            @test (
                MOI.get(model, MOI.ConstraintBasisStatus(), vc[1]) ==
                MOI.NONBASIC
            )
            @test (
                MOI.get(model, MOI.ConstraintBasisStatus(), vc[1]) ==
                MOI.NONBASIC
            )
            @test MOI.get(model, MOI.ConstraintBasisStatus(), c) == MOI.BASIC
        end
    end
end

# changing constraint sense
function linear11test(model::MOI.ModelLike, config::TestConfig{T}) where {T}
    atol = config.atol
    rtol = config.rtol
    # simple 2 variable, 1 constraint problem
    #
    # starts with
    #
    # min x + y
    # st   x + y >= 1
    #      x + y >= 2
    # sol: x+y = 2 (degenerate)
    #
    # with dual
    #
    # max  w + 2z
    # st   w +  z == 1
    #      w +  z == 1
    #      w, z >= 0
    # sol: z = 1, w = 0
    #
    # tranforms problem into:
    #
    # min x + y
    # st   x + y >= 1
    #      x + y <= 2
    # sol: x+y = 1 (degenerate)
    #
    # with dual
    #
    # max  w + 2z
    # st   w +  z == 1
    #      w +  z == 1
    #      w >= 0, z <= 0
    # sol: w = 1, z = 0

    @test MOIU.supports_default_copy_to(model, false) #=copy_names=#
    @test MOI.supports(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
    )
    @test MOI.supports(model, MOI.ObjectiveSense())
    @test MOI.supports_constraint(
        model,
        MOI.ScalarAffineFunction{T},
        MOI.GreaterThan{T},
    )
    @test MOI.supports_constraint(
        model,
        MOI.ScalarAffineFunction{T},
        MOI.LessThan{T},
    )

    MOI.empty!(model)
    @test MOI.is_empty(model)

    v = MOI.add_variables(model, 2)

    c1 = MOI.add_constraint(
        model,
        MOI.ScalarAffineFunction{T}(
            MOI.ScalarAffineTerm{T}.([one(T), one(T)], v),
            zero(T),
        ),
        MOI.GreaterThan(one(T)),
    )
    c2 = MOI.add_constraint(
        model,
        MOI.ScalarAffineFunction{T}(
            MOI.ScalarAffineTerm{T}.([one(T), one(T)], v),
            zero(T),
        ),
        MOI.GreaterThan(T(2)),
    )

    MOI.set(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
        MOI.ScalarAffineFunction{T}(
            MOI.ScalarAffineTerm{T}.([one(T), one(T)], v),
            zero(T),
        ),
    )
    MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)

    if config.solve
        @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED

        MOI.optimize!(model)

        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status
        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT
        @test MOI.get(model, MOI.ObjectiveValue()) ≈ T(2) atol = atol rtol =
            rtol
    end

    c3 = MOI.transform(model, c2, MOI.LessThan(T(2)))

    @test isa(
        c3,
        MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.LessThan{T}},
    )
    @test MOI.is_valid(model, c2) == false
    @test MOI.is_valid(model, c3) == true

    if config.solve
        MOI.optimize!(model)

        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status
        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT
        @test MOI.get(model, MOI.ObjectiveValue()) ≈ one(T) atol = atol rtol =
            rtol
    end
end

# infeasible problem with 2 linear constraints
function linear12test(model::MOI.ModelLike, config::TestConfig{T}) where {T}
    atol = config.atol
    rtol = config.rtol
    # min x
    # s.t. 2x-3y <= -7
    #      y <= 2
    # x,y >= 0

    @test MOIU.supports_default_copy_to(model, false) #=copy_names=#
    @test MOI.supports(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
    )
    @test MOI.supports(model, MOI.ObjectiveSense())
    @test MOI.supports_constraint(
        model,
        MOI.ScalarAffineFunction{T},
        MOI.LessThan{T},
    )
    @test MOI.supports_constraint(model, MOI.SingleVariable, MOI.GreaterThan{T})

    MOI.empty!(model)
    @test MOI.is_empty(model)

    x = MOI.add_variable(model)
    y = MOI.add_variable(model)
    c1 = MOI.add_constraint(
        model,
        MOI.ScalarAffineFunction{T}(
            MOI.ScalarAffineTerm{T}.([T(2), -T(3)], [x, y]),
            zero(T),
        ),
        MOI.LessThan(-T(7)),
    )
    c2 = MOI.add_constraint(
        model,
        MOI.ScalarAffineFunction{T}(
            [MOI.ScalarAffineTerm{T}(one(T), y)],
            zero(T),
        ),
        MOI.LessThan(T(2)),
    )
    bndx = MOI.add_constraint(
        model,
        MOI.SingleVariable(x),
        MOI.GreaterThan(zero(T)),
    )
    @test bndx.value == x.value
    bndy = MOI.add_constraint(
        model,
        MOI.SingleVariable(y),
        MOI.GreaterThan(zero(T)),
    )
    @test bndy.value == y.value

    MOI.set(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
        MOI.ScalarAffineFunction{T}(
            [MOI.ScalarAffineTerm{T}(one(T), x)],
            zero(T),
        ),
    )
    MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)

    if config.solve
        @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED

        MOI.optimize!(model)

        @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE ||
              MOI.get(model, MOI.TerminationStatus()) ==
              MOI.INFEASIBLE_OR_UNBOUNDED
        if config.duals && config.infeas_certificates
            # solver returned an infeasibility ray
            @test MOI.get(model, MOI.ResultCount()) >= 1
            @test MOI.get(model, MOI.DualStatus()) ==
                  MOI.INFEASIBILITY_CERTIFICATE
            cd1 = MOI.get(model, MOI.ConstraintDual(), c1)
            cd2 = MOI.get(model, MOI.ConstraintDual(), c2)
            bndxd = MOI.get(model, MOI.ConstraintDual(), bndx)
            bndyd = MOI.get(model, MOI.ConstraintDual(), bndy)
            @test cd1 < -atol
            @test cd2 < -atol
            @test -3 * cd1 + cd2 ≈ -bndyd atol = atol rtol = rtol
            @test 2 * cd1 ≈ -bndxd atol = atol rtol = rtol
            @test -7 * cd1 + 2 * cd2 > atol
        end
    end
end

# feasibility problem
function linear13test(model::MOI.ModelLike, config::TestConfig{T}) where {T}
    atol = config.atol
    rtol = config.rtol
    # find x, y
    # s.t. 2x + 3y >= 1
    #      x - y == 0

    @test MOIU.supports_default_copy_to(model, false) #=copy_names=#
    @test MOI.supports_constraint(
        model,
        MOI.ScalarAffineFunction{T},
        MOI.EqualTo{T},
    )
    @test MOI.supports_constraint(
        model,
        MOI.ScalarAffineFunction{T},
        MOI.GreaterThan{T},
    )

    MOI.empty!(model)
    @test MOI.is_empty(model)

    x = MOI.add_variable(model)
    y = MOI.add_variable(model)
    c1 = MOI.add_constraint(
        model,
        MOI.ScalarAffineFunction{T}(
            MOI.ScalarAffineTerm{T}.([T(2), T(3)], [x, y]),
            zero(T),
        ),
        MOI.GreaterThan(one(T)),
    )
    c2 = MOI.add_constraint(
        model,
        MOI.ScalarAffineFunction{T}(
            MOI.ScalarAffineTerm{T}.([one(T), -one(T)], [x, y]),
            zero(T),
        ),
        MOI.EqualTo(zero(T)),
    )
    MOI.set(model, MOI.ObjectiveSense(), MOI.FEASIBILITY_SENSE)
    @test MOI.get(model, MOI.ObjectiveSense()) == MOI.FEASIBILITY_SENSE

    if config.solve
        @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED

        MOI.optimize!(model)
        @test MOI.get(model, MOI.ResultCount()) > 0

        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status

        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT

        xsol = MOI.get(model, MOI.VariablePrimal(), x)
        ysol = MOI.get(model, MOI.VariablePrimal(), y)

        c1sol = 2 * xsol + 3 * ysol
        @test c1sol >= 1 || isapprox(c1sol, one(T), atol = atol, rtol = rtol)
        @test xsol - ysol ≈ 0 atol = atol rtol = rtol

        c1primval = MOI.get(model, MOI.ConstraintPrimal(), c1)
        @test c1primval >= 1 ||
              isapprox(c1sol, one(T), atol = atol, rtol = rtol)

        @test MOI.get(model, MOI.ConstraintPrimal(), c2) ≈ 0 atol = atol rtol =
            rtol

        if config.duals
            @test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT
            @test MOI.get(model, MOI.ConstraintDual(), c1) ≈ 0 atol = atol rtol =
                rtol
            @test MOI.get(model, MOI.ConstraintDual(), c2) ≈ 0 atol = atol rtol =
                rtol
        end
    end
end

# Deletion of vector of variables
function linear14test(model::MOI.ModelLike, config::TestConfig{T}) where {T}
    atol = config.atol
    rtol = config.rtol
    # max x + 2y + 3z + 4
    # s.t. 3x + 2y + z <= 2
    #      x, y, z >= 0
    #      z <= 1

    @test MOIU.supports_default_copy_to(model, false) #=copy_names=#
    @test MOI.supports(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
    )
    @test MOI.supports(model, MOI.ObjectiveSense())
    @test MOI.supports_constraint(
        model,
        MOI.ScalarAffineFunction{T},
        MOI.LessThan{T},
    )
    @test MOI.supports_constraint(model, MOI.SingleVariable, MOI.GreaterThan{T})
    @test MOI.supports_constraint(model, MOI.SingleVariable, MOI.LessThan{T})

    MOI.empty!(model)
    @test MOI.is_empty(model)

    x, y, z = MOI.add_variables(model, 3)
    c = MOI.add_constraint(
        model,
        MOI.ScalarAffineFunction{T}(
            MOI.ScalarAffineTerm{T}.([T(3), T(2), one(T)], [x, y, z]),
            zero(T),
        ),
        MOI.LessThan(T(2)),
    )
    clbx = MOI.add_constraint(
        model,
        MOI.SingleVariable(x),
        MOI.GreaterThan(zero(T)),
    )
    @test clbx.value == x.value
    clby = MOI.add_constraint(
        model,
        MOI.SingleVariable(y),
        MOI.GreaterThan(zero(T)),
    )
    @test clby.value == y.value
    clbz = MOI.add_constraint(
        model,
        MOI.SingleVariable(z),
        MOI.GreaterThan(zero(T)),
    )
    @test clbz.value == z.value
    cubz =
        MOI.add_constraint(model, MOI.SingleVariable(z), MOI.LessThan(one(T)))
    @test cubz.value == z.value

    MOI.set(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
        MOI.ScalarAffineFunction{T}(
            MOI.ScalarAffineTerm{T}.([one(T), T(2), T(3)], [x, y, z]),
            T(4),
        ),
    )
    MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)

    if config.solve
        @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED

        MOI.optimize!(model)

        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status

        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT

        @test MOI.get(model, MOI.ObjectiveValue()) ≈ 8 atol = atol rtol = rtol
        if config.dual_objective_value
            @test MOI.get(model, MOI.DualObjectiveValue()) ≈ 8 atol = atol rtol =
                rtol
        end

        @test MOI.get(model, MOI.VariablePrimal(), x) ≈ 0 atol = atol rtol =
            rtol
        @test MOI.get(model, MOI.VariablePrimal(), y) ≈ 1 / T(2) atol = atol rtol =
            rtol
        @test MOI.get(model, MOI.VariablePrimal(), z) ≈ 1 atol = atol rtol =
            rtol

        @test MOI.get(model, MOI.ConstraintPrimal(), c) ≈ 2 atol = atol rtol =
            rtol
        @test MOI.get(model, MOI.ConstraintPrimal(), clbx) ≈ 0 atol = atol rtol =
            rtol
        @test MOI.get(model, MOI.ConstraintPrimal(), clby) ≈ 1 / T(2) atol =
            atol rtol = rtol
        @test MOI.get(model, MOI.ConstraintPrimal(), clbz) ≈ 1 atol = atol rtol =
            rtol
        @test MOI.get(model, MOI.ConstraintPrimal(), cubz) ≈ 1 atol = atol rtol =
            rtol

        if config.duals
            @test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT
            @test MOI.get(model, MOI.ConstraintDual(), c) ≈ -1 atol = atol rtol =
                rtol

            # reduced costs
            @test MOI.get(model, MOI.ConstraintDual(), clbx) ≈ 2 atol = atol rtol =
                rtol
            @test MOI.get(model, MOI.ConstraintDual(), clby) ≈ 0 atol = atol rtol =
                rtol
            @test MOI.get(model, MOI.ConstraintDual(), clbz) ≈ 0 atol = atol rtol =
                rtol
            @test MOI.get(model, MOI.ConstraintDual(), cubz) ≈ -2 atol = atol rtol =
                rtol

            if config.basis
                @test MOI.get(model, MOI.ConstraintBasisStatus(), clbx) ==
                      MOI.NONBASIC
                @test MOI.get(model, MOI.ConstraintBasisStatus(), clby) ==
                      MOI.BASIC
                @test MOI.get(model, MOI.ConstraintBasisStatus(), clbz) ==
                      MOI.BASIC
                @test MOI.get(model, MOI.ConstraintBasisStatus(), cubz) ==
                      MOI.NONBASIC
                @test MOI.get(model, MOI.ConstraintBasisStatus(), c) ==
                      MOI.NONBASIC
            end
        end
    end

    MOI.delete(model, [x, z])

    if config.solve
        MOI.optimize!(model)

        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status

        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT

        @test MOI.get(model, MOI.ObjectiveValue()) ≈ 6 atol = atol rtol = rtol
        if config.dual_objective_value
            @test MOI.get(model, MOI.DualObjectiveValue()) ≈ 6 atol = atol rtol =
                rtol
        end

        @test MOI.get(model, MOI.VariablePrimal(), y) ≈ 1 atol = atol rtol =
            rtol

        @test MOI.get(model, MOI.ConstraintPrimal(), c) ≈ 2 atol = atol rtol =
            rtol
        @test MOI.get(model, MOI.ConstraintPrimal(), clby) ≈ 1 atol = atol rtol =
            rtol

        if config.duals
            @test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT
            @test MOI.get(model, MOI.ConstraintDual(), c) ≈ -1 atol = atol rtol =
                rtol

            # reduced costs
            @test MOI.get(model, MOI.ConstraintDual(), clby) ≈ 0 atol = atol rtol =
                rtol
        end
    end
end

# Empty vector affine function rows (LQOI Issue #48)
function linear15test(model::MOI.ModelLike, config::TestConfig{T}) where {T}
    atol = config.atol
    rtol = config.rtol
    # minimize 0
    # s.t. 0 == 0
    #      x == 1
    @test MOIU.supports_default_copy_to(model, false) #=copy_names=#
    @test MOI.supports(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
    )
    @test MOI.supports(model, MOI.ObjectiveSense())
    @test MOI.supports_constraint(model, MOI.VectorAffineFunction{T}, MOI.Zeros)

    MOI.empty!(model)
    @test MOI.is_empty(model)

    x = MOI.add_variables(model, 1)
    # Create a VectorAffineFunction with two rows, but only
    # one term, belonging to the second row. The first row,
    # which is empty, is essentially a constraint that 0 == 0.
    c = MOI.add_constraint(
        model,
        MOI.VectorAffineFunction{T}(
            MOI.VectorAffineTerm{T}.(2, MOI.ScalarAffineTerm{T}.([one(T)], x)),
            zeros(2),
        ),
        MOI.Zeros(2),
    )

    MOI.set(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),
        MOI.ScalarAffineFunction{T}(
            MOI.ScalarAffineTerm{T}.([zero(T)], x),
            zero(T),
        ),
    )
    MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)

    if config.solve
        @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED

        MOI.optimize!(model)

        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status

        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT

        @test MOI.get(model, MOI.ObjectiveValue()) ≈ 0 atol = atol rtol = rtol
        if config.dual_objective_value
            @test MOI.get(model, MOI.DualObjectiveValue()) ≈ 0 atol = atol rtol =
                rtol
        end

        @test MOI.get(model, MOI.VariablePrimal(), x[1]) ≈ 0 atol = atol rtol =
            rtol

        if config.duals
            @test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT
        end
    end
end

# This test can be passed by solvers that don't support VariablePrimalStart
# because copy_to drops start information with a warning.
function partial_start_test(
    model::MOI.ModelLike,
    config::TestConfig{T},
) where {T}
    atol = config.atol
    rtol = config.rtol
    # maximize 2x + y
    # s.t. x + y <= 1
    #      x, y >= 0
    #      x starts at one(T). Start point for y is unspecified.
    MOI.empty!(model)
    @test MOI.is_empty(model)

    x = MOI.add_variable(model)
    y = MOI.add_variable(model)

    MOI.set(model, MOI.VariablePrimalStart(), x, one(T))

    MOI.add_constraint(model, x, MOI.GreaterThan(zero(T)))
    MOI.add_constraint(model, y, MOI.GreaterThan(zero(T)))
    obj = MOI.ScalarAffineFunction{T}(
        MOI.ScalarAffineTerm{T}.([T(2), one(T)], [x, y]),
        zero(T),
    )
    MOI.set(model, MOI.ObjectiveFunction{typeof(obj)}(), obj)
    MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)
    x_plus_y = MOI.ScalarAffineFunction{T}(
        MOI.ScalarAffineTerm{T}.(one(T), [x, y]),
        zero(T),
    )
    MOI.add_constraint(model, x_plus_y, MOI.LessThan(one(T)))

    if config.solve
        MOI.optimize!(model)
        @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status
        @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT
        @test MOI.get(model, MOI.ObjectiveValue()) ≈ T(2) atol = atol rtol =
            rtol
        @test MOI.get(model, MOI.VariablePrimal(), x) ≈ one(T) atol = atol rtol =
            rtol
        @test MOI.get(model, MOI.VariablePrimal(), y) ≈ zero(T) atol = atol rtol =
            rtol
    end
end

const contlineartests = Dict(
    "linear1" => linear1test,
    "linear2" => linear2test,
    "linear3" => linear3test,
    "linear4" => linear4test,
    "linear5" => linear5test,
    "linear6" => linear6test,
    "linear7" => linear7test,
    "linear8a" => linear8atest,
    "linear8b" => linear8btest,
    "linear8c" => linear8ctest,
    "linear9" => linear9test,
    "linear10" => linear10test,
    "linear10b" => linear10btest,
    "linear11" => linear11test,
    "linear12" => linear12test,
    "linear13" => linear13test,
    "linear14" => linear14test,
    "linear15" => linear15test,
    "partial_start" => partial_start_test,
)

@moitestset contlinear


text:
# export PCCBS
#
# module PCCBS
#
# using CRCBS
# using Parameters, LightGraphs, DataStructures
# using GraphUtils
# using ..PlanningPredicates

################################################################################
############################### ENVIRONMENT DEF ################################
################################################################################
const State = CRCBS.GraphState
# struct State{N} <: CRCBS.GraphState
#     vtx::Int
#     t::Int
#     node::N
# end
const Action = CRCBS.GraphAction

export PCCBSEnv

# @with_kw_noshow struct PCCBSEnv{E,N,I,T,C<:AbstractCostModel,H<:AbstractCostModel} <: GraphEnv{State{N},Action,C}
@with_kw_noshow struct PCCBSEnv{E,N,I,T,C<:AbstractCostModel,H<:AbstractCostModel} <: GraphEnv{State,Action,C}
    search_env::E                   = nothing
    schedule_node::N                = nothing
    node_id::I                      = nothing
    agent_idx::Int                  = -1
    # graph::G                        = get_graph(search_env)
    constraints::T                  = discrete_constraint_table(search_env,agent_idx)
    goal::State                     = State()
    cost_model::C                   = get_cost_model(search_env)
    heuristic::H                    = get_heuristic_model(search_env)
end
CRCBS.get_cost_model(env::PCCBSEnv)       = get_cost_model(env.search_env)
CRCBS.get_heuristic_model(env::PCCBSEnv)  = get_heuristic_model(env.search_env)
GraphUtils.get_node(env::PCCBSEnv)        = env.schedule_node
GraphUtils.get_graph(env::PCCBSEnv)       = get_graph(env.search_env,graph_key(get_node(env))) #graph

function Base.show(io::IO, env::PCCBSEnv)
    print(io,"PCCBSEnv: \n",
        "\t","schedule_node: ",string(get_node(env)),"\n",
        "\t","agent_idx:     ",get_agent_id(env),"\n",
        "\t","goal:          ",string(get_goal(env)),"\n")
end

CRCBS.get_next_state(s::State,a::Action)    = State(get_e(a).dst,get_t(s)+get_dt(a))
CRCBS.get_next_state(env::PCCBSEnv,s,a)  = get_next_state(s,a)
# CRCBS.get_next_state(s::State,a::Action)    = State(get_e(a).dst,get_t(s)+get_dt(a),s.node)
# CRCBS.get_next_state(env::PCCBSEnv,s,a)  = State(get_e(a).dst,get_t(s)+get_dt(a),env.schedule_node)
CRCBS.wait(env::PCCBSEnv,s)              = Action(e=Edge(get_vtx(s),get_vtx(s)))

CRCBS.get_possible_actions(env::PCCBSEnv,s::State) = get_possible_actions(get_node(env),env,s)
function CRCBS.get_possible_actions(node,env::PCCBSEnv,s::State)
    if CRCBS.is_valid(env,s)
        return map(v2->Action(e=Edge(s.vtx,v2)), outneighbors(get_graph(env),s.vtx))
    end
    return Action[]
end
function CRCBS.get_possible_actions(node::Union{COLLECT,DEPOSIT},env::PCCBSEnv,s::State)
    # @assert get_vtx(s) == get_id(get_initial_location_id(node)) "Robot $(get_agent_id(env)) should not be at s=$(string(s)) when task=$(string(node))"
    if !(get_vtx(s) == get_id(get_initial_location_id(node)))
        @log_info(-1,0,"Robot $(get_agent_id(env)) should not be at s=$(string(s)) when task=$(string(node))")
    end
    if CRCBS.is_valid(env,s)
        return [CRCBS.wait(env,s)]
    end
    return Action[]
end
function CRCBS.get_possible_actions(env::MetaAgentCBS.TeamMetaEnv,s::MetaAgentCBS.State{State})
    d_set = Set{Tuple}([(0,0),(-1,0),(0,1),(1,0),(0,-1)])
    for (e,s) in zip(env.envs, s.states)
        intersect!(d_set, CRCBS.get_graph(e).edge_cache[s.vtx])
    end
    meta_actions = Vector{MetaAgentCBS.Action{Action}}()
    for d in d_set
        meta_action = MetaAgentCBS.Action{Action}()
        valid_action = true
        for (e,s) in zip(env.envs, s.states)
            vtx = get_graph(e).vtxs[s.vtx]
            next_vtx = (vtx[1] + d[1], vtx[2] + d[2])
            edge = Edge(get_graph(e).vtx_map[vtx...],get_graph(e).vtx_map[next_vtx...])
            if has_edge(get_graph(e),edge)
                push!(meta_action.actions, Action(e = edge))
            else
                valid_action = false
                break
            end
        end
        if valid_action
            push!(meta_actions, meta_action)
        end
    end
    meta_actions
end

function CRCBS.is_goal(env::PCCBSEnv,s)
    if get_t(s) >= get_t(get_goal(env))
        if states_match(s, get_goal(env))
            #########################################
            # updated goal time
            v = get_vtx(get_schedule(env.search_env),env.node_id)
            path_spec = get_path_spec(get_schedule(env.search_env), v)
            # goal_time = get_tF(env.search_env,v) # time after which goal can be satisfied
            goal_time = get_t(get_goal(env))
            if path_spec.tight == true
                goal_time = get_tF(env.search_env,v) + minimum(get_local_slack(env.search_env,v))
                # goal_time += minimum(get_local_slack(env.search_env,v))
            end
            if (path_spec.free == true) && is_terminal_node(get_graph(get_schedule(env.search_env)),v)
                goal_time = maximum(get_cache(env.search_env).tF)
            end
            # check that all predecessor nodes have been completed.
            if get_t(s) < goal_time
                return false
            end
            #########################################
            return true
        elseif !CRCBS.is_valid(get_goal(env))
            return true
        end
    end
    return false
end

# end # end module PCCBS

ext: jl


text:
###########
# Library #
###########
# using Random
using MatrixLMnet
using Helium
using Test

#####################################################################
# TEST Cross Validation Lasso vs Elastic Net (𝛼=1) - Simulated Data #
#####################################################################

#=
Description:
-----------

Model: 𝐘 = 𝐗 𝛃 𝐙' + 𝜎𝜖, with 𝜖∼𝑁(0,1)

Simulate data set consisting  of 20/20/200 observations and 8 predictors.
We let 𝛽₁ = (3, 1.5, 0, 0, 2, 0, 0, 0), 𝛽₂ = (0, 1.5, 0, 3.5, 2, 0, 0 , 2) where
𝛃 = [𝛽₁, 𝛽₂] and 𝜎 = 3.
The pairwise correlation between 𝑋ᵢ and 𝑋ⱼ was set to be 𝑐𝑜𝑟(𝑖,𝑗)=(0.5)^|𝑖−𝑗|.
Here, the Z matrix is an identity matrix.
=#

# Data testing directory name
dataDir = realpath(joinpath(@__DIR__,"data"))

# Get predictors
X = Helium.readhe(joinpath(dataDir, "Xmat.he"))

# Get response
Y = Helium.readhe(joinpath(dataDir, "Ymat.he"))

# Get Z matrix
Z = Helium.readhe(joinpath(dataDir, "Zmat.he"))

# Build raw data object from MatrixLM.jl
dat = RawData(Response(Y), Predictors(X, Z));

# Hyper parameters
λ = [10.0, 5.0, 3.0]
α = [1.0, 0.5]

rng = 2021#MatrixLMnet.Random.MersenneTwister(2021)

numVersion = VERSION
if Int(numVersion.minor) < 7
      tolVersion=2e-1
else
      tolVersion=1e-6
end

##########################################
# TEST 1 Summary cross-validation - ista #
##########################################

# Elastic net penalized regression
MatrixLMnet.Random.seed!(rng)
est1 = MatrixLMnet.mlmnet_cv(dat, λ, α, 10, 1, method = "ista", hasZIntercept = false, hasXIntercept = false, isVerbose = false);
# Summaries
smmr_Net1 = MatrixLMnet.mlmnet_cv_summary(est1);
smmr_min_Net1 = MatrixLMnet.lambda_min(est1);

idxSmmr = argmin(smmr_Net1[:, :AvgMSE])
test_ElasticNet = (smmr_Net1[idxSmmr, :AvgMSE] == smmr_min_Net1[1, :AvgMSE]) &&
        (smmr_Net1[idxSmmr, :Lambda] == smmr_min_Net1[1, :Lambda]) &&
        (smmr_Net1[idxSmmr, :Alpha] == smmr_min_Net1[1, :Alpha])

# Elastic net penalized regression
MatrixLMnet.Random.seed!(rng)
est2 = MatrixLMnet.mlmnet_cv(dat, λ, 10, 1, method = "ista",  hasZIntercept = false, hasXIntercept = false, isVerbose = false);
# Summaries
smmr_Net2 = MatrixLMnet.mlmnet_cv_summary(est2);
smmr_min_Net2 = MatrixLMnet.lambda_min(est2);

idxSmmr = argmin(smmr_Net2[:, :AvgMSE])
test_Lasso = (smmr_Net2[idxSmmr, :AvgMSE] == smmr_min_Net2[1, :AvgMSE]) &&
        (smmr_Net2[idxSmmr, :Lambda] == smmr_min_Net2[1, :Lambda]) &&
        (smmr_Net2[idxSmmr, :Alpha] == smmr_min_Net2[1, :Alpha])

println("Summary cross-validation test 1 - ista: ",
         @test (test_ElasticNet && test_Lasso))

###########################################
# TEST 2 Summary cross-validation - fista #
###########################################

# Elastic net penalized regression
MatrixLMnet.Random.seed!(rng)
est1 = MatrixLMnet.mlmnet_cv(dat, λ, α, 10, 1, method = "fista", hasZIntercept = false, hasXIntercept = false, isVerbose = false);
# Summaries
smmr_Net1 = MatrixLMnet.mlmnet_cv_summary(est1);
smmr_min_Net1 = MatrixLMnet.lambda_min(est1);

idxSmmr = argmin(smmr_Net1[:, :AvgMSE])
test_ElasticNet = (smmr_Net1[idxSmmr, :AvgMSE] == smmr_min_Net1[1, :AvgMSE]) &&
        (smmr_Net1[idxSmmr, :Lambda] == smmr_min_Net1[1, :Lambda]) &&
        (smmr_Net1[idxSmmr, :Alpha] == smmr_min_Net1[1, :Alpha])

# Elastic net penalized regression
MatrixLMnet.Random.seed!(rng)
est2 = MatrixLMnet.mlmnet_cv(dat, λ, 10, 1, method = "fista",  hasZIntercept = false, hasXIntercept = false, isVerbose = false);
# Summaries
smmr_Net2 = MatrixLMnet.mlmnet_cv_summary(est2);
smmr_min_Net2 = MatrixLMnet.lambda_min(est2);

idxSmmr = argmin(smmr_Net2[:, :AvgMSE])
test_Lasso = (smmr_Net2[idxSmmr, :AvgMSE] == smmr_min_Net2[1, :AvgMSE]) &&
        (smmr_Net2[idxSmmr, :Lambda] == smmr_min_Net2[1, :Lambda]) &&
        (smmr_Net2[idxSmmr, :Alpha] == smmr_min_Net2[1, :Alpha])

println("Summary cross-validation test 2 - fista: ",
         @test (test_ElasticNet && test_Lasso))

########################################################
# TEST 3 Summary cross-validation - fista backtracking #
########################################################

# Elastic net penalized regression
MatrixLMnet.Random.seed!(rng)
est1 = MatrixLMnet.mlmnet_cv(dat, λ, α, 10, 1, method = "fista_bt", hasZIntercept = false, hasXIntercept = false, isVerbose = false);
# Summaries
smmr_Net1 = MatrixLMnet.mlmnet_cv_summary(est1);
smmr_min_Net1 = MatrixLMnet.lambda_min(est1);

idxSmmr = argmin(smmr_Net1[:, :AvgMSE])
test_ElasticNet = (smmr_Net1[idxSmmr, :AvgMSE] == smmr_min_Net1[1, :AvgMSE]) &&
        (smmr_Net1[idxSmmr, :Lambda] == smmr_min_Net1[1, :Lambda]) &&
        (smmr_Net1[idxSmmr, :Alpha] == smmr_min_Net1[1, :Alpha])

# Elastic net penalized regression
MatrixLMnet.Random.seed!(rng)
est2 = MatrixLMnet.mlmnet_cv(dat, λ, 10, 1, method = "fista_bt",  hasZIntercept = false, hasXIntercept = false, isVerbose = false);
# Summaries
smmr_Net2 = MatrixLMnet.mlmnet_cv_summary(est2);
smmr_min_Net2 = MatrixLMnet.lambda_min(est2);

idxSmmr = argmin(smmr_Net2[:, :AvgMSE])
test_Lasso = (smmr_Net2[idxSmmr, :AvgMSE] == smmr_min_Net2[1, :AvgMSE]) &&
        (smmr_Net2[idxSmmr, :Lambda] == smmr_min_Net2[1, :Lambda]) &&
        (smmr_Net2[idxSmmr, :Alpha] == smmr_min_Net2[1, :Alpha])

println("Summary cross-validation test 3 - fista_bt: ",
         @test (test_ElasticNet && test_Lasso))

##########################################
# TEST 4 Summary cross-validation - admm #
##########################################

# Elastic net penalized regression
MatrixLMnet.Random.seed!(rng)
est1 = MatrixLMnet.mlmnet_cv(dat, λ, α, 10, 1, method = "admm", hasZIntercept = false, hasXIntercept = false, isVerbose = false);
# Summaries
smmr_Net1 = MatrixLMnet.mlmnet_cv_summary(est1);
smmr_min_Net1 = MatrixLMnet.lambda_min(est1);

idxSmmr = argmin(smmr_Net1[:, :AvgMSE])
test_ElasticNet = (smmr_Net1[idxSmmr, :AvgMSE] == smmr_min_Net1[1, :AvgMSE]) &&
        (smmr_Net1[idxSmmr, :Lambda] == smmr_min_Net1[1, :Lambda]) &&
        (smmr_Net1[idxSmmr, :Alpha] == smmr_min_Net1[1, :Alpha])

# Elastic net penalized regression
MatrixLMnet.Random.seed!(rng)
est2 = MatrixLMnet.mlmnet_cv(dat, λ, 10, 1, method = "admm",  hasZIntercept = false, hasXIntercept = false, isVerbose = false);
# Summaries
smmr_Net2 = MatrixLMnet.mlmnet_cv_summary(est2);
smmr_min_Net2 = MatrixLMnet.lambda_min(est2);

idxSmmr = argmin(smmr_Net2[:, :AvgMSE])
test_Lasso = (smmr_Net2[idxSmmr, :AvgMSE] == smmr_min_Net2[1, :AvgMSE]) &&
        (smmr_Net2[idxSmmr, :Lambda] == smmr_min_Net2[1, :Lambda]) &&
        (smmr_Net2[idxSmmr, :Alpha] == smmr_min_Net2[1, :Alpha])

println("Summary cross-validation test 4 - admm: ",
         @test (test_ElasticNet && test_Lasso))

########################################
# TEST 5 Summary cross-validation - cd #
########################################

# Elastic net penalized regression
MatrixLMnet.Random.seed!(rng)
est2 = MatrixLMnet.mlmnet_cv(dat, λ, 10, 1, method = "admm",  hasZIntercept = false, hasXIntercept = false, isVerbose = false);
# Summaries
smmr_Net2 = MatrixLMnet.mlmnet_cv_summary(est2);
smmr_min_Net2 = MatrixLMnet.lambda_min(est2);

idxSmmr = argmin(smmr_Net2[:, :AvgMSE])
test_Lasso = (smmr_Net2[idxSmmr, :AvgMSE] == smmr_min_Net2[1, :AvgMSE]) &&
        (smmr_Net2[idxSmmr, :Lambda] == smmr_min_Net2[1, :Lambda]) &&
        (smmr_Net2[idxSmmr, :Alpha] == smmr_min_Net2[1, :Alpha])

println("Summary cross-validation test 5 - admm: ",
         @test (test_Lasso))


text:
module semiclassical

import Base: ==
import ..bases, ..operators, ..operators_dense
import ..timeevolution: integrate, recast!

using ..bases, ..states, ..operators, ..operators_dense, ..timeevolution

const QuantumState{B} = Union{Ket{B}, DenseOperator{B,B}}
const DecayRates = Union{Nothing, Vector{Float64}, Matrix{Float64}}

"""
Semi-classical state.

It consists of a quantum part, which is either a `Ket` or a `DenseOperator` and
a classical part that is specified as a complex vector of arbitrary length.
"""
mutable struct State{B<:Basis,T<:QuantumState{B},C<:Vector{ComplexF64}}
    quantum::T
    classical::C
    function State(quantum::T, classical::C) where {B<:Basis,T<:QuantumState{B},C<:Vector{ComplexF64}}
        new{B,T,C}(quantum, classical)
    end
end

Base.length(state::State) = length(state.quantum) + length(state.classical)
Base.copy(state::State) = State(copy(state.quantum), copy(state.classical))

function ==(a::State, b::State)
    samebases(a.quantum, b.quantum) &&
    length(a.classical)==length(b.classical) &&
    (a.classical==b.classical) &&
    (a.quantum==b.quantum)
end

operators.expect(op, state::State) = expect(op, state.quantum)
operators.variance(op, state::State) = variance(op, state.quantum)
operators.ptrace(state::State, indices::Vector{Int}) = State(ptrace(state.quantum, indices), state.classical)

operators_dense.dm(x::State{B,T}) where {B<:Basis,T<:Ket{B}} = State(dm(x.quantum), x.classical)

"""
    semiclassical.schroedinger_dynamic(tspan, state0, fquantum, fclassical[; fout, ...])

Integrate time-dependent Schrödinger equation coupled to a classical system.

# Arguments
* `tspan`: Vector specifying the points of time for which the output should
        be displayed.
* `psi0`: Initial semi-classical state [`semiclassical.State`](@ref).
* `fquantum`: Function `f(t, psi, u) -> H` returning the time and or state
        dependent Hamiltonian.
* `fclassical`: Function `f(t, psi, u, du)` calculating the possibly time and
        state dependent derivative of the classical equations and storing it
        in the vector `du`.
* `fout=nothing`: If given, this function `fout(t, state)` is called every time
        an output should be displayed. ATTENTION: The given state is neither
        normalized nor permanent!
* `kwargs...`: Further arguments are passed on to the ode solver.
"""
function schroedinger_dynamic(tspan, state0::S, fquantum::Function, fclassical::Function;
                fout::Union{Function,Nothing}=nothing,
                kwargs...) where {B<:Basis,T<:Ket{B},S<:State{B,T}}
    tspan_ = convert(Vector{Float64}, tspan)
    dschroedinger_(t::Float64, state::S, dstate::S) = dschroedinger_dynamic(t, state, fquantum, fclassical, dstate)
    x0 = Vector{ComplexF64}(undef, length(state0))
    recast!(state0, x0)
    state = copy(state0)
    dstate = copy(state0)
    integrate(tspan_, dschroedinger_, x0, state, dstate, fout; kwargs...)
end

"""
    semiclassical.master_dynamic(tspan, state0, fquantum, fclassical; <keyword arguments>)

Integrate time-dependent master equation coupled to a classical system.

# Arguments
* `tspan`: Vector specifying the points of time for which output should
        be displayed.
* `rho0`: Initial semi-classical state [`semiclassical.State`](@ref).
* `fquantum`: Function `f(t, rho, u) -> (H, J, Jdagger)` returning the time
        and/or state dependent Hamiltonian and Jump operators.
* `fclassical`: Function `f(t, rho, u, du)` calculating the possibly time and
        state dependent derivative of the classical equations and storing it
        in the complex vector `du`.
* `fout=nothing`: If given, this function `fout(t, state)` is called every time
        an output should be displayed. ATTENTION: The given state is not
        permanent!
* `kwargs...`: Further arguments are passed on to the ode solver.
"""
function master_dynamic(tspan, state0::State{B,T}, fquantum, fclassical;
                rates::DecayRates=nothing,
                fout::Union{Function,Nothing}=nothing,
                tmp::T=copy(state0.quantum),
                kwargs...) where {B<:Basis,T<:DenseOperator{B,B}}
    tspan_ = convert(Vector{Float64}, tspan)
    function dmaster_(t::Float64, state::S, dstate::S) where {B<:Basis,T<:DenseOperator{B,B},S<:State{B,T}}
        dmaster_h_dynamic(t, state, fquantum, fclassical, rates, dstate, tmp)
    end
    x0 = Vector{ComplexF64}(undef, length(state0))
    recast!(state0, x0)
    state = copy(state0)
    dstate = copy(state0)
    integrate(tspan_, dmaster_, x0, state, dstate, fout; kwargs...)
end

function master_dynamic(tspan, state0::State{B,T}, fquantum, fclassical; kwargs...) where {B<:Basis,T<:Ket{B}}
    master_dynamic(tspan, dm(state0), fquantum, fclassical; kwargs...)
end

function recast!(state::State{B,T,C}, x::C) where {B<:Basis,T<:QuantumState{B},C<:Vector{ComplexF64}}
    N = length(state.quantum)
    copyto!(x, 1, state.quantum.data, 1, N)
    copyto!(x, N+1, state.classical, 1, length(state.classical))
    x
end

function recast!(x::C, state::State{B,T,C}) where {B<:Basis,T<:QuantumState{B},C<:Vector{ComplexF64}}
    N = length(state.quantum)
    copyto!(state.quantum.data, 1, x, 1, N)
    copyto!(state.classical, 1, x, N+1, length(state.classical))
end

function dschroedinger_dynamic(t::Float64, state::State{B,T}, fquantum::Function,
            fclassical::Function, dstate::State{B,T}) where {B<:Basis,T<:Ket{B}}
    fquantum_(t, psi) = fquantum(t, state.quantum, state.classical)
    timeevolution.timeevolution_schroedinger.dschroedinger_dynamic(t, state.quantum, fquantum_, dstate.quantum)
    fclassical(t, state.quantum, state.classical, dstate.classical)
end

function dmaster_h_dynamic(t::Float64, state::State{B,T}, fquantum::Function,
            fclassical::Function, rates::DecayRates, dstate::State{B,T}, tmp::T) where {B<:Basis,T<:DenseOperator{B,B}}
    fquantum_(t, rho) = fquantum(t, state.quantum, state.classical)
    timeevolution.timeevolution_master.dmaster_h_dynamic(t, state.quantum, fquantum_, rates, dstate.quantum, tmp)
    fclassical(t, state.quantum, state.classical, dstate.classical)
end

end # module