text:
################################################################################
#                                INDEX TYPES
################################################################################
"""
    AbstractInfOptIndex

An abstract type for all index objects used in `InfiniteOpt`.
"""
abstract type AbstractInfOptIndex end

"""
    ObjectIndex <: AbstractInfOptIndex

An abstract type for indices of objects stored in `MOI.Utilities.CleverDicts`.
"""
abstract type ObjectIndex <: AbstractInfOptIndex end

"""
    IndependentParameterIndex <: ObjectIndex

A `DataType` for storing the index of a [`IndependentParameter`](@ref).

**Fields**
- `value::Int64`: The index value.
"""
struct IndependentParameterIndex <: ObjectIndex
    value::Int64
end

"""
    DependentParametersIndex <: ObjectIndex

A `DataType` for storing the index of a [`DependentParameters`](@ref) object.

**Fields**
- `value::Int64`: The index value.
"""
struct DependentParametersIndex <: ObjectIndex
    value::Int64
end

"""
    DependentParameterIndex <: AbstractInfOptIndex

A `DataType` for storing the index of an indiviudal parameter in a
[`DependentParameters`](@ref) object.

**Fields**
- `object_index::DependentParametersIndex`: The index of the parameter collection.
- `param_index::Int`: The index of the individual parameter in the above object.
"""
struct DependentParameterIndex <: AbstractInfOptIndex
    object_index::DependentParametersIndex
    param_index::Int
end

# Define convenient alias for infinite parameters
const InfiniteParameterIndex = Union{IndependentParameterIndex, DependentParameterIndex}

"""
    FiniteParameterIndex <: ObjectIndex

A `DataType` for storing the index of a [`FiniteParameter`](@ref).

**Fields**
- `value::Int64`: The index value.
"""
struct FiniteParameterIndex <: ObjectIndex
    value::Int64
end

"""
    ParameterFunctionIndex <: ObjectIndex

A `DataType` for storing the index of a [`InfiniteParameterFunction`](@ref).

**Fields**
- `value::Int64`: The index value.
"""
struct ParameterFunctionIndex <: ObjectIndex
    value::Int64
end

"""
    InfiniteVariableIndex <: ObjectIndex

A `DataType` for storing the index of a [`InfiniteVariable`](@ref).

**Fields**
- `value::Int64`: The index value.
"""
struct InfiniteVariableIndex <: ObjectIndex
    value::Int64
end

"""
    ReducedVariableIndex <: ObjectIndex

A `DataType` for storing the index of a [`ReducedVariable`](@ref).

**Fields**
- `value::Int64`: The index value.
"""
struct ReducedVariableIndex <: ObjectIndex
    value::Int64
end

"""
    PointVariableIndex <: ObjectIndex

A `DataType` for storing the index of a [`PointVariable`](@ref).

**Fields**
- `value::Int64`: The index value.
"""
struct PointVariableIndex <: ObjectIndex
    value::Int64
end

"""
    HoldVariableIndex <: ObjectIndex

A `DataType` for storing the index of a [`HoldVariable`](@ref).

**Fields**
- `value::Int64`: The index value.
"""
struct HoldVariableIndex <: ObjectIndex
    value::Int64
end

"""
    DerivativeIndex <: ObjectIndex

A `DataType` for storing the index of a [`Derivative`](@ref).

**Fields**
- `value::Int64`: The index value.
"""
struct DerivativeIndex <: ObjectIndex
    value::Int64
end

# Define convenient aliases
const FiniteVariableIndex = Union{PointVariableIndex, HoldVariableIndex,
                                  FiniteParameterIndex}

"""
    MeasureIndex <: ObjectIndex

A `DataType` for storing the index of a [`Measure`](@ref).

**Fields**
- `value::Int64`: The index value.
"""
struct MeasureIndex <: ObjectIndex
    value::Int64
end

"""
    ConstraintIndex <: ObjectIndex

A `DataType` for storing the index of constraint objects.

**Fields**
- `value::Int64`: The index value.
"""
struct ConstraintIndex <: ObjectIndex
    value::Int64
end

## Extend the CleverDicts key access methods
# index_to_key
function MOIUC.index_to_key(::Type{C},
                            index::Int64) where {C <: ObjectIndex}
    return C(index)
end

# key_to_index
function MOIUC.key_to_index(key::ObjectIndex)::Int64
    return key.value
end

# Extend Base functions
Base.length(index::AbstractInfOptIndex) = 1
Base.broadcastable(index::AbstractInfOptIndex) = Ref(index)
Base.iterate(index::AbstractInfOptIndex) = (index, true)
Base.iterate(index::AbstractInfOptIndex, state) = nothing
Base.hash(v::ObjectIndex, h::UInt) = hash(v.value, h)

################################################################################
#                            INFINITE SET TYPES
################################################################################
"""
    AbstractInfiniteSet

An abstract type for sets that characterize infinite parameters.
"""
abstract type AbstractInfiniteSet end

"""
    InfiniteScalarSet <: AbstractInfiniteSet

An abstract type for infinite sets that are one-dimensional.
"""
abstract type InfiniteScalarSet <: AbstractInfiniteSet end

"""
    IntervalSet <: InfiniteScalarSet

A `DataType` that stores the lower and upper interval bounds for infinite
parameters that are continuous over a certain that interval. This is for use
with a [`IndependentParameter`](@ref).

**Fields**
- `lower_bound::Float64` Lower bound of the infinite parameter.
- `upper_bound::Float64` Upper bound of the infinite parameter.
"""
struct IntervalSet <: InfiniteScalarSet
    lower_bound::Float64
    upper_bound::Float64
    function IntervalSet(lower::Real, upper::Real)
        if lower > upper
            error("Invalid interval set bounds, lower bound is greater than " *
                  "upper bound.")
        end
        return new(lower, upper)
    end
end

"""
    UniDistributionSet{T <: Distributions.UnivariateDistribution} <: InfiniteScalarSet

A `DataType` that stores the distribution characterizing an infinite parameter that
is random. This is for use with a [`IndependentParameter`](@ref).

**Fields**
- `distribution::T` Distribution of the random parameter.
"""
struct UniDistributionSet{T <: Distributions.UnivariateDistribution} <: InfiniteScalarSet
    distribution::T
end

"""
    InfiniteArraySet <: AbstractInfiniteSet

An abstract type for multi-dimensional infinite sets.
"""
abstract type InfiniteArraySet <: AbstractInfiniteSet end

# Make convenient Union for below
const NonUnivariateDistribution = Union{Distributions.MultivariateDistribution,
                                        Distributions.MatrixDistribution}

"""
    MultiDistributionSet{T <: NonUnivariateDistribution} <: InfiniteArraySet

A `DataType` that stores the distribution characterizing a collection of
infinite parameters that follows its form. This is for use with
[`DependentParameters`](@ref).

**Fields**
- `distribution::T` Distribution of the random parameters.
"""
struct MultiDistributionSet{T <: NonUnivariateDistribution} <: InfiniteArraySet
    distribution::T
end

# make convenient alias for distribution sets
const DistributionSet = Union{UniDistributionSet, MultiDistributionSet}

"""
    CollectionSet{T <: InfiniteScalarSet} <: InfiniteArraySet

A `DataType` that stores a collection of `InfiniteScalarSet`s characterizing a
collection of infinite parameters that follows its form. This is for use with
[`DependentParameters`](@ref).

**Fields**
- `sets::Array{T}` The collection of scalar sets.
"""
struct CollectionSet{T <: InfiniteScalarSet} <: InfiniteArraySet
    sets::Vector{T}
end

################################################################################
#                      GENERATIVE SUPPORT INFORMATION TYPES
################################################################################
"""
    AbstractGenerativeInfo

An abstract type for storing information about generating supports that are made
based on existing supports as required by certain measures and/or derivatives
that depend on a certain independent infinite parameter. Such as the case with
internal collocation supports.
"""
abstract type AbstractGenerativeInfo end

"""
    NoGenerativeSupports <: AbstractGenerativeInfo

A `DataType` to signify that no generative supports will be generated for the
measures and/or the derivatives. Has no fields.
"""
struct NoGenerativeSupports <: AbstractGenerativeInfo end

"""
    UniformGenerativeInfo <: AbstractGenerativeInfo

A `DataType` for generative supports that will be generated in a uniform manner
over finite elements (i.e., in between the existing supports). These generative
supports are described by the `support_basis` which lie in a nominal domain [0, 1].
The constructor is of the form:
```
    UniformGenerativeInfo(support_basis::Vector{<:Real}, label::DataType,
                          [lb::Real = 0, ub::Real = 1])
```
where the `support_basis` is defined over [`lb`, `ub`].

**Fields**
- `support_basis::Vector{Float64}`: The basis of generative supports defined in
   [0, 1] that will be transformed for each finite element.
- `label::DataType`: The unique label to be given to each generative support.
"""
struct UniformGenerativeInfo <: AbstractGenerativeInfo
    support_basis::Vector{Float64}
    label::DataType
    function UniformGenerativeInfo(basis::Vector{<:Real}, label::DataType,
                                   lb::Real = 0, ub::Real = 1)
        if minimum(basis) < lb || maximum(basis) > ub
            error("Support basis violate the given lower and upper bounds. " *
                  "Please specify the appropriate lower bound and upper bounds.")
        end
        return new((basis .- lb) ./ (ub - lb),  label)
    end
end

# Extend Base.:(==)
function Base.:(==)(info1::UniformGenerativeInfo, info2::UniformGenerativeInfo)::Bool
    return info1.support_basis == info2.support_basis && info1.label == info2.label
end

################################################################################
#                       BASIC DERIVATIVE EVALUATION TYPES
################################################################################
"""
    AbstractDerivativeMethod

An abstract type for storing derivative evaluation data that is pertinent to its
reformation/transcription.
"""
abstract type AbstractDerivativeMethod end

"""
    GenerativeDerivativeMethod <: AbstractDerivativeMethod

An abstract type for derivative evaluation method types that will require support
generation when employed (e.g., internal node points associated with orthogonal
collocation). Such methods can be used with derivatives that depend on independent
infinite parameters, but cannot be used for ones that depend on dependent parameters.
"""
abstract type GenerativeDerivativeMethod <: AbstractDerivativeMethod end

"""
    NonGenerativeDerivativeMethod <: AbstractDerivativeMethod

An abstract type for derivative evaluation method types that do not require the
definition of additional support points. Such methods are amendable to any
derivative in InfiniteOpt including those with dependent infinite parameter
dependencies.
"""
abstract type NonGenerativeDerivativeMethod <: AbstractDerivativeMethod end

"""
    FDTechnique

An abstract data type for labels of specific techniques applied in the finite
difference method in derivative evaluation.
"""
abstract type FDTechnique end

"""
    Forward <: FDTechnique

A technique label for finite difference method that implements a forward
difference approximation.
"""
struct Forward <: FDTechnique end

"""
    Central <: FDTechnique

A technique label for finite difference method that implements a central
difference approximation.
"""
struct Central <: FDTechnique end

"""
    Backward <: FDTechnique

A technique label for finite difference method that implements a backward
difference approximation.
"""
struct Backward <: FDTechnique end

"""
    FiniteDifference{T <: FDTechnique} <: NonGenerativeDerivativeMethod

A `DataType` for information about finite difference method applied to
a derivative evaluation. Note that the constructor is of the form:
```julia
    FiniteDifference([technique::FDTechnique = Backward()],
                     [add_boundary_constr::Bool = true])
```
where `technique` is the indicated finite difference method to be applied and
`add_boundary_constr` indicates if the finite difference equation corresponding to
a boundary support should be included. Thus, for backward difference since
corresponds to the terminal point and for forward difference this corresponds to
the initial point. We recommend using `add_boundary_constr = false` when an final
condition is given with a backward method or when an initial condition is given
with a forward method. Note that this argument is ignored for central finite
difference which cannot include any boundary points.

**Fields**
- `technique::T`: Mathematical technqiue behind finite difference
- `add_boundary_constraint::Bool`: Indicate if the boundary constraint should be
  included in the transcription (e.g., the terminal boundary backward equation for
  backward difference)
"""
struct FiniteDifference{T <: FDTechnique} <: NonGenerativeDerivativeMethod
    technique::T
    add_boundary_constraint::Bool
    # set the constructor
    function FiniteDifference(technique::T = Backward(),
        add_boundary_constr::Bool = true) where {T <: FDTechnique}
        return new{T}(technique, add_boundary_constr)
    end
