Repository: tempoCollaboration/OQuPy

Issue:
Bug in SystemChain.add_site_dissipation()
Adding a Markovian Lindblad dissipator to a system chain seems to go wrong, as one can see by the decay of the norm in the following example with dissipation on the first of a two site chain (without any coherent evolution):

```python3

import oqupy
import numpy as np
import matplotlib.pyplot as plt

sigma_z = oqupy.operators.sigma("z")
sigma_minus = oqupy.operators.sigma("-")
up_dm = oqupy.operators.spin_dm("z+")
down_dm = oqupy.operators.spin_dm("z-")

initial_augmented_mps = oqupy.AugmentedMPS([up_dm, down_dm])
system_chain = oqupy.SystemChain(hilbert_space_dimensions=[2,2])
system_chain.add_site_dissipation(0, sigma_minus, gamma=0.2)

pt_tebd_params = oqupy.PtTebdParameters(
    dt=0.1,
    order=2,
    epsrel=1.0e-6)

pt_tebd = oqupy.PtTebd(
    initial_augmented_mps=initial_augmented_mps,
    system_chain=system_chain,
    process_tensors=[None, None, None, None, None],
    parameters=pt_tebd_params,
    dynamics_sites=[0, 1],
    chain_control=None)

num_steps = 20
results = pt_tebd.compute(num_steps, progress_type="bar")

plt.plot(results['norm'].real)
```

The norm drops below 0.7 in 20 time steps, which seems to be a real bug and not just a numerical error.


Discussion:

