The Monte Carlo solver of QuTiP can also be used to solve the dynamics of time-local non-Markovian master equations, i.e., master equations of the Lindblad form
with "rates" γn(t) that can take negative values. This can be done with the .nm_mcsolve
function. The function is based on the influence martingale formalism [Donvil22] and formally requires that the collapse operators An satisfy a completeness relation of the form
∑nAn†An = α𝕀,
where 𝕀 is the identity operator on the system Hilbert space and α > 0. Note that when the collapse operators of a model don't satisfy such a relation, nm_mcsolve
automatically adds an extra collapse operator such that nmmcsolve_completeness
is satisfied. The rate corresponding to this extra collapse operator is set to zero.
Technically, the influence martingale formalism works as follows. We introduce an influence martingale μ(t), which follows the evolution of the system state. When no jump happens, it evolves as
μ(t) = exp (α∫0tK(τ)dτ)
where K(t) is for now an arbitrary function. When a jump corresponding to the collapse operator An happens, the influence martingale becomes
Assuming that the state ρ̄(t) computed by the Monte Carlo average
solves a Lindblad master equation with collapse operators An and rates Γn(t), the state ρ(t) defined by
solves a Lindblad master equation with collapse operators An and shifted rates γn(t) − K(t). Thus, while Γn(t) ≥ 0, the new "rates" γn(t) = Γn(t) − K(t) satisfy no positivity requirement.
The input of .nm_mcsolve
is almost the same as for .mcsolve
. The only difference is how the collapse operators and rate functions should be defined. nm_mcsolve
requires collapse operators An and target "rates" γn (which are allowed to take negative values) to be given in list form [[C_1, gamma_1], [C_2, gamma_2], ...]
. Note that we give the actual rate and not its square root, and that nm_mcsolve
automatically computes associated jump rates Γn(t) ≥ 0 appropriate for simulation.
We conclude with a simple example demonstrating the usage of the nm_mcsolve
function. For more elaborate, physically motivated examples, we refer to the accompanying tutorial notebook.
times = np.linspace(0, 1, 201) psi0 = basis(2, 1) a0 = destroy(2) H = a0.dag() * a0
# Rate functions gamma1 = "kappa * nth" gamma2 = "kappa * (nth+1) + 12 * np.exp(-2*t*3) (-np.sin(15*t)**2)" # gamma2 becomes negative during some time intervals
# nm_mcsolve integration ops_and_rates = [] ops_and_rates.append([a0.dag(), gamma1]) ops_and_rates.append([a0, gamma2]) MCSol = nm_mcsolve(H, psi0, times, ops_and_rates, args={'kappa': 1.0 / 0.129, 'nth': 0.063}, e_ops=[a0.dag() * a0, a0 * a0.dag()], options={'map': 'parallel'}, ntraj=2500)
# mesolve integration for comparison d_ops = [[lindblad_dissipator(a0.dag(), a0.dag()), gamma1], [lindblad_dissipator(a0, a0), gamma2]] MESol = mesolve(H, psi0, times, d_ops, e_ops=[a0.dag() * a0, a0 * a0.dag()], args={'kappa': 1.0 / 0.129, 'nth': 0.063})
plt.figure() plt.plot(times, MCSol.expect[0], 'g', times, MCSol.expect[1], 'b', times, MCSol.trace, 'r') plt.plot(times, MESol.expect[0], 'g--', times, MESol.expect[1], 'b--') plt.title('Monte Carlo time evolution') plt.xlabel('Time') plt.ylabel('Expectation values') plt.legend((r'$langle 1 | rho | 1 rangle$', r'$langle 0 | rho | 0 rangle$', r'$operatorname{tr} rho$')) plt.show()