end

################################################################################
#                              PARAMETER TYPES
################################################################################
"""
    InfOptParameter <: JuMP.AbstractVariable

An abstract type for all parameters used in InfiniteOpt.
"""
abstract type InfOptParameter <: JuMP.AbstractVariable end

"""
    ScalarParameter <: InfOptParameter

An abstract type for scalar parameters used in InfiniteOpt.
"""
abstract type ScalarParameter <: InfOptParameter end

"""
    IndependentParameter{T <: InfiniteScalarSet,
                         M <: AbstractDerivativeMethod,
                         I <: AbstractGenerativeInfo} <: ScalarParameter

A `DataType` for storing independent scalar infinite parameters.

**Fields**
- `set::T`: The infinite set that characterizes the parameter.
- `supports::DataStructures.SortedDict{Float64, Set{DataType}}`: The support points
   used to discretize the parameter and their associated type labels stored as
   `DataTypes`s which should be a subtype of [`AbstractSupportLabel`](@ref).
- `sig_digits::Int`: The number of significant digits used to round the support values.
- `derivative_method::M`: The derivative evaluation method used for derivatives that
   are conducted with respect to this parameter.
- `gnerative_supp_info::I`: The info associated with any generative supports that will
   need to be generated for measures and/or derivatives based on existing supports.
"""
struct IndependentParameter{T <: InfiniteScalarSet,
                            M <: AbstractDerivativeMethod,
                            I <: AbstractGenerativeInfo} <: ScalarParameter
    set::T
    supports::DataStructures.SortedDict{Float64, Set{DataType}} # Support to label set
    sig_digits::Int
    derivative_method::M
    generative_supp_info::I
end

"""
    FiniteParameter <: ScalarParameter

A `DataType` for storing finite parameters meant to be nested in expressions
and replaced with their values at runtime.

**Fields**
- `value::Float64`: The parameter value.
"""
struct FiniteParameter <: ScalarParameter
    value::Float64
end

"""
    DependentParameters{T <: InfiniteArraySet,
                        M <: NonGenerativeDerivativeMethod} <: InfOptParameter

A `DataType` for storing a collection of dependent infinite parameters.

**Fields**
- `set::T`: The infinite set that characterizes the parameters.
- `supports::Dict{Vector{Float64}, Set{DataType}}`: Support dictionary where keys
              are supports and the values are the set of labels for each support.
- `sig_digits::Int`: The number of significant digits used to round the support values.
- `derivative_methods::Vector{M}`: The derivative evaluation methods associated with
  each parameter.
"""
struct DependentParameters{T <: InfiniteArraySet,
                           M <: NonGenerativeDerivativeMethod} <: InfOptParameter
    set::T
    supports::Dict{Vector{Float64}, Set{DataType}} # Support to label set
    sig_digits::Int
    derivative_methods::Vector{M}
end

# Define convenient alias for infinite types
const InfiniteParameter = Union{IndependentParameter, DependentParameters}

"""
    AbstractDataObject

An abstract type for `DataType`s that store core variable `DataType`s and their
model specific information (e.g., dependency mappings). These are what are
stored in the `InfiniteModel` `CleverDict`s.
"""
abstract type AbstractDataObject end

"""
    ScalarParameterData{P <: ScalarParameter} <: AbstractDataObject

A mutable `DataType` for storing `ScalarParameter`s and their data.

**Fields**
- `parameter::P`: The scalar parameter.
- `object_num::Int`: The location of the corresponding `ObjectIndex` in
    `InfiniteModel.param_object_indices` (given by `InfiniteModel.last_object_num`).
- `parameter_num::Int`: Given by `InfiniteModel.last_param_num` (updated when
                        prior parameters are deleted)
- `name::String`: The name used for printing.
- `parameter_func_indices::Vector{ParameterFunctionIndex}`: Indices of dependent
   infinite parameter functions.
- `infinite_var_indices::Vector{InfiniteVariableIndex}`: Indices of dependent
   infinite variables.
- `derivative_indices::Vector{DerivativeIndex}`: Indices of dependent derivatives.
- `measure_indices::Vector{MeasureIndex}`: Indices of dependent measures.
- `constraint_indices::Vector{ConstraintIndex}`: Indices of dependent constraints.
- `in_objective::Bool`: Is this used in objective? This should be true only for finite parameters.
- `generative_measures::Vector{MeasureIndex}`: Indices of measures that use `parameter.generative_supp_info`.
- `has_internal_supports::Bool`: Does this parameter have internal supports?
- `has_generative_supports::Bool`: Have any generative supports been added?
- `has_deriv_constrs::Bool`: Have any derivative evaluation constraints been added
                             to the infinite model associated with this parameter?
"""
mutable struct ScalarParameterData{P <: ScalarParameter} <: AbstractDataObject
    parameter::P
    object_num::Int
    parameter_num::Int
    name::String
    parameter_func_indices::Vector{ParameterFunctionIndex}
    infinite_var_indices::Vector{InfiniteVariableIndex}
    derivative_indices::Vector{DerivativeIndex}
    measure_indices::Vector{MeasureIndex}
    constraint_indices::Vector{ConstraintIndex}
    in_objective::Bool
    generative_measures::Vector{MeasureIndex}
    has_internal_supports::Bool
    has_generative_supports::Bool
    has_deriv_constrs::Bool
end

# Convenient constructor
function ScalarParameterData(param::P,
                             object_num::Int,
                             parameter_num::Int,
                             name::String = ""
                             ) where {P <: ScalarParameter}
    return ScalarParameterData{P}(param, object_num, parameter_num, name,
                                  ParameterFunctionIndex[], InfiniteVariableIndex[],
                                  DerivativeIndex[], MeasureIndex[],
                                  ConstraintIndex[], false, MeasureIndex[],
                                  false, false, false)
end

"""
    MultiParameterData{P <: DependentParameters} <: AbstractDataObject

A mutable `DataType` for storing [`DependentParameters`](@ref) and their data.

**Fields**
- `parameters::P`: The parameter collection.
- `object_num::Int`: The location of the corresponding `ObjectIndex` in
   `InfiniteModel.param_object_indices` (given by `InfiniteModel.last_object_num`).
- `parameter_nums::UnitRange{Int}`: Given by `InfiniteModel.last_param_num`
                                    (updated when prior parameters are deleted)
- `names::Vector{String}`: The names used for printing each parameter.
- `parameter_func_indices::Vector{ParameterFunctionIndex}`: Indices of
   dependent infinite parameter functions.
- `infinite_var_indices::Vector{InfiniteVariableIndex}`: Indices of
   dependent infinite variables.
- `derivative_indices::Vector{Vector{DerivativeIndex}} `: Indices of dependent derivatives.
- `measure_indices::Vector{Vector{MeasureIndex}}`: Indices of dependent measures.
- `constraint_indices::Vector{Vector{ConstraintIndex}}`: Indices of dependent
  constraints.
- `has_internal_supports::Bool`: Does this parameter have internal supports?
- `has_deriv_constrs::Bool`: Have any derivative evaluation constraints been added
                             to the infinite model associated with this parameter?
"""
mutable struct MultiParameterData{P <: DependentParameters} <: AbstractDataObject
    parameters::P
    object_num::Int
    parameter_nums::UnitRange{Int}
    names::Vector{String}
    parameter_func_indices::Vector{ParameterFunctionIndex}
    infinite_var_indices::Vector{InfiniteVariableIndex}
    derivative_indices::Vector{Vector{DerivativeIndex}}
    measure_indices::Vector{Vector{MeasureIndex}}
    constraint_indices::Vector{Vector{ConstraintIndex}}
    has_internal_supports::Bool
    has_deriv_constrs::Vector{Bool}
end

# Convenient constructor
function MultiParameterData(params::P,
                            object_num::Int,
                            parameter_nums::UnitRange{Int},
                            names::Vector{String},
                            ) where {P <: DependentParameters}
    return MultiParameterData{P}(params, object_num, parameter_nums, names,
                                 ParameterFunctionIndex[], InfiniteVariableIndex[],
                                 [DerivativeIndex[] for i in eachindex(names)],
                                 [MeasureIndex[] for i in eachindex(names)],
                                 [ConstraintIndex[] for i in eachindex(names)],
                                 false, zeros(Bool, length(names)))
end

################################################################################
#                             PARAMETER BOUNDS
################################################################################
"""
    ParameterBounds{P <: GeneralVariableRef}

A `DataType` for storing intervaled bounds of parameters. This is used to define
subdomains of [`HoldVariable`](@ref)s and [`BoundedScalarConstraint`](@ref)s.
Note that the GeneralVariableRef must pertain to infinite parameters.

**Fields**
- `intervals::Dict{GeneralVariableRef, IntervalSet}`: A dictionary
  of interval bounds on infinite parameters.
"""
struct ParameterBounds{P <: JuMP.AbstractVariableRef}
    intervals::Dict{P, IntervalSet}
end

################################################################################
#                     INFINITE PARAMETER FUNCTION OBJECTS
################################################################################
"""
    InfiniteParameterFunction{P <: GeneralVariableRef}

A `DataType` for storing infinite parameter functions. These equate to arbitrary
functions that take support instances of infinite parameters `parameter_refs` in
as input and compute a scalar value as output via `func`. These can then can
incorporated in expressions via [`ParameterFunctionRef`](@ref)s.

**Fields**
-`func::Function`: The function the takes infinite parameters as input and provide
                   a scalar number as output.
-`parameter_refs::VectorTuple{P}`: The infinite parameter references that serve as
                                   inputs to `func`. Their formatting is analagous
                                   to those of infinite variables.
- `parameter_nums::Vector{Int}`: The parameter numbers of `parameter_refs`.
- `object_nums::Vector{Int}`: The parameter object numbers associated with `parameter_refs`.
"""
struct InfiniteParameterFunction{P <: JuMP.AbstractVariableRef}
    func::Function
    parameter_refs::VectorTuple{P}
    object_nums::Vector{Int}
    parameter_nums::Vector{Int}
end

"""
    ParameterFunctionData{F <: InfiniteParameterFunction} <: AbstractDataObject

A mutable `DataType` for storing `InfiniteParameterFunction`s and their data.

**Fields**
- `func::F`: The infinite parameter function.
- `name::String`: The name used for printing.
- `measure_indices::Vector{MeasureIndex}`: Indices of dependent measures.
- `constraint_indices::Vector{ConstraintIndex}`: Indices of dependent constraints.
- `reduced_var_indices::Vector{ReducedVariableIndex}`: Indices of dependent reduced variables.
- `derivative_indices::Vector{DerivativeIndex}`: Indices of dependent derivatives.
"""
mutable struct ParameterFunctionData{F <: InfiniteParameterFunction} <: AbstractDataObject
    func::F
    name::String
    measure_indices::Vector{MeasureIndex}
    constraint_indices::Vector{ConstraintIndex}
    reduced_var_indices::Vector{ReducedVariableIndex}
    derivative_indices::Vector{DerivativeIndex}
    function ParameterFunctionData(func::F, name::String = "") where {F <: InfiniteParameterFunction}
        return new{F}(func, name, MeasureIndex[], ConstraintIndex[],
                      ReducedVariableIndex[], DerivativeIndex[])
    end
end

################################################################################
#                               VARIABLE TYPES
################################################################################
"""
    InfOptVariable <: JuMP.AbstractVariable

An abstract type for infinite, reduced, point, and hold variables.
"""
abstract type InfOptVariable <: JuMP.AbstractVariable end

