text:
# # this might be used for Nesterov lookahead, or similar
# before_grad_calc(θ::AbstractVector, updater::ParamUpdater, ∇::AbstractVector) = return

# TODO: add citations/links for each method
# note: good ref: http://sebastianruder.com/optimizing-gradient-descent/
# note: good ref: https://www.quora.com/What-are-differences-between-update-rules-like-AdaDelta-RMSProp-AdaGrad-and-AdaM

init(updater::ParamUpdater, model::Learnable) = init(updater, params(model))

"Stochastic Gradient Descent with Momentum"
type SGD{T<:Number} <: ParamUpdater
    mom::T              # momentum
    lastchg::Vector{T}   # most recent grad change
    (::Type{SGD{T}}){T <: Number}(mom::T) = new{T}(mom)
end
SGD{T}(::Type{T}, mom = T(0.0)) = SGD{T}(T(mom))
SGD{T}(mom::T = 0.0) = SGD{T}(mom)

function init(updater::SGD, θ::AbstractVector)
    updater.lastchg = zeros(θ)
    return
end

# # add momentum into θ, and reset lastchg
# function before_grad_calc(θ::AbstractVector, updater::SGD, ∇::AbstractVector)
#     @inbounds for (i,k) in zip(eachindex(θ), eachindex(updater.lastchg))
#         momchg = updater.mom * updater.lastchg[k]
#         updater.lastchg[k] = momchg
#         θ[i] += momchg
#     end
# end

#
function update!(θ::AbstractVector, updater::SGD, ∇::AbstractVector, lr::Number)
    @inbounds for (i,j,k) in zip(eachindex(θ), eachindex(∇), eachindex(updater.lastchg))
        chg = -lr * ∇[j] + updater.mom * updater.lastchg[k]
        θ[i] += chg
        updater.lastchg[k] = chg
    end
end

# -----------------------------------------------------------------------

"Adaptive Gradient"
type Adagrad{T<:Number} <: ParamUpdater
    ϵ::T
    G::Vector{T}  # sum of squared gradients
    (::Type{Adagrad{T}}){T <: Number}(ϵ::T) = new{T}(ϵ)
end
Adagrad{T}(::Type{T}, ϵ = 1e-2) = Adagrad{T}(T(ϵ))
Adagrad() = Adagrad(Float64)

function init(updater::Adagrad, θ::AbstractVector)
    updater.G = zeros(θ)
    return
end

function update!(θ::AbstractVector, updater::Adagrad, ∇::AbstractVector, lr::Number)
    @inbounds for (i,j,k) in zip(eachindex(θ), eachindex(∇), eachindex(updater.G))
        updater.G[k] += ∇[j]^2
        θ[i] -= lr * ∇[j] / sqrt(updater.ϵ + updater.G[k])
    end
end

# -----------------------------------------------------------------------

"""
See: ADADELTA: An Adaptive Learning Rate Method (Zeiler 2012)

Relatively parameter-free... can probably avoid changing ε and ρ
"""
type Adadelta{T<:Number} <: ParamUpdater
    ϵ::T
    ρ::T
    dmean::Vector{T} # exponential average of squared param changes
    Gmean::Vector{T} # exponential average of squared gradients
    (::Type{Adadelta{T}}){T<:Number}(ϵ::T, ρ::T) = new{T}(ϵ, ρ)
end
Adadelta{T}(::Type{T}, ϵ = 1e-2, ρ = 0.97) = Adadelta{T}(T(ϵ), T(ρ))
Adadelta{T}(ϵ::T = 1e-2, ρ::T = 0.97) = Adadelta{T}(ϵ, ρ)

function init(updater::Adadelta, θ::AbstractVector)
    updater.dmean = zeros(θ)
    updater.Gmean = zeros(θ)
    return
end

