Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix setting of sso.m_ops in heterodyne smesolver and passing through of sc_ops to photocurrent solver. #2081

Merged
merged 10 commits into from
Feb 27, 2023
1 change: 1 addition & 0 deletions doc/changes/2081.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix setting of sso.m_ops in heterodyne smesolver and passing through of sc_ops to photocurrent solver. (`#2081 <https://github.com/qutip/qutip/pull/2081>`_ by Bogdan Reznychenko and Simon Cross)
14 changes: 8 additions & 6 deletions qutip/sparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,13 @@ def _orthogonalize(vec, other):
norm = np.sum(np.conj(vec) * vec)**0.5
vec /= norm

def eigh(mat, eigvals=[]):
def eigh(mat, subset_by_index=()):
val, vec = la.eig(mat)
val = np.real(val)
idx = np.argsort(val)
val = val[idx]
vec = vec[:, idx]
eigvals = subset_by_index
if eigvals:
val = val[eigvals[0]:eigvals[1]+1]
vec = vec[:, eigvals[0]:eigvals[1]+1]
Expand All @@ -47,9 +48,10 @@ def eigh(mat, eigvals=[]):
same_eigv = 0
return val, vec

def eigvalsh(a, UPLO="L", eigvals=[]):
def eigvalsh(a, UPLO="L", subset_by_index=()):
val = la.eigvals(a)
val = np.sort(np.real(val))
eigvals = subset_by_index
if eigvals:
return val[eigvals[0]:eigvals[1]+1]
return val
Expand Down Expand Up @@ -177,10 +179,10 @@ def _dense_eigs(data, isherm, vecs, N, eigvals, num_large, num_small):
else:
if num_small > 0:
evals, evecs = eigh(
data, eigvals=[0, num_small - 1])
data, subset_by_index=[0, num_small - 1])
if num_large > 0:
evals, evecs = eigh(
data, eigvals=[N - num_large, N - 1])
data, subset_by_index=[N - num_large, N - 1])
else:
evals, evecs = la.eig(data)
else:
Expand All @@ -189,9 +191,9 @@ def _dense_eigs(data, isherm, vecs, N, eigvals, num_large, num_small):
evals = eigvalsh(data)
else:
if num_small > 0:
evals = eigvalsh(data, eigvals=[0, num_small - 1])
evals = eigvalsh(data, subset_by_index=[0, num_small - 1])
if num_large > 0:
evals = eigvalsh(data, eigvals=[N - num_large, N - 1])
evals = eigvalsh(data, subset_by_index=[N - num_large, N - 1])
else:
evals = la.eigvals(data)

Expand Down
10 changes: 8 additions & 2 deletions qutip/stochastic.py
Original file line number Diff line number Diff line change
Expand Up @@ -597,13 +597,19 @@ def smesolve(H, rho0, times, c_ops=[], sc_ops=[], e_ops=[],
elif sso.method == 'heterodyne':
if sso.m_ops is None:
m_ops = []
elif len(sso.m_ops) % 2:
raise ValueError(
"The measured operators for the heterodyne method supposed"
" to be pairs of quadratures: m_ops should have even length."
)
hodgestar marked this conversation as resolved.
Show resolved Hide resolved
sso.sops = []
for c in sso.sc_ops:
if sso.m_ops is None:
m_ops += [c + c.dag(), -1j * (c - c.dag())]
sso.sops += [(spre(c) + spost(c.dag())) / np.sqrt(2),
(spre(c) - spost(c.dag())) * -1j / np.sqrt(2)]
sso.m_ops = m_ops
if sso.m_ops is None:
sso.m_ops = m_ops
if not isinstance(sso.dW_factors, list):
sso.dW_factors = [np.sqrt(2)] * len(sso.sops)
elif len(sso.dW_factors) == len(sso.m_ops):
Expand Down Expand Up @@ -689,7 +695,7 @@ def ssesolve(H, psi0, times, sc_ops=[], e_ops=[],
if "method" in kwargs and kwargs["method"] == "photocurrent":
print("stochastic solver with photocurrent method has been moved to "
"it's own function: photocurrent_sesolve")
return photocurrent_sesolve(H, psi0, times, c_ops=c_ops,
return photocurrent_sesolve(H, psi0, times, sc_ops=sc_ops,
e_ops=e_ops, _safe_mode=_safe_mode,
args=args, **kwargs)

Expand Down
66 changes: 65 additions & 1 deletion qutip/tests/test_stochastic_me.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

from qutip import (smesolve, mesolve, photocurrent_mesolve, liouvillian,
QobjEvo, spre, spost, destroy, coherent, parallel_map,
qeye, fock_dm, general_stochastic, ket2dm, num)
qeye, fock_dm, general_stochastic, ket2dm, num,
basis, sigmax, sigmay, sigmaz, sigmam)

def f(t, args):
return args["a"] * t
Expand Down Expand Up @@ -296,3 +297,66 @@ def test_smesolve_bad_e_ops():
res = smesolve(H, psi0, times, sc_ops=sc_ops, e_ops=e_ops, noise=1,
ntraj=ntraj, nsubsteps=nsubsteps, method='homodyne',
map_func=parallel_map)


def test_heterodyne_smesolve_custom_m_ops():
b = 1 # drive amplitude
gamma = 1 # spont. emission rate
eta = 0.3 # coupling efficiency
n_steps = 1000
n_traj = 300
noise_seed = 0 # noise random seed

H = np.sqrt(eta * gamma) * b * sigmay()
c_ops = [np.sqrt(gamma) * sigmam()]
psi0 = basis(2, 0)
times = np.linspace(0, 2 * np.pi, n_steps)

sme_het = smesolve(
H,
psi0,
times,
[],
c_ops,
e_ops=[sigmax(), sigmay(), sigmaz()],
store_measurement=True,
dW_factors=[1e-5, 1e-5],
method="heterodyne",
m_ops=[np.sqrt(eta)*sigmax(), np.sqrt(eta)*sigmay()],
ntraj=n_traj,
noise=noise_seed,
)

assert np.array(sme_het.measurement).shape == (n_traj, n_steps, 1, 2)
np.testing.assert_allclose(
np.array(sme_het.measurement).mean(axis=0)[:, 0, 0].T,
np.sqrt(eta) * sme_het.expect[0],
atol=1e-2,
)


def test_heterodyne_mesolve_incorrect_custom_m_ops():
eta = 0.3

with pytest.raises(ValueError) as err:
smesolve(
sigmax(),
basis(2),
np.linspace(0, 1, 10),
[],
[sigmam()],
e_ops=[],
store_measurement=True,
method="heterodyne",
# three m_ops, which is incorrect:
m_ops=[
np.sqrt(eta) * sigmax(),
np.sqrt(eta) * sigmay(),
np.sqrt(eta) * sigmaz(),
],
ntraj=10,
)
assert str(err.value) == (
"The measured operators for the heterodyne method supposed to be"
" pairs of quadratures: m_ops should have even length."
)