"""
    InfiniteVariable{P <: GeneralVariableRef} <: InfOptVariable

A `DataType` for storing core infinite variable information. Note that indices
that refer to the same dependent parameter group must be in the same tuple element.
It is important to note that `info.start` should contain a start value function
that generates the start value for a given infinite parameter support. This
function should map a support to a start value using user-formatting if
`is_vector_start = false`, otherwise it should do the mapping using a single
support vector as input. Also, the variable reference type `P` must pertain to
infinite parameters.

**Fields**
- `info::JuMP.VariableInfo{Float64, Float64, Float64, Function}`: JuMP variable information.
- `parameter_refs::VectorTuple{P}`: The infinite parameter references that
                                    parameterize the variable.
- `parameter_nums::Vector{Int}`: The parameter numbers of `parameter_refs`.
- `object_nums::Vector{Int}`: The parameter object numbers associated with `parameter_refs`.
- `is_vector_start::Bool`: Does the start function take support values formatted as vectors?
"""
struct InfiniteVariable{P <: JuMP.AbstractVariableRef} <: InfOptVariable
    info::JuMP.VariableInfo{Float64, Float64, Float64, Function}
    parameter_refs::VectorTuple{P}
    parameter_nums::Vector{Int}
    object_nums::Vector{Int}
    is_vector_start::Bool
end

"""
    ReducedVariable{I <: GeneralVariableRef} <: InfOptVariable

A `DataType` for storing reduced infinite variables which partially support an
infinite variable.

**Fields**
- `infinite_variable_ref::I`: The original infinite/derivvative variable.
- `eval_supports::Dict{Int, Float64}`: The original parameter tuple linear indices
                                     to the evaluation supports.
- `parameter_nums::Vector{Int}`: The parameter numbers associated with the reduced
                                 `parameter_refs`.
- `object_nums::Vector{Int}`: The parameter object numbers associated with the
                              reduced `parameter_refs`.
"""
struct ReducedVariable{I <: JuMP.AbstractVariableRef} <: InfOptVariable
    infinite_variable_ref::I
    eval_supports::Dict{Int, Float64}
    parameter_nums::Vector{Int}
    object_nums::Vector{Int}
end

"""
    PointVariable{I <: GeneralVariableRef} <: InfOptVariable

A `DataType` for storing point variable information. Note that the elements
`parameter_values` field must match the format of the parameter reference tuple
defined in [`InfiniteVariable`](@ref)

**Fields**
- `info::JuMP.VariableInfo{Float64, Float64, Float64, Float64}` JuMP Variable information.
- `infinite_variable_ref::I` The infinite variable/derivative reference
    associated with the point variable.
- `parameter_values::Vector{Float64}` The infinite parameter values
    defining the point.
"""
struct PointVariable{I <: JuMP.AbstractVariableRef} <: InfOptVariable
    info::JuMP.VariableInfo{Float64, Float64, Float64, Float64}
    infinite_variable_ref::I
    parameter_values::Vector{Float64}
end

"""
    HoldVariable{P <: GeneralVariableRef} <: InfOptVariable

A `DataType` for storing hold variable information.

**Fields**
- `info::JuMP.VariableInfo{Float64, Float64, Float64, Float64}` JuMP variable information.
- `parameter_bounds::ParameterBounds{P}` Valid parameter sub-domains
"""
struct HoldVariable{P <: JuMP.AbstractVariableRef} <: InfOptVariable
    info::JuMP.VariableInfo{Float64, Float64, Float64, Float64}
    parameter_bounds::ParameterBounds{P}
end

"""
    VariableData{V <: InfOptVariable} <: AbstractDataObject

A mutable `DataType` for storing `InfOptVariable`s and their data.

**Fields**
- `variable::V`: The scalar variable.
- `name::String`: The name used for printing.
- `lower_bound_index::Union{ConstraintIndex, Nothing}`: Index of lower bound constraint.
- `upper_bound_index::Union{ConstraintIndex, Nothing}`: Index of upper bound constraint.
- `fix_index::Union{ConstraintIndex, Nothing}`: Index on fixing constraint.
- `zero_one_index::Union{ConstraintIndex, Nothing}`: Index of binary constraint.
- `integrality_index::Union{ConstraintIndex, Nothing}`: Index of integer constraint.
- `measure_indices::Vector{MeasureIndex}`: Indices of dependent measures.
- `constraint_indices::Vector{ConstraintIndex}`: Indices of dependent constraints.
- `in_objective::Bool`: Is this used in objective?
- `point_var_indices::Vector{PointVariableIndex}`: Indices of dependent point variables.
- `reduced_var_indices::Vector{ReducedVariableIndex}`: Indices of dependent reduced variables.
- `derivative_indices::Vector{DerivativeIndex}`: Indices of dependent derivatives.
- `deriv_constr_indices::Vector{ConstraintIndex}`: Indices of dependent derivative evaluation constraints.
"""
mutable struct VariableData{V <: InfOptVariable} <: AbstractDataObject
    variable::V
    name::String
    lower_bound_index::Union{ConstraintIndex, Nothing}
    upper_bound_index::Union{ConstraintIndex, Nothing}
    fix_index::Union{ConstraintIndex, Nothing}
    zero_one_index::Union{ConstraintIndex, Nothing}
    integrality_index::Union{ConstraintIndex, Nothing}
    measure_indices::Vector{MeasureIndex}
    constraint_indices::Vector{ConstraintIndex}
    in_objective::Bool
    point_var_indices::Vector{PointVariableIndex} # InfiniteVariables only
    reduced_var_indices::Vector{ReducedVariableIndex} # InfiniteVariables only
    derivative_indices::Vector{DerivativeIndex} # infinite and reduced only
    deriv_constr_indices::Vector{ConstraintIndex} # Derivatives only
    function VariableData(var::V, name::String = "") where {V <: InfOptVariable}
        return new{V}(var, name, nothing, nothing, nothing, nothing, nothing,
                   MeasureIndex[], ConstraintIndex[], false, PointVariableIndex[],
                   ReducedVariableIndex[], DerivativeIndex[], ConstraintIndex[])
    end
end

################################################################################
#                              DERIVATIVE TYPES
################################################################################
"""
    Derivative{V <: GeneralVariableRef} <: InfOptVariable

A `DataType` for storing core infinite derivative information. This follows a
derivative of the form: ``\\frac{\\partial x(\\alpha, \\hdots)}{\\partial \\alpha}``
where ``x(\\alpha, \\hdots)`` is an infinite variable and ``\\alpha`` is an infinite
parameter. Here, both ``x`` and ``\\alpha`` must be scalars.

It is important to note that `info.start` should contain a start value function
that generates the start value for a given infinite parameter support. This
function should map a support to a start value using user-formatting if
`is_vector_start = false`, otherwise it should do the mapping using a single
support vector as input. Also, the variable reference type `V` must pertain to
infinite variables and parameters.

**Fields**
- `info::JuMP.VariableInfo{Float64, Float64, Float64, Function}`: JuMP variable information.
- `is_vector_start::Bool`: Does the start function take support values formatted as vectors?
- `variable_ref::V`: The variable reference of the infinite variable argument.
- `parameter_ref::V`: The variable reference of the infinite parameter the defines the
   differential operator.
"""
struct Derivative{V <: JuMP.AbstractVariableRef} <: InfOptVariable
    info::JuMP.VariableInfo{Float64, Float64, Float64, Function}
    is_vector_start::Bool
    variable_ref::V # could be ref of infinite/reduced variable/derivative or measure (top of derivative)
    parameter_ref::V # a scalar infinite parameter ref (bottom of derivative)
end

################################################################################
#                               MEASURE TYPES
################################################################################
"""
    AbstractMeasureData

An abstract type to define data for measures to define the behavior of
[`Measure`](@ref).
"""
abstract type AbstractMeasureData end

"""
    DiscreteMeasureData{P <: Union{JuMP.AbstractVariableRef,
                        Vector{<:JuMP.AbstractVariableRef}},
                        N, B <: Union{Float64, Vector{Float64}}
                        } <: AbstractMeasureData

A DataType for immutable measure abstraction data where the
abstraction is of the form:
``measure = \\int_{\\tau \\in T} f(\\tau) w(\\tau) d\\tau \\approx \\sum_{i = 1}^N \\alpha_i f(\\tau_i) w(\\tau_i)``.
The supports and coefficients are immutable (i.e., they will not change
even if supports are changed for the underlying infinite parameter.) This
type can be used for both 1-dimensional and multi-dimensional measures.

**Fields**
- `parameter_refs::P`: The infinite parameter(s) over which the integration occurs.
                       These can be comprised of multiple independent parameters,
                       but dependent parameters cannot be mixed with other types.
- `coefficients::Vector{Float64}`: Coefficients ``\\alpha_i`` for the above
                                   measure abstraction.
- `supports::Array{Float64, N}`: Supports points ``\\tau_i``. This is a `Vector`
                                 if only one parameter is given, otherwise it is
                                 a `Matrix` where the supports are stored column-wise.
- `label::DataType`: Label for the support points ``\\tau_i`` when stored in the
                   infinite parameter(s), stemming from [`AbstractSupportLabel`](@ref).
- `weight_function::Function`: Weighting function ``w`` must map an individual
                               support value to a `Real` scalar value.
- `lower_bounds::B`: Lower bound in accordance with ``T``, this denotes the
                    intended interval of the measure and should be `NaN` if ignored
- `upper_bounds::B`: Same as above but the upper bound.
- `is_expect::Bool`: Is this data associated with an expectation call?
"""
struct DiscreteMeasureData{P <: Union{JuMP.AbstractVariableRef,
                           Vector{<:JuMP.AbstractVariableRef}},
                           N, B <: Union{Float64, Vector{Float64}}
                           } <: AbstractMeasureData
    parameter_refs::P
    coefficients::Vector{Float64}
    supports::Array{Float64, N} # supports are stored column-wise
    label::DataType # label that will used when the supports are added to the model
    weight_function::Function # single support --> weight value
    lower_bounds::B
    upper_bounds::B
    is_expect::Bool
    # scalar constructor
    function DiscreteMeasureData(param_ref::V, coeffs::Vector{<:Real},
                                 supps::Vector{<:Real}, label::DataType,
                                 weight_func::Function,
                                 lower_bound::Real,
                                 upper_bound::Real,
                                 expect::Bool
                                 ) where {V <: JuMP.AbstractVariableRef}
        return new{V, 1, Float64}(param_ref, coeffs, supps, label, weight_func,
                                  lower_bound, upper_bound, expect)
    end
    # multi constructor
    function DiscreteMeasureData(param_refs::Vector{V}, coeffs::Vector{<:Real},
                                 supps::Matrix{<:Real}, label::DataType,
                                 weight_func::Function,
                                 lower_bound::Vector{<:Real},
                                 upper_bound::Vector{<:Real},
                                 expect::Bool
                                 ) where {V <: JuMP.AbstractVariableRef}
        return new{Vector{V}, 2, Vector{Float64}}(param_refs, coeffs, supps,
                                                  label, weight_func, lower_bound,
                                                  upper_bound, expect)
    end
end

