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

Стохастические дифференциальные уравнения

В этом руководстве содержатся сведения о функциональных возможностях для решения стохастических дифференциальных уравнений (SDE). Другие руководства можно найти в SciMLTutorials.jl.

Здесь предполагается, что вы ознакомились с руководством об обыкновенных дифференциальных уравнениях.

Пример 1. Скалярные стохастические дифференциальные уравнения

В этом примере мы будем решать уравнение

где и . Благодаря стохастическому исчислению мы знаем, что решение этого уравнения имеет следующий вид:

Для численного решения мы определяем тип задачи, задавая ей уравнение и начальное условие:

using DifferentialEquations
α = 1
β = 1
u₀ = 1 / 2
f(u, p, t) = α * u
g(u, p, t) = β * u
dt = 1 // 2^(4)
tspan = (0.0, 1.0)
prob = SDEProblem(f, g, u₀, (0.0, 1.0))
SDEProblem with uType Float64 and tType Float64. In-place: false
timespan: (0.0, 1.0)
u0: 0.5

Интерфейс solve такой же, как и в случае с ODE. Здесь мы воспользуемся классическим алгоритмом Эйлера-Маруямы EM и построим решение:

sol = solve(prob, EM(), dt = dt)
using Plots
plot(sol)

Использование методов более высокого порядка

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

f_analytic(u₀, p, t, W) = u₀ * exp((α - (β^2) / 2) * t + β * W)
ff = SDEFunction(f, g, analytic = f_analytic)
prob = SDEProblem(ff, g, u₀, (0.0, 1.0))
SDEProblem with uType Float64 and tType Float64. In-place: false
timespan: (0.0, 1.0)
u0: 0.5

и затем передаем эту информацию решателю и построим следующее:

#Мы можем построить классический алгоритм Эйлера-Маруямы следующим образом.
sol = solve(prob, EM(), dt = dt)
plot(sol, plot_analytic = true)

Для получения более точного результата можно выбрать решатель более высокого порядка:

sol = solve(prob, SRIW1(), dt = dt, adaptive = false)
plot(sol, plot_analytic = true)

По умолчанию методам высших порядков свойственна адаптивность. Поэтому можно использовать

sol = solve(prob, SRIW1())
plot(sol, plot_analytic = true)

Здесь мы позволили решателю автоматически определить начальный промежуток времени dt. Это значение в начале является консервативным (небольшим), чтобы обеспечить точность. Но можно запустить метод с большим значением dt, передав значение для начального промежутка dt:

sol = solve(prob, SRIW1(), dt = dt)
plot(sol, plot_analytic = true)

Ансамблевое моделирование

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

ensembleprob = EnsembleProblem(prob)
EnsembleProblem with problem SDEProblem

Команды решателя определены на странице о параллельном ансамблевом моделировании. Например, можно выбрать 1000 траекторий с помощью trajectories=1000. Кроме того, при добавлении дополнительных процессов с помощью функции addprocs() будет автоматически выполняться распараллеливание с использованием собственного параллелизма Julia, но этот вариант можно изменить на использование многопоточности с помощью EnsembleThreads(). В совокупности это выглядит следующим образом.

sol = solve(ensembleprob, EnsembleThreads(), trajectories = 1000)
EnsembleSolution Solution of length 1000 with uType:
RODESolution{Float64, 1, Vector{Float64}, Vector{Float64}, Dict{Symbol, Float64}, Vector{Float64}, NoiseProcess{Float64, 1, Float64, Float64, Float64, Vector{Float64}, typeof(DiffEqNoiseProcess.WHITE_NOISE_DIST), typeof(DiffEqNoiseProcess.WHITE_NOISE_BRIDGE), false, ResettableStacks.ResettableStack{Tuple{Float64, Float64, Float64}, false}, ResettableStacks.ResettableStack{Tuple{Float64, Float64, Float64}, false}, RSWM{Float64}, Nothing, RandomNumbers.Xorshifts.Xoroshiro128Plus}, SDEProblem{Float64, Tuple{Float64, Float64}, false, SciMLBase.NullParameters, Nothing, SDEFunction{false, SciMLBase.FullSpecialize, typeof(Main.f), typeof(Main.g), UniformScaling{Bool}, typeof(Main.f_analytic), Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, typeof(SciMLBase.DEFAULT_OBSERVED), Nothing, Nothing}, typeof(Main.g), Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}}, Nothing}, SOSRI, StochasticDiffEq.LinearInterpolationData{Vector{Float64}, Vector{Float64}}, DiffEqBase.Stats, Nothing}

