RF-Pulse Simulation
The following code replicates the RF-pulse simulation of Fig. 3 and plots the $z^s$-magnetization at the end of respective pulse.
For these simulations we need the following packages:
using MRIgeneralizedBloch
using QuadGK
using DifferentialEquations
using SpecialFunctions
using Plots
and we simulate an isolated semi-solid spin pool with the following parameters:
R₁ = 1 # 1/s
T₂ˢ = 10e-6; # s
Here, we simulate π-pulses with the following parameters:
α = π
Tʳᶠ = exp.(range(log(2e-7), log(1e-1), length=100)) # s
ω₁ = α ./ Tʳᶠ # rad/s
ω₀ = 0; # rad/s
Replace first line with α = π/4
or α = π/2
to simulate the other two rows of Fig. 3.
Lorentzian Lineshape
In this script, we simulate the three lineshapes separately, starting with the Lorentzian lineshape for which the Bloch model provides a ground truth.
Bloch Model
We can formulate the Bloch model as
\[\partial_t \begin{pmatrix} x \\ y \\ z \\ 1 \end{pmatrix} = \begin{pmatrix} -R_2 & -ω_0 & ω_1 & 0 \\ ω_0 & -R_2 & 0 & 0 \\ -ω_1 & 0 & -R_1 & R_1 \\ 0 & 0 & 0 & 0 \end{pmatrix} \begin{pmatrix} x \\ y \\ z \\ 1 \end{pmatrix} ,\]
where the matrix is the Hamiltonian of the Bloch model. For a constant $ω_0$ and $ω_1$, we can evaluate the Bloch model by taking the matrix exponential of its Hamiltonian:
H(ω₁, ω₀, R₂, R₁) = [-R₂ -ω₀ ω₁ 0;
ω₀ -R₂ 0 0;
-ω₁ 0 -R₁ R₁;
0 0 0 0]
z_Bloch = similar(Tʳᶠ)
for i = 1:length(Tʳᶠ)
(_, _, z_Bloch[i], _) = exp(H(ω₁[i], ω₀, 1 / T₂ˢ, R₁) * Tʳᶠ[i]) * [0; 0; 1; 1]
end
Graham's Spectral Model
Graham's spectral model is derived by integrating over the lineshape multiplied by the spectral response function of the RF-pulse. This results in the RF-induced saturation rate Rʳᶠ
that is used in an exponential model:
Rʳᶠ = @. ω₁^2 * T₂ˢ * ((exp(-Tʳᶠ / T₂ˢ) -1) * T₂ˢ + Tʳᶠ) / Tʳᶠ
z_Graham_spec_Lorentzian = @. (Rʳᶠ * exp(-Tʳᶠ * (R₁ + Rʳᶠ)) + R₁) / (R₁ + Rʳᶠ);
Graham's Single Frequency Approximation
In the single frequency approximation, Graham assumes that the RF-pulse has only a single frequency, which reduces Rʳᶠ
to
g_Lorentzian(ω₀) = T₂ˢ / π ./ (1 .+ (T₂ˢ .* ω₀).^2)
Rʳᶠ = @. π * ω₁^2 * g_Lorentzian(ω₀)
z_Graham_SF_approx_Lorentzian = @. (Rʳᶠ * exp(-Tʳᶠ * (R₁ + Rʳᶠ)) + R₁) / (R₁ + Rʳᶠ);
where g_Lorentzian(ω₀)
denotes the Lorentzian lineshape.
Sled's Model
Sled's model is given by the ordinary differential equation (ODE)
\[\partial_t z(t) = \left(-\pi \int_0^t G(t-τ) \omega_1(τ)^2 dτ \right) z(t) + R_1 (1-z),\]
where $G(t-τ)$ is the Green's function. The Hamiltonian of this ODE is implemented in apply_hamiltonian_sled!
and the ODE can be solved with the DifferentialEquations.jl package:
z₀ = [1] # initial z-magnetization
z_Sled_Lorentzian = similar(Tʳᶠ)
for i = 1:length(Tʳᶠ)
param = (ω₁[i], 1, ω₀, R₁, T₂ˢ, greens_lorentzian)
prob = ODEProblem(apply_hamiltonian_sled!, z₀, (0, Tʳᶠ[i]), param)
z_Sled_Lorentzian[i] = solve(prob).u[end][1]
end
Generalized Bloch Model
The generalized Bloch model is given by the integro-differential equation (IDE)
\[\partial_t z(t) = - ω_1(t) \int_0^t G(t,τ) ω_1(τ) z(τ) dτ + R_1 (1 - z(t)) ,\]
or by
\[\partial_t z(t) = - ω_y(t) \int_0^t G(t,τ) ω_y(τ) z(τ) dτ - ω_x(t) \int_0^t G(t,τ) ω_x(τ) z(τ) dτ + R_1 (1 - z(t)) ,\]
for off-resonant RF-pulses with $ω_1 = ω_x + i ω_y$. The Hamiltonian of the IDE is implemented in apply_hamiltonian_gbloch!
and we can solve this IDE with the delay-differential equation (DDE) solver of the DifferentialEquations.jl package:
z_fun(p, t) = [1.0]; # initialize history function (will be populated with an interpolation by the DDE solver)
z_gBloch_Lorentzian = similar(Tʳᶠ)
for i = 1:length(Tʳᶠ)
param = (ω₁[i], 1, ω₀, R₁, T₂ˢ, greens_lorentzian)
prob = DDEProblem(apply_hamiltonian_gbloch!, z₀, z_fun, (0, Tʳᶠ[i]), param)
z_gBloch_Lorentzian[i] = solve(prob).u[end][1]
end
Now we have solved all five models and can plot the solutions for comparison:
p = plot(xaxis=:log, legend=:bottomright, xlabel="Tʳᶠ [s]", ylabel="zˢ(Tʳᶠ)")
plot!(p, Tʳᶠ, z_gBloch_Lorentzian, label="generalized Bloch model")
plot!(p, Tʳᶠ, Tʳᶠ .* 0 .+ cos(α), label="cos(α)")
plot!(p, Tʳᶠ, z_Sled_Lorentzian, label="Sled's model")
plot!(p, Tʳᶠ, z_Graham_spec_Lorentzian, label="Graham's spectral model")
plot!(p, Tʳᶠ, z_Graham_SF_approx_Lorentzian, label="Graham's single frequency approximation")
plot!(p, Tʳᶠ, z_Bloch, label="Bloch model")
Gaussian Lineshape
We can repeat these simulations (with the exception of the Bloch model) for the Gaussian lineshape:
Rʳᶠ = @. ω₁^2 * T₂ˢ * (2 * T₂ˢ * (exp(-(Tʳᶠ/T₂ˢ)^2/2)-1) + sqrt(2π) * Tʳᶠ * erf(Tʳᶠ/T₂ˢ/sqrt(2))) / (2 * Tʳᶠ)
z_Graham_spec_Gaussian = @. (Rʳᶠ * exp(-Tʳᶠ * (R₁ + Rʳᶠ)) + R₁) / (R₁ + Rʳᶠ)
g_Gaussian(ω₀) = T₂ˢ / sqrt(2π) * exp(-(T₂ˢ * ω₀)^2 / 2) # lineshape
Rʳᶠ = @. π * ω₁^2 * g_Gaussian(ω₀)
z_Graham_SF_approx_Gaussian = @. (Rʳᶠ * exp(-Tʳᶠ * (R₁ + Rʳᶠ)) + R₁) / (R₁ + Rʳᶠ)
z_Sled_Gaussian = similar(Tʳᶠ)
for i = 1:length(Tʳᶠ)
param = (ω₁[i], 1, ω₀, R₁, T₂ˢ, greens_gaussian)
prob = ODEProblem(apply_hamiltonian_sled!, z₀, (0, Tʳᶠ[i]), param)
z_Sled_Gaussian[i] = solve(prob).u[end][1]
end
z_gBloch_Gaussian = similar(Tʳᶠ)
for i = 1:length(Tʳᶠ)
param = (ω₁[i], 1, ω₀, R₁, T₂ˢ, greens_gaussian)
prob = DDEProblem(apply_hamiltonian_gbloch!, z₀, z_fun, (0, Tʳᶠ[i]), param)
z_gBloch_Gaussian[i] = solve(prob).u[end][1]
end
p = plot(xaxis=:log, legend=:bottomright, xlabel="Tʳᶠ [s]", ylabel="zˢ(Tʳᶠ)")
plot!(p, Tʳᶠ, z_gBloch_Gaussian, label="generalized Bloch model")
plot!(p, Tʳᶠ, Tʳᶠ .* 0 .+ cos(α), label="cos(α)")
plot!(p, Tʳᶠ, z_Sled_Gaussian, label="Sled's model")
plot!(p, Tʳᶠ, z_Graham_spec_Gaussian, label="Graham's spectral model")
plot!(p, Tʳᶠ, z_Graham_SF_approx_Gaussian, label="Graham's single frequency approximation")
Super-Lorentzian Lineshape
Further, we can repeat these simulations for the super-Lorentzian lineshape with the exception of Graham's single frequency approximation, as the super-Lorentzian lineshape diverges at $ω_0 → 0$.
G_superLorentzian = interpolate_greens_function(greens_superlorentzian, 0, maximum(Tʳᶠ)/T₂ˢ)
f_PSD(τ) = quadgk(ct -> (4 / τ / abs(1 - 3 * ct^2) * (exp(- τ^2 / 8 * (1 - 3 * ct^2)^2) - 1) + sqrt(2π) * erf(τ / 2 / sqrt(2) * abs(1 - 3 * ct^2))) / abs(1 - 3 * ct^2), 0.0, 1.0)[1]
Rʳᶠ = @. f_PSD(Tʳᶠ / T₂ˢ) * ω₁^2 * T₂ˢ
z_Graham_spec_superLorentzian = @. (Rʳᶠ * exp(-Tʳᶠ * (R₁ + Rʳᶠ)) + R₁) / (R₁ + Rʳᶠ)
z_Sled_superLorentzian = similar(Tʳᶠ)
for i = 1:length(Tʳᶠ)
param = (ω₁[i], 1, ω₀, R₁, T₂ˢ, G_superLorentzian)
prob = ODEProblem(apply_hamiltonian_sled!, z₀, (0, Tʳᶠ[i]), param)
z_Sled_superLorentzian[i] = solve(prob).u[end][1]
end
z_gBloch_superLorentzian = similar(Tʳᶠ)
for i = 1:length(Tʳᶠ)
param = (ω₁[i], 1, ω₀, R₁, T₂ˢ, G_superLorentzian)
prob = DDEProblem(apply_hamiltonian_gbloch!, z₀, z_fun, (0, Tʳᶠ[i]), param)
z_gBloch_superLorentzian[i] = solve(prob).u[end][1]
end
p = plot(xaxis=:log, legend=:bottomright, xlabel="Tʳᶠ [s]", ylabel="zˢ(Tʳᶠ)")
plot!(p, Tʳᶠ, z_gBloch_superLorentzian, label="generalized Bloch model")
plot!(p, Tʳᶠ, Tʳᶠ .* 0 .+ cos(α), label="cos(α)")
plot!(p, Tʳᶠ, z_Sled_superLorentzian, label="Sled's model")
plot!(p, Tʳᶠ, z_Graham_spec_superLorentzian, label="Graham's spectral model")
This simulation reveals the most pronounced deviations of the generalized Bloch model from established models due to the slower decay of the super-Lorentzian Green's function.
Error Analysis
Assuming a super-Lorentzian lineshape, we quantify the deviations of Sled's model from the generalized Bloch model:
Tʳᶠᵢ = 1e-3 # s
ω₁ᵢ = α / Tʳᶠᵢ # rad/s
param = (ω₁ᵢ, 1, ω₀, R₁, T₂ˢ, G_superLorentzian)
prob = ODEProblem(apply_hamiltonian_sled!, z₀, (0, Tʳᶠᵢ), param)
z_Sled_superLorentzian_i = solve(prob).u[end][1]
prob = DDEProblem(apply_hamiltonian_gbloch!, z₀, z_fun, (0, Tʳᶠᵢ), param)
z_gBloch_superLorentzian_i = solve(prob).u[end][1]
z_Sled_superLorentzian_i - z_gBloch_superLorentzian_i
0.029065309232339964
For $T_{\text{RF}} = 1$ms, the deviations are small compared to the thermal equilibrium magnetization $z^s_0 = 1$, but with $T_{\text{RF}} = 0.1$ms, this deviation becomes sizable:
Tʳᶠᵢ = 1e-4 # s
ω₁ᵢ = α / Tʳᶠᵢ # rad/s
param = (ω₁ᵢ, 1, ω₀, R₁, T₂ˢ, G_superLorentzian)
prob = ODEProblem(apply_hamiltonian_sled!, z₀, (0, Tʳᶠᵢ), param)
z_Sled_superLorentzian_i = solve(prob).u[end][1]
prob = DDEProblem(apply_hamiltonian_gbloch!, z₀, z_fun, (0, Tʳᶠᵢ), param)
z_gBloch_superLorentzian_i = solve(prob).u[end][1]
z_Sled_superLorentzian_i - z_gBloch_superLorentzian_i
0.35470637168033126
This page was generated using Literate.jl.