"""
    FunctionalDiscreteMeasureData{P <: Union{JuMP.AbstractVariableRef,
                                  Vector{<:JuMP.AbstractVariableRef}},
                                  B <: Union{Float64, Vector{Float64}},
                                  I <: AbstractGenerativeInfo
                                  } <: AbstractMeasureData

A DataType for mutable measure abstraction data where the
abstraction is of the form:
``measure = \\int_{\\tau \\in T} f(\\tau) w(\\tau) d\\tau \\approx \\sum_{i = 1}^N \\alpha_i f(\\tau_i) w(\\tau_i)``.
This abstraction is equivalent to that of [`DiscreteMeasureData`](@ref), but
the difference is that the supports are not fully known at the time of measure
creation. Thus, functions are stored that will be used to generate the
concrete support points ``\\tau_i`` and their coefficients ``\\alpha_i`` when
the measure is evaluated (expanded). These supports are identified/generated
in accordance with the `label` with a gaurantee that at least `num_supports` are
generated. For example, if `label = MCSample` and `num_supports = 100` then
the measure will use all of the supports stored in the `parameter_refs` with the
label `MCSample` and will ensure there are at least 100 are generated. This
type can be used for both 1-dimensional and multi-dimensional measures.

For 1-dimensional measures over independent infinite parameters, the
`generative_supp_info` specifies the info needed to make generative supports based
on those with that exist with `label`. Note that only 1 kind of generative
supports are allowed for each infinite parameter.

**Fields**
- `parameter_refs::P`: The infinite parameter(s) over which the integration occurs.
                     These can be comprised of multiple independent parameters,
                     but dependent parameters cannot be mixed with other types.
- `coeff_function::Function`: Coefficient generation function making ``\\alpha_i``
                              for the above measure abstraction. It should take
                              all the supports as input (formatted as an Array)
                              and return the corresponding vector of coefficients.
- `min_num_supports::Int`: Specifies the minimum number of supports ``\\tau_i``
                       desired in association with `parameter_refs` and `label`.
- `label::DataType`: Label for the support points ``\\tau_i`` which are/will be
                   stored in the infinite parameter(s), stemming from [`AbstractSupportLabel`](@ref).
- `generative_supp_info::I`: Information needed to generate supports based on other
   existing ones.
- `weight_function::Function`: Weighting function ``w`` must map an individual
                              support value to a `Real` scalar value.
- `lower_bounds::B`: Lower bounds in accordance with ``T``, this denotes the
                  intended interval of the measure and should be `NaN` if ignored
- `upper_bounds::B`: Same as above but the upper bounds.
- `is_expect::Bool`: Is this data associated with an expectation call?
"""
struct FunctionalDiscreteMeasureData{P <: Union{JuMP.AbstractVariableRef,
                                     Vector{<:JuMP.AbstractVariableRef}},
                                     B <: Union{Float64, Vector{Float64}},
                                     I <: AbstractGenerativeInfo
                                     } <: AbstractMeasureData
    parameter_refs::P
    coeff_function::Function # supports (excluding generative)--> coefficient vector (includes generative)
    min_num_supports::Int # minimum number of supports
    label::DataType # support label of included supports
    generative_supp_info::I
    weight_function::Function # single support --> weight value
    lower_bounds::B
    upper_bounds::B
    is_expect::Bool
    # scalar constructor
    function FunctionalDiscreteMeasureData(param_ref::V, coeff_func::Function,
                                           num_supps::Int, label::DataType,
                                           gen_info::I,
                                           weight_func::Function,
                                           lower_bound::Real,
                                           upper_bound::Real,
                                           expect::Bool
                                           ) where {V <: JuMP.AbstractVariableRef,
                                                    I <: AbstractGenerativeInfo}
        return new{V, Float64, I}(param_ref, coeff_func, num_supps, label, gen_info,
                                  weight_func, lower_bound, upper_bound, expect)
    end
    # multi constructor
    function FunctionalDiscreteMeasureData(param_refs::Vector{V},
                                           coeff_func::Function,
                                           num_supps::Int, label::DataType,
                                           weight_func::Function,
                                           lower_bound::Vector{<:Real},
                                           upper_bound::Vector{<:Real},
                                           expect::Bool
                                           ) where {V <: JuMP.AbstractVariableRef}
        return new{Vector{V}, Vector{Float64}, NoGenerativeSupports}(param_refs,
                                               coeff_func, num_supps,
                                               label, NoGenerativeSupports(),
                                               weight_func, lower_bound,
                                               upper_bound, expect)
    end
end

# Convenient Dispatch constructor
function FunctionalDiscreteMeasureData(param_refs::Vector{V},
                                           coeff_func::Function,
                                           num_supps::Int, label::DataType,
                                           info::NoGenerativeSupports,
                                           weight_func::Function,
                                           lower_bound::Vector{<:Real},
                                           upper_bound::Vector{<:Real},
                                           expect::Bool
                                           ) where {V <: JuMP.AbstractVariableRef}
    return FunctionalDiscreteMeasureData(param_refs, coeff_func, num_supps,
                                         label, weight_func, lower_bound,
                                         upper_bound, expect)
end

"""
    Measure{T <: JuMP.AbstractJuMPScalar, V <: AbstractMeasureData}

A `DataType` for measure abstractions. The abstraction is determined by `data`
and is enacted on `func` when the measure is evaluated (expended).

**Fields**
- `func::T` The `InfiniteOpt` expression to be measured.
- `data::V` Data of the abstraction as described in a `AbstractMeasureData`
            concrete subtype.
- `object_nums::Vector{Int}`: The parameter object numbers of the evaluated
                              measure expression (i.e., the object numbers of
                              `func` excluding those that belong to `data`).
- `parameter_nums::Vector{Int}`: The parameter numbers that parameterize the
                                 evaluated measure expression. (i.e., the
                                 parameter numbers of `func` excluding those
                                 that belong to `data`).
- `constant_func::Bool`: Indicates if `func` is not parameterized by the infinite
                         parameters in `data`. (i.e., do the object numbers of
                         `func` and `data` have no intersection?) This is useful
                         to enable analytic evaluations if possible.
"""
struct Measure{T <: JuMP.AbstractJuMPScalar, V <: AbstractMeasureData}
    func::T
    data::V
    object_nums::Vector{Int}
    parameter_nums::Vector{Int}
    constant_func::Bool
end

"""
    MeasureData <: AbstractDataObject

A mutable `DataType` for storing [`Measure`](@ref)s and their data.

**Fields**
- `measure::Measure`: The measure structure.
- `name::String`: The base name used for printing `name(meas_expr d(par))`.
- `measure_indices::Vector{MeasureIndex}`: Indices of dependent measures.
- `constraint_indices::Vector{ConstraintIndex}`: Indices of dependent constraints.
- `derivative_indices::Vector{DerivativeIndex}`: Indices of dependent derivatives.
- `in_objective::Bool`: Is this used in objective?
"""
mutable struct MeasureData <: AbstractDataObject
    measure::Measure
    name::String
    measure_indices::Vector{MeasureIndex}
    constraint_indices::Vector{ConstraintIndex}
    derivative_indices::Vector{DerivativeIndex}
    in_objective::Bool
    function MeasureData(measure::Measure, name::String = "measure")
        return new(measure, name, MeasureIndex[], ConstraintIndex[],
                   DerivativeIndex[], false)
    end
end

################################################################################
#                              CONSTRAINT TYPES
################################################################################
"""
    BoundedScalarConstraint{F <: JuMP.AbstractJuMPScalar,
                            S <: MOI.AbstractScalarSet,
                            P <: GeneralVariableRef
                            } <: JuMP.AbstractConstraint

A `DataType` that stores scalar constraints that are defined over a sub-domain
of infinite parameters.

**Fields**
- `func::F` The JuMP object.
- `set::S` The MOI set.
- `bounds::ParameterBounds{P}` Set of valid parameter
    sub-domains that further boundconstraint.
- `orig_bounds::ParameterBounds{P}` Set of the constraint's
    original parameter sub-domains (not considering hold variables)
"""
struct BoundedScalarConstraint{F <: JuMP.AbstractJuMPScalar,
                               S <: MOI.AbstractScalarSet,
                               P <: JuMP.AbstractVariableRef
                               } <: JuMP.AbstractConstraint
    func::F
    set::S
    bounds::ParameterBounds{P}
    orig_bounds::ParameterBounds{P}
end

"""
    ConstraintData <: AbstractDataObject

A mutable `DataType` for storing constraints and their data.

**Fields**
- `constraint::JuMP.AbstractConstraint`: The scalar constraint.
- `object_nums::Vector{Int}`: The object numbers of the parameter objects that the
                              constraint depends on.
- `name::String`: The name used for printing.
- `measure_indices::Vector{MeasureIndex}`: Indices of dependent measures.
- `is_info_constraint::Bool`: Is this is constraint based on variable info (e.g., lower bound)
"""
mutable struct ConstraintData <: AbstractDataObject
    constraint::JuMP.AbstractConstraint
    object_nums::Vector{Int}
    name::String
    measure_indices::Vector{MeasureIndex}
    is_info_constraint::Bool
end

################################################################################
#                                INFINITE MODEL
################################################################################
"""
    InfiniteModel <: JuMP.AbstractModel

A `DataType` for storing all of the mathematical modeling information needed to
model an optmization problem with an infinite-dimensional decision space.

**Fields**
- `independent_params::MOIUC.CleverDict{IndependentParameterIndex, ScalarParameterData{IndependentParameter}}`:
   The independent parameters and their mapping information.
- `dependent_params::MOIUC.CleverDict{DependentParametersIndex, MultiParameterData}`:
   The dependent parameters and their mapping information.
- `finite_params::MOIUC.CleverDict{FiniteParameterIndex, ScalarParameterData{FiniteParameter}}`:
   The finite parameters and their mapping information.
- `name_to_param::Union{Dict{String, AbstractInfOptIndex}, Nothing}`:
   Field to help find a parameter given the name.
- `last_param_num::Int`: The last parameter number to be used.
- `param_object_indices::Vector{Union{IndependentParameterIndex, DependentParametersIndex}}`:
  The collection of parameter object indices in creation order.
- `param_functions::MOIUC.CleverDict{ParameterFunctionIndex, ParameterFunctionData{InfiniteParameterFunction}}`:
  The infinite parameter functions and their mapping information.
- `infinite_vars::MOIUC.CleverDict{InfiniteVariableIndex, <:VariableData{<:InfiniteVariable}}`:
   The infinite variables and their mapping information.
- `reduced_vars::MOIUC.CleverDict{ReducedVariableIndex, <:VariableData{<:ReducedVariable}}`:
   The reduced infinite variables and their mapping information.
- `point_vars::MOIUC.CleverDict{PointVariableIndex, <:VariableData{<:PointVariable}}`:
   The point variables and their mapping information.
- `hold_vars::MOIUC.CleverDict{HoldVariableIndex, <:VariableData{<:HoldVariable}}`:
   The hold variables and their mapping information.
- `name_to_var::Union{Dict{String, AbstractInfOptIndex}, Nothing}`:
   Field to help find a variable given the name.
- `has_hold_bounds::Bool`:
   Does any variable have parameter bounds?
- `derivatives::MOIUC.CleverDict{DerivativeIndex, <:VariableData{<:Derivative}}`:
  The derivatives and their mapping information.
- `deriv_lookup::Dict{<:Tuple, DerivativeIndex}`: Map derivative variable-parameter
  pairs to a derivative index to prevent duplicates.
- `measures::MOIUC.CleverDict{MeasureIndex, MeasureData}`:
   The measures and their mapping information.
- `integral_defaults::Dict{Symbol}`:
   The default keyword arguments for [`integral`](@ref).
- `constraints::MOIUC.CleverDict{ConstraintIndex, ConstraintData}`:
   The constraints and their mapping information.
- `name_to_constr::Union{Dict{String, ConstraintIndex}, Nothing}`:
   Field to help find a constraint given the name.
- `objective_sense::MOI.OptimizationSense`: Objective sense.
- `objective_function::JuMP.AbstractJuMPScalar`: Finite scalar function.
- `objective_has_measures::Bool`: Does the objective contain measures?
- `obj_dict::Dict{Symbol, Any}`: Store Julia symbols used with `InfiniteModel`
- `optimizer_constructor`: MOI optimizer constructor (e.g., Gurobi.Optimizer).
- `optimizer_model::JuMP.Model`: Model used to solve `InfiniteModel`
- `ready_to_optimize::Bool`: Is the optimizer_model up to date.
- `ext::Dict{Symbol, Any}`: Store arbitrary extension information.
"""
mutable struct InfiniteModel <: JuMP.AbstractModel
    # Parameter Data
    independent_params::MOIUC.CleverDict{IndependentParameterIndex, ScalarParameterData{<:IndependentParameter}}
    dependent_params::MOIUC.CleverDict{DependentParametersIndex, MultiParameterData}
    finite_params::MOIUC.CleverDict{FiniteParameterIndex, ScalarParameterData{FiniteParameter}}
    name_to_param::Union{Dict{String, AbstractInfOptIndex}, Nothing}
    last_param_num::Int
    param_object_indices::Vector{Union{IndependentParameterIndex, DependentParametersIndex}}
    param_functions::MOIUC.CleverDict{ParameterFunctionIndex, <:ParameterFunctionData}

    # Variable Data
    infinite_vars::MOIUC.CleverDict{InfiniteVariableIndex, <:VariableData{<:InfiniteVariable}}
    reduced_vars::MOIUC.CleverDict{ReducedVariableIndex, <:VariableData{<:ReducedVariable}}
    point_vars::MOIUC.CleverDict{PointVariableIndex, <:VariableData{<:PointVariable}}
    hold_vars::MOIUC.CleverDict{HoldVariableIndex, <:VariableData{<:HoldVariable}}
    name_to_var::Union{Dict{String, AbstractInfOptIndex}, Nothing}
    has_hold_bounds::Bool

    # Derivative Data
    derivatives::MOIUC.CleverDict{DerivativeIndex, <:VariableData{<:Derivative}}
    deriv_lookup::Dict{<:Tuple, DerivativeIndex}

    # Measure Data
    measures::MOIUC.CleverDict{MeasureIndex, MeasureData}

    # Constraint Data
    constraints::MOIUC.CleverDict{ConstraintIndex, ConstraintData}
    name_to_constr::Union{Dict{String, ConstraintIndex}, Nothing}

    # Objective Data
    objective_sense::MOI.OptimizationSense
    objective_function::JuMP.AbstractJuMPScalar
    objective_has_measures::Bool

    # Objects
    obj_dict::Dict{Symbol, Any}

    # Optimize Data
    optimizer_constructor::Any
    optimizer_model::JuMP.Model
    ready_to_optimize::Bool

    # Extensions
    ext::Dict{Symbol, Any}
