#  Copyright 2017, Iain Dunning, Joey Huchette, Miles Lubin, and contributors
#  This Source Code Form is subject to the terms of the Mozilla Public
#  License, v. 2.0. If a copy of the MPL was not distributed with this
#  file, You can obtain one at https://mozilla.org/MPL/2.0/.
#############################################################################
# JuMP
# An algebraic modeling language for Julia
# See https://github.com/jump-dev/JuMP.jl
#############################################################################

"""
    JuMP

JuMP is an algebraic modeling language for Julia.

## Documentation

The documentation for JuMP is hosted at https://jump.dev/JuMP.jl

There is also a copy of the documentation in the `/docs/src` directory of the
source code. To find the directory on your local machine, run
```julia
julia> using JuMP; pkgdir(JuMP, "docs", "src")
```

## Getting help

If you need help, please ask a question on the JuMP community forum at
https://jump.dev/forum.

If you have a reproducible example of a bug in JuMP, please open a GitHub issue
by going to https://github.com/jump-dev/JuMP.jl/issues/new.

## More information

For more information on JuMP, go to https://jump.dev.
"""
module JuMP

import LinearAlgebra
import MacroTools
import MathOptInterface as MOI
import MutableArithmetics as _MA
import OrderedCollections
import OrderedCollections: OrderedDict
import Printf
import SparseArrays

"""
    MOIU

Shorthand for the MathOptInterface.Utilities package.
"""
const MOIU = MOI.Utilities

# TODO(odow): remove this constant
const MOIB = MOI.Bridges

# Exports are at the end of the file.

# These imports must come before the definition of `Model`:
include("shapes.jl")

"""
    ModelMode

An enum to describe the state of the CachingOptimizer inside a JuMP model.

See also: [`mode`](@ref).

## Values

Possible values are:

 * [`AUTOMATIC`]: `moi_backend` field holds a CachingOptimizer in AUTOMATIC mode.
 * [`MANUAL`]: `moi_backend` field holds a CachingOptimizer in MANUAL mode.
 * [`DIRECT`]: `moi_backend` field holds an AbstractOptimizer. No extra copy of
   the model is stored. The `moi_backend` must support `add_constraint` etc.
"""
@enum(ModelMode, AUTOMATIC, MANUAL, DIRECT)

@doc(
    "`moi_backend` field holds a CachingOptimizer in AUTOMATIC mode.",
    AUTOMATIC
)

@doc("`moi_backend` field holds a CachingOptimizer in MANUAL mode.", MANUAL)

@doc(
    "`moi_backend` field holds an AbstractOptimizer. No extra copy of the " *
    "model is stored. The `moi_backend` must support `add_constraint` etc.",
    DIRECT,
)

"""
    AbstractModel

An abstract type that should be subtyped for users creating JuMP extensions.
"""
abstract type AbstractModel end

"""
    value_type(::Type{<:Union{AbstractModel,AbstractVariableRef}})

Return the return type of [`value`](@ref) for variables of that model. It
defaults to `Float64` if it is not implemented.

## Example

```jldoctest
julia> value_type(GenericModel{BigFloat})
BigFloat
```
"""
value_type(::Type{<:AbstractModel}) = Float64

value_type(::Type{T}) where {T<:Number} = T

function value_type(::Type{T}) where {T}
    return error(
        """
        Unable to compute `value_type($T)`.

        If you are developing a JuMP extension, define a new method for \
        `JuMP.value_type(::Type{$T})` that returns the numeric type used in your model.
        """,
    )
end

mutable struct GenericModel{T<:Real} <: AbstractModel
    # In MANUAL and AUTOMATIC modes, CachingOptimizer.
    # In DIRECT mode, will hold an AbstractOptimizer.
    moi_backend::MOI.ModelLike
    # List of shapes of constraints that are not `ScalarShape` or `VectorShape`.
    shapes::Dict{MOI.ConstraintIndex,AbstractShape}
    # List of bridges to add in addition to the ones added in
    # `MOI.Bridges.full_bridge_optimizer`. With `BridgeableConstraint`, the
    # same bridge may be added many times so we store them in a `Set` instead
    # of, for example, a `Vector`.
    bridge_types::Set{Any}
    # Hook into a solve call...function of the form f(m::GenericModel; kwargs...),
    # where kwargs get passed along to subsequent solve calls.
    optimize_hook::Any
    # TODO: Document.
    nlp_model::Union{Nothing,MOI.Nonlinear.Model}
    # Dictionary from variable and constraint names to objects.
    obj_dict::Dict{Symbol,Any}
    # Number of times we add large expressions. Incremented and checked by
    # the `operator_warn` method.
    operator_counter::Int
    # A flag to track whether we have modified the model after calling
    # optimize!.
    is_model_dirty::Bool
    # Enable extensions to attach arbitrary information to a JuMP model by
    # using an extension-specific symbol as a key.
    ext::Dict{Symbol,Any}
    # A model-level option that is used as the default for the set_string_name
    # keyword to @variable and @constraint.
    set_string_names_on_creation::Bool
    #
    variable_in_set_ref::Dict{Any,MOI.ConstraintIndex}
    # A dictionary to store timing information from the JuMP macros.
    enable_macro_timing::Bool
    macro_times::Dict{Tuple{LineNumberNode,String},Float64}
    # A cache to track common subexpressions based on their `objectid`.
    subexpressions::Dict{UInt64,MOI.ScalarNonlinearFunction}
end