function update!{T}(θ::AbstractVector, updater::Adadelta{T}, ∇::AbstractVector, lr::Number)
    ϵ = updater.ϵ
    ρ = updater.ρ
    dm = updater.dmean
    Gm = updater.Gmean
    @inbounds for i=1:length(θ)
        # average g²
        Gm[i] = ρ * Gm[i] + (one(T) - ρ) * ∇[i]^2

        # learning rate is function of prev avg dw² and current avg g²
        δ = lr * sqrt((dm[i] + ϵ) / (Gm[i] + ϵ)) * ∇[i]
        θ[i] -= δ

        # update avg dw²
        dm[i] = ρ * dm[i] + (one(T) - ρ) * δ^2
    end
end

# -----------------------------------------------------------------------

"""
see: ADAM: A method for Stochastic Optimization (Kingma and Ba 2015)

Tracks an exponential moving average of the first and second moments of the gradient,
adjusting for zero-bias.  The defaults are those suggested in the paper.

TODO: AdaMax is similar, using the p-norm as p -> ∞
"""
type Adam{T<:Number} <: ParamUpdater
    ϵ::T  # small number so we don't divide by 0
    ρ₁::T # decay for first moment (β₁ in the paper)
    ρ₂::T # decay for second moment (β₂ in the paper)
    m::Vector{T} # average first moment
    v::Vector{T} # average second moment
    ρ₁ᵗ::Vector{T} # β₁ᵗ from the paper... t-th power of β₁
    ρ₂ᵗ::Vector{T} # β₂ᵗ from the paper... t-th power of β₂
    (::Type{Adam{T}}){T<:Number}(ϵ::T, ρ₁::T, ρ₂::T) = new{T}(ϵ, ρ₁, ρ₂)
end
Adam{T}(::Type{T}, ϵ = T(1e-8), ρ₁ = T(0.9), ρ₂ = T(0.999)) = Adam{T}(T(ϵ), T(ρ₁), T(ρ₂))
Adam{T}(ϵ::T = 1e-8, ρ₁::T = 0.9, ρ₂::T = 0.999) = Adam{T}(ϵ, ρ₁, ρ₂)

function init(updater::Adam, θ::AbstractVector)
    updater.m = zeros(θ)
    updater.v = zeros(θ)
    updater.ρ₁ᵗ = ones(θ)
    updater.ρ₂ᵗ = ones(θ)
    return
end

function update!{T}(θ::AbstractVector, updater::Adam{T}, ∇::AbstractVector, lr::Number)
    ϵ = updater.ϵ
    ρ₁ = updater.ρ₁
    ρ₂ = updater.ρ₂
    m = updater.m
    v = updater.v
    ρ₁ᵗ = updater.ρ₁ᵗ
    ρ₂ᵗ = updater.ρ₂ᵗ
    @inbounds for i=1:length(θ)
        m[i] = ρ₁ * m[i] + (one(T) - ρ₁) * ∇[i]
        v[i] = ρ₂ * v[i] + (one(T) - ρ₂) * ∇[i]^2
        ρ₁ᵗ[i] *= ρ₁
        ρ₂ᵗ[i] *= ρ₂
        θ[i] -= lr * m[i] * sqrt((one(T) - ρ₂ᵗ[i]) / (v[i] + ϵ)) / (one(T) - ρ₁ᵗ[i])
    end
end

# -----------------------------------------------------------------------

"""
see: ADAM: A method for Stochastic Optimization (Kingma and Ba 2015)

AdaMax is similar to Adam, using the p-norm as p -> ∞
"""
type Adamax{T<:Number} <: ParamUpdater
    ρ₁::T # decay for first moment (β₁ in the paper)
    ρ₂::T # decay for second moment (β₂ in the paper)
    m::Vector{T} # average first moment
    u::Vector{T} # average second moment
    ρ₁ᵗ::Vector{T} # β₁ᵗ from the paper... t-th power of β₁
    (::Type{Adamax{T}}){T<:Number}(ρ₁::T, ρ₂::T) = new{T}(ρ₁, ρ₂)
end
Adamax{T}(::Type{T}, ρ₁ = T(0.9), ρ₂ = T(0.999)) = Adamax{T}(T(ρ₁), T(ρ₂))
Adamax{T}(ρ₁::T = 0.9, ρ₂::T = 0.999) = Adamax{T}(ρ₁, ρ₂)

