Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "Penopt"
uuid = "fb8f6f4e-b8f1-11e9-2b95-bdba3fec731e"
authors = ["Michal Kocvara <kocvara@penopt.com>", "Benoît Legat <benoit.legat@gmail.com>", "Michael Stingl <mstingl@penopt.com>"]
version = "0.0.0"
version = "0.1.0"

[deps]
Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
Expand All @@ -12,7 +12,7 @@ OpenBLAS32_jll = "656ef2d0-ae68-5445-9ca0-591084a874a2"
[compat]
Libdl = "1.10"
LinearAlgebra = "1.10"
MathOptInterface = "1"
MathOptInterface = "1.54"
OpenBLAS32_jll = "0.3"
julia = "1.10"

Expand Down
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,28 @@ location of the library, update `PENOPT_LIBPENBMI`, re-run

Whether PENBMI is available is given by `Penopt.has_penbmi()`.

### PENNON

The nonlinear PENNON backend is enabled by setting `PENOPT_LIBPENNON` to a
PENNON shared library or static archive, re-running `Pkg.build("Penopt")`, and
restarting Julia, as for PENBMI. Availability is reported by
`Penopt.has_pennon()`.

`Penopt.Pennon.Optimizer` accepts nonlinear scalar objectives and constraints,
and accepts nonlinear semidefinite constraints as an
`MOI.VectorNonlinearFunction` in an
`MOI.PositiveSemidefiniteConeTriangle`. Derivatives are evaluated with MOI's
sparse reverse-mode automatic differentiation.

You can test the installation with `using Pkg; Pkg.test("Penopt")` in a Julia
session.

## Use with JuMP

Pick the solver explicitly: `Penopt.SDP.Optimizer` solves semidefinite programs
with PENSDP, `Penopt.BMI.Optimizer` solves bilinear matrix inequalities and
quadratic objectives with PENBMI.
quadratic objectives with PENBMI, and `Penopt.Pennon.Optimizer` solves general
nonlinear semidefinite programs with PENNON.

```julia
using JuMP, Penopt
Expand Down
67 changes: 49 additions & 18 deletions deps/build.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@
# https://github.com/kocvara/pensdp
#
# PENSDP is the free SDP-only variant of the PENOPT family of solvers. The
# BMI-capable PENBMI is a commercial product; if the user has a PENBMI license,
# the path to `libpenbmi` can be provided via the `PENOPT_LIBPENBMI`
# environment variable and it will be used in addition to the auto-installed
# PENSDP library. The path may point either to a shared library, which is used
# as is, or to the static archive shipped by PENOPT, in which case a shared
# library is linked from it here.
# BMI-capable PENBMI and nonlinear PENNON libraries may be provided with the
# `PENOPT_LIBPENBMI` and `PENOPT_LIBPENNON` environment variables. A path may
# point either to a shared library, which is used as is, or to the static
# archive shipped by PENOPT, in which case a shared library is linked here.

using Libdl

Expand Down Expand Up @@ -120,43 +118,49 @@ end
# shared library with an unusual name is still recognized as such.
_is_static_library(path) = open(io -> read(io, 8), path) == b"!<arch>\n"

# PENBMI is distributed as a static library. Julia can only `ccall` into a
# shared library so the archive is relinked into one, with all its objects
# force-loaded so that the `penbmi` symbol is exported.
function _build_libpenbmi(archive)
# Julia can only `ccall` into a shared library, so PENOPT static archives are
# relinked here. `entrypoint` avoids force-loading optional objects that depend
# on interfaces, such as AMPL, that are not used by this wrapper.
function _build_penopt_library(archive, name; entrypoint = nothing)
mkpath(LIB_DIR)
output = joinpath(LIB_DIR, "libpenbmi.$(Libdl.dlext)")
output = joinpath(LIB_DIR, "lib$(name).$(Libdl.dlext)")
julia_libdir = joinpath(Sys.BINDIR, Base.LIBDIR, "julia")
os, _ = _platform()
if os == :linux
# PENBMI calls the LP64 BLAS and LAPACK, so the 32-bit-integer OpenBLAS
# is linked in rather than the ILP64 one that Julia itself uses.
libopenblas = OpenBLAS32_jll.libopenblas_path
archive_args = entrypoint === nothing ?
`-Wl,--whole-archive $(archive) -Wl,--no-whole-archive` :
`-Wl,-u,$(entrypoint) $(archive)`
cmd = `gcc -shared -fPIC -o $(output)
-Wl,--no-undefined
-Wl,--whole-archive $(archive) -Wl,--no-whole-archive
-Wl,--no-undefined $(archive_args)
-L$(julia_libdir) -Wl,-rpath,$(julia_libdir)
$(libopenblas) -Wl,-rpath,$(dirname(libopenblas))
-lgfortran -lpthread -ldl -lm`
elseif os == :apple
archive_args = entrypoint === nothing ?
`-Wl,-force_load,$(archive)` : `-Wl,-u,_$(entrypoint) $(archive)`
cmd = `gcc -dynamiclib -o $(output)
-Wl,-force_load,$(archive)
$(archive_args)
-framework Accelerate
-L$(julia_libdir) -Wl,-rpath,$(julia_libdir)
-lgfortran -lpthread -lm`
else
error(
"Linking `$(archive)` into a shared library is currently " *
"implemented for Linux and macOS only. On Windows, build " *
"`penbmi.dll` manually, point `PENOPT_LIBPENBMI` at it and " *
"re-run `Pkg.build(\"Penopt\")`.",
"`lib$(name).dll` manually, point the corresponding PENOPT " *
"environment variable at it, and re-run `Pkg.build(\"Penopt\")`.",
)
end
@info "Linking shared library" cmd
run(cmd)
return output
end