end

"""
    InfiniteModel([optimizer_constructor];
                  [OptimizerModel::Function = TranscriptionModel,
                  caching_mode::MOIU.CachingOptimizerMode = MOIU.AUTOMATIC,
                  bridge_constraints::Bool = true, optimizer_model_kwargs...])

Return a new infinite model where an optimizer is specified if an
`optimizer_constructor` is given. The optimizer
can also later be set with the [`JuMP.set_optimizer`](@ref) call. By default
the `optimizer_model` data field is initialized with a
[`TranscriptionModel`](@ref), but a different type of model can be assigned via
[`set_optimizer_model`](@ref) as can be required by extensions.

**Example**
```jldoctest
julia> using InfiniteOpt, JuMP, Ipopt;

julia> model = InfiniteModel()
An InfiniteOpt Model
Feasibility problem with:
Finite Parameters: 0
Infinite Parameters: 0
Variables: 0
Measures: 0
Derivatives: 0
Optimizer model backend information:
Model mode: AUTOMATIC
CachingOptimizer state: NO_OPTIMIZER
Solver name: No optimizer attached.

julia> model = InfiniteModel(Ipopt.Optimizer)
An InfiniteOpt Model
Feasibility problem with:
Finite Parameters: 0
Infinite Parameters: 0
Variables: 0
Measures: 0
Derivatives: 0
Optimizer model backend information:
Model mode: AUTOMATIC
CachingOptimizer state: EMPTY_OPTIMIZER
Solver name: Ipopt
```
"""
function InfiniteModel(; OptimizerModel::Function = TranscriptionModel,
                       kwargs...)::InfiniteModel
    return InfiniteModel(# Parameters
                         MOIUC.CleverDict{IndependentParameterIndex, ScalarParameterData{<:IndependentParameter}}(),
                         MOIUC.CleverDict{DependentParametersIndex, MultiParameterData}(),
                         MOIUC.CleverDict{FiniteParameterIndex, ScalarParameterData{FiniteParameter}}(),
                         nothing, 0,
                         Union{IndependentParameterIndex, DependentParametersIndex}[],
                         MOIUC.CleverDict{ParameterFunctionIndex, ParameterFunctionData{InfiniteParameterFunction{GeneralVariableRef}}}(),
                         # Variables
                         MOIUC.CleverDict{InfiniteVariableIndex, VariableData{InfiniteVariable{GeneralVariableRef}}}(),
                         MOIUC.CleverDict{ReducedVariableIndex, VariableData{ReducedVariable{GeneralVariableRef}}}(),
                         MOIUC.CleverDict{PointVariableIndex, VariableData{PointVariable{GeneralVariableRef}}}(),
                         MOIUC.CleverDict{HoldVariableIndex, VariableData{HoldVariable{GeneralVariableRef}}}(),
                         nothing, false,
                         # Derivatives
                         MOIUC.CleverDict{DerivativeIndex, VariableData{Derivative{GeneralVariableRef}}}(),
                         Dict{Tuple{GeneralVariableRef, GeneralVariableRef}, DerivativeIndex}(),
                         # Measures
                         MOIUC.CleverDict{MeasureIndex, MeasureData}(),
                         # Constraints
                         MOIUC.CleverDict{ConstraintIndex, ConstraintData}(),
                         nothing,
                         # Objective
                         MOI.FEASIBILITY_SENSE,
                         zero(JuMP.GenericAffExpr{Float64, GeneralVariableRef}),
                         false,
                         # Object dictionary
                         Dict{Symbol, Any}(),
                         # Optimize data
                         nothing, OptimizerModel(;kwargs...), false,
                         # Extensions
                         Dict{Symbol, Any}()
                         )
end

## Set the optimizer_constructor depending on what it is
# MOI.OptimizerWithAttributes
function _set_optimizer_constructor(model::InfiniteModel,
                                    constructor::MOI.OptimizerWithAttributes)::Nothing
    model.optimizer_constructor = constructor.optimizer_constructor
    return
end

# No attributes
function _set_optimizer_constructor(model::InfiniteModel, constructor)::Nothing
    model.optimizer_constructor = constructor
    return
end

# Dispatch for InfiniteModel call with optimizer constructor
function InfiniteModel(optimizer_constructor;
                       OptimizerModel::Function = TranscriptionModel,
                       kwargs...)::InfiniteModel
    model = InfiniteModel()
    model.optimizer_model = OptimizerModel(optimizer_constructor; kwargs...)
    _set_optimizer_constructor(model, optimizer_constructor)
    return model
end

# Define basic InfiniteModel extensions
Base.broadcastable(model::InfiniteModel) = Ref(model)
JuMP.object_dictionary(model::InfiniteModel)::Dict{Symbol, Any} = model.obj_dict

# Define basic accessors
_last_param_num(model::InfiniteModel)::Int = model.last_param_num
_param_object_indices(model::InfiniteModel) = model.param_object_indices

################################################################################
#                             OBJECT REFERENCES
################################################################################
"""
    GeneralVariableRef <: JuMP.AbstractVariableRef

A `DataType` that serves as the principal variable reference in `InfiniteOpt`
for building variable expressions. It contains the needed information to
create a variable type specifc reference (e.g., [`InfiniteVariableRef`](@ref))
via [`dispatch_variable_ref`](@ref) to obtain the correct subtype of
[`DispatchVariableRef`](@ref) based off of `index_type`. This allows us to
construct expressions using concrete containers unlike previous versions of
`InfiniteOpt` which provides us a significant performance boost.

**Fields**
- `model::InfiniteModel`: Infinite model.
- `raw_index::Int64`: The raw index to be used in the `index_type` constructor.
- `index_type::DataType`: The concrete [`AbstractInfOptIndex`](@ref) type/constructor.
- `param_index::Int`: The index of a parameter in [`DependentParameters`](@ref).
  This is ignored for other variable types.
"""
struct GeneralVariableRef <: JuMP.AbstractVariableRef
    model::InfiniteModel
    raw_index::Int64
    index_type::DataType
    param_index::Int # for DependentParameterRefs
    function GeneralVariableRef(model::InfiniteModel, raw_index,
                               index_type::DataType, param_index::Int = -1)
       return new(model, Int64(raw_index), index_type, param_index)
    end
end

"""
    DispatchVariableRef <: JuMP.AbstractVariableRef

An abstract type for variable references that are created from
[`GeneralVariableRef`](@ref)s and are used to dispatch to the appropriate
methods for that particular variable/parameter/measure type.
"""
abstract type DispatchVariableRef <: JuMP.AbstractVariableRef end

"""
    IndependentParameterRef <: DispatchVariableRef

A `DataType` for independent infinite parameters references that parameterize
infinite variables.

**Fields**
- `model::InfiniteModel`: Infinite model.
- `index::IndependentParameterIndex`: Index of the parameter in model.
"""
struct IndependentParameterRef <: DispatchVariableRef
    model::InfiniteModel
    index::IndependentParameterIndex
end

"""
    DependentParameterRef <: DispatchVariableRef

A `DataType` for dependent infinite parameter references that parameterize
infinite variables.

**Fields**
- `model::InfiniteModel`: Infinite model.
- `index::DependentParameterIndex`: Index of the dependent parameter.
"""
struct DependentParameterRef <: DispatchVariableRef
    model::InfiniteModel
    index::DependentParameterIndex
end

"""
    ParameterFunctionRef <: DispatchVariableRef

A `DataType` for infinite parameter function references.

**Fields**
- `model::InfiniteModel`: Infinite model.
- `index::ParameterFunctionIndex`: Index of the infinite parameter function.
"""
struct ParameterFunctionRef <: DispatchVariableRef
    model::InfiniteModel
    index::ParameterFunctionIndex
end

"""
    InfiniteVariableRef <: DispatchVariableRef

A `DataType` for untranscripted infinite dimensional variable references (e.g.,
second stage variables, time dependent variables).

**Fields**
- `model::InfiniteModel`: Infinite model.
- `index::InfiniteVariableIndex`: Index of the variable in model.
"""
struct InfiniteVariableRef <: DispatchVariableRef
    model::InfiniteModel
    index::InfiniteVariableIndex
end

"""
    ReducedVariableRef <: DispatchVariableRef

A `DataTyp`e for partially transcripted infinite dimensional variable references.
This is used to expand measures that contain infinite variables that are not
fully transcripted by the measure.

**Fields**
- `model::InfiniteModel`: Infinite model.
- `index::ReducedVariableIndex`: Index of the variable in model.
"""
struct ReducedVariableRef <: DispatchVariableRef
    model::InfiniteModel
    index::ReducedVariableIndex
end

"""
    DerivativeRef <: DispatchVariableRef

A `DataType` for untranscripted derivative references.

**Fields**
- `model::InfiniteModel`: Infinite model.
- `index::DerivativeIndex`: Index of the derivative in model.
"""
struct DerivativeRef <: DispatchVariableRef
    model::InfiniteModel
    index::DerivativeIndex
end

"""
    MeasureFiniteVariableRef <: DispatchVariableRef

An abstract type to define finite variable and measure references.
"""
abstract type MeasureFiniteVariableRef <: DispatchVariableRef end

"""
    MeasureRef <: FiniteVariableRef

A `DataType` for referring to measure abstractions.

**Fields**
- `model::InfiniteModel`: Infinite model.
- `index::MeasureIndex`: Index of the measure in model.
"""
struct MeasureRef <: MeasureFiniteVariableRef
    model::InfiniteModel
    index::MeasureIndex
end

"""
    FiniteVariableRef <: MeasureFiniteVariableRef

An abstract type to define new finite variable references.
"""
abstract type FiniteVariableRef <: MeasureFiniteVariableRef end

"""
    PointVariableRef <: FiniteVariableRef

A `DataType` for variables defined at a transcipted point (e.g., second stage
variable at a particular scenario, dynamic variable at a discretized time point).

**Fields**
- `model::InfiniteModel`: Infinite model.
- `index::PointVariableIndex`: Index of the variable in model.
"""
struct PointVariableRef <: FiniteVariableRef
    model::InfiniteModel
    index::PointVariableIndex
end