function init(updater::Adamax, θ::AbstractVector)
    updater.m = zeros(θ)
    updater.u = zeros(θ)
    updater.ρ₁ᵗ = ones(θ)
    return
end

function update!{T}(θ::AbstractVector, updater::Adamax{T}, ∇::AbstractVector, lr::Number)
    ρ₁ = updater.ρ₁
    ρ₂ = updater.ρ₂
    m = updater.m
    u = updater.u
    ρ₁ᵗ = updater.ρ₁ᵗ
    @inbounds for i=1:length(θ)
        m[i] = ρ₁ * m[i] + (one(T) - ρ₁) * ∇[i]
        u[i] = max(ρ₂ * u[i], abs(∇[i]))
        ρ₁ᵗ[i] *= ρ₁
        θ[i] -= lr * m[i] / ((u[i] + T(1e-10)) * (one(T) - ρ₁ᵗ[i]))
    end
end

# -----------------------------------------------------------------------

# TODO: RMSProp: http://climin.readthedocs.io/en/latest/rmsprop.html#tieleman2012rmsprop

type RMSProp{T<:Number} <: ParamUpdater
    γ::T # exponential weight of ∇² avg
    g::Vector{T}  # the exponential mean
    (::Type{RMSProp{T}}){T<:Number}(γ::T) = new{T}(γ)
end
RMSProp{T}(::Type{T}, γ = T(0.95)) = RMSProp{T}(T(γ))
RMSProp{T}(γ::T) = RMSProp{T}(γ)
RMSProp() = RMSProp{Float64}(0.95)

function init(updater::RMSProp, θ::AbstractVector)
    updater.g = ones(θ)
    return
end

function update!{T}(θ::AbstractVector, updater::RMSProp{T}, ∇::AbstractVector, lr::Number)
    γ = updater.γ
    g = updater.g
    @inbounds for i=1:length(θ)
        g[i] = γ * g[i] + (one(T) - γ) * ∇[i]^2
        θ[i] -= lr * ∇[i] / sqrt(g[i] + 1e-10)
    end
end

max_stars_repo_name: JuliaML/StochasticOptimization

max_issues_repo_name: JuliaML/StochasticOptimization

max_forks_repo_name: JuliaML/StochasticOptimization


text:
function add_feedforward_constraints!(
    container::OptimizationContainer,
    model::DeviceModel,
    devices::IS.FlattenIteratorWrapper{V},
) where {V <: PSY.Component}
    for ff in get_feedforwards(model)
        @debug "constraints" ff V _group = LOG_GROUP_FEEDFORWARDS_CONSTRUCTION
        add_feedforward_constraints!(container, model, devices, ff)
    end
    return
end

function add_feedforward_constraints!(
    container::OptimizationContainer,
    model::ServiceModel,
    ::V,
) where {V <: PSY.AbstractReserve}
    for ff in get_feedforwards(model)
        @debug "constraints" ff V _group = LOG_GROUP_FEEDFORWARDS_CONSTRUCTION
        contributing_devices = get_contributing_devices(model)
        add_feedforward_constraints!(container, model, contributing_devices, ff)
    end
    return
end

function _add_feedforward_constraints!(
    container::OptimizationContainer,
    ::Type{T},
    ::P,
    ::VariableKey{U, V},
    devices::IS.FlattenIteratorWrapper{V},
    model::DeviceModel,
) where {T <: ConstraintType, P <: ParameterType, U <: VariableType, V <: PSY.Component}
    time_steps = get_time_steps(container)
    names = [PSY.get_name(d) for d in devices]
    constraint_lb =
        add_constraints_container!(container, T(), V, names, time_steps, meta="$(U)_lb")
    constraint_ub =
        add_constraints_container!(container, T(), V, names, time_steps, meta="$(U)_ub")
    array = get_variable(container, U(), V)
    parameter = get_parameter_array(container, P(), V)
    multiplier = get_parameter_multiplier_array(container, P(), V)
    jump_model = get_jump_model(container)
    upper_bound_range_with_parameter!(
        jump_model,
        constraint_ub,
        array,
        multiplier,
        parameter,
        devices,
    )
    lower_bound_range_with_parameter!(
        jump_model,
        constraint_lb,
        array,
        multiplier,
        parameter,
        devices,
    )
    return