_build_libpenbmi(archive) = _build_penopt_library(archive, "penbmi")

function _libpenbmi_path()
path = get(ENV, "PENOPT_LIBPENBMI", "")
if isempty(path)
Expand All @@ -170,7 +174,21 @@ function _libpenbmi_path()
return _is_static_library(path) ? _build_libpenbmi(path) : path
end

function _write_deps(libpensdp_path, libpenbmi_path)
function _libpennon_path()
path = get(ENV, "PENOPT_LIBPENNON", "")
if isempty(path)
return path
elseif !isfile(path)
error(
"The `PENOPT_LIBPENNON` environment variable is set to " *
"`$(path)` but no such file exists.",
)
end
return _is_static_library(path) ?
_build_penopt_library(path, "pennon"; entrypoint = "pennlp") : path
end

function _write_deps(libpensdp_path, libpenbmi_path, libpennon_path)
open(joinpath(DEPS_DIR, "deps.jl"), "w") do io
println(io, "# Auto-generated by deps/build.jl - do not edit.")
println(io, "import Libdl")
Expand All @@ -180,6 +198,7 @@ function _write_deps(libpensdp_path, libpenbmi_path)
# string. `has_penbmi()` guards every ccall in `Penopt.jl`, so the
# `ccall((:penbmi, libpenbmi), ...)` site is never reached.
println(io, "const libpenbmi = ", repr(libpenbmi_path))
println(io, "const libpennon = ", repr(libpennon_path))
println(io)
println(io, """
function check_deps()
Expand All @@ -203,6 +222,14 @@ function _write_deps(libpensdp_path, libpenbmi_path)
error(\"\$(libpenbmi) cannot be opened.\")
end
end
if !isempty(libpennon)
if !isfile(libpennon)
error("\$(libpennon) does not exist.")
end
if Libdl.dlopen_e(libpennon) == C_NULL
error("\$(libpennon) cannot be opened.")
end
end
return
end""")
end
Expand All @@ -211,13 +238,17 @@ end

function main()
libpenbmi_path = _libpenbmi_path()
libpennon_path = _libpennon_path()
extract_dir = _download_and_extract()
libpensdp_path = _build_shared_library(extract_dir)
_write_deps(libpensdp_path, libpenbmi_path)
_write_deps(libpensdp_path, libpenbmi_path, libpennon_path)
@info "Penopt: PENSDP installed at $(libpensdp_path)"
if !isempty(libpenbmi_path)
@info "Penopt: PENBMI installed at $(libpenbmi_path)"
end
if !isempty(libpennon_path)
@info "Penopt: PENNON installed at $(libpennon_path)"
end
return
end

Expand Down
Loading
Loading