diff --git a/.SpellCheck.toml b/.SpellCheck.toml new file mode 100644 index 00000000..3f0c2656 --- /dev/null +++ b/.SpellCheck.toml @@ -0,0 +1,9 @@ +[default] +extend-ignore-identifiers-re = [ + # Match any identifier that contains "OT" + ".*OT.*" +] + +[default.extend-identifiers] +# Explicitly accept "OT" +OT = "OT" \ No newline at end of file diff --git a/.github/workflows/CompatHelper.yml b/.github/workflows/CompatHelper.yml new file mode 100644 index 00000000..f889647c --- /dev/null +++ b/.github/workflows/CompatHelper.yml @@ -0,0 +1,43 @@ +on: + schedule: + - cron: 0 0 * * * + workflow_dispatch: +permissions: + contents: write + pull-requests: write +jobs: + CompatHelper: + runs-on: ubuntu-latest + steps: + - name: Check if Julia is already available in the PATH + id: julia_in_path + run: which julia + continue-on-error: true + - name: Install Julia, but only if it is not already available in the PATH + uses: julia-actions/setup-julia@v2 + with: + version: "1" + arch: ${{ runner.arch }} + if: steps.julia_in_path.outcome != 'success' + - name: "Add the General registry via Git" + run: | + import Pkg + ENV["JULIA_PKG_SERVER"] = "" + Pkg.Registry.add("General") + shell: julia --color=yes {0} + - name: "Install CompatHelper" + run: | + import Pkg + name = "CompatHelper" + uuid = "aa819f21-2bde-4658-8897-bab36330d9b7" + version = "3" + Pkg.add(; name, uuid, version) + shell: julia --color=yes {0} + - name: "Run CompatHelper" + run: | + import CompatHelper + CompatHelper.main() + shell: julia --color=yes {0} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }} diff --git a/.github/workflows/Documenter.yml b/.github/workflows/Documenter.yml index 106e8374..e99ed3c8 100644 --- a/.github/workflows/Documenter.yml +++ b/.github/workflows/Documenter.yml @@ -11,10 +11,10 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: julia-actions/setup-julia@latest with: - version: '1.6' + version: '1' - name: Install dependencies run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' - name: Build and deploy diff --git a/.github/workflows/GitHubCI.yml b/.github/workflows/GitHubCI.yml index bed05606..1260cd97 100644 --- a/.github/workflows/GitHubCI.yml +++ b/.github/workflows/GitHubCI.yml @@ -3,11 +3,46 @@ on: pull_request: branches: - master + - dev + paths-ignore: + - "docs/**" push: branches: - master - tags: '*' + paths-ignore: + - "docs/**" jobs: + # formatter: + # runs-on: ${{ matrix.os }} + # strategy: + # matrix: + # julia-version: [1] + # julia-arch: [x86] + # os: [ubuntu-latest] + # steps: + # - uses: julia-actions/setup-julia@latest + # with: + # version: ${{ matrix.julia-version }} + + # - uses: actions/checkout@v4 + # - name: Install JuliaFormatter and format + # # This will use the latest version by default but you can set the version like so: + # # + # # julia -e 'using Pkg; Pkg.add(PackageSpec(name="JuliaFormatter", version="0.13.0"))' + # run: | + # julia -e 'using Pkg; Pkg.add(PackageSpec(name="JuliaFormatter", version="1.0.50"))' + # julia -e 'using JuliaFormatter; format(".", verbose=true)' + # - name: Format check + # run: | + # julia -e ' + # out = Cmd(`git diff`) |> read |> String + # if out == "" + # exit(0) + # else + # @error "Some files have not been formatted !!!" + # write(stdout, out) + # exit(1) + # end' test: name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} runs-on: ${{ matrix.os }} @@ -15,18 +50,32 @@ jobs: fail-fast: false matrix: version: - - '1.6' + - 'lts' + - '1' + - 'pre' os: - ubuntu-latest arch: - x64 + - x86 + include: + # test macOS and Windows with latest Julia only + - os: macOS-latest + arch: x64 + version: 1 + - os: windows-latest + arch: x64 + version: 1 + - os: windows-latest + arch: x86 + version: 1 steps: - - uses: actions/checkout@v2 - - uses: julia-actions/setup-julia@v1 + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v2 with: version: ${{ matrix.version }} arch: ${{ matrix.arch }} - - uses: actions/cache@v1 + - uses: actions/cache@v4 env: cache-name: cache-artifacts with: @@ -39,7 +88,7 @@ jobs: - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 - uses: julia-actions/julia-processcoverage@v1 - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v4 with: file: lcov.info - uses: coverallsapp/github-action@master diff --git a/.github/workflows/SpellCheck.yml b/.github/workflows/SpellCheck.yml new file mode 100644 index 00000000..b2417364 --- /dev/null +++ b/.github/workflows/SpellCheck.yml @@ -0,0 +1,15 @@ +name: Spell Check + +on: [pull_request] + +jobs: + typos-check: + name: Spell Check with Typos + runs-on: ubuntu-latest + steps: + - name: Checkout Actions Repository + uses: actions/checkout@v4 + - name: Check spelling + uses: crate-ci/typos@master + with: + config: ./.SpellCheck.toml diff --git a/.github/workflows/TagBot.yml b/.github/workflows/TagBot.yml index f49313b6..ef908969 100644 --- a/.github/workflows/TagBot.yml +++ b/.github/workflows/TagBot.yml @@ -4,6 +4,22 @@ on: types: - created workflow_dispatch: + inputs: + lookback: + default: "3" +permissions: + actions: read + checks: read + contents: write + deployments: read + issues: read + discussions: read + packages: read + pages: read + pull-requests: read + repository-projects: read + security-events: read + statuses: read jobs: TagBot: if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' @@ -12,4 +28,4 @@ jobs: - uses: JuliaRegistries/TagBot@v1 with: token: ${{ secrets.GITHUB_TOKEN }} - ssh: ${{ secrets.DOCUMENTER_KEY }} + ssh: ${{ secrets.DOCUMENTER_KEY }} \ No newline at end of file diff --git a/.github/workflows/register.yml b/.github/workflows/register.yml new file mode 100644 index 00000000..5b7cd3b3 --- /dev/null +++ b/.github/workflows/register.yml @@ -0,0 +1,16 @@ +name: Register Package +on: + workflow_dispatch: + inputs: + version: + description: Version to register or component to bump + required: true +jobs: + register: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: julia-actions/RegisterAction@latest + with: + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/docs/make.jl b/docs/make.jl index a38fa00a..e622ba25 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -4,21 +4,18 @@ using Documenter, ConstraintProgrammingExtensions makedocs( sitename="ConstraintProgrammingExtensions", - format = Documenter.HTML( + format=Documenter.HTML( # See https://github.com/JuliaDocs/Documenter.jl/issues/868 - prettyurls = get(ENV, "CI", nothing) == "true", - mathengine = Documenter.MathJax2(), - collapselevel = 1, + prettyurls=get(ENV, "CI", nothing) == "true", + mathengine=Documenter.MathJax2(), + collapselevel=1, ), - strict = true, - modules = [ConstraintProgrammingExtensions], - checkdocs = :exports, - pages = [ + strict=true, + modules=[ConstraintProgrammingExtensions], + checkdocs=:exports, + pages=[ "Introduction" => "index.md", - "Reference" => [ - "reference/sets.md", - "reference/bridges_sets.md", - ], + "Reference" => ["reference/sets.md", "reference/bridges_sets.md"], "Comparison to other CP packages" => [ "mappings/constraintsolver.md", "mappings/cplexcp.md", @@ -30,11 +27,11 @@ makedocs( "mappings/numberjack.md", "mappings/sas.md", "mappings/yalmip.md", - ] + ], ], ) deploydocs( - push_preview = true, - repo = "github.com/dourouc05/ConstraintProgrammingExtensions.jl.git", + push_preview=true, + repo="github.com/JuliaConstraints/ConstraintProgrammingExtensions.jl.git", ) diff --git a/docs/src/mappings/minizinc.md b/docs/src/mappings/minizinc.md index 9e775205..93253626 100644 --- a/docs/src/mappings/minizinc.md +++ b/docs/src/mappings/minizinc.md @@ -2,19 +2,19 @@ MiniZinc has a similar goal to this project: a common modelling interface for many underlying solvers. It is based on a similar concept to that of bridges, but with much less flexibility: each high-level constraint is mapped in a fixed way onto lower-level constraints. -* Basic CP constraints: - * Domain: +* Basic CP constraints: + * Domain: * Fixed: `CP.Domain` * Variable: `CP.Membership` * Multivalued: `CP.VectorDomain` * [`table`](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/table.mzn): [one binary variable per possible combination](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/linear/fzn_table_int.mzn) - * All different: + * All different: * Base: `CP.AllDifferent` * [`all_different`](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/all_different.mzn): mapped onto [a MILP-like model](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/fzn_all_different_int.mzn). * [`all_different_reif`](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/all_different.mzn): similar, [with an equivalence](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/fzn_all_different_int_reif.mzn). - * These constraints are available in two includes: `all_different.mzn` and `alldifferent.mzn`. + * These constraints are available in two includes: `all_different.mzn` and `alldifferent.mzn`. * All different except constants: `CP.AllDifferentExceptConstants` - * [`alldifferent_except_0`](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/alldifferent_except_0.mzn) (one excluded value: 0) and [`alldifferent_except`](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/alldifferent_except.mzn) (set of excluded values): either mapped [onto neq and disjunctions or onto GCC](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/fzn_alldifferent_except.mzn). + * [`alldifferent_except_0`](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/alldifferent_except_0.mzn) (one excluded value: 0) and [`alldifferent_except`](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/alldifferent_except.mzn) (set of excluded values): either mapped [onto neq and disjunctions or onto GCC](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/fzn_alldifferent_except.mzn). * [`alldifferent_except_0_reif`](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/alldifferent_except_0.mzn) (one excluded value: 0) and [`alldifferent_except_reif`](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/alldifferent_except.mzn) (set of excluded values): the reified versions are only mapped [onto neq and disjunctions](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/fzn_alldifferent_except_reif.mzn). * With symmetry: `SymmetricAllDifferent` * [`symmetric_all_different`](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/symmetric_all_different.mzn): [`all_different` and `inverse`](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/fzn_symmetric_all_different.mzn) @@ -37,7 +37,7 @@ MiniZinc has a similar goal to this project: a common modelling interface for ma * [`nvalue_fn`](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/nvalue_fn.mzn): function. * Inversion: `CP.Inverse` * [`inverse`](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/inverse.mzn): [index computations](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/fzn_inverse.mzn). - * [`inverse_reif`](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/inverse.mzn): similar, [wih an equivalence](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/fzn_inverse_reif.mzn). + * [`inverse_reif`](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/inverse.mzn): similar, [with an equivalence](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/fzn_inverse_reif.mzn). * Also available [as a function](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/inverse_fn.mzn). * [`inverse_in_range`](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/inverse_in_range.mzn): [?](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/fzn_inverse_in_range.mzn). * Sliding sum: `CP.SlidingSum` @@ -47,9 +47,9 @@ MiniZinc has a similar goal to this project: a common modelling interface for ma * [`value_precede`](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/value_precede.mzn): [several reifications)(https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/fzn_value_precede_int.mzn). * [No reified variant)(https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/fzn_value_precede_int_reif.mzn). * Combinatorial sets: - * Bin packing: + * Bin packing: * Raw: `CP.BinPacking` (with supplementary load variables) - * [`bin_packing`](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/bin_packing.mzn): mapped onto [a MILP-like model](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/fzn_bin_packing.mzn), but without binary variables (replaced by their definition in the capacity constraint: `bin[item] == value`). + * [`bin_packing`](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/bin_packing.mzn): mapped onto [a MILP-like model](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/fzn_bin_packing.mzn), but without binary variables (replaced by their definition in the capacity constraint: `bin[item] == value`). * [`bin_packing_reif`](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/bin_packing.mzn): similar, [with an equivalence](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/fzn_bin_packing_reif.mzn). * Capacitated: `CP.FixedCapacityBinPacking` and `CP.VariableCapacityBinPacking` (with supplementary load variables) * [`bin_packing_capa`](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/bin_packing_capa.mzn): same MILP-like model [with a linear capacity constraint](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/fzn_bin_packing_capa.mzn). @@ -62,7 +62,7 @@ MiniZinc has a similar goal to this project: a common modelling interface for ma * Knapsack: `CP.Knapsack` with values * [`knapsack`](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/knapsack.mzn): mapped onto [a MILP model](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/fzn_knapsack.mzn). * [`knapsack_reif`](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/knapsack.mzn): similar, [with an equivalence](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/fzn_knapsack_reif.mzn). -* Sorting: +* Sorting: * Maximum/minimum: `CP.MaximumAmong` and `CP.MinimumAmong` * [`maximum`](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/maximum.mzn): built-in, [except for linear solvers](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/linear/redefinitions.mzn) * [`minimum`](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/minimum.mzn): built-in, [except for linear solvers](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/linear/redefinitions.mzn) @@ -90,7 +90,7 @@ MiniZinc has a similar goal to this project: a common modelling interface for ma * [`lex2`](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/lex2.mzn): in a matrix, have both rows and columns lexicographically sorted, [mapped to two chains](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/fzn_lex2.mzn). * [`strict_lex2`](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/strict_lex2.mzn): in a matrix, have both rows and columns strictly lexicographically sorted, [mapped to two chains](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/fzn_strict_lex2.mzn). * Reifications are available. -* Scheduling: +* Scheduling: * Rectangle overlapping: `CP.NonOverlappingOrthotopes` * [`diffn`](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/diffn.mzn): [mapped to a disjunction of linear inequalities](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/fzn_diffn.mzn). * [`diffn_k`](https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/diffn.mzn): generalisation to `k` dimensions. diff --git a/src/FlatZinc/optimizer.jl b/src/FlatZinc/optimizer.jl index 094729ef..2d1e7af0 100644 --- a/src/FlatZinc/optimizer.jl +++ b/src/FlatZinc/optimizer.jl @@ -21,7 +21,7 @@ function _FznResults() end function _parse_fzn_value(str::AbstractString) - # Heuristically guess the type of the output value: either integer or + # Heuristically guess the type of the output value: either integer or # float. if '.' in str return parse(Float64, str) @@ -38,7 +38,9 @@ mapping the name of the variables to their values (either a scalar or a vector of numbers). The values are automatically transformed into the closest type (integer or float). """ -function _parse_to_assignments(str::String)::Vector{Dict{String, Vector{Number}}} +function _parse_to_assignments( + str::String, +)::Vector{Dict{String, Vector{Number}}} results = Dict{String, Vector{Number}}[] # If the infeasibility marker is discovered, return an empty list. @@ -49,16 +51,16 @@ function _parse_to_assignments(str::String)::Vector{Dict{String, Vector{Number}} # Remove comments from the output (starting with % and ending with a new line). str = replace(str, r"%(.*)(\\r|\\n|\\r\\n)" => s"") - # There may be several results returned by the solver. Each solution is + # There may be several results returned by the solver. Each solution is # separated from the others by `'-' ^ 10`. - str_split = split(str, '-' ^ 10)[1:(end - 1)] + str_split = split(str, '-'^10)[1:(end-1)] n_results = length(str_split) sizehint!(results, n_results) for i in 1:n_results push!(results, Dict{String, Vector{Number}}()) - # Each value is indicated in its own statement, separated by a + # Each value is indicated in its own statement, separated by a # semi-colon. for part in split(strip(str_split[i]), ';') if isempty(part) @@ -69,17 +71,18 @@ function _parse_to_assignments(str::String)::Vector{Dict{String, Vector{Number}} var = strip(var) val = strip(val) - # Either an array or a scalar. Always return an array for + # Either an array or a scalar. Always return an array for # simplicity. A scalar is simply an array with one element. if !occursin("array", val) # Scalar. Just a value: "1", "1.0". results[i][var] = [_parse_fzn_value(val)] else - # Array. Several arguments: "array1d(1..2, [1, 2])", - # "array2d(1..2, 1..2, [1, 2, 3, 4])". - # TODO: should dimensions be preserved? (First argument[s] of arrayNd.) + # Array. Several arguments: "array1d(1..2, [1, 2])", + # "array2d(1..2, 1..2, [1, 2, 3, 4])". + # TODO: should dimensions be preserved? (First argument[s] of "arrayNd".) val = split(split(val, '[')[2], ']')[1] - results[i][var] = map(_parse_fzn_value, map(strip, split(val, ','))) + results[i][var] = + map(_parse_fzn_value, map(strip, split(val, ','))) end end end @@ -88,7 +91,7 @@ function _parse_to_assignments(str::String)::Vector{Dict{String, Vector{Number}} end # MOI wrapper. -# Based on AmplNLWriter.jl's _NLResults and Optimizer. +# Based on AmplNLWriter.jl's _NLResults and Optimizer. # The main difference is that typical solutions do not have a Float64 type, # but rather Int. However, it all depends on the actual FZN solver that is # used below (some of them can still deal with floats). @@ -119,8 +122,8 @@ to the FlatZinc-compatible CLI of the solver or a `Cmd` object to call the solver (typically, this is useful to set environment variables). `solver_args` is a vector of `String` arguments passed solver executable. -However, prefer passing `key=value` options via `MOI.RawOptimizerAttribute`. -These arguments are passed to `Base.pipeline`. +However, prefer passing `key=value` options via `MOI.RawOptimizerAttribute`. +These arguments are passed to `Base.pipeline`. [See the Julia documentation for more details](https://docs.julialang.org/en/v1/base/base/#Base.pipeline-Tuple{Base.AbstractCmd}). ## Examples @@ -146,7 +149,6 @@ function Optimizer( 0.0, true, solver_args, - _FznResults(), NaN, NaN, @@ -154,27 +156,39 @@ function Optimizer( ) end -Base.show(io::IO, ::Optimizer) = print(io, "A FlatZinc (flattened MiniZinc) model") +function Base.show(io::IO, ::Optimizer) + return print(io, "A FlatZinc (flattened MiniZinc) model") +end MOI.get(model::Optimizer, ::MOI.SolverName) = "FlatZincWriter" -function MOI.supports(model::Optimizer, attr::MOI.AnyAttribute, x...) +function MOI.supports(model::Optimizer, attr::MOI.AnyAttribute, x...) return MOI.supports(model.inner, attr, x...)::Bool end -function MOI.supports_add_constrained_variable(model::Optimizer, x::Type{S}) where {S <: MOI.AbstractScalarSet} +function MOI.supports_add_constrained_variable( + model::Optimizer, + x::Type{S}, +) where {S <: MOI.AbstractScalarSet} return MOI.supports_add_constrained_variable(model.inner, x)::Bool end -function MOI.supports_add_constrained_variables(model::Optimizer, x::Type{S}) where {S <: MOI.AbstractScalarSet} +function MOI.supports_add_constrained_variables( + model::Optimizer, + x::Type{S}, +) where {S <: MOI.AbstractScalarSet} return MOI.supports_add_constrained_variables(model.inner, x)::Bool end -function MOI.supports_constraint(model::Optimizer, f::Type{F}, s::Type{S}) where {F <: MOI.AbstractFunction, S <: MOI.AbstractSet} +function MOI.supports_constraint( + model::Optimizer, + f::Type{F}, + s::Type{S}, +) where {F <: MOI.AbstractFunction, S <: MOI.AbstractSet} return MOI.supports_constraint(model.inner, f, s)::Bool end -function MOI.get(model::Optimizer, attr::MOI.AnyAttribute, x...) +function MOI.get(model::Optimizer, attr::MOI.AnyAttribute, x...) return MOI.get(model.inner, attr, x...) end @@ -182,20 +196,27 @@ function MOI.get(model::Optimizer, ::Type{MOI.VariableIndex}, name::String) return MOI.get(model.inner, MOI.VariableIndex, name) end -function MOI.set(model::Optimizer, attr::MOI.AnyAttribute, x...) +function MOI.set(model::Optimizer, attr::MOI.AnyAttribute, x...) MOI.set(model.inner, attr, x...) return end -function MOI.add_variable(model::Optimizer) +function MOI.add_variable(model::Optimizer) return MOI.add_variable(model.inner) end -function MOI.add_constrained_variable(model::Optimizer, x::MOI.AbstractScalarSet) +function MOI.add_constrained_variable( + model::Optimizer, + x::MOI.AbstractScalarSet, +) return MOI.add_constrained_variable(model.inner, x) end -function MOI.add_constraint(model::Optimizer, f::MOI.AbstractFunction, s::MOI.AbstractSet) +function MOI.add_constraint( + model::Optimizer, + f::MOI.AbstractFunction, + s::MOI.AbstractSet, +) return MOI.add_constraint(model.inner, f, s) end @@ -224,10 +245,10 @@ function MOI.optimize!(model::Optimizer) temp_dir = mktempdir() fzn_file = joinpath(temp_dir, "model.fzn") open(io -> write(io, model.inner), fzn_file, "w") - + model.fzn_time = time() - start_time - # Generate the list of options. Always put the user-defined options at + # Generate the list of options. Always put the user-defined options at # the end. opts = copy(model.options) if !iszero(model.time_limit_ms) @@ -242,11 +263,8 @@ function MOI.optimize!(model::Optimizer) try io = IOBuffer() ret = run( - pipeline( - `$(model.solver_command) $(opts) $(fzn_file)`, - stdout=io, - ), - wait=true + pipeline(`$(model.solver_command) $(opts) $(fzn_file)`, stdout=io), + wait=true, ) if ret.exitcode != 0 @@ -282,7 +300,11 @@ function MOI.get(model::Optimizer, ::MOI.TerminationStatus) return model.results.termination_status end -function MOI.get(model::Optimizer, attr::MOI.VariablePrimal, vi::MOI.VariableIndex) +function MOI.get( + model::Optimizer, + attr::MOI.VariablePrimal, + vi::MOI.VariableIndex, +) if length(model.results.primal_solutions) >= attr.result_index return model.results.primal_solutions[attr.result_index][vi] else @@ -297,23 +319,25 @@ MOI.get(model::Optimizer, ::MOI.SolveTimeSec) = model.solve_time MOI.supports(::Optimizer, ::MOI.TimeLimitSec) = true MOI.get(model::Optimizer, ::MOI.TimeLimitSec) = model.time_limit_ms / 1_000.0 -function MOI.set(model::Optimizer, ::MOI.TimeLimitSec, limit::Real) - model.time_limit_ms = limit * 1_000.0 +function MOI.set(model::Optimizer, ::MOI.TimeLimitSec, limit::Real) + return model.time_limit_ms = limit * 1_000.0 end -function MOI.set(model::Optimizer, ::MOI.TimeLimitSec, limit::Nothing) - model.time_limit_ms = 0.0 +function MOI.set(model::Optimizer, ::MOI.TimeLimitSec, limit::Nothing) + return model.time_limit_ms = 0.0 end MOI.supports(::Optimizer, ::MOI.Silent) = true MOI.get(model::Optimizer, ::MOI.Silent) = !model.verboseness -function MOI.set(model::Optimizer, ::MOI.Silent, silentness::Bool) - model.verboseness = !silentness +function MOI.set(model::Optimizer, ::MOI.Silent, silentness::Bool) + return model.verboseness = !silentness end MOI.supports(::Optimizer, ::MOI.ResultCount) = true -MOI.get(model::Optimizer, ::MOI.ResultCount) = length(model.results.primal_solutions) +function MOI.get(model::Optimizer, ::MOI.ResultCount) + return length(model.results.primal_solutions) +end -# Specific case of dual solution: getting it must be supported (MOI +# Specific case of dual solution: getting it must be supported (MOI # requirement), but few CP solvers have it accessible (none?). # https://github.com/jump-dev/MathOptInterface.jl/pull/1561#pullrequestreview-740032701 @@ -325,10 +349,13 @@ MOI.get(::Optimizer, ::MOI.DualStatus) = MOI.NO_SOLUTION """ _parse_to_assignments(sols::Vector{Dict{String, Vector{Number}}}, model::CP.FlatZinc.Model) -Parses the output of `_parse_to_assignments` and stores the solutions into +Parses the output of `_parse_to_assignments` and stores the solutions into `model`. This function is responsible for filling `model.results`. """ -function _parse_to_moi_solutions(sols::Vector{Dict{String, Vector{Number}}}, model::Optimizer) +function _parse_to_moi_solutions( + sols::Vector{Dict{String, Vector{Number}}}, + model::Optimizer, +) @assert length(model.results.primal_solutions) == 0 for i in 1:length(sols) @@ -336,18 +363,20 @@ function _parse_to_moi_solutions(sols::Vector{Dict{String, Vector{Number}}}, mod push!(model.results.primal_solutions, Dict{MOI.VariableIndex, Real}()) for (var_name, val) in sol - # At least for now: no vector at the FlatZinc layer. Just ensure + # At least for now: no vector at the FlatZinc layer. Just ensure # that this is consistent. - @assert length(val) == 1 + @assert length(val) == 1 var_idx = MOI.get(model, MOI.VariableIndex, var_name) model.results.primal_solutions[i][var_idx] = val[1] end end - - model.results.termination_status = (length(sols) == 0) ? MOI.INFEASIBLE : MOI.OPTIMAL - model.results.primal_status = (length(sols) == 0) ? MOI.NO_SOLUTION : MOI.FEASIBLE_POINT - + + model.results.termination_status = + (length(sols) == 0) ? MOI.INFEASIBLE : MOI.OPTIMAL + model.results.primal_status = + (length(sols) == 0) ? MOI.NO_SOLUTION : MOI.FEASIBLE_POINT + @assert length(model.results.primal_solutions) == length(sols) - return + return end diff --git a/test/Bridges/Constraint/Knapsack/varcapav_to_varcapa.jl b/test/Bridges/Constraint/Knapsack/varcapav_to_varcapa.jl index e9f63f50..c17b3173 100644 --- a/test/Bridges/Constraint/Knapsack/varcapav_to_varcapa.jl +++ b/test/Bridges/Constraint/Knapsack/varcapav_to_varcapa.jl @@ -1,6 +1,13 @@ -@testset "VariableCapacityValuedKnapsack2VariableCapacityKnapsackBrige: $(fct_type), 2 items, $(T)" for fct_type in ["vector of variables", "vector affine function"], T in [Int, Float64] +@testset "VariableCapacityValuedKnapsack2VariableCapacityKnapsackBridge: $(fct_type), 2 items, $(T)" for fct_type in + [ + "vector of variables", + "vector affine function", + ], + T in [Int, Float64] + mock = MOIU.MockOptimizer(VariableCapacityKnapsackModel{T}()) - model = COIB.VariableCapacityValuedKnapsack2VariableCapacityKnapsack{T}(mock) + model = + COIB.VariableCapacityValuedKnapsack2VariableCapacityKnapsack{T}(mock) if T == Int @test MOI.supports_constraint(model, MOI.VariableIndex, MOI.Integer) @@ -51,22 +58,71 @@ @test MOI.is_valid(model, x_value) @test MOI.is_valid(model, c) - bridge = MOIBC.bridges(model)[MOI.ConstraintIndex{MOI.VectorOfVariables, CP.Knapsack{CP.VARIABLE_CAPACITY_KNAPSACK, CP.VALUED_KNAPSACK, T}}(-1)] + bridge = MOIBC.bridges(model)[MOI.ConstraintIndex{ + MOI.VectorOfVariables, + CP.Knapsack{CP.VARIABLE_CAPACITY_KNAPSACK, CP.VALUED_KNAPSACK, T}, + }( + -1, + )] @testset "Bridge properties" begin - @test MOIBC.concrete_bridge_type(typeof(bridge), MOI.VectorOfVariables, CP.Knapsack{CP.VARIABLE_CAPACITY_KNAPSACK, CP.VALUED_KNAPSACK, T}) == typeof(bridge) - @test MOIB.added_constrained_variable_types(typeof(bridge)) == Tuple{Type}[] + @test MOIBC.concrete_bridge_type( + typeof(bridge), + MOI.VectorOfVariables, + CP.Knapsack{CP.VARIABLE_CAPACITY_KNAPSACK, CP.VALUED_KNAPSACK, T}, + ) == typeof(bridge) + @test MOIB.added_constrained_variable_types(typeof(bridge)) == + Tuple{Type}[] @test Set(MOIB.added_constraint_types(typeof(bridge))) == Set([ - (MOI.VectorAffineFunction{T}, CP.Knapsack{CP.VARIABLE_CAPACITY_KNAPSACK, CP.UNVALUED_KNAPSACK, T}), + ( + MOI.VectorAffineFunction{T}, + CP.Knapsack{ + CP.VARIABLE_CAPACITY_KNAPSACK, + CP.UNVALUED_KNAPSACK, + T, + }, + ), (MOI.ScalarAffineFunction{T}, MOI.EqualTo{T}), ]) @test MOI.get(bridge, MOI.NumberOfVariables()) == 0 - @test MOI.get(bridge, MOI.NumberOfConstraints{MOI.VectorAffineFunction{T}, CP.Knapsack{CP.VARIABLE_CAPACITY_KNAPSACK, CP.UNVALUED_KNAPSACK, T}}()) == 1 - @test MOI.get(bridge, MOI.NumberOfConstraints{MOI.ScalarAffineFunction{T}, MOI.EqualTo{T}}()) == 1 + @test MOI.get( + bridge, + MOI.NumberOfConstraints{ + MOI.VectorAffineFunction{T}, + CP.Knapsack{ + CP.VARIABLE_CAPACITY_KNAPSACK, + CP.UNVALUED_KNAPSACK, + T, + }, + }(), + ) == 1 + @test MOI.get( + bridge, + MOI.NumberOfConstraints{ + MOI.ScalarAffineFunction{T}, + MOI.EqualTo{T}, + }(), + ) == 1 - @test MOI.get(bridge, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{T}, CP.Knapsack{CP.VARIABLE_CAPACITY_KNAPSACK, CP.UNVALUED_KNAPSACK, T}}()) == [bridge.kp] - @test MOI.get(bridge, MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{T}, MOI.EqualTo{T}}()) == [bridge.value] + @test MOI.get( + bridge, + MOI.ListOfConstraintIndices{ + MOI.VectorAffineFunction{T}, + CP.Knapsack{ + CP.VARIABLE_CAPACITY_KNAPSACK, + CP.UNVALUED_KNAPSACK, + T, + }, + }(), + ) == [bridge.kp] + @test MOI.get( + bridge, + MOI.ListOfConstraintIndices{ + MOI.ScalarAffineFunction{T}, + MOI.EqualTo{T}, + }(), + ) == [bridge.value] end @testset "Value constraint" begin @@ -81,7 +137,8 @@ @test f.terms[1].variable == x_1 @test f.terms[2].variable == x_2 @test f.terms[3].variable == x_value - @test MOI.get(model, MOI.ConstraintSet(), bridge.value) == MOI.EqualTo(zero(T)) + @test MOI.get(model, MOI.ConstraintSet(), bridge.value) == + MOI.EqualTo(zero(T)) end @testset "BinPacking constraint" begin @@ -95,6 +152,7 @@ @test f.terms[1].scalar_term.variable == x_1 @test f.terms[2].scalar_term.variable == x_2 @test f.terms[3].scalar_term.variable == x_capa - @test MOI.get(model, MOI.ConstraintSet(), bridge.kp) == CP.Knapsack(weights) + @test MOI.get(model, MOI.ConstraintSet(), bridge.kp) == + CP.Knapsack(weights) end end