end

function _add_sc_feedforward_constraints!(
    container::OptimizationContainer,
    ::Type{T},
    ::P,
    ::VariableKey{U, V},
    devices::IS.FlattenIteratorWrapper{V},
    model::DeviceModel{V, W},
) where {
    T <: FeedforwardSemiContinousConstraint,
    P <: OnStatusParameter,
    U <: Union{ActivePowerVariable, PowerAboveMinimumVariable},
    V <: PSY.Component,
    W <: AbstractDeviceFormulation,
}
    time_steps = get_time_steps(container)
    names = [PSY.get_name(d) for d in devices]
    constraint_lb =
        add_constraints_container!(container, T(), V, names, time_steps, meta="$(U)_lb")
    constraint_ub =
        add_constraints_container!(container, T(), V, names, time_steps, meta="$(U)_ub")
    array_lb = get_expression(container, ActivePowerRangeExpressionLB(), V)
    array_ub = get_expression(container, ActivePowerRangeExpressionUB(), V)
    parameter = get_parameter_array(container, P(), V)
    mult_ub = DenseAxisArray(zeros(length(devices), time_steps[end]), names, time_steps)
    mult_lb = DenseAxisArray(zeros(length(devices), time_steps[end]), names, time_steps)
    jump_model = get_jump_model(container)
    upper_bound_range_with_parameter!(
        jump_model,
        constraint_ub,
        array_ub,
        mult_ub,
        parameter,
        devices,
    )
    lower_bound_range_with_parameter!(
        jump_model,
        constraint_lb,
        array_lb,
        mult_lb,
        parameter,
        devices,
    )
    return
end

function _add_sc_feedforward_constraints!(
    container::OptimizationContainer,
    ::Type{T},
    ::P,
    ::VariableKey{U, V},
    devices::IS.FlattenIteratorWrapper{V},
    model::DeviceModel{V, W},
) where {
    T <: FeedforwardSemiContinousConstraint,
    P <: ParameterType,
    U <: VariableType,
    V <: PSY.Component,
    W <: AbstractDeviceFormulation,
}
    time_steps = get_time_steps(container)
    names = [PSY.get_name(d) for d in devices]
    constraint_lb =
        add_constraints_container!(container, T(), V, names, time_steps, meta="$(U)_lb")
    constraint_ub =
        add_constraints_container!(container, T(), V, names, time_steps, meta="$(U)_ub")
    variable = get_variable(container, U(), V)
    parameter = get_parameter_array(container, P(), V)
    upper_bounds = [get_variable_upper_bound(U(), d, W()) for d in devices]
    lower_bounds = [get_variable_lower_bound(U(), d, W()) for d in devices]
    if any(isnothing.(upper_bounds)) || any(isnothing.(lower_bounds))
        throw(IS.InvalidValueError("Bounds for variable $U $V not defined correctly"))
    end
    mult_ub = DenseAxisArray(repeat(upper_bounds, 1, time_steps[end]), names, time_steps)
    mult_lb = DenseAxisArray(repeat(lower_bounds, 1, time_steps[end]), names, time_steps)
    jump_model = get_jump_model(container)
    upper_bound_range_with_parameter!(
        jump_model,
        constraint_ub,
        variable,
        mult_ub,
        parameter,
        devices,
    )
    lower_bound_range_with_parameter!(
        jump_model,
        constraint_lb,
        variable,
        mult_lb,
        parameter,
        devices,
    )
    return
end

function add_feedforward_constraints!(
    container::OptimizationContainer,
    model::DeviceModel,
    devices::IS.FlattenIteratorWrapper{T},
    ff::SemiContinuousFeedforward,
) where {T <: PSY.Component}
    parameter_type = get_default_parameter_type(ff, T)
    time_steps = get_time_steps(container)
    for var in get_affected_values(ff)
        variable = get_variable(container, var)
        axes = JuMP.axes(variable)
        IS.@assert_op axes[1] == [PSY.get_name(d) for d in devices]
        IS.@assert_op axes[2] == time_steps
        # If the variable was a lower bound != 0, not removing the LB can cause infeasibilities
        for v in variable
            if JuMP.has_lower_bound(v) && JuMP.lower_bound(v) > 0.0
                @debug "lb reset" JuMP.lower_bound(v) v _group =
                    LOG_GROUP_FEEDFORWARDS_CONSTRUCTION
                JuMP.set_lower_bound(v, 0.0)
            end
        end
        _add_sc_feedforward_constraints!(
            container,
            FeedforwardSemiContinousConstraint,
            parameter_type,
            var,
            devices,
            model,
        )
    end
    return