value_type(::Type{GenericModel{T}}) where {T} = T

function Base.getproperty(model::GenericModel, name::Symbol)
    if name == :nlp_data
        error(
            """
            The internal field `.nlp_data` was removed from `Model` in JuMP v1.2.0.

            If you encountered this message without accessing `model.nlp_data` directly, \
            it means you are using a package that is incompatible with your installed \
            version of JuMP. As a temporary fix, install a compatible version with \
            `import Pkg; Pkg.pkg"add JuMP@1.1"`, then restart Julia for the changes to \
            take effect. You should also open a GitHub issue for the package you are \
            using so that it can be fixed for future users.
            """,
        )
    end
    return getfield(model, name)
end

"""
    GenericModel{T}([optimizer_factory]; kwargs...) where {T<:Real}

Create a new instance of a JuMP model.

If `optimizer_factory` is provided, the model is initialized with the optimizer
using `set_optimizer(model, optimizer_factory; kwargs...)`. See
[`set_optimizer`](@ref) for details on `kwargs`.

If `optimizer_factory` is not provided, use [`set_optimizer`](@ref) to set the
optimizer before calling [`optimize!`](@ref).

## Value type `T`

Passing a type other than `Float64` as the value type `T` is an advanced
operation. The value type must match that expected by the chosen optimizer.
Consult the optimizers documentation for details.

If not documented, assume that the optimizer supports only `Float64`.

Choosing an unsupported value type will throw an [`MOI.UnsupportedConstraint`](@ref)
or an [`MOI.UnsupportedAttribute`](@ref) error, the timing of which (during the
model construction or during a call to [`optimize!`](@ref)) depends on how the
solver is interfaced to JuMP.

## Example

```jldoctest
julia> model = GenericModel{BigFloat}();

julia> typeof(model)
GenericModel{BigFloat}
```
"""
function GenericModel{T}(
    @nospecialize(optimizer_factory = nothing);
    kwargs...,
) where {T<:Real}
    inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{T}())
    cache = MOI.Utilities.CachingOptimizer(inner, MOI.Utilities.AUTOMATIC)
    model = direct_generic_model(T, cache)
    if optimizer_factory !== nothing
        set_optimizer(model, optimizer_factory; kwargs...)
    end
    return model
end

"""
    direct_generic_model(
        value_type::Type{T},
        backend::MOI.ModelLike;
    ) where {T<:Real}

Return a new JuMP model using [`backend`](@ref) to store the model and solve it.

As opposed to the [`Model`](@ref) constructor, no cache of the model is
stored outside of [`backend`](@ref) and no bridges are automatically applied to
[`backend`](@ref).

## Notes

The absence of a cache reduces the memory footprint but, it is important to bear
in mind the following implications of creating models using this *direct* mode:

* When [`backend`](@ref) does not support an operation, such as modifying
  constraints or adding variables/constraints after solving, an error is
  thrown. For models created using the [`Model`](@ref) constructor, such
  situations can be dealt with by storing the modifications in a cache and
  loading them into the optimizer when `optimize!` is called.
* No constraint bridging is supported by default.
* The optimizer used cannot be changed the model is constructed.
* The model created cannot be copied.
"""
function direct_generic_model(
    value_type::Type{T},
    backend::MOI.ModelLike;
) where {T<:Real}
    @assert MOI.is_empty(backend)
    return GenericModel{T}(
        backend,
        Dict{MOI.ConstraintIndex,AbstractShape}(),
        Set{Any}(),
        nothing,
        nothing,
        Dict{Symbol,Any}(),
        0,
        false,
        Dict{Symbol,Any}(),
        true,
        Dict{Any,MOI.ConstraintIndex}(),
        false,
        Dict{Tuple{LineNumberNode,String},Float64}(),
        Dict{UInt64,MOI.ScalarNonlinearFunction}(),
    )
end

"""
    direct_generic_model(::Type{T}, factory::MOI.OptimizerWithAttributes)

Create a [`direct_generic_model`](@ref) using `factory`, a
`MOI.OptimizerWithAttributes` object created by [`optimizer_with_attributes`](@ref).

## Example

```jldoctest
julia> import HiGHS

julia> optimizer = optimizer_with_attributes(
           HiGHS.Optimizer,
           "presolve" => "off",
           MOI.Silent() => true,
       );

julia> model = direct_generic_model(Float64, optimizer)
A JuMP Model
├ mode: DIRECT
├ solver: HiGHS
├ objective_sense: FEASIBILITY_SENSE
├ num_variables: 0
├ num_constraints: 0
└ Names registered in the model: none
```
is equivalent to:
```jldoctest
julia> import HiGHS

julia> model = direct_generic_model(Float64, HiGHS.Optimizer())
A JuMP Model
├ mode: DIRECT
├ solver: HiGHS
├ objective_sense: FEASIBILITY_SENSE
├ num_variables: 0
├ num_constraints: 0
└ Names registered in the model: none

julia> set_attribute(model, "presolve", "off")

julia> set_attribute(model, MOI.Silent(), true)
```
"""
function direct_generic_model(
    ::Type{T},
    factory::MOI.OptimizerWithAttributes,
) where {T}
    return direct_generic_model(T, MOI.instantiate(factory))
end

