From ff18e988fbd72294468d3fe336a528a0cb204f8a Mon Sep 17 00:00:00 2001 From: joaquimg Date: Sun, 23 Aug 2026 20:15:50 -0300 Subject: [PATCH 1/4] Fix dual sign of equalities --- docs/src/manual.md | 2 +- docs/src/mathematical_background.md | 12 +- docs/src/reference.md | 2 - src/attributes.jl | 50 +---- src/dual_equality_constraints.jl | 34 ++-- test/Tests/test_attributes.jl | 11 +- test/Tests/test_dual_constraint_signs.jl | 229 ++++++++++++++++++++++ test/Tests/test_dual_names.jl | 16 +- test/Tests/test_dualize_conic_linear.jl | 6 +- test/Tests/test_dualize_exponential.jl | 18 +- test/Tests/test_dualize_linear.jl | 82 ++++---- test/Tests/test_dualize_power.jl | 14 +- test/Tests/test_dualize_quadratic.jl | 7 +- test/Tests/test_dualize_rsoc.jl | 16 +- test/Tests/test_dualize_sdp.jl | 16 +- test/Tests/test_dualize_soc.jl | 16 +- test/Tests/test_partial_dual_linear.jl | 20 +- test/Tests/test_partial_dual_quadratic.jl | 10 +- test/runtests.jl | 1 + 19 files changed, 398 insertions(+), 164 deletions(-) create mode 100644 test/Tests/test_dual_constraint_signs.jl diff --git a/docs/src/manual.md b/docs/src/manual.md index ef1ed022..2bc38ab7 100644 --- a/docs/src/manual.md +++ b/docs/src/manual.md @@ -205,7 +205,7 @@ When define MOI attributes to communicate information in MOI, layers like Dualiz these attributes to the right variables or constraints before or after their transformation. So the best practice is to define such variable or constraint attributes and attempt to `MOI.get` or `MOI.set` them. They is an API that you need to define for these attributes that contains the -functions are `dual_attribute`, `dual_attribute_value`, +functions are `dual_attribute`, `constrained_variable_dual_attribute`, `fixed_variable_value`, `fixed_constrained_variables_get`, `equality_constraint_get`. That's a lot of functions, some of which may never be useful for your specific attributes (e.g., if it is never used on an equality constraint) so no need diff --git a/docs/src/mathematical_background.md b/docs/src/mathematical_background.md index ae6401ee..47501ed2 100644 --- a/docs/src/mathematical_background.md +++ b/docs/src/mathematical_background.md @@ -54,8 +54,14 @@ and the dual is: ``` Note that the equality constraints have minus signs that could be flipped for -simplicity. However, for generality, we keep the minus signs so that the models -displayed here precisely match the outputs of the package. +simplicity, because negating both sides of an equality does not change it. +However, we keep the minus signs, so that the term ``- \sum_{i=1}^m A_i^T y_i`` +appears with the same sign in every dual constraint, whether that constraint +ends up being an equality (as here) or a membership in a nontrivial cone (as in +the compact form below). This makes the dual constraints directly readable as +the stationarity rows of the KKT conditions, which matters when the dual is +used to build a complementarity system. It also means the models displayed +here precisely match the outputs of the package. A linear inequality constraint ``a^T x + b \ge c`` should be interpreted as ``a^T x + b - c \in \mathbb{R}_+``, and similarly ``a^T x + b \le c`` should be @@ -179,7 +185,7 @@ and the dual is: \end{align} ``` -Note that signs changed in the constraints of the dual compared to the standard form. This is because the standard form would have negative signs in all terms in a equality constraint, which were inverted for simplicity. However, in the compact form, this operation is not allowed because it would change a nontrivial cone ``\mathcal{C}_i``. +Note that the constraints of the dual carry the same signs as in the standard form. Negating both sides of an equality would be allowed in the standard form, but it is not allowed here, because it would change a nontrivial cone ``\mathcal{V}_j^*``. Keeping a single convention for both forms means that adding or removing an explicit constraint on a primal variable does not flip the signs of the corresponding dual constraint. ##### Linear Programming diff --git a/docs/src/reference.md b/docs/src/reference.md index 30bdf97a..f136b82b 100644 --- a/docs/src/reference.md +++ b/docs/src/reference.md @@ -75,8 +75,6 @@ Dualization.PrimalDualMap ```@docs Dualization.dual_attribute -Dualization.dual_attribute_value_get -Dualization.dual_attribute_value_set Dualization.constrained_variable_dual_attribute Dualization.fixed_variable_value Dualization.fixed_constrained_variables_get diff --git a/src/attributes.jl b/src/attributes.jl index 23926483..310b9a0f 100644 --- a/src/attributes.jl +++ b/src/attributes.jl @@ -1,6 +1,3 @@ -_minus(::Nothing) = nothing -_minus(x) = -x - function _variable_attribute(attr::MOI.ConstraintPrimal) return MOI.VariablePrimal(attr.result_index) end @@ -57,34 +54,6 @@ function dual_attribute(::MOI.ConstraintDualStart) return MOI.VariablePrimalStart() end -""" - dual_attribute_value_set(attr::MOI.AbstractVariableAttribute, value) - -Used as pre-processing for `MOI.set`ting `value` for a variable. -""" -function dual_attribute_value_set end - -""" - dual_attribute_value_get(attr::MOI.AbstractVariableAttribute, value) - -Used as pre-processing for `MOI.get`ting `value` for a variable. -""" -function dual_attribute_value_get end - -function dual_attribute_value_set( - ::Union{MOI.VariablePrimal,MOI.VariablePrimalStart}, - value, -) - return _minus(value) -end - -function dual_attribute_value_get( - ::Union{MOI.VariablePrimal,MOI.VariablePrimalStart}, - value, -) - return _minus(value) -end - """ constrained_variable_dual_attribute(attr::MOI.AbstractConstraintAttribute) @@ -157,7 +126,7 @@ function MOI.set( optimizer.dual_problem.dual_model, dual_attribute(attr), data.dual_constraint, - dual_attribute_value_set(attr, value), + value, ) return end @@ -170,14 +139,15 @@ function MOI.get( primal_dual_map = optimizer.dual_problem.primal_dual_map data = primal_dual_map.primal_variable_data[vi] if isnothing(data.primal_constrained_variable_constraint) - # Classical free variable - return dual_attribute_value_get( - attr, - MOI.get( - optimizer.dual_problem.dual_model, - dual_attribute(attr), - data.dual_constraint, - ), + # Classical free variable. + # The value is used as is: dual constraints are built as + # `-A_{.j}^T y + a_j in V_j^*` for every primal variable, whether the + # resulting constraint is an equality or a membership in a nontrivial + # cone, so no sign correction is needed here. + return MOI.get( + optimizer.dual_problem.dual_model, + dual_attribute(attr), + data.dual_constraint, ) end if isnothing(data.dual_constraint) diff --git a/src/dual_equality_constraints.jl b/src/dual_equality_constraints.jl index 0ce317eb..7d59a32c 100644 --- a/src/dual_equality_constraints.jl +++ b/src/dual_equality_constraints.jl @@ -22,9 +22,14 @@ function _add_dual_equality_constraints( non_parameter_variables = setdiff(all_variables, variable_parameters) # Loop at every constraint to collect the scalar affine terms in the - # `scalar_affine_terms` list (a dics mapping variable index to + # `scalar_affine_terms` list (a dics mapping variable index to # a scalar affine function). - # TODO: flip these signs a priorie instead of require post processing later + # `scalar_affine_terms[x_j]` holds the column `A_{.j}` of the primal + # constraint matrix, with the sign it has in the primal model, while every + # dual constraint uses `-A_{.j}^T y`. + # These terms cannot be negated here: they are also returned for + # `_get_dual_objective`, whose entries for variables that are parameters + # must keep the primal sign. scalar_affine_terms = _get_scalar_affine_terms( primal_model, primal_dual_map.primal_constraint_data, @@ -39,8 +44,9 @@ function _add_dual_equality_constraints( # Collect affine terms of dual constraints that come from the quadratic # part of the primal objective function, and add them into # `scalar_affine_terms`. - # These terms are added with flipped signs (because the sign will be flipped again). - # TODO: unflip these signs + # These terms are added with the same sign convention as the terms coming + # from the primal constraints, so that all of them can be negated at once + # when the dual constraint function is built. _add_scalar_affine_terms_from_quad_obj( scalar_affine_terms, primal_dual_map.primal_var_in_quad_obj_to_dual_slack_var, @@ -49,8 +55,9 @@ function _add_dual_equality_constraints( ) # terms from mixing variables and parameters - # These terms are added with flipped signs (because the sign will be flipped again). - # TODO: unflip these signs + # As above, these are added with the primal sign convention. Note that they + # are keyed by the *variable* of each product `parameter * variable`, hence + # they never touch the parameter entries read by `_get_dual_objective`. _add_scalar_affine_terms_from_quad_params( scalar_affine_terms, primal_dual_map.primal_parameter_to_dual_parameter, @@ -66,7 +73,6 @@ function _add_dual_equality_constraints( # primal constrained variable. # If the dual set is Reals, the constraint is not added, butthe function # is cached inthe primal dual map. - # TODO: flip these signs a priori instead of requiring post-processing later _add_constrained_variable_constraint( dual_model, primal_model, @@ -90,14 +96,18 @@ function _add_dual_equality_constraints( # these are constraints associated to primal variables that are not # treated as constrained variables, that is "free variables" (x \in R) # therefore their associated dual constraints are equalities. + # The function is `-A_{.j}^T y + a_j` for minimization and + # `-A_{.j}^T y - a_j` for maximization, the same convention used by + # `_add_constrained_variable_constraint`. Negating both sides here + # would be valid for an equality, but it would make the sign of a dual + # constraint depend on whether the primal variable is free or + # constrained, see + # https://github.com/jump-dev/Dualization.jl/issues/70. dual_ci = MOI.Utilities.normalize_and_add_constraint( dual_model, MOI.ScalarAffineFunction( - # TODO: flip these two signs bellow to match _add_constrained_variable_constraint - # MOI.Utilities.operate_terms(-, scalar_affine_terms[primal_vi]), - # sense_change * get(scalar_terms, primal_vi, zero(T))), - MOI.Utilities.operate_terms(+, scalar_affine_terms[primal_vi]), - -sense_change * get(scalar_terms, primal_vi, zero(T)), + MOI.Utilities.operate_terms(-, scalar_affine_terms[primal_vi]), + sense_change * get(scalar_terms, primal_vi, zero(T)), ), MOI.EqualTo(zero(T)), ) diff --git a/test/Tests/test_attributes.jl b/test/Tests/test_attributes.jl index 3a93f4c0..cda92fa7 100644 --- a/test/Tests/test_attributes.jl +++ b/test/Tests/test_attributes.jl @@ -233,7 +233,10 @@ function _test_simple(T, dual_model) }(), )[] @test MOI.get(dual, MOI.VariablePrimalStart(), x) == 1 - @test MOI.get(dual_model, MOI.ConstraintDualStart(), dual_eq) == -1 + # The dual constraint of the free variable `x` is `-2y + 0 == 0`, so the + # primal start of `x` is stored as the dual start of that constraint with + # no sign change, see https://github.com/jump-dev/Dualization.jl/issues/70. + @test MOI.get(dual_model, MOI.ConstraintDualStart(), dual_eq) == 1 # We could set it to zero, but `nothing` should be fine for the solver, # let's only revisit if we have a convincing use case @test isnothing(MOI.get(dual_model, MOI.ConstraintPrimalStart(), dual_eq)) @@ -252,6 +255,12 @@ function _test_simple(T, dual_model) MOI.set(dual, MOI.VariablePrimalStart(), vars[], nothing) @test isnothing(MOI.get(dual, MOI.VariablePrimalStart(), vars[])) + + # `MOI.VariableName` maps to the `MOI.ConstraintName` of the associated + # dual constraint and is passed through unchanged. + MOI.set(dual, MOI.VariableName(), x, "x") + @test MOI.get(dual, MOI.VariableName(), x) == "x" + @test MOI.get(dual_model, MOI.ConstraintName(), dual_eq) == "x" return end diff --git a/test/Tests/test_dual_constraint_signs.jl b/test/Tests/test_dual_constraint_signs.jl new file mode 100644 index 00000000..a700d1d0 --- /dev/null +++ b/test/Tests/test_dual_constraint_signs.jl @@ -0,0 +1,229 @@ +# Copyright (c) 2017: Guilherme Bodin, and contributors +# +# Use of this source code is governed by an MIT-style license that can be found +# in the LICENSE.md file or at https://opensource.org/licenses/MIT. + +#= +Every dual constraint associated with a primal variable `x_j` is built as + + -A_{.j}^T y + a_j in V_j^* (minimization) + -A_{.j}^T y - a_j in V_j^* (maximization) + +where `A_{.j}` is the column of `x_j` in the primal constraints and `a_j` is +its objective coefficient. + +For an equality (`V_j^*` being `Reals`, i.e. `x_j` is a free variable) both +sides could be negated without changing the model. That is +undesirable for downstream users that read the dual constraints as the +stationarity rows of a KKT system. + +These tests pin the uniform convention down. +=# + +# Return the terms of the single dual constraint associated with the only +# primal variable of `primal_model`, as a `(coefficients, constant)` pair. +# `MOI.Utilities.normalize_and_add_constraint` moves the constant of a scalar +# constraint into the set, so the constant is read back from the set when the +# dual constraint is a scalar one. +function _dual_constraint_terms(primal_model) + dual_model = Dualization.dualize(primal_model).dual_model + cons = MOI.get(dual_model, MOI.ListOfConstraintTypesPresent()) + for (F, S) in cons + if F === MOI.VariableIndex + continue # a bound on a dual variable, not a dual constraint + end + ci = only(MOI.get(dual_model, MOI.ListOfConstraintIndices{F,S}())) + f = MOI.get(dual_model, MOI.ConstraintFunction(), ci) + set = MOI.get(dual_model, MOI.ConstraintSet(), ci) + if f isa MOI.ScalarAffineFunction + # `f in EqualTo(b)` is the same as `f - b in EqualTo(0)` + return MOI.coefficient.(f.terms), + MOI.constant(f) - MOI.constant(set) + else + scalars = MOI.Utilities.scalarize(f) + return reduce(vcat, MOI.coefficient.(s.terms) for s in scalars), + only(unique(MOI.constant.(scalars))) + end + end + return error("no dual constraint found") +end + +@testset "dual constraint signs (issue #70)" begin + # A primal with a single variable `x`, one constraint `2x >= 3` and the + # objective `11x + 13`. The dual constraint must be `-2y + 11` for `Min` + # and `-2y - 11` for `Max`, no matter how `x` is declared. + function _single_variable_model(::Type{T}, sense, variable) where {T} + model = TestModel{T}() + x, _ = variable(model) + MOI.add_constraint( + model, + MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(T(2), x)], zero(T)), + MOI.GreaterThan(T(3)), + ) + MOI.set(model, MOI.ObjectiveSense(), sense) + MOI.set( + model, + MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(), + MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(T(11), x)], T(13)), + ) + return model + end + + # `x` is free because no variable-wise constraint is added at all. + _free(model) = (MOI.add_variable(model), nothing) + # `x` is free, but stated explicitly as a constrained variable in `Reals`. + function _reals(model) + x, ci = MOI.add_constrained_variables(model, MOI.Reals(1)) + return only(x), ci + end + # `x` is free, but stated explicitly as a constrained variable in `Zeros`, + # whose dual set is `Reals`. + function _zeros_dual(model) + x, ci = MOI.add_constrained_variables(model, MOI.Zeros(1)) + return only(x), ci + end + # `x >= 0`: the dual constraint is an inequality instead of an equality. + function _nonnegative(model) + x, ci = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) + return x, ci + end + + @testset "uniform sign for $(nameof(variable))" for variable in [ + _free, + _reals, + _zeros_dual, + _nonnegative, + ] + # Minimization: `-A^T y + a_0`. + model = _single_variable_model(Float64, MOI.MIN_SENSE, variable) + coefficients, constant = _dual_constraint_terms(model) + @test coefficients == [-2.0] + @test constant == 11.0 + # Maximization: `-A^T y - a_0`. + model = _single_variable_model(Float64, MOI.MAX_SENSE, variable) + coefficients, constant = _dual_constraint_terms(model) + @test coefficients == [-2.0] + @test constant == -11.0 + end + + @testset "free variable matches Reals and Zeros" begin + # This is the invariant the issue is about: the way a free variable is + # spelled in the primal must not change the dual constraint. + for sense in [MOI.MIN_SENSE, MOI.MAX_SENSE] + reference = _dual_constraint_terms( + _single_variable_model(Float64, sense, _free), + ) + for variable in [_reals, _zeros_dual] + @test _dual_constraint_terms( + _single_variable_model(Float64, sense, variable), + ) == reference + end + end + end + + @testset "equality and inequality rows agree" begin + #= + primal + min -4x1 -3x2 -1 + s.t. + 2x1 + x2 <= 3 :y_1 + x1 + 2x2 <= 3 :y_2 + x2 >= 0 + + `x1` is free, so its dual constraint is an equality, while `x2` is + constrained, so its dual constraint is an inequality. Both must expose + the same `-A_{.j}^T y` sign; only the set differs. + =# + primal_model = TestModel{Float64}() + x1 = MOI.add_variable(primal_model) + x2, _ = MOI.add_constrained_variable(primal_model, MOI.GreaterThan(0.0)) + MOI.add_constraint(primal_model, 1.0 * x1 + 2.0 * x2, MOI.LessThan(3.0)) + MOI.set(primal_model, MOI.ObjectiveSense(), MOI.MIN_SENSE) + MOI.set( + primal_model, + MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), + -4.0 * x1 - 3.0 * x2 - 1.0, + ) + dual_model = Dualization.dualize(primal_model).dual_model + # `x1` is free: `-y - 4 == 0`. + eq_ci = only( + MOI.get( + dual_model, + MOI.ListOfConstraintIndices{ + MOI.ScalarAffineFunction{Float64}, + MOI.EqualTo{Float64}, + }(), + ), + ) + eq_f = MOI.get(dual_model, MOI.ConstraintFunction(), eq_ci) + eq_set = MOI.get(dual_model, MOI.ConstraintSet(), eq_ci) + @test MOI.coefficient.(eq_f.terms) == [-1.0] + @test MOI.constant(eq_f) - MOI.constant(eq_set) == -4.0 + # `x2 >= 0`: `-2y - 3 >= 0`. + ineq_ci = only( + MOI.get( + dual_model, + MOI.ListOfConstraintIndices{ + MOI.ScalarAffineFunction{Float64}, + MOI.GreaterThan{Float64}, + }(), + ), + ) + ineq_f = MOI.get(dual_model, MOI.ConstraintFunction(), ineq_ci) + ineq_set = MOI.get(dual_model, MOI.ConstraintSet(), ineq_ci) + @test MOI.coefficient.(ineq_f.terms) == [-2.0] + @test MOI.constant(ineq_f) - MOI.constant(ineq_set) == -3.0 + end + + @testset "quadratic objective keeps +P w" begin + #= + primal + min 0.5 * 4 * x^2 + 11x + 13 + s.t. + 2x >= 3 :y + + The dual constraint follows the KKT stationarity row + `a_0 - A^T y + P w = 0`, that is, `11 - 2y + 4w == 0`. The sign in + front of `P` is a free choice (`w` is free and only appears in a + symmetric quadratic term elsewhere), and `+P` is the one that matches + the documented stationarity condition. + + For a maximization problem with a concave objective `0.5 x^T N x`, + the documented dual row is `-a_0 - A^T y - N w = 0`, so the slack + term keeps the coefficient `-N`, which is positive again here. + =# + for (sense, a_0, w_coefficient) in + [(MOI.MIN_SENSE, 11.0, 4.0), (MOI.MAX_SENSE, -11.0, 4.0)] + primal_model = TestModel{Float64}() + x = MOI.add_variable(primal_model) + MOI.add_constraint(primal_model, 2.0 * x, MOI.GreaterThan(3.0)) + MOI.set(primal_model, MOI.ObjectiveSense(), sense) + # `Max` needs a concave objective, hence the flipped `P`. + P = sense == MOI.MIN_SENSE ? 4.0 : -4.0 + MOI.set( + primal_model, + MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{Float64}}(), + MOI.ScalarQuadraticFunction( + [MOI.ScalarQuadraticTerm(P, x, x)], + [MOI.ScalarAffineTerm(11.0, x)], + 13.0, + ), + ) + dual_model = Dualization.dualize(primal_model).dual_model + ci = only( + MOI.get( + dual_model, + MOI.ListOfConstraintIndices{ + MOI.ScalarAffineFunction{Float64}, + MOI.EqualTo{Float64}, + }(), + ), + ) + f = MOI.get(dual_model, MOI.ConstraintFunction(), ci) + set = MOI.get(dual_model, MOI.ConstraintSet(), ci) + # The dual variable `y` comes first, then the slack `w`. + @test MOI.coefficient.(f.terms) == [-2.0, w_coefficient] + @test MOI.constant(f) - MOI.constant(set) == a_0 + end + end +end diff --git a/test/Tests/test_dual_names.jl b/test/Tests/test_dual_names.jl index 0b994eb0..87f9dc12 100644 --- a/test/Tests/test_dual_names.jl +++ b/test/Tests/test_dual_names.jl @@ -48,9 +48,13 @@ @test MOI.get(dual_model, MOI.VariableName(), vi_2) == "" # Query constraint names ci_1 = - primal_dual_map.primal_variable_data[MOI.VariableIndex(1)].dual_constraint + primal_dual_map.primal_variable_data[MOI.VariableIndex( + 1, + )].dual_constraint ci_2 = - primal_dual_map.primal_variable_data[MOI.VariableIndex(2)].dual_constraint + primal_dual_map.primal_variable_data[MOI.VariableIndex( + 2, + )].dual_constraint @test MOI.get(dual_model, MOI.ConstraintName(), ci_1) == "" @test MOI.get(dual_model, MOI.ConstraintName(), ci_2) == "" @@ -76,9 +80,13 @@ @test MOI.get(dual_model, MOI.VariableName(), vi_2) == "dualvar_lessthan" # Query constraint names ci_1 = - primal_dual_map.primal_variable_data[MOI.VariableIndex(1)].dual_constraint + primal_dual_map.primal_variable_data[MOI.VariableIndex( + 1, + )].dual_constraint ci_2 = - primal_dual_map.primal_variable_data[MOI.VariableIndex(2)].dual_constraint + primal_dual_map.primal_variable_data[MOI.VariableIndex( + 2, + )].dual_constraint @test MOI.get(dual_model, MOI.ConstraintName(), ci_1) == "dualcon_x1" @test MOI.get(dual_model, MOI.ConstraintName(), ci_2) == "dualcon_x2" end diff --git a/test/Tests/test_dualize_conic_linear.jl b/test/Tests/test_dualize_conic_linear.jl index 1ff782c9..0304b971 100644 --- a/test/Tests/test_dualize_conic_linear.jl +++ b/test/Tests/test_dualize_conic_linear.jl @@ -106,7 +106,7 @@ dual max -4w_4 - 3w_5 + 12w_6 s.t - w_1 + w_3 == 3 + -w_1 - w_3 == -3 [-w_2 + 2] in Nonpositives [-w_3 - 4] in Nonnegatives =# @@ -162,9 +162,9 @@ ) eq_con1_fun = MOI.get(dual_model, MOI.ConstraintFunction(), ci_eq) eq_con1_set = MOI.get(dual_model, MOI.ConstraintSet(), ci_eq) - @test MOI.coefficient.(eq_con1_fun.terms) == [1.0; 1.0] + @test MOI.coefficient.(eq_con1_fun.terms) == [-1.0; -1.0] @test MOI.constant.(eq_con1_fun) == 0.0 - @test MOI.constant(eq_con1_set) == 3.0 + @test MOI.constant(eq_con1_set) == -3.0 ci_np = first( MOI.get( dual_model, diff --git a/test/Tests/test_dualize_exponential.jl b/test/Tests/test_dualize_exponential.jl index 5910efb3..e87f8234 100644 --- a/test/Tests/test_dualize_exponential.jl +++ b/test/Tests/test_dualize_exponential.jl @@ -55,9 +55,9 @@ dual max 2w_2 + w_1 s.t. - w_1 + w_3 == 1 - w_2 + w_4 == 1 - w_5 == 1 + -w_1 - w_3 == -1 + -w_2 - w_4 == -1 + -w_5 == -1 (w_3, w_4, w_5) ∈ DualExponentialCone =# primal_model = exp2_test() @@ -102,19 +102,19 @@ eq_con1_fun = MOI.get(dual_model, MOI.ConstraintFunction(), eq_con1) eq_con1_set = MOI.get(dual_model, MOI.ConstraintSet(), eq_con1) - @test MOI.coefficient.(eq_con1_fun.terms) == [1.0; 1.0] + @test MOI.coefficient.(eq_con1_fun.terms) == [-1.0; -1.0] @test MOI.constant.(eq_con1_fun) == 0.0 - @test MOI.constant(eq_con1_set) == 1.0 + @test MOI.constant(eq_con1_set) == -1.0 eq_con2_fun = MOI.get(dual_model, MOI.ConstraintFunction(), eq_con2) eq_con2_set = MOI.get(dual_model, MOI.ConstraintSet(), eq_con2) - @test MOI.coefficient.(eq_con2_fun.terms) == [1.0; 1.0] + @test MOI.coefficient.(eq_con2_fun.terms) == [-1.0; -1.0] @test MOI.constant.(eq_con2_fun) == 0.0 - @test MOI.constant(eq_con2_set) == 1.0 + @test MOI.constant(eq_con2_set) == -1.0 eq_con3_fun = MOI.get(dual_model, MOI.ConstraintFunction(), eq_con3) eq_con3_set = MOI.get(dual_model, MOI.ConstraintSet(), eq_con3) - @test MOI.coefficient.(eq_con3_fun.terms) == [1.0] + @test MOI.coefficient.(eq_con3_fun.terms) == [-1.0] @test MOI.constant.(eq_con3_fun) == 0.0 - @test MOI.constant(eq_con3_set) == 1.0 + @test MOI.constant(eq_con3_set) == -1.0 dual_exp_con = MOI.get( dual_model, diff --git a/test/Tests/test_dualize_linear.jl b/test/Tests/test_dualize_linear.jl index aca22432..f7692a4f 100644 --- a/test/Tests/test_dualize_linear.jl +++ b/test/Tests/test_dualize_linear.jl @@ -16,8 +16,8 @@ s.t. y_2 >= 0 y_3 <= 0 - y_2 + y_3 == 0 :x_1 - 2y_3 == -4 :x_2 + -y_2 - y_3 == 0 :x_1 + -2y_3 == 4 :x_2 =# primal_model = lp1_test() dual_model, primal_dual_map = dual_model_and_map(primal_model) @@ -68,8 +68,8 @@ standard dual max 3y_4 + 3y_3 + y_1 - 1 s.a. - y_1 + 2y_3 + y_4 = -4 :x_1 - y_2 + y_3 + 2y_4 = -3 :x_2 + -y_1 - 2y_3 - y_4 = 4 :x_1 + -y_2 - y_3 - 2y_4 = 3 :x_2 y_1 >= 0 y_2 >= 0 y_3 <= 0 @@ -77,7 +77,7 @@ compact dual max 3y_4 + 3y_3 + y_1 - 1 s.a. - y_1 + 2y_3 + y_4 = -4 :x_1 + -y_1 - 2y_3 - y_4 = 4 :x_1 - y_3 - 2y_4 >= 3 :x_2 y_1 >= 0 y_3 <= 0 @@ -135,9 +135,9 @@ }(), )[] f = MOI.get(dual_model, MOI.ConstraintFunction(), ci) - @test Set(MOI.coefficient.(f.terms)) == Set([1.0; 2.0; 1.0]) + @test Set(MOI.coefficient.(f.terms)) == Set([-1.0; -2.0; -1.0]) @test MOI.constant(f) == 0.0 - @test MOI.get(dual_model, MOI.ConstraintSet(), ci) == MOI.EqualTo(-4.0) + @test MOI.get(dual_model, MOI.ConstraintSet(), ci) == MOI.EqualTo(4.0) ci = MOI.get( dual_model, MOI.ListOfConstraintIndices{ @@ -235,7 +235,7 @@ }(), ) @test Set(MOI.get.(dual_model, MOI.ConstraintSet(), cis)) == - Set([MOI.EqualTo(-4.0), MOI.EqualTo(-3.0)]) + Set([MOI.EqualTo(4.0), MOI.EqualTo(3.0)]) end @testset "lp14_test_min" begin @@ -338,13 +338,17 @@ @test MOI.constant(f) == 0.0 @test MOI.get(dual_model, MOI.ConstraintSet(), ci) == MOI.LessThan(3.0) - # TODO flip these - # ci = MOI.get(dual_model, MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{Float64},MOI.EqualTo{Float64}}())[] - # f = MOI.get(dual_model, MOI.ConstraintFunction(), ci) - # @test Set(MOI.coefficient.(f.terms)) == Set([-7.0; +11.0; -15.0]) - # @test MOI.constant(f) == 0.0 - # @test MOI.get(dual_model, MOI.ConstraintSet(), ci) == MOI.EqualTo(-4.0) - + ci = MOI.get( + dual_model, + MOI.ListOfConstraintIndices{ + MOI.ScalarAffineFunction{Float64}, + MOI.EqualTo{Float64}, + }(), + )[] + f = MOI.get(dual_model, MOI.ConstraintFunction(), ci) + @test Set(MOI.coefficient.(f.terms)) == Set([-7.0; +11.0; -15.0]) + @test MOI.constant(f) == 0.0 + @test MOI.get(dual_model, MOI.ConstraintSet(), ci) == MOI.EqualTo(-4.0) end @testset "lp14_test_max compact" begin #= @@ -456,13 +460,17 @@ @test MOI.constant(f) == 0.0 @test MOI.get(dual_model, MOI.ConstraintSet(), ci) == MOI.LessThan(-3.0) - # TODO flip these - # ci = MOI.get(dual_model, MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{Float64},MOI.EqualTo{Float64}}())[] - # f = MOI.get(dual_model, MOI.ConstraintFunction(), ci) - # @test Set(MOI.coefficient.(f.terms)) == Set([-7.0; +11.0; -15.0]) - # @test MOI.constant(f) == 0.0 - # @test MOI.get(dual_model, MOI.ConstraintSet(), ci) == MOI.EqualTo(4.0) - + ci = MOI.get( + dual_model, + MOI.ListOfConstraintIndices{ + MOI.ScalarAffineFunction{Float64}, + MOI.EqualTo{Float64}, + }(), + )[] + f = MOI.get(dual_model, MOI.ConstraintFunction(), ci) + @test Set(MOI.coefficient.(f.terms)) == Set([-7.0; +11.0; -15.0]) + @test MOI.constant(f) == 0.0 + @test MOI.get(dual_model, MOI.ConstraintSet(), ci) == MOI.EqualTo(4.0) end @testset "lp14_test_min standard" begin #= @@ -563,23 +571,22 @@ MOI.EqualTo{Float64}, }(), ) - # TODO: review signs here @test Set(MOI.get.(dual_model, MOI.ConstraintSet(), cis)) == - Set([MOI.EqualTo(2.0), MOI.EqualTo(-3.0), MOI.EqualTo(4.0)]) + Set([MOI.EqualTo(-2.0), MOI.EqualTo(3.0), MOI.EqualTo(-4.0)]) for ci in cis set = MOI.get(dual_model, MOI.ConstraintSet(), ci) f = MOI.get(dual_model, MOI.ConstraintFunction(), ci) - if set == MOI.EqualTo(2.0) + if set == MOI.EqualTo(-2.0) @test Set(MOI.coefficient.(f.terms)) == - Set(-[-5.0; 9.0; -13.0; -1]) + Set([-5.0; 9.0; -13.0; -1]) @test MOI.constant(f) == 0.0 - elseif set == MOI.EqualTo(-3.0) + elseif set == MOI.EqualTo(3.0) @test Set(MOI.coefficient.(f.terms)) == - Set(-[6.0; -10.0; +14.0; -1]) + Set([6.0; -10.0; +14.0; -1]) @test MOI.constant(f) == 0.0 - elseif set == MOI.EqualTo(4.0) + elseif set == MOI.EqualTo(-4.0) @test Set(MOI.coefficient.(f.terms)) == - Set(-[-7.0; +11.0; -15.0]) + Set([-7.0; +11.0; -15.0]) @test MOI.constant(f) == 0.0 else error("wrong ci") @@ -685,23 +692,22 @@ MOI.EqualTo{Float64}, }(), ) - # TODO: review signs here @test Set(MOI.get.(dual_model, MOI.ConstraintSet(), cis)) == - Set([MOI.EqualTo(-2.0), MOI.EqualTo(3.0), MOI.EqualTo(-4.0)]) + Set([MOI.EqualTo(2.0), MOI.EqualTo(-3.0), MOI.EqualTo(4.0)]) for ci in cis set = MOI.get(dual_model, MOI.ConstraintSet(), ci) f = MOI.get(dual_model, MOI.ConstraintFunction(), ci) - if set == MOI.EqualTo(-2.0) + if set == MOI.EqualTo(2.0) @test Set(MOI.coefficient.(f.terms)) == - Set(-[-5.0; 9.0; -13.0; -1]) + Set([-5.0; 9.0; -13.0; -1]) @test MOI.constant(f) == 0.0 - elseif set == MOI.EqualTo(3.0) + elseif set == MOI.EqualTo(-3.0) @test Set(MOI.coefficient.(f.terms)) == - Set(-[6.0; -10.0; +14.0; -1]) + Set([6.0; -10.0; +14.0; -1]) @test MOI.constant(f) == 0.0 - elseif set == MOI.EqualTo(-4.0) + elseif set == MOI.EqualTo(4.0) @test Set(MOI.coefficient.(f.terms)) == - Set(-[-7.0; +11.0; -15.0]) + Set([-7.0; +11.0; -15.0]) @test MOI.constant(f) == 0.0 else error("wrong ci") diff --git a/test/Tests/test_dualize_power.jl b/test/Tests/test_dualize_power.jl index d13fa75a..9e7a64fb 100644 --- a/test/Tests/test_dualize_power.jl +++ b/test/Tests/test_dualize_power.jl @@ -55,9 +55,9 @@ dual min -w_2 - 2w_1 s.t. - w_1 + w_3 == 0 - w_2 + w_4 == 0 - w_5 == -1 + -w_1 - w_3 == 0 + -w_2 - w_4 == 0 + -w_5 == 1 (w_3, w_4, w_5) ∈ DualPowerCone =# primal_model = pow2_test() @@ -109,19 +109,19 @@ eq_con1_fun = MOI.get(dual_model, MOI.ConstraintFunction(), eq_con1) eq_con1_set = MOI.get(dual_model, MOI.ConstraintSet(), eq_con1) - @test MOI.coefficient.(eq_con1_fun.terms) == [1.0; 1.0] + @test MOI.coefficient.(eq_con1_fun.terms) == [-1.0; -1.0] @test MOI.constant.(eq_con1_fun) == 0.0 @test MOI.constant(eq_con1_set) == 0.0 eq_con2_fun = MOI.get(dual_model, MOI.ConstraintFunction(), eq_con2) eq_con2_set = MOI.get(dual_model, MOI.ConstraintSet(), eq_con2) - @test MOI.coefficient.(eq_con2_fun.terms) == [1.0; 1.0] + @test MOI.coefficient.(eq_con2_fun.terms) == [-1.0; -1.0] @test MOI.constant.(eq_con2_fun) == 0.0 @test MOI.constant(eq_con2_set) == 0.0 eq_con3_fun = MOI.get(dual_model, MOI.ConstraintFunction(), eq_con3) eq_con3_set = MOI.get(dual_model, MOI.ConstraintSet(), eq_con3) - @test MOI.coefficient.(eq_con3_fun.terms) == [1.0] + @test MOI.coefficient.(eq_con3_fun.terms) == [-1.0] @test MOI.constant.(eq_con3_fun) == 0.0 - @test MOI.constant(eq_con3_set) == -1.0 + @test MOI.constant(eq_con3_set) == 1.0 dual_pow_con = MOI.get(dual_model, MOI.ConstraintFunction(), pow_con) diff --git a/test/Tests/test_dualize_quadratic.jl b/test/Tests/test_dualize_quadratic.jl index 1e6fd7d4..4281e1ce 100644 --- a/test/Tests/test_dualize_quadratic.jl +++ b/test/Tests/test_dualize_quadratic.jl @@ -73,7 +73,7 @@ 1, ), ) - @test MOI.coefficient.(eq_con1_fun.terms) == [1.0; 1.0; -2.0; -1.0] + @test MOI.coefficient.(eq_con1_fun.terms) == [-1.0; -1.0; 2.0; 1.0] @test MOI.constant.(eq_con1_fun) == 0.0 @test MOI.constant(eq_con1_set) == 0.0 eq_con2_fun = MOI.get( @@ -96,8 +96,7 @@ 2, ), ) - @test MOI.coefficient.(eq_con2_fun.terms) == - [2.0; 1.0; -1.0; -2.0; -1.0] + @test MOI.coefficient.(eq_con2_fun.terms) == [-2.0; -1.0; 1.0; 2.0; 1.0] @test MOI.constant.(eq_con2_fun) == 0.0 @test MOI.constant(eq_con2_set) == 0.0 eq_con3_fun = MOI.get( @@ -120,7 +119,7 @@ 3, ), ) - @test MOI.coefficient.(eq_con3_fun.terms) == [3.0; -1.0; -2.0] + @test MOI.coefficient.(eq_con3_fun.terms) == [-3.0; 1.0; 2.0] @test MOI.constant.(eq_con3_fun) == 0.0 @test MOI.constant(eq_con3_set) == 0.0 diff --git a/test/Tests/test_dualize_rsoc.jl b/test/Tests/test_dualize_rsoc.jl index 62c17f9f..edc63f00 100644 --- a/test/Tests/test_dualize_rsoc.jl +++ b/test/Tests/test_dualize_rsoc.jl @@ -56,10 +56,10 @@ dual max w_2 + (1/2)w_1 s.t. - w_5 == -1 - w_6 == -1 - w_1 + w_3 == 0 - w_2 + w_4 == 0 + -w_5 == 1 + -w_6 == 1 + -w_1 - w_3 == 0 + -w_2 - w_4 == 0 (w_3, w_4, w_5, w_6) \in RotatedSecondOrderCone =# primal_model = rsoc2_test() @@ -104,14 +104,14 @@ eq_con1_fun = MOI.get(dual_model, MOI.ConstraintFunction(), eq_con1) eq_con1_set = MOI.get(dual_model, MOI.ConstraintSet(), eq_con1) - @test MOI.coefficient.(eq_con1_fun.terms) == [1.0] + @test MOI.coefficient.(eq_con1_fun.terms) == [-1.0] @test MOI.constant.(eq_con1_fun) == 0.0 - @test MOI.constant(eq_con1_set) == -1.0 + @test MOI.constant(eq_con1_set) == 1.0 eq_con2_fun = MOI.get(dual_model, MOI.ConstraintFunction(), eq_con2) eq_con2_set = MOI.get(dual_model, MOI.ConstraintSet(), eq_con2) - @test MOI.coefficient.(eq_con2_fun.terms) == [1.0] + @test MOI.coefficient.(eq_con2_fun.terms) == [-1.0] @test MOI.constant.(eq_con2_fun) == 0.0 - @test MOI.constant(eq_con2_set) == -1.0 + @test MOI.constant(eq_con2_set) == 1.0 rsoc_con = MOI.get( dual_model, diff --git a/test/Tests/test_dualize_sdp.jl b/test/Tests/test_dualize_sdp.jl index a588c555..4b62663c 100644 --- a/test/Tests/test_dualize_sdp.jl +++ b/test/Tests/test_dualize_sdp.jl @@ -54,9 +54,9 @@ dual max y_1 s.t. - y_2 == 1 - y_1 + 2y_3 == 0 - y_4 == 1 + -y_2 == -1 + -y_1 - 2y_3 == 0 + -y_4 == -1 [y_2 y_3 y_3 y_4] in PSD =# @@ -109,19 +109,19 @@ eq_con1_fun = MOI.get(dual_model, MOI.ConstraintFunction(), eq_con1) eq_con1_set = MOI.get(dual_model, MOI.ConstraintSet(), eq_con1) - @test MOI.coefficient.(eq_con1_fun.terms) == [1.0] + @test MOI.coefficient.(eq_con1_fun.terms) == [-1.0] @test MOI.constant.(eq_con1_fun) == 0.0 - @test MOI.constant(eq_con1_set) == 1.0 + @test MOI.constant(eq_con1_set) == -1.0 eq_con2_fun = MOI.get(dual_model, MOI.ConstraintFunction(), eq_con2) eq_con2_set = MOI.get(dual_model, MOI.ConstraintSet(), eq_con2) - @test MOI.coefficient.(eq_con2_fun.terms) == [1.0; 2.0] + @test MOI.coefficient.(eq_con2_fun.terms) == [-1.0; -2.0] @test MOI.constant.(eq_con2_fun) == 0.0 @test MOI.constant(eq_con2_set) == 0.0 eq_con3_fun = MOI.get(dual_model, MOI.ConstraintFunction(), eq_con3) eq_con3_set = MOI.get(dual_model, MOI.ConstraintSet(), eq_con3) - @test MOI.coefficient.(eq_con3_fun.terms) == [1.0] + @test MOI.coefficient.(eq_con3_fun.terms) == [-1.0] @test MOI.constant.(eq_con3_fun) == 0.0 - @test MOI.constant(eq_con3_set) == 1.0 + @test MOI.constant(eq_con3_set) == -1.0 sdp_con = MOI.get(dual_model, MOI.ConstraintFunction(), spd_con) diff --git a/test/Tests/test_dualize_soc.jl b/test/Tests/test_dualize_soc.jl index 152c32b7..303e4dd2 100644 --- a/test/Tests/test_dualize_soc.jl +++ b/test/Tests/test_dualize_soc.jl @@ -50,9 +50,9 @@ dual min -w_4 s.t. - w_2 == -1 - w_3 == -1 - w_1 + w_4 == 0 + -w_2 == 1 + -w_3 == 1 + -w_1 - w_4 == 0 w_1 >= ||(w_2, w_3)|| =# primal_model = soc2_test() @@ -93,19 +93,19 @@ eq_con1_fun = MOI.get(dual_model, MOI.ConstraintFunction(), eq_con1) eq_con1_set = MOI.get(dual_model, MOI.ConstraintSet(), eq_con1) - @test MOI.coefficient.(eq_con1_fun.terms) == [1.0; 1.0] + @test MOI.coefficient.(eq_con1_fun.terms) == [-1.0; -1.0] @test MOI.constant.(eq_con1_fun) == 0.0 @test MOI.constant(eq_con1_set) == 0.0 eq_con2_fun = MOI.get(dual_model, MOI.ConstraintFunction(), eq_con2) eq_con2_set = MOI.get(dual_model, MOI.ConstraintSet(), eq_con2) - @test MOI.coefficient.(eq_con2_fun.terms) == [1.0] + @test MOI.coefficient.(eq_con2_fun.terms) == [-1.0] @test MOI.constant.(eq_con2_fun) == 0.0 - @test MOI.constant(eq_con2_set) == -1.0 + @test MOI.constant(eq_con2_set) == 1.0 eq_con3_fun = MOI.get(dual_model, MOI.ConstraintFunction(), eq_con3) eq_con3_set = MOI.get(dual_model, MOI.ConstraintSet(), eq_con3) - @test MOI.coefficient.(eq_con3_fun.terms) == [1.0] + @test MOI.coefficient.(eq_con3_fun.terms) == [-1.0] @test MOI.constant.(eq_con3_fun) == 0.0 - @test MOI.constant(eq_con3_set) == -1.0 + @test MOI.constant(eq_con3_set) == 1.0 soc_con = MOI.get( dual_model, diff --git a/test/Tests/test_partial_dual_linear.jl b/test/Tests/test_partial_dual_linear.jl index 3cd4c712..1d6e8179 100644 --- a/test/Tests/test_partial_dual_linear.jl +++ b/test/Tests/test_partial_dual_linear.jl @@ -17,7 +17,7 @@ s.t. y_2 >= 0 y_3 <= 0 - y_2 + y_3 == 0 :x_1 + -y_2 - y_3 == 0 :x_1 =# primal_model = lp1_test() dual = Dualization.dualize( @@ -77,7 +77,7 @@ 1, ), ) - @test MOI.coefficient.(eq_con1_fun.terms) == [1.0; 1.0] + @test MOI.coefficient.(eq_con1_fun.terms) == [-1.0; -1.0] @test MOI.constant.(eq_con1_fun) == 0.0 @test MOI.constant(eq_con1_set) == 0.0 @@ -244,9 +244,9 @@ dual obj ignored s.t. - # y_1 + y_3 == 0 :x_1 - y_2 + 2y_3 == 0 :x_2 - # y_3 == 4.0 :x_3 + # -y_1 - y_3 == 0 :x_1 + -y_2 - 2y_3 == 0 :x_2 + # -y_3 == -4.0 :x_3 # y_1 <= 0 y_2 <= 0 y_3 <= 0 @@ -303,7 +303,7 @@ 1, ), ) - @test MOI.coefficient.(eq_con2_fun.terms) == [2.0; 1.0] + @test MOI.coefficient.(eq_con2_fun.terms) == [-2.0; -1.0] @test MOI.constant.(eq_con2_fun) == 0.0 @test MOI.constant(eq_con2_set) == 0.0 @@ -347,9 +347,9 @@ dual obj ignored s.t. - # y_1 + y_3 == 0 :x_1 - y_2 + 2y_3 == 0 :x_2 - # y_3 == 4.0 :x_3 + # -y_1 - y_3 == 0 :x_1 + -y_2 - 2y_3 == 0 :x_2 + # -y_3 == -4.0 :x_3 # y_1 <= 0 y_2 <= 0 y_3 <= 0 @@ -410,7 +410,7 @@ 1, ), ) - @test MOI.coefficient.(eq_con2_fun.terms) == [2.0; 1.0] + @test MOI.coefficient.(eq_con2_fun.terms) == [-2.0; -1.0] @test MOI.constant.(eq_con2_fun) == 0.0 @test MOI.constant(eq_con2_set) == 0.0 diff --git a/test/Tests/test_partial_dual_quadratic.jl b/test/Tests/test_partial_dual_quadratic.jl index 14af7ac8..12ca9b5f 100644 --- a/test/Tests/test_partial_dual_quadratic.jl +++ b/test/Tests/test_partial_dual_quadratic.jl @@ -77,7 +77,7 @@ 1, ), ) - @test MOI.coefficient.(eq_con1_fun.terms) == [1.0; 1.0; -2.0; -1.0] + @test MOI.coefficient.(eq_con1_fun.terms) == [-1.0; -1.0; 2.0; 1.0] @test MOI.constant.(eq_con1_fun) == 0.0 @test MOI.constant(eq_con1_set) == 0.0 eq_con2_fun = MOI.get( @@ -100,8 +100,7 @@ 2, ), ) - @test MOI.coefficient.(eq_con2_fun.terms) == - [2.0; 1.0; -1.0; -1.0; -2.0] + @test MOI.coefficient.(eq_con2_fun.terms) == [-2.0; -1.0; 1.0; 1.0; 2.0] @test MOI.constant.(eq_con2_fun) == 0.0 @test MOI.constant(eq_con2_set) == 0.0 @@ -226,7 +225,7 @@ 1, ), ) - @test MOI.coefficient.(eq_con1_fun.terms) == [1.0; 1.0; -2.0; -1.0] + @test MOI.coefficient.(eq_con1_fun.terms) == [-1.0; -1.0; 2.0; 1.0] @test MOI.constant.(eq_con1_fun) == 0.0 @test MOI.constant(eq_con1_set) == 0.0 eq_con2_fun = MOI.get( @@ -249,8 +248,7 @@ 2, ), ) - @test MOI.coefficient.(eq_con2_fun.terms) == - [2.0; 1.0; -1.0; -1.0; -2.0] + @test MOI.coefficient.(eq_con2_fun.terms) == [-2.0; -1.0; 1.0; 1.0; 2.0] @test MOI.constant.(eq_con2_fun) == 0.0 @test MOI.constant(eq_con2_set) == 0.0 diff --git a/test/runtests.jl b/test/runtests.jl index efb6f59a..b68c3086 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -73,6 +73,7 @@ include("Tests/test_dualize_exponential.jl") include("Tests/test_dualize_power.jl") include("Tests/test_dualize_quadratic.jl") include("Tests/test_dual_names.jl") +include("Tests/test_dual_constraint_signs.jl") include("Tests/test_partial_dual_linear.jl") include("Tests/test_partial_dual_quadratic.jl") From 529eebe7fa6ed09156766352c06c5985eb40d1c1 Mon Sep 17 00:00:00 2001 From: joaquimg Date: Sun, 23 Aug 2026 22:00:05 -0300 Subject: [PATCH 2/4] Fix docs and format --- src/dual_names.jl | 2 +- test/Tests/test_dual_names.jl | 16 ++++------------ 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/dual_names.jl b/src/dual_names.jl index 153f2689..d169cb9f 100644 --- a/src/dual_names.jl +++ b/src/dual_names.jl @@ -30,7 +30,7 @@ Subject to julia> print(dual_model) Max dual_var_c Subject to - dual_con_x : dual_var_c = 1 + dual_con_x : -dual_var_c == -1 dual_var_c ≥ 0 ``` """ diff --git a/test/Tests/test_dual_names.jl b/test/Tests/test_dual_names.jl index 87f9dc12..0b994eb0 100644 --- a/test/Tests/test_dual_names.jl +++ b/test/Tests/test_dual_names.jl @@ -48,13 +48,9 @@ @test MOI.get(dual_model, MOI.VariableName(), vi_2) == "" # Query constraint names ci_1 = - primal_dual_map.primal_variable_data[MOI.VariableIndex( - 1, - )].dual_constraint + primal_dual_map.primal_variable_data[MOI.VariableIndex(1)].dual_constraint ci_2 = - primal_dual_map.primal_variable_data[MOI.VariableIndex( - 2, - )].dual_constraint + primal_dual_map.primal_variable_data[MOI.VariableIndex(2)].dual_constraint @test MOI.get(dual_model, MOI.ConstraintName(), ci_1) == "" @test MOI.get(dual_model, MOI.ConstraintName(), ci_2) == "" @@ -80,13 +76,9 @@ @test MOI.get(dual_model, MOI.VariableName(), vi_2) == "dualvar_lessthan" # Query constraint names ci_1 = - primal_dual_map.primal_variable_data[MOI.VariableIndex( - 1, - )].dual_constraint + primal_dual_map.primal_variable_data[MOI.VariableIndex(1)].dual_constraint ci_2 = - primal_dual_map.primal_variable_data[MOI.VariableIndex( - 2, - )].dual_constraint + primal_dual_map.primal_variable_data[MOI.VariableIndex(2)].dual_constraint @test MOI.get(dual_model, MOI.ConstraintName(), ci_1) == "dualcon_x1" @test MOI.get(dual_model, MOI.ConstraintName(), ci_2) == "dualcon_x2" end From 8bbb9ca1ea1ee7780e86d146dc8131ed974a9d9a Mon Sep 17 00:00:00 2001 From: joaquimg Date: Sun, 23 Aug 2026 22:08:12 -0300 Subject: [PATCH 3/4] fix dsoc --- src/dual_names.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dual_names.jl b/src/dual_names.jl index d169cb9f..2836b5d2 100644 --- a/src/dual_names.jl +++ b/src/dual_names.jl @@ -30,7 +30,7 @@ Subject to julia> print(dual_model) Max dual_var_c Subject to - dual_con_x : -dual_var_c == -1 + dual_con_x : -dual_var_c = -1 dual_var_c ≥ 0 ``` """ From f20901ebb31b94d81639c7215f69af46b580b213 Mon Sep 17 00:00:00 2001 From: Joaquim Date: Wed, 26 Aug 2026 01:20:12 -0300 Subject: [PATCH 4/4] Apply suggestion from @joaquimg --- src/attributes.jl | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/attributes.jl b/src/attributes.jl index 310b9a0f..1f715be0 100644 --- a/src/attributes.jl +++ b/src/attributes.jl @@ -139,11 +139,6 @@ function MOI.get( primal_dual_map = optimizer.dual_problem.primal_dual_map data = primal_dual_map.primal_variable_data[vi] if isnothing(data.primal_constrained_variable_constraint) - # Classical free variable. - # The value is used as is: dual constraints are built as - # `-A_{.j}^T y + a_j in V_j^*` for every primal variable, whether the - # resulting constraint is an equality or a membership in a nontrivial - # cone, so no sign correction is needed here. return MOI.get( optimizer.dual_problem.dual_model, dual_attribute(attr),