!!! warn При использовании пользовательского процесса шума, возможно, потребуется указать его в пользовательской функции prob_func в конструкторе EnsembleProblem, так как для каждой траектории необходим свой процесс шума.

Множество других элементов управления, включая инструменты анализа, определены на странице об ансамблевом моделировании. Очень простой анализ можно выполнить с помощью функции EnsembleSummary, которая строит статистику средних/квадратичных значений и имеет соответствующий шаблон графика. Например, можно получить статистику на каждом временном шаге 0.01 и построить график среднее + погрешность, используя следующее.

using DifferentialEquations.EnsembleAnalysis
summ = EnsembleSummary(sol, 0:0.01:1)
plot(summ, labels = "Middle 95%")
summ = EnsembleSummary(sol, 0:0.01:1; quantiles = [0.25, 0.75])
plot!(summ, labels = "Middle 50%", legend = true)

Кроме того, можно легко вычислить корреляцию между значениями в t=0.2 и t=0.7 следующим образом.

timepoint_meancor(sol, 0.2, 0.7) # Дает оба средних значения, а затем коэффициент корреляции
(0.6138549457510034, 1.0029244305681482, 0.3919552711142436)

Пример 2. Системы стохастических дифференциальных уравнений с диагональным шумом

В общем случае SDE

обобщается до системы уравнений так же, как и ODE. Здесь g теперь является матрицей значений. Одним из распространенных случаев, который используется по умолчанию в DifferentialEquations.jl, является диагональный шум, где g — это диагональная матрица. Это означает, что каждая функция в системе получает свое случайное число. Вместо того чтобы работать с матрицами в этом случае, мы просто определяем f и g как функции на месте. Таким образом, f(du,u,p,t) дает вектор du, который является детерминированным изменением, а g(du2,u,p,t) дает вектор du2, для которого du2.*W является стохастической частью уравнения.

Например, уравнение Лоренца с аддитивным шумом имеет ту же детерминированную часть, что и уравнения Лоренца, но к каждому шагу уравнения добавляется аддитивный шум, который просто равен 3*N(0,dt), где N — нормальное распределение, а dt — временной шаг. Это выполняется следующим образом.

using DifferentialEquations
using Plots
function lorenz(du, u, p, t)
    du[1] = 10.0(u[2] - u[1])
    du[2] = u[1] * (28.0 - u[3]) - u[2]
    du[3] = u[1] * u[2] - (8 / 3) * u[3]
end

function σ_lorenz(du, u, p, t)
    du[1] = 3.0
    du[2] = 3.0
    du[3] = 3.0
end

prob_sde_lorenz = SDEProblem(lorenz, σ_lorenz, [1.0, 0.0, 0.0], (0.0, 10.0))
sol = solve(prob_sde_lorenz)
plot(sol, idxs = (1, 2, 3))

Отметим, что для функции шума допустимо смешение членов уравнения. Например

function σ_lorenz(du, u, p, t)
    du[1] = sin(u[3]) * 3.0
    du[2] = u[2] * u[1] * 3.0
    du[3] = 3.0
end
σ_lorenz (generic function with 1 method)

является допустимой функцией шума, которая снова выдаст диагональный шум с помощью du2.*W.

Пример 3. Системы стохастических дифференциальных уравнений со скалярным шумом

В этом примере мы будем решать систему уравнений SDE со скалярным шумом. Это означает, что ко всем уравнениям SDE применяется один и тот же процесс шума. Нам необходимо определить процесс скалярного шума, используя интерфейс процесса шума. Поскольку нам необходим процесс WienerProcess, который начинается в точке 0.0 в момент времени 0.0, мы используем команду W = WienerProcess(0.0,0.0,0.0) для определения нужного броуновского движения, а затем передаем его в параметр noise в задаче SDEProblem. Для полного примера решим линейное уравнение SDE со скалярным шумом с помощью алгоритма высокого порядка:

using DifferentialEquations
using Plots
f(du, u, p, t) = (du .= u)
g(du, u, p, t) = (du .= u)
u0 = rand(4, 2)

W = WienerProcess(0.0, 0.0, 0.0)
prob = SDEProblem(f, g, u0, (0.0, 1.0), noise = W)
sol = solve(prob, SRIW1())
plot(sol)

Пример 4. Системы стохастических дифференциальных уравнений (SDE) с недиагональным шумом

В предыдущих примерах у нас был диагональный шум, то есть вектор случайных чисел dW, размер которого совпадает с выводом g, где шум применяется поэлементно, и скалярный шум, когда ко всем зависимым переменным применяется одна случайная величина. Однако более общий вид шума позволяет линейно смешивать члены уравнения с помощью g, являющегося матрицей.