"""
    Model([optimizer_factory;] add_bridges::Bool = true)

Create a new instance of a JuMP model.

If `optimizer_factory` is provided, the model is initialized with thhe optimizer
returned by `MOI.instantiate(optimizer_factory)`.

If `optimizer_factory` is not provided, use [`set_optimizer`](@ref) to set the
optimizer before calling [`optimize!`](@ref).

If `add_bridges`, JuMP adds a [`MOI.Bridges.LazyBridgeOptimizer`](@ref) to
automatically reformulate the problem into a form supported by the optimizer.

## Example

```jldoctest
julia> import Ipopt

julia> model = Model(Ipopt.Optimizer);

julia> solver_name(model)
"Ipopt"

julia> import HiGHS

julia> import MultiObjectiveAlgorithms as MOA

julia> model = Model(() -> MOA.Optimizer(HiGHS.Optimizer); add_bridges = false);
```
"""
const Model = GenericModel{Float64}

"""
    direct_model(backend::MOI.ModelLike)

Return a new JuMP model using [`backend`](@ref) to store the model and solve it.

As opposed to the [`Model`](@ref) constructor, no cache of the model is
stored outside of [`backend`](@ref) and no bridges are automatically applied to
[`backend`](@ref).

## Notes

The absence of a cache reduces the memory footprint but, it is important to bear
in mind the following implications of creating models using this *direct* mode:

* When [`backend`](@ref) does not support an operation, such as modifying
  constraints or adding variables/constraints after solving, an error is
  thrown. For models created using the [`Model`](@ref) constructor, such
  situations can be dealt with by storing the modifications in a cache and
  loading them into the optimizer when `optimize!` is called.
* No constraint bridging is supported by default.
* The optimizer used cannot be changed the model is constructed.
* The model created cannot be copied.
"""
direct_model(backend::MOI.ModelLike) = direct_generic_model(Float64, backend)

"""
    direct_model(factory::MOI.OptimizerWithAttributes)

Create a [`direct_model`](@ref) using `factory`, a `MOI.OptimizerWithAttributes`
object created by [`optimizer_with_attributes`](@ref).

## Example

```jldoctest
julia> import HiGHS

julia> optimizer = optimizer_with_attributes(
           HiGHS.Optimizer,
           "presolve" => "off",
           MOI.Silent() => true,
       );

julia> model = direct_model(optimizer)
A JuMP Model
├ mode: DIRECT
├ solver: HiGHS
├ objective_sense: FEASIBILITY_SENSE
├ num_variables: 0
├ num_constraints: 0
└ Names registered in the model: none
```
is equivalent to:
```jldoctest
julia> import HiGHS

julia> model = direct_model(HiGHS.Optimizer())
A JuMP Model
├ mode: DIRECT
├ solver: HiGHS
├ objective_sense: FEASIBILITY_SENSE
├ num_variables: 0
├ num_constraints: 0
└ Names registered in the model: none

julia> set_attribute(model, "presolve", "off")

julia> set_attribute(model, MOI.Silent(), true)
```
"""
function direct_model(factory::MOI.OptimizerWithAttributes)
    optimizer = MOI.instantiate(factory)
    return direct_model(optimizer)
end

Base.broadcastable(model::GenericModel) = Ref(model)

"""
    backend(model::GenericModel)

Return the lower-level MathOptInterface model that sits underneath JuMP. This
model depends on which operating mode JuMP is in (see [`mode`](@ref)).

 * If JuMP is in `DIRECT` mode (that is, the model was created using
   [`direct_model`](@ref)), the backend will be the optimizer passed to
   [`direct_model`](@ref).
 * If JuMP is in `MANUAL` or `AUTOMATIC` mode, the backend is a
   `MOI.Utilities.CachingOptimizer`.

Use [`index`](@ref) to get the index of a variable or constraint in the backend
model.

!!! warning
    This function should only be used by advanced users looking to access
    low-level MathOptInterface or solver-specific functionality.

## Notes

If JuMP is not in `DIRECT` mode, the type returned by `backend` may change
between any JuMP releases. Therefore, only use the public API exposed by
MathOptInterface, and do not access internal fields. If you require access to
the innermost optimizer, see [`unsafe_backend`](@ref). Alternatively, use
[`direct_model`](@ref) to create a JuMP model in `DIRECT` mode.

See also: [`unsafe_backend`](@ref).

## Example

```jldoctest
julia> import HiGHS

julia> model = direct_model(HiGHS.Optimizer());

julia> set_silent(model)

julia> @variable(model, x >= 0)
x

julia> highs = backend(model)
A HiGHS model with 1 columns and 0 rows.

julia> index(x)
MOI.VariableIndex(1)
```
"""
backend(model::GenericModel) = model.moi_backend

