Skip to content

Commit

Permalink
Avoid BLAS calls for small matrix operations
Browse files Browse the repository at this point in the history
 - Add benchmarking component test
 - Remove matrix operations from SeparableHjmModel
 - Remove small matrix oprations from GaussianHjmModel
  • Loading branch information
FrameConsult authored and sschlenkrich committed May 4, 2024
1 parent d5a67bf commit 0ed89be
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 23 deletions.
19 changes: 16 additions & 3 deletions src/models/rates/GaussianHjmModel.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,18 @@ struct GaussianHjmModelVolatility
DfT::AbstractMatrix
end

function volatility(o::GaussianHjmModelVolatility, u::ModelTime)
# return o.HHfInv * (o.DfT .* o.sigma_f(u)) # beware DfT multiplication
σ = o.sigma_f(u)
d = length(σ)
return [
sum( o.HHfInv[i,k] * o.DfT[k,j] * σ[k] for k = 1:d )
for i = 1:d, j = 1:d
]
end

"Calculate volatility matrix."
(o::GaussianHjmModelVolatility)(u::ModelTime) = o.HHfInv * (o.DfT .* o.sigma_f(u)) # beware DfT multiplication
(o::GaussianHjmModelVolatility)(u::ModelTime) = volatility(o, u)


"""
Expand Down Expand Up @@ -282,7 +292,8 @@ function log_zero_bond(m::GaussianHjmModel, model_alias::String, t::ModelTime, T
G = G_hjm(m, t, T)
y = func_y(m, t)
X_ = @view(X.X[idx:idx+(d-1),:])
return X_' * G .+ 0.5 * (G'*y*G)
GyG = sum(G[i] * sum(y[i,j] * G[j] for j in 1:d) for i in 1:d)
return X_' * G .+ (0.5 * GyG)
end

"""
Expand Down Expand Up @@ -333,7 +344,9 @@ function log_compounding_factor(
G2 = G_hjm(m, t, T2)
y = func_y(m, t)
X_ = @view(X.X[idx:idx+(d-1),:])
return X_' * (G2 .- G1) .+ 0.5 * ((G2'*y*G2) - (G1'*y*G1))
G1yG1 = sum(G1[i] * sum(y[i,j] * G1[j] for j in 1:d) for i in 1:d)
G2yG2 = sum(G2[i] * sum(y[i,j] * G2[j] for j in 1:d) for i in 1:d)
return X_' * (G2 .- G1) .+ 0.5 * (G2yG2 - G1yG1)
end


Expand Down
46 changes: 34 additions & 12 deletions src/models/rates/SeparableHjmModel.jl
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ end
Diagonal entries of ``H(s,t)``.
"""
function H_hjm(chi::AbstractVector, s::ModelTime, t::ModelTime)
return exp.(-chi .* (t-s))
# exp.(-chi .* (t-s))
δt = t-s
return [ exp(-χ * δt) for χ in chi ]
end

"""
Expand All @@ -54,8 +56,10 @@ end
Vector function ``G(s,t)``.
"""
function G_hjm(chi::AbstractVector, s::ModelTime, t::ModelTime)
# (1.0 .- exp.(-chi .* (t-s))) ./ chi
δt = t-s
# This is an unsafe implementation. Better use Taylor expansion.
return (1.0 .- exp.(-chi .* (t-s))) ./ chi
return [ (1.0 - exp(-χ * δt)) / χ for χ in chi ]
end

"""
Expand Down Expand Up @@ -93,6 +97,7 @@ end
Benchmark times volatility scaling matrix ``H [H^f]^{-1} = [H^f H^{-1}]^{-1}``.
"""
function benchmark_times_scaling(chi::AbstractVector, delta::AbstractVector)
# Hf_H_inv = exp.(-delta * chi')
Hf_H_inv = [ exp(-chi_ * delta_) for delta_ in delta, chi_ in chi ] # beware the order of loops!
HHfInv = inv(Hf_H_inv)
return HHfInv
Expand All @@ -118,13 +123,22 @@ function func_y(
s::ModelTime,
t::ModelTime,
)
# chi_i_p_chi_j = chi .+ chi'
# H_i_j = exp.(-chi_i_p_chi_j .* (t-s))
# V = sigmaT * transpose(sigmaT)
# G_i_j = (1. .- H_i_j) ./ chi_i_p_chi_j
# y1 = y0 .* H_i_j .+ V .* G_i_j
#
# better exploit symmetry and update in-place
chi_i_p_chi_j = [ (chi_i + chi_j) for chi_i in chi, chi_j in chi ]
H_i_j = exp.(-chi_i_p_chi_j .* (t-s))
V = sigmaT * transpose(sigmaT)
# this is unsafe, better use Taylor expansion
G_i_j = (1. .- H_i_j) ./ chi_i_p_chi_j
return y0 .* H_i_j .+ V .* G_i_j
d = length(chi)
δt = t - s
return [
y0[i,j] * exp(-(chi[i] + chi[j]) * δt) +
sum( sigmaT[i,k] * sigmaT[j,k] for k in 1:d ) *
(1.0 - exp(-(chi[i] + chi[j]) * δt)) / (chi[i] + chi[j])
for i in 1:d, j in 1:d
]
end


Expand All @@ -146,13 +160,21 @@ function _func_y(
s::ModelTime,
t::ModelTime,
)
# chi_i_p_chi_j = chi .+ chi'
# H_i_j = exp.(-chi_i_p_chi_j .* (t-s))
# V = sigmaT * transpose(sigmaT)
# G_i_j = (1. .- H_i_j) ./ chi_i_p_chi_j
# y1 = 0 + V .* G_i_j
#
# better exploit symmetry and update in-place
chi_i_p_chi_j = [ (chi_i + chi_j) for chi_i in chi, chi_j in chi ]
H_i_j = exp.(-chi_i_p_chi_j .* (t-s))
V = sigmaT * transpose(sigmaT)
# this is unsafe, better use Taylor expansion
G_i_j = (1. .- H_i_j) ./ chi_i_p_chi_j
return V .* G_i_j
d = length(chi)
δt = t - s
return [
sum( sigmaT[i,k] * sigmaT[j,k] for k in 1:d ) *
(1.0 - exp(-(chi[i] + chi[j]) * δt)) / (chi[i] + chi[j])
for i in 1:d, j in 1:d
]
end


Expand Down
63 changes: 63 additions & 0 deletions test/componenttests/scenarios/swap_benchmarks.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@

using DiffFusion
using OrderedCollections
using Test


@testset "Benchmark scenario generation." begin

@testset "Test Vanilla swaps" begin
results = OrderedDict[]
for example_string in DiffFusion.Examples.examples
@info "Run example " * example_string
serialised_example = DiffFusion.Examples.load(example_string)
example = DiffFusion.Examples.build(serialised_example)
example["config/simulation"]["simulation_times"]["step"] = 0.25
example["config/instruments"]["obs_times"]["step"] = 0.25
example["config/simulation"]["n_paths"] = 2^13
example["config/simulation"]["with_progress_bar"] = false
example["config/instruments"]["with_progress_bar"] = false
#
path_ = DiffFusion.Examples.path!(example)
portfolio_ = DiffFusion.Examples.portfolio!(
example,
32, # swap
0, # swaptions
0, # berms
)
legs = vcat(portfolio_...)
#
config = example["config/instruments"]
obs_times = config["obs_times"]
if isa(obs_times, AbstractDict)
obs_times = Vector(obs_times["start"]:obs_times["step"]:obs_times["stop"])
end
with_progress_bar = config["with_progress_bar"]
discount_curve_key = config["discount_curve_key"]
#
for leg in legs
if isa(leg, DiffFusion.BermudanSwaptionLeg)
DiffFusion.reset_regression!(leg, path_, leg.regression_data.make_regression)
end
end
#
GC.gc()
time_ = @elapsed @time scens = DiffFusion.scenarios(
legs,
obs_times,
path_,
discount_curve_key,
with_progress_bar=with_progress_bar
)
push!(
results,
OrderedDict(
"example" => example_string,
"run_time" => time_,
)
)
end
end


end
16 changes: 8 additions & 8 deletions test/unittests/models/rates/gaussian_hjm_model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ using LinearAlgebra
]
@test isapprox(theta_model[1], theta_ref[1], atol=1.5e-13 )
@test isapprox(theta_model[2], theta_ref[2], atol=7.0e-13 )
@test isapprox(theta_model[3], theta_ref[3], atol=3.0e-11 )
@test isapprox(theta_model[3], theta_ref[3], atol=4.0e-11 )
@test isapprox(theta_model[4], theta_ref[4], rtol=7.5e-9 )
@test isapprox(theta_model[5], theta_ref[5], rtol=2.0e-9 )
end
Expand Down Expand Up @@ -250,8 +250,8 @@ using LinearAlgebra
SX_2 = DiffFusion.model_state(X, m)
@test string(SX_2) == string(SX)
@test DiffFusion.log_bank_account(m, DiffFusion.alias(m), 1.0, SX) == [ 4., 8., 12. ]
@test_throws AssertionError DiffFusion.log_bank_account(m, "WrongAlias", 1.0, SX)
@test DiffFusion.log_zero_bond(m, DiffFusion.alias(m), 4.0, 8.0, SX) == X[1:3,:]' * G .+ 0.5 * G'*y*G
@test_throws AssertionError DiffFusion.log_bank_account(m, "WrongAlias", 1.0, SX)
@test isapprox(DiffFusion.log_zero_bond(m, DiffFusion.alias(m), 4.0, 8.0, SX), X[1:3,:]' * G .+ 0.5 * G'*y*G, rtol=1.0e-14)
@test_throws AssertionError DiffFusion.log_zero_bond(m, "WrongAlias", 4.0, 8.0, SX)
#
df1 = DiffFusion.log_zero_bond(m, DiffFusion.alias(m), 4.0, 8.0, SX)
Expand All @@ -262,17 +262,17 @@ using LinearAlgebra
#
dfT = DiffFusion.log_zero_bonds(m, DiffFusion.alias(m), 4.0, [4.0, 6.0, 8.0, 10.0], SX)
@test size(dfT) == (3, 4)
@test dfT[:,1] == DiffFusion.log_zero_bond(m, DiffFusion.alias(m), 4.0, 4.0, SX)
@test dfT[:,2] == DiffFusion.log_zero_bond(m, DiffFusion.alias(m), 4.0, 6.0, SX)
@test dfT[:,3] == DiffFusion.log_zero_bond(m, DiffFusion.alias(m), 4.0, 8.0, SX)
@test dfT[:,4] == DiffFusion.log_zero_bond(m, DiffFusion.alias(m), 4.0, 10.0, SX)
@test isapprox(dfT[:,1], DiffFusion.log_zero_bond(m, DiffFusion.alias(m), 4.0, 4.0, SX), rtol=1.0e-14)
@test isapprox(dfT[:,2], DiffFusion.log_zero_bond(m, DiffFusion.alias(m), 4.0, 6.0, SX), rtol=1.0e-14)
@test isapprox(dfT[:,3], DiffFusion.log_zero_bond(m, DiffFusion.alias(m), 4.0, 8.0, SX), rtol=1.0e-14)
@test isapprox(dfT[:,4], DiffFusion.log_zero_bond(m, DiffFusion.alias(m), 4.0, 10.0, SX), rtol=1.0e-14)
#
X = [ 0., 0., 1., 2., 3., 4., 0. ] * [ 1., 2., 3.]'
s_alias = [ "1", "2", "Theta_3F_x_1", "Theta_3F_x_2", "Theta_3F_x_3", "Theta_3F_s", "7" ]
dict = DiffFusion.alias_dictionary(s_alias)
SX = DiffFusion.model_state(X, dict)
@test DiffFusion.log_bank_account(m, "Theta_3F", 1.0, SX) == [ 4., 8., 12. ]
@test DiffFusion.log_zero_bond(m, "Theta_3F", 4.0, 8.0, SX) == X[3:5,:]' * G .+ 0.5 * G'*y*G
@test isapprox(DiffFusion.log_zero_bond(m, "Theta_3F", 4.0, 8.0, SX), X[3:5,:]' * G .+ 0.5 * G'*y*G, rtol=1.0e-14)
#
df1 = DiffFusion.log_zero_bond(m, "Theta_3F", 4.0, 8.0, SX)
df2 = DiffFusion.log_zero_bond(m, "Theta_3F", 4.0, 10.0, SX)
Expand Down

0 comments on commit 0ed89be

Please sign in to comment.