Обратите внимание, что нелинейные смешивания не являются уравнениями SDE, а относятся к более общему классу случайных обыкновенных дифференциальных уравнений (RODE), которые имеют отдельный набор решателей.

Определим задачу с четырьмя процессами Винера и двумя зависимыми случайными переменными. В данном случае мы хотим, чтобы выводом g была матрица 2x4 так, чтобы решением было g(u,p,t)*dW, матричное умножение. Например, можно сделать следующее.

using DifferentialEquations
f(du, u, p, t) = du .= 1.01u
function g(du, u, p, t)
    du[1, 1] = 0.3u[1]
    du[1, 2] = 0.6u[1]
    du[1, 3] = 0.9u[1]
    du[1, 4] = 0.12u[1]
    du[2, 1] = 1.2u[2]
    du[2, 2] = 0.2u[2]
    du[2, 3] = 0.3u[2]
    du[2, 4] = 1.8u[2]
end
prob = SDEProblem(f, g, ones(2), (0.0, 1.0), noise_rate_prototype = zeros(2, 4))
SDEProblem with uType Vector{Float64} and tType Float64. In-place: true
timespan: (0.0, 1.0)
u0: 2-element Vector{Float64}:
 1.0
 1.0

В нашем g мы определяем функции для вычисления значений матрицы. Теперь можно представить решаемое уравнение SDE как систему уравнений

означающую, что, например, du[1,1] и du[2,1] соответствуют стохастическим изменениям с одним и тем же случайным числом в первом и втором уравнениях SDE.

Эту задачу можно решить только моими методами SDE, которые совместимы с недиагональным шумом. Этот вопрос рассматривается на странице решателей SDE.

Сама матрица определяется именованным аргументом noise_rate_prototype в конструкторе SDEProblem. Это прототип для типа, когда du будет находиться в g. Это может быть любой тип AbstractMatrix. Итак, задача определяется следующим образом.

# Определить разреженную матрицу, создав плотную матрицу и задав некоторые значения не равными нулю
using SparseArrays
A = zeros(2, 4)
A[1, 1] = 1
A[1, 4] = 1
A[2, 4] = 1
A = sparse(A)

# Пусть `g` записывает значения разреженной матрицы
function g(du, u, p, t)
    du[1, 1] = 0.3u[1]
    du[1, 4] = 0.12u[2]
    du[2, 4] = 1.8u[2]
end

# Пусть `g` использует разреженную матрицу
prob = SDEProblem(f, g, ones(2), (0.0, 1.0), noise_rate_prototype = A)
SDEProblem with uType Vector{Float64} and tType Float64. In-place: true
timespan: (0.0, 1.0)
u0: 2-element Vector{Float64}:
 1.0
 1.0

и теперь g(u,p,t) выполняет запись в разреженную матрицу, а g(u,p,t)*dW является умножением разреженных матриц.

Пример 4. Окрашенный шум

Окрашенный шум может быть определен с помощью интерфейса процесса шума. В этой части документации показано, как определить собственный процесс шума my_noise, который может быть передан в SDEProblem.

SDEProblem(f, g, u0, tspan, noise = my_noise)

Учтите, что общие задачи с окрашенным шумом совместимы только с методами EM и EulerHeun. Этот вопрос рассматривается на странице решателей SDE.

Пример. Пространственно окрашенный шум в модели Хестона

Определим уравнение Хестона из финансовой математики:

Здесь у нас есть диагональная задача с шумом, которая задается следующим образом.

function f(du, u, p, t)
    du[1] = μ * u[1]
    du[2] = κ * (Θ - u[2])
end
function g(du, u, p, t)
    du[1] = √u[2] * u[1]
    du[2] = Θ * √u[2]
end
g (generic function with 1 method)

Однако шум имеет матрицу корреляции для некоторой константы ρ. Выбираем ρ=0.2:

ρ = 0.2
Γ = [1 ρ; ρ 1]
2×2 Matrix{Float64}:
 1.0  0.2
 0.2  1.0

Для решения этой задачи можно определить процесс CorrelatedWienerProcess, который начинается с нуля (W(0)=0), следующим образом:

tspan = (0.0, 1.0)
heston_noise = CorrelatedWienerProcess!(Γ, tspan[1], zeros(2), zeros(2))
t: 1-element Vector{Float64}:
 0.0
u: 1-element Vector{Vector{Float64}}:
 [0.0, 0.0]

Затем на основе этих данных строится SDE:

SDEProblem(f, g, ones(2), tspan, noise = heston_noise)
SDEProblem with uType Vector{Float64} and tType Float64. In-place: true
timespan: (0.0, 1.0)
u0: 2-element Vector{Float64}:
 1.0
 1.0

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