"""
    unsafe_backend(model::GenericModel)

Return the innermost optimizer associated with the JuMP model `model`.

**This function should only be used by advanced users looking to access
low-level solver-specific functionality. It has a high-risk of incorrect usage.
We strongly suggest you use the alternative suggested below.**

See also: [`backend`](@ref).

To obtain the index of a variable or constraint in the unsafe backend, use
[`optimizer_index`](@ref).

## Unsafe behavior

This function is unsafe for two main reasons.

First, the formulation and order of variables and constraints in the unsafe
backend may be different to the variables and constraints in `model`. This
can happen because of bridges, or because the solver requires the variables or
constraints in a specific order. In addition, the variable or constraint index
returned by [`index`](@ref) at the JuMP level may be different to the index of
the corresponding variable or constraint in the `unsafe_backend`. There is no
solution to this. Use the alternative suggested below instead.

Second, the `unsafe_backend` may be empty, or lack some modifications made to
the JuMP model. Thus, before calling `unsafe_backend` you should first call
[`MOI.Utilities.attach_optimizer`](@ref) to ensure that the backend is
synchronized with the JuMP model.

```jldoctest
julia> import HiGHS

julia> model = Model(HiGHS.Optimizer)
A JuMP Model
├ solver: HiGHS
├ objective_sense: FEASIBILITY_SENSE
├ num_variables: 0
├ num_constraints: 0
└ Names registered in the model: none

julia> MOI.Utilities.attach_optimizer(model)

julia> inner = unsafe_backend(model)
A HiGHS model with 0 columns and 0 rows.
```

Moreover, if you modify the JuMP model, the reference you have to the backend
(that is, `inner` in the example above) may be out-dated, and you should call
[`MOI.Utilities.attach_optimizer`](@ref) again.

This function is also unsafe in the reverse direction: if you modify the unsafe
backend, for example, by adding a new constraint to `inner`, the changes may be
silently discarded by JuMP when the JuMP `model` is modified or solved.

## Alternative

Instead of `unsafe_backend`, create a model using [`direct_model`](@ref) and
call [`backend`](@ref) instead.

For example, instead of:

```jldoctest
julia> import HiGHS

julia> model = Model(HiGHS.Optimizer);

julia> set_silent(model)

julia> @variable(model, x >= 0)
x

julia> MOI.Utilities.attach_optimizer(model)

julia> highs = unsafe_backend(model)
A HiGHS model with 1 columns and 0 rows.

julia> optimizer_index(x)
MOI.VariableIndex(1)
```

Use:

```jldoctest
julia> import HiGHS

julia> model = direct_model(HiGHS.Optimizer());

julia> set_silent(model)

julia> @variable(model, x >= 0)
x

julia> highs = backend(model)  # No need to call `attach_optimizer`.
A HiGHS model with 1 columns and 0 rows.

julia> index(x)
MOI.VariableIndex(1)
```
"""
unsafe_backend(model::GenericModel) = unsafe_backend(backend(model))

function unsafe_backend(model::MOIU.CachingOptimizer)
    if MOIU.state(model) == MOIU.NO_OPTIMIZER
        error(
            """
            Unable to get the backend optimizer because the `CachingOptimizer` is in \
            state `NO_OPTIMIZER`.

            Call `set_optimizer(model, optimizer)` to attach an optimizer before \
            calling `unsafe_backend`.
            """,
        )
    end
    return unsafe_backend(model.optimizer)
end

function unsafe_backend(model::MOI.Bridges.LazyBridgeOptimizer)
    return unsafe_backend(model.model)
end

unsafe_backend(model::MOI.ModelLike) = model

_moi_mode(::MOI.ModelLike) = DIRECT

function _moi_mode(model::MOIU.CachingOptimizer)
    return model.mode == MOIU.AUTOMATIC ? AUTOMATIC : MANUAL
end

"""
    mode(model::GenericModel)

Return the [`ModelMode`](@ref) of `model`.

## Example

```jldoctest
julia> model = Model();

julia> mode(model)
AUTOMATIC::ModelMode = 0
```
"""
function mode(model::GenericModel)
    # The type of `backend(model)` is not type-stable, so we use a function
    # barrier (`_moi_mode`) to improve performance.
    return _moi_mode(backend(model))
end

"""
    set_string_names_on_creation(model::GenericModel, value::Bool)

Set the default argument of the `set_string_name` keyword in the
[`@variable`](@ref) and [`@constraint`](@ref) macros to `value`.

The `set_string_name` keyword is used to determine whether to assign `String`
names to all variables and constraints in `model`.

By default, `value` is `true`. However, for larger models calling
`set_string_names_on_creation(model, false)` can improve performance at the cost
of reducing the readability of printing and solver log messages.

## Example

```jldoctest
julia> import HiGHS

julia> model = Model(HiGHS.Optimizer);

julia> set_string_names_on_creation(model)
true

julia> set_string_names_on_creation(model, false)

julia> set_string_names_on_creation(model)
false
```
"""
function set_string_names_on_creation(model::GenericModel, value::Bool)
    model.set_string_names_on_creation = value
    return
end

function set_string_names_on_creation(model::GenericModel)
    return model.set_string_names_on_creation
end

set_string_names_on_creation(::AbstractModel) = true

_moi_bridge_constraints(::MOI.ModelLike) = false

function _moi_bridge_constraints(model::MOIU.CachingOptimizer)
    return model.optimizer isa MOI.Bridges.LazyBridgeOptimizer
end

"""
    bridge_constraints(model::GenericModel)

When in direct mode, return `false`.

When in manual or automatic mode, return a `Bool` indicating whether the
optimizer is set and unsupported constraints are automatically bridged
to equivalent supported constraints when an appropriate transformation is
available.

## Example

```jldoctest
julia> import Ipopt

julia> model = Model(Ipopt.Optimizer);

julia> bridge_constraints(model)
true

julia> model = Model(Ipopt.Optimizer; add_bridges = false);

julia> bridge_constraints(model)
false
```
"""
function bridge_constraints(model::GenericModel)
    # The type of `backend(model)` is not type-stable, so we use a function
    # barrier (`_moi_bridge_constraints`) to improve performance.
    return _moi_bridge_constraints(backend(model))
end

# No optimizer is attached.
_moi_call_bridge_function(::Function, ::Nothing, args...) = nothing

