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

Сокращение времени компиляции, оптимизация времени выполнения и низкое использование зависимостей

Хотя в DifferentialEquations.jl по умолчанию установлен баланс между производительностью во время выполнения и компиляции, пользователи должны знать, что существует множество вариантов управления размером зависимостей и поведением кэширования при компиляции для дальнейшей детализации этого компромисса. Для таких механизмов управления доступны следующие методы.

Управление специализацией и предварительной компиляцией функции

По умолчанию решатели DifferentialEquations.jl используют технику заключения функций в оболочку в целях полной прекомпиляции решателей и, соответственно, уменьшения времени компиляции. Однако в некоторых случаях может потребоваться контролировать это поведение, чтобы попытаться достигнуть более быстрого времени выполнения или более быстрого времени компиляции. Это можно сделать с помощью аргументов specialization конструкторов AbstractDEProblem.

Например, в задаче ODEProblem имеется ODEProblem{iip,specialize}(...). Этот второй параметр типа управляет уровнем специализации со следующими вариантами.

  • SciMLBase.AutoSpecialize: задано по умолчанию. Использует схему позднего заключения в оболочку, чтобы соблюсти баланс между временем выполнения и временем компиляции.

  • SciMLBase.NoSpecialize: никогда не будет специализировать составляющие функции, имея наименьшее время компиляции, но наибольшее время выполнения.

  • SciMLBase.FullSpecialize: полностью переспециализирует решатель для заданного уравнения ODE, достигая наиболее быстрого времени выполнения при увеличении времени компиляции. Рекомендуется при проведении сравнительного анализа и выполнении длительных вычислений, например в циклах оптимизации.

Более подробную информацию об уровнях специализации можно найти в документации к SciMLBase по уровням специализации.

В пакете DifferentialEquations.jl и его ODE-пакете OrdinaryDiffEq.jl предварительно скомпилированы некоторые стандартные типы задач и решатели. Типы задач включают три описанных выше уровня специализации и настройку по умолчанию. В число решателей входят следующие:

  • стандартные решатели для нежестких задач, такие как Tsit5()

  • стандартные решатели для жестких задач, такие как Rosenbrock23()

  • стандартные решатели с определением жесткости, такие как AutoTsit5(Rosenbrock23())

  • методы с низкой емкостью для законов сохранения, такие как SSPRK43() (по умолчанию предварительная компиляция отключена)

Для изменения объема предварительной компиляции можно использовать Preferences.jl. Например, чтобы отключить предварительную компиляцию для нестандартных типов задач (уровней специализации) и всех жестких/неявных/низкоемкостных решателей, в активном проекте можно выполнить следующий код.

using Preferences, UUIDs
set_preferences!(UUID("1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"), "PrecompileNonStiff" => true)
set_preferences!(UUID("1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"), "PrecompileStiff" => false)
set_preferences!(UUID("1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"), "PrecompileAutoSwitch" => false)
set_preferences!(UUID("1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"), "PrecompileLowStorage" => false)
set_preferences!(UUID("1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"), "PrecompileDefaultSpecialize" => true)
set_preferences!(UUID("1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"), "PrecompileAutoSpecialize" => false)
set_preferences!(UUID("1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"), "PrecompileFunctionWrapperSpecialize" => false)
set_preferences!(UUID("1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"), "PrecompileNoSpecialize" => false)

При этом будет создан файл LocalPreferences.toml рядом с активным в данный момент файлом Project.toml.

Уменьшение размера зависимостей за счет прямой зависимости от конкретных решателей

DifferentialEquations.jl — это большая библиотека, содержащая функциональность множества различных решателей и пакетов дополнений. Однако часто возникает необходимость сократить размер зависимости и использовать только те части библиотеки, которые необходимы для приложения. Это можно сделать благодаря модульной структуре пакетов SciML.

Распространенный пример: использование только OrdinaryDiffEq.jl

Одним из распространенных примеров является использование только ODE-решателей OrdinaryDiffEq.jl. Все решатели повторно экспортируют SciMLBase.jl (где хранятся типы задач и решений), поэтому достаточно только OrdinaryDiffEq.jl. Таким образом, замена

