├── REQUIRE ├── .codecov.yml ├── test ├── FileFormats │ ├── SDPA │ │ └── models │ │ │ ├── bad_blocks.sdpa │ │ │ ├── bad_vars.sdpa │ │ │ ├── bad_diag.sdpa │ │ │ ├── bad_entry.sdpa │ │ │ ├── example_B.sdpa │ │ │ └── example_A.sdpa │ ├── MPS │ │ ├── failing_models │ │ │ ├── rows_bad_sense.mps │ │ │ ├── rows_duplicate.mps │ │ │ ├── rows_malformed.mps │ │ │ ├── rows_missing.mps │ │ │ ├── columns_malformed.mps │ │ │ ├── name_spaces.mps │ │ │ ├── bounds_malformed.mps │ │ │ ├── bounds_invalid_type.mps │ │ │ ├── bounds_missing_column.mps │ │ │ ├── parse_items_single_1.mps │ │ │ ├── rhs_objective.mps │ │ │ ├── bounds_invalid_type2.mps │ │ │ ├── bounds_missing_column2.mps │ │ │ ├── rhs_malformed2.mps │ │ │ ├── rhs_malformed.mps │ │ │ ├── parse_items_single_2.mps │ │ │ ├── parse_items_single_3.mps │ │ │ ├── ranges_malformed.mps │ │ │ ├── ranges_malformed2.mps │ │ │ ├── columns_marker.mps │ │ │ └── columns_marker2.mps │ │ ├── free_integer.mps.gz │ │ ├── free_integer.mps │ │ └── stacked_data.mps │ ├── CBF │ │ └── models │ │ │ ├── example_C.cbf.gz │ │ │ ├── incompatible_version.cbf │ │ │ ├── bad_cone_string_A.cbf │ │ │ ├── corrupt_line_E.cbf │ │ │ ├── corrupt_line_B.cbf │ │ │ ├── corrupt_line_C.cbf │ │ │ ├── corrupt_line_D.cbf │ │ │ ├── corrupt_line_A.cbf │ │ │ ├── bad_power_dim_A.cbf │ │ │ ├── bad_power_dim_B.cbf │ │ │ ├── bad_cone_string_C.cbf │ │ │ ├── example_C.cbf │ │ │ ├── bad_cone_string_D.cbf │ │ │ ├── example_B.cbf │ │ │ ├── example_D.cbf │ │ │ ├── bad_cone_string_B.cbf │ │ │ └── example_A.cbf │ ├── MOF │ │ ├── empty_model.mof.json.gz │ │ ├── empty_model.mof.json │ │ └── failing_models │ │ │ ├── incompatible_version.mof.json │ │ │ ├── unsupported_objective_sense.mof.json │ │ │ ├── missing_variable_name.mof.json │ │ │ ├── blank_variable_name.mof.json │ │ │ ├── unsupported_objective_function.mof.json │ │ │ ├── unsupported_constraint_function.mof.json │ │ │ └── unsupported_constraint_set.mof.json │ └── FileFormats.jl ├── Test │ ├── Test.jl │ ├── modellike.jl │ ├── intconic.jl │ ├── config.jl │ ├── nlp.jl │ ├── contquadratic.jl │ └── intlinear.jl ├── issue980.jl ├── isbits.jl ├── Utilities │ ├── lazy_iterators.jl │ ├── print_with_acronym.jl │ ├── Utilities.jl │ ├── constraints.jl │ └── variables.jl ├── Bridges │ ├── Constraint │ │ ├── bridge.jl │ │ ├── square.jl │ │ ├── indicator_activate_on_zero.jl │ │ ├── soc_to_psd.jl │ │ ├── rsoc.jl │ │ ├── vectorize.jl │ │ ├── quad_to_soc.jl │ │ ├── relentr_to_exp.jl │ │ └── scalarize.jl │ ├── Variable │ │ ├── bridge.jl │ │ ├── flip_sign.jl │ │ ├── rsoc_to_soc.jl │ │ └── soc_to_rsoc.jl │ ├── Bridges.jl │ └── Objective │ │ ├── map.jl │ │ └── functionize.jl ├── functions.jl ├── interval.jl ├── runtests.jl ├── hygiene.jl ├── dummy.jl ├── attributes.jl ├── Benchmarks │ └── Benchmarks.jl ├── instantiate.jl └── isapprox.jl ├── .gitignore ├── docs ├── Project.toml ├── src │ └── index.md └── make.jl ├── .github └── workflows │ ├── TagBot.yml │ └── CompatHelper.yml ├── src ├── Test │ ├── Test.jl │ ├── config.jl │ ├── intconic.jl │ └── UnitTests │ │ ├── unit_tests.jl │ │ └── attributes.jl ├── Bridges │ ├── Objective │ │ ├── Objective.jl │ │ ├── single_bridge_optimizer.jl │ │ ├── functionize.jl │ │ ├── map.jl │ │ └── slack.jl │ ├── Constraint │ │ ├── single_bridge_optimizer.jl │ │ ├── function_conversion.jl │ │ ├── indicator_activate_on_zero.jl │ │ ├── rsoc.jl │ │ ├── set_map.jl │ │ └── bridge.jl │ ├── Variable │ │ ├── Variable.jl │ │ ├── single_bridge_optimizer.jl │ │ ├── zeros.jl │ │ ├── soc_to_rsoc.jl │ │ └── flip_sign.jl │ ├── Bridges.jl │ └── bridge.jl ├── Utilities │ ├── lazy_iterators.jl │ ├── Utilities.jl │ ├── sets.jl │ ├── constraints.jl │ └── variables.jl ├── error.jl ├── FileFormats │ ├── FileFormats.jl │ └── MOF │ │ └── MOF.jl ├── modifications.jl ├── MathOptInterface.jl └── indextypes.jl ├── appveyor.yml ├── Project.toml ├── .travis.yml ├── LICENSE.md ├── perf ├── cachingoptimizer.jl └── bellman_ford.jl └── README.md /REQUIRE: -------------------------------------------------------------------------------- 1 | julia 0.7 2 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | -------------------------------------------------------------------------------- /test/FileFormats/SDPA/models/bad_blocks.sdpa: -------------------------------------------------------------------------------- 1 | 2 2 | 3 3 | 2 2 4 | 1.0 2.0 5 | -------------------------------------------------------------------------------- /test/FileFormats/SDPA/models/bad_vars.sdpa: -------------------------------------------------------------------------------- 1 | 3 2 | 2 3 | 2 2 4 | 2.0 3.0 5 | -------------------------------------------------------------------------------- /test/FileFormats/SDPA/models/bad_diag.sdpa: -------------------------------------------------------------------------------- 1 | 1 2 | 1 3 | -2 4 | 1.0 5 | 0 1 1 2 1.0 6 | -------------------------------------------------------------------------------- /test/FileFormats/SDPA/models/bad_entry.sdpa: -------------------------------------------------------------------------------- 1 | 2 2 | 2 3 | 2 2 4 | 1.0 1.0 5 | 0 1 2 2 6 | -------------------------------------------------------------------------------- /test/FileFormats/MPS/failing_models/rows_bad_sense.mps: -------------------------------------------------------------------------------- 1 | NAME 2 | ROWS 3 | X c 4 | COLUMNS 5 | ENDATA 6 | -------------------------------------------------------------------------------- /test/FileFormats/MPS/failing_models/rows_duplicate.mps: -------------------------------------------------------------------------------- 1 | NAME 2 | ROWS 3 | N c 4 | G c 5 | COLUMNS 6 | ENDATA 7 | -------------------------------------------------------------------------------- /test/FileFormats/MPS/failing_models/rows_malformed.mps: -------------------------------------------------------------------------------- 1 | NAME 2 | ROWS 3 | N c d 4 | COLUMNS 5 | ENDATA 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.jl.cov 2 | *.jl.*.cov 3 | *.jl.mem 4 | .vscode/* 5 | docs/build/ 6 | docs/site/ 7 | test/Benchmarks/*.json 8 | Manifest.toml 9 | -------------------------------------------------------------------------------- /test/FileFormats/MPS/failing_models/rows_missing.mps: -------------------------------------------------------------------------------- 1 | NAME 2 | ROWS 3 | N c 4 | COLUMNS 5 | x d 1 6 | ENDATA 7 | -------------------------------------------------------------------------------- /test/FileFormats/MPS/failing_models/columns_malformed.mps: -------------------------------------------------------------------------------- 1 | NAME 2 | ROWS 3 | N c 4 | COLUMNS 5 | x c d 1 6 | ENDATA 7 | -------------------------------------------------------------------------------- /test/FileFormats/MPS/free_integer.mps.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wikunia/MathOptInterface.jl/master/test/FileFormats/MPS/free_integer.mps.gz -------------------------------------------------------------------------------- /test/FileFormats/CBF/models/example_C.cbf.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wikunia/MathOptInterface.jl/master/test/FileFormats/CBF/models/example_C.cbf.gz -------------------------------------------------------------------------------- /test/FileFormats/MOF/empty_model.mof.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wikunia/MathOptInterface.jl/master/test/FileFormats/MOF/empty_model.mof.json.gz -------------------------------------------------------------------------------- /test/FileFormats/MPS/failing_models/name_spaces.mps: -------------------------------------------------------------------------------- 1 | NAME Bad Error 2 | ROWS 3 | N c 4 | COLUMNS 5 | x c 1 6 | ENDATA 7 | -------------------------------------------------------------------------------- /test/FileFormats/MPS/failing_models/bounds_malformed.mps: -------------------------------------------------------------------------------- 1 | NAME 2 | ROWS 3 | N c 4 | COLUMNS 5 | x c 1 6 | BOUNDS 7 | ZZ x 8 | ENDATA 9 | -------------------------------------------------------------------------------- /test/FileFormats/CBF/models/incompatible_version.cbf: -------------------------------------------------------------------------------- 1 | VER 2 | 4 3 | 4 | OBJSENSE 5 | MIN 6 | 7 | VAR 8 | 1 1 9 | F 1 10 | 11 | OBJACOORD 12 | 1 13 | 0 1.0 14 | -------------------------------------------------------------------------------- /test/FileFormats/CBF/models/bad_cone_string_A.cbf: -------------------------------------------------------------------------------- 1 | VER 2 | 3 3 | 4 | OBJSENSE 5 | MIN 6 | 7 | VAR 8 | 1 1 9 | bad_cone_string 1 10 | 11 | OBJACOORD 12 | 1 13 | 0 1.0 14 | -------------------------------------------------------------------------------- /test/FileFormats/MPS/failing_models/bounds_invalid_type.mps: -------------------------------------------------------------------------------- 1 | NAME 2 | ROWS 3 | N c 4 | COLUMNS 5 | x c 1 6 | BOUNDS 7 | LO bounds x 8 | ENDATA 9 | -------------------------------------------------------------------------------- /test/FileFormats/MPS/failing_models/bounds_missing_column.mps: -------------------------------------------------------------------------------- 1 | NAME 2 | ROWS 3 | N c 4 | COLUMNS 5 | x c 1 6 | BOUNDS 7 | BI bounds y 8 | ENDATA 9 | -------------------------------------------------------------------------------- /test/FileFormats/MPS/failing_models/parse_items_single_1.mps: -------------------------------------------------------------------------------- 1 | NAME 2 | ROWS 3 | N c 4 | COLUMNS 5 | x c 1 6 | RHS 7 | rhs c 1 8 | ENDATA 9 | -------------------------------------------------------------------------------- /test/Test/Test.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | 3 | @testset "$(file)" for file in readdir(@__DIR__) 4 | if file == "Test.jl" 5 | continue 6 | end 7 | include(file) 8 | end 9 | -------------------------------------------------------------------------------- /docs/Project.toml: -------------------------------------------------------------------------------- 1 | [deps] 2 | Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" 3 | MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" 4 | 5 | [compat] 6 | Documenter = "0.24" 7 | -------------------------------------------------------------------------------- /test/FileFormats/CBF/models/corrupt_line_E.cbf: -------------------------------------------------------------------------------- 1 | VER 2 | 3 3 | 4 | OBJSENSE 5 | this is a corrupted line 6 | 7 | VAR 8 | 1 1 9 | F 1 10 | 11 | OBJACOORD 12 | 1 13 | 0 1.0 14 | -------------------------------------------------------------------------------- /test/FileFormats/MPS/failing_models/rhs_objective.mps: -------------------------------------------------------------------------------- 1 | NAME 2 | ROWS 3 | N c 4 | COLUMNS 5 | x c 1 6 | RHS 7 | rhs c 1 8 | ENDATA 9 | -------------------------------------------------------------------------------- /test/FileFormats/CBF/models/corrupt_line_B.cbf: -------------------------------------------------------------------------------- 1 | VER this is a corrupted line 2 | 3 3 | 4 | OBJSENSE 5 | MIN 6 | 7 | VAR 8 | 1 1 9 | F 1 10 | 11 | OBJACOORD 12 | 1 13 | 0 1.0 14 | -------------------------------------------------------------------------------- /test/FileFormats/CBF/models/corrupt_line_C.cbf: -------------------------------------------------------------------------------- 1 | VER 2 | 3 3 | 4 | OBJSENSE 5 | MIN 6 | 7 | VAR 8 | 1 1 this is a corrupted line 9 | F 1 10 | 11 | OBJACOORD 12 | 1 13 | 0 1.0 14 | -------------------------------------------------------------------------------- /test/FileFormats/CBF/models/corrupt_line_D.cbf: -------------------------------------------------------------------------------- 1 | VER 2 | 3 3 | 4 | OBJSENSE 5 | MIN 6 | 7 | VAR 8 | 1 1 9 | F 1 10 | 11 | OBJACOORD 12 | 1 13 | 0 1.0 this is a corrupted line 14 | -------------------------------------------------------------------------------- /test/FileFormats/MPS/failing_models/bounds_invalid_type2.mps: -------------------------------------------------------------------------------- 1 | NAME 2 | ROWS 3 | N c 4 | COLUMNS 5 | x c 1 6 | BOUNDS 7 | PL bounds x 1 8 | ENDATA 9 | -------------------------------------------------------------------------------- /test/FileFormats/SDPA/models/example_B.sdpa: -------------------------------------------------------------------------------- 1 | " Test with empty lines and lower triangular entries 2 | 3 | 1 4 | 5 | 1 6 | 7 | 2 8 | 9 | 1.0 10 | 0 1 2 1 1.0 11 | 12 | 1 1 2 1 1.0 13 | -------------------------------------------------------------------------------- /test/FileFormats/CBF/models/corrupt_line_A.cbf: -------------------------------------------------------------------------------- 1 | this is a corrupted line 2 | 3 | VER 4 | 3 5 | 6 | OBJSENSE 7 | MIN 8 | 9 | VAR 10 | 1 1 11 | F 1 12 | 13 | OBJACOORD 14 | 1 15 | 0 1.0 16 | -------------------------------------------------------------------------------- /test/FileFormats/MPS/failing_models/bounds_missing_column2.mps: -------------------------------------------------------------------------------- 1 | NAME 2 | ROWS 3 | N c 4 | COLUMNS 5 | x c 1 6 | BOUNDS 7 | LO bounds y 1 8 | ENDATA 9 | -------------------------------------------------------------------------------- /test/FileFormats/MPS/failing_models/rhs_malformed2.mps: -------------------------------------------------------------------------------- 1 | NAME 2 | ROWS 3 | N c 4 | G d 5 | COLUMNS 6 | x c 1 7 | x d 1 8 | RHS 9 | rhs con1 1 10 | ENDATA 11 | -------------------------------------------------------------------------------- /test/FileFormats/CBF/models/bad_power_dim_A.cbf: -------------------------------------------------------------------------------- 1 | VER 2 | 3 3 | 4 | POWCONES 5 | 1 3 6 | 3 7 | 4.0 8 | 1.0 9 | 2.0 10 | 11 | OBJSENSE 12 | MAX 13 | 14 | VAR 15 | 4 1 16 | @0:POW 4 17 | 18 | OBJACOORD 19 | 1 20 | 2 1.0 21 | -------------------------------------------------------------------------------- /test/FileFormats/MPS/failing_models/rhs_malformed.mps: -------------------------------------------------------------------------------- 1 | NAME 2 | ROWS 3 | N c 4 | G d 5 | COLUMNS 6 | x c 1 7 | x d 1 8 | RHS 9 | rhs d con 1 10 | ENDATA 11 | -------------------------------------------------------------------------------- /test/FileFormats/CBF/models/bad_power_dim_B.cbf: -------------------------------------------------------------------------------- 1 | VER 2 | 3 3 | 4 | POW*CONES 5 | 1 3 6 | 3 7 | 4.0 8 | 1.0 9 | 2.0 10 | 11 | OBJSENSE 12 | MAX 13 | 14 | VAR 15 | 4 1 16 | @0:POW* 4 17 | 18 | OBJACOORD 19 | 1 20 | 2 1.0 21 | -------------------------------------------------------------------------------- /test/FileFormats/CBF/models/bad_cone_string_C.cbf: -------------------------------------------------------------------------------- 1 | VER 2 | 3 3 | 4 | POW*CONES 5 | 1 2 6 | 2 7 | 4.0 8 | 1.0 9 | 10 | OBJSENSE 11 | MAX 12 | 13 | VAR 14 | 3 1 15 | @0:bad_cone_string 3 16 | 17 | OBJACOORD 18 | 1 19 | 2 1.0 20 | -------------------------------------------------------------------------------- /test/FileFormats/MPS/failing_models/parse_items_single_2.mps: -------------------------------------------------------------------------------- 1 | NAME 2 | ROWS 3 | N c 4 | G d 5 | COLUMNS 6 | x c 1 7 | x d 1 8 | RHS 9 | rhs c 1 d 1 10 | ENDATA 11 | -------------------------------------------------------------------------------- /test/FileFormats/MPS/failing_models/parse_items_single_3.mps: -------------------------------------------------------------------------------- 1 | NAME 2 | ROWS 3 | N c 4 | G d 5 | COLUMNS 6 | x c 1 7 | x d 1 8 | RHS 9 | rhs d 1 c 1 10 | ENDATA 11 | -------------------------------------------------------------------------------- /test/FileFormats/MOF/empty_model.mof.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MathOptFormat Model", 3 | "version": { 4 | "major": 0, 5 | "minor": 4 6 | }, 7 | "variables": [], 8 | "objective": {"sense": "feasibility"}, 9 | "constraints": [] 10 | } 11 | -------------------------------------------------------------------------------- /docs/src/index.md: -------------------------------------------------------------------------------- 1 | # MathOptInterface 2 | 3 | [MathOptInterface.jl](https://github.com/JuliaOpt/MathOptInterface.jl) is a standardized API for mathematical optimization solvers. 4 | 5 | ```@contents 6 | Pages = ["apimanual.md", "apireference.md"] 7 | Depth = 3 8 | ``` 9 | -------------------------------------------------------------------------------- /.github/workflows/TagBot.yml: -------------------------------------------------------------------------------- 1 | name: TagBot 2 | on: 3 | schedule: 4 | - cron: 0 * * * * 5 | jobs: 6 | TagBot: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: JuliaRegistries/TagBot@v1 10 | with: 11 | token: ${{ secrets.GITHUB_TOKEN }} 12 | -------------------------------------------------------------------------------- /test/FileFormats/MOF/failing_models/incompatible_version.mof.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MathOptFormat Model", 3 | "version": { 4 | "major": 999, 5 | "minor": 0 6 | }, 7 | "variables": [], 8 | "objective": {"sense": "feasibility"}, 9 | "constraints": [] 10 | } 11 | -------------------------------------------------------------------------------- /test/FileFormats/MPS/failing_models/ranges_malformed.mps: -------------------------------------------------------------------------------- 1 | NAME 2 | ROWS 3 | N c 4 | G d 5 | COLUMNS 6 | x c 1 7 | x d 1 8 | RHS 9 | rhs d 1 10 | RANGES 11 | ranges d x 2 12 | ENDATA 13 | -------------------------------------------------------------------------------- /test/FileFormats/MPS/failing_models/ranges_malformed2.mps: -------------------------------------------------------------------------------- 1 | NAME 2 | ROWS 3 | N c 4 | G d 5 | COLUMNS 6 | x c 1 7 | x d 1 8 | RHS 9 | rhs d 1 10 | RANGES 11 | ranges e 2 12 | ENDATA 13 | -------------------------------------------------------------------------------- /test/FileFormats/MOF/failing_models/unsupported_objective_sense.mof.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MathOptFormat Model", 3 | "version": { 4 | "major": 0, 5 | "minor": 4 6 | }, 7 | "variables": [{"name": "x"}], 8 | "objective": { 9 | "sense": "UnsupportedSense" 10 | }, 11 | "constraints": [] 12 | } 13 | -------------------------------------------------------------------------------- /test/FileFormats/SDPA/models/example_A.sdpa: -------------------------------------------------------------------------------- 1 | "Example from http://plato.asu.edu/ftp/sdpa_format.txt 2 | "A sample problem. 3 | 2 4 | 2 5 | 2 2 6 | 10.0 20.0 7 | 0 1 1 1 1.0 8 | 0 1 2 2 2.0 9 | 0 2 1 1 3.0 10 | 0 2 2 2 4.0 11 | 1 1 1 1 1.0 12 | 1 1 2 2 1.0 13 | 2 1 2 2 1.0 14 | 2 2 1 1 5.0 15 | 2 2 1 2 2.0 16 | 2 2 2 2 6.0 17 | -------------------------------------------------------------------------------- /test/FileFormats/MPS/free_integer.mps: -------------------------------------------------------------------------------- 1 | NAME 2 | ROWS 3 | N obj 4 | G con1 5 | COLUMNS 6 | MARKER 'MARKER' 'INTORG' 7 | x obj 1 con1 1 8 | MARKER 'MARKER' 'INTEND' 9 | RHS 10 | rhs con1 1 11 | BOUNDS 12 | FR bounds x 13 | ENDATA 14 | -------------------------------------------------------------------------------- /test/FileFormats/MPS/failing_models/columns_marker.mps: -------------------------------------------------------------------------------- 1 | NAME 2 | ROWS 3 | N obj 4 | G con 5 | COLUMNS 6 | MARKER 'MARKER' 'INTORG' 7 | x obj 1 8 | MARKER 'MARKER' 'INTEND' 9 | x con 1 10 | RHS 11 | rhs con 1 12 | ENDATA 13 | -------------------------------------------------------------------------------- /test/FileFormats/MOF/failing_models/missing_variable_name.mof.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MathOptFormat Model", 3 | "version": { 4 | "major": 0, 5 | "minor": 4 6 | }, 7 | "variables": [{}], 8 | "objective": { 9 | "sense": "min", 10 | "function": {"head": "SingleVariable", "variable": "x"} 11 | }, 12 | "constraints": [] 13 | } 14 | -------------------------------------------------------------------------------- /test/FileFormats/MOF/failing_models/blank_variable_name.mof.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MathOptFormat Model", 3 | "version": { 4 | "major": 0, 5 | "minor": 4 6 | }, 7 | "variables": [{"name": ""}], 8 | "objective": { 9 | "sense": "min", 10 | "function": {"head": "SingleVariable", "variable": ""} 11 | }, 12 | "constraints": [] 13 | } 14 | -------------------------------------------------------------------------------- /test/Test/modellike.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | import MathOptInterface 3 | const MOI = MathOptInterface 4 | const MOIT = MOI.Test 5 | 6 | @testset "Failed copy" begin 7 | include("../dummy.jl") 8 | model = DummyModelWithAdd() 9 | MOIT.failcopytestc(model) 10 | MOIT.failcopytestia(model) 11 | MOIT.failcopytestva(model) 12 | MOIT.failcopytestca(model) 13 | end 14 | -------------------------------------------------------------------------------- /test/FileFormats/MPS/failing_models/columns_marker2.mps: -------------------------------------------------------------------------------- 1 | NAME 2 | ROWS 3 | N obj 4 | G con1 5 | G con2 6 | COLUMNS 7 | MARKER 'MARKER' 'INTORG' 8 | x con2 1 9 | MARKER 'MARKER' 'INTEND' 10 | x obj 1 con1 1 11 | RHS 12 | rhs con1 1 con2 1 13 | ENDATA 14 | -------------------------------------------------------------------------------- /test/FileFormats/MOF/failing_models/unsupported_objective_function.mof.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MathOptFormat Model", 3 | "version": { 4 | "major": 0, 5 | "minor": 4 6 | }, 7 | "variables": [{"name": "x"}], 8 | "objective": { 9 | "sense": "min", 10 | "function": { 11 | "head": "UnsupportedObjectiveFunction" 12 | } 13 | }, 14 | "constraints": [] 15 | } 16 | -------------------------------------------------------------------------------- /test/Test/intconic.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | import MathOptInterface 3 | const MOI = MathOptInterface 4 | const MOIT = MOI.Test 5 | const MOIU = MOI.Utilities 6 | 7 | mock = MOIU.MockOptimizer(MOIU.Model{Float64}()) 8 | config = MOIT.TestConfig() 9 | 10 | @testset "SOC" begin 11 | mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 1.0, 0.0]) 12 | MOIT.intsoc1test(mock, config) 13 | end 14 | -------------------------------------------------------------------------------- /test/FileFormats/CBF/models/example_C.cbf: -------------------------------------------------------------------------------- 1 | VER 2 | 3 3 | 4 | OBJSENSE 5 | MAX 6 | 7 | VAR 8 | 16 8 9 | F 1 10 | L= 1 11 | L+ 1 12 | L- 1 13 | Q 3 14 | QR 3 15 | EXP 3 16 | EXP* 3 17 | 18 | OBJACOORD 19 | 16 20 | 0 1.0 21 | 1 1.0 22 | 2 1.0 23 | 3 1.0 24 | 4 1.0 25 | 5 1.0 26 | 6 1.0 27 | 7 1.0 28 | 8 1.0 29 | 9 1.0 30 | 10 1.0 31 | 11 1.0 32 | 12 1.0 33 | 13 1.0 34 | 14 1.0 35 | 15 1.0 36 | 37 | OBJBCOORD 38 | -1.0 39 | -------------------------------------------------------------------------------- /test/issue980.jl: -------------------------------------------------------------------------------- 1 | module Issue980 2 | 3 | import MathOptInterface 4 | const MOI = MathOptInterface 5 | 6 | abstract type StaticArray end 7 | (::Type{SA})(x...) where {SA <: StaticArray} = SA(x) 8 | 9 | function crash() 10 | model = MOI.Utilities.Model{Float64}() 11 | bridged = MOI.Bridges.LazyBridgeOptimizer(MOI.Utilities.Model{Float64}()) 12 | MOI.copy_to(bridged, model) 13 | end 14 | 15 | crash() 16 | 17 | end 18 | -------------------------------------------------------------------------------- /src/Test/Test.jl: -------------------------------------------------------------------------------- 1 | module Test 2 | 3 | using MathOptInterface 4 | const MOI = MathOptInterface 5 | const MOIU = MOI.Utilities 6 | 7 | using Test 8 | 9 | include("config.jl") 10 | 11 | include("modellike.jl") 12 | 13 | include("contlinear.jl") 14 | include("contconic.jl") 15 | include("contquadratic.jl") 16 | 17 | include("intlinear.jl") 18 | include("intconic.jl") 19 | 20 | include("nlp.jl") 21 | 22 | include("UnitTests/unit_tests.jl") 23 | 24 | end # module 25 | -------------------------------------------------------------------------------- /test/FileFormats/MOF/failing_models/unsupported_constraint_function.mof.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MathOptFormat Model", 3 | "version": { 4 | "major": 0, 5 | "minor": 4 6 | }, 7 | "variables": [{"name": "x"}], 8 | "objective": { 9 | "sense": "min", 10 | "function": {"head": "SingleVariable", "variable": "x"} 11 | }, 12 | "constraints": [{ 13 | "function": {"head": "UnsupportedConstraintFunction"}, 14 | "set": {"head": "ZeroOne"} 15 | }] 16 | } 17 | -------------------------------------------------------------------------------- /test/FileFormats/MOF/failing_models/unsupported_constraint_set.mof.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MathOptFormat Model", 3 | "version": { 4 | "major": 0, 5 | "minor": 4 6 | }, 7 | "variables": [{"name": "x"}], 8 | "objective": { 9 | "sense": "min", 10 | "function": {"head": "SingleVariable", "variable": "x"} 11 | }, 12 | "constraints": [{ 13 | "function": {"head": "SingleVariable", "variable": "x"}, 14 | "set": {"head": "UnsupportedConstraintSet"} 15 | }] 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/CompatHelper.yml: -------------------------------------------------------------------------------- 1 | name: CompatHelper 2 | on: 3 | schedule: 4 | - cron: '00 00 * * *' 5 | jobs: 6 | CompatHelper: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: julia-actions/setup-julia@latest 10 | with: 11 | version: 1.3 12 | - name: Pkg.add("CompatHelper") 13 | run: julia -e 'using Pkg; Pkg.add("CompatHelper")' 14 | - name: CompatHelper.main() 15 | env: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | run: julia -e 'using CompatHelper; CompatHelper.main()' 18 | -------------------------------------------------------------------------------- /test/FileFormats/CBF/models/bad_cone_string_D.cbf: -------------------------------------------------------------------------------- 1 | VER 2 | 3 3 | 4 | POWCONES 5 | 2 4 6 | 2 7 | 4.0 8 | 1.0 9 | 2 10 | 1.0 11 | 1.0 12 | 13 | POW*CONES 14 | 2 4 15 | 2 16 | 3.0 17 | 1.0 18 | 2 19 | 1.0 20 | 9.0 21 | 22 | OBJSENSE 23 | MAX 24 | 25 | VAR 26 | 6 2 27 | @1:POW 3 28 | @1:POW* 3 29 | 30 | INT 31 | 3 32 | 0 33 | 2 34 | 4 35 | 36 | CON 37 | 6 2 38 | @0:bad_cone_string 3 39 | @0:POW* 3 40 | 41 | OBJACOORD 42 | 2 43 | 2 1.0 44 | 5 1.0 45 | 46 | ACOORD 47 | 6 48 | 1 0 1.0 49 | 2 0 1.0 50 | 2 1 1.0 51 | 4 4 1.0 52 | 5 3 1.0 53 | 5 4 1.0 54 | 55 | BCOORD 56 | 2 57 | 0 1.0 58 | 3 1.0 59 | -------------------------------------------------------------------------------- /test/FileFormats/CBF/models/example_B.cbf: -------------------------------------------------------------------------------- 1 | # Example C.3 from the CBF documentation version 2 2 | VER 3 | 2 4 | 5 | OBJSENSE 6 | MIN 7 | 8 | PSDVAR 9 | 1 10 | 2 11 | 12 | VAR 13 | 2 1 14 | F 2 15 | 16 | PSDCON 17 | 1 18 | 2 19 | 20 | CON 21 | 1 1 22 | L+ 1 23 | 24 | OBJFCOORD 25 | 2 26 | 0 0 0 1.0 27 | 0 1 1 1.0 28 | 29 | OBJACOORD 30 | 2 31 | 0 1.0 32 | 1 1.0 33 | 34 | OBJBCOORD 35 | 1.0 36 | 37 | FCOORD 38 | 1 39 | 0 0 1 0 1.0 40 | 41 | ACOORD 42 | 2 43 | 0 0 -1.0 44 | 0 1 -1.0 45 | 46 | HCOORD 47 | 4 48 | 0 0 1 0 1.0 49 | 0 0 1 1 3.0 50 | 0 1 0 0 3.0 51 | 0 1 1 0 1.0 52 | 53 | DCOORD 54 | 2 55 | 0 0 0 -1.0 56 | 0 1 1 -1.0 57 | -------------------------------------------------------------------------------- /test/FileFormats/CBF/models/example_D.cbf: -------------------------------------------------------------------------------- 1 | # Modified example C.3 from the CBF documentation version 3 2 | VER 3 | 3 4 | 5 | POWCONES 6 | 2 4 7 | 2 8 | 4.0 9 | 1.0 10 | 2 11 | 1.0 12 | 1.0 13 | 14 | POW*CONES 15 | 2 4 16 | 2 17 | 3.0 18 | 1.0 19 | 2 20 | 1.0 21 | 9.0 22 | 23 | OBJSENSE 24 | MAX 25 | 26 | VAR 27 | 6 2 28 | @1:POW 3 29 | @1:POW* 3 30 | 31 | INT 32 | 3 33 | 0 34 | 2 35 | 4 36 | 37 | CON 38 | 6 2 39 | @0:POW 3 40 | @0:POW* 3 41 | 42 | OBJACOORD 43 | 2 44 | 2 1.0 45 | 5 1.0 46 | 47 | ACOORD 48 | 6 49 | 1 0 1.0 50 | 2 0 1.0 51 | 2 1 1.0 52 | 4 4 1.0 53 | 5 3 1.0 54 | 5 4 1.0 55 | 56 | BCOORD 57 | 2 58 | 0 1.0 59 | 3 1.0 60 | -------------------------------------------------------------------------------- /test/isbits.jl: -------------------------------------------------------------------------------- 1 | # Test isbit-ness of VariableIndex and terms. 2 | # It is important that these struct remain isbits as otherwise, 3 | # the performance of function will be deteriored. 4 | # These tests explicit this design choice and makes sure that it remains the case. 5 | @testset "isbits" begin 6 | x = @inferred MOI.VariableIndex(1) 7 | @test isbits(x) 8 | at = @inferred MOI.ScalarAffineTerm(1.0, x) 9 | @test isbits(at) 10 | @test isbits(@inferred MOI.VectorAffineTerm(1, at)) 11 | qt = @inferred MOI.ScalarQuadraticTerm(1.0, x, x) 12 | @test isbits(qt) 13 | @test isbits(@inferred MOI.VectorQuadraticTerm(1, qt)) 14 | end 15 | -------------------------------------------------------------------------------- /docs/make.jl: -------------------------------------------------------------------------------- 1 | using Documenter, MathOptInterface 2 | 3 | makedocs( 4 | sitename = "MathOptInterface", 5 | format = Documenter.HTML( 6 | # See https://github.com/JuliaDocs/Documenter.jl/issues/868 7 | prettyurls = get(ENV, "CI", nothing) == "true", 8 | mathengine = Documenter.MathJax() 9 | ), 10 | # See https://github.com/JuliaOpt/JuMP.jl/issues/1576 11 | strict = true, 12 | pages = [ 13 | "Introduction" => "index.md", 14 | "Manual" => "apimanual.md", 15 | "Reference" => "apireference.md", 16 | ] 17 | ) 18 | 19 | deploydocs( 20 | repo = "github.com/JuliaOpt/MathOptInterface.jl.git", 21 | ) 22 | -------------------------------------------------------------------------------- /test/FileFormats/CBF/models/bad_cone_string_B.cbf: -------------------------------------------------------------------------------- 1 | VER 2 | 2 3 | 4 | OBJSENSE 5 | MIN 6 | 7 | PSDVAR 8 | 1 9 | 3 10 | 11 | VAR 12 | 3 1 13 | F 3 14 | 15 | CON 16 | 5 2 17 | L= 2 18 | bad_cone_string 3 19 | 20 | OBJFCOORD 21 | 5 22 | 0 0 0 2.0 23 | 0 1 0 1.0 24 | 0 1 1 2.0 25 | 0 2 1 1.0 26 | 0 2 2 2.0 27 | 28 | OBJACOORD 29 | 1 30 | 1 1.0 31 | 32 | FCOORD 33 | 9 34 | 0 0 0 0 1.0 35 | 0 0 1 1 1.0 36 | 0 0 2 2 1.0 37 | 1 0 0 0 1.0 38 | 1 0 1 0 1.0 39 | 1 0 2 0 1.0 40 | 1 0 1 1 1.0 41 | 1 0 1 2 1.0 42 | 1 0 2 2 1.0 43 | 44 | ACOORD 45 | 6 46 | 0 1 1.0 47 | 1 0 1.0 48 | 1 2 1.0 49 | 2 1 1.0 50 | 3 0 1.0 51 | 4 2 1.0 52 | 53 | BCOORD 54 | 2 55 | 0 -1.0 56 | 1 -0.5 57 | -------------------------------------------------------------------------------- /test/FileFormats/CBF/models/example_A.cbf: -------------------------------------------------------------------------------- 1 | # Example C.1 from the CBF documentation version 2 2 | VER 3 | 2 4 | 5 | OBJSENSE 6 | MIN 7 | 8 | PSDVAR 9 | 1 10 | 3 11 | 12 | VAR 13 | 3 1 14 | F 3 15 | 16 | CON 17 | 5 2 18 | L= 2 19 | Q 3 20 | 21 | OBJFCOORD 22 | 5 23 | 0 0 0 2.0 24 | 0 1 0 1.0 25 | 0 1 1 2.0 26 | 0 2 1 1.0 27 | 0 2 2 2.0 28 | 29 | OBJACOORD 30 | 1 31 | 1 1.0 32 | 33 | FCOORD 34 | 9 35 | 0 0 0 0 1.0 36 | 0 0 1 1 1.0 37 | 0 0 2 2 1.0 38 | 1 0 0 0 1.0 39 | 1 0 1 0 1.0 40 | 1 0 2 0 1.0 41 | 1 0 1 1 1.0 42 | 1 0 1 2 1.0 43 | 1 0 2 2 1.0 44 | 45 | ACOORD 46 | 6 47 | 0 1 1.0 48 | 1 0 1.0 49 | 1 2 1.0 50 | 2 1 1.0 51 | 3 0 1.0 52 | 4 2 1.0 53 | 54 | BCOORD 55 | 2 56 | 0 -1.0 57 | 1 -0.5 58 | -------------------------------------------------------------------------------- /test/Utilities/lazy_iterators.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | using MathOptInterface 3 | const MOI = MathOptInterface 4 | const MOIU = MOI.Utilities 5 | 6 | @testset "EmptyVector{$T}" for T in [Int, Float64] 7 | v = MOIU.EmptyVector{T}() 8 | @test size(v) == (0,) 9 | @test isempty(v) 10 | @test eltype(v) == T 11 | @test iterate(v) === nothing 12 | c = collect(v) 13 | @test c isa Vector{T} 14 | @test isempty(c) 15 | end 16 | 17 | @testset "LazyMap{$T}" for T in [Int, Float64] 18 | v = MOIU.LazyMap{T}(x -> x^2, [2, 3]) 19 | @test size(v) == (2,) 20 | @test !isempty(v) 21 | @test eltype(v) == T 22 | c = collect(v) 23 | @test c isa Vector{T} 24 | @test c == [4, 9] 25 | end 26 | -------------------------------------------------------------------------------- /test/Bridges/Constraint/bridge.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | 3 | using MathOptInterface 4 | const MOI = MathOptInterface 5 | const MOIT = MathOptInterface.Test 6 | const MOIU = MathOptInterface.Utilities 7 | const MOIB = MathOptInterface.Bridges 8 | 9 | struct DummyBridge <: MOIB.Constraint.AbstractBridge end 10 | 11 | @testset "AbstractBridge" begin 12 | model = MOIU.Model{Float64}() 13 | bridge = DummyBridge() 14 | attr = MOI.ConstraintPrimalStart() 15 | @test !MOI.supports(model, attr, typeof(bridge)) 16 | @test_throws MOI.UnsupportedAttribute(attr) MOI.set(model, attr, bridge, 1.0) 17 | attr = MOI.ConstraintFunction() 18 | err = MOI.SetAttributeNotAllowed(attr) 19 | @test_throws err MOI.set(model, attr, bridge, MOI.EqualTo(1.0)) 20 | end 21 | -------------------------------------------------------------------------------- /test/Test/config.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | import MathOptInterface 3 | const MOI = MathOptInterface 4 | const MOIT = MOI.Test 5 | const MOIU = MOI.Utilities 6 | 7 | function atest(model::MOI.ModelLike, config::MOIT.TestConfig) 8 | @test config.atol == 1e-8 9 | @test config.rtol == 1e-8 10 | @test config.solve 11 | @test config.query 12 | @test config.duals 13 | @test config.infeas_certificates 14 | end 15 | 16 | function btest(model::MOI.ModelLike, config::MOIT.TestConfig) 17 | @test false # b is in exclude 18 | end 19 | 20 | const customtests = Dict("a" => atest, 21 | "b" => btest) 22 | 23 | MOIT.@moitestset custom 24 | 25 | @testset "TestConfig" begin 26 | mock = MOIU.MockOptimizer(MOIU.Model{Float64}()) 27 | config = MOIT.TestConfig() 28 | customtest(mock, config, ["b"]) 29 | end 30 | -------------------------------------------------------------------------------- /test/functions.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | using MathOptInterface 3 | const MOI = MathOptInterface 4 | 5 | @testset "Functions" begin 6 | x = MOI.VariableIndex(1) 7 | y = MOI.VariableIndex(2) 8 | z = MOI.VariableIndex(3) 9 | @testset "Broadcast" begin 10 | xf = MOI.SingleVariable(x) 11 | yf = MOI.SingleVariable(y) 12 | zf = MOI.SingleVariable(z) 13 | function sum_indices(sv1::MOI.SingleVariable, sv2::MOI.SingleVariable) 14 | return sv1.variable.value + sv2.variable.value 15 | end 16 | @test sum_indices.(xf, [yf, zf]) == [3, 4] 17 | end 18 | @testset "Copy" begin 19 | @testset "VectorOfVariables" begin 20 | f = MOI.VectorOfVariables([x, y]) 21 | f_copy = copy(f) 22 | f_copy.variables[2] = z 23 | @test f.variables[2] == y 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /test/Utilities/print_with_acronym.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | using MathOptInterface 3 | const MOI = MathOptInterface 4 | const MOIU = MOI.Utilities 5 | 6 | @test sprint(MOIU.print_with_acronym, "MathOptInterface") == "MOI" 7 | @test sprint(MOIU.print_with_acronym, "MathOptInterface.MathOptInterface") == "MOI.MOI" 8 | @test sprint(MOIU.print_with_acronym, "MathOptInterface.Utilities.MathOptInterface") == "MOIU.MOI" 9 | @test sprint(MOIU.print_with_acronym, "MathOptInterfaceXXBridges") == "MOIXXBridges" 10 | @test sprint(MOIU.print_with_acronym, "MathOptInterface.BridgesXX") == "MOIBXX" 11 | @test sprint(MOIU.print_with_acronym, "MathOptInterface.Test.x") == "MOIT.x" 12 | @test sprint(MOIU.print_with_acronym, "MathOptInterface.x.Test") == "MOI.x.Test" 13 | @test sprint(MOIU.print_with_acronym, "MathOptInterface.Utilities.Test") == "MOIU.Test" 14 | @test sprint(MOIU.print_with_acronym, "MathOptInterface.Utilities.Test") == "MOIU.Test" 15 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - julia_version: 1.0 4 | 5 | platform: 6 | - x86 # 32-bit 7 | - x64 # 64-bit 8 | 9 | ## uncomment the following lines to allow failures on nightly julia 10 | ## (tests will run but not make your overall status red) 11 | #matrix: 12 | # allow_failures: 13 | # - julia_version: latest 14 | 15 | branches: 16 | only: 17 | - master 18 | - /release-.*/ 19 | 20 | notifications: 21 | - provider: Email 22 | on_build_success: false 23 | on_build_failure: false 24 | on_build_status_changed: false 25 | 26 | install: 27 | - ps: iex ((new-object net.webclient).DownloadString("https://raw.githubusercontent.com/JuliaCI/Appveyor.jl/version-1/bin/install.ps1")) 28 | 29 | build_script: 30 | - echo "%JL_BUILD_SCRIPT%" 31 | - C:\julia\bin\julia -e "%JL_BUILD_SCRIPT%" 32 | 33 | test_script: 34 | - echo "%JL_TEST_SCRIPT%" 35 | - C:\julia\bin\julia -e "%JL_TEST_SCRIPT%" 36 | -------------------------------------------------------------------------------- /test/Bridges/Variable/bridge.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | 3 | using MathOptInterface 4 | const MOI = MathOptInterface 5 | const MOIT = MathOptInterface.Test 6 | const MOIU = MathOptInterface.Utilities 7 | const MOIB = MathOptInterface.Bridges 8 | 9 | struct DummyVariableBridge <: MOIB.Variable.AbstractBridge end 10 | 11 | @testset "AbstractBridge" begin 12 | model = MOIU.Model{Float64}() 13 | bridge = DummyVariableBridge() 14 | attr = MOI.VariablePrimalStart() 15 | @test !MOI.supports(model, attr, typeof(bridge)) 16 | i = MOIB.Variable.IndexInVector(1) 17 | @test_throws MOI.UnsupportedAttribute(attr) MOI.set(model, attr, bridge, 1.0) 18 | @test_throws MOI.UnsupportedAttribute(attr) MOI.set(model, attr, bridge, 1.0, i) 19 | attr = MOI.VariablePrimal() 20 | err = MOI.SetAttributeNotAllowed(attr) 21 | @test_throws err MOI.set(model, attr, bridge, 1.0) 22 | @test_throws err MOI.set(model, attr, bridge, 1.0, i) 23 | end 24 | -------------------------------------------------------------------------------- /Project.toml: -------------------------------------------------------------------------------- 1 | name = "MathOptInterface" 2 | uuid = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" 3 | version = "0.9.13" 4 | 5 | [deps] 6 | BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" 7 | CodecBzip2 = "523fee87-0ab8-5b00-afb7-3ecf72e48cfd" 8 | CodecZlib = "944b1d66-785c-5afd-91f1-9de20f533193" 9 | JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" 10 | JSONSchema = "7d188eb4-7ad8-530c-ae41-71a32a6d4692" 11 | LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" 12 | MutableArithmetics = "d8a4904e-b15c-11e9-3269-09a3773c0cb0" 13 | OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" 14 | SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" 15 | Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" 16 | Unicode = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" 17 | 18 | [compat] 19 | BenchmarkTools = "0.4, 0.5" 20 | CodecBzip2 = "~0.6, 0.7" 21 | CodecZlib = "~0.6, 0.7" 22 | JSON = "~0.21" 23 | JSONSchema = "0.2" 24 | MutableArithmetics = "0.2" 25 | OrderedCollections = "1" 26 | julia = "1" 27 | -------------------------------------------------------------------------------- /test/interval.jl: -------------------------------------------------------------------------------- 1 | @testset "Interval" begin 2 | @test MOI.Interval(MOI.GreaterThan(1.)) === MOI.Interval(1., Inf) 3 | @test MOI.Interval(MOI.LessThan(2.)) === MOI.Interval(-Inf, 2.) 4 | @test MOI.Interval(MOI.EqualTo(3.)) === MOI.Interval(3., 3.) 5 | 6 | @test MOI.Interval(MOI.GreaterThan(1.f0)) === MOI.Interval(1.f0, Inf32) 7 | @test MOI.Interval(MOI.LessThan(2.f0)) === MOI.Interval(-Inf32, 2.f0) 8 | @test MOI.Interval(MOI.EqualTo(3.f0)) === MOI.Interval(3.f0, 3.f0) 9 | 10 | @test_throws MethodError MOI.Interval(MOI.GreaterThan(false)) 11 | @test_throws MethodError MOI.Interval(MOI.LessThan(true)) 12 | @test MOI.Interval(MOI.EqualTo(true)) === MOI.Interval(true, true) 13 | 14 | @test_throws MethodError MOI.Interval(MOI.GreaterThan(1)) 15 | @test_throws MethodError MOI.Interval(MOI.LessThan(2)) 16 | @test MOI.Interval(MOI.EqualTo(3)) === MOI.Interval(3, 3) 17 | 18 | @test MOI.Interval(MOI.Interval(1, 2)) == MOI.Interval(1, 2) 19 | end 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | ## Documentation: http://docs.travis-ci.com/user/languages/julia/ 2 | language: julia 3 | codecov: true 4 | os: 5 | - linux 6 | # - osx 7 | julia: 8 | - 1.0 9 | - 1.1 10 | - 1.2 11 | - 1.3 12 | - 1.4 13 | notifications: 14 | email: false 15 | git: 16 | depth: 99999999 17 | 18 | # Integration with JuMP-dev gitter channel 19 | notifications: 20 | webhooks: 21 | urls: 22 | - https://webhooks.gitter.im/e/cb052648b833828852b4 23 | on_success: change # options: [always|never|change] default: always 24 | on_failure: always # options: [always|never|change] default: always 25 | on_start: never # options: [always|never|change] default: always 26 | 27 | jobs: 28 | include: 29 | - stage: "Documentation" 30 | julia: 1.0 31 | os: linux 32 | script: 33 | - julia --project=docs/ -e 'import Pkg; Pkg.instantiate(); Pkg.develop(Pkg.PackageSpec(path=pwd()))' 34 | - julia --project=docs/ docs/make.jl 35 | after_success: skip 36 | -------------------------------------------------------------------------------- /test/runtests.jl: -------------------------------------------------------------------------------- 1 | using MathOptInterface 2 | const MOI = MathOptInterface 3 | const MOIT = MathOptInterface.Test 4 | const MOIU = MathOptInterface.Utilities 5 | const MOIB = MathOptInterface.Bridges 6 | 7 | using Test 8 | 9 | # It needs to be called first to trigger the crash. 10 | include("issue980.jl") 11 | 12 | # Tests for solvers are located in MOI.Test. 13 | 14 | include("dummy.jl") 15 | 16 | # MOI tests not relying on any submodule 17 | @testset "MOI" begin 18 | include("isbits.jl") 19 | include("isapprox.jl") 20 | include("interval.jl") 21 | include("errors.jl") 22 | include("functions.jl") 23 | include("sets.jl") 24 | include("attributes.jl") 25 | include("instantiate.jl") 26 | end 27 | 28 | # Utilities submodule tests 29 | @testset "MOI.$(submodule)" for submodule in [ 30 | "Benchmarks", 31 | "Bridges", 32 | "FileFormats", 33 | "Test", 34 | "Utilities", 35 | ] 36 | include("$(submodule)/$(submodule).jl") 37 | end 38 | 39 | # Test hygiene of @model macro 40 | include("hygiene.jl") 41 | -------------------------------------------------------------------------------- /test/Bridges/Bridges.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | 3 | function _timed_include(file) 4 | println("Testing ", file) 5 | start = time() 6 | include(file) 7 | run_time = round(time() - start, digits=1) 8 | println(" Took $(run_time) seconds") 9 | end 10 | 11 | @testset "$(file)" for file in ["bridge_optimizer.jl", "lazy_bridge_optimizer.jl"] 12 | _timed_include(file) 13 | end 14 | @testset "Variable bridges" begin 15 | variable_dir = joinpath(@__DIR__, "Variable") 16 | @testset "$(file)" for file in readdir(variable_dir) 17 | _timed_include(joinpath(variable_dir, file)) 18 | end 19 | end 20 | @testset "Constraint bridges" begin 21 | constraint_dir = joinpath(@__DIR__, "Constraint") 22 | @testset "$(file)" for file in readdir(constraint_dir) 23 | _timed_include(joinpath(constraint_dir, file)) 24 | end 25 | end 26 | @testset "Objective bridges" begin 27 | objective_dir = joinpath(@__DIR__, "Objective") 28 | @testset "$(file)" for file in readdir(objective_dir) 29 | _timed_include(joinpath(objective_dir, file)) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /src/Bridges/Objective/Objective.jl: -------------------------------------------------------------------------------- 1 | module Objective 2 | 3 | using MathOptInterface 4 | const MOI = MathOptInterface 5 | const MOIU = MOI.Utilities 6 | const MOIB = MOI.Bridges 7 | 8 | # Definition of an objective bridge 9 | include("bridge.jl") 10 | 11 | # Mapping between objective function attributes and bridges 12 | include("map.jl") 13 | 14 | # Bridge optimizer bridging a specific objective bridge 15 | include("single_bridge_optimizer.jl") 16 | 17 | # Objective bridges 18 | include("functionize.jl") 19 | const Functionize{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{FunctionizeBridge{T}, OT} 20 | include("slack.jl") 21 | const Slack{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{SlackBridge{T}, OT} 22 | 23 | """ 24 | add_all_bridges(bridged_model, T::Type) 25 | 26 | Add all bridges defined in the `Bridges.Objective` submodule to `bridged_model`. 27 | The coefficient type used is `T`. 28 | """ 29 | function add_all_bridges(bridged_model, T::Type) 30 | MOIB.add_bridge(bridged_model, FunctionizeBridge{T}) 31 | MOIB.add_bridge(bridged_model, SlackBridge{T}) 32 | return 33 | end 34 | 35 | end 36 | -------------------------------------------------------------------------------- /test/Utilities/Utilities.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | 3 | @testset "Functions" begin 4 | include("functions.jl") 5 | end 6 | @testset "Mutable Arithmetics" begin 7 | include("mutable_arithmetics.jl") 8 | end 9 | @testset "Sets" begin 10 | include("sets.jl") 11 | end 12 | @testset "Constraints" begin 13 | include("constraints.jl") 14 | end 15 | @testset "Variables" begin 16 | include("variables.jl") 17 | end 18 | @testset "Model" begin 19 | include("model.jl") 20 | end 21 | @testset "Universal Fallback" begin 22 | include("universalfallback.jl") 23 | end 24 | @testset "Parser" begin 25 | include("parser.jl") 26 | end 27 | @testset "Mock Optimizer" begin 28 | include("mockoptimizer.jl") 29 | end 30 | @testset "Caching Optimizer" begin 31 | include("cachingoptimizer.jl") 32 | end 33 | @testset "Copy" begin 34 | include("copy.jl") 35 | end 36 | 37 | @testset "CleverDicts" begin 38 | include("CleverDicts.jl") 39 | end 40 | @testset "Lazy iterators" begin 41 | include("lazy_iterators.jl") 42 | end 43 | 44 | @testset "Print with acronym" begin 45 | include("print_with_acronym.jl") 46 | end 47 | -------------------------------------------------------------------------------- /test/FileFormats/MPS/stacked_data.mps: -------------------------------------------------------------------------------- 1 | * min x + y 2 | * s.t. 1 <= x <= 5 con1 G == 1 + (-4) 3 | * 2 <= x <= 6 con2 L == 6 + (+4) 4 | * 3 <= x <= 7 con3 E == 3 + (4) 5 | * 4 <= 2x <= 8 con4 E == 8 + (-4) 6 | * y ∈ {1, 2, 3, 4} 7 | *2345678901234567890123456789012345678901234567890 8 | NAME stacked_data 9 | ROWS 10 | N obj 11 | N blank_obj 12 | G con1 13 | L con2 14 | E con3 15 | E con4 16 | COLUMNS 17 | x obj 1 con1 1 18 | x con2 1 con3 1 19 | x con4 1 20 | x con4 1 21 | y obj 1 22 | z obj 1 23 | x blank_obj 1 24 | y blank_obj 1 blank_obj 1 25 | RHS 26 | rhs con1 1 con2 6 27 | rhs con3 3 con4 8 28 | RANGES 29 | ranges con1 -4 con2 4 30 | ranges con3 4 31 | ranges con4 -4 32 | BOUNDS 33 | FR bounds x 0 34 | UI bounds y 4 35 | LI bounds y 1 36 | BV bounds z 1 37 | ENDATA 38 | -------------------------------------------------------------------------------- /src/Utilities/lazy_iterators.jl: -------------------------------------------------------------------------------- 1 | struct EmptyVector{T} <: AbstractVector{T} end 2 | Base.size(::EmptyVector) = (0,) 3 | Base.isempty(::EmptyVector) = true 4 | Base.eltype(::EmptyVector{T}) where {T} = T 5 | Base.iterate(::EmptyVector) = nothing 6 | 7 | """ 8 | struct LazyMap{T, VT} 9 | f::Function 10 | data::VT 11 | end 12 | 13 | Iterator over the elements of `data` mapped by `f`. This is similar to 14 | `Base.Generator(f, data)` except that the `eltype` of a `LazyMap` is given at 15 | construction while the `eltype` of `Base.Generator(f, data)` is `Any`. 16 | """ 17 | struct LazyMap{T, VT} 18 | f::Function 19 | data::VT 20 | end 21 | function LazyMap{T}(f::Function, data) where {T} 22 | return LazyMap{T, typeof(data)}(f, data) 23 | end 24 | Base.size(it::LazyMap) = size(it.data) 25 | function Base.iterate(it::LazyMap, args...) 26 | elem_state = iterate(it.data, args...) 27 | if elem_state === nothing 28 | return nothing 29 | else 30 | return it.f(elem_state[1]), elem_state[2] 31 | end 32 | end 33 | Base.IteratorSize(it::LazyMap) = Base.IteratorSize(it.data) 34 | Base.eltype(::LazyMap{T}) where {T} = T 35 | -------------------------------------------------------------------------------- /test/hygiene.jl: -------------------------------------------------------------------------------- 1 | module Hygiene 2 | 3 | using Test 4 | 5 | import MathOptInterface 6 | const MOI = MathOptInterface 7 | 8 | # Dict is used in the @model macro but setting Dict in the outer scope 9 | # should not affect it 10 | Dict = nothing 11 | 12 | MathOptInterface.Utilities.@model(LPModel, # Name of model 13 | (), # untyped scalar sets 14 | (MOI.EqualTo, MOI.GreaterThan, MOI.LessThan, MOI.Interval), # typed scalar sets 15 | (MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives), # untyped vector sets 16 | (), # typed vector sets 17 | (), # untyped scalar functions 18 | (MOI.ScalarAffineFunction,), # typed scalar functions 19 | (MOI.VectorOfVariables,), # untyped vector functions 20 | (MOI.VectorAffineFunction,)) # typed vector functions 21 | 22 | model = LPModel{Float64}() 23 | 24 | @test model isa MathOptInterface.ModelLike 25 | @test model isa MathOptInterface.Utilities.AbstractModel{Float64} 26 | 27 | end 28 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MathOptInterface.jl package is licensed under the MIT "Expat" License: 2 | 3 | > Copyright (c) 2017: Miles Lubin and contributors 4 | > Copyright (c) 2017: Google Inc. 5 | > 6 | > Permission is hereby granted, free of charge, to any person obtaining a copy 7 | > of this software and associated documentation files (the "Software"), to deal 8 | > in the Software without restriction, including without limitation the rights 9 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | > copies of the Software, and to permit persons to whom the Software is 11 | > furnished to do so, subject to the following conditions: 12 | > 13 | > The above copyright notice and this permission notice shall be included in all 14 | > copies or substantial portions of the Software. 15 | > 16 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | > SOFTWARE. 23 | > 24 | -------------------------------------------------------------------------------- /perf/cachingoptimizer.jl: -------------------------------------------------------------------------------- 1 | # See https://github.com/JuliaOpt/MathOptInterface.jl/issues/321, 2 | # https://github.com/JuliaOpt/MathOptInterface.jl/pull/323 and 3 | # https://github.com/JuliaOpt/MathOptInterface.jl/pull/390 4 | 5 | using BenchmarkTools 6 | using MathOptInterface 7 | const MOI = MathOptInterface 8 | const MOIU = MOI.Utilities 9 | 10 | @MOIU.model Model () (MOI.Interval,) () () () (MOI.ScalarAffineFunction,) () () 11 | optimizer = MOIU.MockOptimizer(Model{Float64}()) 12 | caching_optimizer = MOIU.CachingOptimizer(Model{Float64}(), optimizer) 13 | MOIU.reset_optimizer(caching_optimizer) # detach optimizer 14 | v = MOI.add_variables(caching_optimizer, 2) 15 | cf = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([0.0, 0.0], v), 0.0) 16 | c = MOI.add_constraint(caching_optimizer, cf, MOI.Interval(-Inf, 1.0)) 17 | @btime MOI.set($caching_optimizer, $(MOI.ConstraintSet()), $c, $(MOI.Interval(0.0, 2.0))) 18 | MOIU.attach_optimizer(caching_optimizer) 19 | @btime MOI.set($caching_optimizer, $(MOI.ConstraintSet()), $c, $(MOI.Interval(0.0, 2.0))) 20 | @btime MOI.set($(caching_optimizer.model_cache), $(MOI.ConstraintSet()), $c, $(MOI.Interval(0.0, 2.0))) 21 | @btime MOI.set($(caching_optimizer.optimizer), $(MOI.ConstraintSet()), $(caching_optimizer.model_to_optimizer_map[c]), $(MOI.Interval(0.0, 2.0))) 22 | -------------------------------------------------------------------------------- /src/Utilities/Utilities.jl: -------------------------------------------------------------------------------- 1 | module Utilities 2 | 3 | using LinearAlgebra # For dot 4 | 5 | using MathOptInterface 6 | const MOI = MathOptInterface 7 | 8 | const MOIU = MOI.Utilities # used in macro 9 | 10 | const SVF = MOI.SingleVariable 11 | const VVF = MOI.VectorOfVariables 12 | const SAF{T} = MOI.ScalarAffineFunction{T} 13 | const VAF{T} = MOI.VectorAffineFunction{T} 14 | const SQF{T} = MOI.ScalarQuadraticFunction{T} 15 | const VQF{T} = MOI.VectorQuadraticFunction{T} 16 | 17 | const VI = MOI.VariableIndex 18 | const CI{F,S} = MOI.ConstraintIndex{F,S} 19 | 20 | function print_with_acronym(io::IO, s::AbstractString) 21 | s = replace(s, "MathOptInterface.Utilities" => "MOIU") 22 | s = replace(s, "MathOptInterface.Bridges" => "MOIB") 23 | s = replace(s, "MathOptInterface.Test" => "MOIT") 24 | s = replace(s, "MathOptInterface" => "MOI") 25 | print(io, s) 26 | end 27 | 28 | include("functions.jl") 29 | include("mutable_arithmetics.jl") 30 | include("sets.jl") 31 | include("constraints.jl") 32 | include("copy.jl") 33 | include("results.jl") 34 | include("variables.jl") 35 | 36 | include("model.jl") 37 | include("parser.jl") 38 | include("mockoptimizer.jl") 39 | include("cachingoptimizer.jl") 40 | include("universalfallback.jl") 41 | 42 | include("CleverDicts.jl") 43 | include("lazy_iterators.jl") 44 | 45 | end # module 46 | -------------------------------------------------------------------------------- /test/dummy.jl: -------------------------------------------------------------------------------- 1 | import MathOptInterface 2 | const MOI = MathOptInterface 3 | const MOIU = MOI.Utilities 4 | 5 | abstract type AbstractDummyModel <: MOI.ModelLike end 6 | 7 | function MOI.empty!(::AbstractDummyModel) end 8 | function MOI.copy_to(dest::AbstractDummyModel, src::MOI.ModelLike; copy_names=true) 9 | return MOIU.default_copy_to(dest, src, copy_names) 10 | end 11 | MOI.supports(::AbstractDummyModel, ::MOI.ObjectiveSense) = true 12 | MOI.supports(::AbstractDummyModel, ::MOI.ConstraintPrimalStart, 13 | ::Type{<:MOI.ConstraintIndex}) = true 14 | MOI.supports_constraint(::AbstractDummyModel, ::Type{MOI.SingleVariable}, 15 | ::Type{MOI.EqualTo{Float64}}) = true 16 | MOI.supports_constraint(::AbstractDummyModel, ::Type{MOI.VectorOfVariables}, 17 | ::Type{MOI.Zeros}) = true 18 | 19 | struct DummyModel <: AbstractDummyModel 20 | end 21 | 22 | # Implements add_variable and add_constraint 23 | struct DummyModelWithAdd <: AbstractDummyModel 24 | end 25 | MOI.add_variable(::DummyModelWithAdd) = MOI.VariableIndex(0) 26 | MOI.add_variables(::DummyModelWithAdd, n) = fill(MOI.VariableIndex(0), n) 27 | function MOI.add_constraint(::DummyModelWithAdd, ::MOI.SingleVariable, 28 | ::MOI.EqualTo{Float64}) 29 | return MOI.ConstraintIndex{MOI.SingleVariable, MOI.EqualTo{Float64}}(0) 30 | end 31 | -------------------------------------------------------------------------------- /test/Bridges/Objective/map.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | using MathOptInterface 3 | const MOI = MathOptInterface 4 | const MOIB = MOI.Bridges 5 | 6 | struct ObjectiveDummyBridge <: MOIB.Objective.AbstractBridge 7 | id::Int 8 | end 9 | 10 | function test_empty(map) 11 | @test isempty(map) 12 | @test length(map) == 0 13 | @test isempty(values(map)) 14 | @test iterate(map) === nothing 15 | @test MOIB.Objective.function_type(map) === nothing 16 | end 17 | 18 | map = MOIB.Objective.Map() 19 | test_empty(map) 20 | @test sprint(MOIB.print_num_bridges, map) == "\nwith 0 objective bridges" 21 | 22 | x = MOI.VariableIndex(1) 23 | fx = MOI.SingleVariable(x) 24 | MOIB.Objective.add_key_for_bridge(map, ObjectiveDummyBridge(1), fx) 25 | @test MOIB.Objective.root_bridge(map) == ObjectiveDummyBridge(1) 26 | @test sprint(MOIB.print_num_bridges, map) == "\nwith 1 objective bridge" 27 | func = 1.0fx 28 | MOIB.Objective.add_key_for_bridge(map, ObjectiveDummyBridge(2), func) 29 | @test MOIB.Objective.root_bridge(map) == ObjectiveDummyBridge(2) 30 | @test sprint(MOIB.print_num_bridges, map) == "\nwith 2 objective bridges" 31 | 32 | empty!(map) 33 | test_empty(map) 34 | @test sprint(MOIB.print_num_bridges, map) == "\nwith 0 objective bridges" 35 | 36 | @testset "EmptyMap" begin 37 | map = MOIB.Objective.EmptyMap() 38 | test_empty(map) 39 | empty!(map) 40 | test_empty(map) 41 | @test sprint(MOIB.print_num_bridges, map) == "" 42 | end 43 | -------------------------------------------------------------------------------- /test/attributes.jl: -------------------------------------------------------------------------------- 1 | @testset "Attributes" begin 2 | @testset "is_set_by_optimize" begin 3 | @test MOI.is_set_by_optimize(MOI.TerminationStatus()) 4 | @test !MOI.is_set_by_optimize(MOI.ConstraintSet()) 5 | @test !MOI.is_set_by_optimize(MOI.ObjectiveSense()) 6 | end 7 | @testset "is_copyable" begin 8 | @test !MOI.is_copyable(MOI.TerminationStatus()) 9 | @test !MOI.is_copyable(MOI.ConstraintSet()) 10 | @test MOI.is_copyable(MOI.ObjectiveSense()) 11 | end 12 | @testset "supports" begin 13 | model = DummyModel() 14 | @test_throws ArgumentError MOI.supports(model, MOI.TerminationStatus()) 15 | @test_throws ArgumentError begin 16 | MOI.supports(model, MOI.ConstraintSet(), 17 | MOI.ConstraintIndex{MOI.SingleVariable, 18 | MOI.EqualTo{Float64}}) 19 | end 20 | @test MOI.supports(model, MOI.ObjectiveSense()) 21 | end 22 | @testset "set vector" begin 23 | attr = MOI.VariablePrimalStart() 24 | err = DimensionMismatch("Number of indices (1) does not match the " * 25 | "number of values (2) set to `$attr`.") 26 | model = DummyModel() 27 | x = MOI.VariableIndex(1) 28 | @test_throws err MOI.set(model, MOI.VariablePrimalStart(), [x], 29 | ones(2)) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MathOptInterface 2 | 3 | | **Documentation** | **Build Status** | **Social** | 4 | |:-----------------:|:----------------:|:----------:| 5 | | [![][docs-stable-img]][docs-stable-url] [![][docs-dev-img]][docs-dev-url] | [![Build Status][build-img]][build-url] [![Codecov branch][codecov-img]][codecov-url] | [![Gitter][gitter-img]][gitter-url] [][discourse-url] | 6 | 7 | An abstraction layer for mathematical optimization solvers. Replaces [MathProgBase](https://github.com/JuliaOpt/MathProgBase.jl). 8 | 9 | [docs-stable-img]: https://img.shields.io/badge/docs-stable-blue.svg 10 | [docs-dev-img]: https://img.shields.io/badge/docs-dev-blue.svg 11 | [docs-stable-url]: http://www.juliaopt.org/MathOptInterface.jl/stable 12 | [docs-dev-url]: http://www.juliaopt.org/MathOptInterface.jl/dev 13 | 14 | [build-img]: https://travis-ci.org/JuliaOpt/MathOptInterface.jl.svg?branch=master 15 | [build-url]: https://travis-ci.org/JuliaOpt/MathOptInterface.jl 16 | [codecov-img]: http://codecov.io/github/JuliaOpt/MathOptInterface.jl/coverage.svg?branch=master 17 | [codecov-url]: http://codecov.io/github/JuliaOpt/MathOptInterface.jl?branch=master 18 | 19 | [gitter-url]: https://gitter.im/JuliaOpt/JuMP-dev?utm_source=share-link&utm_medium=link&utm_campaign=share-link 20 | [gitter-img]: https://badges.gitter.im/JuliaOpt/JuMP-dev.svg 21 | [discourse-url]: https://discourse.julialang.org/c/domain/opt 22 | -------------------------------------------------------------------------------- /test/Utilities/constraints.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | import MathOptInterface 3 | const MOI = MathOptInterface 4 | 5 | @testset "Scalar" begin 6 | model = MOIU.Model{Float64}() 7 | x = MOI.add_variable(model) 8 | @testset "SingleVariable" begin 9 | f = MOI.SingleVariable(x) 10 | ci = MOIU.normalize_and_add_constraint(model, f, MOI.EqualTo(1.0), 11 | allow_modify_function = false) 12 | @test MOI.get(model, MOI.ConstraintFunction(), ci) == f 13 | @test MOI.get(model, MOI.ConstraintSet(), ci) == MOI.EqualTo(1.0) 14 | end 15 | @testset "ScalarAffineFunction" begin 16 | f = MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 2.0) 17 | g = MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 0.0) 18 | ci = MOIU.normalize_and_add_constraint(model, f, MOI.EqualTo(3.0)) 19 | @test f.constant == 2.0 20 | @test MOI.get(model, MOI.ConstraintFunction(), ci) ≈ g 21 | @test MOI.get(model, MOI.ConstraintSet(), ci) == MOI.EqualTo(1.0) 22 | ci = MOIU.normalize_and_add_constraint(model, f, MOI.Interval(-1.0, 1.0), 23 | allow_modify_function = true) 24 | @test f.constant == 0.0 25 | @test MOI.get(model, MOI.ConstraintFunction(), ci) ≈ g 26 | @test MOI.get(model, MOI.ConstraintSet(), ci) == MOI.Interval(-3.0, 27 | -1.0) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /src/Bridges/Objective/single_bridge_optimizer.jl: -------------------------------------------------------------------------------- 1 | """ 2 | SingleBridgeOptimizer{BT<:AbstractBridge, OT<:MOI.ModelLike} <: AbstractBridgeOptimizer 3 | 4 | The `SingleBridgeOptimizer` bridges any objective functions supported by the 5 | bridge `BT`. This is in contrast with the [`MathOptInterface.Bridges.LazyBridgeOptimizer`](@ref) 6 | which only bridges the objective functions that are unsupported by the internal model, 7 | even if they are supported by one of its bridges. 8 | """ 9 | mutable struct SingleBridgeOptimizer{BT<:AbstractBridge, OT<:MOI.ModelLike} <: MOIB.AbstractBridgeOptimizer 10 | model::OT 11 | map::Map # `MOI.ObjectiveFunction` -> objective bridge 12 | end 13 | function SingleBridgeOptimizer{BT}(model::OT) where {BT, OT <: MOI.ModelLike} 14 | SingleBridgeOptimizer{BT, OT}(model, Map()) 15 | end 16 | 17 | function bridges(bridge::MOI.Bridges.AbstractBridgeOptimizer) 18 | return EmptyMap() 19 | end 20 | bridges(bridge::SingleBridgeOptimizer) = bridge.map 21 | 22 | MOIB.supports_constraint_bridges(::SingleBridgeOptimizer) = false 23 | function MOIB.is_bridged(::SingleBridgeOptimizer, ::Type{<:MOI.AbstractSet}) 24 | return false 25 | end 26 | function MOIB.is_bridged(::SingleBridgeOptimizer, 27 | ::Type{<:MOI.AbstractFunction}, 28 | ::Type{<:MOI.AbstractSet}) 29 | return false 30 | end 31 | function MOIB.supports_bridging_objective_function( 32 | ::SingleBridgeOptimizer{BT}, F::Type{<:MOI.AbstractScalarFunction}) where BT 33 | return supports_objective_function(BT, F) 34 | end 35 | function MOIB.is_bridged( 36 | b::SingleBridgeOptimizer, F::Type{<:MOI.AbstractScalarFunction}) 37 | return MOIB.supports_bridging_objective_function(b, F) 38 | end 39 | function MOIB.bridge_type(::SingleBridgeOptimizer{BT}, 40 | ::Type{<:MOI.AbstractScalarFunction}) where BT 41 | return BT 42 | end 43 | -------------------------------------------------------------------------------- /test/Benchmarks/Benchmarks.jl: -------------------------------------------------------------------------------- 1 | using MathOptInterface, Test 2 | 3 | const MOI = MathOptInterface 4 | const MOIU = MOI.Utilities 5 | 6 | const NUM_BENCHMARKS = length(MOI.Benchmarks.BENCHMARKS) 7 | 8 | @testset "suite" begin 9 | suite = MOI.Benchmarks.suite() do 10 | MOIU.MockOptimizer(MOIU.Model{Float64}()) 11 | end 12 | @test length(suite.data) == NUM_BENCHMARKS 13 | 14 | suite = MOI.Benchmarks.suite( 15 | exclude = [r"delete_"] 16 | ) do 17 | MOIU.MockOptimizer(MOIU.Model{Float64}()) 18 | end 19 | # Note: update this value whenever more benchmarks are added to 20 | # `src/Benchmarks/Benchmarks.jl`. 21 | @test 6 <= length(suite.data) <= NUM_BENCHMARKS - 3 22 | end 23 | 24 | @testset "Perform benchmark" begin 25 | params = joinpath(@__DIR__, "baseline_params.json") 26 | baseline = joinpath(@__DIR__, "baseline_baseline.json") 27 | @test !isfile(params) 28 | @test !isfile(baseline) 29 | @testset "create_baseline" begin 30 | suite = MOI.Benchmarks.suite() do 31 | MOIU.MockOptimizer(MOIU.Model{Float64}()) 32 | end 33 | MOI.Benchmarks.create_baseline( 34 | suite, "baseline"; directory=@__DIR__, seconds = 2, verbose = true 35 | ) 36 | end 37 | @test isfile(params) 38 | @test isfile(baseline) 39 | @testset "compare_against_baseline" begin 40 | suite = MOI.Benchmarks.suite() do 41 | MOIU.MockOptimizer(MOIU.Model{Float64}()) 42 | end 43 | MOI.Benchmarks.compare_against_baseline( 44 | suite, "baseline"; directory=@__DIR__, seconds = 2, verbose = true 45 | ) 46 | end 47 | rm(params) 48 | rm(baseline) 49 | @testset "Report" begin 50 | report = read(joinpath(@__DIR__, "report.txt"), String) 51 | @test occursin("=> invariant", report) 52 | end 53 | rm(joinpath(@__DIR__, "report.txt")) 54 | end 55 | -------------------------------------------------------------------------------- /test/Bridges/Constraint/square.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | 3 | using MathOptInterface 4 | const MOI = MathOptInterface 5 | const MOIT = MathOptInterface.Test 6 | const MOIU = MathOptInterface.Utilities 7 | const MOIB = MathOptInterface.Bridges 8 | 9 | include("../utilities.jl") 10 | 11 | mock = MOIU.MockOptimizer(MOIU.Model{Float64}()) 12 | config = MOIT.TestConfig() 13 | 14 | @testset "Square" begin 15 | bridged_mock = MOIB.Constraint.Square{Float64}(mock) 16 | 17 | MOIT.basic_constraint_tests( 18 | bridged_mock, config, 19 | include = [(F, S) 20 | for F in [MOI.VectorOfVariables, MOI.VectorAffineFunction{Float64}, 21 | MOI.VectorQuadraticFunction{Float64}] 22 | for S in [MOI.PositiveSemidefiniteConeSquare]]) 23 | 24 | 25 | mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, ones(4), 26 | (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => [2, 2]) 27 | MOIT.psds0vtest(bridged_mock, config) 28 | mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, ones(4), 29 | (MOI.VectorAffineFunction{Float64}, MOI.PositiveSemidefiniteConeTriangle) => [[1, -1, 1]], 30 | (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => [2, 2]) 31 | MOIT.psds0ftest(bridged_mock, config) 32 | ci = first(MOI.get(bridged_mock, 33 | MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, 34 | MOI.PositiveSemidefiniteConeSquare}())) 35 | test_delete_bridge(bridged_mock, ci, 4, 36 | ((MOI.VectorAffineFunction{Float64}, 37 | MOI.PositiveSemidefiniteConeTriangle, 0), 38 | (MOI.ScalarAffineFunction{Float64}, 39 | MOI.EqualTo{Float64}, 1))) 40 | end 41 | -------------------------------------------------------------------------------- /src/Utilities/sets.jl: -------------------------------------------------------------------------------- 1 | """ 2 | shift_constant(set::MOI.AbstractScalarSet, 3 | offset) 4 | 5 | Returns a new scalar set `new_set` such that `func`-in-`set` is equivalent to 6 | `func + offset`-in-`new_set`. 7 | 8 | ## Examples 9 | 10 | The call `shift_constant(MOI.Interval(-2, 3), 1)` is equal to 11 | `MOI.Interval(-1, 4)`. 12 | """ 13 | function shift_constant(set::Union{MOI.LessThan{T}, 14 | MOI.GreaterThan{T}, 15 | MOI.EqualTo{T}}, 16 | offset::T) where T 17 | return typeof(set)(MOI.constant(set) + offset) 18 | end 19 | function shift_constant(set::MOI.Interval, offset) 20 | return MOI.Interval(set.lower + offset, set.upper + offset) 21 | end 22 | 23 | const ScalarLinearSet{T} = Union{MOI.EqualTo{T}, MOI.LessThan{T}, MOI.GreaterThan{T}} 24 | const VectorLinearSet = Union{MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives} 25 | 26 | vector_set_type(::Type{<:MOI.EqualTo}) = MOI.Zeros 27 | vector_set_type(::Type{<:MOI.LessThan}) = MOI.Nonpositives 28 | vector_set_type(::Type{<:MOI.GreaterThan}) = MOI.Nonnegatives 29 | 30 | scalar_set_type(::Type{<:MOI.Zeros}, T::Type) = MOI.EqualTo{T} 31 | scalar_set_type(::Type{<:MOI.Nonpositives}, T::Type) = MOI.LessThan{T} 32 | scalar_set_type(::Type{<:MOI.Nonnegatives}, T::Type) = MOI.GreaterThan{T} 33 | 34 | """ 35 | is_diagonal_vectorized_index(index::Base.Integer) 36 | 37 | Return whether `index` is the index of a diagonal element in a 38 | [`MOI.AbstractSymmetricMatrixSetTriangle`](@ref) set. 39 | """ 40 | function is_diagonal_vectorized_index(index::Base.Integer) 41 | perfect_square = 1 + 8index 42 | return isqrt(perfect_square)^2 == perfect_square 43 | end 44 | 45 | """ 46 | side_dimension_for_vectorized_dimension(n::Integer) 47 | 48 | Return the dimension `d` such that 49 | `MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(d))` is `n`. 50 | """ 51 | function side_dimension_for_vectorized_dimension(n::Base.Integer) 52 | return div(isqrt(1 + 8n), 2) 53 | end 54 | -------------------------------------------------------------------------------- /src/Utilities/constraints.jl: -------------------------------------------------------------------------------- 1 | """ 2 | normalize_and_add_constraint(model::MOI.ModelLike, 3 | func::MOI.AbstractScalarFunction, 4 | set::MOI.AbstractScalarSet; 5 | allow_modify_function::Bool=false) 6 | 7 | Adds the scalar constraint obtained by moving the constant term in `func` to 8 | the set in `model`. If `allow_modify_function` is `true` then the function 9 | `func` can be modified. 10 | """ 11 | function normalize_and_add_constraint end 12 | 13 | function normalize_and_add_constraint(model::MOI.ModelLike, 14 | func::MOI.AbstractScalarFunction, 15 | set::MOI.AbstractScalarSet; 16 | allow_modify_function::Bool=false) where T 17 | return MOI.add_constraint( 18 | model, normalize_constant( 19 | func, set; allow_modify_function=allow_modify_function)...) 20 | end 21 | 22 | """ 23 | normalize_constant(func::MOI.AbstractScalarFunction, 24 | set::MOI.AbstractScalarSet; 25 | allow_modify_function::Bool=false) 26 | 27 | Return the `func`-in-`set` constraint in normalized form. That is, if `func` is 28 | [`MOI.ScalarQuadraticFunction`](@ref) or 29 | [`MOI.ScalarAffineFunction`](@ref), the 30 | constant is moved to the set. If `allow_modify_function` is `true` then the 31 | function `func` can be modified. 32 | """ 33 | function normalize_constant(func::MOI.AbstractFunction, set::MOI.AbstractSet; 34 | allow_modify_function::Bool=false) 35 | return func, set 36 | end 37 | function normalize_constant( 38 | func::Union{MOI.ScalarAffineFunction{T}, MOI.ScalarQuadraticFunction{T}}, 39 | set::MOI.AbstractScalarSet; allow_modify_function::Bool=false) where T 40 | set = shift_constant(set, -func.constant) 41 | if !allow_modify_function 42 | func = copy(func) 43 | end 44 | func.constant = zero(T) 45 | return func, set 46 | end 47 | -------------------------------------------------------------------------------- /src/Bridges/Constraint/single_bridge_optimizer.jl: -------------------------------------------------------------------------------- 1 | """ 2 | SingleBridgeOptimizer{BT<:AbstractBridge, OT<:MOI.ModelLike} <: AbstractBridgeOptimizer 3 | 4 | The `SingleBridgeOptimizer` bridges any constraint supported by the bridge `BT`. 5 | This is in contrast with the [`MathOptInterface.Bridges.LazyBridgeOptimizer`](@ref) 6 | which only bridges the constraints that are unsupported by the internal model, 7 | even if they are supported by one of its bridges. 8 | """ 9 | mutable struct SingleBridgeOptimizer{BT<:AbstractBridge, OT<:MOI.ModelLike} <: MOIB.AbstractBridgeOptimizer 10 | model::OT 11 | map::Map # index of bridged constraint -> constraint bridge 12 | con_to_name::Dict{MOI.ConstraintIndex, String} 13 | name_to_con::Union{Dict{String, MOI.ConstraintIndex}, Nothing} 14 | end 15 | function SingleBridgeOptimizer{BT}(model::OT) where {BT, OT <: MOI.ModelLike} 16 | SingleBridgeOptimizer{BT, OT}( 17 | model, Map(), Dict{MOI.ConstraintIndex, String}(), nothing) 18 | end 19 | 20 | function bridges(bridge::MOI.Bridges.AbstractBridgeOptimizer) 21 | return EmptyMap() 22 | end 23 | function bridges(bridge::SingleBridgeOptimizer) 24 | return bridge.map 25 | end 26 | 27 | MOIB.supports_constraint_bridges(::SingleBridgeOptimizer) = true 28 | function MOIB.is_bridged(::SingleBridgeOptimizer, ::Type{<:MOI.AbstractSet}) 29 | return false 30 | end 31 | function MOIB.supports_bridging_constraint( 32 | ::SingleBridgeOptimizer{BT}, F::Type{<:MOI.AbstractFunction}, 33 | S::Type{<:MOI.AbstractSet}) where BT 34 | return MOI.supports_constraint(BT, F, S) 35 | end 36 | function MOIB.is_bridged(b::SingleBridgeOptimizer, F::Type{<:MOI.AbstractFunction}, 37 | S::Type{<:MOI.AbstractSet}) 38 | return MOIB.supports_bridging_constraint(b, F, S) 39 | end 40 | function MOIB.is_bridged(::SingleBridgeOptimizer, ::Type{<:MOI.AbstractScalarFunction}) 41 | return false 42 | end 43 | function MOIB.bridge_type(::SingleBridgeOptimizer{BT}, 44 | ::Type{<:MOI.AbstractFunction}, 45 | ::Type{<:MOI.AbstractSet}) where BT 46 | return BT 47 | end 48 | -------------------------------------------------------------------------------- /src/error.jl: -------------------------------------------------------------------------------- 1 | """ 2 | UnsupportedError <: Exception 3 | 4 | Abstract type for error thrown when an element is not supported by the model. 5 | """ 6 | abstract type UnsupportedError <: Exception end 7 | 8 | """ 9 | element_name(err::UnsupportedError) 10 | 11 | Return the name of the element that is not supported. 12 | """ 13 | function element_name end 14 | 15 | function Base.showerror(io::IO, err::UnsupportedError) 16 | print(io, typeof(err), ": ", element_name(err), 17 | " is not supported by the model") 18 | m = message(err) 19 | if Base.isempty(m) 20 | print(io, ".") 21 | else 22 | print(io, ": ", m) 23 | end 24 | end 25 | 26 | """ 27 | NotAllowedError <: Exception 28 | 29 | Abstract type for error thrown when an operation is supported but cannot be 30 | applied in the current state of the model. 31 | """ 32 | abstract type NotAllowedError <: Exception end 33 | 34 | """ 35 | operation_name(err::NotAllowedError) 36 | 37 | Return the name of the operation throwing the error in a gerund (i.e. -ing 38 | form). 39 | """ 40 | function operation_name end 41 | 42 | function Base.showerror(io::IO, err::NotAllowedError) 43 | print(io, typeof(err), ": ", operation_name(err), 44 | " cannot be performed") 45 | m = message(err) 46 | if Base.isempty(m) 47 | print(io, ".") 48 | else 49 | print(io, ": ", m) 50 | end 51 | print(io, " You may want to use a `CachingOptimizer` in `AUTOMATIC` mode", 52 | " or you may need to call `reset_optimizer` before doing this", 53 | " operation if the `CachingOptimizer` is in `MANUAL` mode.") 54 | end 55 | 56 | """ 57 | message(err::Union{UnsupportedError, NotAllowedError}) 58 | 59 | Return a `String` containing a human-friendly explanation of why the operation 60 | is not supported/cannot be performed. It is printed in the error message if it 61 | is not empty. By convention, it should be stored in the `message` field; if 62 | this is the case, the `message` method does not have to be implemented. 63 | """ 64 | message(err::Union{UnsupportedError, NotAllowedError}) = err.message 65 | -------------------------------------------------------------------------------- /test/Bridges/Objective/functionize.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | 3 | using MathOptInterface 4 | const MOI = MathOptInterface 5 | const MOIT = MathOptInterface.Test 6 | const MOIU = MathOptInterface.Utilities 7 | const MOIB = MathOptInterface.Bridges 8 | 9 | include("../utilities.jl") 10 | 11 | mock = MOIU.MockOptimizer(MOIU.Model{Float64}()) 12 | config = MOIT.TestConfig() 13 | 14 | bridged_mock = MOIB.Objective.Functionize{Float64}(mock) 15 | 16 | @testset "solve_singlevariable_obj" begin 17 | MOIU.set_mock_optimize!(mock, 18 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0], MOI.FEASIBLE_POINT) 19 | ) 20 | MOIT.solve_singlevariable_obj(bridged_mock, config) 21 | @test MOI.get(mock, MOI.ObjectiveFunctionType()) == MOI.ScalarAffineFunction{Float64} 22 | @test MOI.get(bridged_mock, MOI.ObjectiveFunctionType()) == MOI.SingleVariable 23 | @test MOI.get(mock, MOI.ObjectiveSense()) == MOI.MIN_SENSE 24 | @test MOI.get(bridged_mock, MOI.ObjectiveSense()) == MOI.MIN_SENSE 25 | vis = MOI.get(bridged_mock, MOI.ListOfVariableIndices()) 26 | func = MOI.SingleVariable(vis[1]) 27 | @test MOI.get(mock, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}()) ≈ 28 | convert(MOI.ScalarAffineFunction{Float64}, func) 29 | @test MOI.get(bridged_mock, MOI.ObjectiveFunction{MOI.SingleVariable}()) == func 30 | MOI.set(bridged_mock, MOI.ObjectiveSense(), MOI.MAX_SENSE) 31 | @test MOI.get(mock, MOI.ObjectiveSense()) == MOI.MAX_SENSE 32 | @test MOI.get(bridged_mock, MOI.ObjectiveSense()) == MOI.MAX_SENSE 33 | test_delete_objective(bridged_mock, 1, tuple()) 34 | end 35 | 36 | # Tests that the `ObjectiveValue` attribute passed has the correct 37 | # `result_index`. 38 | @testset "solve_result_index" begin 39 | MOIU.set_mock_optimize!(mock, 40 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( 41 | mock, 42 | MOI.OPTIMAL, 43 | (MOI.FEASIBLE_POINT, [1.0]), 44 | MOI.FEASIBLE_POINT, 45 | (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [1.0], 46 | ) 47 | ) 48 | MOIT.solve_result_index(bridged_mock, config) 49 | end 50 | -------------------------------------------------------------------------------- /test/Bridges/Constraint/indicator_activate_on_zero.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | 3 | using MathOptInterface 4 | const MOI = MathOptInterface 5 | const MOIT = MathOptInterface.Test 6 | const MOIU = MathOptInterface.Utilities 7 | const MOIB = MathOptInterface.Bridges 8 | 9 | include("../utilities.jl") 10 | 11 | @testset "Indicator activated on 0" begin 12 | # linear problem with indicator constraint 13 | # similar to indicator1_test with reversed z1 14 | # max 2x1 + 3x2 15 | # s.t. x1 + x2 <= 10 16 | # z1 == 0 ==> x2 <= 8 17 | # z2 == 1 ==> x2 + x1/5 <= 9 18 | # (1-z1) + z2 >= 1 <=> z2 - z1 >= 0 19 | model = MOIU.MockOptimizer(MOIU.Model{Float64}()); 20 | config = MOIT.TestConfig() 21 | 22 | x1 = MOI.add_variable(model) 23 | x2 = MOI.add_variable(model) 24 | z1 = MOI.add_variable(model) 25 | z2 = MOI.add_variable(model) 26 | 27 | vc1 = MOI.add_constraint(model, z1, MOI.ZeroOne()) 28 | @test vc1.value == z1.value 29 | vc2 = MOI.add_constraint(model, z2, MOI.ZeroOne()) 30 | @test vc2.value == z2.value 31 | f1 = MOI.VectorAffineFunction( 32 | [MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, z1)), 33 | MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, x2)), 34 | ], 35 | [0.0, 0.0] 36 | ) 37 | iset1 = MOI.IndicatorSet{MOI.ACTIVATE_ON_ZERO}(MOI.LessThan(8.0)) 38 | 39 | 40 | BT = MOIB.Constraint.concrete_bridge_type(MOIB.Constraint.IndicatorActiveOnFalseBridge{Float64}, typeof(f1), typeof(iset1)) 41 | BT2 = MOIB.Constraint.concrete_bridge_type(MOIB.Constraint.IndicatorActiveOnFalseBridge, typeof(f1), typeof(iset1)) 42 | bridge = MOIB.Constraint.bridge_constraint(BT, model, f1, iset1) 43 | 44 | @test BT === BT2 45 | @test bridge isa BT 46 | 47 | z1comp = bridge.variable_index 48 | @test MOI.get(model, MOI.ConstraintFunction(), bridge.zero_one_cons) == MOI.SingleVariable(z1comp) 49 | @test MOI.get(model, MOI.ConstraintSet(), bridge.disjunction_cons) == MOI.EqualTo(1.0) 50 | disjunction_cons = MOI.get(model, MOI.ConstraintFunction(), bridge.disjunction_cons) 51 | for t in disjunction_cons.terms 52 | @test t.variable_index == z1 || t.variable_index == z1comp 53 | @test t.coefficient ≈ 1.0 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /perf/bellman_ford.jl: -------------------------------------------------------------------------------- 1 | using BenchmarkTools 2 | using MathOptInterface 3 | const MOI = MathOptInterface 4 | const MOIU = MOI.Utilities 5 | const MOIB = MOI.Bridges 6 | 7 | # Model similar to SDPA format, it gives a good example because it does not 8 | # support a lot hence need a lot of bridges 9 | MOIU.@model(SDPAModel, 10 | (), (MOI.EqualTo,), (MOI.Nonnegatives, MOI.PositiveSemidefiniteConeTriangle), (), 11 | (), (MOI.ScalarAffineFunction,), (MOI.VectorOfVariables,), ()) 12 | MOI.supports_constraint(::SDPAModel{T}, ::Type{MOI.SingleVariable}, ::Type{MOI.GreaterThan{T}}) where {T} = false 13 | MOI.supports_constraint(::SDPAModel{T}, ::Type{MOI.SingleVariable}, ::Type{MOI.LessThan{T}}) where {T} = false 14 | MOI.supports_constraint(::SDPAModel{T}, ::Type{MOI.SingleVariable}, ::Type{MOI.EqualTo{T}}) where {T} = false 15 | MOI.supports_constraint(::SDPAModel{T}, ::Type{MOI.SingleVariable}, ::Type{MOI.Interval{T}}) where {T} = false 16 | MOI.supports_constraint(::SDPAModel, ::Type{MOI.VectorOfVariables}, ::Type{MOI.Reals}) = false 17 | MOI.supports(::SDPAModel{T}, ::MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{T}}) where {T} = false 18 | MOI.supports(::SDPAModel, ::MOI.ObjectiveFunction{MOI.SingleVariable}) = false 19 | 20 | function interval_constraint() 21 | model = SDPAModel{Float64}() 22 | bridged = MOIB.full_bridge_optimizer(model, Float64) 23 | F = MOI.ScalarAffineFunction{Float64} 24 | S = MOI.Interval{Float64} 25 | MOIB.bridge_index(bridged, F, S) 26 | display(@benchmark begin 27 | MOIB._reset_dist($bridged) 28 | MOIB.node($bridged, $F, $S) 29 | end) 30 | display(@benchmark begin 31 | MOIB._reset_dist($bridged) 32 | MOIB.bridge_index($bridged, $F, $S) 33 | end) 34 | end 35 | 36 | interval_constraint() 37 | 38 | function quadratic_objective() 39 | model = SDPAModel{Float64}() 40 | bridged = MOIB.full_bridge_optimizer(model, Float64) 41 | F = MOI.ScalarQuadraticFunction{Float64} 42 | MOIB.bridge_index(bridged, F) 43 | display(@benchmark begin 44 | MOIB._reset_dist($bridged) 45 | MOIB.node($bridged, $F) 46 | end) 47 | display(@benchmark begin 48 | MOIB._reset_dist($bridged) 49 | MOIB.bridge_index($bridged, $F) 50 | end) 51 | end 52 | 53 | display(quadratic_objective()) 54 | -------------------------------------------------------------------------------- /src/Bridges/Objective/functionize.jl: -------------------------------------------------------------------------------- 1 | """ 2 | FunctionizeBridge{T} 3 | 4 | The `FunctionizeBridge` converts a `SingleVariable` objective into a 5 | `ScalarAffineFunction{T}` objective. 6 | """ 7 | struct FunctionizeBridge{T} <: AbstractBridge end 8 | function bridge_objective(::Type{FunctionizeBridge{T}}, model::MOI.ModelLike, 9 | func::MOI.SingleVariable) where T 10 | F = MOI.ScalarAffineFunction{T} 11 | MOI.set(model, MOI.ObjectiveFunction{F}(), convert(F, func)) 12 | return FunctionizeBridge{T}() 13 | end 14 | 15 | function supports_objective_function( 16 | ::Type{<:FunctionizeBridge}, ::Type{MOI.SingleVariable}) 17 | return true 18 | end 19 | MOIB.added_constrained_variable_types(::Type{<:FunctionizeBridge}) = Tuple{DataType}[] 20 | function MOIB.added_constraint_types(::Type{<:FunctionizeBridge}) 21 | return Tuple{DataType, DataType}[] 22 | end 23 | function MOIB.set_objective_function_type(::Type{FunctionizeBridge{T}}) where T 24 | return MOI.ScalarAffineFunction{T} 25 | end 26 | 27 | # Attributes, Bridge acting as a model 28 | function MOI.get(bridge::FunctionizeBridge, ::MOI.NumberOfVariables) 29 | return 0 30 | end 31 | function MOI.get(bridge::FunctionizeBridge, ::MOI.ListOfVariableIndices) 32 | return MOI.VariableIndex[] 33 | end 34 | 35 | # No variables or constraints are created in this bridge so there is nothing to 36 | # delete. 37 | function MOI.delete(model::MOI.ModelLike, bridge::FunctionizeBridge) end 38 | 39 | function MOI.set(::MOI.ModelLike, ::MOI.ObjectiveSense, 40 | ::FunctionizeBridge, ::MOI.OptimizationSense) 41 | # `FunctionizeBridge` is sense agnostic, therefore, we don't need to change 42 | # anything. 43 | end 44 | function MOI.get(model::MOI.ModelLike, 45 | attr::MOIB.ObjectiveFunctionValue{MOI.SingleVariable}, 46 | bridge::FunctionizeBridge{T}) where T 47 | F = MOI.ScalarAffineFunction{T} 48 | return MOI.get(model, MOIB.ObjectiveFunctionValue{F}(attr.result_index)) 49 | end 50 | function MOI.get(model::MOI.ModelLike, attr::MOI.ObjectiveFunction{MOI.SingleVariable}, 51 | bridge::FunctionizeBridge{T}) where T 52 | F = MOI.ScalarAffineFunction{T} 53 | func = MOI.get(model, MOI.ObjectiveFunction{F}()) 54 | return convert(MOI.SingleVariable, func) 55 | end 56 | -------------------------------------------------------------------------------- /src/Bridges/Variable/Variable.jl: -------------------------------------------------------------------------------- 1 | module Variable 2 | 3 | using MathOptInterface 4 | const MOI = MathOptInterface 5 | const MOIU = MOI.Utilities 6 | const MOIB = MOI.Bridges 7 | 8 | # Definition of a variable bridge 9 | include("bridge.jl") 10 | 11 | # Mapping between variable indices and bridges 12 | include("map.jl") 13 | 14 | # Bridge optimizer bridging a specific variable bridge 15 | include("single_bridge_optimizer.jl") 16 | 17 | # TODO(odow): the compiler in Julia <= 1.2 (and in later versions unless 18 | # fixed) gets stuck compiling add_constrained_variable for some inputs. This 19 | # method seemed necessary to fix it. 20 | # See https://github.com/JuliaLang/julia/issues/32167 for more. 21 | function MOI.Bridges.Variable.bridge_constrained_variable(BridgeType, b, s) 22 | throw(MOI.UnsupportedConstraint{MOIU.variable_function_type(typeof(s)), typeof(s)}()) 23 | end 24 | 25 | # Variable bridges 26 | include("zeros.jl") 27 | const Zeros{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{ZerosBridge{T}, OT} 28 | include("free.jl") 29 | const Free{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{FreeBridge{T}, OT} 30 | include("flip_sign.jl") 31 | const NonposToNonneg{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{NonposToNonnegBridge{T}, OT} 32 | include("vectorize.jl") 33 | const Vectorize{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{VectorizeBridge{T}, OT} 34 | include("soc_to_rsoc.jl") 35 | const SOCtoRSOC{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{SOCtoRSOCBridge{T}, OT} 36 | include("rsoc_to_soc.jl") 37 | const RSOCtoSOC{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{RSOCtoSOCBridge{T}, OT} 38 | include("rsoc_to_psd.jl") 39 | const RSOCtoPSD{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{RSOCtoPSDBridge{T}, OT} 40 | 41 | """ 42 | add_all_bridges(bridged_model, T::Type) 43 | 44 | Add all bridges defined in the `Bridges.Variable` submodule to `bridged_model`. 45 | The coefficient type used is `T`. 46 | """ 47 | function add_all_bridges(bridged_model, T::Type) 48 | MOIB.add_bridge(bridged_model, ZerosBridge{T}) 49 | MOIB.add_bridge(bridged_model, FreeBridge{T}) 50 | MOIB.add_bridge(bridged_model, NonposToNonnegBridge{T}) 51 | MOIB.add_bridge(bridged_model, VectorizeBridge{T}) 52 | MOIB.add_bridge(bridged_model, SOCtoRSOCBridge{T}) 53 | MOIB.add_bridge(bridged_model, RSOCtoSOCBridge{T}) 54 | MOIB.add_bridge(bridged_model, RSOCtoPSDBridge{T}) 55 | return 56 | end 57 | 58 | end 59 | -------------------------------------------------------------------------------- /src/Bridges/Bridges.jl: -------------------------------------------------------------------------------- 1 | module Bridges 2 | 3 | using MathOptInterface 4 | const MOI = MathOptInterface 5 | const MOIU = MOI.Utilities 6 | 7 | const CI = MOI.ConstraintIndex 8 | 9 | include("bridge.jl") 10 | include("bridge_optimizer.jl") 11 | 12 | # Variable bridges 13 | include("Variable/Variable.jl") 14 | # Constraint bridges 15 | include("Constraint/Constraint.jl") 16 | # Objective bridges 17 | include("Objective/Objective.jl") 18 | 19 | include("lazy_bridge_optimizer.jl") 20 | include("debug.jl") 21 | 22 | """ 23 | full_bridge_optimizer(model::MOI.ModelLike, ::Type{T}) where T 24 | 25 | Returns a `LazyBridgeOptimizer` bridging `model` for every bridge defined in 26 | this package and for the coefficient type `T`. 27 | """ 28 | function full_bridge_optimizer(model::MOI.ModelLike, T::Type) 29 | bridged_model = LazyBridgeOptimizer(model) 30 | Variable.add_all_bridges(bridged_model, T) 31 | Constraint.add_all_bridges(bridged_model, T) 32 | Objective.add_all_bridges(bridged_model, T) 33 | return bridged_model 34 | end 35 | 36 | print_num_bridges(io::IO, ::Variable.EmptyMap) = nothing 37 | print_num_bridges(io::IO, ::Constraint.EmptyMap) = nothing 38 | print_num_bridges(io::IO, ::Objective.EmptyMap) = nothing 39 | function print_num_bridges(io::IO, B::Variable.Map) 40 | s(n) = n == 1 ? "" : "s" 41 | indent = " "^get(io, :indent, 0) 42 | n = length(B) 43 | print(io, "\n$(indent)with $(n) variable bridge$(s(n))") 44 | end 45 | function print_num_bridges(io::IO, B::Constraint.Map) 46 | s(n) = n == 1 ? "" : "s" 47 | indent = " "^get(io, :indent, 0) 48 | n = length(B) 49 | print(io, "\n$(indent)with $(n) constraint bridge$(s(n))") 50 | end 51 | function print_num_bridges(io::IO, B::Objective.Map) 52 | s(n) = n == 1 ? "" : "s" 53 | indent = " "^get(io, :indent, 0) 54 | n = length(B) 55 | print(io, "\n$(indent)with $(n) objective bridge$(s(n))") 56 | end 57 | 58 | function Base.show(io::IO, B::AbstractBridgeOptimizer) 59 | MOIU.print_with_acronym(io, summary(B)) 60 | print_num_bridges(io, Variable.bridges(B)) 61 | print_num_bridges(io, Constraint.bridges(B)) 62 | print_num_bridges(io, Objective.bridges(B)) 63 | if :model in propertynames(B) 64 | indent = " "^get(io, :indent, 0) 65 | print(io, "\n$(indent)with inner model ") 66 | show(IOContext(io, :indent => get(io, :indent, 0)+2), B.model) 67 | end 68 | end 69 | 70 | end # module 71 | -------------------------------------------------------------------------------- /test/Test/nlp.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | 3 | import MathOptInterface 4 | const MOI = MathOptInterface 5 | 6 | @testset "hs071" begin 7 | mock = MOI.Utilities.MockOptimizer( 8 | MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), 9 | eval_objective_value=false 10 | ) 11 | config = MOI.Test.TestConfig(optimal_status = MOI.LOCALLY_SOLVED) 12 | MOI.Utilities.set_mock_optimize!( 13 | mock, 14 | (mock) -> begin 15 | MOI.Utilities.mock_optimize!( 16 | mock, config.optimal_status, 17 | [1.0, 4.7429996418092970, 3.8211499817883077, 1.379408289755698] 18 | ) 19 | MOI.set(mock, MOI.ObjectiveValue(), 17.014017145179164) 20 | end 21 | ) 22 | MOI.Test.hs071_test(mock, config) 23 | MOI.Test.hs071_no_hessian_test(mock, config) 24 | 25 | d = MOI.Test.HS071(false) 26 | VI = MOI.VariableIndex 27 | @test MOI.objective_expr(d) == :(x[$(VI(1))] * x[$(VI(4))] * (x[$(VI(1))] + 28 | x[$(VI(2))] + x[$(VI(3))]) + x[$(VI(3))]) 29 | @test MOI.constraint_expr(d, 1) == 30 | :(x[$(VI(1))] * x[$(VI(2))] * x[$(VI(3))] * x[$(VI(4))] >= 25.0) 31 | @test MOI.constraint_expr(d, 2) == 32 | :(x[$(VI(1))]^2 + x[$(VI(2))]^2 + x[$(VI(3))]^2 + x[$(VI(4))]^2 == 40.0) 33 | @test_throws ErrorException MOI.constraint_expr(d, 3) 34 | end 35 | 36 | @testset "mixed_complementarity" begin 37 | mock = MOI.Utilities.MockOptimizer( 38 | MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) 39 | ) 40 | config = MOI.Test.TestConfig(optimal_status = MOI.LOCALLY_SOLVED) 41 | MOI.Utilities.set_mock_optimize!( 42 | mock, 43 | (mock) -> MOI.Utilities.mock_optimize!( 44 | mock, config.optimal_status, [2.8, 0.0, 0.8, 1.2] 45 | ) 46 | ) 47 | MOI.Test.mixed_complementaritytest(mock, config) 48 | end 49 | 50 | @testset "math_program_complementarity_constraints" begin 51 | mock = MOI.Utilities.MockOptimizer( 52 | MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) 53 | ) 54 | config = MOI.Test.TestConfig(optimal_status = MOI.LOCALLY_SOLVED) 55 | MOI.Utilities.set_mock_optimize!( 56 | mock, 57 | (mock) -> MOI.Utilities.mock_optimize!( 58 | mock, config.optimal_status, [1.0, 0.0, 3.5, 0.0, 0.0, 0.0, 3.0, 6.0] 59 | ) 60 | ) 61 | MOI.Test.math_program_complementarity_constraintstest(mock, config) 62 | end 63 | -------------------------------------------------------------------------------- /src/Test/config.jl: -------------------------------------------------------------------------------- 1 | struct TestConfig{T <: Number} 2 | atol::Float64 # absolute tolerance for ... 3 | rtol::Float64 # relative tolerance for ... 4 | solve::Bool # optimize and test result 5 | query::Bool # can get objective function, and constraint functions, and constraint sets 6 | modify_lhs::Bool # can modify function of a constraint 7 | duals::Bool # test dual solutions 8 | dual_objective_value::Bool # test `DualObjectiveValue` 9 | infeas_certificates::Bool # check for primal or dual infeasibility certificates when appropriate 10 | # The expected "optimal" status returned by the solver. Either 11 | # MOI.OPTIMAL or MOI.LOCALLY_SOLVED. 12 | optimal_status::MOI.TerminationStatusCode 13 | basis::Bool # can get variable and constraint basis status 14 | function TestConfig{T}(; 15 | atol::Float64 = 1e-8, rtol::Float64 = 1e-8, solve::Bool = true, 16 | query::Bool = true, modify_lhs::Bool = true, duals::Bool = true, 17 | dual_objective_value::Bool = duals, infeas_certificates::Bool = true, 18 | optimal_status = MOI.OPTIMAL, basis::Bool = false) where {T <: Number} 19 | new(atol, rtol, solve, query, modify_lhs, duals, dual_objective_value, 20 | infeas_certificates, optimal_status, basis) 21 | end 22 | TestConfig(;kwargs...) = TestConfig{Float64}(; kwargs...) 23 | end 24 | 25 | """ 26 | @moitestset setname subsets 27 | 28 | Defines a function `setnametest(model, config, exclude)` that runs the tests defined in the dictionary `setnametests` 29 | with the model `model` and config `config` except the tests whose dictionary key is in `exclude`. 30 | If `subsets` is `true` then each test runs in fact multiple tests hence the `exclude` argument is passed 31 | as it can also contains test to be excluded from these subsets of tests. 32 | """ 33 | macro moitestset(setname, subsets=false) 34 | testname = Symbol(string(setname) * "test") 35 | testdict = Symbol(string(testname) * "s") 36 | if subsets 37 | runtest = :( f(model, config, exclude) ) 38 | else 39 | runtest = :( f(model, config) ) 40 | end 41 | esc(:( 42 | function $testname(model::$MOI.ModelLike, config::$MOI.Test.TestConfig, exclude::Vector{String} = String[]) 43 | for (name,f) in $testdict 44 | if name in exclude 45 | continue 46 | end 47 | @testset "$name" begin 48 | $runtest 49 | end 50 | end 51 | end 52 | )) 53 | end 54 | -------------------------------------------------------------------------------- /src/Utilities/variables.jl: -------------------------------------------------------------------------------- 1 | """ 2 | get_bounds(model::MOI.ModelLike, ::Type{T}, x::MOI.VariableIndex) 3 | 4 | Return a tuple `(lb, ub)` of type `Tuple{T, T}`, where `lb` and `ub` are lower 5 | and upper bounds, respectively, imposed on `x` in `model`. 6 | """ 7 | function get_bounds( 8 | model::MOI.ModelLike, ::Type{T}, x::MOI.VariableIndex) where {T} 9 | xval = x.value 10 | c_lt = MOI.ConstraintIndex{MOI.SingleVariable, MOI.LessThan{T}}(xval) 11 | c_gt = MOI.ConstraintIndex{MOI.SingleVariable, MOI.GreaterThan{T}}(xval) 12 | c_int = MOI.ConstraintIndex{MOI.SingleVariable, MOI.Interval{T}}(xval) 13 | c_eq = MOI.ConstraintIndex{MOI.SingleVariable, MOI.EqualTo{T}}(xval) 14 | c_sc = MOI.ConstraintIndex{MOI.SingleVariable, MOI.Semicontinuous{T}}(xval) 15 | c_si = MOI.ConstraintIndex{MOI.SingleVariable, MOI.Semiinteger{T}}(xval) 16 | if MOI.is_valid(model, c_int) 17 | # It is assumed that none of the other ConstraintIndexs are valid 18 | int::MOI.Interval{T} = MOI.get(model, MOI.ConstraintSet(), c_int) 19 | return int.lower, int.upper 20 | elseif MOI.is_valid(model, c_eq) 21 | # It is assumed that none of the other ConstraintIndexs are valid 22 | eq::MOI.EqualTo{T} = MOI.get(model, MOI.ConstraintSet(), c_eq) 23 | return eq.value, eq.value 24 | elseif MOI.is_valid(model, c_sc) 25 | # It is assumed that none of the other ConstraintIndexs are valid 26 | sc::MOI.Semicontinuous{T} = MOI.get(model, MOI.ConstraintSet(), c_sc) 27 | return min(zero(T), sc.lower), max(zero(T), sc.upper) 28 | elseif MOI.is_valid(model, c_si) 29 | # It is assumed that none of the other ConstraintIndexs are valid 30 | si::MOI.Semiinteger{T} = MOI.get(model, MOI.ConstraintSet(), c_si) 31 | return min(zero(T), si.lower), max(zero(T), si.upper) 32 | elseif MOI.is_valid(model, c_lt) 33 | lt::MOI.LessThan{T} = MOI.get(model, MOI.ConstraintSet(), c_lt) 34 | # It is valid to have both LessThan and GreaterThan constraints on the 35 | # same variable. 36 | if MOI.is_valid(model, c_gt) 37 | gt_1::MOI.GreaterThan{T} = MOI.get(model, MOI.ConstraintSet(), c_gt) 38 | return gt_1.lower, lt.upper 39 | else 40 | return typemin(T), lt.upper 41 | end 42 | elseif MOI.is_valid(model, c_gt) 43 | gt_2::MOI.GreaterThan{T} = MOI.get(model, MOI.ConstraintSet(), c_gt) 44 | return gt_2.lower, typemax(T) 45 | else 46 | return typemin(T), typemax(T) 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /src/Bridges/Variable/single_bridge_optimizer.jl: -------------------------------------------------------------------------------- 1 | """ 2 | SingleBridgeOptimizer{BT<:AbstractBridge, OT<:MOI.ModelLike} <: AbstractBridgeOptimizer 3 | 4 | The `SingleBridgeOptimizer` bridges any constrained variables supported by the 5 | bridge `BT`. This is in contrast with the [`MathOptInterface.Bridges.LazyBridgeOptimizer`](@ref) 6 | which only bridges the constrained variables that are unsupported by the internal model, 7 | even if they are supported by one of its bridges. 8 | 9 | !!! note 10 | Two bridge optimizers using variable bridges cannot be used together as both 11 | of them assume that the underlying model only returns variable indices with 12 | nonnegative values. 13 | """ 14 | mutable struct SingleBridgeOptimizer{BT<:AbstractBridge, OT<:MOI.ModelLike} <: MOIB.AbstractBridgeOptimizer 15 | model::OT 16 | map::Map # index of bridged variable -> variable bridge 17 | var_to_name::Dict{MOI.VariableIndex, String} 18 | name_to_var::Union{Dict{String, MOI.VariableIndex}, Nothing} 19 | con_to_name::Dict{MOI.ConstraintIndex, String} 20 | name_to_con::Union{Dict{String, MOI.ConstraintIndex}, Nothing} 21 | end 22 | function SingleBridgeOptimizer{BT}(model::OT) where {BT, OT <: MOI.ModelLike} 23 | SingleBridgeOptimizer{BT, OT}( 24 | model, Map(), 25 | Dict{MOI.VariableIndex, String}(), nothing, 26 | Dict{MOI.ConstraintIndex, String}(), nothing) 27 | end 28 | 29 | function bridges(bridge::MOI.Bridges.AbstractBridgeOptimizer) 30 | return EmptyMap() 31 | end 32 | bridges(bridge::SingleBridgeOptimizer) = bridge.map 33 | 34 | MOIB.supports_constraint_bridges(::SingleBridgeOptimizer) = false 35 | function MOIB.supports_bridging_constrained_variable( 36 | ::SingleBridgeOptimizer{BT}, S::Type{<:MOI.AbstractSet}) where BT 37 | return supports_constrained_variable(BT, S) 38 | end 39 | function MOIB.is_variable_bridged( 40 | ::SingleBridgeOptimizer, ::Type{<:MOI.AbstractSet}) 41 | return true 42 | end 43 | function MOIB.is_bridged(b::SingleBridgeOptimizer, S::Type{<:MOI.AbstractSet}) 44 | return MOIB.supports_bridging_constrained_variable(b, S) 45 | end 46 | function MOIB.is_bridged(::SingleBridgeOptimizer, 47 | ::Type{<:MOI.AbstractFunction}, 48 | ::Type{<:MOI.AbstractSet}) 49 | return false 50 | end 51 | function MOIB.is_bridged(::SingleBridgeOptimizer, ::Type{<:MOI.AbstractScalarFunction}) 52 | return false 53 | end 54 | function MOIB.bridge_type(::SingleBridgeOptimizer{BT}, 55 | ::Type{<:MOI.AbstractSet}) where BT 56 | return BT 57 | end 58 | -------------------------------------------------------------------------------- /src/Bridges/Variable/zeros.jl: -------------------------------------------------------------------------------- 1 | """ 2 | ZerosBridge{T} <: Bridges.Variable.AbstractBridge 3 | 4 | Transforms constrained variables in [`MathOptInterface.Zeros`](@ref) to zeros, 5 | which ends up creating no variables in the underlying model. 6 | The bridged variables are therefore similar to parameters with zero values. 7 | Parameters with non-zero value can be created with constrained variables in 8 | [`MOI.EqualTo`](@ref) by combining a [`VectorizeBridge`](@ref) and this bridge. 9 | The functions cannot be unbridged, given a function, we cannot determine, if 10 | the bridged variables were used. 11 | The dual values cannot be determined by the bridge but they can be determined 12 | by the bridged optimizer using [`MathOptInterface.Utilities.get_fallback`](@ref) 13 | if a `CachingOptimizer` is used (since `ConstraintFunction` cannot be got 14 | as functions cannot be unbridged). 15 | """ 16 | struct ZerosBridge{T} <: AbstractBridge 17 | n::Int # Number of variables 18 | end 19 | function bridge_constrained_variable(::Type{ZerosBridge{T}}, 20 | model::MOI.ModelLike, 21 | set::MOI.Zeros) where T 22 | return ZerosBridge{T}(MOI.dimension(set)) 23 | end 24 | 25 | function supports_constrained_variable( 26 | ::Type{<:ZerosBridge}, ::Type{MOI.Zeros}) 27 | return true 28 | end 29 | function MOIB.added_constrained_variable_types(::Type{<:ZerosBridge}) 30 | return Tuple{DataType}[] 31 | end 32 | function MOIB.added_constraint_types(::Type{<:ZerosBridge}) 33 | return Tuple{DataType, DataType}[] 34 | end 35 | 36 | # Attributes, Bridge acting as a model 37 | MOI.get(bridge::ZerosBridge, ::MOI.NumberOfVariables) = 0 38 | function MOI.get(bridge::ZerosBridge, ::MOI.ListOfVariableIndices) 39 | return MOI.VariableIndex[] 40 | end 41 | 42 | # References 43 | function MOI.delete(::MOI.ModelLike, ::ZerosBridge) end 44 | 45 | # Attributes, Bridge acting as a constraint 46 | 47 | function MOI.get(::MOI.ModelLike, ::MOI.ConstraintSet, 48 | bridge::ZerosBridge) 49 | return MOI.Zeros(bridge.n) 50 | end 51 | 52 | function MOI.get(::MOI.ModelLike, ::MOI.ConstraintPrimal, 53 | bridge::ZerosBridge{T}) where T 54 | return zeros(T, bridge.n) 55 | end 56 | 57 | function MOI.get(::MOI.ModelLike, ::MOI.VariablePrimal, 58 | ::ZerosBridge{T}, ::IndexInVector) where T 59 | return zero(T) 60 | end 61 | 62 | function MOIB.bridged_function(::ZerosBridge{T}, ::IndexInVector) where T 63 | return zero(MOI.ScalarAffineFunction{T}) 64 | end 65 | function unbridged_map(::ZerosBridge, ::MOI.VariableIndex, 66 | ::IndexInVector) 67 | return nothing 68 | end 69 | -------------------------------------------------------------------------------- /test/Bridges/Variable/flip_sign.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | 3 | using MathOptInterface 4 | const MOI = MathOptInterface 5 | const MOIT = MathOptInterface.Test 6 | const MOIU = MathOptInterface.Utilities 7 | const MOIB = MathOptInterface.Bridges 8 | 9 | include("../utilities.jl") 10 | 11 | mock = MOIU.MockOptimizer(MOIU.UniversalFallback(MOIU.Model{Float64}())) 12 | config = MOIT.TestConfig() 13 | 14 | @testset "NonposToNonneg" begin 15 | bridged_mock = MOIB.Variable.NonposToNonneg{Float64}(mock) 16 | 17 | @testset "lin2v" begin 18 | mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [-4, 3, 16, 0], 19 | (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[7, 2, -4]]) 20 | MOIT.lin2vtest(bridged_mock, config) 21 | 22 | @test MOI.get(mock, MOI.NumberOfVariables()) == 4 23 | @test MOI.get(bridged_mock, MOI.NumberOfVariables()) == 4 24 | vis = MOI.get(bridged_mock, MOI.ListOfVariableIndices()) 25 | y = vis[4] 26 | @test y.value == -1 27 | 28 | @test MOI.supports(bridged_mock, MOI.VariablePrimalStart(), MOI.VariableIndex) 29 | @test MOI.supports(bridged_mock, MOI.VariablePrimalStart(), typeof(MOIB.bridge(bridged_mock, y))) 30 | MOI.set(bridged_mock, MOI.VariablePrimalStart(), y, 1.0) 31 | x, y_flipped, z, s = MOI.get(mock, MOI.ListOfVariableIndices()) 32 | @test MOI.get(mock, MOI.VariablePrimalStart(), y_flipped) == -1 33 | @test MOI.get(bridged_mock, MOI.VariablePrimalStart(), y) == 1 34 | end 35 | 36 | @testset "lin4" begin 37 | MOIU.set_mock_optimize!(mock, 38 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( 39 | mock, MOI.INFEASIBLE, MOI.INFEASIBLE_POINT, 40 | MOI.INFEASIBILITY_CERTIFICATE) 41 | ) 42 | MOIT.lin4test(bridged_mock, config) 43 | 44 | @test MOI.get(mock, MOI.NumberOfVariables()) == 1 45 | @test length(MOI.get(mock, MOI.ListOfVariableIndices())) == 1 46 | @test first(MOI.get(mock, MOI.ListOfVariableIndices())).value ≥ 0 47 | @test MOI.get(bridged_mock, MOI.NumberOfVariables()) == 1 48 | vis = MOI.get(bridged_mock, MOI.ListOfVariableIndices()) 49 | @test vis == [MOI.VariableIndex(-1)] 50 | test_delete_bridged_variable(bridged_mock, vis[1], MOI.Nonpositives, 1, ( 51 | (MOI.VectorOfVariables, MOI.Nonnegatives, 0), 52 | )) 53 | end 54 | 55 | @testset "Delete in vector" begin 56 | MOI.empty!(bridged_mock) 57 | vis, ci = MOI.add_constrained_variables(bridged_mock, MOI.Nonpositives(4)) 58 | test_delete_bridged_variable(bridged_mock, vis[2], MOI.Nonpositives, 4, ( 59 | (MOI.VectorOfVariables, MOI.Nonnegatives, 0), 60 | ), used_bridges = 0, used_constraints = 0) 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /test/instantiate.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | 3 | using MathOptInterface 4 | const MOI = MathOptInterface 5 | const MOIU = MathOptInterface.Utilities 6 | 7 | struct DummyOptimizer <: MOI.AbstractOptimizer end 8 | MOI.is_empty(::DummyOptimizer) = true 9 | 10 | @testset "Instantiate with $T" for T in [Float64, Int] 11 | f() = MOIU.MockOptimizer(MOIU.UniversalFallback(MOIU.Model{T}())) 12 | optimizer_constructor = MOI.OptimizerWithAttributes(f, MOI.Silent() => true, "a" => 1, "b" => 2) 13 | optimizer = MOI.instantiate(optimizer_constructor) 14 | @test optimizer isa MOIU.MockOptimizer{MOIU.UniversalFallback{MOIU.Model{T}}} 15 | @test MOI.get(optimizer, MOI.Silent()) 16 | for with_names in [true, false] 17 | optimizer = MOI.instantiate(optimizer_constructor, with_bridge_type=T, with_names=with_names) 18 | @test optimizer isa MOI.Bridges.LazyBridgeOptimizer{MOIU.MockOptimizer{MOIU.UniversalFallback{MOIU.Model{T}}}} 19 | @test MOI.get(optimizer, MOI.Silent()) 20 | @test MOI.get(optimizer, MOI.RawParameter("a")) == 1 21 | @test MOI.get(optimizer, MOI.RawParameter("b")) == 2 22 | end 23 | 24 | optimizer_constructor = MOI.OptimizerWithAttributes(DummyOptimizer, []) 25 | optimizer = MOI.instantiate(optimizer_constructor) 26 | @test optimizer isa DummyOptimizer 27 | for with_names in [true, false] 28 | optimizer = MOI.instantiate(optimizer_constructor, with_bridge_type=T, with_names=with_names) 29 | @test optimizer isa MOI.Bridges.LazyBridgeOptimizer{MOIU.CachingOptimizer{DummyOptimizer, MOIU.UniversalFallback{MOIU.Model{T}}}} 30 | end 31 | 32 | err = ErrorException("The provided `optimizer_constructor` returned a non-empty optimizer.") 33 | function g() 34 | model = f() 35 | MOI.add_variable(model) 36 | return model 37 | end 38 | @test_throws err MOI.instantiate(g) 39 | @test_throws err MOI.instantiate(g, with_bridge_type=T) 40 | optimizer_constructor = MOI.OptimizerWithAttributes(g) 41 | @test_throws err MOI.instantiate(optimizer_constructor) 42 | @test_throws err MOI.instantiate(optimizer_constructor, with_bridge_type=T) 43 | 44 | err = ErrorException(MOI._INSTANTIATE_NOT_CALLABLE_MESSAGE) 45 | @test_throws err MOI.OptimizerWithAttributes(1) 46 | @test_throws err MOI.OptimizerWithAttributes(1, MOI.Silent() => true) 47 | @test_throws err MOI.instantiate(1) 48 | @test_throws err MOI.instantiate(1, with_bridge_type=T) 49 | 50 | err = ErrorException("The provided `optimizer_constructor` returned an object of type " * 51 | "$Int. Expected a MathOptInterface.AbstractOptimizer.") 52 | h() = 1 53 | @test_throws err MOI.instantiate(h) 54 | @test_throws err MOI.instantiate(h, with_bridge_type=T) 55 | optimizer_constructor = MOI.OptimizerWithAttributes(h) 56 | @test_throws err MOI.instantiate(optimizer_constructor) 57 | @test_throws err MOI.instantiate(optimizer_constructor, with_bridge_type=T) 58 | end 59 | -------------------------------------------------------------------------------- /src/Test/intconic.jl: -------------------------------------------------------------------------------- 1 | # Integer conic problems 2 | 3 | function intsoc1test(model::MOI.ModelLike, config::TestConfig) 4 | atol = config.atol 5 | rtol = config.rtol 6 | 7 | # Problem SINTSOC1 8 | # min 0x - 2y - 1z 9 | # st x == 1 10 | # x >= ||(y,z)|| 11 | # (y,z) binary 12 | 13 | @test MOIU.supports_default_copy_to(model, #=copy_names=# false) 14 | @test MOI.supports(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}()) 15 | @test MOI.supports_constraint(model, MOI.VectorAffineFunction{Float64}, MOI.Zeros) 16 | @test MOI.supports_constraint(model, MOI.SingleVariable, MOI.ZeroOne) 17 | @test MOI.supports_constraint(model, MOI.VectorOfVariables, MOI.SecondOrderCone) 18 | 19 | MOI.empty!(model) 20 | @test MOI.is_empty(model) 21 | 22 | x,y,z = MOI.add_variables(model, 3) 23 | 24 | MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([-2.0,-1.0], [y,z]), 0.0)) 25 | MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) 26 | 27 | ceq = MOI.add_constraint(model, MOI.VectorAffineFunction([MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, x))], [-1.0]), MOI.Zeros(1)) 28 | csoc = MOI.add_constraint(model, MOI.VectorOfVariables([x,y,z]), MOI.SecondOrderCone(3)) 29 | 30 | @test MOI.get(model, MOI.NumberOfConstraints{MOI.VectorAffineFunction{Float64},MOI.Zeros}()) == 1 31 | @test MOI.get(model, MOI.NumberOfConstraints{MOI.VectorOfVariables,MOI.SecondOrderCone}()) == 1 32 | loc = MOI.get(model, MOI.ListOfConstraints()) 33 | @test length(loc) == 2 34 | @test (MOI.VectorAffineFunction{Float64},MOI.Zeros) in loc 35 | @test (MOI.VectorOfVariables,MOI.SecondOrderCone) in loc 36 | 37 | bin1 = MOI.add_constraint(model, MOI.SingleVariable(y), MOI.ZeroOne()) 38 | # We test this after the creation of every `SingleVariable` constraint 39 | # to ensure a good coverage of corner cases. 40 | @test bin1.value == y.value 41 | bin2 = MOI.add_constraint(model, MOI.SingleVariable(z), MOI.ZeroOne()) 42 | @test bin2.value == z.value 43 | 44 | if config.solve 45 | @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED 46 | 47 | MOI.optimize!(model) 48 | 49 | @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status 50 | 51 | @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT 52 | 53 | @test MOI.get(model, MOI.ObjectiveValue()) ≈ -2 atol=atol rtol=rtol 54 | 55 | @test MOI.get(model, MOI.VariablePrimal(), x) ≈ 1 atol=atol rtol=rtol 56 | @test MOI.get(model, MOI.VariablePrimal(), y) ≈ 1 atol=atol rtol=rtol 57 | @test MOI.get(model, MOI.VariablePrimal(), z) ≈ 0 atol=atol rtol=rtol 58 | end 59 | end 60 | 61 | const intsoctests = Dict("intsoc1" => intsoc1test) 62 | 63 | @moitestset intsoc 64 | 65 | const intconictests = Dict("intsoc" => intsoctest) 66 | 67 | @moitestset intconic true 68 | -------------------------------------------------------------------------------- /src/Bridges/Variable/soc_to_rsoc.jl: -------------------------------------------------------------------------------- 1 | """ 2 | SOCtoRSOCBridge{T} <: Bridges.Variable.AbstractBridge 3 | 4 | Same transformation as [`MOI.Bridges.Constraint.SOCRBridge`](@ref). 5 | """ 6 | struct SOCtoRSOCBridge{T} <: AbstractBridge 7 | variables::Vector{MOI.VariableIndex} 8 | constraint::MOI.ConstraintIndex{MOI.VectorOfVariables, MOI.RotatedSecondOrderCone} 9 | end 10 | function bridge_constrained_variable( 11 | ::Type{SOCtoRSOCBridge{T}}, model::MOI.ModelLike, 12 | set::MOI.SecondOrderCone) where T 13 | variables, constraint = MOI.add_constrained_variables( 14 | model, MOI.RotatedSecondOrderCone(MOI.dimension(set))) 15 | return SOCtoRSOCBridge{T}(variables, constraint) 16 | end 17 | 18 | function supports_constrained_variable( 19 | ::Type{<:SOCtoRSOCBridge}, ::Type{MOI.SecondOrderCone}) 20 | return true 21 | end 22 | function MOIB.added_constrained_variable_types(::Type{<:SOCtoRSOCBridge}) 23 | return [(MOI.RotatedSecondOrderCone,)] 24 | end 25 | function MOIB.added_constraint_types(::Type{<:SOCtoRSOCBridge}) 26 | return Tuple{DataType, DataType}[] 27 | end 28 | 29 | # Attributes, Bridge acting as a model 30 | function MOI.get(bridge::SOCtoRSOCBridge, ::MOI.NumberOfVariables) 31 | return length(bridge.variables) 32 | end 33 | function MOI.get(bridge::SOCtoRSOCBridge, ::MOI.ListOfVariableIndices) 34 | return bridge.variables 35 | end 36 | function MOI.get(bridge::SOCtoRSOCBridge, 37 | ::MOI.NumberOfConstraints{MOI.VectorOfVariables, 38 | MOI.RotatedSecondOrderCone}) 39 | return 1 40 | end 41 | function MOI.get(bridge::SOCtoRSOCBridge, 42 | ::MOI.ListOfConstraintIndices{MOI.VectorOfVariables, 43 | MOI.RotatedSecondOrderCone}) 44 | return [bridge.constraint] 45 | end 46 | 47 | # References 48 | function MOI.delete(model::MOI.ModelLike, bridge::SOCtoRSOCBridge) 49 | MOI.delete(model, bridge.variables) 50 | end 51 | 52 | # Attributes, Bridge acting as a constraint 53 | 54 | function MOI.get(::MOI.ModelLike, ::MOI.ConstraintSet, 55 | bridge::SOCtoRSOCBridge{T}) where T 56 | return MOI.SecondOrderCone(length(bridge.variables)) 57 | end 58 | 59 | function MOI.get( 60 | model::MOI.ModelLike, 61 | attr::Union{MOI.ConstraintPrimal, MOI.ConstraintDual}, 62 | bridge::SOCtoRSOCBridge) 63 | return rotate_result(model, attr, bridge.constraint) 64 | end 65 | 66 | function MOI.get(model::MOI.ModelLike, attr::MOI.VariablePrimal, 67 | bridge::SOCtoRSOCBridge, i::IndexInVector) 68 | return rotate_result(model, attr, bridge.variables, i) 69 | end 70 | 71 | function MOIB.bridged_function(bridge::SOCtoRSOCBridge{T}, i::IndexInVector) where T 72 | return rotate_bridged_function(T, bridge.variables, i) 73 | end 74 | function unbridged_map(bridge::SOCtoRSOCBridge{T}, vis::Vector{MOI.VariableIndex}) where T 75 | return rotate_unbridged_map(T, bridge.variables, vis) 76 | end 77 | -------------------------------------------------------------------------------- /src/Bridges/Objective/map.jl: -------------------------------------------------------------------------------- 1 | """ 2 | Map <: AbstractDict{MOI.ObjectiveFunction, AbstractBridge} 3 | 4 | A mapping from a bridged objective function type to the bridge responsible for 5 | bridging that type of objective function. 6 | """ 7 | mutable struct Map <: AbstractDict{MOI.ObjectiveFunction, AbstractBridge} 8 | bridges::Dict{MOI.ObjectiveFunction, AbstractBridge} 9 | function_type::Union{Nothing, Type{<:MOI.AbstractScalarFunction}} 10 | end 11 | function Map() 12 | return Map(Dict{MOI.ObjectiveFunction, AbstractBridge}(), nothing) 13 | end 14 | 15 | # Implementation of `AbstractDict` interface. 16 | 17 | Base.isempty(map::Map) = isempty(map.bridges) 18 | function Base.empty!(map::Map) 19 | empty!(map.bridges) 20 | map.function_type = nothing 21 | end 22 | function Base.haskey(map::Map, attr::MOI.ObjectiveFunction) 23 | return haskey(map.bridges, attr) 24 | end 25 | function Base.getindex(map::Map, attr::MOI.ObjectiveFunction) 26 | return map.bridges[attr] 27 | end 28 | Base.length(map::Map) = length(map.bridges) 29 | Base.values(map::Map) = values(map.bridges) 30 | Base.iterate(map::Map, args...) = iterate(map.bridges, args...) 31 | 32 | # Custom interface for information needed by `AbstractBridgeOptimizer`s that is 33 | # not part of the `AbstractDict` interface. 34 | 35 | """ 36 | function_type(map::Map) 37 | 38 | Return the function type of the [`root_bridge`](@ref) or `nothing` if `map` is 39 | empty. 40 | """ 41 | function_type(map::Map) = map.function_type 42 | 43 | """ 44 | root_bridge(map::Map) 45 | 46 | Return the last bridge added. 47 | """ 48 | function root_bridge(map::Map) 49 | attr = MOI.ObjectiveFunction{function_type(map)}() 50 | return map[attr] 51 | end 52 | 53 | """ 54 | add_key_for_bridge(map::Map, bridge::AbstractBridge, 55 | func::MOI.AbstractScalarFunction) 56 | 57 | Stores the mapping `atttr => bridge` where `attr` is 58 | `MOI.ObjectiveFunction{typeof(func)}()` and set [`function_type`](@ref) to 59 | `typeof(func)`. 60 | """ 61 | function add_key_for_bridge(map::Map, bridge::AbstractBridge, 62 | func::MOI.AbstractScalarFunction) 63 | attr = MOI.ObjectiveFunction{typeof(func)}() 64 | map.function_type = typeof(func) 65 | map.bridges[attr] = bridge 66 | end 67 | 68 | """ 69 | EmptyMap <: AbstractDict{MOI.ObjectiveFunction, AbstractBridge} 70 | 71 | Empty version of [`Map`](@ref). It is used by 72 | [`MathOptInterface.Bridges.Variable.SingleBridgeOptimizer`](@ref) and 73 | [`MathOptInterface.Bridges.Constraint.SingleBridgeOptimizer`](@ref) as they do 74 | not bridge any objective function. 75 | """ 76 | struct EmptyMap <: AbstractDict{MOI.ObjectiveFunction, AbstractBridge} end 77 | Base.isempty(::EmptyMap) = true 78 | function Base.empty!(::EmptyMap) end 79 | Base.length(::EmptyMap) = 0 80 | Base.haskey(::EmptyMap, ::MOI.ObjectiveFunction) = false 81 | Base.values(::EmptyMap) = MOIU.EmptyVector{AbstractBridge}() 82 | Base.iterate(::EmptyMap) = nothing 83 | function_type(::EmptyMap) = nothing 84 | -------------------------------------------------------------------------------- /src/Bridges/Constraint/function_conversion.jl: -------------------------------------------------------------------------------- 1 | """ 2 | abstract type AbstractFunctionConversionBridge{F, S} <: AbstractBridge end 3 | 4 | Bridge a constraint `G`-in-`S` into a constraint `F`-in-`S` where `F` and `G` 5 | are equivalent representations of the same function. By convention, the 6 | transformed function is stored in the `constraint` field. 7 | """ 8 | abstract type AbstractFunctionConversionBridge{F, S} <: AbstractBridge end 9 | 10 | function MOI.get(model::MOI.ModelLike, attr::MOI.AbstractConstraintAttribute, 11 | bridge::AbstractFunctionConversionBridge) 12 | if invariant_under_function_conversion(attr) 13 | return MOI.get(model, attr, bridge.constraint) 14 | else 15 | throw(ArgumentError("Bridge of type `$(typeof(bridge))` does not support accessing the attribute `$attr` because `MOIB.Constraint.invariant_under_function_conversion($attr)` returns `false`.")) 16 | end 17 | end 18 | 19 | function MOI.supports(model::MOI.ModelLike, attr::MOI.AbstractConstraintAttribute, 20 | ::Type{<:AbstractFunctionConversionBridge{F, S}}) where {F, S} 21 | return invariant_under_function_conversion(attr) && 22 | MOI.supports(model, attr, MOI.ConstraintIndex{F, S}) 23 | end 24 | function MOI.set(model::MOI.ModelLike, attr::MOI.AbstractConstraintAttribute, 25 | bridge::AbstractFunctionConversionBridge, value) 26 | if invariant_under_function_conversion(attr) 27 | return MOI.set(model, attr, bridge.constraint, value) 28 | else 29 | throw(ArgumentError("Bridge of type `$(typeof(bridge))` does not support setting the attribute `$attr` because `MOIB.Constraint.invariant_under_function_conversion($attr)` returns `false`.")) 30 | end 31 | end 32 | 33 | """ 34 | invariant_under_function_conversion(attr::MOI.AbstractConstraintAttribute) 35 | 36 | Returns whether the value of the attribute does not change if the constraint 37 | `F`-in-`S` is transformed into a constraint `G`-in-`S` where `F` and `G` are 38 | equivalent representations of the same function. If it returns true, then 39 | subtypes of [`Constraint.AbstractFunctionConversionBridge`](@ref) such as 40 | [`Constraint.ScalarFunctionizeBridge`](@ref) and 41 | [`Constraint.VectorFunctionizeBridge`](@ref) will automatically support 42 | [`MOI.get`](@ref) and [`MOI.set`](@ref) for `attr`. 43 | """ 44 | invariant_under_function_conversion(::MOI.AbstractConstraintAttribute) = false 45 | 46 | function invariant_under_function_conversion(::Union{ 47 | MOI.ConstraintSet, 48 | MOI.ConstraintBasisStatus, 49 | MOI.ConstraintPrimal, 50 | MOI.ConstraintPrimalStart, 51 | MOI.ConstraintDual, 52 | MOI.ConstraintDualStart}) 53 | return true 54 | end 55 | 56 | include("functionize.jl") 57 | const ScalarFunctionize{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{ScalarFunctionizeBridge{T}, OT} 58 | const VectorFunctionize{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{VectorFunctionizeBridge{T}, OT} 59 | # TODO add affine -> quadratic conversion bridge 60 | -------------------------------------------------------------------------------- /src/Bridges/Constraint/indicator_activate_on_zero.jl: -------------------------------------------------------------------------------- 1 | """ 2 | IndicatorActiveOnFalseBridge{T} 3 | 4 | The `IndicatorActiveOnFalseBridge` replaces an indicator constraint activated 5 | on 0 with a variable ``z_0`` with the constraint activated on 1, with a variable ``z_1``. 6 | It stores the added `variable_index` and added constraints: 7 | - ``z_1 \\in \\mathbb{B}`` in `zero_one_cons` 8 | - ``z_0 + z_1 == 1`` in `` in `disjunction_cons` 9 | - The added `ACTIVATE_ON_ONE` indicator constraint in `indicator_cons_index`. 10 | """ 11 | struct IndicatorActiveOnFalseBridge{T, F <: MOI.AbstractVectorFunction, S <: MOI.AbstractScalarSet} <: AbstractBridge 12 | variable_index::MOI.VariableIndex 13 | zero_one_cons::MOI.ConstraintIndex{MOI.SingleVariable, MOI.ZeroOne} 14 | disjunction_cons::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T}, MOI.EqualTo{T}} 15 | indicator_cons_index::MOI.ConstraintIndex{F, MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE, S}} 16 | end 17 | 18 | function bridge_constraint(::Type{IndicatorActiveOnFalseBridge{T,F,S}}, model::MOI.ModelLike, f::MOI.VectorAffineFunction{T}, s::IS) where {S <: MOI.AbstractScalarSet, T <: Real, F, IS <: MOI.IndicatorSet{MOI.ACTIVATE_ON_ZERO, S}} 19 | f_scalars = MOIU.eachscalar(f) 20 | z2, zo_cons = MOI.add_constrained_variable(model, MOI.ZeroOne()) 21 | # z1 + z2 == 1 22 | z1_z2 = MOIU.operate(+, T, f_scalars[1], MOI.SingleVariable(z2)) 23 | dcons = MOIU.normalize_and_add_constraint(model, z1_z2, MOI.EqualTo(one(T))) 24 | f2 = MOIU.operate(vcat, T, MOI.SingleVariable(z2), f_scalars[2]) 25 | ci = MOI.add_constraint(model, f2, MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE}(s.set)) 26 | return IndicatorActiveOnFalseBridge{T,F,S}(z2, zo_cons, dcons, ci) 27 | end 28 | 29 | function MOI.supports_constraint(::Type{<:IndicatorActiveOnFalseBridge{T}}, 30 | ::Type{<:MOI.VectorAffineFunction}, 31 | ::Type{<:MOI.IndicatorSet{MOI.ACTIVATE_ON_ZERO}}) where {T} 32 | return true 33 | end 34 | 35 | function MOIB.added_constrained_variable_types(::Type{<:IndicatorActiveOnFalseBridge}) 36 | return [(MOI.ZeroOne,)] 37 | end 38 | function MOIB.added_constraint_types(::Type{IndicatorActiveOnFalseBridge{T, F, S}}) where {T, F, S} 39 | return [(MOI.ScalarAffineFunction{T}, MOI.EqualTo{T}), 40 | (F, MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE, S})] 41 | end 42 | 43 | function concrete_bridge_type(::Type{<:IndicatorActiveOnFalseBridge{T}}, 44 | ::Type{F}, 45 | ::Type{MOI.IndicatorSet{MOI.ACTIVATE_ON_ZERO, S}}) where {T, F<:MOI.VectorAffineFunction, S<:MOI.AbstractScalarSet} 46 | return IndicatorActiveOnFalseBridge{T, F, S} 47 | end 48 | 49 | function concrete_bridge_type(::Type{<:IndicatorActiveOnFalseBridge}, 50 | ::Type{F}, 51 | ::Type{MOI.IndicatorSet{MOI.ACTIVATE_ON_ZERO, S}}) where {F<:MOI.VectorAffineFunction, S<:MOI.AbstractScalarSet} 52 | return IndicatorActiveOnFalseBridge{Float64, F, S} 53 | end 54 | -------------------------------------------------------------------------------- /src/FileFormats/FileFormats.jl: -------------------------------------------------------------------------------- 1 | module FileFormats 2 | 3 | import MathOptInterface 4 | const MOI = MathOptInterface 5 | 6 | import CodecBzip2 7 | import CodecZlib 8 | 9 | include("utils.jl") 10 | 11 | include("CBF/CBF.jl") 12 | include("LP/LP.jl") 13 | include("MOF/MOF.jl") 14 | include("MPS/MPS.jl") 15 | include("SDPA/SDPA.jl") 16 | 17 | """ 18 | FileFormat 19 | 20 | List of accepted export formats. 21 | 22 | - `FORMAT_AUTOMATIC`: try to detect the file format based on the file name 23 | - `FORMAT_CBF`: the Conic Benchmark format 24 | - `FORMAT_LP`: the LP file format 25 | - `FORMAT_MOF`: the MathOptFormat file format 26 | - `FORMAT_MPS`: the MPS file format 27 | - `FORMAT_SDPA`: the SemiDefinite Programming Algorithm format 28 | """ 29 | @enum( 30 | FileFormat, 31 | FORMAT_AUTOMATIC, 32 | FORMAT_CBF, 33 | FORMAT_LP, 34 | FORMAT_MOF, 35 | FORMAT_MPS, 36 | FORMAT_SDPA, 37 | ) 38 | 39 | """ 40 | Model( 41 | ; 42 | format::FileFormat = FORMAT_AUTOMATIC, 43 | filename::Union{Nothing, String} = nothing, 44 | kwargs... 45 | ) 46 | 47 | Return model corresponding to the `FileFormat` `format`, or, if 48 | `format == FORMAT_AUTOMATIC`, guess the format from `filename`. 49 | 50 | The `filename` argument is only needed if `format == FORMAT_AUTOMATIC`. 51 | 52 | `kwargs` are passed to the underlying model constructor. 53 | """ 54 | function Model( 55 | ; 56 | format::FileFormat = FORMAT_AUTOMATIC, 57 | filename::Union{Nothing, String} = nothing, 58 | kwargs... 59 | ) 60 | if format == FORMAT_CBF 61 | return CBF.Model(; kwargs...) 62 | elseif format == FORMAT_LP 63 | return LP.Model(; kwargs...) 64 | elseif format == FORMAT_MOF 65 | return MOF.Model(; kwargs...) 66 | elseif format == FORMAT_MPS 67 | return MPS.Model(; kwargs...) 68 | elseif format == FORMAT_SDPA 69 | return SDPA.Model(; kwargs...) 70 | else 71 | @assert format == FORMAT_AUTOMATIC 72 | if filename === nothing 73 | error("When `format==FORMAT_AUTOMATIC` you must pass a `filename`.") 74 | end 75 | for (ext, model) in [ 76 | (".cbf", CBF.Model), 77 | (".lp", LP.Model), 78 | (".mof.json", MOF.Model), 79 | (".mps", MPS.Model), 80 | (".sdpa", SDPA.Model) 81 | ] 82 | if endswith(filename, ext) || occursin("$(ext).", filename) 83 | return model(; kwargs...) 84 | end 85 | end 86 | error("Unable to automatically detect format of $(filename).") 87 | end 88 | end 89 | 90 | const MATH_OPT_FORMATS = Union{ 91 | CBF.Model, 92 | LP.Model, 93 | MOF.Model, 94 | MPS.Model, 95 | SDPA.Model, 96 | } 97 | 98 | function MOI.write_to_file(model::MATH_OPT_FORMATS, filename::String) 99 | compressed_open(filename, "w", AutomaticCompression()) do io 100 | write(io, model) 101 | end 102 | end 103 | 104 | function MOI.read_from_file(model::MATH_OPT_FORMATS, filename::String) 105 | compressed_open(filename, "r", AutomaticCompression()) do io 106 | read!(io, model) 107 | end 108 | end 109 | 110 | end 111 | -------------------------------------------------------------------------------- /test/Test/contquadratic.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | import MathOptInterface 3 | const MOI = MathOptInterface 4 | const MOIT = MOI.Test 5 | const MOIU = MOI.Utilities 6 | 7 | mock = MOIU.MockOptimizer(MOIU.Model{Float64}()) 8 | config = MOIT.TestConfig() 9 | 10 | @testset "QP" begin 11 | MOIU.set_mock_optimize!(mock, 12 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [4/7, 3/7, 6/7], 13 | (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => [5/7, 6/7])) 14 | MOIT.qp1test(mock, config) 15 | MOIU.set_mock_optimize!(mock, 16 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [4/7, 3/7, 6/7], 17 | (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => [5/7, 6/7]), 18 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [4/7, 3/7, 6/7], 19 | (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => [10/7, 12/7])) 20 | MOIT.qp2test(mock, config) 21 | MOIU.set_mock_optimize!(mock, 22 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1/4, 3/4], 23 | (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => [11/4]), 24 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 0.0], 25 | (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => [-2.0])) 26 | MOIT.qp3test(mock, config) 27 | end 28 | @testset "QCP" begin 29 | MOIU.set_mock_optimize!(mock, 30 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1/2, 7/4], 31 | (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => [zeros(2)], 32 | (MOI.ScalarQuadraticFunction{Float64}, MOI.LessThan{Float64}) => [-1.0])) 33 | MOIT.qcp1test(mock, config) 34 | MOIU.set_mock_optimize!(mock, 35 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [√2], 36 | (MOI.ScalarQuadraticFunction{Float64}, MOI.LessThan{Float64}) => [-1/(2*√2)])) 37 | MOIT.qcp2test(mock, config) 38 | MOIT.qcp3test(mock, config) 39 | MOIU.set_mock_optimize!(mock, 40 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 1.0], 41 | (MOI.ScalarQuadraticFunction{Float64}, MOI.LessThan{Float64}) => [-1/3])) 42 | MOIT.qcp4test(mock, config) 43 | MOIU.set_mock_optimize!(mock, 44 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 1.0], 45 | (MOI.ScalarQuadraticFunction{Float64}, MOI.GreaterThan{Float64}) => [1/3])) 46 | MOIT.qcp5test(mock, config) 47 | end 48 | @testset "Non-convex QCP" begin 49 | MOIU.set_mock_optimize!(mock, 50 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [4.0, 1.0], MOI.FEASIBLE_POINT)) 51 | MOIT.ncqcp1test(mock, config) 52 | MOIU.set_mock_optimize!(mock, 53 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [2.0, 2.0], MOI.FEASIBLE_POINT)) 54 | MOIT.ncqcp2test(mock, config) 55 | end 56 | @testset "SOCP" begin 57 | MOIU.set_mock_optimize!(mock, 58 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1/2, 1/2, 1/√2], 59 | (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => [1/√2], 60 | (MOI.ScalarQuadraticFunction{Float64}, MOI.LessThan{Float64}) => [-1/√2])) 61 | MOIT.socp1test(mock, config) 62 | end 63 | -------------------------------------------------------------------------------- /test/Bridges/Variable/rsoc_to_soc.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | 3 | using MathOptInterface 4 | const MOI = MathOptInterface 5 | const MOIT = MathOptInterface.Test 6 | const MOIU = MathOptInterface.Utilities 7 | const MOIB = MathOptInterface.Bridges 8 | 9 | include("../utilities.jl") 10 | 11 | mock = MOIU.MockOptimizer(MOIU.Model{Float64}()) 12 | config = MOIT.TestConfig() 13 | 14 | bridged_mock = MOIB.Variable.RSOCtoSOC{Float64}(mock) 15 | 16 | @testset "rotatedsoc4" begin 17 | mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, 18 | [√2, 0.0, 1.0, 1.0], 19 | (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [-1.0]) 20 | MOIT.rotatedsoc4test(bridged_mock, config) 21 | 22 | ceqs = MOI.get(mock, MOI.ListOfConstraintIndices{ 23 | MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}}()) 24 | @test length(ceqs) == 1 25 | MOI.set(bridged_mock, MOI.ConstraintName(), ceqs[1], "c") 26 | 27 | @testset "Test mock model" begin 28 | var_names = ["a", "b", "c", "d"] 29 | MOI.set( 30 | mock, MOI.VariableName(), 31 | MOI.get(mock, MOI.ListOfVariableIndices()), var_names) 32 | socs = MOI.get(mock, MOI.ListOfConstraintIndices{ 33 | MOI.VectorOfVariables, MOI.SecondOrderCone}()) 34 | @test length(socs) == 1 35 | MOI.set(mock, MOI.ConstraintName(), socs[1], "soc") 36 | 37 | s2 = √2 38 | s = """ 39 | variables: a, b, c, d 40 | soc: [a, b, c, d] in MathOptInterface.SecondOrderCone(4) 41 | c: $s2*a <= 2.0 42 | maxobjective: c + d 43 | """ 44 | model = MOIU.Model{Float64}() 45 | MOIU.loadfromstring!(model, s) 46 | MOIU.test_models_equal(mock, model, var_names, ["soc", "c"]) 47 | end 48 | 49 | @testset "Test bridged model" begin 50 | var_names = ["t", "u", "x", "y"] 51 | MOI.set( 52 | bridged_mock, MOI.VariableName(), 53 | MOI.get(bridged_mock, MOI.ListOfVariableIndices()), var_names) 54 | rsocs = MOI.get(bridged_mock, MOI.ListOfConstraintIndices{ 55 | MOI.VectorOfVariables, MOI.RotatedSecondOrderCone}()) 56 | @test length(rsocs) == 1 57 | MOI.set(bridged_mock, MOI.ConstraintName(), rsocs[1], "rsoc") 58 | 59 | s = """ 60 | variables: t, u, x, y 61 | rsoc: [t, u, x, y] in MathOptInterface.RotatedSecondOrderCone(4) 62 | c: t + u <= 2.0 63 | maxobjective: x + y 64 | """ 65 | model = MOIU.Model{Float64}() 66 | MOIU.loadfromstring!(model, s) 67 | MOIU.test_models_equal(bridged_mock, model, var_names, ["rsoc", "c"]) 68 | end 69 | 70 | @testset "Delete" begin 71 | tuxy = MOI.get(bridged_mock, MOI.ListOfVariableIndices()) 72 | 73 | message = string("Cannot delete variable as it is constrained with other", 74 | " variables in a `MOI.VectorOfVariables`.") 75 | for i in eachindex(tuxy) 76 | err = MOI.DeleteNotAllowed(tuxy[i], message) 77 | @test_throws err MOI.delete(bridged_mock, tuxy[i]) 78 | end 79 | 80 | test_delete_bridged_variables(bridged_mock, tuxy, MOI.RotatedSecondOrderCone, 4, ( 81 | (MOI.VectorOfVariables, MOI.SecondOrderCone, 0), 82 | )) 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /test/Bridges/Variable/soc_to_rsoc.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | 3 | using MathOptInterface 4 | const MOI = MathOptInterface 5 | const MOIT = MathOptInterface.Test 6 | const MOIU = MathOptInterface.Utilities 7 | const MOIB = MathOptInterface.Bridges 8 | 9 | include("../utilities.jl") 10 | 11 | mock = MOIU.MockOptimizer(MOIU.Model{Float64}()) 12 | config = MOIT.TestConfig() 13 | 14 | bridged_mock = MOIB.Variable.SOCtoRSOC{Float64}(mock) 15 | 16 | @testset "soc1v" begin 17 | mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1/√2 + 1/2, 1/√2 - 1/2, 1/√2], 18 | (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-√2]]) 19 | MOIT.soc1vtest(bridged_mock, config) 20 | 21 | ceqs = MOI.get(mock, MOI.ListOfConstraintIndices{ 22 | MOI.VectorAffineFunction{Float64}, MOI.Zeros}()) 23 | @test length(ceqs) == 1 24 | MOI.set(bridged_mock, MOI.ConstraintName(), ceqs[1], "ceq") 25 | 26 | @testset "Test mock model" begin 27 | var_names = ["a", "b", "c"] 28 | MOI.set( 29 | mock, MOI.VariableName(), 30 | MOI.get(mock, MOI.ListOfVariableIndices()), var_names) 31 | rsocs = MOI.get(mock, MOI.ListOfConstraintIndices{ 32 | MOI.VectorOfVariables, MOI.RotatedSecondOrderCone}()) 33 | @test length(rsocs) == 1 34 | MOI.set(mock, MOI.ConstraintName(), rsocs[1], "rsoc") 35 | 36 | invs2 = 1 / √2 37 | s = """ 38 | variables: a, b, c 39 | rsoc: [a, b, c] in MathOptInterface.RotatedSecondOrderCone(3) 40 | ceq: [$invs2*a + $invs2*b + -1.0] in MathOptInterface.Zeros(1) 41 | maxobjective: $invs2*a + -$invs2*b + c 42 | """ 43 | model = MOIU.Model{Float64}() 44 | MOIU.loadfromstring!(model, s) 45 | MOIU.test_models_equal(mock, model, var_names, ["rsoc", "ceq"]) 46 | end 47 | 48 | @testset "Test bridged model" begin 49 | var_names = ["x", "y", "z"] 50 | MOI.set( 51 | bridged_mock, MOI.VariableName(), 52 | MOI.get(bridged_mock, MOI.ListOfVariableIndices()), var_names) 53 | socs = MOI.get(bridged_mock, MOI.ListOfConstraintIndices{ 54 | MOI.VectorOfVariables, MOI.SecondOrderCone}()) 55 | @test length(socs) == 1 56 | MOI.set(bridged_mock, MOI.ConstraintName(), socs[1], "soc") 57 | 58 | s = """ 59 | variables: x, y, z 60 | soc: [x, y, z] in MathOptInterface.SecondOrderCone(3) 61 | ceq: [x + -1.0] in MathOptInterface.Zeros(1) 62 | maxobjective: y + z 63 | """ 64 | model = MOIU.Model{Float64}() 65 | MOIU.loadfromstring!(model, s) 66 | MOIU.test_models_equal(bridged_mock, model, var_names, ["soc", "ceq"]) 67 | end 68 | 69 | @testset "Delete" begin 70 | xyz = MOI.get(bridged_mock, MOI.ListOfVariableIndices()) 71 | 72 | message = string("Cannot delete variable as it is constrained with other", 73 | " variables in a `MOI.VectorOfVariables`.") 74 | for i in eachindex(xyz) 75 | err = MOI.DeleteNotAllowed(xyz[i], message) 76 | @test_throws err MOI.delete(bridged_mock, xyz[i]) 77 | end 78 | 79 | test_delete_bridged_variables(bridged_mock, xyz, MOI.SecondOrderCone, 3, ( 80 | (MOI.VectorOfVariables, MOI.RotatedSecondOrderCone, 0), 81 | )) 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /src/modifications.jl: -------------------------------------------------------------------------------- 1 | """ 2 | struct ModifyConstraintNotAllowed{F<:AbstractFunction, S<:AbstractSet, 3 | C<:AbstractFunctionModification} <: NotAllowedError 4 | constraint_index::ConstraintIndex{F, S} 5 | change::C 6 | message::String 7 | end 8 | 9 | An error indicating that the constraint modification `change` cannot be applied 10 | to the constraint of index `ci`. 11 | """ 12 | struct ModifyConstraintNotAllowed{F<:AbstractFunction, S<:AbstractSet, 13 | C<:AbstractFunctionModification} <: NotAllowedError 14 | constraint_index::ConstraintIndex{F, S} 15 | change::C 16 | message::String 17 | end 18 | function ModifyConstraintNotAllowed( 19 | ci::ConstraintIndex{F, S}, 20 | change::AbstractFunctionModification, 21 | message="") where {F<:AbstractFunction, S<:AbstractSet} 22 | return ModifyConstraintNotAllowed{F, S, typeof(change)}(ci, change, message) 23 | end 24 | throw_modify_not_allowed(ci::ConstraintIndex, args...) = throw(ModifyConstraintNotAllowed(ci, args...)) 25 | 26 | operation_name(err::ModifyConstraintNotAllowed{F, S}) where {F, S} = "Modifying the constraints $(err.constraint_index) with $(err.change)" 27 | 28 | """ 29 | struct ModifyObjectiveNotAllowed{C<:AbstractFunctionModification} <: NotAllowedError 30 | change::C 31 | message::String 32 | end 33 | 34 | An error indicating that the objective modification `change` cannot be applied 35 | to the objective. 36 | """ 37 | struct ModifyObjectiveNotAllowed{C<:AbstractFunctionModification} <: NotAllowedError 38 | change::C 39 | message::String 40 | end 41 | function ModifyObjectiveNotAllowed(change::AbstractFunctionModification) 42 | return ModifyObjectiveNotAllowed(change, "") 43 | end 44 | throw_modify_not_allowed(::ObjectiveFunction, args...) = throw(ModifyObjectiveNotAllowed(args...)) 45 | 46 | operation_name(err::ModifyObjectiveNotAllowed) = "Modifying the objective function with $(err.change)" 47 | 48 | """ 49 | ## Constraint Function 50 | 51 | modify(model::ModelLike, ci::ConstraintIndex, change::AbstractFunctionModification) 52 | 53 | Apply the modification specified by `change` to the function of constraint `ci`. 54 | 55 | An [`ModifyConstraintNotAllowed`](@ref) error is thrown if modifying 56 | constraints is not supported by the model `model`. 57 | 58 | ### Examples 59 | 60 | ```julia 61 | modify(model, ci, ScalarConstantChange(10.0)) 62 | ``` 63 | 64 | ## Objective Function 65 | 66 | modify(model::ModelLike, ::ObjectiveFunction, change::AbstractFunctionModification) 67 | 68 | Apply the modification specified by `change` to the objective function of 69 | `model`. To change the function completely, call `set` instead. 70 | 71 | An [`ModifyObjectiveNotAllowed`](@ref) error is thrown if modifying 72 | objectives is not supported by the model `model`. 73 | 74 | ### Examples 75 | 76 | ```julia 77 | modify(model, ObjectiveFunction{ScalarAffineFunction{Float64}}(), ScalarConstantChange(10.0)) 78 | ``` 79 | """ 80 | function modify end 81 | 82 | function modify(model::ModelLike, ci::ConstraintIndex, 83 | change::AbstractFunctionModification) 84 | throw_modify_not_allowed(ci, change) 85 | end 86 | 87 | function modify(model::ModelLike, attr::ObjectiveFunction, 88 | change::AbstractFunctionModification) 89 | throw_modify_not_allowed(attr, change) 90 | end 91 | -------------------------------------------------------------------------------- /test/Bridges/Constraint/soc_to_psd.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | 3 | using MathOptInterface 4 | const MOI = MathOptInterface 5 | const MOIT = MathOptInterface.Test 6 | const MOIU = MathOptInterface.Utilities 7 | const MOIB = MathOptInterface.Bridges 8 | 9 | include("../utilities.jl") 10 | 11 | mock = MOIU.MockOptimizer(MOIU.UniversalFallback(MOIU.Model{Float64}())) 12 | config = MOIT.TestConfig() 13 | 14 | @testset "SOCtoPSD" begin 15 | bridged_mock = MOIB.Constraint.SOCtoPSD{Float64}(mock) 16 | 17 | MOIT.basic_constraint_tests(bridged_mock, config, 18 | include = [(F, MOI.SecondOrderCone) for F in [ 19 | MOI.VectorOfVariables, MOI.VectorAffineFunction{Float64}, MOI.VectorQuadraticFunction{Float64} 20 | ]]) 21 | 22 | mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 1/√2, 1/√2], 23 | (MOI.VectorAffineFunction{Float64}, MOI.PositiveSemidefiniteConeTriangle) => [[√2/2, -1/2, √2/4, -1/2, √2/4, √2/4]], 24 | (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-√2]]) 25 | MOIT.soc1vtest(bridged_mock, config) 26 | MOIT.soc1ftest(bridged_mock, config) 27 | 28 | ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.SecondOrderCone}())) 29 | 30 | @testset "$attr" for attr in [MOI.ConstraintPrimalStart(), MOI.ConstraintDualStart()] 31 | @test MOI.supports(bridged_mock, attr, typeof(ci)) 32 | value = [√2, -1.0, -1.0] 33 | MOI.set(bridged_mock, attr, ci, value) 34 | @test MOI.get(bridged_mock, attr, ci) ≈ value 35 | end 36 | 37 | test_delete_bridge(bridged_mock, ci, 3, ((MOI.VectorAffineFunction{Float64}, MOI.PositiveSemidefiniteConeTriangle, 0),)) 38 | end 39 | 40 | @testset "RSOCtoPSD" begin 41 | bridged_mock = MOIB.Constraint.RSOCtoPSD{Float64}(mock) 42 | 43 | MOIT.basic_constraint_tests( 44 | bridged_mock, config, 45 | include = [(F, MOI.RotatedSecondOrderCone) 46 | for F in [MOI.VectorOfVariables, MOI.VectorAffineFunction{Float64}, MOI.VectorQuadraticFunction{Float64} 47 | ]]) 48 | 49 | mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0.5, 1.0, 1/√2, 1/√2], 50 | (MOI.SingleVariable, MOI.EqualTo{Float64}) => [-√2, -1/√2], 51 | (MOI.VectorAffineFunction{Float64}, MOI.PositiveSemidefiniteConeTriangle) => [[√2, -1/2, √2/8, -1/2, √2/8, √2/8]]) 52 | MOIT.rotatedsoc1vtest(bridged_mock, config) 53 | mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1/√2, 1/√2], 54 | (MOI.VectorAffineFunction{Float64}, MOI.PositiveSemidefiniteConeTriangle) => [[√2, -1/2, √2/8, -1/2, √2/8, √2/8]]) 55 | MOIT.rotatedsoc1ftest(bridged_mock, config) 56 | 57 | ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone}())) 58 | 59 | @testset "$attr" for attr in [MOI.ConstraintPrimalStart(), MOI.ConstraintDualStart()] 60 | @test MOI.supports(bridged_mock, attr, typeof(ci)) 61 | value = [√2, √2/4, -1.0, -1.0] 62 | MOI.set(bridged_mock, attr, ci, value) 63 | @test MOI.get(bridged_mock, attr, ci) ≈ value 64 | end 65 | 66 | test_delete_bridge(bridged_mock, ci, 2, ((MOI.VectorAffineFunction{Float64}, MOI.PositiveSemidefiniteConeTriangle, 0),)) 67 | end 68 | -------------------------------------------------------------------------------- /src/Bridges/Constraint/rsoc.jl: -------------------------------------------------------------------------------- 1 | """ 2 | RSOCBridge{T, F, G} 3 | 4 | The `RotatedSecondOrderCone` is `SecondOrderCone` representable; see [1, p. 104]. 5 | Indeed, we have ``2tu = (t/√2 + u/√2)^2 - (t/√2 - u/√2)^2`` hence 6 | ```math 7 | 2tu \\ge || x ||_2^2 8 | ``` 9 | is equivalent to 10 | ```math 11 | (t/√2 + u/√2)^2 \\ge || x ||_2^2 + (t/√2 - u/√2)^2. 12 | ``` 13 | We can therefore use the transformation ``(t, u, x) \\mapsto (t/√2+u/√2, t/√2-u/√2, x)``. 14 | Note that the linear transformation is a symmetric involution (i.e. it is its own transpose and its own inverse). 15 | That means in particular that the norm is of constraint primal and duals are preserved by the tranformation. 16 | 17 | [1] Ben-Tal, Aharon, and Arkadi Nemirovski. *Lectures on modern convex optimization: analysis, algorithms, and engineering applications*. Society for Industrial and Applied Mathematics, 2001. 18 | """ 19 | struct RSOCBridge{T, F, G} <: SetMapBridge{T, MOI.SecondOrderCone, MOI.RotatedSecondOrderCone, F, G} 20 | constraint::CI{F, MOI.SecondOrderCone} 21 | end 22 | 23 | function rotate_function_type(G::Type{<:MOI.AbstractVectorFunction}, T::Type) 24 | S = MOIU.promote_operation(/, T, MOIU.scalar_type(G), T) 25 | Y = MOIU.promote_operation(-, T, S, S) 26 | Z = MOIU.promote_operation(+, T, S, S) 27 | return MOIU.promote_operation(vcat, T, Z, Y, G) 28 | end 29 | 30 | function concrete_bridge_type(::Type{<:RSOCBridge{T}}, 31 | G::Type{<:MOI.AbstractVectorFunction}, 32 | ::Type{MOI.RotatedSecondOrderCone}) where T 33 | return RSOCBridge{T, rotate_function_type(G, T), G} 34 | end 35 | 36 | function map_set(::Type{<:RSOCBridge}, set::MOI.RotatedSecondOrderCone) 37 | return MOI.SecondOrderCone(MOI.dimension(set)) 38 | end 39 | function inverse_map_set(::Type{<:RSOCBridge}, set::MOI.SecondOrderCone) 40 | return MOI.RotatedSecondOrderCone(MOI.dimension(set)) 41 | end 42 | 43 | """ 44 | SOCRBridge{T, F, G} 45 | 46 | We simply do the inverse transformation of [`RSOCBridge`](@ref). In fact, as the 47 | transformation is an involution, we do the same transformation. 48 | """ 49 | struct SOCRBridge{T, F, G} <: SetMapBridge{T, MOI.RotatedSecondOrderCone, MOI.SecondOrderCone, F, G} 50 | constraint::CI{F, MOI.RotatedSecondOrderCone} 51 | end 52 | 53 | function concrete_bridge_type(::Type{<:SOCRBridge{T}}, 54 | G::Type{<:MOI.AbstractVectorFunction}, 55 | ::Type{MOI.SecondOrderCone}) where T 56 | return SOCRBridge{T, rotate_function_type(G, T), G} 57 | end 58 | 59 | function map_set(::Type{<:SOCRBridge}, set::MOI.SecondOrderCone) 60 | return MOI.RotatedSecondOrderCone(MOI.dimension(set)) 61 | end 62 | function inverse_map_set(::Type{<:SOCRBridge}, set::MOI.RotatedSecondOrderCone) 63 | return MOI.SecondOrderCone(MOI.dimension(set)) 64 | end 65 | 66 | function map_function(::Type{<:Union{RSOCBridge{T}, SOCRBridge{T}}}, func) where T 67 | scalars = MOIU.eachscalar(func) 68 | t = scalars[1] 69 | u = scalars[2] 70 | x = scalars[3:end] 71 | s2 = √T(2) 72 | ts = MOIU.operate!(/, T, t, s2) 73 | us = MOIU.operate!(/, T, u, s2) 74 | # Cannot use `operate!` here since `ts` and `us` are needed for the next 75 | # line 76 | y = ts - us 77 | z = MOIU.operate!(+, T, ts, us) 78 | return MOIU.operate(vcat, T, z, y, x) 79 | end 80 | # The map is an involution 81 | inverse_map_function(BT::Type{<:Union{RSOCBridge, SOCRBridge}}, func) = map_function(BT, func) 82 | # The map is symmetric 83 | adjoint_map_function(BT::Type{<:Union{RSOCBridge, SOCRBridge}}, func) = map_function(BT, func) 84 | # The map is a symmetric involution 85 | inverse_adjoint_map_function(BT::Type{<:Union{RSOCBridge, SOCRBridge}}, func) = map_function(BT, func) 86 | -------------------------------------------------------------------------------- /test/Utilities/variables.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | using MathOptInterface 3 | const MOI = MathOptInterface 4 | const MOIU = MOI.Utilities 5 | 6 | @testset "get_bounds" begin 7 | @testset "Float64" begin 8 | T = Float64 9 | model = MOIU.Model{T}() 10 | 11 | x = MOI.add_variable(model) 12 | y_v, y_c = MOI.add_constrained_variable(model, MOI.GreaterThan{T}(1.0)) 13 | z_v, z_c = MOI.add_constrained_variable(model, MOI.LessThan{T}(-1.0)) 14 | w_v, w_c = MOI.add_constrained_variable(model, MOI.Interval{T}(-2.0, 3.0)) 15 | u_v, u_c = MOI.add_constrained_variable( 16 | model, MOI.Semicontinuous{T}(1.0, 3.5)) 17 | v_v, v_c = MOI.add_constrained_variable( 18 | model, MOI.Semiinteger{T}(-1.0, 2.5)) 19 | e_v, e_c = MOI.add_constrained_variable(model, MOI.EqualTo(1.35)) 20 | 21 | @test -Inf == @inferred MOIU.get_bounds(model, T, x)[1] 22 | @test Inf == @inferred MOIU.get_bounds(model, T, x)[2] 23 | 24 | @test 1.0 == @inferred MOIU.get_bounds(model, T, y_v)[1] 25 | @test Inf == @inferred MOIU.get_bounds(model, T, y_v)[2] 26 | 27 | @test -Inf == @inferred MOIU.get_bounds(model, T, z_v)[1] 28 | @test -1.0 == @inferred MOIU.get_bounds(model, T, z_v)[2] 29 | 30 | @test -2.0 == @inferred MOIU.get_bounds(model, T, w_v)[1] 31 | @test 3.0 == @inferred MOIU.get_bounds(model, T, w_v)[2] 32 | 33 | @test 0.0 == @inferred MOIU.get_bounds(model, T, u_v)[1] 34 | @test 3.5 == @inferred MOIU.get_bounds(model, T, u_v)[2] 35 | 36 | @test -1.0 == @inferred MOIU.get_bounds(model, T, v_v)[1] 37 | @test 2.5 == @inferred MOIU.get_bounds(model, T, v_v)[2] 38 | 39 | @test 1.35 == @inferred MOIU.get_bounds(model, T, e_v)[1] 40 | @test 1.35 == @inferred MOIU.get_bounds(model, T, e_v)[2] 41 | 42 | MOI.add_constraint(model, MOI.SingleVariable(y_v), MOI.LessThan{T}(3.6)) 43 | 44 | @test 1.0 == @inferred MOIU.get_bounds(model, T, y_v)[1] 45 | @test 3.6 == @inferred MOIU.get_bounds(model, T, y_v)[2] 46 | end 47 | 48 | @testset "UInt128" begin 49 | T = UInt128 50 | model = MOIU.Model{T}() 51 | 52 | x = MOI.add_variable(model) 53 | y_v, y_c = MOI.add_constrained_variable(model, MOI.GreaterThan{T}(1)) 54 | z_v, z_c = MOI.add_constrained_variable(model, MOI.LessThan{T}(1)) 55 | w_v, w_c = MOI.add_constrained_variable(model, MOI.Interval{T}(2, 3)) 56 | u_v, u_c = MOI.add_constrained_variable( 57 | model, MOI.Semicontinuous{T}(1, 3)) 58 | v_v, v_c = MOI.add_constrained_variable( 59 | model, MOI.Semiinteger{T}(4, 7)) 60 | 61 | @test typemin(T) == @inferred MOIU.get_bounds(model, T, x)[1] 62 | @test typemax(T) == @inferred MOIU.get_bounds(model, T, x)[2] 63 | 64 | @test 1 == @inferred MOIU.get_bounds(model, T, y_v)[1] 65 | @test typemax(T) == @inferred MOIU.get_bounds(model, T, y_v)[2] 66 | 67 | @test typemin(T) == @inferred MOIU.get_bounds(model, T, z_v)[1] 68 | @test 1 == @inferred MOIU.get_bounds(model, T, z_v)[2] 69 | 70 | @test 2 == @inferred MOIU.get_bounds(model, T, w_v)[1] 71 | @test 3 == @inferred MOIU.get_bounds(model, T, w_v)[2] 72 | 73 | @test 0 == @inferred MOIU.get_bounds(model, T, u_v)[1] 74 | @test 3 == @inferred MOIU.get_bounds(model, T, u_v)[2] 75 | 76 | @test 0 == @inferred MOIU.get_bounds(model, T, v_v)[1] 77 | @test 7 == @inferred MOIU.get_bounds(model, T, v_v)[2] 78 | 79 | MOI.add_constraint(model, MOI.SingleVariable(y_v), MOI.LessThan{T}(5)) 80 | 81 | @test 1 == @inferred MOIU.get_bounds(model, T, y_v)[1] 82 | @test 5 == @inferred MOIU.get_bounds(model, T, y_v)[2] 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /src/Test/UnitTests/unit_tests.jl: -------------------------------------------------------------------------------- 1 | #= 2 | These tests aim to minimally test each expected feature in MOI, in addition 3 | to the full end-to-end tests in contlinear.jl etc 4 | =# 5 | 6 | const unittests = Dict{String, Function}() 7 | 8 | """ 9 | test_model_solution(model::MOI.ModelLike, config::TestConfig; 10 | objective_value = nothing, 11 | variable_primal = nothing, 12 | constraint_primal = nothing, 13 | constraint_dual = nothing 14 | ) 15 | 16 | Solve, and then test, various aspects of a model. 17 | 18 | First, check that `TerminationStatus == MOI.OPTIMAL`. 19 | 20 | If `objective_value` is not nothing, check that the attribute `ObjectiveValue()` 21 | is approximately `objective_value`. 22 | 23 | If `variable_primal` is not nothing, check that the attribute `PrimalStatus` is 24 | `MOI.FEASIBLE_POINT`. Then for each `(index, value)` in `variable_primal`, check 25 | that the primal value of the variable `index` is approximately `value`. 26 | 27 | If `constraint_primal` is not nothing, check that the attribute `PrimalStatus` is 28 | `MOI.FEASIBLE_POINT`. Then for each `(index, value)` in `constraint_primal`, check 29 | that the primal value of the constraint `index` is approximately `value`. 30 | 31 | Finally, if `config.duals = true`, and if `constraint_dual` is not nothing, 32 | check that the attribute `DualStatus` is `MOI.FEASIBLE_POINT`. Then for each 33 | `(index, value)` in `constraint_dual`, check that the dual of the constraint 34 | `index` is approximately `value`. 35 | 36 | ### Example 37 | 38 | MOIU.loadfromstring!(model, \"\"\" 39 | variables: x 40 | minobjective: 2.0x + 1.0 41 | c: x >= 1.0 42 | \"\"\") 43 | x = MOI.get(model, MOI.VariableIndex, "x") 44 | c = MOI.get(model, MOI.ConstraintIndex{MOI.SingleVariable, MOI.GreaterThan{Float64}}, "c") 45 | test_model_solution(model, config; 46 | objective_value = 3.0, 47 | variable_primal = [(x, 1.0)], 48 | constraint_primal = [(c, 1.0)], 49 | constraint_dual = [(c, 2.0)] 50 | ) 51 | 52 | """ 53 | function test_model_solution(model, config; 54 | objective_value = nothing, 55 | variable_primal = nothing, 56 | constraint_primal = nothing, 57 | constraint_dual = nothing 58 | ) 59 | config.solve || return 60 | atol, rtol = config.atol, config.rtol 61 | MOI.optimize!(model) 62 | @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status 63 | if objective_value != nothing 64 | @test MOI.get(model, MOI.ObjectiveValue()) ≈ objective_value atol=atol rtol=rtol 65 | end 66 | if variable_primal != nothing 67 | @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT 68 | for (index, solution_value) in variable_primal 69 | @test MOI.get(model, MOI.VariablePrimal(), index) ≈ solution_value atol=atol rtol=rtol 70 | end 71 | end 72 | if constraint_primal != nothing 73 | @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT 74 | for (index, solution_value) in constraint_primal 75 | @test MOI.get(model, MOI.ConstraintPrimal(), index) ≈ solution_value atol=atol rtol=rtol 76 | end 77 | end 78 | if config.duals 79 | if constraint_dual != nothing 80 | @test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT 81 | for (index, solution_value) in constraint_dual 82 | @test MOI.get(model, MOI.ConstraintDual(), index) ≈ solution_value atol=atol rtol=rtol 83 | end 84 | end 85 | end 86 | end 87 | 88 | include("variables.jl") 89 | include("objectives.jl") 90 | include("constraints.jl") 91 | include("basic_constraint_tests.jl") 92 | include("modifications.jl") 93 | include("solve.jl") 94 | include("attributes.jl") 95 | 96 | @moitestset unit 97 | -------------------------------------------------------------------------------- /test/Bridges/Constraint/rsoc.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | 3 | using MathOptInterface 4 | const MOI = MathOptInterface 5 | const MOIT = MathOptInterface.Test 6 | const MOIU = MathOptInterface.Utilities 7 | const MOIB = MathOptInterface.Bridges 8 | 9 | include("../utilities.jl") 10 | 11 | mock = MOIU.MockOptimizer(MOIU.UniversalFallback(MOIU.Model{Float64}())) 12 | config = MOIT.TestConfig() 13 | 14 | @testset "RSOC" begin 15 | bridged_mock = MOIB.Constraint.RSOC{Float64}(mock) 16 | 17 | MOIT.basic_constraint_tests( 18 | bridged_mock, config, 19 | include = [(F, S) 20 | for F in [MOI.VectorOfVariables, MOI.VectorAffineFunction{Float64}, 21 | MOI.VectorQuadraticFunction{Float64}] 22 | for S in [MOI.RotatedSecondOrderCone]]) 23 | 24 | mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0.5, 1.0, 1/√2, 1/√2], 25 | (MOI.SingleVariable, MOI.EqualTo{Float64}) => [-√2, -1/√2], 26 | (MOI.VectorAffineFunction{Float64}, MOI.SecondOrderCone) => [[3/2, 1/2, -1.0, -1.0]]) 27 | MOIT.rotatedsoc1vtest(bridged_mock, config) 28 | mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1/√2, 1/√2], 29 | (MOI.VectorAffineFunction{Float64}, MOI.SecondOrderCone) => [[3/2, 1/2, -1.0, -1.0]]) 30 | MOIT.rotatedsoc1ftest(bridged_mock, config) 31 | ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone}())) 32 | 33 | @testset "$attr" for attr in [MOI.ConstraintPrimalStart(), MOI.ConstraintDualStart()] 34 | @test MOI.supports(bridged_mock, attr, typeof(ci)) 35 | value = [√2, 1/√2, -1.0, -1.0] 36 | MOI.set(bridged_mock, attr, ci, value) 37 | @test MOI.get(bridged_mock, attr, ci) ≈ value 38 | end 39 | 40 | test_delete_bridge(bridged_mock, ci, 2, ((MOI.VectorAffineFunction{Float64}, MOI.SecondOrderCone, 0),)) 41 | end 42 | 43 | @testset "SOCR" begin 44 | bridged_mock = MOIB.Constraint.SOCR{Float64}(mock) 45 | 46 | MOIT.basic_constraint_tests( 47 | bridged_mock, config, 48 | include = [(F, S) 49 | for F in [MOI.VectorOfVariables, MOI.VectorAffineFunction{Float64}, 50 | MOI.VectorQuadraticFunction{Float64}] 51 | for S in [MOI.SecondOrderCone]]) 52 | 53 | mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 1/√2, 1/√2], 54 | (MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone) => [[1 - 1/√2, 1 + 1/√2, -1]], 55 | (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-√2]]) 56 | MOIT.soc1vtest(bridged_mock, config) 57 | mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 1/√2, 1/√2], 58 | (MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone) => [[1 - 1/√2, 1 + 1/√2, -1]], 59 | (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-√2]]) 60 | MOIT.soc1ftest(bridged_mock, config) 61 | ci = first(MOI.get( 62 | bridged_mock, 63 | MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, 64 | MOI.SecondOrderCone}())) 65 | 66 | @testset "$attr" for attr in [MOI.ConstraintPrimalStart(), MOI.ConstraintDualStart()] 67 | @test MOI.supports(bridged_mock, attr, typeof(ci)) 68 | value = [√2, 1/√2, -1.0, -1.0] 69 | MOI.set(bridged_mock, attr, ci, value) 70 | @test MOI.get(bridged_mock, attr, ci) ≈ value 71 | end 72 | 73 | test_delete_bridge(bridged_mock, ci, 3, 74 | ((MOI.VectorAffineFunction{Float64}, 75 | MOI.RotatedSecondOrderCone, 0),)) 76 | end 77 | -------------------------------------------------------------------------------- /test/Test/intlinear.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | import MathOptInterface 3 | const MOI = MathOptInterface 4 | const MOIT = MOI.Test 5 | const MOIU = MOI.Utilities 6 | 7 | mock = MOIU.MockOptimizer(MOIU.Model{Float64}()) 8 | config = MOIT.TestConfig() 9 | 10 | MOIU.set_mock_optimize!(mock, 11 | (mock::MOIU.MockOptimizer) -> begin 12 | MOI.set(mock, MOI.ObjectiveBound(), 20.0) 13 | MOIU.mock_optimize!(mock, [4, 5, 1]) 14 | end) 15 | MOIT.int1test(mock, config) 16 | MOIU.set_mock_optimize!(mock, 17 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0, 1, 2]), 18 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1, 1, 2]), 19 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 3.0, 12.0]), 20 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0.0, 0.0, 2.0, 2.0, 0.0, 2.0, 0.0, 0.0, 6.0, 24.0])) 21 | MOIT.int2test(mock, config) 22 | # FIXME [1, 0...] is not the correct optimal solution but it passes the test 23 | MOIU.set_mock_optimize!(mock, 24 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0; zeros(10)])) 25 | MOIT.int3test(mock, config) 26 | MOIU.set_mock_optimize!(mock, 27 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1, 0, 0, 1, 1])) 28 | MOIT.knapsacktest(mock, config) 29 | MOIU.set_mock_optimize!(mock, 30 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.25, 8.75, 0., 1.]) 31 | ) 32 | MOIT.indicator1_test(mock, config) 33 | MOIU.set_mock_optimize!(mock, 34 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [2.0, 8.0, 1., 0.]) 35 | ) 36 | MOIT.indicator2_test(mock, config) 37 | MOIU.set_mock_optimize!(mock, 38 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.25, 8.75, 1., 1.]) 39 | ) 40 | MOIT.indicator3_test(mock, config) 41 | MOIU.set_mock_optimize!(mock, 42 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.25, 8.75, 0., 1.]) 43 | ) 44 | MOIT.indicator4_test(mock, config) 45 | MOIU.set_mock_optimize!(mock, 46 | (mock::MOIU.MockOptimizer) -> begin 47 | MOI.set(mock, MOI.ObjectiveBound(), 0.0) 48 | MOIU.mock_optimize!(mock, [0.0, 0.0]) 49 | end, 50 | (mock::MOIU.MockOptimizer) -> begin 51 | MOI.set(mock, MOI.ObjectiveBound(), 2.0) 52 | MOIU.mock_optimize!(mock, [2.0, 1.0]) 53 | end, 54 | (mock::MOIU.MockOptimizer) -> begin 55 | MOI.set(mock, MOI.ObjectiveBound(), 2.0) 56 | MOIU.mock_optimize!(mock, [2.0, 2.0]) 57 | end, 58 | (mock::MOIU.MockOptimizer) -> begin 59 | MOI.set(mock, MOI.ObjectiveBound(), 2.5) 60 | MOIU.mock_optimize!(mock, [2.5, 2.5]) 61 | end, 62 | (mock::MOIU.MockOptimizer) -> begin 63 | MOI.set(mock, MOI.ObjectiveBound(), 3.0) 64 | MOIU.mock_optimize!(mock, [3.0, 3.0]) 65 | end, 66 | (mock::MOIU.MockOptimizer) -> MOI.set(mock, MOI.TerminationStatus(), MOI.INFEASIBLE) 67 | ) 68 | MOIT.semiconttest(mock,config) 69 | MOIU.set_mock_optimize!(mock, 70 | (mock::MOIU.MockOptimizer) -> begin 71 | MOI.set(mock, MOI.ObjectiveBound(), 0.0) 72 | MOIU.mock_optimize!(mock, [0.0, 0.0]) 73 | end, 74 | (mock::MOIU.MockOptimizer) -> begin 75 | MOI.set(mock, MOI.ObjectiveBound(), 2.0) 76 | MOIU.mock_optimize!(mock, [2.0, 1.0]) 77 | end, 78 | (mock::MOIU.MockOptimizer) -> begin 79 | MOI.set(mock, MOI.ObjectiveBound(), 2.0) 80 | MOIU.mock_optimize!(mock, [2.0, 2.0]) 81 | end, 82 | (mock::MOIU.MockOptimizer) -> begin 83 | MOI.set(mock, MOI.ObjectiveBound(), 3.0) 84 | MOIU.mock_optimize!(mock, [3.0, 2.5]) 85 | end, 86 | (mock::MOIU.MockOptimizer) -> begin 87 | MOI.set(mock, MOI.ObjectiveBound(), 3.0) 88 | MOIU.mock_optimize!(mock, [3.0, 3.0]) 89 | end, 90 | (mock::MOIU.MockOptimizer) -> MOI.set(mock, MOI.TerminationStatus(), MOI.INFEASIBLE) 91 | ) 92 | MOIT.semiinttest(mock,config) -------------------------------------------------------------------------------- /test/FileFormats/FileFormats.jl: -------------------------------------------------------------------------------- 1 | using MathOptInterface 2 | using Test 3 | 4 | const MOI = MathOptInterface 5 | 6 | @testset "MOI.FileFormats tests" begin 7 | @testset "$(file)" for file in ["CBF", "LP", "MOF", "MPS", "SDPA"] 8 | include(joinpath(@__DIR__, file, "$(file).jl")) 9 | end 10 | 11 | @testset "Copying options" begin 12 | models = [ 13 | MOI.FileFormats.CBF.Model, 14 | MOI.FileFormats.LP.Model, 15 | MOI.FileFormats.MOF.Model, 16 | MOI.FileFormats.MPS.Model, 17 | MOI.FileFormats.SDPA.Model, 18 | ] 19 | for src in models 20 | model_src = src() 21 | for dest in models 22 | model_dest = dest() 23 | MOI.copy_to(model_dest, model_src) 24 | @test !isempty(sprint(write, model_dest)) 25 | end 26 | model_dest = MOI.Utilities.MockOptimizer( 27 | MOI.Utilities.Model{Float64}() 28 | ) 29 | MOI.copy_to(model_dest, model_src) 30 | end 31 | end 32 | 33 | @testset "Calling MOF.compressed_open" begin 34 | for cs in [MOI.FileFormats.Bzip2(), MOI.FileFormats.Gzip()] 35 | for open_type in ["a", "r+", "w+", "a+"] 36 | @test_throws ArgumentError MOI.FileFormats.compressed_open( 37 | (x) -> nothing, "dummy.gz", open_type, cs 38 | ) 39 | end 40 | end 41 | end 42 | 43 | @testset "Provided compression schemes" begin 44 | filename = joinpath(@__DIR__, "MPS", "free_integer.mps") 45 | model = MOI.FileFormats.Model(filename = filename) 46 | MOI.read_from_file(model, filename) 47 | filename = joinpath(@__DIR__, "free_integer.mps") 48 | MOI.write_to_file(model, filename * ".garbage") 49 | for ext in ["", ".bz2", ".gz"] 50 | MOI.write_to_file(model, filename * ext) 51 | model2 = MOI.FileFormats.Model(filename = filename * ext) 52 | MOI.read_from_file(model2, filename) 53 | end 54 | 55 | sleep(1.0) # Allow time for unlink to happen. 56 | for ext in ["", ".garbage", ".bz2", ".gz"] 57 | rm(filename * ext, force = true) 58 | end 59 | end 60 | 61 | @testset "Model" begin 62 | for (format, model) in [ 63 | (MOI.FileFormats.FORMAT_CBF, MOI.FileFormats.CBF.Model()), 64 | (MOI.FileFormats.FORMAT_LP, MOI.FileFormats.LP.Model()), 65 | (MOI.FileFormats.FORMAT_MOF, MOI.FileFormats.MOF.Model()), 66 | (MOI.FileFormats.FORMAT_MPS, MOI.FileFormats.MPS.Model()), 67 | (MOI.FileFormats.FORMAT_SDPA, MOI.FileFormats.SDPA.Model()), 68 | ] 69 | @test typeof( 70 | MOI.FileFormats.Model(format = format, filename = "foo.bar") 71 | ) == typeof(model) 72 | end 73 | @test_throws( 74 | ErrorException( 75 | "When `format==FORMAT_AUTOMATIC` you must pass a `filename`." 76 | ), 77 | MOI.FileFormats.Model(format = MOI.FileFormats.FORMAT_AUTOMATIC) 78 | ) 79 | for (ext, model) in [ 80 | (".cbf", MOI.FileFormats.CBF.Model()), 81 | (".lp", MOI.FileFormats.LP.Model()), 82 | (".mof.json", MOI.FileFormats.MOF.Model()), 83 | (".mps", MOI.FileFormats.MPS.Model()), 84 | (".sdpa", MOI.FileFormats.SDPA.Model()), 85 | ] 86 | @test typeof(MOI.FileFormats.Model(filename = "a$(ext)")) == 87 | typeof(model) 88 | @test typeof(MOI.FileFormats.Model(filename = "a$(ext).gz")) == 89 | typeof(model) 90 | end 91 | @test_throws( 92 | ErrorException("Unable to automatically detect format of a.b."), 93 | MOI.FileFormats.Model(filename = "a.b") 94 | ) 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /test/isapprox.jl: -------------------------------------------------------------------------------- 1 | @testset "Functions isapprox" begin 2 | x = MOI.VariableIndex(1) 3 | y = MOI.VariableIndex(2) 4 | z = MOI.VariableIndex(3) 5 | @testset "Variable" begin 6 | @testset "Single" begin 7 | @test MOI.SingleVariable(x) == MOI.SingleVariable(x) 8 | @test MOI.SingleVariable(x) != MOI.SingleVariable(y) 9 | end 10 | @testset "Vector" begin 11 | @test MOI.VectorOfVariables([x, y]) == MOI.VectorOfVariables([x, y]) 12 | @test MOI.VectorOfVariables([y, x]) != MOI.VectorOfVariables([x, y]) 13 | @test MOI.VectorOfVariables([x, x]) != MOI.VectorOfVariables([x]) 14 | @test MOI.VectorOfVariables([x]) != MOI.VectorOfVariables([y]) 15 | end 16 | end 17 | @testset "Affine" begin 18 | @testset "Scalar" begin 19 | @test MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1, 1], [x, z]), 1) ≈ MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0, 1e-7, 1.0], [x, y, z]), 1.0) atol=1e-6 20 | @test MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0, 1e-7], [x, y]), 1.0) ≈ MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1, x)], 1) atol=1e-6 21 | f = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([2, 4], [x, y]), 6) 22 | g = deepcopy(f) 23 | @test g ≈ f 24 | f.terms[2] = MOI.ScalarAffineTerm(3, y) 25 | @test !(g ≈ f) 26 | end 27 | @testset "Vector" begin 28 | f = MOI.VectorAffineFunction(MOI.VectorAffineTerm.([1, 1, 2], 29 | MOI.ScalarAffineTerm.([2, 4, 3], 30 | [x, y, y])), [6, 8]) 31 | g = deepcopy(f) 32 | @test f ≈ g 33 | f.terms[3] = MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(9, y)) 34 | @test !(f ≈ g) 35 | push!(f.terms, MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(-6, y))) 36 | @test f ≈ g 37 | end 38 | end 39 | @testset "Quadratic" begin 40 | @testset "Affine" begin 41 | f = MOI.ScalarQuadraticFunction([MOI.ScalarAffineTerm(3, x)], 42 | MOI.ScalarQuadraticTerm.([1, 2, 3], 43 | [x, y, x], 44 | [x, y, y]), 8) 45 | g = deepcopy(f) 46 | @test f ≈ g 47 | push!(f.affine_terms, MOI.ScalarAffineTerm(2, y)) 48 | @test !(f ≈ g) 49 | g = deepcopy(f) 50 | push!(f.quadratic_terms, MOI.ScalarQuadraticTerm(2, y, x)) 51 | @test !(f ≈ g) 52 | push!(f.quadratic_terms, MOI.ScalarQuadraticTerm(-2, y, x)) 53 | @test f ≈ g 54 | end 55 | @testset "Vector" begin 56 | f = MOI.VectorQuadraticFunction(MOI.VectorAffineTerm.([1, 2, 1], MOI.ScalarAffineTerm.([3, 1, 1], 57 | [x, x, y])), 58 | MOI.VectorQuadraticTerm.([1, 1, 2], MOI.ScalarQuadraticTerm.([1, 2, 3], 59 | [x, y, x], 60 | [x, y, y])), [10, 11, 12]) 61 | g = deepcopy(f) 62 | @test f ≈ g 63 | f.affine_terms[1] = MOI.VectorAffineTerm(3, MOI.ScalarAffineTerm(4, x)) 64 | @test !(f ≈ g) 65 | push!(g.affine_terms, MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(-3, x))) 66 | push!(g.affine_terms, MOI.VectorAffineTerm(3, MOI.ScalarAffineTerm(4, x))) 67 | @test f ≈ g 68 | f.quadratic_terms[1] = MOI.VectorQuadraticTerm(3, MOI.ScalarQuadraticTerm(1, x, x)) 69 | @test !(f ≈ g) 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /test/Bridges/Constraint/vectorize.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | 3 | using MathOptInterface 4 | const MOI = MathOptInterface 5 | const MOIT = MathOptInterface.Test 6 | const MOIU = MathOptInterface.Utilities 7 | const MOIB = MathOptInterface.Bridges 8 | 9 | include("../utilities.jl") 10 | 11 | mock = MOIU.MockOptimizer(MOIU.UniversalFallback(MOIU.Model{Float64}())) 12 | config = MOIT.TestConfig() 13 | 14 | @testset "Vectorize" begin 15 | bridged_mock = MOIB.Constraint.Vectorize{Float64}(mock) 16 | 17 | MOIT.scalar_function_constant_not_zero(bridged_mock) 18 | 19 | MOIT.basic_constraint_tests(bridged_mock, config, 20 | include=Iterators.product( 21 | [MOI.SingleVariable, 22 | MOI.ScalarAffineFunction{Float64}], 23 | # TODO add it when operate(vcat, ...) 24 | # is implemented for quadratic 25 | #MOI.ScalarQuadraticFunction{Float64}], 26 | [MOI.EqualTo{Float64}, 27 | MOI.GreaterThan{Float64}, 28 | MOI.LessThan{Float64}])) 29 | 30 | MOIU.set_mock_optimize!(mock, 31 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1, 0], 32 | (MOI.VectorAffineFunction{Float64}, MOI.Nonpositives) => [[-1]], 33 | (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => [[0], [1]])) 34 | MOIT.linear2test(bridged_mock, config) 35 | 36 | MOIU.set_mock_optimize!(mock, 37 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0, 0]), 38 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, 0]), 39 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, -100])) 40 | MOIT.linear4test(bridged_mock, config) 41 | 42 | MOIU.set_mock_optimize!(mock, 43 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [4/3, 4/3]), 44 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [2, 0]), 45 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [4, 0]), 46 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [2])) 47 | MOIT.linear5test(mock, config) 48 | 49 | MOIU.set_mock_optimize!(mock, 50 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0, 0]), 51 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, 0]), 52 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, -100])) 53 | MOIT.linear6test(mock, config) 54 | 55 | MOIU.set_mock_optimize!(mock, 56 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0, 1/2, 1], 57 | (MOI.VectorAffineFunction{Float64}, MOI.Nonpositives) => [[-1], [-2]], 58 | (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => [[2], [0], [0]]), 59 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1], 60 | (MOI.VectorAffineFunction{Float64}, MOI.Nonpositives) => [[-1]], 61 | (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => [[0]])) 62 | # linear14 has double variable bounds for the z variable 63 | mock.eval_variable_constraint_dual = false 64 | MOIT.linear14test(bridged_mock, config) 65 | mock.eval_variable_constraint_dual = true 66 | 67 | mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, ones(3), 68 | (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[2]]) 69 | MOIT.psdt0vtest(bridged_mock, config) 70 | 71 | ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}}())) 72 | 73 | @testset "$attr" for attr in [MOI.ConstraintPrimalStart(), MOI.ConstraintDualStart()] 74 | @test MOI.supports(bridged_mock, attr, typeof(ci)) 75 | MOI.set(bridged_mock, attr, ci, 2.0) 76 | @test MOI.get(bridged_mock, attr, ci) == 2.0 77 | end 78 | 79 | test_delete_bridge(bridged_mock, ci, 3, 80 | ((MOI.VectorAffineFunction{Float64}, 81 | MOI.Zeros, 0),)) 82 | end 83 | -------------------------------------------------------------------------------- /src/FileFormats/MOF/MOF.jl: -------------------------------------------------------------------------------- 1 | module MOF 2 | 3 | import ..FileFormats 4 | import OrderedCollections 5 | import JSON 6 | import JSONSchema 7 | import MathOptInterface 8 | 9 | const MOI = MathOptInterface 10 | const Object = OrderedCollections.OrderedDict{String, Any} 11 | const SCHEMA_PATH = joinpath(@__DIR__, "v0.4.0.json") 12 | const VERSION = let data = JSON.parsefile(SCHEMA_PATH, use_mmap=false) 13 | VersionNumber( 14 | data["properties"]["version"]["properties"]["major"]["const"], 15 | data["properties"]["version"]["properties"]["minor"]["const"] 16 | ) 17 | end 18 | 19 | function _parse_mof_version(version::Object) 20 | return VersionNumber(version["major"], version["minor"]) 21 | end 22 | 23 | struct Nonlinear <: MOI.AbstractScalarFunction 24 | expr::Expr 25 | end 26 | Base.copy(nonlinear::Nonlinear) = Nonlinear(copy(nonlinear.expr)) 27 | 28 | MOI.Utilities.@model(InnerModel, 29 | (MOI.ZeroOne, MOI.Integer), 30 | (MOI.EqualTo, MOI.GreaterThan, MOI.LessThan, MOI.Interval, 31 | MOI.Semicontinuous, MOI.Semiinteger), 32 | (MOI.Reals, MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives, 33 | MOI.SecondOrderCone, MOI.RotatedSecondOrderCone, MOI.GeometricMeanCone, 34 | MOI.ExponentialCone, MOI.DualExponentialCone, MOI.NormOneCone, 35 | MOI.NormInfinityCone, MOI.NormSpectralCone, MOI.NormNuclearCone, 36 | MOI.RelativeEntropyCone, MOI.RootDetConeTriangle, MOI.RootDetConeSquare, 37 | MOI.LogDetConeTriangle, MOI.LogDetConeSquare, 38 | MOI.PositiveSemidefiniteConeTriangle, MOI.PositiveSemidefiniteConeSquare), 39 | (MOI.PowerCone, MOI.DualPowerCone, MOI.SOS1, MOI.SOS2), 40 | (Nonlinear,), 41 | (MOI.ScalarAffineFunction, MOI.ScalarQuadraticFunction), 42 | (MOI.VectorOfVariables,), 43 | (MOI.VectorAffineFunction, MOI.VectorQuadraticFunction) 44 | ) 45 | 46 | # IndicatorSet is handled by UniversalFallback. 47 | 48 | const Model = MOI.Utilities.UniversalFallback{InnerModel{Float64}} 49 | 50 | struct Options 51 | print_compact::Bool 52 | validate::Bool 53 | warn::Bool 54 | end 55 | 56 | function get_options(m::Model) 57 | return get(m.model.ext, :MOF_OPTIONS, Options(false, true, false)) 58 | end 59 | 60 | """ 61 | Model(; kwargs...) 62 | 63 | Create an empty instance of FileFormats.MOF.Model. 64 | 65 | Keyword arguments are: 66 | 67 | - `print_compact::Bool=false`: print the JSON file in a compact format without 68 | spaces or newlines. 69 | 70 | - `validate::Bool=true`: validate each file prior to reading against the MOF 71 | schema 72 | 73 | - `warn::Bool=false`: print a warning when variables or constraints are renamed 74 | """ 75 | function Model(; 76 | print_compact::Bool = false, validate::Bool = true, warn::Bool = false 77 | ) 78 | model = MOI.Utilities.UniversalFallback(InnerModel{Float64}()) 79 | model.model.ext[:MOF_OPTIONS] = Options(print_compact, validate, warn) 80 | return model 81 | end 82 | 83 | function Base.show(io::IO, ::Model) 84 | print(io, "A MathOptFormat Model") 85 | return 86 | end 87 | 88 | """ 89 | validate(filename::String) 90 | 91 | Validate that the MOF file `filename` conforms to the MOF JSON schema. Returns 92 | `nothing` if the file is valid, otherwise throws an error describing why the 93 | file is not valid. 94 | """ 95 | function validate(filename::String) 96 | FileFormats.compressed_open( 97 | filename, "r", FileFormats.AutomaticCompression() 98 | ) do io 99 | validate(io) 100 | end 101 | return 102 | end 103 | 104 | function validate(io::IO) 105 | object = JSON.parse(io) 106 | seekstart(io) 107 | mof_schema = JSONSchema.Schema(JSON.parsefile(SCHEMA_PATH, use_mmap=false)) 108 | if !JSONSchema.isvalid(object, mof_schema) 109 | error("Unable to read file because it does not conform to the MOF " * 110 | "schema: ", JSONSchema.diagnose(object, mof_schema)) 111 | end 112 | return 113 | end 114 | 115 | include("nonlinear.jl") 116 | 117 | include("read.jl") 118 | include("write.jl") 119 | 120 | end 121 | -------------------------------------------------------------------------------- /src/Bridges/bridge.jl: -------------------------------------------------------------------------------- 1 | """ 2 | AbstractBridge 3 | 4 | Represents a bridged constraint or variable in a 5 | [`MathOptInterface.Bridges.AbstractBridgeOptimizer`](@ref). It contains the 6 | indices of the variables and constraints that it has created in the model. These 7 | can be obtained using [`MathOptInterface.NumberOfVariables`](@ref), 8 | [`MathOptInterface.ListOfVariableIndices`](@ref), 9 | [`MathOptInterface.NumberOfConstraints`](@ref) and 10 | [`MathOptInterface.ListOfConstraintIndices`](@ref) using 11 | [`MathOptInterface.get`](@ref) with the bridge in place of the 12 | [`MathOptInterface.ModelLike`](@ref). Attributes of the bridged model such as 13 | [`MathOptInterface.ConstraintDual`](@ref) and 14 | [`MathOptInterface.ConstraintPrimal`](@ref), can be obtained using 15 | [`MathOptInterface.get`](@ref) with the bridge in place of the constraint index. 16 | These calls are used by the 17 | [`MathOptInterface.Bridges.AbstractBridgeOptimizer`](@ref) to communicate with 18 | the bridge so they should be implemented by the bridge. 19 | """ 20 | abstract type AbstractBridge end 21 | 22 | """ 23 | MOI.get(b::AbstractBridge, ::MOI.NumberOfConstraints{F, S}) where {F, S} 24 | 25 | The number of constraints of the type `F`-in-`S` created by the bridge `b` in the model. 26 | """ 27 | MOI.get(::AbstractBridge, ::MOI.NumberOfConstraints) = 0 28 | 29 | """ 30 | MOI.get(b::AbstractBridge, ::MOI.ListOfConstraintIndices{F, S}) where {F, S} 31 | 32 | A `Vector{ConstraintIndex{F,S}}` with indices of all constraints of 33 | type `F`-in`S` created by the bride `b` in the model (i.e., of length equal to 34 | the value of `NumberOfConstraints{F,S}()`). 35 | """ 36 | function MOI.get(::AbstractBridge, 37 | ::MOI.ListOfConstraintIndices{F, S}) where {F, S} 38 | return MOI.ConstraintIndex{F, S}[] 39 | end 40 | 41 | """ 42 | MOI.supports(model::MOI.ModelLike, attr::MOI.AbstractConstraintAttribute, 43 | BT::Type{<:AbstractBridge}) 44 | 45 | Return a `Bool` indicating whether `BT` supports setting `attr` to `model`. 46 | """ 47 | function MOI.supports(::MOI.ModelLike, ::MOI.AbstractConstraintAttribute, 48 | ::Type{<:AbstractBridge}) 49 | return false 50 | end 51 | 52 | """ 53 | function MOI.get(model::MOI.ModelLike, attr::MOI.AbstractConstraintAttribute, 54 | bridge::AbstractBridge) 55 | 56 | Return the value of the attribute `attr` of the model `model` for the 57 | constraint bridged by `bridge`. 58 | """ 59 | function MOI.get(::MOI.ModelLike, attr::MOI.AbstractConstraintAttribute, 60 | bridge::AbstractBridge) 61 | throw(ArgumentError("Bridge of type `$(typeof(bridge))` does not support accessing the attribute `$attr`.")) 62 | end 63 | 64 | """ 65 | function MOI.set(model::MOI.ModelLike, attr::MOI.AbstractConstraintAttribute, 66 | bridge::AbstractBridge, value) 67 | 68 | Set the value of the attribute `attr` of the model `model` for the 69 | constraint bridged by `bridge`. 70 | """ 71 | function MOI.set(model::MOI.ModelLike, attr::MOI.AbstractConstraintAttribute, 72 | bridge::AbstractBridge, value) 73 | if MOI.is_copyable(attr) && !MOI.supports(model, attr, typeof(bridge)) 74 | throw(MOI.UnsupportedAttribute(attr)) 75 | else 76 | throw(MOI.SetAttributeNotAllowed(attr)) 77 | end 78 | end 79 | 80 | """ 81 | added_constrained_variable_types(BT::Type{<:Variable.AbstractBridge})::Vector{Tuple{DataType}} 82 | 83 | Return a list of the types of constrained variables that bridges of concrete 84 | type `BT` add. This is used by the [`LazyBridgeOptimizer`](@ref). 85 | """ 86 | function added_constrained_variable_types end 87 | 88 | """ 89 | added_constraint_types(BT::Type{<:Constraint.AbstractBridge})::Vector{Tuple{DataType, DataType}} 90 | 91 | Return a list of the types of constraints that bridges of concrete type `BT` 92 | add. This is used by the [`LazyBridgeOptimizer`](@ref). 93 | """ 94 | function added_constraint_types end 95 | 96 | """ 97 | set_objective_function_type(BT::Type{<:Objective.AbstractBridge})::Type{<:MOI.AbstractScalarFunction} 98 | 99 | Return the type of objective function that bridges of concrete type `BT` 100 | set. This is used by the [`LazyBridgeOptimizer`](@ref). 101 | """ 102 | function set_objective_function_type end 103 | -------------------------------------------------------------------------------- /test/Bridges/Constraint/quad_to_soc.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | 3 | using MathOptInterface 4 | const MOI = MathOptInterface 5 | const MOIT = MathOptInterface.Test 6 | const MOIU = MathOptInterface.Utilities 7 | const MOIB = MathOptInterface.Bridges 8 | 9 | include("../utilities.jl") 10 | 11 | mock = MOIU.MockOptimizer(MOIU.Model{Float64}()) 12 | config = MOIT.TestConfig() 13 | 14 | @testset "QuadtoSOC" begin 15 | bridged_mock = MOIB.Constraint.QuadtoSOC{Float64}(mock) 16 | @testset "Error for non-convex quadratic constraints" begin 17 | x = MOI.add_variable(bridged_mock) 18 | @test_throws ErrorException begin 19 | MOI.add_constraint(bridged_mock, 20 | MOI.ScalarQuadraticFunction(MOI.ScalarAffineTerm{Float64}[], 21 | [MOI.ScalarQuadraticTerm(1.0, x, x)], 22 | 0.0), 23 | MOI.GreaterThan(0.0)) 24 | end 25 | @test_throws ErrorException begin 26 | MOI.add_constraint(bridged_mock, 27 | MOI.ScalarQuadraticFunction(MOI.ScalarAffineTerm{Float64}[], 28 | [MOI.ScalarQuadraticTerm(-1.0, x, x)], 29 | 0.0), 30 | MOI.LessThan(0.0)) 31 | end 32 | end 33 | @testset "Quadratic constraints with 2 variables" begin 34 | MOIU.set_mock_optimize!(mock, 35 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, 36 | MOI.OPTIMAL, 37 | (MOI.FEASIBLE_POINT, [0.5, 0.5]) 38 | ), 39 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, 40 | MOI.OPTIMAL, 41 | (MOI.FEASIBLE_POINT, [0.5, (√13 - 1)/4]) 42 | ) 43 | ) 44 | MOIT.solve_qcp_edge_cases(bridged_mock, config) 45 | ci = first(MOI.get(mock, 46 | MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, 47 | MOI.RotatedSecondOrderCone}())) 48 | x, y = MOI.get(mock, MOI.ListOfVariableIndices()) 49 | # The matrix is 50 | # 2 1 51 | # 1 2 52 | # for which the cholesky factorization is U' * U with U = 53 | # √2 √2/2 54 | # . √3/√2 55 | expected = MOI.VectorAffineFunction{Float64}(MOI.VectorAffineTerm.([3, 3, 4], 56 | MOI.ScalarAffineTerm.([√2, √2/2, √3/√2], 57 | [x, y, y])), 58 | [1.0, 1.0, 0.0, 0.0]) 59 | @test MOI.get(mock, MOI.ConstraintFunction(), ci) ≈ expected 60 | end 61 | @testset "QCP tests" begin 62 | MOIU.set_mock_optimize!(mock, 63 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1/2, 7/4], 64 | (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => [zeros(2)], 65 | (MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone) => [[0.25, 1.0, -1/√2]])) 66 | MOIT.qcp1test(bridged_mock, config) 67 | MOIU.set_mock_optimize!(mock, 68 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [√2], 69 | (MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone) => [[1/√2, 1/(2 * √2), -1/√2]])) 70 | MOIT.qcp2test(bridged_mock, config) 71 | MOIT.qcp3test(bridged_mock, config) 72 | @testset "Bridge deletion" begin 73 | ci = first(MOI.get(bridged_mock, 74 | MOI.ListOfConstraintIndices{MOI.ScalarQuadraticFunction{Float64}, 75 | MOI.LessThan{Float64}}())) 76 | test_delete_bridge(bridged_mock, ci, 1, ((MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone, 0),)) 77 | end 78 | MOIU.set_mock_optimize!(mock, 79 | (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 1.0], 80 | (MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone) => [[1.0, 1/3, -1/√2, -1/√6]])) 81 | MOIT.qcp4test(bridged_mock, config) 82 | MOIT.qcp5test(bridged_mock, config) 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /src/Bridges/Variable/flip_sign.jl: -------------------------------------------------------------------------------- 1 | """ 2 | FlipSignBridge{T, S1, S2} 3 | 4 | Bridge constrained variables in `S1` into constrained variables in `S2` by 5 | multiplying the variables by `-1` and taking the point reflection of the set 6 | across the origin. The flipped `MOI.VectorOfVariables`-in-`S` constraint is 7 | stored in the `flipped_constraint` field by convention. 8 | """ 9 | abstract type FlipSignBridge{ 10 | T, S1<:MOI.AbstractSet, S2<:MOI.AbstractSet} <: AbstractBridge end 11 | 12 | function supports_constrained_variable( 13 | ::Type{<:FlipSignBridge{T, S1}}, ::Type{S1}) where {T, S1<:MOI.AbstractVectorSet} 14 | return true 15 | end 16 | function MOIB.added_constrained_variable_types( 17 | ::Type{<:FlipSignBridge{T, S1, S2}}) where {T, S1, S2} 18 | return [(S2,)] 19 | end 20 | function MOIB.added_constraint_types(::Type{<:FlipSignBridge}) 21 | return Tuple{DataType, DataType}[] 22 | end 23 | 24 | # Attributes, Bridge acting as a model 25 | function MOI.get(bridge::FlipSignBridge, ::MOI.NumberOfVariables) 26 | return length(bridge.flipped_variables) 27 | end 28 | function MOI.get(bridge::FlipSignBridge, ::MOI.ListOfVariableIndices) 29 | return bridge.flipped_variables 30 | end 31 | function MOI.get(::FlipSignBridge{T, S1, S2}, 32 | ::MOI.NumberOfConstraints{MOI.VectorOfVariables, S2}) where {T, S1, S2<:MOI.AbstractVectorSet} 33 | return 1 34 | end 35 | function MOI.get(bridge::FlipSignBridge{T, S1, S2}, 36 | ::MOI.ListOfConstraintIndices{MOI.VectorOfVariables, S2}) where {T, S1, S2<:MOI.AbstractVectorSet} 37 | return [bridge.flipped_constraint] 38 | end 39 | 40 | # References 41 | function MOI.delete(model::MOI.ModelLike, bridge::FlipSignBridge) 42 | MOI.delete(model, bridge.flipped_variables) 43 | end 44 | 45 | function MOI.delete(model::MOI.ModelLike, bridge::FlipSignBridge, i::IndexInVector) 46 | MOI.delete(model, bridge.flipped_variables[i.value]) 47 | deleteat!(bridge.flipped_variables, i.value) 48 | end 49 | 50 | # Attributes, Bridge acting as a constraint 51 | 52 | function MOI.get(model::MOI.ModelLike, 53 | attr::Union{MOI.ConstraintPrimal, MOI.ConstraintDual}, 54 | bridge::FlipSignBridge) 55 | return -MOI.get(model, attr, bridge.flipped_constraint) 56 | end 57 | 58 | function MOI.get(model::MOI.ModelLike, 59 | attr::Union{MOI.VariablePrimal, MOI.VariablePrimalStart}, 60 | bridge::FlipSignBridge, i::IndexInVector) 61 | return -MOI.get(model, attr, bridge.flipped_variables[i.value]) 62 | end 63 | 64 | function MOIB.bridged_function(bridge::FlipSignBridge{T}, i::IndexInVector) where T 65 | func = MOI.SingleVariable(bridge.flipped_variables[i.value]) 66 | return MOIU.operate(-, T, func) 67 | end 68 | function unbridged_map(bridge::FlipSignBridge{T}, vi::MOI.VariableIndex, 69 | i::IndexInVector) where T 70 | func = MOIU.operate(-, T, MOI.SingleVariable(vi)) 71 | return (bridge.flipped_variables[i.value] => func,) 72 | end 73 | 74 | function MOI.supports(model::MOI.ModelLike, attr::MOI.VariablePrimalStart, 75 | ::Type{<:FlipSignBridge}) 76 | return MOI.supports(model, attr, MOI.VariableIndex) 77 | end 78 | function MOI.set(model::MOI.ModelLike, attr::MOI.VariablePrimalStart, 79 | bridge::FlipSignBridge, value, i::IndexInVector) 80 | MOI.set(model, attr, bridge.flipped_variables[i.value], -value) 81 | end 82 | 83 | """ 84 | NonposToNonnegBridge{T, F<:MOI.AbstractVectorFunction, G<:MOI.AbstractVectorFunction} <: 85 | FlipSignBridge{T, MOI.Nonpositives, MOI.Nonnegatives, F, G} 86 | 87 | Transforms constrained variables in `Nonpositives` into constrained variables in 88 | `Nonnegatives`. 89 | """ 90 | struct NonposToNonnegBridge{T} <: FlipSignBridge{T, MOI.Nonpositives, MOI.Nonnegatives} 91 | flipped_variables::Vector{MOI.VariableIndex} 92 | flipped_constraint::MOI.ConstraintIndex{MOI.VectorOfVariables, MOI.Nonnegatives} 93 | end 94 | function bridge_constrained_variable(::Type{NonposToNonnegBridge{T}}, 95 | model::MOI.ModelLike, 96 | set::MOI.Nonpositives) where T 97 | flipped_variables, flipped_constraint = MOI.add_constrained_variables( 98 | model, MOI.Nonnegatives(set.dimension)) 99 | return NonposToNonnegBridge{T}(flipped_variables, flipped_constraint) 100 | end 101 | -------------------------------------------------------------------------------- /src/MathOptInterface.jl: -------------------------------------------------------------------------------- 1 | module MathOptInterface 2 | 3 | """ 4 | ModelLike 5 | 6 | Abstract supertype for objects that implement the "Model" interface for defining 7 | an optimization problem. 8 | """ 9 | abstract type ModelLike end 10 | 11 | # This allows to use `ModelLike`s in broadcast calls without the need to 12 | # embed it in a `Ref` 13 | Base.broadcastable(model::ModelLike) = Ref(model) 14 | 15 | Base.show(io::IO, model::ModelLike) = Utilities.print_with_acronym(io, summary(model)) 16 | 17 | 18 | """ 19 | AbstractOptimizer 20 | 21 | Abstract supertype for objects representing an instance of an optimization problem 22 | tied to a particular solver. This is typically a solver's in-memory representation. 23 | In addition to `ModelLike`, `AbstractOptimizer` objects let you solve the 24 | model and query the solution. 25 | """ 26 | abstract type AbstractOptimizer <: ModelLike end 27 | 28 | """ 29 | optimize!(optimizer::AbstractOptimizer) 30 | 31 | Start the solution procedure. 32 | """ 33 | function optimize! end 34 | 35 | """ 36 | write_to_file(model::ModelLike, filename::String) 37 | 38 | Writes the current model data to the given file. 39 | Supported file types depend on the model type. 40 | """ 41 | function write_to_file end 42 | 43 | """ 44 | read_from_file(model::ModelLike, filename::String) 45 | 46 | Read the file `filename` into the model `model`. If `model` is non-empty, this may 47 | throw an error. 48 | 49 | Supported file types depend on the model type. 50 | 51 | ### Note 52 | 53 | Once the contents of the file are loaded into the model, users can query the variables via 54 | `get(model, ListOfVariableIndices())`. However, some filetypes, such as LP files, do not 55 | maintain an explicit ordering of the variables. Therefore, the returned list may be in an 56 | arbitrary order. To avoid depending on the order of the indices, users should look up each 57 | variable index by name: `get(model, VariableIndex, "name")`. 58 | """ 59 | function read_from_file end 60 | 61 | """ 62 | is_empty(model::ModelLike) 63 | 64 | Returns `false` if the `model` has any model attribute set or has any variables or constraints. 65 | Note that an empty model can have optimizer attributes set. 66 | """ 67 | function is_empty end 68 | 69 | """ 70 | empty!(model::ModelLike) 71 | 72 | Empty the model, that is, remove all variables, constraints and model attributes but not optimizer attributes. 73 | """ 74 | function empty! end 75 | 76 | """ 77 | copy_to(dest::ModelLike, src::ModelLike; copy_names=true, warn_attributes=true) 78 | 79 | Copy the model from `src` into `dest`. The target `dest` is emptied, and all 80 | previous indices to variables or constraints in `dest` are invalidated. Returns 81 | a dictionary-like object that translates variable and constraint indices from 82 | the `src` model to the corresponding indices in the `dest` model. 83 | 84 | If `copy_names` is `false`, the `Name`, `VariableName` and `ConstraintName` 85 | attributes are not copied even if they are set in `src`. If a constraint that 86 | is copied from `src` is not supported by `dest` then an 87 | [`UnsupportedConstraint`](@ref) error is thrown. Similarly, if a model, variable 88 | or constraint attribute that is copied from `src` is not supported by `dest` 89 | then an [`UnsupportedAttribute`](@ref) error is thrown. Unsupported *optimizer* 90 | attributes are treated differently: 91 | 92 | * If `warn_attributes` is `true`, a warning is displayed, otherwise, 93 | * the attribute is silently ignored. 94 | 95 | ### Example 96 | 97 | ```julia 98 | # Given empty `ModelLike` objects `src` and `dest`. 99 | 100 | x = add_variable(src) 101 | 102 | is_valid(src, x) # true 103 | is_valid(dest, x) # false (`dest` has no variables) 104 | 105 | index_map = copy_to(dest, src) 106 | is_valid(dest, x) # false (unless index_map[x] == x) 107 | is_valid(dest, index_map[x]) # true 108 | ``` 109 | """ 110 | function copy_to end 111 | 112 | include("error.jl") 113 | include("indextypes.jl") 114 | include("functions.jl") 115 | include("sets.jl") 116 | include("attributes.jl") 117 | include("constraints.jl") 118 | include("modifications.jl") 119 | include("variables.jl") 120 | include("nlp.jl") 121 | 122 | # submodules 123 | include("Utilities/Utilities.jl") # MOI.Utilities 124 | include("Test/Test.jl") # MOI.Test 125 | include("Bridges/Bridges.jl") # MOI.Bridges 126 | include("Benchmarks/Benchmarks.jl") 127 | include("FileFormats/FileFormats.jl") 128 | 129 | include("instantiate.jl") 130 | 131 | end 132 | -------------------------------------------------------------------------------- /test/Bridges/Constraint/relentr_to_exp.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | 3 | using MathOptInterface 4 | const MOI = MathOptInterface 5 | const MOIT = MathOptInterface.Test 6 | const MOIU = MathOptInterface.Utilities 7 | const MOIB = MathOptInterface.Bridges 8 | 9 | include("../utilities.jl") 10 | 11 | mock = MOIU.MockOptimizer(MOIU.UniversalFallback(MOIU.Model{Float64}())) 12 | config = MOIT.TestConfig() 13 | 14 | @testset "RelativeEntropy" begin 15 | bridged_mock = MOIB.Constraint.RelativeEntropy{Float64}(mock) 16 | 17 | MOIT.basic_constraint_tests(bridged_mock, config, 18 | include = [(F, MOI.RelativeEntropyCone) for F in [ 19 | MOI.VectorOfVariables, MOI.VectorAffineFunction{Float64}, MOI.VectorQuadraticFunction{Float64} 20 | ]]) 21 | 22 | entr1 = 2 * log(2) 23 | entr2 = 3 * log(3 / 5) 24 | var_primal = [entr1 + entr2, entr1, entr2] 25 | exps_duals = [[-1, log(0.5) - 1, 2], [-1, log(5 / 3) - 1, 0.6]] 26 | mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, var_primal, 27 | (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => [1.0], 28 | (MOI.VectorAffineFunction{Float64}, MOI.ExponentialCone) => exps_duals) 29 | 30 | MOIT.relentr1test(bridged_mock, config) 31 | 32 | var_names = ["u"] 33 | MOI.set(bridged_mock, MOI.VariableName(), MOI.get(bridged_mock, MOI.ListOfVariableIndices()), var_names) 34 | 35 | greater = MOI.get(mock, MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}}()) 36 | exps = MOI.get(mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.ExponentialCone}()) 37 | (y1, y2) = MOI.get(mock, MOI.ListOfVariableIndices())[2:3] 38 | 39 | @testset "Test mock model" begin 40 | MOI.set(mock, MOI.VariableName(), y1, "y1") 41 | MOI.set(mock, MOI.VariableName(), y2, "y2") 42 | @test length(greater) == 1 43 | MOI.set(mock, MOI.ConstraintName(), greater[1], "greater") 44 | @test length(exps) == 2 45 | MOI.set(mock, MOI.ConstraintName(), exps[1], "exps1") 46 | MOI.set(mock, MOI.ConstraintName(), exps[2], "exps2") 47 | 48 | s = """ 49 | variables: u, y1, y2 50 | greater: u + -1.0y1 + -1.0y2 >= 0.0 51 | exps1: [-1.0y1, 2.0, 1.0] in MathOptInterface.ExponentialCone() 52 | exps2: [-1.0y2, 3.0, 5.0] in MathOptInterface.ExponentialCone() 53 | minobjective: u 54 | """ 55 | model = MOIU.Model{Float64}() 56 | MOIU.loadfromstring!(model, s) 57 | MOIU.test_models_equal(mock, model, vcat(var_names, "y1", "y2"), ["greater", "exps1", "exps2"]) 58 | end 59 | 60 | @testset "Test bridged model" begin 61 | relentr = MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.RelativeEntropyCone}()) 62 | @test length(relentr) == 1 63 | MOI.set(bridged_mock, MOI.ConstraintName(), relentr[1], "relentr") 64 | 65 | s = """ 66 | variables: u 67 | relentr: [u, 1.0, 5.0, 2.0, 3.0] in MathOptInterface.RelativeEntropyCone(5) 68 | minobjective: u 69 | """ 70 | model = MOIU.Model{Float64}() 71 | MOIU.loadfromstring!(model, s) 72 | MOIU.test_models_equal(bridged_mock, model, var_names, ["relentr"]) 73 | end 74 | 75 | ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.RelativeEntropyCone}())) 76 | 77 | @testset "$attr" for attr in [MOI.ConstraintPrimalStart(), MOI.ConstraintDualStart()] 78 | @test MOI.supports(bridged_mock, attr, typeof(ci)) 79 | value = (attr isa MOI.ConstraintPrimalStart) ? [entr1 + entr2 + 1, 1, 5, 2, 3] : [2, 2, 0.6, log(0.5) - 1, log(5 / 3) - 1] 80 | MOI.set(bridged_mock, attr, ci, value) 81 | @test MOI.get(bridged_mock, attr, ci) ≈ value 82 | if attr isa MOI.ConstraintPrimalStart 83 | @test MOI.get(mock, MOI.VariablePrimalStart(), y1) == entr1 84 | @test MOI.get(mock, MOI.VariablePrimalStart(), y2) == entr2 85 | @test MOI.get(mock, attr, greater[1]) == 1 86 | @test MOI.get(mock, attr, exps[1]) == [-entr1, 2, 1] 87 | @test MOI.get(mock, attr, exps[2]) == [-entr2, 3, 5] 88 | else 89 | @test MOI.get(mock, attr, greater[1]) == 2 90 | for i in 1:2 91 | @test MOI.get(mock, attr, exps[i]) == [value[1], value[3 + i], value[1 + i]] 92 | end 93 | end 94 | end 95 | 96 | test_delete_bridge(bridged_mock, ci, 1, ( 97 | (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}, 0), 98 | (MOI.VectorAffineFunction{Float64}, MOI.ExponentialCone, 0))) 99 | end 100 | -------------------------------------------------------------------------------- /src/Bridges/Constraint/set_map.jl: -------------------------------------------------------------------------------- 1 | abstract type SetMapBridge{T, S2, S1, F, G} <: AbstractBridge end 2 | 3 | function bridge_constraint( 4 | BT::Type{<:SetMapBridge{T, S2, S1, F, G}}, model::MOI.ModelLike, 5 | func::G, set::S1) where {T, S2, S1, F, G} 6 | mapped_func = map_function(BT, func) 7 | constraint = MOI.add_constraint(model, mapped_func, map_set(BT, set)) 8 | return BT(constraint) 9 | end 10 | 11 | function MOI.supports_constraint( 12 | ::Type{<:SetMapBridge{T, S2, S1}}, ::Type{<:MOI.AbstractScalarFunction}, 13 | ::Type{S1}) where {T, S2, S1<:MOI.AbstractScalarSet} 14 | return true 15 | end 16 | function MOI.supports_constraint( 17 | ::Type{<:SetMapBridge{T, S2, S1}}, ::Type{<:MOI.AbstractVectorFunction}, 18 | ::Type{S1}) where {T, S2, S1<:MOI.AbstractVectorSet} 19 | return true 20 | end 21 | MOIB.added_constrained_variable_types(::Type{<:SetMapBridge}) = Tuple{DataType}[] 22 | function MOIB.added_constraint_types(::Type{<:SetMapBridge{T, S2, S1, F}}) where {T, S2, S1, F} 23 | return [(F, S2)] 24 | end 25 | 26 | # Attributes, Bridge acting as a model 27 | function MOI.get(::SetMapBridge{T, S2, S1, F}, 28 | ::MOI.NumberOfConstraints{F, S2}) where {T, S2, S1, F} 29 | return 1 30 | end 31 | function MOI.get(bridge::SetMapBridge{T, S2, S1, F}, 32 | ::MOI.ListOfConstraintIndices{F, S2}) where {T, S2, S1, F} 33 | return [bridge.constraint] 34 | end 35 | 36 | # References 37 | function MOI.delete(model::MOI.ModelLike, bridge::SetMapBridge) 38 | MOI.delete(model, bridge.constraint) 39 | end 40 | 41 | # Attributes, Bridge acting as a constraint 42 | function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintFunction, 43 | bridge::SetMapBridge{T, S2, S1, F, G}) where {T, S2, S1, F, G} 44 | mapped_func = MOI.get(model, attr, bridge.constraint) 45 | func = inverse_map_function(typeof(bridge), mapped_func) 46 | return MOIU.convert_approx(G, func) 47 | end 48 | function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintSet, bridge::SetMapBridge) 49 | set = MOI.get(model, attr, bridge.constraint) 50 | return inverse_map_set(typeof(bridge), set) 51 | end 52 | function MOI.set(model::MOI.ModelLike, attr::MOI.ConstraintSet, 53 | bridge::SetMapBridge{T, S2, S1}, new_set::S1) where {T, S2, S1} 54 | MOI.set(model, attr, bridge.constraint, map_set(typeof(bridge), new_set)) 55 | end 56 | 57 | function MOI.supports( 58 | model::MOI.ModelLike, 59 | attr::Union{MOI.ConstraintPrimalStart, MOI.ConstraintDualStart}, 60 | ::Type{<:SetMapBridge{T, S2, S1, F}}) where {T, S2, S1, F} 61 | 62 | return MOI.supports(model, attr, MOI.ConstraintIndex{F, S2}) 63 | end 64 | function MOI.get(model::MOI.ModelLike, attr::Union{MOI.ConstraintPrimal, MOI.ConstraintPrimalStart}, 65 | bridge::SetMapBridge) 66 | value = MOI.get(model, attr, bridge.constraint) 67 | return inverse_map_function(typeof(bridge), value) 68 | end 69 | function MOI.set(model::MOI.ModelLike, attr::MOI.ConstraintPrimalStart, 70 | bridge::SetMapBridge, value) 71 | mapped_value = map_function(typeof(bridge), value) 72 | MOI.set(model, attr, bridge.constraint, mapped_value) 73 | end 74 | function MOI.get(model::MOI.ModelLike, attr::Union{MOI.ConstraintDual, MOI.ConstraintDualStart}, 75 | bridge::SetMapBridge) 76 | value = MOI.get(model, attr, bridge.constraint) 77 | return adjoint_map_function(typeof(bridge), value) 78 | end 79 | function MOI.set(model::MOI.ModelLike, attr::MOI.ConstraintDualStart, 80 | bridge::SetMapBridge, value) 81 | mapped_value = inverse_adjoint_map_function(typeof(bridge), value) 82 | MOI.set(model, attr, bridge.constraint, mapped_value) 83 | end 84 | 85 | function MOI.modify(model::MOI.ModelLike, bridge::SetMapBridge, 86 | change::MOI.VectorConstantChange) 87 | # By linearity of the map, we can just change the constant 88 | constant = map_function(typeof(bridge), change.new_constant) 89 | MOI.modify(model, bridge.constraint, MOI.VectorConstantChange(constant)) 90 | end 91 | 92 | include("flip_sign.jl") 93 | const GreaterToLess{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{GreaterToLessBridge{T}, OT} 94 | const LessToGreater{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{LessToGreaterBridge{T}, OT} 95 | const NonnegToNonpos{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{NonnegToNonposBridge{T}, OT} 96 | const NonposToNonneg{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{NonposToNonnegBridge{T}, OT} 97 | include("rsoc.jl") 98 | const RSOC{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{RSOCBridge{T}, OT} 99 | const SOCR{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{SOCRBridge{T}, OT} 100 | -------------------------------------------------------------------------------- /src/indextypes.jl: -------------------------------------------------------------------------------- 1 | # Index types 2 | 3 | """ 4 | ConstraintIndex{F, S} 5 | 6 | A type-safe wrapper for `Int64` for use in referencing `F`-in-`S` constraints in 7 | a model. 8 | The parameter `F` is the type of the function in the constraint, and the 9 | parameter `S` is the type of set in the constraint. To allow for deletion, 10 | indices need not be consecutive. Indices within a constraint type (i.e. `F`-in-`S`) 11 | must be unique, but non-unique indices across different constraint types are allowed. 12 | If `F` is [`SingleVariable`](@ref) then the index is equal to the index of the 13 | variable. That is for an `index::ConstraintIndex{SingleVariable}`, we always 14 | have 15 | ```julia 16 | index.value == MOI.get(model, MOI.ConstraintFunction(), index).variable.value 17 | ``` 18 | """ 19 | struct ConstraintIndex{F, S} 20 | value::Int64 21 | end 22 | 23 | """ 24 | VariableIndex 25 | 26 | A type-safe wrapper for `Int64` for use in referencing variables in a model. 27 | To allow for deletion, indices need not be consecutive. 28 | """ 29 | struct VariableIndex 30 | value::Int64 31 | end 32 | 33 | # The default hash is slow. It's important for the performance of dictionaries 34 | # of VariableIndices to define our own. 35 | # https://github.com/JuliaLang/julia/issues/10208 36 | Base.hash(v::VariableIndex, h::UInt) = hash(v.value, h) 37 | 38 | # No need to define isequal because the default matches our implementation of 39 | # hash. 40 | 41 | const Index = Union{ConstraintIndex,VariableIndex} 42 | 43 | """ 44 | struct InvalidIndex{IndexType<:Index} <: Exception 45 | index::IndexType 46 | end 47 | 48 | An error indicating that the index `index` is invalid. 49 | """ 50 | struct InvalidIndex{IndexType<:Index} <: Exception 51 | index::IndexType 52 | end 53 | 54 | function Base.showerror(io::IO, err::InvalidIndex) 55 | print("The index $(err.index) is invalid. Note that an index becomes invalid after it has been deleted.") 56 | end 57 | 58 | """ 59 | is_valid(model::ModelLike, index::Index)::Bool 60 | 61 | Return a `Bool` indicating whether this index refers to a valid object in the model `model`. 62 | """ 63 | is_valid(model::ModelLike, ref::Index) = false 64 | 65 | """ 66 | throw_if_not_valid(model::ModelLike, index::Index) 67 | 68 | Throw an `InvalidIndex(index)` error if `MOI.is_valid(model, index)` returns 69 | `false`. 70 | """ 71 | function throw_if_not_valid(model::ModelLike, index::Index) 72 | if !is_valid(model, index) 73 | throw(InvalidIndex(index)) 74 | end 75 | end 76 | 77 | """ 78 | struct DeleteNotAllowed{IndexType <: Index} <: NotAllowedError 79 | index::IndexType 80 | message::String 81 | end 82 | 83 | An error indicating that the index `index` cannot be deleted. 84 | """ 85 | struct DeleteNotAllowed{IndexType <: Index} <: NotAllowedError 86 | index::IndexType 87 | message::String 88 | end 89 | DeleteNotAllowed(index::Index) = DeleteNotAllowed(index, "") 90 | 91 | function operation_name(err::DeleteNotAllowed) 92 | return "Deleting the index $(err.index)" 93 | end 94 | 95 | """ 96 | delete(model::ModelLike, index::Index) 97 | 98 | Delete the referenced object from the model. Throw [`DeleteNotAllowed`](@ref) if 99 | if `index` cannot be deleted. 100 | 101 | The following modifications also take effect if `Index` is [`VariableIndex`](@ref): 102 | * If `index` used in the objective function, it is removed from the function, 103 | i.e., it is substituted for zero. 104 | * For each `func`-in-`set` constraint of the model: 105 | - If `func isa SingleVariable` and `func.variable == index` then the 106 | constraint is deleted. 107 | - If `func isa VectorOfVariables` and `index in func.variable` then 108 | * if `length(func.variable) == 1` is one, the constraint is deleted; 109 | * if `length(func.variable) > 1` and `supports_dimension_update(set)` then 110 | then the variable is removed from `func` and `set` is replaced by 111 | `update_dimension(set, MOI.dimension(set) - 1)`. 112 | * Otherwise, a [`DeleteNotAllowed`](@ref) error is thrown. 113 | - Otherwise, the variable is removed from `func`, i.e., it is substituted for 114 | zero. 115 | """ 116 | delete(model::ModelLike, index::Index) = throw(DeleteNotAllowed(index)) 117 | 118 | """ 119 | delete(model::ModelLike, indices::Vector{R<:Index}) where {R} 120 | 121 | Delete the referenced objects in the vector `indices` from the model. 122 | It may be assumed that `R` is a concrete type. The default fallback sequentially 123 | deletes the individual items in `indices`, although specialized implementations 124 | may be more efficient. 125 | """ 126 | function delete(model::ModelLike, indices::Vector{<:Index}) 127 | for index in indices 128 | delete(model, index) 129 | end 130 | end 131 | -------------------------------------------------------------------------------- /src/Test/UnitTests/attributes.jl: -------------------------------------------------------------------------------- 1 | """ 2 | solver_name(model::MOI.ModelLike, config::TestConfig) 3 | 4 | Test that the [`MOI.SolverName`](@ref) attribute is implemented for `model`. 5 | """ 6 | function solver_name(model::MOI.ModelLike, config::TestConfig) 7 | if config.solve 8 | @test MOI.get(model, MOI.SolverName()) isa AbstractString 9 | end 10 | end 11 | unittests["solver_name"] = solver_name 12 | 13 | """ 14 | silent(model::MOI.ModelLike, config::TestConfig) 15 | 16 | Test that the [`MOI.Silent`](@ref) attribute is implemented for `model`. 17 | """ 18 | function silent(model::MOI.ModelLike, config::TestConfig) 19 | if config.solve 20 | @test MOI.supports(model, MOI.Silent()) 21 | # Get the current value to restore it at the end of the test 22 | value = MOI.get(model, MOI.Silent()) 23 | MOI.set(model, MOI.Silent(), !value) 24 | @test !value == MOI.get(model, MOI.Silent()) 25 | # Check that `set` does not just take `!` of the current value 26 | MOI.set(model, MOI.Silent(), !value) 27 | @test !value == MOI.get(model, MOI.Silent()) 28 | MOI.set(model, MOI.Silent(), value) 29 | @test value == MOI.get(model, MOI.Silent()) 30 | end 31 | end 32 | unittests["silent"] = silent 33 | 34 | """ 35 | time_limit_sec(model::MOI.ModelLike, config::TestConfig) 36 | 37 | Test that the [`MOI.TimeLimitSec`](@ref) attribute is implemented for `model`. 38 | """ 39 | function time_limit_sec(model::MOI.ModelLike, config::TestConfig) 40 | if config.solve 41 | @test MOI.supports(model, MOI.TimeLimitSec()) 42 | # Get the current value to restore it at the end of the test 43 | value = MOI.get(model, MOI.TimeLimitSec()) 44 | MOI.set(model, MOI.TimeLimitSec(), 0.0) 45 | @test MOI.get(model, MOI.TimeLimitSec()) == 0.0 46 | MOI.set(model, MOI.TimeLimitSec(), 1.0) 47 | @test MOI.get(model, MOI.TimeLimitSec()) == 1.0 48 | MOI.set(model, MOI.TimeLimitSec(), value) 49 | @test value == MOI.get(model, MOI.TimeLimitSec()) # Equality should hold 50 | end 51 | end 52 | unittests["time_limit_sec"] = time_limit_sec 53 | 54 | """ 55 | number_threads(model::MOI.ModelLike, config::TestConfig) 56 | 57 | Test that the [`MOI.NumberOfThreads`](@ref) attribute is implemented for `model`. 58 | """ 59 | function number_threads(model::MOI.ModelLike, config::TestConfig) 60 | if config.solve 61 | @test MOI.supports(model, MOI.NumberOfThreads()) 62 | # Get the current value to restore it at the end of the test 63 | value = MOI.get(model, MOI.NumberOfThreads()) 64 | MOI.set(model, MOI.NumberOfThreads(), 1) 65 | @test MOI.get(model, MOI.NumberOfThreads()) == 1 66 | MOI.set(model, MOI.NumberOfThreads(), 3) 67 | @test MOI.get(model, MOI.NumberOfThreads()) == 3 68 | MOI.set(model, MOI.NumberOfThreads(), value) 69 | @test value == MOI.get(model, MOI.NumberOfThreads()) 70 | end 71 | end 72 | unittests["number_threads"] = number_threads 73 | 74 | """ 75 | raw_status_string(model::MOI.ModelLike, config::TestConfig) 76 | 77 | Test that the [`MOI.RawStatusString`](@ref) attribute is implemented for 78 | `model`. 79 | """ 80 | function raw_status_string(model::MOI.ModelLike, config::TestConfig) 81 | MOI.empty!(model) 82 | @test MOI.is_empty(model) 83 | x = MOI.add_variable(model) 84 | MOI.add_constraint(model, MOI.SingleVariable(x), MOI.GreaterThan(0.0)) 85 | MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) 86 | MOI.set(model, MOI.ObjectiveFunction{MOI.SingleVariable}(), 87 | MOI.SingleVariable(x)) 88 | test_model_solution(model, config, objective_value = 0.0, 89 | variable_primal = [(x, 0.0)]) 90 | if config.solve 91 | @test MOI.get(model, MOI.RawStatusString()) isa AbstractString 92 | end 93 | end 94 | unittests["raw_status_string"] = raw_status_string 95 | 96 | """ 97 | solve_time(model::MOI.ModelLike, config::TestConfig) 98 | 99 | Test that the [`MOI.SolveTime`](@ref) attribute is implemented for `model`. 100 | """ 101 | function solve_time(model::MOI.ModelLike, config::TestConfig) 102 | MOI.empty!(model) 103 | @test MOI.is_empty(model) 104 | x = MOI.add_variable(model) 105 | MOI.add_constraint(model, MOI.SingleVariable(x), MOI.LessThan(0.0)) 106 | MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) 107 | MOI.set(model, MOI.ObjectiveFunction{MOI.SingleVariable}(), 108 | MOI.SingleVariable(x)) 109 | test_model_solution(model, config, objective_value = 0.0, 110 | variable_primal = [(x, 0.0)]) 111 | if config.solve 112 | time = MOI.get(model, MOI.SolveTime()) 113 | @test time ≥ 0.0 114 | end 115 | end 116 | unittests["solve_time"] = solve_time 117 | -------------------------------------------------------------------------------- /src/Bridges/Objective/slack.jl: -------------------------------------------------------------------------------- 1 | """ 2 | SlackBridge{T, F, G} 3 | 4 | The `SlackBridge` converts an objective function of type `G` into a 5 | [`MOI.SingleVariable`](@ref) objective by creating a slack variable and a 6 | `F`-in-[`MOI.LessThan`](@ref) constraint for minimization or 7 | `F`-in-[`MOI.LessThan`](@ref) constraint for maximization where `F` is 8 | `MOI.Utilities.promote_operation(-, T, G, MOI.SingleVariable}`. 9 | Note that when using this bridge, changing the optimization sense 10 | is not supported. Set the sense to `MOI.FEASIBILITY_SENSE` first 11 | to delete the bridge in order to change the sense, then re-add the objective. 12 | """ 13 | struct SlackBridge{T, F<:MOI.AbstractScalarFunction, G<:MOI.AbstractScalarFunction} <: AbstractBridge 14 | slack::MOI.VariableIndex 15 | constraint::Union{MOI.ConstraintIndex{F, MOI.LessThan{T}}, 16 | MOI.ConstraintIndex{F, MOI.GreaterThan{T}}} 17 | end 18 | function bridge_objective(::Type{SlackBridge{T, F, G}}, model::MOI.ModelLike, 19 | func::G) where {T, F, G<:MOI.AbstractScalarFunction} 20 | slack = MOI.add_variable(model) 21 | fslack = MOI.SingleVariable(slack) 22 | f = MOIU.operate(-, T, func, fslack) 23 | if MOI.get(model, MOI.ObjectiveSense()) == MOI.MIN_SENSE 24 | set = MOI.LessThan(zero(T)) 25 | elseif MOI.get(model, MOI.ObjectiveSense()) == MOI.MAX_SENSE 26 | set = MOI.GreaterThan(zero(T)) 27 | else 28 | error("Set `MOI.ObjectiveSense` before `MOI.ObjectiveFunction` when", 29 | " using `MOI.Bridges.Objective.SlackBridge`.") 30 | end 31 | constraint = MOIU.normalize_and_add_constraint(model, f, set) 32 | MOI.set(model, MOI.ObjectiveFunction{MOI.SingleVariable}(), fslack) 33 | return SlackBridge{T, F, G}(slack, constraint) 34 | end 35 | 36 | function supports_objective_function( 37 | ::Type{<:SlackBridge}, ::Type{MOI.SingleVariable}) 38 | return false 39 | end 40 | function supports_objective_function( 41 | ::Type{<:SlackBridge}, ::Type{<:MOI.AbstractScalarFunction}) 42 | return true 43 | end 44 | MOIB.added_constrained_variable_types(::Type{<:SlackBridge}) = Tuple{DataType}[] 45 | function MOIB.added_constraint_types(::Type{<:SlackBridge{T, F}}) where {T, F} 46 | return [(F, MOI.GreaterThan{T}), (F, MOI.LessThan{T})] 47 | end 48 | function MOIB.set_objective_function_type(::Type{<:SlackBridge}) 49 | return MOI.SingleVariable 50 | end 51 | function concrete_bridge_type(::Type{<:SlackBridge{T}}, 52 | G::Type{<:MOI.AbstractScalarFunction}) where T 53 | F = MOIU.promote_operation(-, T, G, MOI.SingleVariable) 54 | return SlackBridge{T, F, G} 55 | end 56 | 57 | # Attributes, Bridge acting as a model 58 | function MOI.get(bridge::SlackBridge, ::MOI.NumberOfVariables) 59 | return 1 60 | end 61 | function MOI.get(bridge::SlackBridge, ::MOI.ListOfVariableIndices) 62 | return [bridge.slack] 63 | end 64 | function MOI.get(bridge::SlackBridge{T, F}, ::MOI.NumberOfConstraints{F, S}) where { 65 | T, F, S <: Union{MOI.GreaterThan{T}, MOI.LessThan{T}}} 66 | return bridge.constraint isa MOI.ConstraintIndex{F, S} ? 1 : 0 67 | end 68 | function MOI.get(bridge::SlackBridge{T, F}, ::MOI.ListOfConstraintIndices{F, S}) where { 69 | T, F, S <: Union{MOI.GreaterThan{T}, MOI.LessThan{T}}} 70 | if bridge.constraint isa MOI.ConstraintIndex{F, S} 71 | return [bridge.constraint] 72 | else 73 | return MOI.ConstraintIndex{F, S}[] 74 | end 75 | end 76 | 77 | function MOI.delete(model::MOI.ModelLike, bridge::SlackBridge) 78 | MOI.delete(model, bridge.constraint) 79 | MOI.delete(model, bridge.slack) 80 | end 81 | 82 | function MOI.get(model::MOI.ModelLike, 83 | attr::MOIB.ObjectiveFunctionValue{G}, 84 | bridge::SlackBridge{T, F, G}) where {T, F, G} 85 | slack = MOI.get(model, MOIB.ObjectiveFunctionValue{MOI.SingleVariable}(attr.result_index)) 86 | # There may be a gap between the value of the original objective `g` and the 87 | # value of `bridge.slack`. Since `bridge.constraint` is `g - slack`, we can 88 | # get the value of the original objective by summing the value of `slack` 89 | # with the `ConstraintPrimal` of the constraint. 90 | obj_slack_constant = MOI.get(model, MOI.ConstraintPrimal(), bridge.constraint) 91 | # The constant was moved to the set as it is a scalar constraint. 92 | constant = MOI.constant(MOI.get(model, MOI.ConstraintSet(), bridge.constraint)) 93 | return obj_slack_constant + slack - constant 94 | end 95 | function MOI.get(model::MOI.ModelLike, attr::MOI.ObjectiveFunction{G}, 96 | bridge::SlackBridge{T, F, G}) where {T, F, G<:MOI.AbstractScalarFunction} 97 | func = MOI.get(model, MOI.ConstraintFunction(), bridge.constraint) 98 | return MOIU.convert_approx(G, MOIU.remove_variable(func, bridge.slack)) 99 | end 100 | -------------------------------------------------------------------------------- /src/Bridges/Constraint/bridge.jl: -------------------------------------------------------------------------------- 1 | """ 2 | AbstractBridge 3 | 4 | Subtype of [`MathOptInterface.Bridges.AbstractBridge`](@ref) for constraint 5 | bridges. 6 | """ 7 | abstract type AbstractBridge <: MOIB.AbstractBridge end 8 | 9 | # TODO [breaking] merge with `Bridges.Variable.IndexInVector` into `Bridges.IndexInVector` 10 | """ 11 | IndexInVector 12 | 13 | Index of variable in vector of variables. 14 | """ 15 | struct IndexInVector 16 | value::Int 17 | end 18 | 19 | """ 20 | bridge_constraint(BT::Type{<:AbstractBridge}, model::MOI.ModelLike, 21 | func::AbstractFunction, set::MOI.AbstractSet) 22 | 23 | Bridge the constraint `func`-in-`set` using bridge `BT` to `model` and returns 24 | a bridge object of type `BT`. The bridge type `BT` should be a concrete type, 25 | that is, all the type parameters of the bridge should be set. Use 26 | [`concrete_bridge_type`](@ref) to obtain a concrete type for given function 27 | and set types. 28 | """ 29 | function bridge_constraint end 30 | 31 | """ 32 | MOI.get(b::AbstractBridge, ::MOI.NumberOfVariables) 33 | 34 | The number of variables created by the bridge `b` in the model. 35 | """ 36 | MOI.get(::AbstractBridge, ::MOI.NumberOfVariables) = 0 37 | 38 | """ 39 | MOI.get(b::AbstractBridge, ::MOI.NumberOfVariables) 40 | 41 | The list of variables created by the bridge `b` in the model. 42 | """ 43 | MOI.get(::AbstractBridge, ::MOI.ListOfVariableIndices) = MOI.VariableIndex[] 44 | 45 | """ 46 | MOI.supports_constraint(BT::Type{<:AbstractBridge}, F::Type{<:MOI.AbstractFunction}, S::Type{<:MOI.AbstractSet})::Bool 47 | 48 | Return a `Bool` indicating whether the bridges of type `BT` support bridging `F`-in-`S` constraints. 49 | """ 50 | MOI.supports_constraint(::Type{<:AbstractBridge}, ::Type{<:MOI.AbstractFunction}, ::Type{<:MOI.AbstractSet}) = false 51 | 52 | """ 53 | added_constrained_variable_types(BT::Type{<:MOI.Bridges.Constraint.AbstractBridge}, 54 | F::Type{<:MOI.AbstractFunction}, 55 | S::Type{<:MOI.AbstractSet}) 56 | 57 | Return a list of the types of constrained variables that bridges of type `BT` 58 | add for bridging `F`-in-`S` constraints. This falls back to 59 | `added_constrained_variable_types(concrete_bridge_type(BT, F, S))` 60 | so bridges should not implement this method. 61 | """ 62 | function MOIB.added_constrained_variable_types(BT::Type{<:AbstractBridge}, 63 | F::Type{<:MOI.AbstractFunction}, 64 | S::Type{<:MOI.AbstractSet}) 65 | MOIB.added_constrained_variable_types(concrete_bridge_type(BT, F, S)) 66 | end 67 | 68 | """ 69 | added_constraint_types(BT::Type{<:MOI.Bridges.Constraint.AbstractBridge}, 70 | F::Type{<:MOI.AbstractFunction}, 71 | S::Type{<:MOI.AbstractSet}) 72 | 73 | Return a list of the types of constraints that bridges of type `BT` add for 74 | bridging `F`-in-`S` constraints. This falls back to 75 | `added_constraint_types(concrete_bridge_type(BT, F, S))` 76 | so bridges should not implement this method. 77 | """ 78 | function MOIB.added_constraint_types(BT::Type{<:AbstractBridge}, 79 | F::Type{<:MOI.AbstractFunction}, 80 | S::Type{<:MOI.AbstractSet}) 81 | MOIB.added_constraint_types(concrete_bridge_type(BT, F, S)) 82 | end 83 | 84 | """ 85 | concrete_bridge_type(BT::Type{<:AbstractBridge}, 86 | F::Type{<:MOI.AbstractFunction}, 87 | S::Type{<:MOI.AbstractSet})::DataType 88 | 89 | Return the concrete type of the bridge supporting `F`-in-`S` constraints. This 90 | function can only be called if `MOI.supports_constraint(BT, F, S)` is `true`. 91 | 92 | ## Examples 93 | 94 | As a [`MathOptInterface.SingleVariable`](@ref)-in-[`MathOptInterface.Interval`](@ref) 95 | constraint is bridged into a 96 | [`MathOptInterface.SingleVariable`](@ref)-in-[`MathOptInterface.GreaterThan`](@ref) 97 | and a 98 | [`MathOptInterface.SingleVariable`](@ref)-in-[`MathOptInterface.LessThan`](@ref) 99 | by the [`SplitIntervalBridge`](@ref), 100 | ```jldoctest 101 | BT = MOI.Bridges.Constraint.SplitIntervalBridge{Float64} 102 | F = MOI.SingleVariable 103 | S = MOI.Interval{Float64} 104 | MOI.Bridges.Constraint.concrete_bridge_type(BT, F, S) 105 | 106 | # output 107 | 108 | MOI.Bridges.Constraint.SplitIntervalBridge{Float64,MOI.SingleVariable} 109 | ``` 110 | """ 111 | function concrete_bridge_type(bridge_type::DataType, 112 | ::Type{<:MOI.AbstractFunction}, 113 | ::Type{<:MOI.AbstractSet}) 114 | return bridge_type 115 | end 116 | 117 | function concrete_bridge_type(b::MOIB.AbstractBridgeOptimizer, 118 | F::Type{<:MOI.AbstractFunction}, 119 | S::Type{<:MOI.AbstractSet}) 120 | return concrete_bridge_type(MOIB.bridge_type(b, F, S), F, S) 121 | end 122 | -------------------------------------------------------------------------------- /test/Bridges/Constraint/scalarize.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | 3 | using MathOptInterface 4 | const MOI = MathOptInterface 5 | const MOIT = MathOptInterface.Test 6 | const MOIU = MathOptInterface.Utilities 7 | const MOIB = MathOptInterface.Bridges 8 | 9 | include("../utilities.jl") 10 | 11 | mock = MOIU.MockOptimizer(MOIU.UniversalFallback(MOIU.Model{Float64}())) 12 | config = MOIT.TestConfig() 13 | 14 | @testset "Scalarize" begin 15 | bridged_mock = MOIB.Constraint.Scalarize{Float64}(mock) 16 | 17 | MOIT.basic_constraint_tests( 18 | bridged_mock, config, 19 | include = [(F, S) 20 | for F in [MOI.VectorOfVariables, MOI.VectorAffineFunction{Float64}, 21 | MOI.VectorQuadraticFunction{Float64}] 22 | for S in [MOI.Nonnegatives, MOI.Nonpositives, MOI.Zeros]]) 23 | 24 | # VectorOfVariables-in-Nonnegatives 25 | # VectorAffineFunction-in-Zeros 26 | mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 0.0, 2.0], 27 | (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => [-3, -1]) 28 | MOIT.lin1vtest(bridged_mock, config) 29 | ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.Zeros}())) 30 | test_delete_bridge(bridged_mock, ci, 3, 31 | ((MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}, 0), 32 | (MOI.SingleVariable, MOI.GreaterThan{Float64}, 0))) 33 | ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorOfVariables, MOI.Nonnegatives}())) 34 | func = MOI.get(bridged_mock, MOI.ConstraintFunction(), ci) 35 | MOI.delete(bridged_mock, func.variables[2]) 36 | new_func = MOI.VectorOfVariables(func.variables[[1, 3]]) 37 | @test MOI.get(bridged_mock, MOI.ConstraintFunction(), ci) == new_func 38 | @test MOI.get(bridged_mock, MOI.ConstraintSet(), ci) == MOI.Nonnegatives(2) 39 | 40 | @testset "$attr" for attr in [MOI.ConstraintPrimalStart(), MOI.ConstraintDualStart()] 41 | @test MOI.supports(bridged_mock, attr, typeof(ci)) 42 | values = [1.0, 2.0] 43 | MOI.set(bridged_mock, attr, ci, values) 44 | @test MOI.get(bridged_mock, attr, ci) == values 45 | end 46 | 47 | test_delete_bridge(bridged_mock, ci, 2, 48 | ((MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}, 0), 49 | (MOI.SingleVariable, MOI.GreaterThan{Float64}, 0))) 50 | 51 | # VectorAffineFunction-in-Nonnegatives 52 | # VectorAffineFunction-in-Zeros 53 | mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 0.0, 2.0], 54 | (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => [0, 2, 0], 55 | (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => [-3, -1]) 56 | MOIT.lin1ftest(bridged_mock, config) 57 | 58 | ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.Zeros}())) 59 | 60 | @testset "$attr" for attr in [MOI.ConstraintPrimalStart(), MOI.ConstraintDualStart()] 61 | @test MOI.supports(bridged_mock, attr, typeof(ci)) 62 | values = [1.0, -2.0] 63 | MOI.set(bridged_mock, attr, ci, values) 64 | @test MOI.get(bridged_mock, attr, ci) == values 65 | end 66 | 67 | test_delete_bridge(bridged_mock, ci, 3, 68 | ((MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}, 0), 69 | (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}, 0))) 70 | 71 | ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives}())) 72 | 73 | @testset "$attr" for attr in [MOI.ConstraintPrimalStart(), MOI.ConstraintDualStart()] 74 | @test MOI.supports(bridged_mock, attr, typeof(ci)) 75 | values = [1.0, -2.0, 3.0] 76 | MOI.set(bridged_mock, attr, ci, values) 77 | @test MOI.get(bridged_mock, attr, ci) == values 78 | end 79 | 80 | test_delete_bridge(bridged_mock, ci, 3, 81 | ((MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}, 0), 82 | (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}, 0))) 83 | 84 | # VectorOfVariables-in-Nonnegatives 85 | # VectorOfVariables-in-Nonpositives 86 | # VectorOfVariables-in-Zeros 87 | # VectorAffineFunction-in-Zeros 88 | mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [-4, -3, 16, 0], 89 | (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => [7, 2, -4]) 90 | MOIT.lin2vtest(bridged_mock, config) 91 | 92 | # VectorAffineFunction-in-Nonnegatives 93 | # VectorAffineFunction-in-Nonpositives 94 | # VectorAffineFunction-in-Zeros 95 | mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [-4, -3, 16, 0], 96 | (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => [0], 97 | (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [0], 98 | (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => [7, 2, -4, 7]) 99 | MOIT.lin2ftest(bridged_mock, config) 100 | end 101 | --------------------------------------------------------------------------------