Документация Engee

Использование пользовательского двоичного файла

Многие решатели написаны не на Julia, а на таких языках, как C или C++. JuMP взаимодействует с такими решателями через двоичные зависимости.

Для многих решателей с открытым исходным кодом соответствующий двоичный файл устанавливается автоматически при выполнении команды Pkg.add("Solver"). Например, Pkg.add("ECOS") также установит двоичный файл ECOS.

На этой странице описывается, как происходит эта установка и как можно использовать пользовательский двоичный файл.

Совместимость

Для выполнения этих инструкций требуется версия не ниже Julia 1.6.

Вводная информация

Каждый решатель, поддерживаемый JuMP, структурирован как пакет Julia. Например, интерфейс для решателя ECOS предоставляется пакетом ECOS.jl.

На этой странице в качестве примера используется пакет ECOS.jl, потому что его легко компилировать. В других решателях действуют те же соглашения. Например, интерфейс для решателя Clp предоставляется пакетом Clp.jl.

Пакет ECOS.jl предоставляет интерфейс между API ECOS на C и MathOptInterface. Однако он не занимается установкой двоичного файла решателя; за это отвечает пакет JLL.

JLL — это пакет Julia, который служит оболочкой для предварительно скомпилированного двоичного файла. Сборка двоичных файлов выполняется с помощью Yggdrasil (например, ECOS), а размещаются они в репозитории JuliaBinaryWrappers на GitHub (например, ECOS_jll.jl).

В пакетах JLL мало кода. Их единственная задача — применять dlopen к динамической библиотеке вместе со всеми зависимостями.

Пакеты JLL управляют своими двоичными зависимостями с помощью системы артефактов Julia. В каждом пакете JLL есть файл Artifacts.toml с описанием того, где находится каждый двоичный артефакт для каждой платформы, на которой он может быть установлен. Вот файл Artifacts.toml для ECOS_jll.jl.

Двоичных файлов, устанавливаемых пакетом JLL, большинству пользователей должно быть достаточно. Однако в редких случаях может потребоваться пользовательский двоичный файл. Вот две основные причины для использования пользовательского двоичного файла:

  • Требуется двоичный файл с пользовательскими настройками компиляции (например, для отладки).

  • Требуется двоичный файл с набором зависимостей, которые отсутствуют в Yggdrasil (например, с зависимостью от коммерческого решателя, такого как Gurobi или CPLEX).

В следующих разделах объясняется, как заменить двоичные файлы, предоставляемые пакетом JLL, на скомпилированные самостоятельно. Напомним, что для простоты мы используем в качестве примера ECOS, но для других решателей процедура та же самая.

Изучение переопределяемого пакета JLL

Сначала нужно изучить структуру и имена файлов пакета JLL, который требуется переопределить.

Местонахождение файлов можно определить с помощью .artifact_dir:

julia> using ECOS_jll

julia> ECOS_jll.artifact_dir
"/Users/oscar/.julia/artifacts/2addb75332eff5a1657b46bb6bf30d2410bc7ecf"

На других компьютерах этот путь может быть иным.

Вот что содержится в папке:

julia> readdir(ECOS_jll.artifact_dir)
4-element Vector{String}:
 "include"
 "lib"
 "logs"
 "share"

julia> readdir(joinpath(ECOS_jll.artifact_dir, "lib"))
1-element Vector{String}:
 "libecos.dylib"

У других решателей может быть каталог bin с исполняемыми файлами. Чтобы использовать пользовательский двоичный файл в ECOS, нужно заменить файл /lib/libecos.dylib им.

Компиляция пользовательского двоичного файла

Следующий шаг — компиляция пользовательского двоичного файла. Так как решатель ECOS написан на языке C без каких-либо зависимостей, это легко сделать при наличии компилятора C:

oscar@Oscars-MBP jll_example % git clone https://github.com/embotech/ecos.git
[... lines omitted ...]
oscar@Oscars-MBP jll_example % cd ecos
oscar@Oscars-MBP ecos % make shared
[... many lines omitted...]
oscar@Oscars-MBP ecos % mkdir lib
oscar@Oscars-MBP ecos % cp libecos.dylib lib