"""
    HoldVariableRef <: FiniteVariableRef

A `DataType` for finite fixed variable references (e.g., first stage variables,
steady-state variables).

**Fields**
- `model::InfiniteModel`: Infinite model.
- `index::HoldVariableIndex`: Index of the variable in model.
"""
struct HoldVariableRef <: FiniteVariableRef
    model::InfiniteModel
    index::HoldVariableIndex
end

"""
    FiniteParameterRef <: FiniteVariableRef

A `DataType` for finite parameters references who are replaced with their values
at the transcription step.

**Fields**
- `model::InfiniteModel`: Infinite model.
- `index::FiniteParameterIndex`: Index of the parameter in model.
"""
struct FiniteParameterRef <: FiniteVariableRef
    model::InfiniteModel
    index::FiniteParameterIndex
end

## Define convenient aliases
const DecisionVariableRef = Union{InfiniteVariableRef, ReducedVariableRef,
                                  PointVariableRef, HoldVariableRef,
                                  DerivativeRef}

const UserDecisionVariableRef = Union{InfiniteVariableRef, PointVariableRef,
                                      HoldVariableRef, DerivativeRef}

const ScalarParameterRef = Union{IndependentParameterRef, FiniteParameterRef}

"""
    InfOptConstraintRef

A `DataType` for constraints that are in `InfiniteModel`s

**Fields**
- `model::InfiniteModel`: Infinite model.
- `index::ConstraintIndex`: Index of the constraint in model.
"""
struct InfOptConstraintRef
    model::InfiniteModel
    index::ConstraintIndex
end

# Make dumby model type for calling @expression
struct _DumbyModel <: JuMP.AbstractModel end
const _Model = _DumbyModel()

################################################################################
#                            PARAMETER BOUND METHODS
################################################################################
## Modify parameter dictionary to expand any multidimensional parameter keys
# Case where dictionary is already in correct form
function _expand_parameter_tuple(
    param_bounds::NTuple{N, Pair{GeneralVariableRef, IntervalSet}}
    )::Dict{GeneralVariableRef, IntervalSet} where {N}
    return Dict(param_bounds...)
end

# Case where dictionary contains vectors
function _expand_parameter_dict(
    param_bounds::NTuple{N, Pair{<:Union{GeneralVariableRef, AbstractArray{<:GeneralVariableRef}}, IntervalSet}}
    )::Dict{GeneralVariableRef, IntervalSet} where {N}
    # Initialize new dictionary
    new_dict = Dict{GeneralVariableRef, IntervalSet}()
    # Find vector keys and expand
    for (key, set) in param_bounds
        # expand over the array of parameters if this is
        if isa(key, AbstractArray)
            for param in key
                new_dict[param] = set
            end
        # otherwise we have parameter reference
        else
            new_dict[key] = set
        end
    end
    return new_dict
end

# Case where dictionary contains vectors
function _expand_parameter_dict(param_bounds::Tuple)
    error("Invalid parameter bound dictionary format.")
end

# Constructor for expanding array parameters
function ParameterBounds(intervals::NTuple{N, Pair}
    )::ParameterBounds{GeneralVariableRef} where {N}
    return ParameterBounds(_expand_parameter_dict(intervals))
end

# Default method
function ParameterBounds()::ParameterBounds{GeneralVariableRef}
    return ParameterBounds(Dict{GeneralVariableRef, IntervalSet}())
end

# Make dictionary accessor
function intervals(pb::ParameterBounds{P})::Dict{P, IntervalSet} where {P}
    return pb.intervals
end

# Extend simple 1 argument Base dispatches
for op = (:length, :isempty, :keys, :iterate)
    @eval Base.$op(bounds::ParameterBounds) = $op(intervals(bounds))
end

# Extend simple 2 argument Base dispatches where the second is arbitrary
for op = (:getindex, :haskey, :iterate)
    @eval Base.$op(bounds::ParameterBounds, arg) = $op(intervals(bounds), arg)
end

# Extend Base.:(==)
function Base.:(==)(bounds1::ParameterBounds, bounds2::ParameterBounds)::Bool
    return intervals(bounds1) == intervals(bounds2)
end

# Extend Base.copy
Base.copy(bounds::ParameterBounds) = ParameterBounds(copy(intervals(bounds)))

# Extend Base.setindex!
function Base.setindex!(pb::ParameterBounds{P}, value::IntervalSet,
                        index::P)::IntervalSet where {P}
    return intervals(pb)[index] = value
end

# Extend Base.delete!
function Base.delete!(pb::ParameterBounds{P}, key::P)::ParameterBounds{P} where {P}
    delete!(intervals(pb), key)
    return pb
end

# Extend Base.merge
function Base.merge(pb1::ParameterBounds{P},
                    pb2::ParameterBounds{P})::ParameterBounds{P} where {P}
    new_dict = merge(intervals(pb1), intervals(pb2))
    return ParameterBounds(new_dict)
end

# Extend Base.merge!
function Base.merge!(pb1::ParameterBounds{P},
                     pb2::ParameterBounds{P})::ParameterBounds{P} where {P}
    merge!(intervals(pb1), intervals(pb2))
    return pb1
end

# Extend Base.filter
function Base.filter(f::Function,
                     pb::ParameterBounds{P})::ParameterBounds{P} where {P}
    new_dict = filter(f, intervals(pb))
    return ParameterBounds(new_dict)
end


text:
# This definitely needs cleaning
using OrdinaryDiffEq, DiffEqDevTools, Test, Random
using DiffEqProblemLibrary.ODEProblemLibrary: importodeproblems; importodeproblems()
import DiffEqProblemLibrary.ODEProblemLibrary: prob_ode_linear, prob_ode_2Dlinear
probArr = Vector{ODEProblem}(undef, 2)
probArr[1] = prob_ode_linear

probArr[2] = prob_ode_2Dlinear
Random.seed!(100)
## Convergence Testing
dts = 1 .//2 .^(8:-1:4)
testTol = 0.2

for i = 1:2
  global dts
  prob = probArr[i]
  sim = test_convergence(dts,prob,RichardsonEuler())
  @test sim.𝒪est[:final] ≈ 4 atol=testTol
end

ext: jl


text:
create(c::Client, ::Type{Guild}; kwargs...) = create_guild(c; kwargs...)
create(c::Client, g::Guild, s::ScheduledEvent; kwargs...) = create_guild_scheduled_event(c, g, s; kwargs...)
create(c::Client, g::Guild; kwargs...) = create(c, g, ScheduledEvent(; kwargs...))

retrieve(c::Client, ::Type{Guild}, guild::Integer) = get_guild(c, guild)
retrieve(c::Client, ::Type{Guild}; kwargs...) = get_current_user_guilds(c; kwargs...)
retrieve(c::Client, g::Guild, s::Snowflake; kwargs...) = get_guild_scheduled_event(c, g, s; kwargs...)

update(c::Client, g::AbstractGuild; kwargs...) = modify_guild(c, g.id; kwargs...)
# TODO: kwargs version of this that auto updates ScheduledEvent fields
# for scheduledevents get guild from ScheduledEvent.guild_id
update(c::Client, g::Guild, s::ScheduledEvent; kwargs...) = modify_guild_scheduled_event(c, g, s; kwargs...)

delete(c::Client, g::AbstractGuild) = delete_guild(c, g.id)


text:
# ------------------------------------- Atmosphere -------------------------------------
# Define abstract type and interface.
abstract type Atmosphere end

"""
    get_temperature(a::Atmosphere)

Get atmosphere temperature (K).
"""
get_temperature(a::Atmosphere) = a.temperature

"""
    get_pressure(a::Atmosphere)

Get atmosphere pressure (Pa).
"""
get_pressure(a::Atmosphere) = a.pressure

"""
    get_density(a::Atmosphere)

Get atmosphere density (kg/m³).
"""
get_density(a::Atmosphere) = a.density

"""
    get_sound_velocity(a::Atmospere)

Get sound velocity (m/s).
"""
get_sound_velocity(a::Atmosphere) = a.sound_velocity

"""
    get_height(a::Atmospere)

Get geopotential height (m).
"""
get_height(a::Atmosphere) = a.height

# --------------------------------- F16StevensAtmosphere ---------------------------------
"""
    F16StevensAtmosphere{T<:Number}<:Atmosphere

ISA atmosphere model as coded in [1].

# Fields
- `temperature::T`: temperature (K).
- `pressure::T`: pressure (Pa).
- `density::T`: density (kg/m³).
- `sound_velocity::T`: sound velocity (m/s).
- `height::T`: height (m).

# Notes
- Differs from [`F16StevensAtmosphere`](@ref). Check `FlightMechanicsUtils.jl` for more
  information.

# References
1. Stevens, B. L., Lewis, F. L., (1992). Aircraft control and simulation: dynamics, controls
   design, and autonomous systems. John Wiley & Sons. Section A.6 (page 715)
"""
struct F16StevensAtmosphere{T<:Number}<:Atmosphere
    """Temperature (K)"""
    temperature::T
    """Pressure (Pa)"""
    pressure::T
    """Density (kg/m³)"""
    density::T
    """Sound velocity (m/s)"""
    sound_velocity::T
    """Geopotential Height (m)"""
    height::T
end

"""
    F16StevensAtmosphere(h)

Calculate atmosphere for given height, `h` (m).

# Returns
- `F16StevensAtmosphere`
"""
function F16StevensAtmosphere(h)
    T, ρ, a, p = F16Stevens.atmosphere_f16(h)
    return F16StevensAtmosphere(T, p, ρ, a, h)
end

# ---------------------------------------- ISA1976 ----------------------------------------
"""
    ISA1976{T<:Number}<:Atmosphere

ISA 1976 atmosphere implementation.

# Fields
- `temperature::T`: temperature (K).
- `pressure::T`: pressure (Pa).
- `density::T`: density (kg/m³).
- `sound_velocity::T`: sound velocity (m/s).
- `height::T`: height (m).
"""
struct ISA1976{T<:Number}<:Atmosphere
    """Temperature (K)"""
    temperature::T
    """Pressure (Pa)"""
    pressure::T
    """Density (kg/m³)"""
    density::T
    """Sound velocity (m/s)"""
    sound_velocity::T
    """Geopotential Height (m)"""
    height::T
end

"""
    ISA1976(h)

Calculate atmosphere for given height, `h` (m).

# Returns
- `ISA1976`
"""
ISA1976(h) = ISA1976(atmosphere_isa(h)..., h)


text:
mutable struct generic_factor_t <: LCMCore.LCMType
  utime::Int64

  numvariables::Int32
  variablelabels::Vector{String}

  factortype::String

  # additional payload for generic user
  datadescription::String
  datalength::Int32
  data::Vector{UInt8}
end

@lcmtypesetup(generic_factor_t,
  data => (datalength,),
  variablelabels => (numvariables,),
)


text:
using SeaweedFSClient
using Test

fid = ()

function test_create()
    global fid
    fid = create((hostname="localhost", port=9333))
    fid !== nothing && fid.status == 200
end

function test_save()
    global fid
    r = save(fid, filedata("123456"))
    r !== nothing && r.status == 201
end

function test_load()
    global fid
    r = String(load(fid, range=0:2))
    r == "123"
end

function test_delete()
    global fid
    r = delete(fid)
    r !== nothing && r.status == 202
end

@testset "SeaweedFSClient.jl" begin
    if isdir("/trip") # then localhost and SeaweedFS server is present
        @test test_create()
        @test test_save()
        @test test_load()
        @test test_delete()
    end
end


text:
"""
    has_consistent_distributions(m::Union)

Return true if no problems are found in the distributions for a discrete problem. Print information and return false if problems are found.

Tests whether
- All probabilities are positive
- Probabilities for all distributions sum to 1
- All items with positive probability are in the support
"""
function has_consistent_distributions end

function has_consistent_distributions(m::POMDP)
    return has_consistent_initial_distribution(m) &&
        has_consistent_transition_distributions(m) &&
        has_consistent_observation_distributions(m)
end

function has_consistent_distributions(m::MDP)
    return has_consistent_initial_distribution(m) &&
        has_consistent_transition_distributions(m)