using DifferentialEquations

на

#Сначала добавить пакет OrdinaryDiffEq.
#Using Pkg; Pkg.add("OrdinaryDiffEq")
using OrdinaryDiffEq

будет работать, если вы используете только эти функции.

Обобщение идеи

В общем случае вам всегда будет нужна библиотека SciMLBase.jl, поскольку в ней определены все фундаментальные типы, но решатели будут автоматически экспортировать ее повторно. Для решателей, как правило, требуется только пакет решателя. Таким образом, сочетания SciMLBase+Sundials, SciMLBase+LSODA и т. д. предоставят общий интерфейс с настройкой конкретного решателя. SciMLBase.jl является очень простой зависимостью, поэтому с ней нет никаких проблем.

Для работы с пакетами дополнений обычно требуется SciMLBase, выбранный вами пакет решателя и пакет дополнений. Так, например, для предопределенных обратных вызовов, скорее всего, понадобится сочетание SciMLBase+OrdinaryDiffEq+DiffEqCallbacks. Если вы не уверены, из какого пакета взята конкретная команда, используйте @which. Например, из документации по обратным вызовам мы имеем следующее.

using DifferentialEquations
function fitz(du, u, p, t)
    V, R = u
    a, b, c = p
    du[1] = c * (V - V^3 / 3 + R)
    du[2] = -(1 / c) * (V - a - b * R)
end
u0 = [-1.0; 1.0]
tspan = (0.0, 20.0)
p = (0.2, 0.2, 3.0)
prob = ODEProblem(fitz, u0, tspan, p)
cb = ProbIntsUncertainty(0.2, 1)
ensemble_prob = EnsembleProblem(prob)
sim = solve(ensemble_prob, Euler(), trajectories = 100, callback = cb, dt = 1 / 10)
EnsembleSolution Solution of length 100 with uType:
ODESolution{Float64, 2, Vector{Vector{Float64}}, Nothing, Nothing, Vector{Float64}, Vector{Vector{Vector{Float64}}}, ODEProblem{Vector{Float64}, Tuple{Float64, Float64}, true, Tuple{Float64, Float64, Float64}, ODEFunction{true, SciMLBase.AutoSpecialize, FunctionWrappersWrappers.FunctionWrappersWrapper{Tuple{FunctionWrappers.FunctionWrapper{Nothing, Tuple{Vector{Float64}, Vector{Float64}, Tuple{Float64, Float64, Float64}, Float64}}, FunctionWrappers.FunctionWrapper{Nothing, Tuple{Vector{ForwardDiff.Dual{ForwardDiff.Tag{DiffEqBase.OrdinaryDiffEqTag, Float64}, Float64, 1}}, Vector{ForwardDiff.Dual{ForwardDiff.Tag{DiffEqBase.OrdinaryDiffEqTag, Float64}, Float64, 1}}, Tuple{Float64, Float64, Float64}, Float64}}, FunctionWrappers.FunctionWrapper{Nothing, Tuple{Vector{ForwardDiff.Dual{ForwardDiff.Tag{DiffEqBase.OrdinaryDiffEqTag, Float64}, Float64, 1}}, Vector{Float64}, Tuple{Float64, Float64, Float64}, ForwardDiff.Dual{ForwardDiff.Tag{DiffEqBase.OrdinaryDiffEqTag, Float64}, Float64, 1}}}, FunctionWrappers.FunctionWrapper{Nothing, Tuple{Vector{ForwardDiff.Dual{ForwardDiff.Tag{DiffEqBase.OrdinaryDiffEqTag, Float64}, Float64, 1}}, Vector{ForwardDiff.Dual{ForwardDiff.Tag{DiffEqBase.OrdinaryDiffEqTag, Float64}, Float64, 1}}, Tuple{Float64, Float64, Float64}, ForwardDiff.Dual{ForwardDiff.Tag{DiffEqBase.OrdinaryDiffEqTag, Float64}, Float64, 1}}}}, false}, UniformScaling{Bool}, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, typeof(SciMLBase.DEFAULT_OBSERVED), Nothing, Nothing}, Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}}, SciMLBase.StandardODEProblem}, Euler, OrdinaryDiffEq.InterpolationData{ODEFunction{true, SciMLBase.AutoSpecialize, FunctionWrappersWrappers.FunctionWrappersWrapper{Tuple{FunctionWrappers.FunctionWrapper{Nothing, Tuple{Vector{Float64}, Vector{Float64}, Tuple{Float64, Float64, Float64}, Float64}}, FunctionWrappers.FunctionWrapper{Nothing, Tuple{Vector{ForwardDiff.Dual{ForwardDiff.Tag{DiffEqBase.OrdinaryDiffEqTag, Float64}, Float64, 1}}, Vector{ForwardDiff.Dual{ForwardDiff.Tag{DiffEqBase.OrdinaryDiffEqTag, Float64}, Float64, 1}}, Tuple{Float64, Float64, Float64}, Float64}}, FunctionWrappers.FunctionWrapper{Nothing, Tuple{Vector{ForwardDiff.Dual{ForwardDiff.Tag{DiffEqBase.OrdinaryDiffEqTag, Float64}, Float64, 1}}, Vector{Float64}, Tuple{Float64, Float64, Float64}, ForwardDiff.Dual{ForwardDiff.Tag{DiffEqBase.OrdinaryDiffEqTag, Float64}, Float64, 1}}}, FunctionWrappers.FunctionWrapper{Nothing, Tuple{Vector{ForwardDiff.Dual{ForwardDiff.Tag{DiffEqBase.OrdinaryDiffEqTag, Float64}, Float64, 1}}, Vector{ForwardDiff.Dual{ForwardDiff.Tag{DiffEqBase.OrdinaryDiffEqTag, Float64}, Float64, 1}}, Tuple{Float64, Float64, Float64}, ForwardDiff.Dual{ForwardDiff.Tag{DiffEqBase.OrdinaryDiffEqTag, Float64}, Float64, 1}}}}, false}, UniformScaling{Bool}, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, typeof(SciMLBase.DEFAULT_OBSERVED), Nothing, Nothing}, Vector{Vector{Float64}}, Vector{Float64}, Vector{Vector{Vector{Float64}}}, OrdinaryDiffEq.EulerCache{Vector{Float64}, Vector{Float64}}}, DiffEqBase.Stats, Nothing}