end

@doc raw"""
        ub_ff(container::OptimizationContainer,
              cons_name::Symbol,
              constraint_infos,
              param_reference,
              var_key::VariableKey)

Constructs a parameterized upper bound constraint to implement feedforward from other models.
The Parameters are initialized using the uppper boundary values of the provided variables.

``` variable[var_name, t] <= param_reference[var_name] ```

# LaTeX

`` x \leq param^{max}``

# Arguments
* container::OptimizationContainer : the optimization_container model built in PowerSimulations
* cons_name::Symbol : name of the constraint
* param_reference : Reference to the PJ.ParameterRef used to determine the upperbound
* var_key::VariableKey : the name of the continuous variable
"""
function add_feedforward_constraints!(
    container::OptimizationContainer,
    ::DeviceModel,
    devices::IS.FlattenIteratorWrapper{T},
    ff::UpperBoundFeedforward,
) where {T <: PSY.Component}
    time_steps = get_time_steps(container)
    parameter_type = get_default_parameter_type(ff, T)
    param_ub = get_parameter_array(container, parameter_type, T)
    multiplier_ub = get_parameter_multiplier_array(container, parameter_type, T)
    for var in get_affected_values(ff)
        variable = get_variable(container, var)
        set_name, set_time = JuMP.axes(variable)
        IS.@assert_op set_name == [PSY.get_name(d) for d in devices]
        IS.@assert_op set_time == time_steps

        var_type = get_entry_type(var)
        con_ub = add_constraints_container!(
            container,
            FeedforwardUpperBoundConstraint(),
            T,
            set_name,
            time_steps,
            meta="$(var_type)ub",
        )

        for t in time_steps, name in set_name
            con_ub[name, t] = JuMP.@constraint(
                container.JuMPmodel,
                variable[name, t] <= param_ub[name, t] * multiplier_ub[name, t]
            )
        end
    end
    return
end

@doc raw"""
        lb_ff(container::OptimizationContainer,
              cons_name::Symbol,
              constraint_infos,
              param_reference,
              var_key::VariableKey)

Constructs a parameterized upper bound constraint to implement feedforward from other models.
The Parameters are initialized using the uppper boundary values of the provided variables.

``` variable[var_name, t] <= param_reference[var_name] ```

# LaTeX

`` x \leq param^{max}``

# Arguments
* container::OptimizationContainer : the optimization_container model built in PowerSimulations
* cons_name::Symbol : name of the constraint
* param_reference : Reference to the PJ.ParameterRef used to determine the upperbound
* var_key::VariableKey : the name of the continuous variable
"""
function add_feedforward_constraints!(
    container::OptimizationContainer,
    ::DeviceModel,
    devices::IS.FlattenIteratorWrapper{T},
    ff::LowerBoundFeedforward,
) where {T <: PSY.Component}
    time_steps = get_time_steps(container)
    parameter_type = get_default_parameter_type(ff, T)
    param_ub = get_parameter_array(container, parameter_type, T)
    multiplier_ub = get_parameter_multiplier_array(container, parameter_type, T)
    for var in get_affected_values(ff)
        variable = get_variable(container, var)
        set_name, set_time = JuMP.axes(variable)
        IS.@assert_op set_name == [PSY.get_name(d) for d in devices]
        IS.@assert_op set_time == time_steps

        var_type = get_entry_type(var)
        con_ub = add_constraints_container!(
            container,
            FeedforwardLowerBoundConstraint(),
            T,
            set_name,
            time_steps,
            meta="$(var_type)lb",
        )

        for t in time_steps, name in set_name
            con_ub[name, t] = JuMP.@constraint(
                container.JuMPmodel,
                variable[name, t] >= param_ub[name, t] * multiplier_ub[name, t]
            )
        end
    end
    return