end

"""
    has_consistent_transition_distributions(m)

Return true if no problems are found in the transition distributions for a discrete problem. Print information and return false if problems are found.

See `has_consistent_distributions` for information on what checks are performed.
"""
function has_consistent_transition_distributions(m::Union{MDP,POMDP})
    ok = true
    for s in states(m)
        if !isterminal(m, s)
            for a in actions(m)
                d = transition(m, s, a)
                psum = 0.0
                sup = Set(support(d))
                for sp in sup
                    if pdf(d, sp) > 0.0 && !(sp in states(m))
                        @warn "sp in support(transition(m, s, a)), but not in states(m)" s a sp
                        ok = false
                    end
                end
                for sp in states(m)
                    p = pdf(d, sp)
                    if p < 0.0
                        @warn "Transition probability negative ($p < 0.0)." s a sp
                        ok = false
                    elseif p > 0.0 && !(sp in sup)
                        @warn "State $sp with probability $p is not in support" s a
                        ok = false
                    end
                    psum += p
                end
                if !isapprox(psum, 1.0)
                    @warn "Transition probabilities sum to $psum, not 1." s a
                    ok = false
                end
            end
        end
    end
    return ok
end

"""
    has_consistent_observation_distributions(m)

Return true if no problems are found in the observation distributions for a discrete POMDP. Print information and return false if problems are found.

See `has_consistent_distributions` for information on what checks are performed.
"""
function has_consistent_observation_distributions(m::POMDP)
    ok = true
    for s in states(m)
        if !isterminal(m, s)
            for a in actions(m)
                for sp in states(m)
                    obs = observation(m, s, a, sp)
                    psum = 0.0
                    sup = Set(support(obs))
                    for o in sup
                        if pdf(obs, o) > 0.0 && !(o in observations(m))
                            @warn "o in support(observation(m, s, a, sp)), but not in observations(m)" s a sp o
                            ok = false
                        end
                    end
                    for o in observations(m)
                        p = pdf(obs, o)
                        if p < 0.0
                            @warn "Observation probability negative ($p < 0.0)." s a sp o
                            ok = false
                        elseif p > 0.0 && !(o in sup)
                            @warn "Observation $o with probability $p is not in support." s a sp
                            ok = false
                        end
                        psum += p
                    end
                    if !isapprox(psum, 1.0)
                        @warn "Observation probabilities sum to $psum, not 1." s a sp
                        ok = false
                    end
                end
            end
        end
    end
    return ok
end

"""
    has_consistent_initial_distribution(m)

Return true if no problems are found with the initial state distribution for a discrete problem. Print information and return false if problems are found.

See `has_consistent_distributions` for information on what checks are performed.
"""
function has_consistent_initial_distribution(m::Union{MDP,POMDP})
    ok = true
    d = initialstate(m)
    sup = Set(support(d))
    psum = 0.0
    for s in states(m)
        p = pdf(d, s)
        psum += p
        if p < 0.0
            @warn "Initial state probability negative ($p < 0.0)" s
            ok = false
        elseif p > 0.0 && !(s in sup)
            @warn "State $s with probability $p is not in initial distribution support."
            ok = false
        end
    end
    if !isapprox(psum, 1.0)
        @warn "Initial state probabilities sum to $psum, not 1."
        ok = false
    end
    return ok
end

"""
   probability_check(pomdp::POMDP)
Checks if the transition and observation function of the discrete `pomdp`
have probability mass that sums up to unity for all state-action pairs.
"""
function probability_check(pomdp::POMDP)
    @warn "probability_check(m) is deprecated. Use @test has_consistent_distributions(m) instead."
    obs_prob_consistency_check(pomdp)
    trans_prob_consistency_check(pomdp)
end

"""
    obs_prob_consistency_check(pomdp::POMDP)
Checks if the observation function of the discrete `pomdp`
has probability mass that sums up to unity for all state-action pairs.
"""
function obs_prob_consistency_check(pomdp::POMDP)
    @warn "obs_prob_consistency_check(m) is deprecated. Use @test has_consistent_observation_distributions(m) instead."
    # initalize space
    sspace = states(pomdp)
    aspace = actions(pomdp)
    ospace = observations(pomdp)
    # iterate through all s-a pairs
    for s in sspace
        for a in aspace
            obs = observation(pomdp, a, s)
            psum = 0.0
            for o in ospace
                p = pdf(obs, o)
                @assert p ≥ 0 "Probability is negative for state: $s, action: $a, observation: $o"
                psum += p
            end
            @assert isapprox(psum, 1.0) "Observation probability does not sum to unity for state: $s, action: $a"
        end
    end
end

"""
    trans_prob_consistency_check(pomdp::Union{MDP, POMDP})
Checks if the transition function of the discrete problem
has probability mass that sums up to unity for all state-action pairs.
"""
function trans_prob_consistency_check(pomdp::Union{MDP, POMDP})
    @warn "trans_prob_consistency_check(m) is deprecated. Use @test has_consistent_transition_distributions(m) instead."
    # initalize space
    sspace = states(pomdp)
    aspace = actions(pomdp)
    # iterate through all s-a pairs
    for s in sspace
        for a in aspace
            tran = transition(pomdp, s, a)
            psum = 0.0
            for sp in sspace
                p = pdf(tran, sp)
                @assert p ≥ 0 "Probability is negative for state: $s, action: $a, next state: $sp"
                psum += p
            end
            @assert isapprox(psum, 1.0) "Transition probability does not sum to unity for state: $s, action: $a"
        end
    end
end


text:
using Formats
using Test

datapath = joinpath(@__DIR__, "..", "data")

struct TestIO1 <: Formats.FormatHandler end
struct TestIO2 <: Formats.FormatHandler end
struct TestIO3 <: Formats.FormatHandler end

testio1 = TestIO1()
testio2 = TestIO2()
testio3 = TestIO3()

function Base.read(::TestIO1, ::MIME"text/plain", io::IO)
    chars = Char[]
    while true
        char = read(io, Char)
        if char == '/'
            break
        end
        push!(chars, char)
    end
    String(chars)
end

function Base.write(::TestIO1, ::MIME"text/plain", io::IO, x)
    write(io, x)
    write(io, "/")
end

@testset "Formats" begin

    @testset "Specify" begin
        Formats.newregistry() do
            f1 = specify("kitten.png", "image/png")
            @test isspecified(f1)
            @test !isinferred(f1)
            @test !isunknown(f1)
            @test !isambiguous(f1)
            @test getformat(f1) == "image/png"
            @test getcoding(f1) == nothing
            @test resource(f1) == "kitten.png"

            f2 = specify("oldcat.dat", "image/bmp", "application/gzip")
            @test isspecified(f2)
            @test !isinferred(f2)
            @test !isunknown(f2)
            @test !isambiguous(f2)
            @test getformat(f2) == "image/bmp"
            @test getcoding(f2) == "application/gzip"
        end
    end

    @testset "Guess from filename" begin
        Formats.newregistry() do
            f1 = infer("kitten.png")
            @test !isspecified(f1)
            @test !isinferred(f1)
            @test isunknown(f1)
            @test !isambiguous(f1)
            @test_throws ErrorException getformat(f1)
            @test getcoding(f1) == nothing

            Formats.addformat("image/png")
            Formats.addextension("image/png", ".png")
            f2 = infer("kitten.png")
            @test !isspecified(f2)
            @test isinferred(f2)
            @test !isunknown(f2)
            @test !isambiguous(f2)
            @test getformat(f2) == "image/png"
            @test getcoding(f2) == nothing

            f3 = infer("kitten.png.gz")
            @test !isspecified(f3)
            @test !isinferred(f3)
            @test isunknown(f3)
            @test !isambiguous(f3)
            @test_throws ErrorException getformat(f3)
            @test getcoding(f3) == nothing

            Formats.addcoding("application/gzip")
            Formats.addextension("application/gzip", ".gz")
            f4 = infer("kitten.png.gz")
            @test !isspecified(f4)
            @test isinferred(f4)
            @test !isunknown(f4)
            @test !isambiguous(f4)
            @test getformat(f4) == "image/png"
            @test getcoding(f4) == "application/gzip"

            Formats.addformat("game/pong")
            Formats.addextension("game/pong", ".png")
            f5 = infer("kitten.png")
            @test !isspecified(f5)
            @test isinferred(f5)
            @test !isunknown(f5)
            @test isambiguous(f5)
            getformat(f5)
            @test getcoding(f5) == nothing
        end
    end

    @testset "Guess from IOStream" begin
        Formats.newregistry() do
            io = open("$(datapath)/kitten.png")

            f1 = infer(io)
            @test !isspecified(f1)
            @test !isinferred(f1)
            @test isunknown(f1)
            @test !isambiguous(f1)
            @test_throws ErrorException getformat(f1)
            @test getcoding(f1) == nothing

            Formats.addformat("image/png")
            Formats.addsignature("image/png",
                    [0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a])
            Formats.addsignature("image/png", "fake!")
            f2 = infer(io)
            @test !isspecified(f2)
            @test isinferred(f2)
            @test !isunknown(f2)
            @test !isambiguous(f2)
            @test getformat(f2) == "image/png"
            @test getcoding(f2) == nothing

            Formats.addformat("game/pong")
            Formats.addsignature("game/pong",
                    [0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a])
            f3 = infer(io)
            @test !isspecified(f3)
            @test isinferred(f3)
            @test !isunknown(f3)
            @test isambiguous(f3)
            getformat(f3)
            @test getcoding(f3) == nothing
        end
    end

    @testset "Guess from Formatted object" begin
        Formats.newregistry() do
            Formats.addformat("image/png")
            Formats.addsignature("image/png",
                    [0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a])
            f1 = infer("$(datapath)/kitten.png")

            f2 = open(f1)
            @test !isspecified(f2)
            @test !isinferred(f2)
            @test isunknown(f2)
            @test !isambiguous(f2)
            @test_throws ErrorException getformat(f2)
            @test getcoding(f2) == nothing

            f3 = infer(f2)
            @test !isspecified(f3)
            @test isinferred(f3)
            @test !isunknown(f3)
            @test !isambiguous(f3)
            @test getformat(f3) == "image/png"
            @test getcoding(f3) == nothing
        end
    end

    @testset "Passthrough with formatted" begin
        Formats.newregistry() do
            f1 = infer("kitten.png")
            Formats.addformat("image/png")
            Formats.addextension("image/png", ".png")
            Formats.addsignature("image/png",
                    [0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a])

            f2 = formatted(f1)
            @test !isspecified(f2)
            @test !isinferred(f2)
            @test isunknown(f2)
            @test !isambiguous(f2)
            @test_throws ErrorException getformat(f2)
            @test getcoding(f2) == nothing

            f3 = formatted("kitten.png")
            @test !isspecified(f3)
            @test isinferred(f3)
            @test !isunknown(f3)
            @test !isambiguous(f3)
            @test getformat(f3) == "image/png"
            @test getcoding(f3) == nothing

            f4 = formatted(open("$(datapath)/kitten.png"))
            @test !isspecified(f4)
            @test isinferred(f4)
            @test !isunknown(f4)
            @test !isambiguous(f4)
            @test getformat(f4) == "image/png"
            @test getcoding(f4) == nothing
        end
    end

    @testset "Resolving reader" begin
        Formats.newregistry() do
            Formats.addformat("image/png")
            mime = MIME"image/png"()
            Formats.addreader("image/png", testio1)
            @test Formats.resolvereader(mime) == testio1
            Formats.addreader("image/png", testio2)
            Formats.resolvereader(mime)
            Formats.preferreader(testio1, "image/png")
            @test Formats.resolvereader(mime) == testio1
            Formats.preferreader(testio2, "image/png")
            @test Formats.resolvereader(mime) == testio2
            Formats.addreader("image/png", testio3)
            @test Formats.resolvereader(mime) == testio2
            Formats.preferreader(testio3)
            @test Formats.resolvereader(mime) == testio3
        end
    end

    @testset "Resolving writer" begin
        Formats.newregistry() do
            Formats.addformat("image/png")
            mime = MIME"image/png"()
            Formats.addwriter("image/png", testio1)
            @test Formats.resolvewriter(mime) == testio1
            Formats.addwriter("image/png", testio2)
            Formats.resolvewriter(mime)
            Formats.preferwriter(testio1, "image/png")
            @test Formats.resolvewriter(mime) == testio1
            Formats.preferwriter(testio2, "image/png")
            @test Formats.resolvewriter(mime) == testio2
            Formats.addwriter("image/png", testio3)
            @test Formats.resolvewriter(mime) == testio2
            Formats.preferwriter(testio3)
            @test Formats.resolvewriter(mime) == testio3
        end
    end

    @testset "Formatted reading" begin
        Formats.newregistry() do
            Formats.addformat("text/plain")
            Formats.addreader("text/plain", testio1)
            io = IOBuffer("kitten/cat/feline/")
            f = specify(io, "text/plain")
            @test read(f) == "kitten"
            @test read(f) == "cat"
            @test read(f) == "feline"
            @test_throws Exception read(f)
        end
    end

    @testset "Formatted writing" begin
        Formats.newregistry() do
            Formats.addformat("text/plain")
            Formats.addwriter("text/plain", testio1)
            io = IOBuffer(write = true)
            f = specify(io, "text/plain")
            write(f, "kitten")
            write(f, "cat")
            write(f, "feline")
            @test String(take!(io)) == "kitten/cat/feline/"
        end
    end