Компиляция пользовательских двоичных файлов решателей — сложная операция. Из-за сложностей, связанных с компиляцией различных решателей, сообщество JuMP не сможет помочь вам в диагностике и устранении проблемы компиляции.

После этого этапа компиляции появляется папка /tmp/jll_example/ecos, содержащая каталоги lib и include с теми же файлами, что и в ECOS_jll:

julia> readdir(joinpath("ecos", "lib"))
1-element Vector{String}:
 "libecos.dylib"

Переопределение одной библиотеки

Чтобы переопределить библиотеку libecos, нужно знать, как она называется в ECOS_jll. (В большинстве случаев она также будет называться libecos, но не всегда.)

Проверить это можно двумя способами.

  1. Прочитайте информацию в конце файла сведений пакета JLL на GitHub. Например, для

ECOS_jll есть единственный элемент LibraryProduct с именем libecos.

  1. Введите ECOS_jll. и дважды нажмите клавишу [TAB] для автоматического заполнения

доступных вариантов:

julia> ECOS_jll.
LIBPATH           PATH_list          best_wrapper       get_libecos_path   libecos_handle
LIBPATH_list      __init__           dev_jll            is_available       libecos_path
PATH              artifact_dir       find_artifact_dir  libecos

Here you can see there is libecos, and more usefully for us, libecos_path.

Узнав имя переопределяемой переменной (заканчивающееся на _path), используйте Preferences.jl, чтобы указать новый путь:

using Preferences
set_preferences!(
    "LocalPreferences.toml",
    "ECOS_jll",
    "libecos_path" => "/tmp/jll_example/ecos/lib/libecos"
)

В результате в текущем каталоге будет создан файл с именем LocalPreferences.toml и таким содержимым:

[ECOS_jll]
libecos_path = "/tmp/jll_example/ecos/lib/libecos"

Теперь, если перезапустить Julia, получится следующее.

julia> using ECOS_jll

julia> ECOS_jll.libecos
"/tmp/jll_example/ecos/lib/libecos"

Чтобы вернуться к использованию библиотеки по умолчанию, просто удалите файл LocalPreferences.toml.

Переопределение всего артефакта

Иногда решатель может предоставлять несколько библиотек и исполняемых файлов, и указывать пути для всех них утомительно. В этом случае можно заменить весь артефакт с помощью файла Override.toml Julia.

Для переопределения всего артефакта необходимо воспроизвести структуру и содержимое пакета JLL, рассмотренные выше.

В большинстве случаев достаточно воссоздать каталоги include, lib и bin (если они есть). Каталоги logs и share можно спокойно пропустить. Хорошо запомните, какие файлы содержатся в каждом каталоге и как они называются.

В нашем примере с ECOS мы уже воспроизвели структуру, когда скомпилировали ECOS.

Поэтому теперь нужно сообщить Julia, что должна использоваться пользовательская установка вместо установки по умолчанию. Для этого можно создать файл переопределения по пути ~/.julia/artifacts/Overrides.toml.

Overrides.toml имеет следующее содержимое:

# Переопределение для ECOS_jll
2addb75332eff5a1657b46bb6bf30d2410bc7ecf = "/tmp/jll_example/ecos"

где 2addb75332eff5a1657b46bb6bf30d2410bc7ecf — папка из исходного каталога ECOS_jll.artifact_dir, а "/tmp/jll_example/ecos" — расположение новой установки. Замените их соответствующим образом для вашей системы.

Если перезапустить Julia после создания файла переопределения, вы увидите следующее.

julia> using ECOS_jll

julia> ECOS_jll.artifact_dir
"/tmp/jll_example/ecos"

Теперь при использовании ECOS будет применяться пользовательский двоичный файл.

Использование Cbc с пользовательским двоичным файлом

В качестве еще одного примера продемонстрируем использование Cbc.jl с пользовательским двоичным файлом.