function _moi_call_bridge_function(::Function, ::MOI.ModelLike, args...)
    return error(
        """
        Cannot use bridge functions because `add_bridges` was set to `false` in \
        the `Model` constructor.

        Recreate the model without setting `add_bridges = false`, or avoid calling \
        functions that require bridges.
        """,
    )
end

function _moi_call_bridge_function(
    f::Function,
    model::MOI.Bridges.LazyBridgeOptimizer,
    args...,
)
    return f(model, args...)
end

function _moi_call_bridge_function(
    f::Function,
    model::MOI.Utilities.CachingOptimizer,
    args...,
)
    return _moi_call_bridge_function(f, model.optimizer, args...)
end

"""
    add_bridge(
        model::GenericModel{T},
        BT::Type{<:MOI.Bridges.AbstractBridge};
        coefficient_type::Type{S} = T,
    ) where {T,S}

Add `BT{T}` to the list of bridges that can be used to transform unsupported
constraints into an equivalent formulation using only constraints supported by
the optimizer.

See also: [`remove_bridge`](@ref).

## Example

```jldoctest
julia> model = Model();

julia> add_bridge(model, MOI.Bridges.Constraint.SOCtoNonConvexQuadBridge)

julia> add_bridge(
           model,
           MOI.Bridges.Constraint.NumberConversionBridge;
           coefficient_type = Complex{Float64}
       )
```
"""
function add_bridge(
    model::GenericModel{S},
    BT::Type{<:MOI.Bridges.AbstractBridge};
    coefficient_type::Type{T} = S,
) where {S,T}
    push!(model.bridge_types, BT{T})
    _moi_call_bridge_function(MOI.Bridges.add_bridge, backend(model), BT{T})
    return
end

"""
    remove_bridge(
        model::GenericModel{S},
        BT::Type{<:MOI.Bridges.AbstractBridge};
        coefficient_type::Type{T} = S,
    ) where {S,T}

Remove `BT{T}` from the list of bridges that can be used to transform
unsupported constraints into an equivalent formulation using only constraints
supported by the optimizer.

See also: [`add_bridge`](@ref).

## Example

```jldoctest
julia> model = Model();

julia> add_bridge(model, MOI.Bridges.Constraint.SOCtoNonConvexQuadBridge)

julia> remove_bridge(model, MOI.Bridges.Constraint.SOCtoNonConvexQuadBridge)

julia> add_bridge(
           model,
           MOI.Bridges.Constraint.NumberConversionBridge;
           coefficient_type = Complex{Float64},
       )

julia> remove_bridge(
           model,
           MOI.Bridges.Constraint.NumberConversionBridge;
           coefficient_type = Complex{Float64},
       )
```
"""
function remove_bridge(
    model::GenericModel{S},
    BT::Type{<:MOI.Bridges.AbstractBridge};
    coefficient_type::Type{T} = S,
) where {T,S}
    delete!(model.bridge_types, BT{T})
    _moi_call_bridge_function(MOI.Bridges.remove_bridge, backend(model), BT{T})
    if mode(model) != DIRECT
        MOI.Utilities.reset_optimizer(model)
    end
    return
end

"""
     print_bridge_graph([io::IO,] model::GenericModel)

Print the hyper-graph containing all variable, constraint, and objective types
that could be obtained by bridging the variables, constraints, and objectives
that are present in the model.

!!! warning
    This function is intended for advanced users. If you want to see only the
    bridges that are currently used, use [`print_active_bridges`](@ref) instead.

## Explanation of output

Each node in the hyper-graph corresponds to a variable, constraint, or objective
type.
  * Variable nodes are indicated by `[ ]`
  * Constraint nodes are indicated by `( )`
  * Objective nodes are indicated by `| |`
The number inside each pair of brackets is an index of the node in the
hyper-graph.

Note that this hyper-graph is the full list of possible transformations. When
the bridged model is created, we select the shortest hyper-path(s) from this
graph, so many nodes may be un-used.

For more information, see Legat, B., Dowson, O., Garcia, J., and Lubin, M.
(2020).  "MathOptInterface: a data structure for mathematical optimization
problems." URL: [https://arxiv.org/abs/2002.03447](https://arxiv.org/abs/2002.03447)
"""
function print_bridge_graph(io::IO, model::GenericModel)
    return _moi_call_bridge_function(backend(model)) do m
        return MOI.Bridges.print_graph(io, m)
    end
end

print_bridge_graph(model::GenericModel) = print_bridge_graph(Base.stdout, model)

"""
    print_active_bridges([io::IO = stdout,] model::GenericModel)

Print a list of the variable, constraint, and objective bridges that are
currently used in the model.
"""
function print_active_bridges(io::IO, model::GenericModel)
    return _moi_call_bridge_function(backend(model)) do m
        return MOI.Bridges.print_active_bridges(io, m)
    end
end

"""
    print_active_bridges([io::IO = stdout,] model::GenericModel, ::Type{F}) where {F}

Print a list of bridges required for an objective function of type `F`.
"""
function print_active_bridges(io::IO, model::GenericModel, ::Type{F}) where {F}
    return _moi_call_bridge_function(backend(model)) do m
        return MOI.Bridges.print_active_bridges(io, m, moi_function_type(F))
    end
end

"""
    print_active_bridges(
        [io::IO = stdout,]
        model::GenericModel,
        F::Type,
        S::Type{<:MOI.AbstractSet},
    )

Print a list of bridges required for a constraint of type `F`-in-`S`.
"""
function print_active_bridges(
    io::IO,
    model::GenericModel,
    F::Type,
    S::Type{<:MOI.AbstractSet},
)
    return _moi_call_bridge_function(backend(model)) do m
        return MOI.Bridges.print_active_bridges(io, m, moi_function_type(F), S)
    end