Patch:
diff --git a/oqupy/mps_mpo.py b/oqupy/mps_mpo.py
index 29e080c..6a24a85 100644
--- a/oqupy/mps_mpo.py
+++ b/oqupy/mps_mpo.py
@@ -198,14 +198,13 @@ def compute_nn_gate(
     nn_gate: NnGate
         Nearest neighbor gate.
     """
-    # exponentiate and transpose such that
-    # axis 0 is the input and axis 1 is the output leg of the propagator.
-    propagator = linalg.expm(dt*liouvillian).T
+    # exponentiate the liouvillian to become a propagator
+    propagator = linalg.expm(dt*liouvillian)
     # split leg 0 and leg 1 each into left and right.
-    propagator.shape = [hs_dim_l**2,
-                        hs_dim_r**2,
-                        hs_dim_l**2,
-                        hs_dim_r**2]
+    propagator.shape = [hs_dim_l**2, # left output
+                        hs_dim_r**2, # right output
+                        hs_dim_l**2, # left input
+                        hs_dim_r**2] # right input
     temp = np.swapaxes(propagator, 1, 2)
     temp = temp.reshape([hs_dim_l**2 * hs_dim_l**2,
                             hs_dim_r**2 * hs_dim_r**2])
@@ -217,7 +216,9 @@ def compute_nn_gate(
     sqrt_s = np.sqrt(s)
     u_sqrt_s = u * sqrt_s
     sqrt_s_vh =(sqrt_s * vh.T).T
+    # left tensor with legs: left output, left input, bond
     tensor_l = u_sqrt_s.reshape(hs_dim_l**2, hs_dim_l**2, chi)
+    # right tensor with legs: bond, right output, right input
     tensor_r = sqrt_s_vh.reshape(chi, hs_dim_r**2, hs_dim_r**2)
 
     return NnGate(site=site, tensors=(tensor_l, tensor_r))
diff --git a/oqupy/operators.py b/oqupy/operators.py
index 2566dcf..8dde09e 100644
--- a/oqupy/operators.py
+++ b/oqupy/operators.py
@@ -197,7 +197,7 @@ def cross_left_right_super(
         operator_2_l: ndarray,
         operator_2_r: ndarray) -> ndarray:
     """
-    Construct anit-commutator of cross term (acting on two Hilbert spaces).
+    Contruct map from rho to [(op1l x op2l) rho (op1r x op2r)].
     """
     op1l_op1r = np.kron(operator_1_l, operator_1_r.T)
     op2l_op2r = np.kron(operator_2_l, operator_2_r.T)
diff --git a/oqupy/system.py b/oqupy/system.py
index a7d11c1..184a68b 100644
--- a/oqupy/system.py
+++ b/oqupy/system.py
@@ -429,7 +429,8 @@ class SystemChain(BaseAPIClass):
         self._nn_liouvillians = []
         for hs_dim_l, hs_dim_r in zip(self._hs_dims[:-1], self._hs_dims[1:]):
             self._nn_liouvillians.append(
-                np.zeros((hs_dim_l**4, hs_dim_r**4), dtype=NpDtype))
+                np.zeros((hs_dim_l**2 * hs_dim_r**2, hs_dim_l**2 * hs_dim_r**2),
+                dtype=NpDtype))
 
         super().__init__(name, description)
 
@@ -496,7 +497,7 @@ class SystemChain(BaseAPIClass):
         liouvillian: ndarray
             Liouvillian acting on the single site.
         """
-        raise NotImplementedError()
+        self._site_liouvillians[site] += np.array(liouvillian, dtype=NpDtype)
 
     def add_site_dissipation(
             self,
@@ -525,12 +526,13 @@ class SystemChain(BaseAPIClass):
         gamma: float
             Optional multiplicative factor :math:`\gamma`.
         """
-        op = lindblad_operator
+        op = np.array(lindblad_operator, dtype=NpDtype)
         op_dagger = op.conjugate().T
         self._site_liouvillians[site] += \
-            gamma * (opr.left_right_super(op, op_dagger)
+            gamma * (opr.left_right_super(op, op_dagger) \
                       - 0.5 * opr.acommutator(np.dot(op_dagger, op)))
 
+
     def add_nn_hamiltonian(
             self,
             site: int,
@@ -585,7 +587,7 @@ class SystemChain(BaseAPIClass):
         liouvillian_l_r: ndarray
             Liouvillian acting on sites :math:`n` and :math:`n+1`.
         """
-        self._nn_liouvillians[site] += liouvillian_l_r
+        self._nn_liouvillians[site] += np.array(liouvillian_l_r, dtype=NpDtype)
 
     def add_nn_dissipation(
             self,


Test patch:
diff --git a/tests/coverage/pt_tebd_test.py b/tests/coverage/pt_tebd_test.py
index 80e47fc..b2fcc54 100644
--- a/tests/coverage/pt_tebd_test.py
+++ b/tests/coverage/pt_tebd_test.py
@@ -17,11 +17,12 @@ Tests for the time_evovling_mpo.pt_tebd module.
 
 import pytest
 
+import numpy as np
 import oqupy
 
 up_dm = oqupy.operators.spin_dm("z+")
-system_chain = oqupy.SystemChain(hilbert_space_dimensions=[2,2])
-initial_augmented_mps = oqupy.AugmentedMPS([up_dm, up_dm])
+system_chain = oqupy.SystemChain(hilbert_space_dimensions=[2,3])
+initial_augmented_mps = oqupy.AugmentedMPS([up_dm, np.diag([1,0,0])])
 pt_tebd_params = oqupy.PtTebdParameters(dt=0.2, order=2, epsrel=1.0e-4)
 
 def test_get_augmented_mps():
@@ -32,8 +33,10 @@ def test_get_augmented_mps():
         parameters=pt_tebd_params)
 
     augmented_mps = pt_tebd.get_augmented_mps()
-    assert augmented_mps.gammas[1].shape == (1,4,1,1)
+    assert augmented_mps.gammas[0].shape == (1,4,1,1)
+    assert augmented_mps.gammas[1].shape == (1,9,1,1)
 
-    pt_tebd.compute(end_step=1, progress_type='silent')
+    pt_tebd.compute(end_step=2, progress_type='silent')
     augmented_mps = pt_tebd.get_augmented_mps()
-    assert augmented_mps.gammas[1].shape == (1,4,1,1)
+    assert augmented_mps.gammas[0].shape == (1,4,1,1)
+    assert augmented_mps.gammas[1].shape == (1,9,1,1)
diff --git a/tests/physics/example_H_test.py b/tests/physics/example_H_test.py
new file mode 100644
index 0000000..2688cc7
--- /dev/null
+++ b/tests/physics/example_H_test.py
@@ -0,0 +1,101 @@
+# Copyright 2020 The TEMPO Collaboration
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""
+Tests for the time_evovling_mpo.backends.tensor_network modules.
+"""
+import sys
+sys.path.insert(0,'.')
+
+import pytest
+import numpy as np
+
+import oqupy
+
+# -----------------------------------------------------------------------------
+# -- Test F: Test Lindblad dissipation for PT-TEBD  ---------------------------
+
+# --- Parameters --------------------------------------------------------------
+
+# -- time steps --
+dt = 0.1
+num_steps = 10
+
+# -- bath --
+alpha = 0.3
+omega_cutoff = 3.0
+temperature = 0.8
+pt_dkmax = 10
+pt_epsrel = 1.0e-6
+
+# -- chain --
+N = 5
+Omega = 1.0
+eta = 0.3
+Delta = 1.2
+h = np.array(
+    [[1.0, 0.0, 0.0],
+     [2.0, 0.0, 0.0],
+     [3.0, 0.0, 0.0],
+     [4.0, 0.0, 0.0],
+     [5.0, 0.0, 0.0]]) * np.pi / 10
+J = np.array([[Delta, 1.0+eta, 1.0-eta]]*(N-1))
+up_dm = oqupy.operators.spin_dm("z+")
+down_dm = oqupy.operators.spin_dm("z-")
+tebd_order = 2
+tebd_epsrel = 1.0e-7
+
+
+def test_pt_tebd_site_dissipation_H1():
+    # -- initial state --
+    initial_augmented_mps = oqupy.AugmentedMPS([up_dm, down_dm, down_dm])
+
+    # -- add single site dissipation --
+    system_chain = oqupy.SystemChain(hilbert_space_dimensions=[2,2,2])
+    # lowering operator on site 0:
+    system_chain.add_site_dissipation(0,[[0,0],[1,0]])
+    # identity cross raising operator on sites 1 and 2:
+    system_chain.add_nn_dissipation(1,np.identity(2),[[0,1],[0,0]])
+
+    # -- PT-TEBD parameters --
+    pt_tebd_params = oqupy.PtTebdParameters(
+        dt=dt,
+        order=tebd_order,
+        epsrel=tebd_epsrel)
+
+    num_steps = int(1.0/pt_tebd_params.dt)
+
+    pt_tebd = oqupy.PtTebd(
+        initial_augmented_mps=initial_augmented_mps,
+        system_chain=system_chain,
+        process_tensors=[None]*3,
+        parameters=pt_tebd_params,
+        dynamics_sites=[0,1,2],
+        chain_control=None)
+
+    r = pt_tebd.compute(num_steps, progress_type="silent")
+
+    np.testing.assert_almost_equal(
+        r['dynamics'][0].states[-1],
+        [[np.exp(-1),0],[0,1-np.exp(-1)]],
+        decimal=4)
+    np.testing.assert_almost_equal(
+        r['dynamics'][1].states[-1],
+        [[0,0],[0,1]],
+        decimal=4)
+    np.testing.assert_almost_equal(
+        r['dynamics'][2].states[-1],
+        [[1-np.exp(-1),0],[0,np.exp(-1)]],
+        decimal=4)
+
+# -----------------------------------------------------------------------------