Изучение переопределяемого пакета JLL

Сначала узнаем, где установлен пакет Cbc_jll:

julia> using Cbc_jll

julia> Cbc_jll.artifact_dir
"/Users/oscar/.julia/artifacts/e481bc81db5e229ba1f52b2b4bd57484204b1b06"

julia> readdir(Cbc_jll.artifact_dir)
5-element Vector{String}:
 "bin"
 "include"
 "lib"
 "logs"
 "share"

julia> readdir(joinpath(Cbc_jll.artifact_dir, "bin"))
1-element Vector{String}:
 "cbc"

julia> readdir(joinpath(Cbc_jll.artifact_dir, "lib"))
10-element Vector{String}:
 "libCbc.3.10.5.dylib"
 "libCbc.3.dylib"
 "libCbc.dylib"
 "libCbcSolver.3.10.5.dylib"
 "libCbcSolver.3.dylib"
 "libCbcSolver.dylib"
 "libOsiCbc.3.10.5.dylib"
 "libOsiCbc.3.dylib"
 "libOsiCbc.dylib"
 "pkgconfig"

Компиляция пользовательского двоичного файла

Далее необходимо скомпилировать Cbc. Компиляция пакета Cbc может быть непростой задачей (у него много зависимостей), но для пользователей macOS есть рецепт Homebrew:

(base) oscar@Oscars-MBP jll_example % brew install cbc
[ ... lines omitted ... ]
(base) oscar@Oscars-MBP jll_example % brew list cbc
/usr/local/Cellar/cbc/2.10.5/bin/cbc
/usr/local/Cellar/cbc/2.10.5/include/cbc/ (76 files)
/usr/local/Cellar/cbc/2.10.5/lib/libCbc.3.10.5.dylib
/usr/local/Cellar/cbc/2.10.5/lib/libCbcSolver.3.10.5.dylib
/usr/local/Cellar/cbc/2.10.5/lib/libOsiCbc.3.10.5.dylib
/usr/local/Cellar/cbc/2.10.5/lib/pkgconfig/ (2 files)
/usr/local/Cellar/cbc/2.10.5/lib/ (6 other files)
/usr/local/Cellar/cbc/2.10.5/share/cbc/ (59 files)
/usr/local/Cellar/cbc/2.10.5/share/coin/ (4 files)

Переопределение отдельных библиотек

Для переопределения определенных библиотек с помощью Preferences.jl сначала нужно проверить имя каждой библиотеки в Cbc_jll:

julia> Cbc_jll.
LIBPATH               cbc                    get_libcbcsolver_path  libOsiCbc_path
LIBPATH_list          cbc_path               is_available           libcbcsolver
PATH                  dev_jll                libCbc                 libcbcsolver_handle
PATH_list             find_artifact_dir      libCbc_handle          libcbcsolver_path
__init__              get_cbc_path           libCbc_path
artifact_dir          get_libCbc_path        libOsiCbc
best_wrapper          get_libOsiCbc_path     libOsiCbc_handle

Затем в файл LocalPreferences.toml нужно добавить следующее:

[Cbc_jll]
cbc_path = "/usr/local/Cellar/cbc/2.10.5/bin/cbc"
libCbc_path = "/usr/local/Cellar/cbc/2.10.5/lib/libCbc.3.10.5"
libOsiCbc_path = "/usr/local/Cellar/cbc/2.10.5/lib/libOsiCbc.3.10.5"
libcbcsolver_path = "/usr/local/Cellar/cbc/2.10.5/lib/libCbcSolver.3.10.5"

Обратите внимание, что регистр символов учитывается, поэтому libcbcsolver_path соответствует libCbcSolver.3.10.5.

Переопределение всего артефакта

Чтобы использовать установку Homebrew в качестве пользовательского двоичного файла, нужно добавить в файл ~/.julia/artifacts/Overrides.toml следующее:

# Переопределение для Cbc_jll
e481bc81db5e229ba1f52b2b4bd57484204b1b06 = "/usr/local/Cellar/cbc/2.10.5"