end

"""
    print_active_bridges(
        [io::IO = stdout,]
        model::GenericModel,
        S::Type{<:MOI.AbstractSet},
    )

Print a list of bridges required to add a variable constrained to the set `S`.
"""
function print_active_bridges(
    io::IO,
    model::GenericModel,
    S::Type{<:MOI.AbstractSet},
)
    return _moi_call_bridge_function(backend(model)) do m
        return MOI.Bridges.print_active_bridges(io, m, S)
    end
end

function print_active_bridges(model::GenericModel, args...)
    return print_active_bridges(Base.stdout, model, args...)
end

"""
    empty!(model::GenericModel)::GenericModel

Empty the model, that is, remove all variables, constraints and model
attributes but not optimizer attributes. Always return the argument.

Note: removes extensions data.

## Example

```jldoctest
julia> model = Model();

julia> @variable(model, x[1:2]);

julia> isempty(model)
false

julia> empty!(model)
A JuMP Model
├ solver: none
├ objective_sense: FEASIBILITY_SENSE
├ num_variables: 0
├ num_constraints: 0
└ Names registered in the model: none

julia> print(model)
Feasibility
Subject to

julia> isempty(model)
true
```
"""
function Base.empty!(model::GenericModel)::GenericModel
    # The method changes the Model object to, basically, the state it was when
    # created (if the optimizer was already pre-configured). The exceptions
    # are:
    # * optimize_hook: it is basically an optimizer attribute and we promise
    #   to leave them alone (as do MOI.empty!).
    # * bridge_types: for consistency with MOI.empty! for
    #   MOI.Bridges.LazyBridgeOptimizer.
    # * operator_counter: it is just a counter for a single-time warning
    #   message (so keeping it helps to discover inefficiencies).
    MOI.empty!(model.moi_backend)
    empty!(model.shapes)
    model.nlp_model = nothing
    empty!(model.obj_dict)
    empty!(model.ext)
    empty!(model.macro_times)
    model.is_model_dirty = false
    return model
end

"""
    isempty(model::GenericModel)

Verifies whether the model is empty, that is, whether the MOI backend is empty
and whether the model is in the same state as at its creation, apart from
optimizer attributes.

## Example

```jldoctest
julia> model = Model();

julia> isempty(model)
true

julia> @variable(model, x[1:2]);

julia> isempty(model)
false
```
"""
function Base.isempty(model::GenericModel)
    return MOI.is_empty(model.moi_backend) &&
           isempty(model.shapes) &&
           model.nlp_model === nothing &&
           isempty(model.obj_dict) &&
           isempty(model.ext) &&
           !model.is_model_dirty
end

"""
    object_dictionary(model::GenericModel)

Return the dictionary that maps the symbol name of a variable, constraint, or
expression to the corresponding object.

Objects are registered to a specific symbol in the macros. For example,
`@variable(model, x[1:2, 1:2])` registers the array of variables `x` to the
symbol `:x`.

This method should be defined for any subtype of `AbstractModel`.

See also: [`unregister`](@ref).

## Example

```jldoctest
julia> model = Model();

julia> @variable(model, x[1:2]);

julia> object_dictionary(model)
Dict{Symbol, Any} with 1 entry:
  :x => VariableRef[x[1], x[2]]
```
"""
object_dictionary(model::GenericModel) = model.obj_dict

"""
    unregister(model::GenericModel, key::Symbol)

Unregister the name `key` from `model` so that a new variable, constraint, or
expression can be created with the same key.

Note that this will not delete the object `model[key]`; it will just remove the
reference at `model[key]`. To delete the object, use [`delete`](@ref) as well.

See also: [`delete`](@ref), [`object_dictionary`](@ref).

## Example

```jldoctest
julia> model = Model();

julia> @variable(model, x)
x

julia> @variable(model, x)
ERROR: An object of name `:x` is already registered in this model.

Consider instead using the anonymous construction syntax, for example, `x = @variable(model, [1:N], ...)` where the name of the object does not appear inside the macro.

Alternatively, use `unregister(model, :x)` to first unregister the existing name from the model. Note that this will not delete the object from the model; it will just remove the reference at `model[:x]`.

Stacktrace:
[...]

julia> num_variables(model)
1

julia> unregister(model, :x)

julia> @variable(model, x)
x

julia> num_variables(model)
2
```
"""
function unregister(model::AbstractModel, key::Symbol)
    delete!(object_dictionary(model), key)
    return
end

"""
    Base.getindex(m::JuMP.AbstractModel, name::Symbol)

To allow easy accessing of JuMP Variables and Constraints via `[]` syntax.

Returns the variable, or group of variables, or constraint, or group of
constraints, of the given name which were added to the model. This errors if
multiple variables or constraints share the same name.
"""
function Base.getindex(m::AbstractModel, name::Symbol)
    obj_dict = object_dictionary(m)
    if !haskey(obj_dict, name)
        throw(KeyError(name))
    end
    return obj_dict[name]
end

"""
    Base.setindex!(m::JuMP.AbstractModel, value, name::Symbol)

Stores the object `value` in the model `m` using so that it can be accessed via
`getindex`.  Can be called with `[]` syntax.
"""
function Base.setindex!(model::AbstractModel, value, name::Symbol)
    return object_dictionary(model)[name] = value
end

