From 111532a2bdb322a1f72bab0902bbbd213bab25ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 14 Jul 2026 18:18:03 +0200 Subject: [PATCH 1/2] Fix iterators for generic arrays --- src/operators.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/operators.jl b/src/operators.jl index 1fd2308..b3cd206 100644 --- a/src/operators.jl +++ b/src/operators.jl @@ -91,11 +91,11 @@ function Base.show(io::IO, i::IteratorValues) end function _tuple(axe) - if axe[1] isa Tuple + # axe can also be `keys(dict)` + if !isempty(axe) && first(axe) isa Tuple return axe - else - return tuple.(axe) end + return tuple.(axe) end function iterators(axes) From 2913af80394ba8a24b1cca5eefd1aec70a233f31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Fri, 17 Jul 2026 14:14:23 +0000 Subject: [PATCH 2/2] Add integration test for indexing a constraint family over dict keys --- test/generic_index_set.jl | 62 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 test/generic_index_set.jl diff --git a/test/generic_index_set.jl b/test/generic_index_set.jl new file mode 100644 index 0000000..29df2fd --- /dev/null +++ b/test/generic_index_set.jl @@ -0,0 +1,62 @@ +# Copyright (c) 2024: Benoît Legat 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. + +# Integration test for using a non-`Vector` index set (e.g. `keys(dict)`) with a GenOpt +# `container` constraint. +# +# Why it is useful: models are frequently indexed by the keys of a `Dict` (component ids, +# names, ...) rather than by `1:n`, exactly like PowerModels. `keys(dict)` is iterable but not +# integer-indexable, so the iterator machinery must not assume `axe[1]`. This builds a +# vectorized constraint indexed over `keys(dict)` and checks it solves. + +module TestGenericIndexSet + +using Test +import JuMP +import GenOpt +import HiGHS +import MathOptInterface as MOI + +function runtests() + for name in names(@__MODULE__; all = true) + if startswith("$(name)", "test_") + @testset "$(name)" begin + getfield(@__MODULE__, name)() + end + end + end + return +end + +function _model() + inner = HiGHS.Optimizer() + MOI.set(inner, MOI.RawOptimizerAttribute("output_flag"), false) + optimizer = MOI.Bridges.full_bridge_optimizer(inner, Float64) + MOI.Bridges.add_bridge(optimizer, GenOpt.FunctionGeneratorBridge{Float64}) + return JuMP.direct_model(optimizer) +end + +function test_constraint_indexed_over_dict_keys() + demand = Dict(1 => 3.0, 2 => 5.0, 3 => 4.0) + model = _model() + JuMP.@variable(model, x[1:3, 1:1]) + JuMP.@objective(model, Min, sum(x)) + # Index the family of constraints by `keys(demand)`, not `1:n`. + JuMP.@constraint( + model, + [i in keys(demand)], + x[i, 1] >= demand[i], + container = GenOpt.ParametrizedArray, + ) + JuMP.optimize!(model) + @test JuMP.termination_status(model) == MOI.OPTIMAL + @test JuMP.value(x[1, 1]) ≈ 3.0 + @test JuMP.value(x[2, 1]) ≈ 5.0 + @test JuMP.value(x[3, 1]) ≈ 4.0 +end + +end + +TestGenericIndexSet.runtests()