end

function add_feedforward_constraints!(
    container::OptimizationContainer,
    ::ServiceModel,
    contributing_devices::Vector{T},
    ff::LowerBoundFeedforward,
) where {T <: PSY.Component}
    time_steps = get_time_steps(container)
    parameter_type = get_default_parameter_type(ff, T)
    param_ub = get_parameter_array(container, parameter_type, T)
    multiplier_ub = get_parameter_multiplier_array(container, parameter_type, T)
    for var in get_affected_values(ff)
        variable = get_variable(container, var)
        set_name, set_time = JuMP.axes(variable)
        IS.@assert_op set_name == [PSY.get_name(d) for d in contributing_devices]
        IS.@assert_op set_time == time_steps

        var_type = get_entry_type(var)
        con_ub = add_constraints_container!(
            container,
            FeedforwardLowerBoundConstraint(),
            T,
            set_name,
            time_steps,
            meta="$(var_type)lb",
        )

        for t in time_steps, name in set_name
            con_ub[name, t] = JuMP.@constraint(
                container.JuMPmodel,
                variable[name, t] >= param_ub[name, t] * multiplier_ub[name, t]
            )
        end
    end
    return
end

@doc raw"""
        add_feedforward_constraints(container::OptimizationContainer,
                        cons_name::Symbol,
                        param_reference,
                        var_key::VariableKey)

Constructs a parameterized integral limit constraint to implement feedforward from other models.
The Parameters are initialized using the upper boundary values of the provided variables.

``` sum(variable[var_name, t] for t in 1:affected_periods)/affected_periods <= param_reference[var_name] ```

# LaTeX

`` \sum_{t} x \leq param^{max}``

# Arguments
* container::OptimizationContainer : the optimization_container model built in PowerSimulations
* model::DeviceModel : the device model
* devices::IS.FlattenIteratorWrapper{T} : list of devices
* ff::FixValueFeedforward : a instance of the FixValue Feedforward
"""
function add_feedforward_constraints!(
    container::OptimizationContainer,
    ::DeviceModel,
    devices::IS.FlattenIteratorWrapper{T},
    ff::EnergyLimitFeedforward,
) where {T <: PSY.Component}
    time_steps = get_time_steps(container)
    parameter_type = get_default_parameter_type(ff, T)
    param = get_parameter_array(container, parameter_type, T)
    multiplier = get_parameter_multiplier_array(container, parameter_type, T)
    affected_periods = get_number_of_periods(ff)
    for var in get_affected_values(ff)
        variable = get_variable(container, var)
        set_name, set_time = JuMP.axes(variable)
        IS.@assert_op set_name == [PSY.get_name(d) for d in devices]
        IS.@assert_op set_time == time_steps

        if affected_periods > set_time[end]
            error(
                "The number of affected periods $affected_periods is larger than the periods available $(set_time[end])",
            )
        end
        no_trenches = set_time[end] ÷ affected_periods
        var_type = get_entry_type(var)
        con_ub = add_constraints_container!(
            container,
            FeedforwardIntegralLimitConstraint(),
            T,
            set_name,
            1:no_trenches,
            meta="$(var_type)integral",
        )

        for name in set_name, i in 1:no_trenches
            con_ub[name, i] = JuMP.@constraint(
                container.JuMPmodel,
                sum(
                    variable[name, t] for
                    t in (1 + (i - 1) * affected_periods):(i * affected_periods)
                ) <= sum(
                    param[name, t] * multiplier[name, t] for
                    t in (1 + (i - 1) * affected_periods):(i * affected_periods)
                )
            )
        end
    end
    return
end