"""
    haskey(model::AbstractModel, name::Symbol)

Determine whether the model has a mapping for a given name.
"""
function Base.haskey(model::AbstractModel, name::Symbol)
    return haskey(object_dictionary(model), name)
end

"""
    set_optimize_hook(model::GenericModel, f::Union{Function,Nothing})

Set the function `f` as the optimize hook for `model`.

`f` should have a signature `f(model::GenericModel; kwargs...)`, where the `kwargs` are
those passed to [`optimize!`](@ref).

## Notes

 * The optimize hook should generally modify the model, or some external state
   in some way, and then call `optimize!(model; ignore_optimize_hook = true)` to
   optimize the problem, bypassing the hook.
 * Use `set_optimize_hook(model, nothing)` to unset an optimize hook.

## Example

```jldoctest
julia> model = Model();

julia> function my_hook(model::Model; kwargs...)
           println(kwargs)
           println("Calling with `ignore_optimize_hook = true`")
           optimize!(model; ignore_optimize_hook = true)
           return
       end
my_hook (generic function with 1 method)

julia> set_optimize_hook(model, my_hook)
my_hook (generic function with 1 method)

julia> optimize!(model; test_arg = true)
Base.Pairs{Symbol, Bool, Nothing, @NamedTuple{test_arg::Bool}}(:test_arg => 1)
Calling with `ignore_optimize_hook = true`
ERROR: NoOptimizer()
[...]
```
"""
set_optimize_hook(model::GenericModel, f) = (model.optimize_hook = f)

"""
    AbstractJuMPScalar <: MutableArithmetics.AbstractMutable

Abstract base type for all scalar types

The subtyping of `AbstractMutable` will allow calls of some `Base` functions
to be redirected to a method in MA that handles type promotion more carefully
(for example the promotion in sparse matrix products in SparseArrays usually
does not work for JuMP types) and exploits the mutability of `AffExpr` and `QuadExpr`.
"""
abstract type AbstractJuMPScalar <: _MA.AbstractMutable end

function _get_index_keyword_indexing_error()
    return ErrorException(
        """
        Indexing an `Array` with keyword arguments is not supported.

        Indexing with keyword arguments _is_ supported for `Container.DenseAxisArray`
        (and `Container.SparseAxisArray`).

        Force the container type by passing `container = DenseAxisArray` to any of
        the JuMP macros. For example:

        ```julia
        julia> model = Model();

        julia> @variable(model, x[i=1:3], container = DenseAxisArray)
        1-dimensional DenseAxisArray{VariableRef,1,...} with index sets:
            Dimension 1, Base.OneTo(3)
        And data, a 3-element Vector{VariableRef}:
         x[1]
         x[2]
         x[3]

        julia> x[i=2]
        x[2]
        ```
        """,
    )
end

function Base.getindex(x::Array{<:AbstractJuMPScalar}; kwargs...)
    if isempty(kwargs)
        if length(x) == 1
            return first(x)
        end
        throw(BoundsError(x, tuple()))
    end
    return throw(_get_index_keyword_indexing_error())
end

"""
    owner_model(s::AbstractJuMPScalar)

Return the model owning the scalar `s`.

## Example

```jldoctest
julia> model = Model();

julia> @variable(model, x);

julia> owner_model(x) === model
true
```
"""
function owner_model end

Base.ndims(::Type{<:AbstractJuMPScalar}) = 0
Base.ndims(::AbstractJuMPScalar) = 0

Base.IteratorEltype(::Type{<:AbstractJuMPScalar}) = Base.HasEltype()
Base.eltype(::Type{T}) where {T<:AbstractJuMPScalar} = T

# These are required to create symmetric containers of AbstractJuMPScalars.
LinearAlgebra.symmetric_type(::Type{T}) where {T<:AbstractJuMPScalar} = T

LinearAlgebra.symmetric(scalar::AbstractJuMPScalar, ::Symbol) = scalar

LinearAlgebra.issymmetric(::AbstractJuMPScalar) = true

LinearAlgebra.hermitian_type(::Type{T}) where {T<:AbstractJuMPScalar} = T

function LinearAlgebra.hermitian(x::F, ::Symbol) where {F<:AbstractJuMPScalar}
    return convert(F, real(x))
end

LinearAlgebra.ishermitian(x::AbstractJuMPScalar) = isreal(x)

LinearAlgebra.adjoint(scalar::AbstractJuMPScalar) = conj(scalar)

Base.iterate(x::AbstractJuMPScalar) = (x, true)
Base.iterate(::AbstractJuMPScalar, state) = nothing
Base.isempty(::AbstractJuMPScalar) = false
Base.length(::AbstractJuMPScalar) = 1

"""
    isequal_canonical(
        x::T,
        y::T
    ) where {T<:AbstractJuMPScalar,AbstractArray{<:AbstractJuMPScalar}}

Return `true` if `x` is equal to `y` after dropping zeros and disregarding
the order.

This method is mainly useful for testing, because fallbacks like `x == y` do not
account for valid mathematical comparisons like `x[1] + 0 x[2] + 1 == x[1] + 1`.

## Example

```jldoctest
julia> model = Model();

julia> @variable(model, x[1:2]);

julia> a = x[1] + 1.0
x[1] + 1

julia> b = x[1] + x[2] + 1.0
x[1] + x[2] + 1

julia> add_to_expression!(b, -1.0, x[2])
x[1] + 0 x[2] + 1

julia> a == b
false

julia> isequal_canonical(a, b)
true
```
"""
function isequal_canonical end