end # @testset


text:
rawdata = []
open("data.txt") do file
    for line in eachline(file)
        push!(rawdata,parse(Int,line))
    end
end

sorteddata = sort(rawdata)

for y in 200:-1:1
    x = 1
    a = sorteddata[x]
    b = sorteddata[y]
    while (a + b != 2020)
        println("x=", x)
        println("y=", y)
        println("a=", a)
        println("b=", b)
        if (a + b < 2020)
            x += 1
            a = sorteddata[x]
        end
        if (a + b > 2020)
            break
        end
        if a + b == 2020
            println("Found it")
            println("a=", a)
            println("b=", b)
            answer = a * b
            println("answer=", answer)
            exit(0)
        end
    end
end


text:
using YaoBase
export Subroutine, subroutine

"""
    Subroutine{D, BT <: AbstractBlock, C} <: AbstractContainer{BT, D}

Subroutine node on given locations. This allows you to shoehorn a smaller
circuit to a larger one.
"""
struct Subroutine{D,BT<:AbstractBlock,C} <: AbstractContainer{BT,D}
    n::Int
    content::BT
    locs::NTuple{C,Int}
end

function Subroutine(n::Int, block::BT, locs::NTuple{C,Int}) where {N,D,C,BT<:AbstractBlock{D}}
    @assert_locs_safe n locs
    if !(length(locs) == nqudits(block) && n>= nqudits(block))
        throw(
            LocationConflictError(
                "length of locs must be equal to the size of block, and smaller than size of itself.",
            ),
        )
    end
    return Subroutine{D,BT,C}(n, block, locs)
end
YaoBase.nqudits(b::Subroutine) = b.n

"""
    subroutine(n, block, locs)

Create a [`Subroutine`](@ref) block with total number of current active qubits `n`,
which concentrates given wire location together to `length(locs)` active qubits,
and relax the concentration afterwards.

# Example

Subroutine is equivalent to [`put`](@ref) a block on given position mathematically, but more efficient
and convenient for large blocks.

```jldoctest; setup=:(using YaoBlocks; using YaoArrayRegister)
julia> r = rand_state(3)
ArrayReg{2, ComplexF64, Array...}
    active qudits: 3/3
    nlevel: 2

julia> apply!(copy(r), subroutine(X, 1)) ≈ apply!(copy(r), put(1=>X))
true
```

It works for in-contigious locs as well

```jldoctest; setup=:(using YaoBlocks; using YaoArrayRegister)
julia> r = rand_state(4)
ArrayReg{2, ComplexF64, Array...}
    active qudits: 4/4
    nlevel: 2

julia> cc = subroutine(4, kron(X, Y), (1, 3))
nqudits: 4
Subroutine: (1, 3)
└─ kron
   ├─ 1=>X
   └─ 2=>Y

julia> pp = chain(4, put(1=>X), put(3=>Y))
nqudits: 4
chain
├─ put on (1)
│  └─ X
└─ put on (3)
   └─ Y

julia> apply!(copy(r), cc) ≈ apply!(copy(r), pp)
true
```
"""
function subroutine(n::Int, block::AbstractBlock, locs)
    return Subroutine(n, block, Tuple(locs))
end

# support lazy qubits
subroutine(n::Int, block::Function, locs) =
    subroutine(n, parse_block(length(locs), block), locs)

"""
    subroutine(block, locs) -> f(n)

Lazy curried version of [`subroutine`](@ref).
"""
subroutine(block::AbstractBlock, locs) = @λ(n -> subroutine(n, block, locs))
subroutine(block::Function, locs) = @λ(n -> subroutine(n, block, locs))

occupied_locs(c::Subroutine) = map(i -> c.locs[i], c.content |> occupied_locs)
chsubblocks(pb::Subroutine{D}, blk::AbstractBlock{D}) where {D} = Subroutine(pb.n, blk, pb.locs)
PropertyTrait(::Subroutine) = PreserveAll()

function _apply!(r::AbstractRegister, c::Subroutine)
    focus!(r, c.locs)
    _apply!(r, c.content)
    relax!(r, c.locs, to_nactive = nqudits(c))
    return r
end

function mat(::Type{T}, c::Subroutine{D,<:AbstractBlock}) where {D,T}
    mat(T, PutBlock(c.n, c.content, c.locs))
end

Base.adjoint(blk::Subroutine) = Subroutine(blk.n, adjoint(blk.content), blk.locs)

function Base.:(==)(a::Subroutine{D,BT}, b::Subroutine{D,BT}) where {D,BT}
    return a.n == b.n && a.content == b.content && a.locs == b.locs
end

YaoBase.nactive(c::Subroutine) = length(c.locs)

function YaoBase.iscommute(x::Subroutine{D}, y::Subroutine{D}) where {D}
    _check_block_sizes(x, y)
    isempty(setdiff(occupied_locs(x), occupied_locs(y))) && return true
    if x.locs == y.locs
        return iscommute(x.content, y.content)
    else
        return iscommute_fallback(x, y)
    end
end


text:
# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/FEMQuad.jl/blob/master/LICENSE

### Gauss quadrature rules for pyramid elements

""" Gauss-Legendre quadrature, 5 point rule on pyramid. """
function get_quadrature_points(::Type{Val{:GLPYR5}})
    g1 =  0.5842373946721771876874344
    g2 = -2.0/3.0
    g3 =  2.0/5.0
    w1 =  81.0/100.0
    w2 =  125.0/27.0
    weights = (w1, w1, w1, w1, w2)
    points = (
              (-g1, -g1, g2),
              ( g1, -g1, g2),
              ( g1,  g1, g2),
              (-g1,  g1, g2),
              (0.0, 0.0, g3))
    return zip(weights, points)
end

function get_order(::Type{Val{:GLPYR5}})
    return 1
end

""" Gauss-Legendre quadrature, 5 point rule on pyramid. """
function get_quadrature_points(::Type{Val{:GLPYR5B}})
    a = 2.0/15.0
    h1 = 0.1531754163448146
    h2 = 0.6372983346207416
    weights = (a, a, a, a, a)
    points = (
              (0.5, 0.0, h1),
              (0.0, 0.5, h1),
              (-0.5, 0.0, h1),
              (0.0, -0.5, h1),
              (0.0, 0.0, h2)
             )
    return zip(weights, points)
end

function get_order(::Type{Val{:GLPYR5B}})
    return 1
end


text:
using TelegramProgressBot
using Documenter

DocMeta.setdocmeta!(TelegramProgressBot, :DocTestSetup, :(using TelegramProgressBot); recursive=true)

makedocs(;
    modules=[TelegramProgressBot],
    authors="Sebastian Micluța-Câmpeanu <m.c.sebastian95@gmail.com>, Petru-Vlad Toma <tomapv@gmail.com>",
    repo="https://github.com/ctp-fpub/TelegramProgressBot.jl/blob/{commit}{path}#{line}",
    sitename="TelegramProgressBot.jl",
    format=Documenter.HTML(;
        prettyurls=get(ENV, "CI", "false") == "true",
        canonical="https://ctp-fpub.github.io/TelegramProgressBot.jl",
        assets=String[],
    ),
    pages=[
        "Home" => "index.md",
    ],
)

deploydocs(;
    repo="github.com/ctp-fpub/TelegramProgressBot.jl",
)


text:
struct BernsteinPolynomial{N, T} <: AbstractPolynomial{N, T}
    coeffs::NTuple{N, T}
end

# Construction
BernsteinPolynomial(coeffs::Tuple) = BernsteinPolynomial(promote(coeffs...))
BernsteinPolynomial(coeffs...) = BernsteinPolynomial(coeffs)

# Conversion to monomial basis
Polynomial(b::BernsteinPolynomial{1}) = Polynomial(b.coeffs)
function Polynomial(b::BernsteinPolynomial{N, T}) where {N, T}
    # b(t) = (1 - t) * b1(t) + t * b2(t)
    #      = b1(t) + t * (b2(t) - b1(t))
    b1 = BernsteinPolynomial(reverse(tail(reverse(b.coeffs))))
    b2 = BernsteinPolynomial(tail(b.coeffs))
    Polynomial(b1) + Polynomial(0, 1) * Polynomial(b2 - b1)
end

# Utility
@inline constant(b::BernsteinPolynomial) = b.coeffs[1]
Base.zero(::Type{BernsteinPolynomial{N, T}}) where {N, T} = BernsteinPolynomial(ntuple(_ -> zero(T), Val(N)))
Base.zero(b::BernsteinPolynomial) = zero(typeof(b))

# Evaluation
@inline (b::BernsteinPolynomial{1})(t) = constant(b)
@inline function (b::BernsteinPolynomial)(t)
    b1 = BernsteinPolynomial(reverse(tail(reverse(b.coeffs))))
    b2 = BernsteinPolynomial(tail(b.coeffs))
    (oneunit(t) - t) * b1(t) + t * b2(t)
end

for op in [:+, :-]
    @eval begin
        # Two BernsteinPolynomials
        Base.$op(b1::BernsteinPolynomial{N}, b2::BernsteinPolynomial{N}) where {N} = BernsteinPolynomial(_map($op, b1.coeffs, b2.coeffs))

        # BernsteinPolynomials and constant
        Base.$op(b::BernsteinPolynomial, c) = BernsteinPolynomial(_map(p -> $op(p, c), b.coeffs))
        Base.$op(c, b::BernsteinPolynomial) = BernsteinPolynomial(_map(p -> $op(c, p), b.coeffs))
    end
end

for op in [:*, :/]
    @eval Base.$op(b::BernsteinPolynomial, c) = BernsteinPolynomial(_map(x -> $op(x, c), b.coeffs))
end
Base.:*(c, b::BernsteinPolynomial) = BernsteinPolynomial(_map(x -> c * x, b.coeffs))

@inline function derivative(b::BernsteinPolynomial{N}) where {N}
    # https://pages.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/Bezier/bezier-der.html
    coeffs = b.coeffs
    n = N - 1
    BernsteinPolynomial(ntuple(i -> n * (coeffs[i + 1] - coeffs[i]), n))
end

"""
```math
\\int_{0}^{1} b\\left(\\tau\\right) e^{c \\tau} d\\tau
```
"""
@inline function exponential_integral(b::BernsteinPolynomial{N}, c; inv_c=inv(c), exp_c=exp(c)) where N
    pn = b.coeffs[N]
    if N === 1
        return inv_c * constant(b) * (exp_c - 1)
    else
        return inv_c * (pn * exp_c - constant(b) - exponential_integral(derivative(b), c, inv_c=inv_c, exp_c=exp_c))
    end
end