Если нужно узнать источник ProbIntsUncertainty(0.2,1), можно сделать следующее.

@which ProbIntsUncertainty(0.2, 1)

ProbIntsUncertainty(σ, order) in DiffEqCallbacks at /root/.julia/packages/DiffEqCallbacks/G7xUH/src/probints.jl:33

Здесь указано, что он находится в пакете DiffEqCallbacks.jl. Таким образом, в данном случае можно сделать следующее:

using OrdinaryDiffEq, DiffEqCallbacks
function fitz(du, u, p, t)
    V, R = u
    a, b, c = p
    du[1] = c * (V - V^3 / 3 + R)
    du[2] = -(1 / c) * (V - a - b * R)
end
u0 = [-1.0; 1.0]
tspan = (0.0, 20.0)
p = (0.2, 0.2, 3.0)
prob = ODEProblem(fitz, u0, tspan, p)
cb = ProbIntsUncertainty(0.2, 1)
ensemble_prob = EnsembleProblem(prob)
sim = solve(ensemble_prob, Euler(), trajectories = 100, callback = cb, dt = 1 / 10)
EnsembleSolution Solution of length 100 with uType:
ODESolution{Float64, 2, Vector{Vector{Float64}}, Nothing, Nothing, Vector{Float64}, Vector{Vector{Vector{Float64}}}, ODEProblem{Vector{Float64}, Tuple{Float64, Float64}, true, Tuple{Float64, Float64, Float64}, ODEFunction{true, SciMLBase.AutoSpecialize, FunctionWrappersWrappers.FunctionWrappersWrapper{Tuple{FunctionWrappers.FunctionWrapper{Nothing, Tuple{Vector{Float64}, Vector{Float64}, Tuple{Float64, Float64, Float64}, Float64}}, FunctionWrappers.FunctionWrapper{Nothing, Tuple{Vector{ForwardDiff.Dual{ForwardDiff.Tag{DiffEqBase.OrdinaryDiffEqTag, Float64}, Float64, 1}}, Vector{ForwardDiff.Dual{ForwardDiff.Tag{DiffEqBase.OrdinaryDiffEqTag, Float64}, Float64, 1}}, Tuple{Float64, Float64, Float64}, Float64}}, FunctionWrappers.FunctionWrapper{Nothing, Tuple{Vector{ForwardDiff.Dual{ForwardDiff.Tag{DiffEqBase.OrdinaryDiffEqTag, Float64}, Float64, 1}}, Vector{Float64}, Tuple{Float64, Float64, Float64}, ForwardDiff.Dual{ForwardDiff.Tag{DiffEqBase.OrdinaryDiffEqTag, Float64}, Float64, 1}}}, FunctionWrappers.FunctionWrapper{Nothing, Tuple{Vector{ForwardDiff.Dual{ForwardDiff.Tag{DiffEqBase.OrdinaryDiffEqTag, Float64}, Float64, 1}}, Vector{ForwardDiff.Dual{ForwardDiff.Tag{DiffEqBase.OrdinaryDiffEqTag, Float64}, Float64, 1}}, Tuple{Float64, Float64, Float64}, ForwardDiff.Dual{ForwardDiff.Tag{DiffEqBase.OrdinaryDiffEqTag, Float64}, Float64, 1}}}}, false}, UniformScaling{Bool}, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, typeof(SciMLBase.DEFAULT_OBSERVED), Nothing, Nothing}, Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}}, SciMLBase.StandardODEProblem}, Euler, OrdinaryDiffEq.InterpolationData{ODEFunction{true, SciMLBase.AutoSpecialize, FunctionWrappersWrappers.FunctionWrappersWrapper{Tuple{FunctionWrappers.FunctionWrapper{Nothing, Tuple{Vector{Float64}, Vector{Float64}, Tuple{Float64, Float64, Float64}, Float64}}, FunctionWrappers.FunctionWrapper{Nothing, Tuple{Vector{ForwardDiff.Dual{ForwardDiff.Tag{DiffEqBase.OrdinaryDiffEqTag, Float64}, Float64, 1}}, Vector{ForwardDiff.Dual{ForwardDiff.Tag{DiffEqBase.OrdinaryDiffEqTag, Float64}, Float64, 1}}, Tuple{Float64, Float64, Float64}, Float64}}, FunctionWrappers.FunctionWrapper{Nothing, Tuple{Vector{ForwardDiff.Dual{ForwardDiff.Tag{DiffEqBase.OrdinaryDiffEqTag, Float64}, Float64, 1}}, Vector{Float64}, Tuple{Float64, Float64, Float64}, ForwardDiff.Dual{ForwardDiff.Tag{DiffEqBase.OrdinaryDiffEqTag, Float64}, Float64, 1}}}, FunctionWrappers.FunctionWrapper{Nothing, Tuple{Vector{ForwardDiff.Dual{ForwardDiff.Tag{DiffEqBase.OrdinaryDiffEqTag, Float64}, Float64, 1}}, Vector{ForwardDiff.Dual{ForwardDiff.Tag{DiffEqBase.OrdinaryDiffEqTag, Float64}, Float64, 1}}, Tuple{Float64, Float64, Float64}, ForwardDiff.Dual{ForwardDiff.Tag{DiffEqBase.OrdinaryDiffEqTag, Float64}, Float64, 1}}}}, false}, UniformScaling{Bool}, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, typeof(SciMLBase.DEFAULT_OBSERVED), Nothing, Nothing}, Vector{Vector{Float64}}, Vector{Float64}, Vector{Vector{Vector{Float64}}}, OrdinaryDiffEq.EulerCache{Vector{Float64}, Vector{Float64}}}, DiffEqBase.Stats, Nothing}

вместо полного using DifferentialEquations. Обратите внимание, что в силу особенностей функционирования зависимостей Julia будет работать любая внутренняя функция пакета. Единственные зависимости, которые необходимо использовать (using) явным образом, — это специально вызываемые функции. Таким образом, этот метод можно применять для определения всех используемых пакетов DiffEq.