# Check if two arrays of AbstractJuMPScalars are equal. Useful for testing.
function isequal_canonical(
    x::AbstractArray{<:AbstractJuMPScalar},
    y::AbstractArray{<:AbstractJuMPScalar},
)
    return size(x) == size(y) && all(isequal_canonical.(x, y))
end

const _Constant = Union{Number,LinearAlgebra.UniformScaling}
_constant_to_number(x::Number) = x
_constant_to_number(J::LinearAlgebra.UniformScaling) = J.λ

# These includes are inter-dependent, and _must_ come in this particular order.
include("Containers/Containers.jl")
include("constraints.jl")
include("variables.jl")
include("objective.jl")
include("aff_expr.jl")
include("quad_expr.jl")
include("nlp.jl")
include("nlp_expr.jl")
include("macros.jl")
include("optimizer_interface.jl")

# These includes are self-contained and can go in any order, except that
# operators.jl must come after mutable_arithmetics.jl
include("callbacks.jl")
include("complement.jl")
include("copy.jl")
include("feasibility_checker.jl")
include("file_formats.jl")
include("jump_moi_function.jl")
include("lp_matrix_data.jl")
include("lp_sensitivity2.jl")
include("indicator.jl")
include("reified.jl")
include("mutable_arithmetics.jl")
include("operators.jl")
include("sd.jl")
include("sets.jl")
include("solution_summary.jl")

# print.jl must come last, because it uses types defined in earlier files.
include("print.jl")

# MOI contains a number of Enums that are often accessed by users such as
# `MOI.OPTIMAL`. This piece of code re-exports them from JuMP so that users can
# use: `MOI.OPTIMAL`, `JuMP.OPTIMAL`, or `using JuMP; OPTIMAL`.

for sym in [:ResultStatusCode, :TerminationStatusCode, :OptimizationSense]
    @eval const $sym = MOI.$sym
    for enum in instances(getfield(MOI, sym))
        @eval const $(Symbol(enum)) = $enum
        docstr = Docs.docstr(Docs.Binding(MOI, Symbol(enum)))
        Docs.doc!(@__MODULE__, Docs.Binding(@__MODULE__, Symbol(enum)), docstr)
    end
end

# JuMP exports everything except internal symbols, which are defined as those
# whose name starts with an underscore. Macros whose names start with
# underscores are internal as well. If you don't want all of these symbols
# in your environment, then use `import JuMP` instead of `using JuMP`.

# Do not add JuMP-defined symbols to this exclude list. Instead, rename them
# with an underscore.
const _EXCLUDE_SYMBOLS = [Symbol(@__MODULE__), :eval, :include]

for sym in names(@__MODULE__; all = true)
    sym_string = string(sym)
    if sym in _EXCLUDE_SYMBOLS ||
       startswith(sym_string, "_") ||
       startswith(sym_string, "@_")
        continue
    end
    if !(
        Base.isidentifier(sym) ||
        (startswith(sym_string, "@") && Base.isidentifier(sym_string[2:end]))
    )
        continue
    end
    @eval export $sym
end

# We need to explicitly export `MOI` because it was exported in previous
# versions, and it is not now because we use `import MathOptInterface as MOI`.
export MOI

# !!! note
#
#     The next codeblock is a PrecompileTools workflow that gets run when
#     JuMP.jl is precompiled. Methods that are called here will be cached so
#     that they do not need to be compiled in every session.
#
#     One downside of the design of JuMP (from the perspective of compilation)
#     is that the API has a large surface area, that is, there are many
#     combinations of function-in-set, and they get compiled individually.
#
#     The model below attempts to construct a very diverse model that utilizes
#     most of JuMP's machinery, but if you find a particular model has a bad
#     time-to-first solve (TTFX), then we likely need to add a new type of
#     constraint or objective to the precompile model.

import PrecompileTools

function _optimizer_for_precompile_tools()
    return MOI.Utilities.MockOptimizer(MOI.Utilities.Model{Float64}())
end

PrecompileTools.@setup_workload begin
    PrecompileTools.@compile_workload begin
        # We don't care about this particular optimizer, but it still
        # exercises generic code paths that calls like
        # Model(HiGHS.Optimizer) also need.
        model = Model(_optimizer_for_precompile_tools)
        optimize!(model)
        # Because lots of the work is done by macros, and macros are expanded
        # at lowering time, not much of this would get precompiled without `@eval`
        @eval begin
            model = Model()
            @variables(model, begin
                x1 >= 0
                0 <= x2 <= 1
                x3 == 2
                x4, Bin
                x5, Int
                x6[1:3]
                x7[2:3]
                x8[i = 1:3; isodd(i)], (start = i)
            end)
            @expressions(model, begin
                a, 2 * x1 + 3 * x2
                b, 1 + x1^2 + x2
                nl_expr, sin(x1)
            end)
            @constraints(model, begin
                c1, a >= 0
                c2[i=2:3, j=["a"]], a <= i
                c3, a == 0
                c4, 0 <= a <= 1
                c5, b >= 0
                c6, b <= 0
                c7, b == 0
                c8, 0 <= b <= 1
                [x1, x2, x1] in SecondOrderCone()
                [1.0*x1 x2; x2 x1] >= 0, PSDCone()
                1.0 * x1 ⟂ x2
                nl_expr <= 1
            end)
            @objective(model, Min, x1)
            @objective(model, Max, a)
            @objective(model, Min, b)
        end
    end
end

end
