├── .gitignore ├── .osil └── .gitignore ├── deps ├── .gitignore ├── ordered-compare-error.patch ├── os-printlevel.patch └── build.jl ├── test ├── REQUIRE └── runtests.jl ├── REQUIRE ├── .travis.yml ├── appveyor.yml ├── README.md ├── src ├── translations.jl ├── probmod.jl └── CoinOptServices.jl └── LICENSE.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.jl.cov 2 | *.jl.mem 3 | -------------------------------------------------------------------------------- /.osil/.gitignore: -------------------------------------------------------------------------------- 1 | *.osil 2 | *.osol 3 | *.osrl 4 | -------------------------------------------------------------------------------- /deps/.gitignore: -------------------------------------------------------------------------------- 1 | downloads 2 | src 3 | usr 4 | deps.jl 5 | -------------------------------------------------------------------------------- /test/REQUIRE: -------------------------------------------------------------------------------- 1 | JuMP 0.15 2 | OffsetArrays 0.2.13 3 | FactCheck 4 | -------------------------------------------------------------------------------- /REQUIRE: -------------------------------------------------------------------------------- 1 | julia 0.6 2 | Clp 3 | Cbc 4 | Ipopt 5 | LightXML 0.2 6 | BinDeps 7 | MathProgBase 0.5.0 0.8 8 | @osx Homebrew 9 | @windows WinRPM 10 | -------------------------------------------------------------------------------- /deps/ordered-compare-error.patch: -------------------------------------------------------------------------------- 1 | --- /SYMPHONY/src/LP/lp_genfunc.c 2015-03-07 15:52:32.000000000 -0500 2 | +++ /SYMPHONY/src/LP/lp_genfunc.c 2017-11-14 10:03:57.000000000 -0500 3 | @@ -2419,7 +2419,7 @@ 4 | send_to_pool); 5 | 6 | should_stop_adding_cgl_cuts(p, i, &should_stop); 7 | - if(i < 0 && num_cuts > 0) should_stop = TRUE; 8 | + if(i < 0 && *num_cuts > 0) should_stop = TRUE; 9 | //} 10 | if (should_stop == TRUE) { 11 | break; 12 | -------------------------------------------------------------------------------- /deps/os-printlevel.patch: -------------------------------------------------------------------------------- 1 | diff --git a/OS/src/OSUtils/OSParameters.h b/OS/src/OSUtils/OSParameters.h 2 | index d8af53f..6a469a4 100644 3 | --- a/OS/src/OSUtils/OSParameters.h 4 | +++ b/OS/src/OSUtils/OSParameters.h 5 | @@ -115,7 +115,7 @@ enum ENUM_OUTPUT_LEVEL 6 | ENUM_OUTPUT_LEVEL_NUMBER_OF_LEVELS // insert other values above this one... 7 | }; 8 | 9 | -#define DEFAULT_OUTPUT_LEVEL ENUM_OUTPUT_LEVEL_error 10 | +#define DEFAULT_OUTPUT_LEVEL -1 11 | 12 | /** 13 | * Enumeration for the different areas that can produce output. 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: julia 2 | os: 3 | - linux 4 | - osx 5 | julia: 6 | - 0.6 7 | - nightly 8 | matrix: 9 | allow_failures: 10 | - julia: nightly 11 | notifications: 12 | email: false 13 | sudo: false 14 | cache: 15 | directories: 16 | - $HOME/usr 17 | addons: 18 | apt_packages: 19 | - gfortran 20 | env: 21 | - MAKEFLAGS="-j3" 22 | LD_LIBRARY_PATH=$HOME/usr/lib 23 | # uncomment the following lines to override the default test script 24 | script: 25 | - if [[ -a .git/shallow ]]; then git fetch --unshallow; fi 26 | - julia -e 'Pkg.clone(pwd())' 27 | - julia -e 'Pkg.build("CoinOptServices")' > stdout.log 28 | - gem install gist 29 | - gist stdout.log || echo empty 30 | - julia -e 'Pkg.test("CoinOptServices")' 31 | before_cache: 32 | - cp -R $HOME/.julia/*/Cbc/deps/usr $HOME 33 | - cp -R $HOME/.julia/*/Ipopt/deps/usr $HOME 34 | - cp -R $HOME/.julia/*/CoinOptServices/deps/usr $HOME 35 | -------------------------------------------------------------------------------- /test/runtests.jl: -------------------------------------------------------------------------------- 1 | using CoinOptServices, JuMP 2 | using Base.Test 3 | 4 | # JuMP version of bonminEx1_Nonlinear.osil 5 | m = Model(solver = OsilSolver()) 6 | @variable(m, 0 <= x0 <= 1, Bin) 7 | @variable(m, x1 >= 0) 8 | @variable(m, x2 >= 0) 9 | @variable(m, 0 <= x3 <= 5, Int) 10 | @objective(m, Min, x0 - x1 - x2) 11 | #@NLobjective(m, Min, x1/x2) 12 | @NLconstraint(m, (x1 - 0.5)^2 + (x2 - 0.5)^2 <= 1/π) 13 | @constraint(m, x0 - x1 <= 0) 14 | @constraint(m, x1 + x2 + x3 <= 2) 15 | #@NLconstraint(m, 1 <= log(x1/x2)) 16 | 17 | 18 | #d = JuMP.JuMPNLPEvaluator(m, JuMP.prepConstrMatrix(m)) 19 | #MathProgBase.initialize(d, [:ExprGraph]); 20 | #MathProgBase.obj_expr(d) 21 | #MathProgBase.constr_expr(d, 1) 22 | 23 | solve(m) 24 | 25 | # Issue #13 26 | nvar = 10 27 | solver=OsilSolver(solver = "couenne") 28 | m = Model(solver=solver) 29 | @variable(m, -10 <= x[i=1:nvar] <= 10) 30 | @NLobjective(m, Min, sum(1/(1+exp(-x[i])) for i in 1:nvar)) 31 | @constraint(m, sum(x[i] for i in 1:nvar) <= .4*nvar) 32 | @test solve(m) == :Optimal 33 | @test isapprox(getvalue(x[1]), -10.0) 34 | 35 | 36 | include(Pkg.dir("JuMP","test","runtests.jl")) 37 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x86/0.6/julia-0.6-latest-win32.exe" 4 | - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x64/0.6/julia-0.6-latest-win64.exe" 5 | - JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x86/julia-latest-win32.exe" 6 | - JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x64/julia-latest-win64.exe" 7 | 8 | branches: 9 | only: 10 | - master 11 | - /release-.*/ 12 | 13 | notifications: 14 | - provider: Email 15 | on_build_success: false 16 | on_build_failure: false 17 | on_build_status_changed: false 18 | 19 | install: 20 | - ps: "[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12" 21 | # if there's a newer build queued for the same PR, cancel this one 22 | - ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod ` 23 | https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | ` 24 | Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { ` 25 | throw "There are newer queued builds for this pull request, failing early." } 26 | # Download most recent Julia Windows binary 27 | - ps: (new-object net.webclient).DownloadFile( 28 | $env:JULIA_URL, 29 | "C:\projects\julia-binary.exe") 30 | # Run installer silently, output to C:\projects\julia 31 | - C:\projects\julia-binary.exe /S /D=C:\projects\julia 32 | 33 | build_script: 34 | # Need to convert from shallow to complete for Pkg.clone to work 35 | - IF EXIST .git\shallow (git fetch --unshallow) 36 | - C:\projects\julia\bin\julia --startup-file=yes -e "versioninfo(); 37 | Pkg.clone(pwd(), \"CoinOptServices\"); Pkg.build(\"CoinOptServices\")" 38 | 39 | test_script: 40 | - C:\projects\julia\bin\julia -e "Pkg.test(\"CoinOptServices\")" 41 | -------------------------------------------------------------------------------- /deps/build.jl: -------------------------------------------------------------------------------- 1 | using BinDeps 2 | 3 | @BinDeps.setup 4 | 5 | libOS = library_dependency("libOS", aliases=["libOS-6"]) 6 | version = "2.9.2" 7 | 8 | provides(Sources, URI("http://www.coin-or.org/download/source/OS/OS-$version.tgz"), 9 | [libOS], os = :Unix) 10 | 11 | @static if is_windows() 12 | using WinRPM 13 | push!(WinRPM.sources, "http://download.opensuse.org/repositories/home:/kelman:/mingw-coinor/openSUSE_13.2") 14 | WinRPM.update() 15 | provides(WinRPM.RPM, "OptimizationServices", [libOS], os = :Windows) 16 | end 17 | 18 | @static if is_apple() 19 | using Homebrew 20 | provides(Homebrew.HB, "Optimizationservices", [libOS], os = :Darwin) 21 | end 22 | 23 | for dep in ("Cbc", "Ipopt") 24 | depsjl = Pkg.dir(dep, "deps", "deps.jl") 25 | isfile(depsjl) ? include(depsjl) : error("$dep not properly ", 26 | "installed. Please run\nPkg.build(\"$dep\")") 27 | end 28 | cbclibdir = dirname(libcbcsolver) 29 | ipoptlibdir = dirname(libipopt) 30 | 31 | prefix = joinpath(BinDeps.depsdir(libOS), "usr") 32 | patchdir = BinDeps.depsdir(libOS) 33 | builddir = joinpath(BinDeps.depsdir(libOS), "src", "OS-$version", "build") 34 | 35 | ENV2 = copy(ENV) 36 | @static if is_unix() 37 | ENV2["PKG_CONFIG_PATH"] = string(joinpath(cbclibdir, "pkgconfig"), 38 | ":", joinpath(ipoptlibdir, "pkgconfig")) 39 | end 40 | cbcincdir = joinpath(cbclibdir, "..", "include", "coin") 41 | 42 | provides(SimpleBuild, 43 | (@build_steps begin 44 | GetSources(libOS) 45 | CreateDirectory(builddir, true) 46 | @build_steps begin 47 | ChangeDirectory(builddir) 48 | pipeline(`cat $patchdir/os-printlevel.patch`, `patch -p1 -d ..`) 49 | pipeline(`cat $patchdir/ordered-compare-error.patch`, `patch -p1 -d ..`) 50 | setenv(`../configure --prefix=$prefix --enable-dependency-linking 51 | coin_skip_warn_cflags=yes coin_skip_warn_cxxflags=yes coin_skip_warn_fflags=yes 52 | --with-coinutils-lib="-L$cbclibdir -lCoinUtils" 53 | --with-coinutils-incdir=$cbcincdir 54 | --with-osi-lib="-L$cbclibdir -lOsi -lCoinUtils" 55 | --with-osi-incdir=$cbcincdir 56 | --with-clp-lib="-L$cbclibdir -lClp -lOsiClp" 57 | --with-clp-incdir=$cbcincdir 58 | --with-cgl-lib="-L$cbclibdir -lCgl" 59 | --with-cgl-incdir=$cbcincdir 60 | --with-cbc-lib="-L$cbclibdir -lCbc" 61 | --with-cbc-incdir=$cbcincdir 62 | --with-blas="-L$ipoptlibdir -lcoinblas" 63 | --with-lapack="-L$ipoptlibdir -lcoinlapack" 64 | --with-mumps-lib="-L$ipoptlibdir -lcoinmumps" 65 | --with-ipopt-lib="-L$ipoptlibdir -lipopt"`, ENV2) 66 | `make` 67 | `make -j1 install` 68 | `make -C OS/test alltests` 69 | end 70 | end), [libOS], os = :Unix) 71 | 72 | @BinDeps.install Dict([(:libOS, :libOS)]) 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CoinOptServices.jl 2 | 3 | Linux, OSX: [![Build Status](https://travis-ci.org/JuliaOpt/CoinOptServices.jl.svg?branch=master)](https://travis-ci.org/JuliaOpt/CoinOptServices.jl) 4 | 5 | Windows: [![Build Status](https://ci.appveyor.com/api/projects/status/github/JuliaOpt/CoinOptServices.jl?branch=master&svg=true)](https://ci.appveyor.com/project/tkelman/coinoptservices-jl/branch/master) 6 | 7 | **The build script for this package has known issues on macOS and Linux that 8 | are difficult to address because the latest release of Optimization Services 9 | (2.10.1) does not compile on recent compilers (e.g., GCC 6.3 and later). 10 | If you are unable to install successfully, we recommend using 11 | [AmplNLWriter](https://github.com/JuliaOpt/AmplNLWriter.jl) for access to 12 | Bonmin and Couenne. Linux and macOS users should compile Bonmin or Couenne 13 | locally with ASL support and point `AmplNLSolver` to the path of the respective 14 | solver binary. Windows users should continue to use the binaries installed 15 | through this package for now. We hope to have new Julia packages to install 16 | Bonmin and Couenne binaries soon. (Help is welcome!). 17 | Users may also consider trying out pure-Julia solvers that 18 | have similar functionality, e.g., 19 | [Pajarito](https://github.com/JuliaOpt/Pajarito.jl), 20 | [Katana](https://github.com/lanl-ansi/Katana.jl), [POD](https://github.com/lanl-ansi/POD.jl), and 21 | [Juniper](https://github.com/lanl-ansi/Juniper.jl).** 22 | 23 | This [Julia](https://github.com/JuliaLang/julia) package is an interface 24 | between [MathProgBase.jl](https://github.com/JuliaOpt/MathProgBase.jl) and 25 | [COIN-OR](http://www.coin-or.org) [Optimization Services (OS)](https://projects.coin-or.org/OS), 26 | translating between the [Julia-expression-tree MathProgBase format](http://mathprogbasejl.readthedocs.org/en/latest/nlp.html#obj_expr) 27 | for nonlinear objective and constraint functions and the 28 | [Optimization Services instance Language (OSiL)](http://www.coin-or.org/OS/OSiL.html) 29 | XML-based optimization problem interchange format. 30 | 31 | By writing ``.osil`` files and using the ``OSSolverService`` command-line 32 | driver, this package allows Julia optimization modeling languages such as 33 | [JuMP](https://github.com/JuliaOpt/JuMP.jl) to access any solver supported by 34 | ``OSSolverService``. This includes the COIN-OR solvers [Clp](https://projects.coin-or.org/Clp) 35 | (linear programming), [Cbc](https://projects.coin-or.org/Cbc) (mixed-integer 36 | linear programming), [Ipopt](https://projects.coin-or.org/Ipopt) (nonlinear 37 | programming), [Bonmin](https://projects.coin-or.org/Bonmin) (evaluation-based 38 | mixed-integer nonlinear programming), [Couenne](https://projects.coin-or.org/Couenne) 39 | (expression-tree-based mixed-integer nonlinear programming), and several others. 40 | 41 | Note that [Clp](https://github.com/JuliaOpt/Clp.jl), [Cbc](https://github.com/JuliaOpt/Cbc.jl), 42 | and [Ipopt](https://github.com/JuliaOpt/Ipopt.jl) already have Julia packages 43 | that interface directly with their respective in-memory C API's. Particularly 44 | for Clp.jl and Cbc.jl, the existing packages should be faster than the 45 | CoinOptServices.jl approach of going through an OSiL file on disk. 46 | Initial comparisons show that Ipopt.jl is also substantially faster than 47 | CoinOptServices.jl. For nonlinear problems ``OSSolverService`` performs 48 | automatic differentiation in C++ using [CppAD](https://projects.coin-or.org/CppAD), 49 | which has different performance characteristics than the pure-Julia 50 | [ReverseDiffSparse.jl](https://github.com/mlubin/ReverseDiffSparse.jl) package 51 | used for nonlinear programming in JuMP. TODO: determine why CppAD is slower than expected 52 | 53 | Writing of ``.osil`` files is implemented using the 54 | [LightXML.jl](https://github.com/JuliaLang/LightXML.jl) Julia bindings to 55 | [libxml2](http://xmlsoft.org) to construct XML files from element trees. 56 | Reading of ``.osil`` files will be done later, to provide a (de-)serialization 57 | format for storage, archival, and interchange of optimization problems between 58 | various modeling languages. 59 | 60 | ## Installation 61 | 62 | You can install the package by running: 63 | 64 | julia> Pkg.add("CoinOptServices") 65 | 66 | On OS X, this will automatically download precompiled binaries 67 | via [Homebrew.jl](https://github.com/JuliaLang/Homebrew.jl). 68 | 69 | On Windows, this will automatically download precompiled binaries 70 | via [WinRPM.jl](https://github.com/JuliaLang/WinRPM.jl). 71 | Currently these are packaged in [@tkelman](https://github.com/tkelman)'s 72 | [personal project](https://build.opensuse.org/project/show/home:kelman:mingw-coinor) 73 | on the openSUSE build service, but these will be submitted to the official 74 | default repository eventually. 75 | 76 | On Linux, this will compile the COIN OS library and its dependencies from 77 | source if they are not found in ``DL_LOAD_PATH``. Note that OS is a large 78 | C++ library with many dependencies, and it is not currently packaged for 79 | any released Linux distributions. Submit a pull request to support using 80 | the library from a system package manager if this changes. It is 81 | recommended to set ``ENV["MAKEFLAGS"] = "-j4"`` before installing the 82 | package so compilation does not take as long. 83 | 84 | The current BinDeps setup assumes Ipopt.jl and Cbc.jl have already been 85 | successfully installed in order to reuse the binaries for those solvers. 86 | You will need to have a Fortran compiler such as ``gfortran`` installed 87 | in order to compile Ipopt. On Linux, use your system package manager to 88 | install ``gfortran``. You will also need to have ``pkg-config`` installed. 89 | 90 | This package builds the remaining COIN-OR libraries OS, CppAD, Bonmin, 91 | Couenne, and a few other solvers (DyLP, Vol, SYMPHONY, Bcp) 92 | that do not yet have Julia bindings. 93 | 94 | ## Usage 95 | 96 | CoinOptServices is usable as a solver in JuMP as follows. 97 | 98 | julia> using JuMP, CoinOptServices 99 | julia> m = Model(solver = OsilSolver()) 100 | 101 | Then model and solve your optimization problem as usual. See 102 | [JuMP's documentation](http://www.juliaopt.org/JuMP.jl/0.18/) for more 103 | details. The ``OsilSolver()`` constructor takes several optional keyword 104 | arguments. You can specify ``OsilSolver(solver = "couenne")`` to request 105 | a particular sub-solver, ``OsilSolver(osil = "/path/to/file.osil")`` or 106 | similarly ``osol`` or ``osrl`` keyword arguments to request non-default 107 | paths for writing the OSiL instance file, OSoL options file, or OSrL 108 | results file. The default location for writing these files is under 109 | ``Pkg.dir("CoinOptServices", ".osil")``. The ``printLevel`` keyword argument 110 | can be set to an integer from 0 to 5, and corresponds to the ``-printLevel`` 111 | command line flag for ``OSSolverService``. This only controls the print 112 | level of the OS driver, not the solvers themselves. 113 | 114 | Note that if you want to solve multiple problems simultaneously, you 115 | need to set the ``osil``, ``osol``, and ``osrl`` keyword arguments to 116 | independent file names for each problem. See 117 | [issue #1](https://github.com/JuliaOpt/CoinOptServices.jl/issues/1) for details. 118 | 119 | All additional inputs to ``OsilSolver`` are treated as solver-specific 120 | options. These options should be input as Julia ``Dict`` objects, with 121 | keys corresponding to OSoL ```` properties ``"name"``, 122 | ``"value"``, ``"solver"``, ``"category"``, ``"type"``, or ``"description"``. 123 | A convenience function ``OSOption(optname, optval, kwargs...)`` is provided 124 | to automatically set ``"type"`` based on the Julia type of ``optval``. 125 | 126 | CoinOptServices should also work with any other MathProgBase-compliant 127 | linear or nonlinear optimization modeling tools, though this has not been 128 | tested. There are features in OSiL for representing conic optimization 129 | problems, but these are not currently exposed or connected to the 130 | MathProgBase conic interface. 131 | -------------------------------------------------------------------------------- /src/translations.jl: -------------------------------------------------------------------------------- 1 | jl2osnl_varargs = Dict( 2 | :+ => "sum", 3 | :* => "product") 4 | 5 | jl2osnl_ternary = copy(jl2osnl_varargs) 6 | jl2osnl_ternary[:ifelse] = "if" 7 | 8 | jl2osnl_binary = Dict( 9 | :+ => "plus", 10 | :.+ => "plus", 11 | :- => "minus", 12 | :.- => "minus", 13 | :* => "times", 14 | :.* => "times", 15 | :/ => "divide", 16 | :./ => "divide", 17 | :div => "quotient", 18 | :÷ => "quotient", 19 | :.÷ => "quotient", 20 | :rem => "rem", 21 | :^ => "power", 22 | :.^ => "power", 23 | :log => "log", 24 | :< => "lt", 25 | :<= => "leq", 26 | :≤ => "leq", 27 | :> => "gt", 28 | :>= => "geq", 29 | :≥ => "geq", 30 | :(==) => "eq", 31 | :!= => "neq", 32 | :≠ => "neq") 33 | # and, or, xor, not? 34 | 35 | jl2osnl_unary = Dict( 36 | :- => "negate", 37 | :√ => "sqrt", 38 | :abs2 => "square", 39 | :ceil => "ceiling", 40 | :log => "ln", 41 | :log10 => "log10", 42 | :asin => "arcsin", 43 | :asinh => "arcsinh", 44 | :acos => "arccos", 45 | :acosh => "arccosh", 46 | :atan => "arctan", 47 | :atanh => "arctanh", 48 | :acot => "arccot", 49 | :acoth => "arccoth", 50 | :asec => "arcsec", 51 | :asech => "arcsech", 52 | :acsc => "arccsc", 53 | :acsch => "arccsch") 54 | 55 | for op in [:abs, :sqrt, :floor, :factorial, :exp, :sign, :erf, 56 | :sin, :sinh, :cos, :cosh, :tanh, # :tan, 57 | :cot, :coth, :sec, :sech, :csc, :csch] 58 | jl2osnl_unary[op] = string(op) 59 | end 60 | 61 | jl2osil_vartypes = Dict(:Cont => "C", :Int => "I", :Bin => "B", 62 | :SemiCont => "D", :SemiInt => "J", :Fixed => "C") 63 | # assuming lb == ub for all occurrences of :Fixed vars 64 | 65 | osrl2jl_status = Dict( 66 | "unbounded" => :Unbounded, 67 | "globallyOptimal" => :Optimal, 68 | "locallyOptimal" => :Optimal, 69 | "optimal" => :Optimal, 70 | "bestSoFar" => :Error, # be conservative for now 71 | "feasible" => :Error, # not sure when this happens - maybe with no objective? 72 | "infeasible" => :Infeasible, 73 | "unsure" => :Error, 74 | "error" => :Error, 75 | "other" => :Error, # OSBonminSolver and OSCouenneSolver use this for LIMIT_EXCEEDED 76 | "stoppedByLimit" => :UserLimit, 77 | "stoppedByBounds" => :Error, # does this ever happen? 78 | "IpoptAccetable" => :Optimal, # this (with typo) only occurs in OSIpoptSolver 79 | "BonminAccetable" => :Optimal, # this (with typo) only occurs in a 80 | "BonminAcceptable" => :Optimal, # possibly-obsolete version of OSBonminSolver 81 | "IpoptAcceptable" => :Optimal) # the typos may get fixed at some point 82 | 83 | function addLinElem!(indicator, densevals, elem::Expr) 84 | # convert Expr of the form :(val * x[idx]) to (idx, val) 85 | # then set indicator[idx] = true; densevals[idx] += val 86 | @assertequal(elem.head, :call) 87 | elemargs = elem.args 88 | @assertequal(elemargs[1], :*) 89 | @assertequal(length(elemargs), 3) 90 | elemarg3 = elemargs[3] 91 | @assertequal(elemarg3.head, :ref) 92 | elemarg3args = elemarg3.args 93 | @assertequal(elemarg3args[1], :x) 94 | @assertequal(length(elemarg3args), 2) 95 | idx::Int = elemarg3args[2] 96 | indicator[idx] = true 97 | densevals[idx] += elemargs[2] 98 | return 0.0 99 | end 100 | function addLinElem!(indicator, densevals, elem) 101 | # for elem not an Expr, assume it's a constant term and return it 102 | return elem 103 | end 104 | 105 | #= 106 | function constr2bounds(ex::Expr, sense::Symbol, rhs::Float64) 107 | # return (lb, ub) for a 3-term constraint expression 108 | if sense == :(<=) 109 | return (-Inf, rhs) 110 | elseif sense == :(>=) 111 | return (rhs, Inf) 112 | elseif sense == :(==) 113 | return (rhs, rhs) 114 | else 115 | error("Unknown constraint sense $sense") 116 | end 117 | end 118 | function constr2bounds(lhs::Float64, lsense::Symbol, ex::Expr, 119 | rsense::Symbol, rhs::Float64) 120 | # return (lb, ub) for a 5-term range constraint expression 121 | if lsense == :(<=) && rsense == :(<=) 122 | return (lhs, rhs) 123 | else 124 | error("Unknown constraint sense $lhs $lsense $ex $rsense $rhs") 125 | end 126 | end 127 | =# 128 | 129 | function expr2osnl!(parent, ex::Expr) 130 | # convert nonlinear expression from Expr to OSnL, 131 | # adding any new child xml elements to parent 132 | head = ex.head 133 | args = ex.args 134 | numargs = length(args) 135 | if head == :call 136 | if numargs < 2 137 | error("Do not know how to handle :call expression $ex ", 138 | "with fewer than 2 args") 139 | elseif numargs == 2 140 | child = args2osnl!(parent, args, jl2osnl_unary, "unary") 141 | elseif numargs == 3 142 | if haskey(jl2osnl_binary, args[1]) 143 | # handle some special cases, see below 144 | child = binary2osnl!(parent, args...) 145 | else 146 | error("Do not know how to convert binary $(args[1]) to osnl") 147 | end 148 | elseif numargs == 4 149 | child = args2osnl!(parent, args, jl2osnl_ternary, "ternary") 150 | else 151 | child = args2osnl!(parent, args, jl2osnl_varargs, "varargs") 152 | end 153 | elseif head == :ref 154 | child = var2osnl!(parent, args) 155 | elseif head == :comparison 156 | error("Do not know how to convert comparisons with $numargs arguments to osnl") 157 | else 158 | error("Do not know how to handle expression $ex with head $head") 159 | end 160 | return child 161 | end 162 | function expr2osnl!(parent, ex) 163 | # for anything not an Expr, assume it's a constant number 164 | child = new_child(parent, "number") 165 | set_attribute(child, "value", Float64(ex)) 166 | return child 167 | end 168 | 169 | function args2osnl!(parent, args, table, label) 170 | if haskey(table, args[1]) 171 | child = new_child(parent, table[args[1]]) 172 | for i = 2:length(args) 173 | expr2osnl!(child, args[i]) 174 | end 175 | else 176 | error("Do not know how to convert $label $(args[1]) to osnl") 177 | end 178 | return child 179 | end 180 | 181 | function var2osnl!(parent, args) 182 | # convert :(x[idx]) to osnl, adding xml element to parent 183 | @assertequal(args[1], :x) 184 | @assertequal(length(args), 2) 185 | idx::Int = args[2] 186 | child = new_child(parent, "variable") 187 | set_attribute(child, "idx", idx - 1) # OSiL is 0-based 188 | return child 189 | end 190 | 191 | function binary2osnl_generic!(parent, op::Symbol, ex1, ex2) 192 | # convert generic binary operation from Expr(:call, op, ex1, ex2) 193 | # to OSnL, adding any new child xml elements to parent 194 | child = new_child(parent, jl2osnl_binary[op]) 195 | expr2osnl!(child, ex1) 196 | expr2osnl!(child, ex2) 197 | return child 198 | end 199 | function binary2osnl!(parent, op::Symbol, ex1::Expr, ex2::Number) 200 | # special cases for square, variable * coef, variable / coef 201 | if ex2 == 2 && (op == :^ || op == :.^) 202 | child = new_child(parent, "square") 203 | expr2osnl!(child, ex1) 204 | # do same thing for sqrt here? 205 | elseif ex1.head == :ref && (op == :* || op == :.*) 206 | child = var2osnl!(parent, ex1.args) 207 | set_attribute(child, "coef", ex2) 208 | elseif ex1.head == :ref && (op == :/ || op == :./) 209 | child = var2osnl!(parent, ex1.args) 210 | set_attribute(child, "coef", 1 / ex2) 211 | else 212 | child = binary2osnl_generic!(parent, op, ex1, ex2) 213 | end 214 | return child 215 | end 216 | function binary2osnl!(parent, op::Symbol, ex1::Number, ex2::Expr) 217 | # special case for coef * variable 218 | if ex2.head == :ref && (op == :* || op == :.*) 219 | child = var2osnl!(parent, ex2.args) 220 | set_attribute(child, "coef", ex1) 221 | else 222 | child = binary2osnl_generic!(parent, op, ex1, ex2) 223 | end 224 | return child 225 | end 226 | function binary2osnl!(parent, op::Symbol, ex1, ex2) 227 | return binary2osnl_generic!(parent, op, ex1, ex2) 228 | end 229 | 230 | function xml2vec(el::XMLElement, n::Integer, defaultval=NaN) 231 | # convert osrl/osil list of variable values, bound or constraint 232 | # dual values, or objective coefficients to dense vector 233 | x = fill(defaultval, n) 234 | indicator = fill(false, n) 235 | for child in child_elements(el) 236 | idx = parse(Int, attribute(child, "idx")) + 1 # OSiL is 0-based 237 | if indicator[idx] # combine duplicates 238 | x[idx] += parse(Float64, content(child)) 239 | else 240 | indicator[idx] = true 241 | x[idx] = parse(Float64, content(child)) 242 | end 243 | end 244 | return x 245 | end 246 | 247 | # TODO: writeproblem and loadproblem for linear and nonlinear <=> osil files 248 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The CoinOptServices.jl package is licensed under the Simplified "2-clause" 2 | BSD License, or the Eclipse Public License - v 1.0, at your option. 3 | 4 | Simplified "2-clause" BSD License: 5 | 6 | > Copyright (c) 2015: Tony Kelman. 7 | > 8 | > Redistribution and use in source and binary forms, with or without 9 | > modification, are permitted provided that the following conditions are 10 | > met: 11 | > 12 | > 1. Redistributions of source code must retain the above copyright 13 | > notice, this list of conditions and the following disclaimer. 14 | > 2. Redistributions in binary form must reproduce the above copyright 15 | > notice, this list of conditions and the following disclaimer in the 16 | > documentation and/or other materials provided with the distribution. 17 | > 18 | > THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | > "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | > LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | > A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | > OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | > SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | > LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | > DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | > THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | > (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | > OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | Eclipse Public License - v 1.0: 31 | 32 | > THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 33 | > LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 34 | > CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 35 | > 36 | > 1. DEFINITIONS 37 | > 38 | > "Contribution" means: 39 | > 40 | > a) in the case of the initial Contributor, the initial code and documentation 41 | > distributed under this Agreement, and 42 | > b) in the case of each subsequent Contributor: 43 | > i) changes to the Program, and 44 | > ii) additions to the Program; 45 | > 46 | > where such changes and/or additions to the Program originate from and are 47 | > distributed by that particular Contributor. A Contribution 'originates' 48 | > from a Contributor if it was added to the Program by such Contributor 49 | > itself or anyone acting on such Contributor's behalf. Contributions do not 50 | > include additions to the Program which: (i) are separate modules of 51 | > software distributed in conjunction with the Program under their own 52 | > license agreement, and (ii) are not derivative works of the Program. 53 | > 54 | > "Contributor" means any person or entity that distributes the Program. 55 | > 56 | > "Licensed Patents" mean patent claims licensable by a Contributor which are 57 | > necessarily infringed by the use or sale of its Contribution alone or when 58 | > combined with the Program. 59 | > 60 | > "Program" means the Contributions distributed in accordance with this 61 | > Agreement. 62 | > 63 | > "Recipient" means anyone who receives the Program under this Agreement, 64 | > including all Contributors. 65 | > 66 | > 2. GRANT OF RIGHTS 67 | > a) Subject to the terms of this Agreement, each Contributor hereby grants 68 | > Recipient a non-exclusive, worldwide, royalty-free copyright license to 69 | > reproduce, prepare derivative works of, publicly display, publicly 70 | > perform, distribute and sublicense the Contribution of such Contributor, 71 | > if any, and such derivative works, in source code and object code form. 72 | > b) Subject to the terms of this Agreement, each Contributor hereby grants 73 | > Recipient a non-exclusive, worldwide, royalty-free patent license under 74 | > Licensed Patents to make, use, sell, offer to sell, import and otherwise 75 | > transfer the Contribution of such Contributor, if any, in source code and 76 | > object code form. This patent license shall apply to the combination of 77 | > the Contribution and the Program if, at the time the Contribution is 78 | > added by the Contributor, such addition of the Contribution causes such 79 | > combination to be covered by the Licensed Patents. The patent license 80 | > shall not apply to any other combinations which include the Contribution. 81 | > No hardware per se is licensed hereunder. 82 | > c) Recipient understands that although each Contributor grants the licenses 83 | > to its Contributions set forth herein, no assurances are provided by any 84 | > Contributor that the Program does not infringe the patent or other 85 | > intellectual property rights of any other entity. Each Contributor 86 | > disclaims any liability to Recipient for claims brought by any other 87 | > entity based on infringement of intellectual property rights or 88 | > otherwise. As a condition to exercising the rights and licenses granted 89 | > hereunder, each Recipient hereby assumes sole responsibility to secure 90 | > any other intellectual property rights needed, if any. For example, if a 91 | > third party patent license is required to allow Recipient to distribute 92 | > the Program, it is Recipient's responsibility to acquire that license 93 | > before distributing the Program. 94 | > d) Each Contributor represents that to its knowledge it has sufficient 95 | > copyright rights in its Contribution, if any, to grant the copyright 96 | > license set forth in this Agreement. 97 | > 98 | > 3. REQUIREMENTS 99 | > 100 | > A Contributor may choose to distribute the Program in object code form under 101 | > its own license agreement, provided that: 102 | > 103 | > a) it complies with the terms and conditions of this Agreement; and 104 | > b) its license agreement: 105 | > i) effectively disclaims on behalf of all Contributors all warranties 106 | > and conditions, express and implied, including warranties or 107 | > conditions of title and non-infringement, and implied warranties or 108 | > conditions of merchantability and fitness for a particular purpose; 109 | > ii) effectively excludes on behalf of all Contributors all liability for 110 | > damages, including direct, indirect, special, incidental and 111 | > consequential damages, such as lost profits; 112 | > iii) states that any provisions which differ from this Agreement are 113 | > offered by that Contributor alone and not by any other party; and 114 | > iv) states that source code for the Program is available from such 115 | > Contributor, and informs licensees how to obtain it in a reasonable 116 | > manner on or through a medium customarily used for software exchange. 117 | > 118 | > When the Program is made available in source code form: 119 | > 120 | > a) it must be made available under this Agreement; and 121 | > b) a copy of this Agreement must be included with each copy of the Program. 122 | > Contributors may not remove or alter any copyright notices contained 123 | > within the Program. 124 | > 125 | > Each Contributor must identify itself as the originator of its Contribution, 126 | > if 127 | > any, in a manner that reasonably allows subsequent Recipients to identify the 128 | > originator of the Contribution. 129 | > 130 | > 4. COMMERCIAL DISTRIBUTION 131 | > 132 | > Commercial distributors of software may accept certain responsibilities with 133 | > respect to end users, business partners and the like. While this license is 134 | > intended to facilitate the commercial use of the Program, the Contributor who 135 | > includes the Program in a commercial product offering should do so in a manner 136 | > which does not create potential liability for other Contributors. Therefore, 137 | > if a Contributor includes the Program in a commercial product offering, such 138 | > Contributor ("Commercial Contributor") hereby agrees to defend and indemnify 139 | > every other Contributor ("Indemnified Contributor") against any losses, 140 | > damages and costs (collectively "Losses") arising from claims, lawsuits and 141 | > other legal actions brought by a third party against the Indemnified 142 | > Contributor to the extent caused by the acts or omissions of such Commercial 143 | > Contributor in connection with its distribution of the Program in a commercial 144 | > product offering. The obligations in this section do not apply to any claims 145 | > or Losses relating to any actual or alleged intellectual property 146 | > infringement. In order to qualify, an Indemnified Contributor must: 147 | > a) promptly notify the Commercial Contributor in writing of such claim, and 148 | > b) allow the Commercial Contributor to control, and cooperate with the 149 | > Commercial Contributor in, the defense and any related settlement 150 | > negotiations. The Indemnified Contributor may participate in any such claim at 151 | > its own expense. 152 | > 153 | > For example, a Contributor might include the Program in a commercial product 154 | > offering, Product X. That Contributor is then a Commercial Contributor. If 155 | > that Commercial Contributor then makes performance claims, or offers 156 | > warranties related to Product X, those performance claims and warranties are 157 | > such Commercial Contributor's responsibility alone. Under this section, the 158 | > Commercial Contributor would have to defend claims against the other 159 | > Contributors related to those performance claims and warranties, and if a 160 | > court requires any other Contributor to pay any damages as a result, the 161 | > Commercial Contributor must pay those damages. 162 | > 163 | > 5. NO WARRANTY 164 | > 165 | > EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN 166 | > "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 167 | > IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, 168 | > NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each 169 | > Recipient is solely responsible for determining the appropriateness of using 170 | > and distributing the Program and assumes all risks associated with its 171 | > exercise of rights under this Agreement , including but not limited to the 172 | > risks and costs of program errors, compliance with applicable laws, damage to 173 | > or loss of data, programs or equipment, and unavailability or interruption of 174 | > operations. 175 | > 176 | > 6. DISCLAIMER OF LIABILITY 177 | > 178 | > EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 179 | > CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 180 | > SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 181 | > LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 182 | > CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 183 | > ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 184 | > EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 185 | > OF SUCH DAMAGES. 186 | > 187 | > 7. GENERAL 188 | > 189 | > If any provision of this Agreement is invalid or unenforceable under 190 | > applicable law, it shall not affect the validity or enforceability of the 191 | > remainder of the terms of this Agreement, and without further action by the 192 | > parties hereto, such provision shall be reformed to the minimum extent 193 | > necessary to make such provision valid and enforceable. 194 | > 195 | > If Recipient institutes patent litigation against any entity (including a 196 | > cross-claim or counterclaim in a lawsuit) alleging that the Program itself 197 | > (excluding combinations of the Program with other software or hardware) 198 | > infringes such Recipient's patent(s), then such Recipient's rights granted 199 | > under Section 2(b) shall terminate as of the date such litigation is filed. 200 | > 201 | > All Recipient's rights under this Agreement shall terminate if it fails to 202 | > comply with any of the material terms or conditions of this Agreement and does 203 | > not cure such failure in a reasonable period of time after becoming aware of 204 | > such noncompliance. If all Recipient's rights under this Agreement terminate, 205 | > Recipient agrees to cease use and distribution of the Program as soon as 206 | > reasonably practicable. However, Recipient's obligations under this Agreement 207 | > and any licenses granted by Recipient relating to the Program shall continue 208 | > and survive. 209 | > 210 | > Everyone is permitted to copy and distribute copies of this Agreement, but in 211 | > order to avoid inconsistency the Agreement is copyrighted and may only be 212 | > modified in the following manner. The Agreement Steward reserves the right to 213 | > publish new versions (including revisions) of this Agreement from time to 214 | > time. No one other than the Agreement Steward has the right to modify this 215 | > Agreement. The Eclipse Foundation is the initial Agreement Steward. The 216 | > Eclipse Foundation may assign the responsibility to serve as the Agreement 217 | > Steward to a suitable separate entity. Each new version of the Agreement will 218 | > be given a distinguishing version number. The Program (including 219 | > Contributions) may always be distributed subject to the version of the 220 | > Agreement under which it was received. In addition, after a new version of the 221 | > Agreement is published, Contributor may elect to distribute the Program 222 | > (including its Contributions) under the new version. Except as expressly 223 | > stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 224 | > licenses to the intellectual property of any Contributor under this Agreement, 225 | > whether expressly, by implication, estoppel or otherwise. All rights in the 226 | > Program not expressly granted under this Agreement are reserved. 227 | > 228 | > This Agreement is governed by the laws of the State of New York and the 229 | > intellectual property laws of the United States of America. No party to this 230 | > Agreement will bring a legal action under this Agreement more than one year 231 | > after the cause of action arose. Each party waives its rights to a jury trial in 232 | > any resulting litigation. 233 | -------------------------------------------------------------------------------- /src/probmod.jl: -------------------------------------------------------------------------------- 1 | # getters 2 | MathProgBase.status(m::OsilMathProgModel) = m.status 3 | MathProgBase.numvar(m::OsilMathProgModel) = m.numberOfVariables 4 | MathProgBase.numconstr(m::OsilMathProgModel) = m.numberOfConstraints 5 | MathProgBase.numlinconstr(m::OsilMathProgModel) = m.numLinConstr 6 | MathProgBase.numquadconstr(m::OsilMathProgModel) = length(m.quadconidx) 7 | MathProgBase.getobjval(m::OsilMathProgModel) = m.objval 8 | MathProgBase.getsolution(m::OsilMathProgModel) = m.solution 9 | MathProgBase.getreducedcosts(m::OsilMathProgModel) = m.reducedcosts 10 | MathProgBase.getconstrduals(m::OsilMathProgModel) = m.constrduals 11 | MathProgBase.getquadconstrduals(m::OsilMathProgModel) = m.quadconstrduals 12 | MathProgBase.getsense(m::OsilMathProgModel) = m.objsense 13 | MathProgBase.getvartype(m::OsilMathProgModel) = m.vartypes 14 | MathProgBase.getvarLB(m::OsilMathProgModel) = m.xl 15 | MathProgBase.getvarUB(m::OsilMathProgModel) = m.xu 16 | MathProgBase.getconstrLB(m::OsilMathProgModel) = m.cl 17 | MathProgBase.getconstrUB(m::OsilMathProgModel) = m.cu 18 | MathProgBase.getquadconstrRHS(m::OsilMathProgModel) = m.qrhs 19 | MathProgBase.getobj(m::OsilMathProgModel) = 20 | xml2vec(m.obj, m.numberOfVariables, 0.0) 21 | 22 | # helper functions 23 | function newvar!(variables::XMLElement, lb, ub) 24 | # create a new child with given lb, ub 25 | var = new_child(variables, "var") 26 | set_attribute(var, "lb", lb) # lb defaults to 0 if not specified! 27 | isfinite(ub) && set_attribute(var, "ub", ub) 28 | return var 29 | end 30 | 31 | function newcon!(constraints::XMLElement, lb, ub) 32 | # create a new child with given lb, ub 33 | con = new_child(constraints, "con") 34 | isfinite(lb) && set_attribute(con, "lb", lb) 35 | isfinite(ub) && set_attribute(con, "ub", ub) 36 | return con 37 | end 38 | 39 | function newobjcoef!(obj::XMLElement, idx, val) 40 | coef = new_child(obj, "coef") 41 | set_attribute(coef, "idx", idx) 42 | add_text(coef, string(val)) 43 | return coef 44 | end 45 | 46 | function create_empty_linconstr!(m::OsilMathProgModel) 47 | linConstrCoefs = new_child(m.instanceData, "linearConstraintCoefficients") 48 | rowstarts = new_child(linConstrCoefs, "start") 49 | add_text(new_child(rowstarts, "el"), "0") 50 | colIdx = new_child(linConstrCoefs, "colIdx") 51 | values = new_child(linConstrCoefs, "value") 52 | if isdefined(m, :quadraticCoefficients) 53 | # need to rearrange so quadraticCoefficients come after linear 54 | unlink(m.quadraticCoefficients) 55 | add_child(m.instanceData, m.quadraticCoefficients) 56 | end 57 | return (linConstrCoefs, rowstarts, colIdx, values) 58 | end 59 | 60 | function addnonzero!(colIdx, values, idx, val) 61 | # add a nonzero element to colIdx and values, with 0-based idx 62 | add_text(new_child(colIdx, "el"), string(idx)) 63 | add_text(new_child(values, "el"), string(val)) 64 | return val 65 | end 66 | 67 | function initialize_quadcoefs!(m::OsilMathProgModel) 68 | # return numberOfQuadraticTerms if quadraticCoefficients has been 69 | # created, otherwise create quadraticCoefficients and return 0 70 | if isdefined(m, :quadraticCoefficients) 71 | return parse(Int, attribute(m.quadraticCoefficients, 72 | "numberOfQuadraticTerms")) 73 | else 74 | m.quadraticCoefficients = new_child(m.instanceData, 75 | "quadraticCoefficients") 76 | return 0 77 | end 78 | end 79 | 80 | function newquadterm!(parent::XMLElement, conidx, rowidx, colidx, val) 81 | term = new_child(parent, "qTerm") 82 | set_attribute(term, "idx", conidx) # -1 for objective terms 83 | set_attribute(term, "idxOne", rowidx - 1) # OSiL is 0-based 84 | set_attribute(term, "idxTwo", colidx - 1) # OSiL is 0-based 85 | set_attribute(term, "coef", val) 86 | return term 87 | end 88 | 89 | function splitlinquad(m::OsilMathProgModel, v::Vector{Float64}) 90 | # split linear and quadratic parts from a constraint vector 91 | # assuming quadratic constraints and expression-tree nonlinear 92 | # constraints are never both present 93 | numquadconstr = length(m.quadconidx) 94 | linpart = Array(Float64, m.numberOfConstraints - numquadconstr) 95 | quadpart = Array(Float64, numquadconstr) 96 | prevquadidx = 0 97 | for (q, nextquadidx) in enumerate(m.quadconidx) 98 | linconrange = (prevquadidx + 1 : nextquadidx - 1) 99 | linpart[linconrange - q + 1] = v[linconrange] 100 | quadpart[q] = v[nextquadidx] 101 | prevquadidx = nextquadidx 102 | end 103 | linconrange = (prevquadidx + 1 : m.numberOfConstraints) 104 | linpart[linconrange - numquadconstr] = v[linconrange] 105 | return (linpart, quadpart) 106 | end 107 | 108 | # setters 109 | function MathProgBase.setobj!(m::OsilMathProgModel, f) 110 | # remove and overwrite any existing objective coefficients 111 | for el in child_elements(m.obj) 112 | unlink(el) 113 | free(el) 114 | end 115 | numberOfObjCoef = 0 116 | for (i, val) in enumerate(f) 117 | (val == 0.0) && continue 118 | numberOfObjCoef += 1 119 | newobjcoef!(m.obj, i - 1, val) # OSiL is 0-based 120 | end 121 | set_attribute(m.obj, "numberOfObjCoef", numberOfObjCoef) 122 | end 123 | 124 | function MathProgBase.setvartype!(m::OsilMathProgModel, vartypes::Vector{Symbol}) 125 | i = 0 126 | first_unset = 0 127 | for vari in child_elements(m.variables) 128 | i += 1 129 | if i > length(vartypes) 130 | set_attribute(vari, "type", "C") 131 | if first_unset == 0 132 | first_unset = i 133 | end 134 | continue 135 | end 136 | if haskey(jl2osil_vartypes, vartypes[i]) 137 | set_attribute(vari, "type", jl2osil_vartypes[vartypes[i]]) 138 | if vartypes[i] == :Bin 139 | if m.xl[i] < 0.0 140 | warn("Setting lower bound for binary variable x[$i] ", 141 | "to 0.0 (was $(m.xl[i]))") 142 | m.xl[i] = 0.0 143 | set_attribute(vari, "lb", 0.0) 144 | end 145 | if m.xu[i] > 1.0 146 | warn("Setting upper bound for binary variable x[$i] ", 147 | "to 1.0 (was $(m.xu[i]))") 148 | m.xu[i] = 1.0 149 | set_attribute(vari, "ub", 1.0) 150 | end 151 | end 152 | else 153 | error("Unrecognized vartype $(vartypes[i])") 154 | end 155 | end 156 | if first_unset != 0 157 | warn("Variable type not provided for variables ", 158 | "$(first_unset : i), assuming Continuous") 159 | else 160 | @assertequal(i, length(vartypes)) 161 | end 162 | m.vartypes = vartypes 163 | end 164 | 165 | function MathProgBase.setvarLB!(m::OsilMathProgModel, xl::Vector{Float64}) 166 | i = 0 167 | for xi in child_elements(m.variables) 168 | i += 1 169 | if xl[i] < 0.0 && attribute(xi, "type") == "B" 170 | warn("Setting lower bound for binary variable x[$i] ", 171 | "to 0.0 (was $(xl[i]))") 172 | xl[i] = 0.0 173 | end 174 | # set bound attribute unconditionally, even if infinite, 175 | # to ensure any previously set value gets overwritten 176 | set_attribute(xi, "lb", xl[i]) 177 | end 178 | @assertequal(i, length(xl)) 179 | m.xl = xl 180 | end 181 | 182 | function MathProgBase.setvarUB!(m::OsilMathProgModel, xu::Vector{Float64}) 183 | i = 0 184 | for xi in child_elements(m.variables) 185 | i += 1 186 | if xu[i] > 1.0 && attribute(xi, "type") == "B" 187 | warn("Setting upper bound for binary variable x[$i] ", 188 | "to 1.0 (was $(xu[i]))") 189 | xu[i] = 1.0 190 | end 191 | # set bound attribute unconditionally, even if infinite, 192 | # to ensure any previously set value gets overwritten 193 | set_attribute(xi, "ub", xu[i]) 194 | end 195 | @assertequal(i, length(xu)) 196 | m.xu = xu 197 | end 198 | 199 | function setlinconstrbounds!(m::OsilMathProgModel, attr, v::Vector{Float64}) 200 | if length(v) > 0 201 | i = 0 202 | q = 1 203 | # need to skip quadratic constraints since MPB treats those separately 204 | numquadconstr = length(m.quadconidx) 205 | if numquadconstr == 0 206 | for child in child_elements(m.constraints) 207 | i += 1 208 | # set bound attribute unconditionally, even if infinite, 209 | # to ensure any previously set value gets overwritten 210 | set_attribute(child, attr, v[i]) 211 | end 212 | else 213 | nextquadidx = m.quadconidx[q] 214 | for child in child_elements(m.constraints) 215 | i += 1 216 | if i == nextquadidx 217 | q += 1 218 | if q <= numquadconstr 219 | nextquadidx = m.quadconidx[q] 220 | else 221 | nextquadidx = m.numberOfConstraints + 1 222 | end 223 | continue 224 | else 225 | # set bound attribute unconditionally, even if infinite, 226 | # to ensure any previously set value gets overwritten 227 | set_attribute(child, attr, v[i - q + 1]) 228 | end 229 | end 230 | end 231 | @assertequal(i - q + 1, length(v)) 232 | @assertequal(q - 1, numquadconstr) 233 | end 234 | return v 235 | end 236 | 237 | function MathProgBase.setconstrLB!(m::OsilMathProgModel, cl::Vector{Float64}) 238 | m.cl = setlinconstrbounds!(m, "lb", cl) 239 | end 240 | 241 | function MathProgBase.setconstrUB!(m::OsilMathProgModel, cu::Vector{Float64}) 242 | m.cu = setlinconstrbounds!(m, "ub", cu) 243 | end 244 | 245 | function MathProgBase.setsense!(m::OsilMathProgModel, objsense::Symbol) 246 | set_attribute(m.obj, "maxOrMin", lowercase(string(objsense))) 247 | m.objsense = objsense 248 | end 249 | 250 | function MathProgBase.setwarmstart!(m::OsilMathProgModel, x0::Vector{Float64}) 251 | @assertequal(length(x0), m.numberOfVariables) 252 | m.x0 = x0 253 | end 254 | 255 | function MathProgBase.addvar!(m::OsilMathProgModel, lb, ub, objcoef) 256 | push!(m.xl, lb) 257 | push!(m.xu, ub) 258 | newvar!(m.variables, lb, ub) 259 | if objcoef != 0.0 260 | set_attribute(m.obj, "numberOfObjCoef", parse(Int, 261 | attribute(m.obj, "numberOfObjCoef"))) + 1 262 | # use old numberOfVariables since OSiL is 0-based 263 | newobjcoef!(m.obj, m.numberOfVariables, objcoef) 264 | end 265 | m.numberOfVariables += 1 266 | set_attribute(m.variables, "numberOfVariables", m.numberOfVariables) 267 | return m # or the new xml element, or nothing ? 268 | end 269 | 270 | function MathProgBase.addconstr!(m::OsilMathProgModel, varidx, coef, lb, ub) 271 | @assertequal(length(varidx), length(coef)) 272 | if m.numLinConstr + length(m.quadconidx) < m.numberOfConstraints 273 | error("Adding a constraint to a nonlinear model not implemented") 274 | # addnlconstr! could be done though, if it existed in MathProgBase 275 | end 276 | push!(m.cl, lb) 277 | push!(m.cu, ub) 278 | newcon!(m.constraints, lb, ub) 279 | 280 | if m.numLinConstr + length(m.quadconidx) == 0 && length(varidx) > 0 281 | (linConstrCoefs, rowstarts, colIdx, values) = 282 | create_empty_linconstr!(m) 283 | numberOfValues = 0 284 | else 285 | # could save linConstrCoefs and the sparse matrix 286 | # data as part of m, but choosing not to optimize this much 287 | linConstrCoefs = find_element(m.instanceData, 288 | "linearConstraintCoefficients") 289 | if linConstrCoefs != nothing 290 | rowstarts = find_element(linConstrCoefs, "start") 291 | colIdx = find_element(linConstrCoefs, "colIdx") 292 | values = find_element(linConstrCoefs, "value") 293 | numberOfValues = parse(Int, 294 | attribute(linConstrCoefs, "numberOfValues")) 295 | end 296 | end 297 | numdupes = 0 298 | if issorted(varidx, lt = (<=)) # this means strictly increasing 299 | for (i, curval) in enumerate(coef) 300 | if curval != 0.0 || i == length(coef) # always add at least one "nonzero" 301 | addnonzero!(colIdx, values, varidx[i] - 1, curval) # OSiL is 0-based 302 | else 303 | numdupes += 1 304 | end 305 | end 306 | elseif length(varidx) > 0 307 | # we have the whole vector of indices here, 308 | # maybe better to sort than use a dense bitarray 309 | p = sortperm(varidx) 310 | curidx = varidx[p[1]] 311 | curval = coef[p[1]] 312 | for i = 2:length(p) 313 | nextidx = varidx[p[i]] 314 | if nextidx > curidx 315 | if curval != 0.0 316 | addnonzero!(colIdx, values, curidx - 1, curval) # OSiL is 0-based 317 | else 318 | numdupes += 1 319 | end 320 | curidx = nextidx 321 | curval = coef[p[i]] 322 | else 323 | numdupes += 1 324 | curval += coef[p[i]] 325 | end 326 | end 327 | # always add at least one "nonzero," even if curval == 0.0 328 | addnonzero!(colIdx, values, curidx - 1, curval) # OSiL is 0-based 329 | end 330 | if linConstrCoefs != nothing 331 | numberOfValues += length(varidx) - numdupes 332 | set_attribute(linConstrCoefs, "numberOfValues", numberOfValues) 333 | add_text(new_child(rowstarts, "el"), string(numberOfValues)) 334 | end 335 | 336 | m.numberOfConstraints += 1 337 | m.numLinConstr += 1 338 | set_attribute(m.constraints, "numberOfConstraints", m.numberOfConstraints) 339 | return m # or the new xml element, or nothing ? 340 | end 341 | 342 | function MathProgBase.setquadobjterms!(m::OsilMathProgModel, 343 | rowidx, colidx, quadval) 344 | @assertequal(length(rowidx), length(colidx)) 345 | @assertequal(length(rowidx), length(quadval)) 346 | numQuadTerms = initialize_quadcoefs!(m) 347 | if isdefined(m, :quadobjterms) 348 | numQuadTerms -= length(m.quadobjterms) 349 | # remove and overwrite any existing quadratic objective terms 350 | for el in m.quadobjterms 351 | unlink(el) 352 | free(el) 353 | end 354 | end 355 | quadobjterms = Array(XMLElement, length(quadval)) 356 | for i = 1:length(quadval) 357 | quadobjterms[i] = newquadterm!(m.quadraticCoefficients, "-1", 358 | rowidx[i], colidx[i], quadval[i]) 359 | end 360 | m.quadobjterms = quadobjterms 361 | set_attribute(m.quadraticCoefficients, "numberOfQuadraticTerms", 362 | numQuadTerms + length(quadval)) 363 | return m # or the new quadobjterms, or nothing ? 364 | end 365 | 366 | function MathProgBase.addquadconstr!(m::OsilMathProgModel, linearidx, 367 | linearval, quadrowidx, quadcolidx, quadval, sense, rhs) 368 | @assertequal(length(quadrowidx), length(quadcolidx)) 369 | @assertequal(length(quadrowidx), length(quadval)) 370 | numQuadTerms = initialize_quadcoefs!(m) 371 | # use old numberOfConstraints since OSiL is 0-based 372 | conidx = m.numberOfConstraints 373 | for i = 1:length(quadval) 374 | newquadterm!(m.quadraticCoefficients, conidx, 375 | quadrowidx[i], quadcolidx[i], quadval[i]) 376 | end 377 | set_attribute(m.quadraticCoefficients, "numberOfQuadraticTerms", 378 | numQuadTerms + length(quadval)) 379 | 380 | push!(m.qrhs, rhs) 381 | if sense == '<' 382 | (lb, ub) = (-Inf, rhs) 383 | elseif sense == '>' 384 | (lb, ub) = (rhs, Inf) 385 | elseif sense == '=' 386 | (lb, ub) = (rhs, rhs) 387 | else 388 | error("Unknown quadratic constraint sense $sense") 389 | end 390 | # always add a dummy linear part for every quadratic 391 | # constraint to make the bookkeeping simpler 392 | if isempty(linearidx) && isempty(linearval) 393 | MathProgBase.addconstr!(m, [1], [0.0], lb, ub) 394 | else 395 | MathProgBase.addconstr!(m, linearidx, linearval, lb, ub) 396 | end 397 | m.numLinConstr -= 1 # since this constraint is quadratic, not linear 398 | pop!(m.cl) # MathProgBase treats quadratic constraints separately 399 | pop!(m.cu) 400 | push!(m.quadconidx, m.numberOfConstraints) 401 | return m # or the new xml element, or nothing ? 402 | end 403 | 404 | # TODO: setquadconstrRHS!, getconstrsolution, getconstrmatrix, getrawsolver, 405 | # getsolvetime, sos constraints, basis, infeasibility/unbounded rays? 406 | 407 | # General wrapper functions 408 | for f in [:getsolution,:getobjval,:optimize!,:status,:getsense,:numvar,:numconstr,:getvartype,:getreducedcosts,:getconstrduals] 409 | @eval $f(m::OsilNonlinearModel) = $f(m.inner) 410 | @eval $f(m::OsilLinearQuadraticModel) = $f(m.inner) 411 | end 412 | for f in [:setsense!,:setvartype!,:setwarmstart!] 413 | @eval $f(m::OsilNonlinearModel, x) = $f(m.inner, x) 414 | @eval $f(m::OsilLinearQuadraticModel, x) = $f(m.inner, x) 415 | end 416 | 417 | # LinearQuadratic wrapper functions 418 | for f in [:numlinconstr,:numquadconstr,:getquadconstrduals,:getvarLB,:getvarUB,:getconstrLB,:getconstrUB,:getobj] 419 | @eval $f(m::OsilLinearQuadraticModel) = $f(m.inner) 420 | end 421 | for f in [:setobj!,:setvarLB!,:setvarUB!,:setconstrLB!,:setconstrUB!] 422 | @eval $f(m::OsilLinearQuadraticModel, x) = $f(m.inner, x) 423 | end 424 | for f in [:addvar!, :setquadobjterms!] 425 | @eval $f(m::OsilLinearQuadraticModel, x, y, z) = $f(m.inner, x, y, z) 426 | end 427 | for f in [:addconstr!] 428 | @eval $f(m::OsilLinearQuadraticModel, w, x, y, z) = $f(m.inner, w, x, y, z) 429 | end 430 | for f in [:addquadconstr!] 431 | @eval $f(m::OsilLinearQuadraticModel, a, b, c, d, e, f, g) = 432 | $f(m.inner, a, b, c, d, e, f, g) 433 | end 434 | 435 | -------------------------------------------------------------------------------- /src/CoinOptServices.jl: -------------------------------------------------------------------------------- 1 | module CoinOptServices 2 | 3 | using MathProgBase, LightXML 4 | importall MathProgBase.SolverInterface 5 | 6 | debug = true # (ccall(:jl_is_debugbuild, Cint, ()) == 1) 7 | if debug 8 | macro assertequal(x, y) 9 | msg = "Expected $x == $y, got " 10 | :($(esc(x)) == $(esc(y)) ? nothing : error($msg, repr($(esc(x))), " != ", repr($(esc(y))))) 11 | end 12 | else 13 | macro assertequal(x, y) 14 | end 15 | end 16 | 17 | include("translations.jl") 18 | 19 | depsjl = joinpath(dirname(@__FILE__), "..", "deps", "deps.jl") 20 | isfile(depsjl) ? include(depsjl) : error("CoinOptServices not properly ", 21 | "installed. Please run\nPkg.build(\"CoinOptServices\")") 22 | OSSolverService = joinpath(dirname(libOS), "..", "bin", "OSSolverService") 23 | bonmin = joinpath(dirname(libOS), "..", "bin", "bonmin") 24 | couenne = joinpath(dirname(libOS), "..", "bin", "couenne") 25 | osildir = Pkg.dir("CoinOptServices", ".osil") 26 | 27 | export OsilSolver, OsilBonminSolver, OsilCouenneSolver, OSOption 28 | struct OsilSolver <: AbstractMathProgSolver 29 | solver::String 30 | osil::String 31 | osol::String 32 | osrl::String 33 | printLevel::Int 34 | options::Vector{Dict} 35 | end 36 | # note that changing DEFAULT_OUTPUT_LEVEL in OS/src/OSUtils/OSOutput.h 37 | # from ENUM_OUTPUT_LEVEL_error (1) to -1 is required to make printLevel=0 38 | # actually silent, since there are several instances of OSPrint that use 39 | # ENUM_OUTPUT_LEVEL_always (0) *before* command-line flags like -printLevel 40 | # have been read, and OSPrint shows output whenever the output level for a 41 | # call is <= the printLevel 42 | OsilSolver(options::Dict...; 43 | solver = "", 44 | osil = joinpath(osildir, "problem.osil"), 45 | osol = joinpath(osildir, "options.osol"), 46 | osrl = joinpath(osildir, "results.osrl"), 47 | printLevel = 1) = 48 | OsilSolver(solver, osil, osol, osrl, printLevel, collect(options)) 49 | OsilBonminSolver(options::Dict...; 50 | osil = joinpath(osildir, "problem.osil"), 51 | osol = joinpath(osildir, "options.osol"), 52 | osrl = joinpath(osildir, "results.osrl"), 53 | printLevel = 1) = 54 | OsilSolver("bonmin", osil, osol, osrl, printLevel, collect(options)) 55 | OsilCouenneSolver(options::Dict...; 56 | osil = joinpath(osildir, "problem.osil"), 57 | osol = joinpath(osildir, "options.osol"), 58 | osrl = joinpath(osildir, "results.osrl"), 59 | printLevel = 1) = 60 | OsilSolver("couenne", osil, osol, osrl, printLevel, collect(options)) 61 | 62 | # translate keyword arguments into an option Dict 63 | # (can't make this a type since it would need a field named type) 64 | function OSOption(; kwargs...) 65 | optdict = Dict() 66 | for (argname, argval) in kwargs 67 | if haskey(optdict, argname) && argval != optdict[argname] 68 | error("Duplicate setting of option $argname; was ", 69 | optdict[argname], ", tried to set to $argval") 70 | else 71 | optdict[argname] = argval 72 | end 73 | end 74 | return optdict 75 | end 76 | function OSOption(optname, optval::AbstractString; kwargs...) 77 | push!(kwargs, (:type, "string")) 78 | return OSOption(name = optname, value = optval; kwargs...) 79 | end 80 | function OSOption(optname, optval::Integer; kwargs...) 81 | push!(kwargs, (:type, "integer")) 82 | return OSOption(name = optname, value = optval; kwargs...) 83 | end 84 | function OSOption(optname, optval::Number; kwargs...) 85 | push!(kwargs, (:type, "numeric")) 86 | return OSOption(name = optname, value = optval; kwargs...) 87 | end 88 | OSOption(optname, optval; kwargs...) = 89 | OSOption(name = optname, value = optval; kwargs...) 90 | 91 | mutable struct OsilMathProgModel <: AbstractMathProgModel 92 | solver::String 93 | osil::String 94 | osol::String 95 | osrl::String 96 | printLevel::Int 97 | options::Vector{Dict} 98 | 99 | numberOfVariables::Int 100 | numberOfConstraints::Int 101 | xl::Vector{Float64} 102 | xu::Vector{Float64} 103 | cl::Vector{Float64} 104 | cu::Vector{Float64} 105 | qrhs::Vector{Float64} 106 | objsense::Symbol 107 | d::AbstractNLPEvaluator 108 | 109 | numLinConstr::Int 110 | quadconidx::Vector{Int} 111 | vartypes::Vector{Symbol} 112 | x0::Vector{Float64} 113 | 114 | status::Symbol 115 | objval::Float64 116 | solution::Vector{Float64} 117 | reducedcosts::Vector{Float64} 118 | constrduals::Vector{Float64} 119 | quadconstrduals::Vector{Float64} 120 | 121 | xdoc::XMLDocument # TODO: finalizer 122 | instanceData::XMLElement 123 | obj::XMLElement 124 | variables::XMLElement 125 | constraints::XMLElement 126 | quadraticCoefficients::XMLElement 127 | quadobjterms::Vector{XMLElement} 128 | 129 | OsilMathProgModel(solver, osil, osol, osrl, printLevel, options) = 130 | new(solver, osil, osol, osrl, printLevel, options) 131 | end 132 | struct OsilLinearQuadraticModel <: AbstractLinearQuadraticModel 133 | inner::OsilMathProgModel 134 | end 135 | struct OsilNonlinearModel <: AbstractNonlinearModel 136 | inner::OsilMathProgModel 137 | end 138 | 139 | OsilMathProgModel(s::OsilSolver) = OsilMathProgModel(s.solver, 140 | s.osil, s.osol, s.osrl, s.printLevel, s.options) 141 | LinearQuadraticModel(s::OsilSolver) = OsilLinearQuadraticModel( 142 | OsilMathProgModel(s)) 143 | NonlinearModel(s::OsilSolver) = OsilNonlinearModel( 144 | OsilMathProgModel(s)) 145 | ConicModel(s::OsilSolver) = LPQPtoConicBridge(LinearQuadraticModel(s)) 146 | 147 | include("probmod.jl") 148 | 149 | function create_osil_common!(m::OsilMathProgModel, xl, xu, cl, cu, objsense) 150 | # create osil data that is common between linear and nonlinear problems 151 | numberOfVariables = length(xl) 152 | numberOfConstraints = length(cl) 153 | @assertequal numberOfVariables length(xu) 154 | @assertequal numberOfConstraints length(cu) 155 | 156 | 157 | m.numberOfVariables = numberOfVariables 158 | m.numberOfConstraints = numberOfConstraints 159 | m.xl = xl 160 | m.xu = xu 161 | m.cl = cl 162 | m.cu = cu 163 | m.objsense = objsense 164 | 165 | # clear existing problem, if defined 166 | isdefined(m, :xdoc) && free(m.xdoc) 167 | m.xdoc = XMLDocument() 168 | xroot = create_root(m.xdoc, "osil") 169 | set_attribute(xroot, "xmlns", "os.optimizationservices.org") 170 | set_attribute(xroot, "xmlns:xsi", 171 | "http://www.w3.org/2001/XMLSchema-instance") 172 | set_attribute(xroot, "xsi:schemaLocation", 173 | "os.optimizationservices.org " * 174 | "http://www.optimizationservices.org/schemas/2.0/OSiL.xsd") 175 | 176 | instanceHeader = new_child(xroot, "instanceHeader") 177 | description = new_child(instanceHeader, "description") 178 | add_text(description, "generated by CoinOptServices.jl on " * 179 | Libc.strftime("%Y/%m/%d at %H:%M:%S", time())) 180 | m.instanceData = new_child(xroot, "instanceData") 181 | 182 | m.variables = new_child(m.instanceData, "variables") 183 | set_attribute(m.variables, "numberOfVariables", numberOfVariables) 184 | for i = 1:numberOfVariables 185 | newvar!(m.variables, xl[i], xu[i]) 186 | end 187 | 188 | objectives = new_child(m.instanceData, "objectives") 189 | # can MathProgBase do multi-objective problems? 190 | set_attribute(objectives, "numberOfObjectives", "1") 191 | m.obj = new_child(objectives, "obj") 192 | set_attribute(m.obj, "maxOrMin", lowercase(string(objsense))) 193 | 194 | m.constraints = new_child(m.instanceData, "constraints") 195 | set_attribute(m.constraints, "numberOfConstraints", numberOfConstraints) 196 | for i = 1:numberOfConstraints 197 | newcon!(m.constraints, cl[i], cu[i]) 198 | end 199 | m.qrhs = Float64[] # move these once MathProgBase.loadquadproblem! exists 200 | m.quadconidx = Int[] 201 | 202 | return m 203 | end 204 | 205 | function MathProgBase.loadproblem!(outer::OsilLinearQuadraticModel, 206 | A, xl, xu, f, cl, cu, objsense) 207 | m = outer.inner 208 | # populate osil data that is specific to linear problems 209 | @assertequal(size(A, 1), length(cl)) 210 | @assertequal(size(A, 2), length(xl)) 211 | @assertequal(size(A, 2), length(f)) 212 | 213 | create_osil_common!(m, xl, xu, cl, cu, objsense) 214 | MathProgBase.setobj!(m, f) 215 | 216 | # transpose linear constraint matrix so it is easier 217 | # to add linear rows in addquadconstr! 218 | if issparse(A) 219 | At = A' 220 | else 221 | At = sparse(A)' 222 | end 223 | rowptr = At.colptr 224 | colval = At.rowval 225 | nzval = At.nzval 226 | if length(nzval) > 0 227 | (linConstrCoefs, rowstarts, colIdx, values) = 228 | create_empty_linconstr!(m) 229 | set_attribute(linConstrCoefs, "numberOfValues", length(nzval)) 230 | @assertequal(rowptr[1], 1) 231 | for i = 2:length(rowptr) 232 | add_text(new_child(rowstarts, "el"), string(rowptr[i] - 1)) # OSiL is 0-based 233 | end 234 | for i = 1:length(colval) 235 | addnonzero!(colIdx, values, colval[i] - 1, nzval[i]) # OSiL is 0-based 236 | end 237 | end 238 | m.numLinConstr = length(cl) 239 | 240 | return m 241 | end 242 | 243 | function MathProgBase.loadproblem!(outer::OsilNonlinearModel, 244 | numberOfVariables, numberOfConstraints, xl, xu, cl, cu, objsense, 245 | d::MathProgBase.AbstractNLPEvaluator) 246 | m = outer.inner 247 | # populate osil data that is specific to nonlinear problems 248 | @assert numberOfVariables == length(xl) 249 | @assert numberOfConstraints == length(cl) 250 | 251 | create_osil_common!(m, xl, xu, cl, cu, objsense) 252 | m.d = d 253 | MathProgBase.initialize(d, [:ExprGraph]) 254 | 255 | # TODO: compare BitArray vs. Array{Bool} here 256 | indicator = falses(numberOfVariables) 257 | densevals = zeros(numberOfVariables) 258 | 259 | objexpr = MathProgBase.obj_expr(d) 260 | nlobj = false 261 | if MathProgBase.isobjlinear(d) 262 | @assertequal(objexpr.head, :call) 263 | objexprargs = objexpr.args 264 | @assertequal(objexprargs[1], :+) 265 | constant = 0.0 266 | for i = 2:length(objexprargs) 267 | constant += addLinElem!(indicator, densevals, objexprargs[i]) 268 | end 269 | (constant == 0.0) || set_attribute(m.obj, "constant", constant) 270 | numberOfObjCoef = 0 271 | i = findnext(indicator, 1) 272 | while i != 0 273 | numberOfObjCoef += 1 274 | newobjcoef!(m.obj, i - 1, densevals[i]) # OSiL is 0-based 275 | densevals[i] = 0.0 # reset for later use in linear constraints 276 | i = findnext(indicator, i + 1) 277 | end 278 | fill!(indicator, false) # for Array{Bool}, set to false one element at a time? 279 | set_attribute(m.obj, "numberOfObjCoef", numberOfObjCoef) 280 | else 281 | nlobj = true 282 | set_attribute(m.obj, "numberOfObjCoef", "0") 283 | # nonlinear objective goes in nonlinearExpressions, 284 | end 285 | 286 | # assume linear constraints are all at start 287 | row = 1 288 | nextrowlinear = MathProgBase.isconstrlinear(d, row) 289 | if nextrowlinear 290 | # has at least 1 linear constraint 291 | (linConstrCoefs, rowstarts, colIdx, values) = 292 | create_empty_linconstr!(m) 293 | numberOfValues = 0 294 | end 295 | while nextrowlinear 296 | constrexpr = MathProgBase.constr_expr(d, row) 297 | 298 | @assertequal(constrexpr.head, :call) 299 | constrlinpart = constrexpr.args[2] 300 | 301 | #(lhs, rhs) = constr2bounds(constrexpr.args...) 302 | @assertequal(constrlinpart.head, :call) 303 | constrlinargs = constrlinpart.args 304 | @assertequal(constrlinargs[1], :+) 305 | for i = 2:length(constrlinargs) 306 | addLinElem!(indicator, densevals, constrlinargs[i]) == 0.0 || 307 | error("Unexpected constant term in linear constraint") 308 | end 309 | idx = findnext(indicator, 1) 310 | while idx != 0 311 | numberOfValues += 1 312 | addnonzero!(colIdx, values, idx - 1, densevals[idx]) # OSiL is 0-based 313 | densevals[idx] = 0.0 # reset for next row 314 | idx = findnext(indicator, idx + 1) 315 | end 316 | fill!(indicator, false) # for Array{Bool}, set to false one element at a time? 317 | add_text(new_child(rowstarts, "el"), string(numberOfValues)) 318 | row += 1 319 | nextrowlinear = MathProgBase.isconstrlinear(d, row) 320 | end 321 | m.numLinConstr = row - 1 322 | if m.numLinConstr > 0 323 | # fill in remaining row starts for nonlinear constraints 324 | for row = m.numLinConstr + 1 : numberOfConstraints 325 | add_text(new_child(rowstarts, "el"), string(numberOfValues)) 326 | end 327 | set_attribute(linConstrCoefs, "numberOfValues", numberOfValues) 328 | end 329 | 330 | numberOfNonlinearExpressions = numberOfConstraints - m.numLinConstr + 331 | (nlobj ? 1 : 0) 332 | if numberOfNonlinearExpressions > 0 333 | # has nonlinear objective or at least 1 nonlinear constraint 334 | nonlinearExpressions = new_child(m.instanceData, 335 | "nonlinearExpressions") 336 | set_attribute(nonlinearExpressions, "numberOfNonlinearExpressions", 337 | numberOfNonlinearExpressions) 338 | if nlobj 339 | nl = new_child(nonlinearExpressions, "nl") 340 | set_attribute(nl, "idx", "-1") 341 | expr2osnl!(nl, MathProgBase.obj_expr(d)) 342 | end 343 | for row = m.numLinConstr + 1 : numberOfConstraints 344 | nl = new_child(nonlinearExpressions, "nl") 345 | set_attribute(nl, "idx", row - 1) # OSiL is 0-based 346 | constrexpr = MathProgBase.constr_expr(d, row) 347 | #(lhs, rhs) = constr2bounds(constrexpr.args...) 348 | if constrexpr.head == :call 349 | @assert(constrexpr.args[1] in [:<=, :(==), :>=]) 350 | constrpart = constrexpr.args[2] 351 | else 352 | @assertequal(constrexpr.head, :comparison) 353 | constrpart = constrexpr.args[end - 2] 354 | end 355 | expr2osnl!(nl, constrpart) 356 | end 357 | end 358 | 359 | return m 360 | end 361 | 362 | function write_osol_file(osol, x0, options) 363 | xdoc = XMLDocument() 364 | xroot = create_root(xdoc, "osol") 365 | set_attribute(xroot, "xmlns", "os.optimizationservices.org") 366 | set_attribute(xroot, "xmlns:xsi", 367 | "http://www.w3.org/2001/XMLSchema-instance") 368 | set_attribute(xroot, "xsi:schemaLocation", 369 | "os.optimizationservices.org " * 370 | "http://www.optimizationservices.org/schemas/2.0/OSoL.xsd") 371 | 372 | optimization = new_child(xroot, "optimization") 373 | if length(x0) > 0 374 | variables = new_child(optimization, "variables") 375 | initialVariableValues = new_child(variables, "initialVariableValues") 376 | set_attribute(initialVariableValues, "numberOfVar", length(x0)) 377 | end 378 | for (idx, val) in enumerate(x0) 379 | vari = new_child(initialVariableValues, "var") 380 | set_attribute(vari, "idx", idx - 1) # OSiL is 0-based 381 | set_attribute(vari, "value", val) 382 | end 383 | 384 | if length(options) > 0 385 | solverOptions = new_child(optimization, "solverOptions") 386 | set_attribute(solverOptions, "numberOfSolverOptions", length(options)) 387 | for optdict in options 388 | solverOption = new_child(solverOptions, "solverOption") 389 | for (argname, argval) in optdict 390 | if symbol(argname) in (:name, :value, :solver, 391 | :category, :type, :description, :numberOfItems) 392 | # TODO: child 's of 's? 393 | set_attribute(solverOption, string(argname), argval) 394 | else 395 | error("Unknown solverOption attribute $argname") 396 | end 397 | end 398 | end 399 | end 400 | 401 | ret = save_file(xdoc, osol) 402 | free(xdoc) 403 | return ret 404 | end 405 | 406 | function read_osrl_file!(m::OsilMathProgModel, osrl) 407 | xdoc = parse_file(osrl, C_NULL, 64) # 64 == XML_PARSE_NOWARNING 408 | xroot = root(xdoc) 409 | # do something with general/generalStatus ? 410 | optimization = find_element(xroot, "optimization") 411 | @assertequal(parse(Int, attribute(optimization, "numberOfVariables")), 412 | m.numberOfVariables) 413 | @assertequal(parse(Int, attribute(optimization, "numberOfConstraints")), 414 | m.numberOfConstraints) 415 | numberOfSolutions = attribute(optimization, "numberOfSolutions") 416 | if numberOfSolutions != "1" 417 | warn("numberOfSolutions expected to be 1, was $numberOfSolutions") 418 | end 419 | solution = find_element(optimization, "solution") 420 | 421 | status = find_element(solution, "status") 422 | statustype = attribute(status, "type") 423 | if statustype == nothing 424 | # OSIpoptSolver needs some fixes for iteration limit exit status 425 | warn("Solution status in $(m.osrl) has no type attribute. Status ", 426 | "content is: ", content(status)) 427 | m.status = :Error 428 | else 429 | if haskey(osrl2jl_status, statustype) 430 | m.status = osrl2jl_status[statustype] 431 | else 432 | error("Unknown solution status type $statustype") 433 | end 434 | statusdescription = attribute(status, "description") 435 | if statusdescription != nothing 436 | # OSBonminSolver and OSCouenneSolver set some funny exit statuses 437 | if statustype == "other" && startswith(statusdescription, "LIMIT") 438 | m.status = :UserLimit 439 | elseif statustype == "error" && (statusdescription == 440 | "The problem is infeasible") 441 | m.status = :Infeasible 442 | elseif statustype == "error" && (statusdescription == 443 | "The problem is unbounded" || 444 | startswith(statusdescription, "CONTINUOUS_UNBOUNDED")) 445 | m.status = :Unbounded 446 | end 447 | end 448 | end 449 | 450 | variables = find_element(solution, "variables") 451 | if variables == nothing 452 | m.solution = fill(NaN, m.numberOfVariables) 453 | (m.status == :Optimal) && warn("status was $statustype but no ", 454 | "variables were present in $osrl") 455 | else 456 | varvalues = find_element(variables, "values") 457 | @assertequal(parse(Int, attribute(varvalues, "numberOfVar")), 458 | m.numberOfVariables) 459 | m.solution = xml2vec(varvalues, m.numberOfVariables) 460 | 461 | # reduced costs 462 | counter = 0 463 | reduced_costs_found = false 464 | for child in child_elements(variables) 465 | if name(child) == "other" 466 | counter += 1 467 | if attribute(child, "name") == "reduced_costs" 468 | @assertequal(parse(Int, attribute(child, "numberOfVar")), 469 | m.numberOfVariables) 470 | if reduced_costs_found 471 | warn("Overwriting existing reduced costs") 472 | end 473 | reduced_costs_found = true 474 | m.reducedcosts = xml2vec(child, m.numberOfVariables) 475 | end 476 | end 477 | end 478 | if !reduced_costs_found 479 | m.reducedcosts = fill(NaN, m.numberOfVariables) 480 | end 481 | numberOfOther = attribute(variables, "numberOfOtherVariableResults") 482 | if numberOfOther == nothing 483 | @assertequal(counter, 0) 484 | else 485 | @assertequal(counter, parse(Int, numberOfOther)) 486 | end 487 | end 488 | 489 | objectives = find_element(solution, "objectives") 490 | if objectives == nothing 491 | m.objval = NaN 492 | (m.status == :Optimal) && warn("status was $statustype but no ", 493 | "objectives were present in $osrl") 494 | else 495 | objvalues = find_element(objectives, "values") 496 | numberOfObj = attribute(objvalues, "numberOfObj") 497 | if numberOfObj != "1" 498 | warn("numberOfObj expected to be 1, was $numberOfObj") 499 | end 500 | m.objval = parse(Float64, content(find_element(objvalues, "obj"))) 501 | end 502 | 503 | # constraint duals 504 | constraints = find_element(solution, "constraints") 505 | if constraints == nothing 506 | m.constrduals = fill(NaN, m.numberOfConstraints) 507 | else 508 | dualValues = find_element(constraints, "dualValues") 509 | @assertequal(parse(Int, attribute(dualValues, "numberOfCon")), 510 | m.numberOfConstraints) 511 | if length(m.quadconidx) == 0 512 | m.constrduals = xml2vec(dualValues, m.numberOfConstraints) 513 | else 514 | # MathProgBase wants quadratic constraint duals separately 515 | # from linear / nonlinear constraint duals 516 | (m.constrduals, m.quadconstrduals) = splitlinquad(m, 517 | xml2vec(dualValues, m.numberOfConstraints)) 518 | end 519 | end 520 | 521 | # TODO: more status details/messages? 522 | free(xdoc) 523 | return m.status 524 | end 525 | 526 | function MathProgBase.optimize!(m::OsilMathProgModel) 527 | if m.objsense == :Max && isdefined(m, :d) && isdefined(m, :vartypes) && 528 | any(x -> !(x == :Cont || x == :Fixed), m.vartypes) 529 | warn("Maximization problems can be buggy with ", 530 | "OSSolverService and MINLP solvers, see ", 531 | "https://projects.coin-or.org/OS/ticket/52. Formulate your ", 532 | "problem as a minimization for more reliable results.") 533 | end 534 | save_file(m.xdoc, m.osil) 535 | if isempty(m.solver) 536 | solvercmd = `` # use default 537 | else 538 | solvercmd = `-solver $(m.solver)` 539 | for opt in filter(x -> !haskey(x, :solver), m.options) 540 | opt[:solver] = m.solver 541 | end 542 | end 543 | if isdefined(m, :x0) 544 | xl, x0, xu = m.xl, m.x0, m.xu 545 | have_warned = false 546 | for i = 1:m.numberOfVariables 547 | if !have_warned && !(xl[i] <= x0[i] <= xu[i]) 548 | warn("Modifying initial conditions to satisfy bounds") 549 | have_warned = true 550 | end 551 | x0[i] = clamp(x0[i], xl[i], xu[i]) 552 | end 553 | write_osol_file(m.osol, x0, m.options) 554 | else 555 | write_osol_file(m.osol, Float64[], m.options) 556 | end 557 | # clear existing content from m.osrl, if any 558 | close(open(m.osrl, "w")) 559 | run(`$OSSolverService -osil $(m.osil) -osol $(m.osol) -osrl $(m.osrl) 560 | $solvercmd -printLevel $(m.printLevel)`) 561 | if filesize(m.osrl) == 0 562 | warn(m.osrl, " is empty") 563 | m.status = :Error 564 | else 565 | read_osrl_file!(m, m.osrl) 566 | end 567 | return m.status 568 | end 569 | 570 | end # module 571 | --------------------------------------------------------------------------------