Two-sided Cavity with Atoms
In this example, we first simulate a continuously coherently driven empty two-sided cavity where we see the transmission and reflection spectrum. Afterwards, we couple $N=2$ two-level system resonantly to the cavity to investigate the transmission and reflection of a weak coherent pulse.
As usual, we start by loading the packages and specifying the system. We already include the Hilbert space for $N$ atoms here. However, for the numerical simulation of the empty cavity we provide a dictionary of the actual QuantumOptics.jl operators we want to use.
using QuantumInputOutputusing SecondQuantizedAlgebrausing QuantumOpticsusing QuantumOpticsBase: daggerusing Plots@variables E::Real κ_L::Real κ_R::Real Δ::Real g::Real γ::RealNatoms = 2hc = FockSpace(:cavity)ha(i) = NLevelSpace(Symbol("a_$i"), 2)h = hc ⊗ tensor([ha(i) for i = 1:Natoms]...);a = Destroy(h, :a, 1) # cavityσ(α, i, j) = Transition(h, Symbol("σ_$(α)"), i, j, 1+α) # two-level atom α∑σ(i, j) = sum(σ(α, i, j) for α = 1:Natoms) # collective atomic operatorEmpty two-sided cavity
We couple a classical drive into the cavity through the left mirror $(\kappa_L)$. The decay through the right mirror can be added in several ways: with the concatenate rule, including it already in the initial cavity SLH triple with a second Lindblad term or by simply including the decay term to the Lindblad by hand. In this example, we use the first option.
G_d = SLH(1, E, 0) # classical driveH_cavity = -Δ*a'aG_c_L = SLH(1, [√(κ_L)*a], H_cavity)G_cav_L_drive = G_d ▷ G_c_LG_c_R = SLH(1, [√(κ_R)*a], 0)G_cav_L_R_drive = G_cav_L_drive ⊞ G_c_RNote that one needs to be careful to not double-count the Hamiltonian terms with the concatenation rule.
H1 = hamiltonian(G_cav_L_R_drive)(0.5E*sqrt(κ_L))im * a + (-0.5E*sqrt(κ_L))im * a' - Δ * a' * aL1_L = jump_operator(G_cav_L_R_drive)[1]E + sqrt(κ_L) * aL1_R = jump_operator(G_cav_L_R_drive)[2]sqrt(κ_R) * aHere, the usual classical cavity drive-term $\sqrt{\kappa_L} E (a^\dagger + a)$ appears as a combination of Hamiltonian and Lindblad term. To solve the dynamics of the system we translate the symbolic expressions into numeric operators (matrices) of QuantumOptics.jl. Since we do not want to include the basis of the atoms, we provide a dictionary of operators with the kwarg operators in the function to_numeric.
# numerical parametersEn = 0.5κ_Rn = 1.0κ_Ln = 1.0Δn = 0.0Δn_ls = [-5.0:0.1:5.0;];lΔ=length(Δn_ls)p_sym = [E, κ_R, κ_L, Δ]p_num = [En, κ_Rn, κ_Ln, Δn]dict_p1 = Dict(p_sym .=> p_num)# cavity-only operatorsbc1 = FockBasis(4)a_QO = destroy(bc1)ops_dict = Dict([a, a'] .=> [a_QO, dagger(a_QO)])H1_QO = to_numeric(H1, bc1; parameter = dict_p1, operators = ops_dict)L1_L_QO = to_numeric(L1_L, bc1; parameter = dict_p1, operators = ops_dict)L1_R_QO = to_numeric(L1_R, bc1; parameter = dict_p1, operators = ops_dict)J1_QO = [L1_L_QO, L1_R_QO]# time evolutionT = [0:0.01:1;]*20ψ0 = fockstate(bc1, 0)t_, ρt = timeevolution.master(T, ψ0, H1_QO, J1_QO)n_cavity = real(expect(a_QO'a_QO, ρt))n_ref = real(expect(dagger(J1_QO[1])*J1_QO[1], ρt))n_trans = real(expect(dagger(J1_QO[2])*J1_QO[2], ρt))p1 = plot(t_, n_cavity; label = "", xlabel = "t", ylabel = "cavity photons", grid = true)p2 = plot(t_, n_ref; label = "reflection")plot!(p2, t_, n_trans; label = "transmission", ls = :dash)plot!(p2; xlabel = "t", ylabel = "intensity rate", grid = true, legend = :best)plot(p1, p2; layout = (2, 1), size = (600, 500))Now we scan the laser-cavity detuning $\Delta$ to plot the transmission and reflection spectrum.
dict_p_Δ(Δn) = Dict(p_sym .=> [En, κ_Rn, κ_Ln, Δn])H1_QO_Δ_(Δn) = to_numeric(H1, bc1; parameter = dict_p_Δ(Δn), operators = ops_dict)n_ref_Δ = zeros(lΔ)n_trans_Δ = zeros(lΔ)for it = 1:lΔ Δn_ = Δn_ls[it] t_it, ρt_it = timeevolution.master(T, ψ0, H1_QO_Δ_(Δn_), J1_QO) n_ref_Δ[it] = real(expect(dagger(J1_QO[1])*J1_QO[1], ρt_it[end])) n_trans_Δ[it] = real(expect(dagger(J1_QO[2])*J1_QO[2], ρt_it[end]))endp = plot(Δn_ls, n_ref_Δ; label = "reflection")plot!(p, Δn_ls, n_trans_Δ; label = "transmission", ls = :dash)plot!(p; xlabel = "Δ", grid = true, legend = :best, size = (600, 350))pTwo-sided cavity with atoms
In the following, we include $N=2$ two-level atoms in the cavity and simulate the transmission and reflection of a coherent Gaussian pulse with a mean photon number of $|\alpha|^2 = 1/10$. We assume that the atoms are on resonance with the cavity, i.e. $\Delta = \Delta_c = \Delta_a$.
H_ac = -Δ*(a'a + ∑σ(2, 2)) + g*(a'∑σ(1, 2) + a*∑σ(2, 1))G_ac = SLH(1, √κ_L*a, H_ac)G_ac_drive = (G_d ▷ G_ac) ⊞ SLH(1, √κ_R*a, 0)H2 = G_ac_drive.hamiltonian(0.5E*sqrt(κ_L))im * a + (-0.5E*sqrt(κ_L))im * a' - Δ * σ_1₂₂ - Δ * σ_2₂₂ + g * a * σ_1₂₁ + g * a * σ_2₂₁ - Δ * a' * a + g * a' * σ_1₁₂ + g * a' * σ_2₁₂L2_L = G_ac_drive.jump_operator[1]E + sqrt(κ_L) * aL2_R = G_ac_drive.jump_operator[2]sqrt(κ_R) * a# numerical parameterκ_Rn2 = κ_Ln2 = 2π*1gn2 = 2π*0.4Δn2 = 0.0γn = 2π*0.1p_sym2 = [κ_R, κ_L, Δ, g]p_num2 = [κ_Rn2, κ_Ln2, Δn2, gn2]σp = 10/κ_Ln2 # pulse widthTp = 4σp # pulse peakTend = 3Tpα0 = √(0.1) # √ of total photon numberΩ0 = α0*2*√(κ_Ln2)/(π^(1/4)*√(σp))Ω1(t) = Ω0/2*exp(-(t-Tp)^2 / (2*σp^2))E_t(t) = Ω1(t)/√(κ_Ln2)T = [0:0.001:1;]*TendΔT = T[2] - T[1]n_pulse = round(sum(abs2.(E_t.(T)))*ΔT, digits = 7)@show n_pulsedict_p2 = Dict(p_sym2 .=> p_num2)dict_p_t2 = Dict([E, conj(E)] .=> [E_t, E_t])n_pulse = 0.1
# numeric basesbc1 = FockBasis(4)ba = NLevelBasis(2)b = bc1 ⊗ tensor([ba for i = 1:Natoms]...)a_QO2 = to_numeric(a, b)σ_QO(α, i, j) = to_numeric(σ(α, i, j), b)# translate to numeric Hamiltonian and LindbladH_QO = to_numeric(H2, b; parameter = dict_p2, time_parameter = dict_p_t2)L2_L_QO = to_numeric(L2_L, b; parameter = dict_p2, time_parameter = dict_p_t2)L2_R_QO = to_numeric(L2_R, b; parameter = dict_p2)# additional atomic decay into free spaceJ_add = [√(γn)*σ_QO(α, 1, 2) for α = 1:Natoms]function input_output(t, ρ) H = H_QO(t) J = [L2_L_QO(t), L2_R_QO, J_add...] return H, J, dagger.(J)end# time evolutionψ0 = fockstate(bc1, 0) ⊗ tensor([nlevelstate(ba, 1) for i = 1:Natoms]...)t2_, ρt2 = timeevolution.master_dynamic(T, ψ0, input_output)L2_L_QO_dag(t) = dagger(L2_L_QO(t))l_t = length(t2_)n_trans2 = zeros(l_t)n_ref2 = zeros(l_t)for it = 1:l_t n_trans2[it] = abs(expect(dagger(L2_R_QO)*L2_R_QO, ρt2[it])) n_ref2[it] = abs(expect(L2_L_QO_dag(t2_[it])*L2_L_QO(t2_[it]), ρt2[it]))endp = plot( t2_, n_trans2; label = "transmission = $(round(sum(n_trans2) * ΔT / n_pulse * 100))%",)plot!( p, t2_, n_ref2; ls = :dash, label = "reflection = $(round(sum(n_ref2) * ΔT / n_pulse * 100))%",)plot!(p; xlabel = "t", legend = :best, grid = true, size = (600, 350))pWe can see that only about 5% is transmitted and 71% are reflected. The rest is scattered into free space by the atoms.
Package versions
These results were obtained using the following versions:
using InteractiveUtilsversioninfo()using PkgPkg.status( [ "QuantumInputOutput", "SecondQuantizedAlgebra", "QuantumOptics", "QuantumCumulants", "Plots", ], mode = PKGMODE_MANIFEST,)Julia Version 1.13.0
Commit d1c37793dd2 (2026-09-09 19:00 UTC)
Build Info:
Official https://julialang.org release
Platform Info:
OS: Linux (x86_64-linux-gnu)
CPU: 4 × AMD EPYC 7763 64-Core Processor
WORD_SIZE: 64
LLVM: libLLVM-20.1.8 (ORCJIT, znver3)
GC: Built with stock GC
Threads: 1 default, 0 interactive, 1 GC (on 4 virtual cores)
Environment:
JULIA_PKG_SERVER_REGISTRY_PREFERENCE = eager
JULIA_DEBUG = Documenter,Literate
JULIA_NUM_THREADS = 1
Status `~/work/QuantumInputOutput.jl/QuantumInputOutput.jl/docs/Manifest.toml`
⌅ [7d9fca2a] Arpack v0.5.3
⌅ [861a8166] Combinatorics v1.0.2
[d38c429a] Contour v0.6.3
⌅ [82cc6244] DataInterpolations v9.5.0
[459566f4] DiffEqCallbacks v4.19.4
[77a26b50] DiffEqNoiseProcess v5.36.4
[c87230d0] FFMPEG v0.4.5
[7a1cc6ca] FFTW v1.10.0
⌅ [53c48c17] FixedPointNumbers v0.8.6
[069b7b12] FunctionWrappers v1.1.3
[28b8d3ca] GR v0.73.27
[42fd0dbc] IterativeSolvers v0.9.4
[1019f520] JLFzf v0.1.11
[682c06a0] JSON v1.9.0
[0b1a1467] KrylovKit v0.10.4
[b964fa9f] LaTeXStrings v1.4.1
[23fbe1c1] Latexify v0.16.12
[7a12625a] LinearMaps v3.11.4
[1914dd2f] MacroTools v0.5.16
[442fdcdd] Measures v0.3.3
[7771a370] ModelingToolkitBase v1.77.0
[d8a4904e] MutableArithmetics v1.8.1
[77ba4419] NaNMath v1.1.4
[e7bfaba1] NumericalIntegration v0.3.4
⌅ [bac558e1] OrderedCollections v1.8.2
[1dea7af3] OrdinaryDiffEq v7.8.1
[1344f307] OrdinaryDiffEqLowOrderRK v2.2.5
[ccf2f8ad] PlotThemes v3.3.0
[995b91a9] PlotUtils v1.5.0
[91a5bcdd] Plots v1.41.7
[aea7be01] PrecompileTools v1.3.4
⌃ [35bcea6d] QuantumCumulants v0.7.1
[18f9eda6] QuantumInputOutput v0.5.3 `~/work/QuantumInputOutput.jl/QuantumInputOutput.jl`
[5717a53b] QuantumInterface v0.4.4
[6e0679c1] QuantumOptics v1.2.10
[4f57444f] QuantumOpticsBase v0.5.16
[3cdcf5f2] RecipesBase v1.3.4
[01d81517] RecipesPipeline v0.6.12
[731186ca] RecursiveArrayTools v4.5.3
[189a3867] Reexport v1.2.2
[05181044] RelocatableFolders v1.0.1
[ae029012] Requires v1.3.1
[0bca4576] SciMLBase v3.56.1
[431bcebd] SciMLPublic v1.3.0
[6c6a2e73] Scratch v1.3.0
⌅ [f7aa4685] SecondQuantizedAlgebra v0.11.0
[992d4aef] Showoff v1.1.1
[276daf66] SpecialFunctions v2.9.0
[90137ffa] StaticArrays v1.9.22
[10745b16] Statistics v1.11.5
[2913bbd2] StatsBase v0.34.13
[789caeaf] StochasticDiffEq v7.2.0
[d1185830] SymbolicUtils v4.48.0
[0c5d862f] Symbolics v7.41.1
[8ea1fca8] TermInterface v2.0.0
[1cfade01] UnicodeFun v0.4.1
[41fe7b60] Unzip v0.2.0
[2a0f44e3] Base64 v1.11.0
[ade2ca70] Dates v1.11.0
[f43a241f] Downloads v1.7.0
[37e2e46d] LinearAlgebra v1.13.0
[44cfe95a] Pkg v1.13.0
[de0858da] Printf v1.11.0
[3fa0cd96] REPL v1.11.0
[9a3f8284] Random v1.11.0
[2f01184e] SparseArrays v1.13.0
[fa267f1f] TOML v1.0.3
[cf7118a7] UUIDs v1.11.0
Info Packages marked with ⌃ and ⌅ have new versions available. Those with ⌃ may be upgradable, but those with ⌅ are restricted by compatibility constraints from upgrading. To see why use `status --outdated -m`
This page was generated using Literate.jl.