@doc raw"""
        add_feedforward_constraints(
            container::OptimizationContainer,
            ::DeviceModel,
            devices::IS.FlattenIteratorWrapper{T},
            ff::FixValueFeedforward,
        ) where {T <: PSY.Component}

Constructs a equality constraint to a fix a variable in one model using the variable value from other model results.

``` variable[var_name, t] == param[var_name, t] ```

# LaTeX

`` x == param``

# Arguments
* container::OptimizationContainer : the optimization_container model built in PowerSimulations
* model::DeviceModel : the device model
* devices::IS.FlattenIteratorWrapper{T} : list of devices
* ff::FixValueFeedforward : a instance of the FixValue Feedforward
"""
function add_feedforward_constraints!(
    container::OptimizationContainer,
    ::DeviceModel,
    devices::IS.FlattenIteratorWrapper{T},
    ff::FixValueFeedforward,
) where {T <: PSY.Component}
    time_steps = get_time_steps(container)
    parameter_type = get_default_parameter_type(ff, T)
    param = get_parameter_array(container, parameter_type, T)
    multiplier = get_parameter_multiplier_array(container, parameter_type, T)
    for var in get_affected_values(ff)
        variable = get_variable(container, var)
        set_name, set_time = JuMP.axes(variable)
        IS.@assert_op set_name == [PSY.get_name(d) for d in devices]
        IS.@assert_op set_time == time_steps

        for t in time_steps, name in set_name
            JuMP.fix(variable[name, t], param[name, t] * multiplier[name, t]; force=true)
        end
    end
    return
end

@doc raw"""
        add_feedforward_constraints(
            container::OptimizationContainer,
            ::DeviceModel,
            devices::IS.FlattenIteratorWrapper{T},
            ff::EnergyTargetFeedforward,
        ) where {T <: PSY.Component}

Constructs a equality constraint to a fix a variable in one model using the variable value from other model results.

``` variable[var_name, t] + slack[var_name, t] >= param[var_name, t] ```

# LaTeX

`` x + slack >= param``

# Arguments
* container::OptimizationContainer : the optimization_container model built in PowerSimulations
* model::DeviceModel : the device model
* devices::IS.FlattenIteratorWrapper{T} : list of devices
* ff::EnergyTargetFeedforward : a instance of the FixValue Feedforward
"""
function add_feedforward_constraints!(
    container::OptimizationContainer,
    ::DeviceModel,
    devices::IS.FlattenIteratorWrapper{T},
    ff::EnergyTargetFeedforward,
) where {T <: PSY.Component}
    time_steps = get_time_steps(container)
    parameter_type = get_default_parameter_type(ff, T)
    param = get_parameter_array(container, parameter_type, T)
    multiplier = get_parameter_multiplier_array(container, parameter_type, T)
    target_period = ff.target_period
    penalty_cost = ff.penalty_cost
    for var in get_affected_values(ff)
        variable = get_variable(container, var)
        slack_var = get_variable(container, EnergyShortageVariable(), T)
        set_name, set_time = JuMP.axes(variable)
        IS.@assert_op set_name == [PSY.get_name(d) for d in devices]
        IS.@assert_op set_time == time_steps

        var_type = get_entry_type(var)
        con_ub = add_constraints_container!(
            container,
            FeedforwardEnergyTargetConstraint(),
            T,
            set_name,
            meta="$(var_type)target",
        )

        for d in devices
            name = PSY.get_name(d)
            con_ub[name] = JuMP.@constraint(
                container.JuMPmodel,
                variable[name, target_period] + slack_var[name, target_period] >=
                param[name, target_period] * multiplier[name, target_period]
            )
            add_to_objective_invariant_expression!(
                container,
                slack_var[name, target_period] * penalty_cost,
            )
        end
    end
    return
end

ext: jl


text:
module toolbox

#Loading different files and exporting function names
include("funcs.jl")
include("sys_io.jl")
include("debug.jl")

end # module

max_stars_repo_name: thjsal/toolbox

max_issues_repo_name: thjsal/toolbox

max_forks_repo_name: thjsal/toolbox


text:
@testset "Padding" begin
    dv = @data ones(3)
    @test isequal(dv, padmissing(dv, 0, 0))
    @test length(padmissing(dv, 2, 0)) == length(dv) + 2
    @test length(padmissing(dv, 0, 2)) == length(dv) + 2
    @test length(padmissing(dv, 2, 2)) == length(dv) + 4
end