From c82d57e20382c4ad09403f827d1c83aaac0a2d4e Mon Sep 17 00:00:00 2001 From: joaquimgarcia_psr Date: Mon, 18 Mar 2019 20:14:58 -0300 Subject: [PATCH 1/5] add variable interface and organize files --- src/ParameterJuMP.jl | 343 ++++++-------------------------------- src/constraints.jl | 274 ++++++++++++++++++++++++++++++ src/variable_interface.jl | 167 +++++++++++++++++++ 3 files changed, 488 insertions(+), 296 deletions(-) create mode 100644 src/constraints.jl create mode 100644 src/variable_interface.jl diff --git a/src/ParameterJuMP.jl b/src/ParameterJuMP.jl index 2a5c402..5138a4c 100644 --- a/src/ParameterJuMP.jl +++ b/src/ParameterJuMP.jl @@ -7,7 +7,7 @@ const MOIU = MOI.Utilities using JuMP export -ModelWithParams, Parameter, Parameters, setvalue! +ModelWithParams, Parameter, Parameters, setvalue!, fix # types # ------------------------------------------------------------------------------ @@ -52,7 +52,7 @@ mutable struct ParameterData # variables_map_lb::Dict{Int64, Vector{Int64}} # variables_map_ub::Dict{Int64, Vector{Int64}} - solved::Bool + # solved::Bool lazy::Bool dual_values::Vector{Float64} @@ -66,7 +66,7 @@ mutable struct ParameterData Dict{CtrRef{SAF, EQ}, JuMP.GenericAffExpr{Float64,Parameter}}(), Dict{CtrRef{SAF, LE}, JuMP.GenericAffExpr{Float64,Parameter}}(), Dict{CtrRef{SAF, GE}, JuMP.GenericAffExpr{Float64,Parameter}}(), - false, + # false, false, Float64[], ) @@ -194,120 +194,63 @@ function Parameters(model::JuMP.Model, val::AbstractArray{R,N}) where {R,N} return out end -# getters/setters +# solve # ------------------------------------------------------------------------------ -function JuMP.value(p::Parameter) - params = _getparamdata(p)::ParameterData - params.future_values[p.ind] -end - """ - setvalue!(p::Parameter, val::Real)::Nothing - -Sets the parameter `p` to the new value `val`. -""" -function setvalue!(p::Parameter, val::Real) - params = _getparamdata(p)::ParameterData - params.sync = false - params.future_values[p.ind] = val - return nothing -end -function JuMP.dual(p::Parameter) - params = _getparamdata(p)::ParameterData - if lazy_duals(params) - return _getdual(p) - else - return params.dual_values[p.ind] - end -end - -function _getdual(p::Parameter) - return _getdual(p.model, p.ind) -end -function _getdual(pcr::ParametrizedConstraintRef)::Float64 - pcr.coef * JuMP.dual(pcr.cref) -end -function _getdual(model::JuMP.Model, ind::Integer) - params = _getparamdata(model)::ParameterData - # See (12) in http://www.juliaopt.org/MathOptInterface.jl/stable/apimanual.html#Duals-1 - # in the dual objective: - sum_i b_i^T y_i - # Here b_i depends on the param and is b_i' + coef * param - # so the dualobjective is: - # - sum_i b_i'^T y_i - param * sum_i coef^T y_i - # The dual of the parameter is: - sum_i coef^T y_i - return -sum(_getdual.(params.constraints_map[ind])) -end + ModelWithParams(args...; kwargs...)::JuMP.Model -# type 1 -# ------------------------------------------------------------------------------ +Returns a JuMP.Model able to handle `Parameters`. -const GAE{C,V} = JuMP.GenericAffExpr{C,V} -const GAEv{C} = JuMP.GenericAffExpr{C,JuMP.VariableRef} -const GAEp{C} = JuMP.GenericAffExpr{C,Parameter} +`args` and `kwargs` are the same parameters that would be passed +to the regular `Model` constructor. -mutable struct ParametrizedAffExpr{C} <: JuMP.AbstractJuMPScalar - v::JuMP.GenericAffExpr{C,JuMP.VariableRef} - p::JuMP.GenericAffExpr{C,Parameter} -end +Example using GLPK solver: -if VERSION >= v"0.7-" - Base.broadcastable(expr::ParametrizedAffExpr) = Ref(expr) +```julia + model = ModelWithParams(with_optimizer(GLPK.Optimizer)) +``` +""" +function ModelWithParams(args...; kwargs...) + m = JuMP.Model(args...; kwargs...) + enable_parameters(m) + return m end -# JuMP.GenericAffExpr{C,Parameter}(params::Vector{Parameter},coefs::Vector{C}) = JuMP.GenericAffExpr{C}(params,coefs,C(0.0)) - -const PAE{C} = ParametrizedAffExpr{C} -const PAEC{S} = JuMP.ScalarConstraint{PAE{Float64}, S} -const PVAEC{S} = JuMP.VectorConstraint{PAE{Float64}, S} - - -# Build constraint -# ------------------------------------------------------------------------------ - -# TODO should be in MOI, MOIU or JuMP -_shift_constant(set::S, offset) where S <: Union{MOI.LessThan,MOI.GreaterThan,MOI.EqualTo} = S(MOIU.getconstant(set) + offset) -function _shift_constant(set::MOI.Interval, offset) - MOI.Interval(set.lower + offset, set.upper + offset) -end +""" + enable_parameters(m::JuMP.Model)::Nothing -function JuMP.build_constraint(_error::Function, aff::ParametrizedAffExpr, set::S) where S <: Union{MOI.LessThan,MOI.GreaterThan,MOI.EqualTo} - offset = aff.v.constant - aff.v.constant = 0.0 - shifted_set = _shift_constant(set, -offset) - return JuMP.ScalarConstraint(aff, shifted_set) +Enables a `JuMP.Model` to handle `Parameters`. +""" +function enable_parameters(m::JuMP.Model) + if haskey(m.ext, :params) + error("Model already has parameter enabled") + end + # test and compare hook + initialize_parameter_data(m) + JuMP.set_optimize_hook(m, parameter_optimizehook) + return nothing end -function JuMP.build_constraint(_error::Function, aff::ParametrizedAffExpr, lb, ub) - JuMP.build_constraint(_error, aff, MOI.Interval(lb, ub)) +function initialize_parameter_data(m::JuMP.Model) + m.ext[:params] = ParameterData() end -function JuMP.add_constraint(m::JuMP.Model, c::PAEC{S}, name::String="") where S - - # build LinearConstraint - c_lin = JuMP.ScalarConstraint(c.func.v, c.set) - - # JuMP´s standard add_constrint - cref = JuMP.add_constraint(m, c_lin, name) - +function parameter_optimizehook(m::JuMP.Model; kwargs...) data = _getparamdata(m)::ParameterData - # needed for lazy get dual - if lazy_duals(data) - for (param, coef) in c.func.p.terms - push!(data.constraints_map[param.ind], ParametrizedConstraintRef(cref, coef)) - end - end + # sync model rhs to newest parameter values + sync(data) - # save the parameter part of a parametric affine expression - _get_param_dict(data, S)[cref] = c.func.p + ret = JuMP.optimize!(m::JuMP.Model, ignore_optimize_hook = true, kwargs...) - return cref + # update duals for later query + if JuMP.has_duals(m) + _update_duals(data) + end + return ret end -# solve -# ------------------------------------------------------------------------------ - """ sync(model::JuMP.Model)::Nothing @@ -316,9 +259,7 @@ Parameters. """ function sync(data::ParameterData) if !is_sync(data) - _update_constraints(data, EQ) - _update_constraints(data, LE) - _update_constraints(data, GE) + _update_constraints(data) data.current_values .= data.future_values data.sync = true end @@ -328,205 +269,15 @@ function sync(model::JuMP.Model) sync(_getparamdata(model)) end -function _update_constraints(data::ParameterData, ::Type{S}) where S - for (cref, gaep) in _get_param_dict(data, S) - _update_constraint(data, cref, gaep) - end - return nothing -end - -function _update_constraint(data::ParameterData, cref, gaep::GAEp{C}) where C - val = 0.0 - @inbounds for (param, coef) in gaep.terms - val += coef * (data.future_values[param.ind] - data.current_values[param.ind]) - end - _update_constraint(data, cref, val) - return nothing -end - -function _update_constraint(data::ParameterData, cref, val::Number) - if !iszero(val) - ci = JuMP.index(cref) - old_set = MOI.get(cref.model.moi_backend, MOI.ConstraintSet(), ci) - # For scalar constraints, the constant in the function is zero and the - # constant is stored in the set. Since `pcr.coef` corresponds to the - # coefficient in the function, we need to take `-pcr.coef`. - new_set = _shift_constant(old_set, -val) - MOI.set(cref.model.moi_backend, MOI.ConstraintSet(), ci, new_set) - end - return nothing -end - - -function _param_optimizehook(m::JuMP.Model; kwargs...) - data = _getparamdata(m)::ParameterData - - # sync model rhs to newest parameter values - sync(data) - - ret = JuMP.optimize!(m::JuMP.Model, ignore_optimize_hook = true, kwargs...) - data.solved = true - - if !lazy_duals(data) && JuMP.has_duals(m) - fill!(data.dual_values, 0.0) - _update_duals(data, EQ) - _update_duals(data, GE) - _update_duals(data, LE) - end - return ret -end - -function _update_duals(data, ::Type{S}) where S - for (cref, gaep) in _get_param_dict(data, S) - _update_duals(data, cref, gaep) - end - return nothing -end - -function _update_duals(data, cref, gaep) - dual_sol = JuMP.dual(cref) - @inbounds for (param, coef) in gaep.terms - data.dual_values[param.ind] -= coef * dual_sol - end - return nothing -end - -""" - ModelWithParams(args...; kwargs...)::JuMP.Model - -Returns a JuMP.Model able to handle `Parameters`. - -`args` and `kwargs` are the same parameters that would be passed -to the regular `Model` constructor. - -Example using GLPK solver: - -```julia - model = ModelWithParams(with_optimizer(GLPK.Optimizer)) -``` -""" -function ModelWithParams(args...; kwargs...) - - m = JuMP.Model(args...; kwargs...) - - JuMP.set_optimize_hook(m, _param_optimizehook) - - m.ext[:params] = ParameterData() - - return m -end - -function JuMP.set_coefficient(con::CtrRef{F, S}, param::Parameter, coef::Number) where {F<:SAF, S} - data = _getparamdata(param) - dict = _get_param_dict(data, S) - old_coef = 0.0 - if haskey(dict, con) - gaep = dict[con] - old_coef = get!(gaep.terms, param, 0.0) - gaep.terms[param] = coef - else - # TODO fix type C - dict[con] = GAEp{Float64}(zero(Float64), param => coef) - end - if !iszero(coef-old_coef) - val = (coef-old_coef)*data.current_values[param.ind] - _update_constraint(data, con, val) - if !iszero(data.future_values[param.ind] - data.current_values[param.ind]) - data.sync = false - end - end - if lazy_duals(data) - ctr_map = data.constraints_map[param.ind] - found_ctr = false - if isempty(ctr_map) - push!(ctr_map, ParametrizedConstraintRef(con, coef)) - else - for (index, pctr) in enumerate(ctr_map) - if pctr.cref == con - if found_ctr - deleteat!(ctr_map, index) - else - ctr_map[index] = ParametrizedConstraintRef(con, coef) - end - found_ctr = true - end - end - end - if found_ctr && !iszero(data.future_values[param.ind]) - data.sync = false - end - end - nothing -end - -""" - delete_from_constraint(con, param::Parameter) - -Removes parameter `param` from constraint `con`. -""" -function delete_from_constraint(con::CtrRef{F, S}, param::Parameter) where {F, S} - data = _getparamdata(param) - dict = _get_param_dict(data, S) - if haskey(dict, con) - old_coef = get!(dict[con].terms, param, 0.0) - _update_constraint(data, con, (0.0-old_coef) * data.current_values[param.ind]) - delete!(dict[con].terms, param) - if !iszero(data.future_values[param.ind] - data.current_values[param.ind]) - data.sync = false - end - end - if lazy_duals(data) - ctr_map = data.constraints_map[param.ind] - found_ctr = false - for (index, pctr) in enumerate(ctr_map) - if pctr.cref == con - deleteat!(ctr_map, index) - found_ctr = true - end - end - if found_ctr && !iszero(data.future_values[param.ind]) - data.sync = false - end - end - nothing -end +# constraints +# ------------------------------------------------------------------------------ -""" - delete_from_constraints(param::Parameter) +include("constraints.jl") -Removes parameter `param` from all constraints. -""" -function delete_from_constraints(::Type{S}, param::Parameter) where S - data = _getparamdata(param) - dict = _get_param_dict(data, S) - for (con, gaep) in dict - if haskey(gaep.terms, param) - if !iszero(gaep.terms[param]) && !iszero(data.future_values[param.ind]) - data.sync = false - end - old_coef = get!(dict[con].terms, param, 0.0) - _update_constraint(data, con, (0.0-old_coef) * data.current_values[param.ind]) - delete!(gaep.terms, param) - end - end - if lazy_duals(data) - if !isempty(data.constraints_map[param.ind]) - data.constraints_map[param.ind] = ParametrizedConstraintRef[] - if !iszero(data.future_values[param.ind]) - data.sync = false - end - end - end - nothing -end +# JuMP variable interface +# ------------------------------------------------------------------------------ -function delete_from_constraints(param::Parameter) - delete_from_constraints(EQ, param) - delete_from_constraints(LE, param) - delete_from_constraints(GE, param) - # TODO lazy - nothing -end +include("variable_interface.jl") # operators and mutable arithmetics # ------------------------------------------------------------------------------ diff --git a/src/constraints.jl b/src/constraints.jl new file mode 100644 index 0000000..75c8d8b --- /dev/null +++ b/src/constraints.jl @@ -0,0 +1,274 @@ +# type 1 +# ------------------------------------------------------------------------------ + +const GAE{C,V} = JuMP.GenericAffExpr{C,V} +const GAEv{C} = JuMP.GenericAffExpr{C,JuMP.VariableRef} +const GAEp{C} = JuMP.GenericAffExpr{C,Parameter} + +mutable struct ParametrizedAffExpr{C} <: JuMP.AbstractJuMPScalar + v::JuMP.GenericAffExpr{C,JuMP.VariableRef} + p::JuMP.GenericAffExpr{C,Parameter} +end + +Base.broadcastable(expr::ParametrizedAffExpr) = Ref(expr) +# JuMP.GenericAffExpr{C,Parameter}(params::Vector{Parameter},coefs::Vector{C}) = JuMP.GenericAffExpr{C}(params,coefs,C(0.0)) + +const PAE{C} = ParametrizedAffExpr{C} +const PAEC{S} = JuMP.ScalarConstraint{PAE{Float64}, S} +const PVAEC{S} = JuMP.VectorConstraint{PAE{Float64}, S} + + +# Build constraint +# ------------------------------------------------------------------------------ + +# TODO should be in MOI, MOIU or JuMP +_shift_constant(set::S, offset) where S <: Union{MOI.LessThan,MOI.GreaterThan,MOI.EqualTo} = S(MOIU.getconstant(set) + offset) +function _shift_constant(set::MOI.Interval, offset) + MOI.Interval(set.lower + offset, set.upper + offset) +end + +function JuMP.build_constraint(_error::Function, aff::ParametrizedAffExpr, set::S) where S <: Union{MOI.LessThan,MOI.GreaterThan,MOI.EqualTo} + offset = aff.v.constant + aff.v.constant = 0.0 + shifted_set = _shift_constant(set, -offset) + return JuMP.ScalarConstraint(aff, shifted_set) +end + +function JuMP.build_constraint(_error::Function, aff::ParametrizedAffExpr, lb, ub) + JuMP.build_constraint(_error, aff, MOI.Interval(lb, ub)) +end + +function JuMP.add_constraint(m::JuMP.Model, c::PAEC{S}, name::String="") where S + + # build LinearConstraint + c_lin = JuMP.ScalarConstraint(c.func.v, c.set) + + # JuMP´s standard add_constrint + cref = JuMP.add_constraint(m, c_lin, name) + + data = _getparamdata(m)::ParameterData + + # needed for lazy get dual + if lazy_duals(data) + for (param, coef) in c.func.p.terms + push!(data.constraints_map[param.ind], ParametrizedConstraintRef(cref, coef)) + end + end + + # save the parameter part of a parametric affine expression + _get_param_dict(data, S)[cref] = c.func.p + + return cref +end + +# update constraint +# ------------------------------------------------------------------------------ + + +function _update_constraints(data::ParameterData) + _update_constraints(data, EQ) + _update_constraints(data, LE) + _update_constraints(data, GE) +end + +function _update_constraints(data::ParameterData, ::Type{S}) where S + for (cref, gaep) in _get_param_dict(data, S) + _update_constraint(data, cref, gaep) + end + return nothing +end + +function _update_constraint(data::ParameterData, cref, gaep::GAEp{C}) where C + val = 0.0 + @inbounds for (param, coef) in gaep.terms + val += coef * (data.future_values[param.ind] - data.current_values[param.ind]) + end + _update_constraint(data, cref, val) + return nothing +end + +function _update_constraint(data::ParameterData, cref, val::Number) + if !iszero(val) + ci = JuMP.index(cref) + old_set = MOI.get(cref.model.moi_backend, MOI.ConstraintSet(), ci) + # For scalar constraints, the constant in the function is zero and the + # constant is stored in the set. Since `pcr.coef` corresponds to the + # coefficient in the function, we need to take `-pcr.coef`. + new_set = _shift_constant(old_set, -val) + MOI.set(cref.model.moi_backend, MOI.ConstraintSet(), ci, new_set) + end + return nothing +end + +# Duals +# ------------------------------------------------------------------------------ + +function JuMP.dual(p::Parameter) + params = _getparamdata(p)::ParameterData + if lazy_duals(params) + return _getdual(p) + else + return params.dual_values[p.ind] + end +end + +# lazy + +function _getdual(p::Parameter) + return _getdual(p.model, p.ind) +end +function _getdual(pcr::ParametrizedConstraintRef)::Float64 + pcr.coef * JuMP.dual(pcr.cref) +end +function _getdual(model::JuMP.Model, ind::Integer) + params = _getparamdata(model)::ParameterData + # See (12) in http://www.juliaopt.org/MathOptInterface.jl/stable/apimanual.html#Duals-1 + # in the dual objective: - sum_i b_i^T y_i + # Here b_i depends on the param and is b_i' + coef * param + # so the dualobjective is: + # - sum_i b_i'^T y_i - param * sum_i coef^T y_i + # The dual of the parameter is: - sum_i coef^T y_i + return -sum(_getdual.(params.constraints_map[ind])) +end + +# non lazy + +function _update_duals(data::ParameterData) + if !lazy_duals(data) + fill!(data.dual_values, 0.0) + _update_duals(data, EQ) + _update_duals(data, GE) + _update_duals(data, LE) + end + return nothing +end + +function _update_duals(data, ::Type{S}) where S + for (cref, gaep) in _get_param_dict(data, S) + _update_duals(data, cref, gaep) + end + return nothing +end + +function _update_duals(data, cref, gaep) + dual_sol = JuMP.dual(cref) + @inbounds for (param, coef) in gaep.terms + data.dual_values[param.ind] -= coef * dual_sol + end + return nothing +end + +# constraint modification +# ------------------------------------------------------------------------------ + +function JuMP.set_coefficient(con::CtrRef{F, S}, param::Parameter, coef::Number) where {F<:SAF, S} + data = _getparamdata(param) + dict = _get_param_dict(data, S) + old_coef = 0.0 + if haskey(dict, con) + gaep = dict[con] + old_coef = get!(gaep.terms, param, 0.0) + gaep.terms[param] = coef + else + # TODO fix type C + dict[con] = GAEp{Float64}(zero(Float64), param => coef) + end + if !iszero(coef-old_coef) + val = (coef-old_coef)*data.current_values[param.ind] + _update_constraint(data, con, val) + if !iszero(data.future_values[param.ind] - data.current_values[param.ind]) + data.sync = false + end + end + if lazy_duals(data) + ctr_map = data.constraints_map[param.ind] + found_ctr = false + if isempty(ctr_map) + push!(ctr_map, ParametrizedConstraintRef(con, coef)) + else + for (index, pctr) in enumerate(ctr_map) + if pctr.cref == con + if found_ctr + deleteat!(ctr_map, index) + else + ctr_map[index] = ParametrizedConstraintRef(con, coef) + end + found_ctr = true + end + end + end + if found_ctr && !iszero(data.future_values[param.ind]) + data.sync = false + end + end + nothing +end + +""" + delete_from_constraint(con, param::Parameter) + +Removes parameter `param` from constraint `con`. +""" +function delete_from_constraint(con::CtrRef{F, S}, param::Parameter) where {F, S} + data = _getparamdata(param) + dict = _get_param_dict(data, S) + if haskey(dict, con) + old_coef = get!(dict[con].terms, param, 0.0) + _update_constraint(data, con, (0.0-old_coef) * data.current_values[param.ind]) + delete!(dict[con].terms, param) + if !iszero(data.future_values[param.ind] - data.current_values[param.ind]) + data.sync = false + end + end + if lazy_duals(data) + ctr_map = data.constraints_map[param.ind] + found_ctr = false + for (index, pctr) in enumerate(ctr_map) + if pctr.cref == con + deleteat!(ctr_map, index) + found_ctr = true + end + end + if found_ctr && !iszero(data.future_values[param.ind]) + data.sync = false + end + end + nothing +end + +""" + delete_from_constraints(param::Parameter) + +Removes parameter `param` from all constraints. +""" +function delete_from_constraints(::Type{S}, param::Parameter) where S + data = _getparamdata(param) + dict = _get_param_dict(data, S) + for (con, gaep) in dict + if haskey(gaep.terms, param) + if !iszero(gaep.terms[param]) && !iszero(data.future_values[param.ind]) + data.sync = false + end + old_coef = get!(dict[con].terms, param, 0.0) + _update_constraint(data, con, (0.0-old_coef) * data.current_values[param.ind]) + delete!(gaep.terms, param) + end + end + if lazy_duals(data) + if !isempty(data.constraints_map[param.ind]) + data.constraints_map[param.ind] = ParametrizedConstraintRef[] + if !iszero(data.future_values[param.ind]) + data.sync = false + end + end + end + nothing +end + +function delete_from_constraints(param::Parameter) + delete_from_constraints(EQ, param) + delete_from_constraints(LE, param) + delete_from_constraints(GE, param) + # TODO lazy + nothing +end diff --git a/src/variable_interface.jl b/src/variable_interface.jl new file mode 100644 index 0000000..64b5de6 --- /dev/null +++ b/src/variable_interface.jl @@ -0,0 +1,167 @@ + +# JuMP Variable interface +# ------------------------------------------------------------------------------ + +# main interface + +JuMP.is_fixed(p::Parameter) = true +JuMP.fix_index(p::Parameter) = + error("Parameters do not have have explicit constraints, hence no constraint index.") +JuMP.set_fix_index(p::Parameter, cindex) = + error("Parameters do not have have explicit constraints, hence no constraint index.") +function JuMP.fix(p::Parameter, val::Real) + params = _getparamdata(p)::ParameterData + params.sync = false + params.future_values[p.ind] = val + return nothing +end +JuMP.unfix(p::Parameter) = error("Parameters cannot be unfixed.") +function JuMP.fix_value(p::Parameter) + params = _getparamdata(p)::ParameterData + params.future_values[p.ind] +end +JuMP.FixRef(p::Parameter) = + error("Parameters do not have have explicit constraints, hence no constraint reference.") + + +""" + setvalue!(p::Parameter, val::Real)::Nothing + +Sets the parameter `p` to the new value `val`. +""" +function setvalue!(p::Parameter, val::Real) + params = _getparamdata(p)::ParameterData + params.sync = false + params.future_values[p.ind] = val + return nothing +end + +function JuMP.value(p::Parameter) + params = _getparamdata(p)::ParameterData + params.future_values[p.ind] +end + +# interface continues + +JuMP.owner_model(p::Parameter) = p.model + +struct ParameterNotOwned <: Exception + parameter::Parameter +end + +function JuMP.check_belongs_to_model(p::Parameter, model::AbstractModel) + if owner_model(p) !== model + throw(ParameterNotOwned(p)) + end +end + +Base.iszero(::Parameter) = false +Base.copy(p::Parameter) = Parameter(p.ind, p.model) +# Base.broadcastable(v::VariableRef) = Ref(v) # NEEDED??? + +""" + delete(model::Model, param::Parameter) + +Delete the parameter `param` from the model `model`. + +Note. +After the first deletion you might experience performance reduction. +Therefore, only use thid command if there is no other way around. +""" +function JuMP.delete(model::Model, param::Parameter) + error("Parameters can be deleted currently.") + if model !== owner_model(param) + error("The variable reference you are trying to delete does not " * + "belong to the model.") + end + # create dictionary map + # turn flag has_deleted +end + +""" + is_valid(model::Model, parameter::Parameter) + +Return `true` if `parameter` refers to a valid parameter in `model`. +""" +function JuMP.is_valid(model::Model, parameter::Parameter) + return model === owner_model(parameter) +end + +# The default hash is slow. It's important for the performance of AffExpr to +# define our own. +# https://github.com/JuliaOpt/MathOptInterface.jl/issues/234#issuecomment-366868878 +function Base.hash(p::Parameter, h::UInt) + return hash(objectid(owner_model(p)), hash(p.ind, h)) +end +function Base.isequal(p1::Parameter, p2::Parameter) + return owner_model(p1) === owner_model(p2) && p1.ind == p2.ind +end + +""" + index(p::Parameter)::Int + +Return the internal index of the parameter `p`. +""" +index(p::Parameter) = v.ind + +# TODO +# name +# set_name +# variable_by_name + +JuMP.has_lower_bound(p::Parameter) = false +JuMP.LowerBoundRef(p::Parameter) = + error("Parameters do not have bounds.") +JuMP.lower_bound_index(p::Parameter) = + error("Parameters do not have bounds.") +JuMP.set_lower_bound_index(p::Parameter, cindex) = + error("Parameters do not have bounds.") +JuMP.set_lower_bound(p::Parameter, lower::Number) = + error("Parameters do not have bounds.") +JuMP.delete_lower_bound(p::Parameter) = + error("Parameters do not have bounds.") +JuMP.lower_bound(p::Parameter) = + error("Parameters do not have bounds.") + +JuMP.has_upper_bound(p::Parameter) = false +JuMP.UpperBoundRef(p::Parameter) = + error("Parameters do not have bounds.") +JuMP.upper_bound_index(p::Parameter) = + error("Parameters do not have bounds.") +JuMP.set_upper_bound_index(p::Parameter, cindex) = + error("Parameters do not have bounds.") +JuMP.set_upper_bound(p::Parameter, lower::Number) = + error("Parameters do not have bounds.") +JuMP.delete_upper_bound(p::Parameter) = + error("Parameters do not have bounds.") +JuMP.upper_bound(p::Parameter) = + error("Parameters do not have bounds.") + +JuMP.is_integer(p::Parameter) = false +JuMP.integer_index(p::Parameter) = + error("Parameters do not have integrality constraints.") +JuMP.set_integer_index(p::Parameter, cindex) = + error("Parameters do not have integrality constraints.") +JuMP.set_integer(p::Parameter) = + error("Parameters do not have integrality constraints.") +JuMP.unset_integer(p::Parameter) = + error("Parameters do not have integrality constraints.") +JuMP.IntegerRef(p::Parameter) = + error("Parameters do not have integrality constraints.") + +JuMP.is_binary(p::Parameter) = false +JuMP.binary_index(p::Parameter) = + error("Parameters do not have binary constraints.") +JuMP.set_binary_index(p::Parameter, cindex) = + error("Parameters do not have binary constraints.") +JuMP.set_binary(p::Parameter) = + error("Parameters do not have binary constraints.") +JuMP.unset_binary(p::Parameter) = + error("Parameters do not have binary constraints.") +JuMP.BinaryRef(p::Parameter) = + error("Parameters do not have binary constraints.") + +JuMP.start_value(p::Parameter) = + error("Parameters do not have start values.") +JuMP.set_start_value(p::Parameter, value::Number) = + error("Parameters do not have start values.") From 45f95e00492a0026ab8b7ff2dbe639d4988495e0 Mon Sep 17 00:00:00 2001 From: joaquimgarcia_psr Date: Tue, 19 Mar 2019 14:58:24 -0300 Subject: [PATCH 2/5] start enabling deletion in a lazy fashion --- src/ParameterJuMP.jl | 21 ++++++++++ src/constraints.jl | 84 ++++++++++++++++++++------------------- src/variable_interface.jl | 27 +++++-------- 3 files changed, 75 insertions(+), 57 deletions(-) diff --git a/src/ParameterJuMP.jl b/src/ParameterJuMP.jl index 5138a4c..188388d 100644 --- a/src/ParameterJuMP.jl +++ b/src/ParameterJuMP.jl @@ -54,6 +54,10 @@ mutable struct ParameterData # solved::Bool + has_deletion::Bool + index_map::Dict{Int64, Int64} + + lazy::Bool dual_values::Vector{Float64} function ParameterData() @@ -68,11 +72,28 @@ mutable struct ParameterData Dict{CtrRef{SAF, GE}, JuMP.GenericAffExpr{Float64,Parameter}}(), # false, false, + Dict{Int64, Int64}(), + false, Float64[], ) end end +""" + index(p::Parameter)::Int64 + +Return the internal index of the parameter `p`. +""" +index(p::Parameter) = index(_getparamdata(p.model), p)::Int64 +index(model::JuMP.Model, p::Parameter) = index(_getparamdata(model), p)::Int64 +function index(data::ParameterData, p::Parameter)::Int64 + if data.has_deletion + return data.index_map[p.ind] + else + return p.ind + end +end + lazy_duals(data::ParameterData) = data.lazy lazy_duals(model::JuMP.Model) = lazy_duals(_getparamdata(model)) diff --git a/src/constraints.jl b/src/constraints.jl index 75c8d8b..6665dfc 100644 --- a/src/constraints.jl +++ b/src/constraints.jl @@ -50,8 +50,8 @@ function JuMP.add_constraint(m::JuMP.Model, c::PAEC{S}, name::String="") where S # needed for lazy get dual if lazy_duals(data) - for (param, coef) in c.func.p.terms - push!(data.constraints_map[param.ind], ParametrizedConstraintRef(cref, coef)) + for (p, coef) in c.func.p.terms + push!(data.constraints_map[index(data, p)], ParametrizedConstraintRef(cref, coef)) end end @@ -80,8 +80,9 @@ end function _update_constraint(data::ParameterData, cref, gaep::GAEp{C}) where C val = 0.0 - @inbounds for (param, coef) in gaep.terms - val += coef * (data.future_values[param.ind] - data.current_values[param.ind]) + @inbounds for (p, coef) in gaep.terms + ind = index(data, p) + val += coef * (data.future_values[ind] - data.current_values[ind]) end _update_constraint(data, cref, val) return nothing @@ -108,14 +109,14 @@ function JuMP.dual(p::Parameter) if lazy_duals(params) return _getdual(p) else - return params.dual_values[p.ind] + return params.dual_values[index(params, p)] end end # lazy function _getdual(p::Parameter) - return _getdual(p.model, p.ind) + return _getdual(p.model, index(p)) end function _getdual(pcr::ParametrizedConstraintRef)::Float64 pcr.coef * JuMP.dual(pcr.cref) @@ -143,17 +144,17 @@ function _update_duals(data::ParameterData) return nothing end -function _update_duals(data, ::Type{S}) where S +function _update_duals(data::ParameterData, ::Type{S}) where S for (cref, gaep) in _get_param_dict(data, S) _update_duals(data, cref, gaep) end return nothing end -function _update_duals(data, cref, gaep) +function _update_duals(data::ParameterData, cref, gaep) dual_sol = JuMP.dual(cref) - @inbounds for (param, coef) in gaep.terms - data.dual_values[param.ind] -= coef * dual_sol + @inbounds for (p, coef) in gaep.terms + data.dual_values[index(data, p)] -= coef * dual_sol end return nothing end @@ -161,43 +162,44 @@ end # constraint modification # ------------------------------------------------------------------------------ -function JuMP.set_coefficient(con::CtrRef{F, S}, param::Parameter, coef::Number) where {F<:SAF, S} - data = _getparamdata(param) +function JuMP.set_coefficient(con::CtrRef{F, S}, p::Parameter, coef::Number) where {F<:SAF, S} + data = _getparamdata(p) dict = _get_param_dict(data, S) old_coef = 0.0 + ind = index(data, p) if haskey(dict, con) gaep = dict[con] - old_coef = get!(gaep.terms, param, 0.0) - gaep.terms[param] = coef + old_coef = get!(gaep.terms, p, 0.0) + gaep.terms[p] = coef else # TODO fix type C - dict[con] = GAEp{Float64}(zero(Float64), param => coef) + dict[con] = GAEp{Float64}(zero(Float64), p => coef) end if !iszero(coef-old_coef) - val = (coef-old_coef)*data.current_values[param.ind] + val = (coef-old_coef)*data.current_values[ind] _update_constraint(data, con, val) - if !iszero(data.future_values[param.ind] - data.current_values[param.ind]) + if !iszero(data.future_values[ind] - data.current_values[ind]) data.sync = false end end if lazy_duals(data) - ctr_map = data.constraints_map[param.ind] + ctr_map = data.constraints_map[ind] found_ctr = false if isempty(ctr_map) push!(ctr_map, ParametrizedConstraintRef(con, coef)) else - for (index, pctr) in enumerate(ctr_map) + for (p_ind, pctr) in enumerate(ctr_map) if pctr.cref == con if found_ctr - deleteat!(ctr_map, index) + deleteat!(ctr_map, p_ind) else - ctr_map[index] = ParametrizedConstraintRef(con, coef) + ctr_map[p_ind] = ParametrizedConstraintRef(con, coef) end found_ctr = true end end end - if found_ctr && !iszero(data.future_values[param.ind]) + if found_ctr && !iszero(data.future_values[ind]) data.sync = false end end @@ -209,19 +211,20 @@ end Removes parameter `param` from constraint `con`. """ -function delete_from_constraint(con::CtrRef{F, S}, param::Parameter) where {F, S} - data = _getparamdata(param) +function delete_from_constraint(con::CtrRef{F, S}, p::Parameter) where {F, S} + data = _getparamdata(p) dict = _get_param_dict(data, S) + ind = index(data, p) if haskey(dict, con) - old_coef = get!(dict[con].terms, param, 0.0) - _update_constraint(data, con, (0.0-old_coef) * data.current_values[param.ind]) - delete!(dict[con].terms, param) - if !iszero(data.future_values[param.ind] - data.current_values[param.ind]) + old_coef = get!(dict[con].terms, p, 0.0) + _update_constraint(data, con, (0.0-old_coef) * data.current_values[ind]) + delete!(dict[con].terms, p) + if !iszero(data.future_values[ind] - data.current_values[ind]) data.sync = false end end if lazy_duals(data) - ctr_map = data.constraints_map[param.ind] + ctr_map = data.constraints_map[ind] found_ctr = false for (index, pctr) in enumerate(ctr_map) if pctr.cref == con @@ -229,7 +232,7 @@ function delete_from_constraint(con::CtrRef{F, S}, param::Parameter) where {F, S found_ctr = true end end - if found_ctr && !iszero(data.future_values[param.ind]) + if found_ctr && !iszero(data.future_values[ind]) data.sync = false end end @@ -241,23 +244,24 @@ end Removes parameter `param` from all constraints. """ -function delete_from_constraints(::Type{S}, param::Parameter) where S - data = _getparamdata(param) +function delete_from_constraints(::Type{S}, p::Parameter) where S + data = _getparamdata(p) dict = _get_param_dict(data, S) + ind = index(data, p) for (con, gaep) in dict - if haskey(gaep.terms, param) - if !iszero(gaep.terms[param]) && !iszero(data.future_values[param.ind]) + if haskey(gaep.terms, p) + if !iszero(gaep.terms[p]) && !iszero(data.future_values[ind]) data.sync = false end - old_coef = get!(dict[con].terms, param, 0.0) - _update_constraint(data, con, (0.0-old_coef) * data.current_values[param.ind]) - delete!(gaep.terms, param) + old_coef = get!(dict[con].terms, p, 0.0) + _update_constraint(data, con, (0.0-old_coef) * data.current_values[ind]) + delete!(gaep.terms, p) end end if lazy_duals(data) - if !isempty(data.constraints_map[param.ind]) - data.constraints_map[param.ind] = ParametrizedConstraintRef[] - if !iszero(data.future_values[param.ind]) + if !isempty(data.constraints_map[ind]) + data.constraints_map[ind] = ParametrizedConstraintRef[] + if !iszero(data.future_values[ind]) data.sync = false end end diff --git a/src/variable_interface.jl b/src/variable_interface.jl index 64b5de6..11cd32f 100644 --- a/src/variable_interface.jl +++ b/src/variable_interface.jl @@ -10,15 +10,15 @@ JuMP.fix_index(p::Parameter) = JuMP.set_fix_index(p::Parameter, cindex) = error("Parameters do not have have explicit constraints, hence no constraint index.") function JuMP.fix(p::Parameter, val::Real) - params = _getparamdata(p)::ParameterData - params.sync = false - params.future_values[p.ind] = val + data = _getparamdata(p)::ParameterData + data.sync = false + data.future_values[index(data, p)] = val return nothing end JuMP.unfix(p::Parameter) = error("Parameters cannot be unfixed.") function JuMP.fix_value(p::Parameter) - params = _getparamdata(p)::ParameterData - params.future_values[p.ind] + data = _getparamdata(p)::ParameterData + data.future_values[index(data, p)] end JuMP.FixRef(p::Parameter) = error("Parameters do not have have explicit constraints, hence no constraint reference.") @@ -30,15 +30,15 @@ JuMP.FixRef(p::Parameter) = Sets the parameter `p` to the new value `val`. """ function setvalue!(p::Parameter, val::Real) - params = _getparamdata(p)::ParameterData - params.sync = false - params.future_values[p.ind] = val + data = _getparamdata(p)::ParameterData + data.sync = false + data.future_values[index(data, p)] = val return nothing end function JuMP.value(p::Parameter) - params = _getparamdata(p)::ParameterData - params.future_values[p.ind] + data = _getparamdata(p)::ParameterData + data.future_values[index(data, p)] end # interface continues @@ -97,13 +97,6 @@ function Base.isequal(p1::Parameter, p2::Parameter) return owner_model(p1) === owner_model(p2) && p1.ind == p2.ind end -""" - index(p::Parameter)::Int - -Return the internal index of the parameter `p`. -""" -index(p::Parameter) = v.ind - # TODO # name # set_name From 4b4b8707ec397983ec82f8e72e09306afdee804c Mon Sep 17 00:00:00 2001 From: joaquimgarcia_psr Date: Fri, 22 Mar 2019 17:45:06 -0300 Subject: [PATCH 3/5] fix operator algebra --- src/operators.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/operators.jl b/src/operators.jl index 46e2530..a62ebda 100644 --- a/src/operators.jl +++ b/src/operators.jl @@ -61,8 +61,8 @@ Base.:(+)(lhs::JuMP.VariableRef, rhs::GAEp{C}) where {C} = PAE{C}(GAEv{C}(zero(C Base.:(-)(lhs::JuMP.VariableRef, rhs::GAEp{C}) where {C} = PAE{C}(GAEv{C}(zero(C), lhs => 1.),-rhs) # VariableRef--ParametrizedAffExpr{C} -Base.:(+)(lhs::JuMP.VariableRef, rhs::PAE{C}) where {C} = PAE{C}(GAEv{C}(zero(C), lhs => 1.),copy(rhs.p)) -Base.:(-)(lhs::JuMP.VariableRef, rhs::PAE{C}) where {C} = PAE{C}(GAEv{C}(zero(C), lhs => 1.),-rhs.p) +Base.:(+)(lhs::JuMP.VariableRef, rhs::PAE{C}) where {C} = PAE{C}(lhs + rhs.v, copy(rhs.p)) +Base.:(-)(lhs::JuMP.VariableRef, rhs::PAE{C}) where {C} = PAE{C}(lhs - rhs.v, -rhs.p) #= GenericAffExpr{C,VariableRef} From 21dcf1a4cd3c7db06f2c39b7b7ccefc16564deb7 Mon Sep 17 00:00:00 2001 From: joaquimgarcia_psr Date: Fri, 22 Mar 2019 23:22:34 -0300 Subject: [PATCH 4/5] adds - reasonable printing - missing methods - new tests - improve affexpr like interface --- src/ParameterJuMP.jl | 5 +- src/constraints.jl | 108 +++++++++++++++++++++++++++++++++++-- src/mutable_arithmetics.jl | 6 +++ src/operators.jl | 19 ++++--- src/print.jl | 42 ++++++++++++++- src/variable_interface.jl | 34 ++++++++---- test/runtests.jl | 1 + test/tests.jl | 77 ++++++++++++++++++++++++++ 8 files changed, 270 insertions(+), 22 deletions(-) diff --git a/src/ParameterJuMP.jl b/src/ParameterJuMP.jl index d174e3d..33e3bdb 100644 --- a/src/ParameterJuMP.jl +++ b/src/ParameterJuMP.jl @@ -5,6 +5,7 @@ const MOI = MathOptInterface const MOIU = MOI.Utilities using JuMP +using SparseArrays export ModelWithParams, Parameter, Parameters @@ -16,7 +17,6 @@ struct Parameter <: JuMP.AbstractJuMPScalar ind::Int64 # local reference model::JuMP.Model end -JuMP.function_string(mode, ::Parameter) = "_param_" # Reference to a constraint in which the parameter has coefficient coef struct ParametrizedConstraintRef{C} @@ -47,6 +47,8 @@ mutable struct ParameterData parameters_map_saf_in_le::Dict{CtrRef{SAF, LE}, JuMP.GenericAffExpr{Float64,Parameter}} parameters_map_saf_in_ge::Dict{CtrRef{SAF, GE}, JuMP.GenericAffExpr{Float64,Parameter}} + names::Dict{Parameter, String} + # overload addvariable # variables_map::Dict{Int64, Vector{Int64}} # variables_map_lb::Dict{Int64, Vector{Int64}} @@ -68,6 +70,7 @@ mutable struct ParameterData Dict{CtrRef{SAF, EQ}, JuMP.GenericAffExpr{Float64,Parameter}}(), Dict{CtrRef{SAF, LE}, JuMP.GenericAffExpr{Float64,Parameter}}(), Dict{CtrRef{SAF, GE}, JuMP.GenericAffExpr{Float64,Parameter}}(), + Dict{Parameter, String}(), # false, false, false, diff --git a/src/constraints.jl b/src/constraints.jl index 75c8d8b..399bb47 100644 --- a/src/constraints.jl +++ b/src/constraints.jl @@ -10,13 +10,115 @@ mutable struct ParametrizedAffExpr{C} <: JuMP.AbstractJuMPScalar p::JuMP.GenericAffExpr{C,Parameter} end -Base.broadcastable(expr::ParametrizedAffExpr) = Ref(expr) -# JuMP.GenericAffExpr{C,Parameter}(params::Vector{Parameter},coefs::Vector{C}) = JuMP.GenericAffExpr{C}(params,coefs,C(0.0)) - const PAE{C} = ParametrizedAffExpr{C} const PAEC{S} = JuMP.ScalarConstraint{PAE{Float64}, S} const PVAEC{S} = JuMP.VectorConstraint{PAE{Float64}, S} +Base.iszero(a::ParametrizedAffExpr) = iszero(a.v) && iszero(a.p) +Base.zero(::Type{ParametrizedAffExpr{C}}) where {C} = PAE{C}(zero(GAEv{C}), zero(GAEp{C})) +Base.one(::Type{ParametrizedAffExpr{C}}) where {C} = PAE{C}(one(GAEv{C}), zero(GAEp{C})) +Base.zero(a::ParametrizedAffExpr) = zero(typeof(a)) +Base.one( a::ParametrizedAffExpr) = one(typeof(a)) +Base.copy(a::ParametrizedAffExpr{C}) where C = PAE{C}(copy(a.v), copy(a.p)) +Base.broadcastable(expr::ParametrizedAffExpr) = Ref(expr) +# JuMP.GenericAffExpr{C,Parameter}(params::Vector{Parameter},coefs::Vector{C}) = JuMP.GenericAffExpr{C}(params,coefs,C(0.0)) + +ParametrizedAffExpr{C}() where {C} = zero(ParametrizedAffExpr{C}) + +function JuMP.map_coefficients_inplace!(f::Function, a::ParametrizedAffExpr) + map_coefficients_inplace!(f, a.v) + # The iterator remains valid if existing elements are updated. + for (coef, par) in linear_terms(a.p) + a.p.terms[par] = f(coef) + end + return a +end + +function JuMP.map_coefficients(f::Function, a::ParametrizedAffExpr) + return JuMP.map_coefficients_inplace!(f, copy(a)) +end + +function Base.sizehint!(a::ParametrizedAffExpr, n::Int, m::Int) + sizehint!(a.v.terms, n) + sizehint!(a.p.terms, m) +end + +function JuMP.value(ex::ParametrizedAffExpr{T}, var_value::Function) where {T} + ret = value(ex.v, var_value) + for (param, coef) in ex.p.terms + ret += coef * var_value(param) + end + ret +end + +JuMP.constant(aff::ParametrizedAffExpr) = aff.v.constant + +# iterator + +# add to expression + +function Base.isequal(aff::ParametrizedAffExpr{C}, + other::ParametrizedAffExpr{C}) where {C} + return isequal(aff.v, other.v) && +isequal(aff.p, other.p) +end + +Base.hash(aff::ParametrizedAffExpr, h::UInt) = hash(aff.v.constant, hash(aff.v.terms, h), hash(aff.p.terms, h)) + +function SparseArrays.dropzeros(aff::ParametrizedAffExpr{C}) where C + v = SparseArrays.dropzeros(aff.v) + p = SparseArrays.dropzeros(aff.p) + return PAE{C}(v,p) +end + +# Check if two AffExprs are equal after dropping zeros and disregarding the +# order. Mostly useful for testing. +function JuMP.isequal_canonical(aff::ParametrizedAffExpr{C}, other::ParametrizedAffExpr{C}) where {C} + aff_nozeros = dropzeros(aff) + other_nozeros = dropzeros(other) + # Note: This depends on equality of OrderedDicts ignoring order. + # This is the current behavior, but it seems questionable. + return isequal(aff_nozeros, other_nozeros) +end + +Base.convert(::Type{ParametrizedAffExpr{C}}, v::JuMP.VariableRef) where {C} = PAE{C}(GAEv{C}(zero(C), v => one(C)), zero(GAEp{C})) +Base.convert(::Type{ParametrizedAffExpr{C}}, v::Real) where {C} = PAE{C}(GAEv{C}(convert(C, v)), zero(GAEp{C})) + +# Check all coefficients are finite, i.e. not NaN, not Inf, not -Inf +function JuMP._assert_isfinite(a::ParametrizedAffExpr) + _assert_isfinite(v) + for (coef, par) in linear_terms(a.p) + isfinite(coef) || error("Invalid coefficient $coef on parameter $par.") + end +end + +JuMP.value(a::ParametrizedAffExpr) = JuMP.value(a, value) + +function JuMP.check_belongs_to_model(a::ParametrizedAffExpr, model::AbstractModel) + JuMP.check_belongs_to_model(a.v, model) + JuMP.check_belongs_to_model(a.p, model) +end + +# Note: No validation is performed that the variables in the AffExpr belong to +# the same model. The verification is done in `check_belongs_to_model` which +# should be called before calling `MOI.ScalarAffineFunction`. +# function MOI.ScalarAffineFunction(a::AffExpr) +# _assert_isfinite(a) +# terms = MOI.ScalarAffineTerm{Float64}[MOI.ScalarAffineTerm(t[1], +# index(t[2])) +# for t in linear_terms(a)] +# return MOI.ScalarAffineFunction(terms, a.constant) +# end +# moi_function(a::GenericAffExpr) = MOI.ScalarAffineFunction(a) +# function moi_function_type(::Type{<:GenericAffExpr{T}}) where T +# return MOI.ScalarAffineFunction{T} +# end + + +# Copy an affine expression to a new model by converting all the +# variables to the new model's variables +# TODO function Base.copy(a::GenericAffExpr, new_model::AbstractModel) + # Build constraint # ------------------------------------------------------------------------------ diff --git a/src/mutable_arithmetics.jl b/src/mutable_arithmetics.jl index f5def2e..533b216 100644 --- a/src/mutable_arithmetics.jl +++ b/src/mutable_arithmetics.jl @@ -80,9 +80,15 @@ end function JuMP.add_to_expression!(aff::PAE, new_coef, new_var::JuMP.VariableRef) JuMP.add_to_expression!(aff.v, new_coef, new_var) end +function JuMP.add_to_expression!(aff::PAE, new_var::JuMP.VariableRef) + JuMP.add_to_expression!(aff.v, new_var) +end function JuMP.add_to_expression!(aff::PAE, new_param::Parameter, new_coef) JuMP.add_to_expression!(aff.p, new_coef, new_param) end +function JuMP.add_to_expression!(aff::PAE, new_param::Parameter) + JuMP.add_to_expression!(aff.p,new_param) +end function JuMP.add_to_expression!(aff::PAE, new_coef, new_param::Parameter) JuMP.add_to_expression!(aff.p, new_coef, new_param) end \ No newline at end of file diff --git a/src/operators.jl b/src/operators.jl index a62ebda..873cef2 100644 --- a/src/operators.jl +++ b/src/operators.jl @@ -37,8 +37,9 @@ Base.:(+)(lhs::Parameter, rhs::Parameter) = PAE{Float64}(GAEv{Float64}(0.0), GAE Base.:(-)(lhs::Parameter, rhs::Parameter) = PAE{Float64}(GAEv{Float64}(0.0), GAEp{Float64}(0.0, lhs => 1.0, rhs => -1.0)) # Parameter--GAEp -# (+){C}(lhs::Parameter, rhs::GAEp{C})::GAEp{C} = GAEp{C}(vcat(rhs.vars,lhs),vcat(rhs.coeffs,one(C))) -# (-){C}(lhs::Parameter, rhs::GAEp{C})::GAEp{C} = GAEp{C}(vcat(rhs.vars,lhs),vcat(-rhs.coeffs,one(C))) +# this one is used internally only, becaus no other gunction returns a GAEp +Base.:(+)(lhs::Parameter, rhs::GAEp{C}) where C = (+)(GAEp{C}(zero(C), lhs => 1.0), rhs) +Base.:(-)(lhs::Parameter, rhs::GAEp{C}) where C = (+)(GAEp{C}(zero(C), lhs => 1.0), -rhs) # Parameter--GAEv/GenericAffExpr{C,VariableRef} Base.:(+)(lhs::Parameter, rhs::GAEv{C}) where {C} = PAE{C}(copy(rhs),GAEp{C}(zero(C), lhs => 1.)) @@ -89,11 +90,11 @@ Base.:(-)(lhs::GAEv{C}, rhs::PAE{C}) where {C} = PAE{C}(lhs-rhs.v,-rhs.p) # GenericAffExpr{C,Parameter}--VariableRef Base.:(+)(lhs::GAEp{C}, rhs::JuMP.VariableRef) where {C} = (+)(rhs,lhs) -Base.:(-)(lhs::GAEp{C}, rhs::JuMP.VariableRef) where {C} = (-)(-rhs,lhs) +Base.:(-)(lhs::GAEp{C}, rhs::JuMP.VariableRef) where {C} = (+)(-rhs,lhs) # GenericAffExpr{C,Parameter}--GenericAffExpr{C,VariableRef} Base.:(+)(lhs::GAEp{C}, rhs::GAEv{C}) where {C} = (+)(rhs,lhs) -Base.:(-)(lhs::GAEp{C}, rhs::GAEv{C}) where {C} = (-)(-rhs,lhs) +Base.:(-)(lhs::GAEp{C}, rhs::GAEv{C}) where {C} = (+)(-rhs,lhs) # GenericAffExpr{C,Parameter}--GenericAffExpr{C,Parameter} # DONE in JuMP @@ -106,23 +107,25 @@ Base.:(-)(lhs::GAEp{C}, rhs::PAE{C}) where {C} = PAE{C}(-rhs.v,lhs-rhs.p) ParametrizedAffExpr{C} =# +Base.:(-)(lhs::PAE{C}) where C = PAE{C}(-lhs.v, -lhs.p) + # Number--PAE Base.:(+)(lhs::PAE, rhs::Number) = (+)(rhs,lhs) -Base.:(-)(lhs::PAE, rhs::Number) = (-)(-rhs,lhs) +Base.:(-)(lhs::PAE, rhs::Number) = (+)(-rhs,lhs) Base.:(*)(lhs::PAE, rhs::Number) = (*)(rhs,lhs) # ParametrizedAffExpr{C}--Parameter Base.:(+)(lhs::PAE{C}, rhs::Parameter) where {C} = (+)(rhs,lhs) -Base.:(-)(lhs::PAE{C}, rhs::Parameter) where {C} = (-)(-rhs,lhs) +Base.:(-)(lhs::PAE{C}, rhs::Parameter) where {C} = (+)(-rhs,lhs) # VariableRef--ParametrizedAffExpr{C} Base.:(+)(lhs::PAE{C}, rhs::JuMP.VariableRef) where {C} = (+)(rhs,lhs) -Base.:(-)(lhs::PAE{C}, rhs::JuMP.VariableRef) where {C} = (-)(-rhs,lhs) +Base.:(-)(lhs::PAE{C}, rhs::JuMP.VariableRef) where {C} = (+)(-rhs,lhs) # ParametrizedAffExpr{C}--GenericAffExpr{C,VariableRef} # ParametrizedAffExpr{C}--GenericAffExpr{C,Parameter} Base.:(+)(lhs::PAE{C}, rhs::GAE{C,V}) where {C,V} = (+)(rhs,lhs) -Base.:(-)(lhs::PAE{C}, rhs::GAE{C,V}) where {C,V} = (-)(-rhs,lhs) +Base.:(-)(lhs::PAE{C}, rhs::GAE{C,V}) where {C,V} = (+)(-rhs,lhs) # ParametrizedAffExpr{C}--ParametrizedAffExpr{C} Base.:(+)(lhs::PAE{C}, rhs::PAE{C}) where {C} = PAE{C}(lhs.v+rhs.v,lhs.p+rhs.p) diff --git a/src/print.jl b/src/print.jl index 0125c3d..e5d774d 100644 --- a/src/print.jl +++ b/src/print.jl @@ -1,3 +1,43 @@ +function JuMP.function_string(::Type{REPLMode}, p::Parameter) + par_name = name(p) + if !isempty(par_name) + return par_name + else + return "noname_param" + end +end + +function JuMP.function_string(::Type{IJuliaMode}, p::Parameter) + par_name = name(p) + if !isempty(par_name) + # TODO: This is wrong if parameter name constains extra "]" + return replace(replace(par_name, "[" => "_{", count = 1), "]" => "}") + else + return "nonameParam" + end +end + function JuMP.function_string(mode, a::ParameterJuMP.ParametrizedAffExpr, show_constant=true) - return JuMP.function_string(mode, a.v, show_constant) + ret = "" + str_v = JuMP.function_string(mode, a.v, false) + if str_v != "0" + ret = str_v + end + str_p = JuMP.function_string(mode, a.p, false) + if str_p != "0" + if ret == "" + ret = str_p + else + if str_p[1] == '-' + ret = ret * " - " * str_p[2:end] + else + ret = ret * " + " * str_p + end + end + end + if !JuMP._is_zero_for_printing(a.v.constant) && show_constant + ret = string(ret, JuMP._sign_string(a.v.constant), + JuMP._string_round(abs(a.v.constant))) + end + return ret end \ No newline at end of file diff --git a/src/variable_interface.jl b/src/variable_interface.jl index 468ef9e..7e54d6b 100644 --- a/src/variable_interface.jl +++ b/src/variable_interface.jl @@ -76,6 +76,7 @@ function JuMP.delete(model::Model, param::Parameter) end # create dictionary map # turn flag has_deleted + # delete names ? end """ @@ -97,17 +98,32 @@ function Base.isequal(p1::Parameter, p2::Parameter) return owner_model(p1) === owner_model(p2) && p1.ind == p2.ind end -""" - index(p::Parameter)::Int - -Return the internal index of the parameter `p`. -""" index(p::Parameter) = v.ind -# TODO -# name -# set_name -# variable_by_name +function JuMP.name(p::Parameter) + dict = _getparamdata(p).names + if haskey(dict, p) + return dict[p] + else + return "" + end +end + +function JuMP.set_name(p::Parameter, s::String) + dict = _getparamdata(p).names + dict[p] = s +end + +function parameter_by_name(model::Model, name::String) + # can be improved with a lazy rev_names map + dict = _getparamdata(model).names + for (par, n) in dict + if n == name + return par + end + end + return nothing +end JuMP.has_lower_bound(p::Parameter) = false JuMP.LowerBoundRef(p::Parameter) = diff --git a/test/runtests.jl b/test/runtests.jl index 906640a..f4e1213 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -25,5 +25,6 @@ include("tests.jl") test11(factory) test12(factory) test13(factory) + test14(factory) end ; diff --git a/test/tests.jl b/test/tests.jl index e659644..14799b9 100644 --- a/test/tests.jl +++ b/test/tests.jl @@ -343,4 +343,81 @@ function test13(args...) y = Parameter(model_2, 1.0) @test_throws ErrorException ParameterJuMP.set_no_duals(model_2) end +end + +using SparseArrays + +macro test_expression(expr) + esc(quote + @test JuMP.isequal_canonical(@expression(m, $expr), $expr) + end) +end + +macro test_expression_with_string(expr, str) + esc(quote + @test string(@inferred $expr) == $str + @test_expression $expr + end) +end + +function test14(args...) + @testset "Test ParametrizedAffExpr" begin + m = ModelWithParams() + @variable(m, x) + @variable(m, y) + a = Parameter(m) + @test name(a) == "" + set_name(a, "a") + @test name(a) == "a" + b = Parameter(m) + set_name(b, "b") + + exp1 = x + y + a + @test typeof(exp1) == ParameterJuMP.ParametrizedAffExpr{Float64} + @test length(exp1.v.terms) == 2 + exp1 = exp1 + y + @test length(exp1.v.terms) == 2 + + @test iszero(ParameterJuMP.ParametrizedAffExpr{Float64}()) + @test iszero(zero(exp1)) + @test iszero(one(exp1) - one(ParameterJuMP.ParametrizedAffExpr{Float64})) + @test iszero(SparseArrays.dropzeros((exp1 - copy(exp1)))) + + empty_func(empty_arg) = 0 + exp2 = map_coefficients(Base.zero, exp1) + @test iszero(SparseArrays.dropzeros(exp2)) + @test iszero(constant(exp2)) + + @test isequal_canonical(exp1, copy(exp1)) + + exp4 = exp1 - copy(exp1) + @test iszero(SparseArrays.dropzeros(exp4)) + + # var + num + @test_expression_with_string 7.1 * x + 2.5 "7.1 x + 2.5" + @test_expression_with_string 1.2 * y + 1.2 "1.2 y + 1.2" + # par + num + @test_expression_with_string 7.1 * a + 2.5 "7.1 a + 2.5" + @test_expression_with_string 1.2 * b + 1.2 "1.2 b + 1.2" + + # par + par + num + @test_expression_with_string b + a + 1.2 "b + a + 1.2" + # par - par + num + @test_expression_with_string b - a + 1.2 "b - a + 1.2" + # par - (par - num) + @test_expression_with_string b - (a - 1.2) "b - a + 1.2" + # par - par - num + @test_expression_with_string b - a - 1.2 "b - a - 1.2" + # var + par + num + @test_expression_with_string x + a + 1.2 "x + a + 1.2" + # var + var + par + num + @test_expression_with_string x + y + a + 1.2 "x + y + a + 1.2" + # var + var - par + num + @test_expression_with_string x + y - a + 1.2 "x + y - a + 1.2" + # var + var - par + par + num + @test_expression_with_string x + y - a + b + 1.2 "x + y + b - a + 1.2" + # par + par - var + var + num + @test_expression_with_string a + b - x + y + 1.2 "y - x + a + b + 1.2" + + end end \ No newline at end of file From 02f452308aa1a5cfc9f15d3aa2b26646f8573d78 Mon Sep 17 00:00:00 2001 From: joaquimgarcia_psr Date: Sat, 23 Mar 2019 10:32:24 -0300 Subject: [PATCH 5/5] fix dependencies --- REQUIRE | 1 + src/ParameterJuMP.jl | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/REQUIRE b/REQUIRE index 2cafaf4..7a0d0e9 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,3 +1,4 @@ julia 1.0 JuMP 0.19 0.20 MathOptInterface 0.8.4 +SparseArrays diff --git a/src/ParameterJuMP.jl b/src/ParameterJuMP.jl index 33e3bdb..9ef3552 100644 --- a/src/ParameterJuMP.jl +++ b/src/ParameterJuMP.jl @@ -1,12 +1,12 @@ module ParameterJuMP +using SparseArrays + +using JuMP using MathOptInterface const MOI = MathOptInterface const MOIU = MOI.Utilities -using JuMP -using SparseArrays - export ModelWithParams, Parameter, Parameters