From a98ed976f347a6b1a072f7155a22c4e930582e20 Mon Sep 17 00:00:00 2001 From: Reza Haghshenas Date: Fri, 12 Mar 2021 11:49:48 -0800 Subject: [PATCH 01/64] add fsimg --- quimb/__init__.py | 2 + quimb/gen/operators.py | 48 ++++++++++++++++- quimb/tensor/circuit.py | 90 ++++++++++++++++++++++++++++++- tests/test_gen/test_operators.py | 6 ++- tests/test_tensor/test_circuit.py | 5 +- 5 files changed, 147 insertions(+), 4 deletions(-) diff --git a/quimb/__init__.py b/quimb/__init__.py index 458486633..017ed842d 100644 --- a/quimb/__init__.py +++ b/quimb/__init__.py @@ -105,6 +105,7 @@ swap, iswap, fsim, + fsimg, controlled, CNOT, cX, @@ -332,6 +333,7 @@ 'swap', 'iswap', 'fsim', + 'fsimg', 'controlled', 'CNOT', 'cX', diff --git a/quimb/gen/operators.py b/quimb/gen/operators.py index dd9b13481..332f3b3af 100644 --- a/quimb/gen/operators.py +++ b/quimb/gen/operators.py @@ -343,17 +343,63 @@ def fsim(theta, phi, dtype=complex, **kwargs): a = cos(theta) b = -1j * sin(theta) c = exp(-1j * phi) - gate = [[1, 0, 0, 0], [0, a, b, 0], [0, b, a, 0], [0, 0, 0, c]] + + gate = qu(gate, dtype=dtype, **kwargs) make_immutable(gate) return gate +#https://arxiv.org/pdf/2010.07965.pdf, Eq. 18 +def fsimg( theta, Zeta, chi, gamma, phi, dtype=complex, **kwargs): + r"""The 'fermionic simulation' gate: + \theta is the iSWAP angle + \phi is the controlled-phase angle + \Zeta, \chi, \gamma are single-qubit phase angles + + .. math:: + + \mathrm{fsimg}(\theta, \Zeta, \chi, \gamma, \phi) = + \begin{bmatrix} + 1 & 0 & 0 & 0\\ + 0 & \exp(-i(\gamma +\zeta )) \cos(\theta) & -i \exp(-i(\gamma - \chi )) sin(\theta) & 0\\ + 0 & -i \exp(-i(\gamma + \chi )) sin(\theta) & \exp(-i(\gamma - \zeta )) \cos(\theta) & 0\\ + 0 & 0 & 0 & \exp(-i (\phi +2 \gamma)) + \end{bmatrix} + + Note that ``theta`` ,``phi``, ``Zeta``, ``chi``, ``gamma`` should be specified in radians and the sign + convention with this gate varies. Here for example, + ``fsimg(- pi / 2, 0, 0, 0,0) == iswap()``. + """ + from cmath import cos, sin, exp + + print ("Holooo") + + a1 = exp(-1j * (gamma + Zeta )) * cos(theta) + a2 = exp(-1j * (gamma - Zeta )) * cos(theta) + + b1 = -1j * exp(-1j * (gamma - chi )) * sin(theta) + b2 = -1j * exp(-1j * (gamma + chi )) * sin(theta) + + c = exp(-1j * (phi + 2*gamma )) + + gate = [[1, 0, 0, 0], + [0, a1, b1, 0], + [0, b2, a2, 0], + [0, 0, 0, c]] + + + gate = qu(gate, dtype=dtype, **kwargs) + make_immutable(gate) + return gate + + + @functools.lru_cache(maxsize=4) def iswap(dtype=complex, **kwargs): iswap = qu([[1., 0., 0., 0.], diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index 64bddcfae..688a3ef36 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -408,6 +408,86 @@ def apply_fsim(psi, theta, phi, i, j, parametrize=False, **gate_opts): psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) + +def fsimg_param_gen(params): + theta, Zeta, chi, gamma, phi = params[0], params[1], params[2], params[3], params[4] + + a11_re = do('cos', theta) + a11_im = do('imag', a11_re) + a11 = do('complex', a11_re, a11_im) + + + e11_im = -(gamma + Zeta) + e11_re = do('imag', e11_im ) + e11 = do('exp', do('complex', e11_re, e11_im)) + + + + + a22_re = do('cos', theta) + a22_im = do('imag', a22_re) + a22 = do('complex', a22_re, a22_im) + + + e22_im = -(gamma - Zeta) + e22_re = do('imag', e22_im ) + e22 = do('exp', do('complex', e22_re, e22_im)) + + + + a21_re = do('sin', theta) + a21_im = do('imag', a21_re) + a21 = do('complex', a21_re, a21_im) + + + e21_im = -(gamma - chi) + e21_re = do('imag', e21_im ) + e21 = do('exp', do('complex', e21_re, e21_im)) + + + a12_re = do('sin', theta) + a12_im = do('imag', a12_re) + a12 = do('complex', a12_re, a12_im) + + + e12_im = -(gamma + chi) + e12_re = do('imag', e12_im ) + e12 = do('exp', do('complex', e12_re, e12_im)) + + + img_re = do('real', -1.j) + img_im = do('imag', -1.j) + img = do('complex', img_re, img_im) + + + c_im = -(2*gamma + phi) + c_re = do('imag', c_im ) + c = do('exp', do('complex', c_re, c_im)) + + + + data = [[[[1, 0], [0, 0]], + [[0, a11*e11], [a21*e21*img, 0]]], + [[[0, a12*e12*img], [a22*e22, 0]], + [[0, 0], [0, c]]]] + + return do('array', data, like=params) + + + + +def apply_fsimg(psi, theta, Zeta, chi, gamma, phi, i, j, parametrize=False, **gate_opts): + mtags = _merge_tags('FSIMG', gate_opts) + if parametrize: + G = ops.PArray(fsimg_param_gen, (theta, Zeta, chi, gamma, phi)) + else: + G = qu.fsimg(theta, Zeta, chi, gamma, phi) + psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) + + + + + def rzz_param_gen(params): gamma = params[0] @@ -536,12 +616,13 @@ def apply_su4( 'CU1': apply_cu1, 'FS': apply_fsim, 'FSIM': apply_fsim, + 'FSIMG': apply_fsimg, 'RZZ': apply_rzz, 'SU4': apply_su4, } ONE_QUBIT_PARAM_GATES = {'RX', 'RY', 'RZ', 'U3', 'U2', 'U1'} -TWO_QUBIT_PARAM_GATES = {'CU3', 'CU2', 'CU1', 'FS', 'FSIM', 'RZZ', 'SU4'} +TWO_QUBIT_PARAM_GATES = {'CU3', 'CU2', 'CU1', 'FS', 'FSIM', 'FSIMG', 'RZZ', 'SU4'} ALL_PARAM_GATES = ONE_QUBIT_PARAM_GATES | TWO_QUBIT_PARAM_GATES @@ -911,6 +992,13 @@ def fsim(self, theta, phi, i, j, gate_round=None, parametrize=False): self.apply_gate('FSIM', theta, phi, i, j, gate_round=gate_round, parametrize=parametrize) + def fsimg(self, theta, Zeta, chi, gamma, phi, i, j, gate_round=None, parametrize=False): + self.apply_gate('FSIMG', theta, Zeta, chi, gamma, phi, i, j, + gate_round=gate_round, parametrize=parametrize) + + + + def rzz(self, theta, i, j, gate_round=None, parametrize=False): self.apply_gate('RZZ', theta, i, j, gate_round=gate_round, parametrize=parametrize) diff --git a/tests/test_gen/test_operators.py b/tests/test_gen/test_operators.py index 520e7e0a3..a93a05c65 100644 --- a/tests/test_gen/test_operators.py +++ b/tests/test_gen/test_operators.py @@ -135,7 +135,7 @@ class TestGates: @pytest.mark.parametrize("gate", ['Rx', 'Ry', 'Rz', 'T_gate', 'S_gate', 'CNOT', 'cX', 'cY', 'cZ', 'hadamard', 'phase_gate', 'iswap', 'swap', 'U_gate', - 'fsim']) + 'fsim', 'fsimg']) @pytest.mark.parametrize('dtype', [np.complex64, np.complex128]) @pytest.mark.parametrize('sparse', [False, True]) def test_construct(self, gate, dtype, sparse): @@ -145,6 +145,8 @@ def test_construct(self, gate, dtype, sparse): args = (0.1, 0.2, 0.3) elif gate in {'fsim'}: args = (-1.3, 5.4) + elif gate in {'fsimg'}: + args = (-1.3, 5.4, 2., 3., 4.) else: args = () G = getattr(qu, gate)(*args, dtype=dtype, sparse=sparse) @@ -161,6 +163,8 @@ def test_gates_import(self): def test_fsim(self): assert_allclose(qu.fsim(- qu.pi / 2, 0.0), qu.iswap(), atol=1e-12) + def test_fsimg(self): + assert_allclose(qu.fsimg(- qu.pi / 2, 0.0, 0.0, 0.0, 0.0), qu.iswap(), atol=1e-12) class TestHamHeis: def test_ham_heis_2(self): diff --git a/tests/test_tensor/test_circuit.py b/tests/test_tensor/test_circuit.py index ece894dd3..9cd5a6ed6 100644 --- a/tests/test_tensor/test_circuit.py +++ b/tests/test_tensor/test_circuit.py @@ -82,9 +82,11 @@ def swappy_circ(n, depth): qi = pairs[2 * i] qj = pairs[2 * i + 1] - gate = np.random.choice(['FSIM', 'SWAP']) + gate = np.random.choice(['FSIM', 'SWAP', 'FSIMG']) if gate == 'FSIM': params = np.random.randn(2) + elif gate == 'FSIMG': + params = np.random.randn(5) else: params = () @@ -166,6 +168,7 @@ def test_all_gate_methods(self, Circ): ('cu2', 2, 2), ('cu1', 2, 1), ('fsim', 2, 2), + ('fsimg', 2, 5), ('rzz', 2, 1), ('su4', 2, 15), ] From f316b82e138e97f5469b6aaa0c368a292f8a4017 Mon Sep 17 00:00:00 2001 From: Reza Haghshenas Date: Fri, 12 Mar 2021 14:38:05 -0800 Subject: [PATCH 02/64] fixing style --- quimb/gen/operators.py | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/quimb/gen/operators.py b/quimb/gen/operators.py index 332f3b3af..8bfe11010 100644 --- a/quimb/gen/operators.py +++ b/quimb/gen/operators.py @@ -348,58 +348,51 @@ def fsim(theta, phi, dtype=complex, **kwargs): [0, b, a, 0], [0, 0, 0, c]] - - gate = qu(gate, dtype=dtype, **kwargs) make_immutable(gate) return gate -#https://arxiv.org/pdf/2010.07965.pdf, Eq. 18 -def fsimg( theta, Zeta, chi, gamma, phi, dtype=complex, **kwargs): +def fsimg(theta, Zeta, chi, gamma, phi, dtype=complex, **kwargs): r"""The 'fermionic simulation' gate: \theta is the iSWAP angle \phi is the controlled-phase angle - \Zeta, \chi, \gamma are single-qubit phase angles - + \Zeta, \chi, \gamma are single-qubit phase angles .. math:: - \mathrm{fsimg}(\theta, \Zeta, \chi, \gamma, \phi) = \begin{bmatrix} 1 & 0 & 0 & 0\\ - 0 & \exp(-i(\gamma +\zeta )) \cos(\theta) & -i \exp(-i(\gamma - \chi )) sin(\theta) & 0\\ - 0 & -i \exp(-i(\gamma + \chi )) sin(\theta) & \exp(-i(\gamma - \zeta )) \cos(\theta) & 0\\ + 0 & \exp(-i(\gamma +\zeta )) \cos(\theta) & + -i \exp(-i(\gamma - \chi )) sin(\theta) & 0\\ + 0 & -i \exp(-i(\gamma + \chi )) sin(\theta) & + \exp(-i(\gamma - \zeta )) \cos(\theta) & 0\\ 0 & 0 & 0 & \exp(-i (\phi +2 \gamma)) \end{bmatrix} - Note that ``theta`` ,``phi``, ``Zeta``, ``chi``, ``gamma`` should be specified in radians and the sign + Note that ``theta`` ,``phi``, ``Zeta``, ``chi``, ``gamma`` + should be specified in radians and the sign convention with this gate varies. Here for example, ``fsimg(- pi / 2, 0, 0, 0,0) == iswap()``. """ from cmath import cos, sin, exp - print ("Holooo") + a1 = exp(-1j * (gamma + Zeta)) * cos(theta) + a2 = exp(-1j * (gamma - Zeta)) * cos(theta) - a1 = exp(-1j * (gamma + Zeta )) * cos(theta) - a2 = exp(-1j * (gamma - Zeta )) * cos(theta) + b1 = -1j * exp(-1j * (gamma - chi)) * sin(theta) + b2 = -1j * exp(-1j * (gamma + chi)) * sin(theta) - b1 = -1j * exp(-1j * (gamma - chi )) * sin(theta) - b2 = -1j * exp(-1j * (gamma + chi )) * sin(theta) - - c = exp(-1j * (phi + 2*gamma )) + c = exp(-1j * (phi + 2*gamma)) gate = [[1, 0, 0, 0], [0, a1, b1, 0], [0, b2, a2, 0], [0, 0, 0, c]] - - gate = qu(gate, dtype=dtype, **kwargs) make_immutable(gate) return gate - @functools.lru_cache(maxsize=4) def iswap(dtype=complex, **kwargs): iswap = qu([[1., 0., 0., 0.], From 2ae17534e4d0d5864a74421c2f5e7e49d9bc6db5 Mon Sep 17 00:00:00 2001 From: Reza Haghshenas Date: Fri, 12 Mar 2021 15:03:58 -0800 Subject: [PATCH 03/64] fixed python-style circuit class --- quimb/tensor/circuit.py | 51 ++++++++++++++++------------------------- 1 file changed, 20 insertions(+), 31 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index 688a3ef36..ad8f842a1 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -408,64 +408,50 @@ def apply_fsim(psi, theta, phi, i, j, parametrize=False, **gate_opts): psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) - def fsimg_param_gen(params): - theta, Zeta, chi, gamma, phi = params[0], params[1], params[2], params[3], params[4] + theta, Zeta, chi, gamma, phi = params[0], params[1], + params[2], params[3], params[4] a11_re = do('cos', theta) a11_im = do('imag', a11_re) a11 = do('complex', a11_re, a11_im) - e11_im = -(gamma + Zeta) - e11_re = do('imag', e11_im ) + e11_re = do('imag', e11_im) e11 = do('exp', do('complex', e11_re, e11_im)) - - - a22_re = do('cos', theta) a22_im = do('imag', a22_re) a22 = do('complex', a22_re, a22_im) - e22_im = -(gamma - Zeta) - e22_re = do('imag', e22_im ) + e22_re = do('imag', e22_im) e22 = do('exp', do('complex', e22_re, e22_im)) - - a21_re = do('sin', theta) a21_im = do('imag', a21_re) a21 = do('complex', a21_re, a21_im) - e21_im = -(gamma - chi) - e21_re = do('imag', e21_im ) + e21_re = do('imag', e21_im) e21 = do('exp', do('complex', e21_re, e21_im)) - a12_re = do('sin', theta) a12_im = do('imag', a12_re) a12 = do('complex', a12_re, a12_im) - e12_im = -(gamma + chi) - e12_re = do('imag', e12_im ) + e12_re = do('imag', e12_im) e12 = do('exp', do('complex', e12_re, e12_im)) - img_re = do('real', -1.j) img_im = do('imag', -1.j) img = do('complex', img_re, img_im) - c_im = -(2*gamma + phi) - c_re = do('imag', c_im ) + c_re = do('imag', c_im) c = do('exp', do('complex', c_re, c_im)) - - data = [[[[1, 0], [0, 0]], [[0, a11*e11], [a21*e21*img, 0]]], [[[0, a12*e12*img], [a22*e22, 0]], @@ -474,9 +460,12 @@ def fsimg_param_gen(params): return do('array', data, like=params) +def apply_fsimg( + psi, + theta, Zeta, chi, gamma, phi, + i, j, parametrize=False, **gate_opts +): - -def apply_fsimg(psi, theta, Zeta, chi, gamma, phi, i, j, parametrize=False, **gate_opts): mtags = _merge_tags('FSIMG', gate_opts) if parametrize: G = ops.PArray(fsimg_param_gen, (theta, Zeta, chi, gamma, phi)) @@ -485,9 +474,6 @@ def apply_fsimg(psi, theta, Zeta, chi, gamma, phi, i, j, parametrize=False, **ga psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) - - - def rzz_param_gen(params): gamma = params[0] @@ -622,7 +608,10 @@ def apply_su4( } ONE_QUBIT_PARAM_GATES = {'RX', 'RY', 'RZ', 'U3', 'U2', 'U1'} -TWO_QUBIT_PARAM_GATES = {'CU3', 'CU2', 'CU1', 'FS', 'FSIM', 'FSIMG', 'RZZ', 'SU4'} +TWO_QUBIT_PARAM_GATES = { + 'CU3', 'CU2', 'CU1', 'FS', 'FSIM', 'FSIMG', + 'RZZ', 'SU4' +} ALL_PARAM_GATES = ONE_QUBIT_PARAM_GATES | TWO_QUBIT_PARAM_GATES @@ -992,13 +981,13 @@ def fsim(self, theta, phi, i, j, gate_round=None, parametrize=False): self.apply_gate('FSIM', theta, phi, i, j, gate_round=gate_round, parametrize=parametrize) - def fsimg(self, theta, Zeta, chi, gamma, phi, i, j, gate_round=None, parametrize=False): + def fsimg( + self, theta, Zeta, chi, + gamma, phi, i, j, gate_round=None, parametrize=False + ): self.apply_gate('FSIMG', theta, Zeta, chi, gamma, phi, i, j, gate_round=gate_round, parametrize=parametrize) - - - def rzz(self, theta, i, j, gate_round=None, parametrize=False): self.apply_gate('RZZ', theta, i, j, gate_round=gate_round, parametrize=parametrize) From 4befb5e0bad51d9d3b82e6b2ae04feff1ddd9343 Mon Sep 17 00:00:00 2001 From: Reza Haghshenas Date: Fri, 12 Mar 2021 15:11:59 -0800 Subject: [PATCH 04/64] fix style test_operators.py --- tests/test_gen/test_operators.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_gen/test_operators.py b/tests/test_gen/test_operators.py index a93a05c65..432dc40e1 100644 --- a/tests/test_gen/test_operators.py +++ b/tests/test_gen/test_operators.py @@ -164,7 +164,10 @@ def test_fsim(self): assert_allclose(qu.fsim(- qu.pi / 2, 0.0), qu.iswap(), atol=1e-12) def test_fsimg(self): - assert_allclose(qu.fsimg(- qu.pi / 2, 0.0, 0.0, 0.0, 0.0), qu.iswap(), atol=1e-12) + assert_allclose( + qu.fsimg(- qu.pi / 2, 0.0, 0.0, 0.0, 0.0), + qu.iswap(), atol=1e-12 + ) class TestHamHeis: def test_ham_heis_2(self): From 74656624bb20af34509a85fefdac127ff09c3077 Mon Sep 17 00:00:00 2001 From: rezahhh Date: Fri, 12 Mar 2021 17:39:21 -0800 Subject: [PATCH 05/64] minor fix --- quimb/tensor/circuit.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index ad8f842a1..d65449c59 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -409,8 +409,12 @@ def apply_fsim(psi, theta, phi, i, j, parametrize=False, **gate_opts): def fsimg_param_gen(params): - theta, Zeta, chi, gamma, phi = params[0], params[1], - params[2], params[3], params[4] + theta, Zeta, chi, gamma, phi = ( + params[0], + params[1], + params[2], + params[3], + params[4]) a11_re = do('cos', theta) a11_im = do('imag', a11_re) From 0fd8ed404c34455ea6f3f6f3d254dfbb7f59100e Mon Sep 17 00:00:00 2001 From: rezahhh Date: Tue, 16 Mar 2021 14:06:13 -0700 Subject: [PATCH 06/64] add fsimt --- quimb/__init__.py | 2 ++ quimb/gen/operators.py | 37 ++++++++++++++++++++++++++++++++++++ quimb/tensor/circuit.py | 42 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 80 insertions(+), 1 deletion(-) diff --git a/quimb/__init__.py b/quimb/__init__.py index 017ed842d..e656c3adb 100644 --- a/quimb/__init__.py +++ b/quimb/__init__.py @@ -105,6 +105,7 @@ swap, iswap, fsim, + fsimt, fsimg, controlled, CNOT, @@ -333,6 +334,7 @@ 'swap', 'iswap', 'fsim', + 'fsimt', 'fsimg', 'controlled', 'CNOT', diff --git a/quimb/gen/operators.py b/quimb/gen/operators.py index 8bfe11010..dbe780827 100644 --- a/quimb/gen/operators.py +++ b/quimb/gen/operators.py @@ -353,6 +353,43 @@ def fsim(theta, phi, dtype=complex, **kwargs): return gate +def fsimt(theta, dtype=complex, **kwargs): + r"""The 'fermionic simulation' gate: + + .. math:: + + \mathrm{fsim}(\theta, \phi) = + \begin{bmatrix} + 1 & 0 & 0 & 0\\ + 0 & \cos(\theta) & -i sin(\theta) & 0\\ + 0 & -i sin(\theta) & \cos(\theta) & 0\\ + 0 & 0 & 0 & \exp(-i \phi) + \end{bmatrix} + + Note that ``theta`` and ``phi`` should be specified in radians and the sign + convention with this gate varies. Here for example, + ``fsim(- pi / 2, 0) == iswap()``. + """ + from cmath import cos, sin, exp + + a = cos(theta) + b = 1j * sin(theta) + gate = [[1, 0, 0, 0], + [0, a, b, 0], + [0, b, a, 0], + [0, 0, 0, 1]] + + gate = qu(gate, dtype=dtype, **kwargs) + make_immutable(gate) + return gate + + + + + + + + def fsimg(theta, Zeta, chi, gamma, phi, dtype=complex, **kwargs): r"""The 'fermionic simulation' gate: \theta is the iSWAP angle diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index d65449c59..97ce4f6cb 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -398,6 +398,28 @@ def fsim_param_gen(params): return do('array', data, like=params) +def fsimt_param_gen(params): + theta = params[0] + a_re = do('cos', theta) + a_im = do('imag', a_re) + a = do('complex', a_re, a_im) + + b_im = do('sin', theta) + b_re = do('imag', b_im) + b = do('complex', b_re, b_im) + + + + data = [[[[1, 0], [0, 0]], + [[0, a], [b, 0]]], + [[[0, b], [a, 0]], + [[0, 0], [0, 1]]]] + + return do('array', data, like=params) + + + + def apply_fsim(psi, theta, phi, i, j, parametrize=False, **gate_opts): mtags = _merge_tags('FSIM', gate_opts) @@ -408,6 +430,18 @@ def apply_fsim(psi, theta, phi, i, j, parametrize=False, **gate_opts): psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) +def apply_fsimt(psi, theta, i, j, parametrize=False, **gate_opts): + mtags = _merge_tags('FSIMT', gate_opts) + if parametrize: + G = ops.PArray(fsimt_param_gen, (theta)) + else: + G = qu.fsimt(theta) + psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) + + + + + def fsimg_param_gen(params): theta, Zeta, chi, gamma, phi = ( params[0], @@ -606,6 +640,7 @@ def apply_su4( 'CU1': apply_cu1, 'FS': apply_fsim, 'FSIM': apply_fsim, + 'FSIMT': apply_fsimt, 'FSIMG': apply_fsimg, 'RZZ': apply_rzz, 'SU4': apply_su4, @@ -613,7 +648,7 @@ def apply_su4( ONE_QUBIT_PARAM_GATES = {'RX', 'RY', 'RZ', 'U3', 'U2', 'U1'} TWO_QUBIT_PARAM_GATES = { - 'CU3', 'CU2', 'CU1', 'FS', 'FSIM', 'FSIMG', + 'CU3', 'CU2', 'CU1', 'FS', 'FSIM', 'FSIMG','FSIMT' 'RZZ', 'SU4' } ALL_PARAM_GATES = ONE_QUBIT_PARAM_GATES | TWO_QUBIT_PARAM_GATES @@ -985,6 +1020,11 @@ def fsim(self, theta, phi, i, j, gate_round=None, parametrize=False): self.apply_gate('FSIM', theta, phi, i, j, gate_round=gate_round, parametrize=parametrize) + + def fsimt(self, theta, i, j, gate_round=None, parametrize=False): + self.apply_gate('FSIMT', theta, i, j, + gate_round=gate_round, parametrize=parametrize) + def fsimg( self, theta, Zeta, chi, gamma, phi, i, j, gate_round=None, parametrize=False From 4db93ba803348530e50eef6b9ebe09992f8ee32e Mon Sep 17 00:00:00 2001 From: rezahhh Date: Tue, 16 Mar 2021 14:34:41 -0700 Subject: [PATCH 07/64] edit --- quimb/tensor/circuit.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index 97ce4f6cb..ec41fc810 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -649,8 +649,7 @@ def apply_su4( ONE_QUBIT_PARAM_GATES = {'RX', 'RY', 'RZ', 'U3', 'U2', 'U1'} TWO_QUBIT_PARAM_GATES = { 'CU3', 'CU2', 'CU1', 'FS', 'FSIM', 'FSIMG','FSIMT' - 'RZZ', 'SU4' -} + 'RZZ', 'SU4'} ALL_PARAM_GATES = ONE_QUBIT_PARAM_GATES | TWO_QUBIT_PARAM_GATES From 3b156cddf527a792734461076661d0f6d9267460 Mon Sep 17 00:00:00 2001 From: rezah Date: Tue, 16 Mar 2021 15:26:33 -0700 Subject: [PATCH 08/64] erro fixed --- quimb/gates.py | 2 ++ quimb/tensor/circuit.py | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/quimb/gates.py b/quimb/gates.py index 7d0137ca8..e1888399a 100644 --- a/quimb/gates.py +++ b/quimb/gates.py @@ -26,3 +26,5 @@ U3 = operators.U_gate fsim = operators.fsim +fsimg = operators.fsimg +fsimt = operators.fsimt diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index ec41fc810..f1f1a4d97 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -430,10 +430,10 @@ def apply_fsim(psi, theta, phi, i, j, parametrize=False, **gate_opts): psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) -def apply_fsimt(psi, theta, i, j, parametrize=False, **gate_opts): +def apply_fsimt(psi,theta, i, j, parametrize=False, **gate_opts): mtags = _merge_tags('FSIMT', gate_opts) if parametrize: - G = ops.PArray(fsimt_param_gen, (theta)) + G = ops.PArray(fsimt_param_gen, (theta,)) else: G = qu.fsimt(theta) psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) @@ -648,7 +648,7 @@ def apply_su4( ONE_QUBIT_PARAM_GATES = {'RX', 'RY', 'RZ', 'U3', 'U2', 'U1'} TWO_QUBIT_PARAM_GATES = { - 'CU3', 'CU2', 'CU1', 'FS', 'FSIM', 'FSIMG','FSIMT' + 'CU3', 'CU2', 'CU1', 'FS', 'FSIM', 'FSIMG','FSIMT', 'RZZ', 'SU4'} ALL_PARAM_GATES = ONE_QUBIT_PARAM_GATES | TWO_QUBIT_PARAM_GATES From a4d7e772cdfcddff41946eb9564beeffc27d23c1 Mon Sep 17 00:00:00 2001 From: rezahhh Date: Wed, 17 Mar 2021 08:02:36 -0700 Subject: [PATCH 09/64] fsim native gate form --- quimb/gen/operators.py | 16 +++++++++------- quimb/tensor/circuit.py | 42 ++++++++++++++++++++++------------------- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/quimb/gen/operators.py b/quimb/gen/operators.py index dbe780827..676a444af 100644 --- a/quimb/gen/operators.py +++ b/quimb/gen/operators.py @@ -390,7 +390,7 @@ def fsimt(theta, dtype=complex, **kwargs): -def fsimg(theta, Zeta, chi, gamma, phi, dtype=complex, **kwargs): +def fsimg(theta, zeta, chi, gamma, phi, dtype=complex, **kwargs): r"""The 'fermionic simulation' gate: \theta is the iSWAP angle \phi is the controlled-phase angle @@ -413,15 +413,17 @@ def fsimg(theta, Zeta, chi, gamma, phi, dtype=complex, **kwargs): """ from cmath import cos, sin, exp - a1 = exp(-1j * (gamma + Zeta)) * cos(theta) - a2 = exp(-1j * (gamma - Zeta)) * cos(theta) + a00 = exp(1j * (gamma + phi)) - b1 = -1j * exp(-1j * (gamma - chi)) * sin(theta) - b2 = -1j * exp(-1j * (gamma + chi)) * sin(theta) + a1 = -1 * exp(1j * (-gamma + phi + zeta)) * sin(theta) + a2 = exp(1j * -(gamma + phi + zeta)) * sin(theta) - c = exp(-1j * (phi + 2*gamma)) + b1 = exp(1j * (-gamma + phi + chi)) * cos(theta) + b2 = exp(1j * -(gamma + phi + chi)) * cos(theta) - gate = [[1, 0, 0, 0], + c = exp(1j * (gamma - phi)) + + gate = [[a00, 0, 0, 0], [0, a1, b1, 0], [0, b2, a2, 0], [0, 0, 0, c]] diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index f1f1a4d97..94c9187d9 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -443,56 +443,60 @@ def apply_fsimt(psi,theta, i, j, parametrize=False, **gate_opts): def fsimg_param_gen(params): - theta, Zeta, chi, gamma, phi = ( + theta, zeta, chi, gamma, phi = ( params[0], params[1], params[2], params[3], params[4]) - a11_re = do('cos', theta) + e00_im = (gamma + phi) + e00_re = do('imag', e00_im) + e00 = do('exp', do('complex', e00_re, e00_im)) + + c_im = (gamma - phi) + c_re = do('imag', c_im) + c = do('exp', do('complex', c_re, c_im)) + + a11_re = -do('sin', theta) a11_im = do('imag', a11_re) a11 = do('complex', a11_re, a11_im) - e11_im = -(gamma + Zeta) + e11_im = (-gamma + phi + zeta) e11_re = do('imag', e11_im) e11 = do('exp', do('complex', e11_re, e11_im)) - a22_re = do('cos', theta) + + a22_re = do('sin', theta) a22_im = do('imag', a22_re) a22 = do('complex', a22_re, a22_im) - e22_im = -(gamma - Zeta) + e22_im = -(gamma + phi + zeta) e22_re = do('imag', e22_im) e22 = do('exp', do('complex', e22_re, e22_im)) - a21_re = do('sin', theta) + + + a21_re = do('cos', theta) a21_im = do('imag', a21_re) a21 = do('complex', a21_re, a21_im) - e21_im = -(gamma - chi) + e21_im = (-gamma + phi + chi) e21_re = do('imag', e21_im) e21 = do('exp', do('complex', e21_re, e21_im)) - a12_re = do('sin', theta) + a12_re = do('cos', theta) a12_im = do('imag', a12_re) a12 = do('complex', a12_re, a12_im) - e12_im = -(gamma + chi) + e12_im = -(gamma + phi + chi) e12_re = do('imag', e12_im) e12 = do('exp', do('complex', e12_re, e12_im)) - img_re = do('real', -1.j) - img_im = do('imag', -1.j) - img = do('complex', img_re, img_im) - c_im = -(2*gamma + phi) - c_re = do('imag', c_im) - c = do('exp', do('complex', c_re, c_im)) - - data = [[[[1, 0], [0, 0]], - [[0, a11*e11], [a21*e21*img, 0]]], - [[[0, a12*e12*img], [a22*e22, 0]], + data = [[[[e00, 0], [0, 0]], + [[0, a11*e11], [a21*e21, 0]]], + [[[0, a12*e12], [a22*e22, 0]], [[0, 0], [0, c]]]] return do('array', data, like=params) From fa22b2d62babfc86ee962f72712f9cb38eb23c45 Mon Sep 17 00:00:00 2001 From: rezahhh Date: Wed, 17 Mar 2021 16:24:43 -0700 Subject: [PATCH 10/64] Fix fsimg --- quimb/gen/operators.py | 8 ++++---- quimb/tensor/circuit.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/quimb/gen/operators.py b/quimb/gen/operators.py index 676a444af..f70008bfa 100644 --- a/quimb/gen/operators.py +++ b/quimb/gen/operators.py @@ -415,11 +415,11 @@ def fsimg(theta, zeta, chi, gamma, phi, dtype=complex, **kwargs): a00 = exp(1j * (gamma + phi)) - a1 = -1 * exp(1j * (-gamma + phi + zeta)) * sin(theta) - a2 = exp(1j * -(gamma + phi + zeta)) * sin(theta) + a1 = exp(1j * (-gamma + phi + zeta)) * sin(theta) + a2 = -1 * exp(1j * -(gamma + phi + zeta)) * sin(theta) - b1 = exp(1j * (-gamma + phi + chi)) * cos(theta) - b2 = exp(1j * -(gamma + phi + chi)) * cos(theta) + b1 = exp(1j * -(gamma + phi + chi)) * cos(theta) + b2 = exp(1j * (-gamma + phi + chi)) * cos(theta) c = exp(1j * (gamma - phi)) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index 94c9187d9..828d4ad49 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -458,7 +458,7 @@ def fsimg_param_gen(params): c_re = do('imag', c_im) c = do('exp', do('complex', c_re, c_im)) - a11_re = -do('sin', theta) + a11_re = do('sin', theta) a11_im = do('imag', a11_re) a11 = do('complex', a11_re, a11_im) @@ -467,7 +467,7 @@ def fsimg_param_gen(params): e11 = do('exp', do('complex', e11_re, e11_im)) - a22_re = do('sin', theta) + a22_re = -do('sin', theta) a22_im = do('imag', a22_re) a22 = do('complex', a22_re, a22_im) @@ -481,7 +481,7 @@ def fsimg_param_gen(params): a21_im = do('imag', a21_re) a21 = do('complex', a21_re, a21_im) - e21_im = (-gamma + phi + chi) + e21_im = -(gamma + phi + chi) e21_re = do('imag', e21_im) e21 = do('exp', do('complex', e21_re, e21_im)) @@ -489,7 +489,7 @@ def fsimg_param_gen(params): a12_im = do('imag', a12_re) a12 = do('complex', a12_re, a12_im) - e12_im = -(gamma + phi + chi) + e12_im = (-gamma + phi + chi) e12_re = do('imag', e12_im) e12 = do('exp', do('complex', e12_re, e12_im)) From dabcdb3866f8dcc2878875263050025312899943 Mon Sep 17 00:00:00 2001 From: rezahhh Date: Sat, 20 Mar 2021 14:52:39 -0700 Subject: [PATCH 11/64] share-tags --- quimb/tensor/optimize.py | 178 +++++++++++++++++++++++++++++++++------ 1 file changed, 154 insertions(+), 24 deletions(-) diff --git a/quimb/tensor/optimize.py b/quimb/tensor/optimize.py index 2f4c08ed2..828ceb5d8 100644 --- a/quimb/tensor/optimize.py +++ b/quimb/tensor/optimize.py @@ -2,6 +2,7 @@ automatically derive gradients for input to scipy optimizers. """ import re +import warnings import functools import importlib from collections.abc import Iterable @@ -121,37 +122,159 @@ def unpack(self, vector=None): return arrays -def parse_network_to_backend(tn, tags, constant_tags, to_constant): +_VARIABLE_TAG = "__VARIABLE{}__" +variable_finder = re.compile(r'__VARIABLE(\d+)__') + + +def _get_tensor_data(t): + """ simple function to extract tensor data """ + + if isinstance(t, PTensor): + data = t.params + else: + data = t.data + + # jax doesn't like numpy.ndarray subclasses... + if isinstance(data, qarray): + data = data.A + + return data + + +def _parse_opt_in(tn, tags, shared_tags, to_constant,): + """ """ tn_ag = tn.copy() variables = [] - variable_tag = "__VARIABLE{}__" + # tags where each individual tensor should get a separate variable + individual_tags = tags - shared_tags + + # handle tagged tensors that are not shared + for t in tn_ag.select(individual_tags, 'any'): + # append the raw data but mark the corresponding tensor + # for reinsertion + data = _get_tensor_data(t) + variables.append(data) + t.add_tag(_VARIABLE_TAG.format(len(variables) - 1)) + + # handle shared tags + for tag in shared_tags: + + var_name = _VARIABLE_TAG.format(len(variables)) + test_data = None + + for t in tn_ag.select(tag): + data = _get_tensor_data(t) + + # detect that this tensor is already variable tagged and skip + # if it is + if any(variable_finder.match(tag) for tag in t.tags): + warnings.warn('TNOptimizer warning, tensor tagged with' + + ' multiple `tags` or `shared_tags`.') + continue + + if test_data is None: + # create variable and store data + variables.append(data) + test_data = data + else: + # check that the shape of the variable's data matches the + # data of this new tensor + if not test_data.shape == data.shape: + raise ValueError('TNOptimizer error, a `shared_tags`' + + ' tag covers tensors with different' + + ' numbers of params.') + + # mark the corresponding tensor for reinsertion + t.add_tag(var_name) + + # iterate over tensors which *don't* have any of the given tags + for t in tn_ag.select_tensors(tags, which='!any'): + t.modify(apply=to_constant) + + return tn_ag, variables + + +def _parse_opt_out(tn, constant_tags, to_constant,): + """ """ + tn_ag = tn.copy() + variables = [] for t in tn_ag: - # check if tensor has any of the constant tags + if t.tags & constant_tags: t.modify(apply=to_constant) continue - # if tags are specified only optimize those tagged - if tags and not (t.tags & tags): - t.modify(apply=to_constant) - continue + # append the raw data but mark the corresponding tensor + # for reinsertion + data = _get_tensor_data(t) + variables.append(data) + t.add_tag(_VARIABLE_TAG.format(len(variables) - 1)) - if isinstance(t, PTensor): - data = t.params - else: - data = t.data + return tn_ag, variables - # jax doesn't like numpy.ndarray subclasses... - if isinstance(data, qarray): - data = data.A - # append the raw data but mark the corresponding tensor for reinsertion - variables.append(data) - t.add_tag(variable_tag.format(len(variables) - 1)) +def parse_network_to_backend( + tn, + to_constant, + tags=None, + shared_tags=None, + constant_tags=None, +): + """ + Parse tensor network to: + - identify the dimension of the optimisation space and the initial point of + the optimisation from the current values in the tensor network + - add variable tags to individual tensors so that optimisation vector + values can be efficiently reinserted into the tensor network + + There are two different modes: + - opt_in : `tags` (and optionally `shared_tags`) are specified and only + these tensor tags will be optimised over. In this case + `constant_tags` is ignored if it is passed + - opt_out : 'tags' is not specified. In this case all tensors will be + optimised over, unless they have one of `constant_tags` tags - return tn_ag, variables + Parameters + ---------- + tn : TensorNetwork + The initial tensor network to parse + to_constant : Callable + Function that fixes a tensor as constant + tags : str, or sequence of str, optional + Set of opt-in tags to optimise + shared_tags : str, or sequence of str, optional + Subset of opt-in tags to joint optimise i.e. all tensors with tag s in + shared_tags will correspond to the same optimisation variables + constant_tags : str, or sequence of str, optional + Set of opt-out tags if `tags` not passed + + Returns + ------- + tn_ag : TensorNetwork + Tensor network tagged for reinsertion of optimisation variable values + variables : list + List of variables extracted from tn + """ + tags = tags_to_oset(tags) + shared_tags = tags_to_oset(shared_tags) + constant_tags = tags_to_oset(constant_tags) + + if tags | shared_tags: + # opt_in + if not (tags & shared_tags) == shared_tags: + tags = tags | shared_tags + warnings.warn('TNOptimizer warning, some `shared_tags` are missing' + + ' from `tags`. Automatically adding these missing' + + ' `shared_tags` to `tags`.') + if constant_tags: + warnings.warn('TNOptimizer warning, if `tags` or `shared_tags` are' + + ' specified then `constant_tags` is ignored.') + return _parse_opt_in(tn, tags, shared_tags, to_constant, ) + + # opt-out + return _parse_opt_out(tn, constant_tags, to_constant, ) def constant_t(t, to_constant): @@ -409,9 +532,6 @@ def value_and_grad(self, arrays): return self._value_and_grad_seq(arrays) -variable_finder = re.compile(r'__VARIABLE(\d+)__') - - def inject_(arrays, tn): for t in tn: for tag in t.tags: @@ -728,6 +848,9 @@ class TNOptimizer: are assumed to be simple options that don't need conversion). tags : str, or sequence of str, optional If supplied, only optimize tensors with any of these tags. + shared_tags : str, or sequence of str, optional + If supplied, each tag in ``shared_tags`` corresponds to a group of + tensors to be optimized together constant_tags : str, or sequence of str, optional If supplied, skip optimizing tensors with any of these tags. loss_target : float, optional @@ -763,6 +886,7 @@ def __init__( loss_constants=None, loss_kwargs=None, tags=None, + shared_tags=None, constant_tags=None, loss_target=None, optimizer='L-BFGS-B', @@ -773,8 +897,9 @@ def __init__( **backend_opts ): self.progbar = progbar - self.tags = tags_to_oset(tags) - self.constant_tags = tags_to_oset(constant_tags) + self.tags = tags + self.shared_tags = shared_tags + self.constant_tags = constant_tags if autodiff_backend.upper() == 'AUTO': autodiff_backend = _DEFAULT_BACKEND @@ -812,7 +937,12 @@ def __init__( # work out which tensors to optimize and get the underlying data self.tn_opt, self.variables = parse_network_to_backend( - tn, self.tags, self.constant_tags, self.handler.to_constant) + tn, + tags=self.tags, + shared_tags=self.shared_tags, + constant_tags=self.constant_tags, + to_constant=self.handler.to_constant + ) # first we wrap the function to convert from array args to TN arg # (i.e. to autodiff library compatible form) From e521edb59af2f110da53c14d66efdb539ca3516e Mon Sep 17 00:00:00 2001 From: rezahhh Date: Mon, 5 Apr 2021 11:09:55 -0700 Subject: [PATCH 12/64] fix Ptensor --- quimb/tensor/optimize.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/quimb/tensor/optimize.py b/quimb/tensor/optimize.py index 828ceb5d8..199d103e4 100644 --- a/quimb/tensor/optimize.py +++ b/quimb/tensor/optimize.py @@ -1016,7 +1016,12 @@ def inject_res_vector_and_return_tn(self): inject_(arrays, self.tn_opt) tn = self.norm_fn(self.tn_opt.copy()) tn.drop_tags(t for t in tn.tags if variable_finder.match(t)) - tn.apply_to_arrays(to_numpy) +# tn.apply_to_arrays(to_numpy) + for t in tn: + if isinstance(t, PTensor): + t.params = to_numpy(t.params) + else: + t.modify(data=to_numpy(t.data)) return tn def optimize(self, n, tol=None, **options): From d4765f1f42749c047ab3ae399cb9bf6979f0cc2d Mon Sep 17 00:00:00 2001 From: rezahhh Date: Mon, 5 Apr 2021 11:14:49 -0700 Subject: [PATCH 13/64] +fix Ptensor --- quimb/tensor/optimize.py | 156 ++++++++++++++++++++++++++++----------- 1 file changed, 114 insertions(+), 42 deletions(-) diff --git a/quimb/tensor/optimize.py b/quimb/tensor/optimize.py index 199d103e4..b42e13e14 100644 --- a/quimb/tensor/optimize.py +++ b/quimb/tensor/optimize.py @@ -58,6 +58,11 @@ def equivalent_complex_type(x): class Vectorizer: """Object for mapping a sequence of mixed real/complex n-dimensional arrays to a single numpy vector and back and forth. + + Parameters + ---------- + array : sequence of array + The set of arrays to map into a single real vector. """ def __init__(self, arrays): @@ -72,6 +77,9 @@ def __init__(self, arrays): self.pack(arrays) def pack(self, arrays, name='vector'): + """Take ``arrays`` and pack their values into attribute `.{name}`, by + default `.vector`. + """ # scipy's optimization routines require real, double data if not hasattr(self, name): @@ -127,8 +135,8 @@ def unpack(self, vector=None): def _get_tensor_data(t): - """ simple function to extract tensor data """ - + """Simple function to extract tensor data. + """ if isinstance(t, PTensor): data = t.params else: @@ -141,8 +149,10 @@ def _get_tensor_data(t): return data -def _parse_opt_in(tn, tags, shared_tags, to_constant,): - """ """ +def _parse_opt_in(tn, tags, shared_tags, to_constant): + """Parse a tensor network where tensors are assumed to be constant unless + tagged. + """ tn_ag = tn.copy() variables = [] @@ -150,7 +160,7 @@ def _parse_opt_in(tn, tags, shared_tags, to_constant,): individual_tags = tags - shared_tags # handle tagged tensors that are not shared - for t in tn_ag.select(individual_tags, 'any'): + for t in tn_ag.select_tensors(individual_tags, 'any'): # append the raw data but mark the corresponding tensor # for reinsertion data = _get_tensor_data(t) @@ -163,14 +173,14 @@ def _parse_opt_in(tn, tags, shared_tags, to_constant,): var_name = _VARIABLE_TAG.format(len(variables)) test_data = None - for t in tn_ag.select(tag): + for t in tn_ag.select_tensors(tag): data = _get_tensor_data(t) # detect that this tensor is already variable tagged and skip # if it is if any(variable_finder.match(tag) for tag in t.tags): warnings.warn('TNOptimizer warning, tensor tagged with' - + ' multiple `tags` or `shared_tags`.') + ' multiple `tags` or `shared_tags`.') continue if test_data is None: @@ -180,10 +190,10 @@ def _parse_opt_in(tn, tags, shared_tags, to_constant,): else: # check that the shape of the variable's data matches the # data of this new tensor - if not test_data.shape == data.shape: - raise ValueError('TNOptimizer error, a `shared_tags`' - + ' tag covers tensors with different' - + ' numbers of params.') + if test_data.shape != data.shape: + raise ValueError('TNOptimizer error, a `shared_tags` tag ' + 'covers tensors with different numbers of' + ' params.') # mark the corresponding tensor for reinsertion t.add_tag(var_name) @@ -196,7 +206,9 @@ def _parse_opt_in(tn, tags, shared_tags, to_constant,): def _parse_opt_out(tn, constant_tags, to_constant,): - """ """ + """Parse a tensor network where tensors are assumed to be variables unless + tagged. + """ tn_ag = tn.copy() variables = [] @@ -224,38 +236,41 @@ def parse_network_to_backend( ): """ Parse tensor network to: - - identify the dimension of the optimisation space and the initial point of - the optimisation from the current values in the tensor network - - add variable tags to individual tensors so that optimisation vector - values can be efficiently reinserted into the tensor network + + - identify the dimension of the optimisation space and the initial + point of the optimisation from the current values in the tensor + network, + - add variable tags to individual tensors so that optimisation vector + values can be efficiently reinserted into the tensor network. There are two different modes: - - opt_in : `tags` (and optionally `shared_tags`) are specified and only - these tensor tags will be optimised over. In this case - `constant_tags` is ignored if it is passed - - opt_out : 'tags' is not specified. In this case all tensors will be - optimised over, unless they have one of `constant_tags` tags + + - 'opt in' : `tags` (and optionally `shared_tags`) are specified and + only these tensor tags will be optimised over. In this case + `constant_tags` is ignored if it is passed, + - 'opt out' : `tags` is not specified. In this case all tensors will be + optimised over, unless they have one of `constant_tags` tags. Parameters ---------- tn : TensorNetwork - The initial tensor network to parse + The initial tensor network to parse. to_constant : Callable - Function that fixes a tensor as constant + Function that fixes a tensor as constant. tags : str, or sequence of str, optional - Set of opt-in tags to optimise + Set of opt-in tags to optimise. shared_tags : str, or sequence of str, optional Subset of opt-in tags to joint optimise i.e. all tensors with tag s in - shared_tags will correspond to the same optimisation variables + shared_tags will correspond to the same optimisation variables. constant_tags : str, or sequence of str, optional - Set of opt-out tags if `tags` not passed + Set of opt-out tags if `tags` not passed. Returns ------- tn_ag : TensorNetwork - Tensor network tagged for reinsertion of optimisation variable values + Tensor network tagged for reinsertion of optimisation variable values. variables : list - List of variables extracted from tn + List of variables extracted from ``tn``. """ tags = tags_to_oset(tags) shared_tags = tags_to_oset(shared_tags) @@ -266,11 +281,12 @@ def parse_network_to_backend( if not (tags & shared_tags) == shared_tags: tags = tags | shared_tags warnings.warn('TNOptimizer warning, some `shared_tags` are missing' - + ' from `tags`. Automatically adding these missing' - + ' `shared_tags` to `tags`.') + ' from `tags`. Automatically adding these missing' + ' `shared_tags` to `tags`.') if constant_tags: warnings.warn('TNOptimizer warning, if `tags` or `shared_tags` are' - + ' specified then `constant_tags` is ignored.') + ' specified then `constant_tags` is ignored - ' + 'consider instead untagging those tensors.') return _parse_opt_in(tn, tags, shared_tags, to_constant, ) # opt-out @@ -850,7 +866,7 @@ class TNOptimizer: If supplied, only optimize tensors with any of these tags. shared_tags : str, or sequence of str, optional If supplied, each tag in ``shared_tags`` corresponds to a group of - tensors to be optimized together + tensors to be optimized together. constant_tags : str, or sequence of str, optional If supplied, skip optimizing tensors with any of these tags. loss_target : float, optional @@ -1001,30 +1017,67 @@ def nevals(self): @property def optimizer(self): + """The underlying optimizer that works with the vectorized functions. + """ return self._optimizer @optimizer.setter def optimizer(self, x): + if isinstance(x, str): + x = x.lower() self._optimizer = x if self.optimizer in _STOC_GRAD_METHODS: self._method = _STOC_GRAD_METHODS[self.optimizer]() else: self._method = self.optimizer - def inject_res_vector_and_return_tn(self): - arrays = self.vectorizer.unpack() + def get_tn_opt(self): + """Extract the optimized tensor network, this is a three part process: + + 1. inject the current optimized vector into the target tensor + network, + 2. run it through ``norm_fn``, + 3. drop any tags used to identify variables. + + Returns + ------- + tn_opt : TensorNetwork + """ + arrays = tuple(map(self.handler.to_constant, self.vectorizer.unpack())) inject_(arrays, self.tn_opt) tn = self.norm_fn(self.tn_opt.copy()) tn.drop_tags(t for t in tn.tags if variable_finder.match(t)) -# tn.apply_to_arrays(to_numpy) + for t in tn: - if isinstance(t, PTensor): - t.params = to_numpy(t.params) - else: - t.modify(data=to_numpy(t.data)) + if isinstance(t, PTensor): + t.params = to_numpy(t.params) + else: + t.modify(data=to_numpy(t.data)) + return tn def optimize(self, n, tol=None, **options): + """Run the optimizer for ``n`` function evaluations, using + :func:`scipy.optimize.minimize` as the driver for the vectorized + computation. + + Parameters + ---------- + n : int + Notionally the maximum number of iterations for the optimizer, note + that depending on the optimizer being used, this may correspond to + number of function evaluations rather than just iterations. + tol : None or float, optional + Tolerance for convergence, note that various more specific + tolerances can usually be supplied to ``options``, depending on + the optimizer being used. + options + Supplied to :func:`scipy.optimize.minimize`. + + Returns + ------- + tn_opt : TensorNetwork + """ from scipy.optimize import minimize try: @@ -1056,9 +1109,28 @@ def callback(_): finally: pbar.close() - return self.inject_res_vector_and_return_tn() + return self.get_tn_opt() def optimize_basinhopping(self, n, nhop, temperature=1.0, **options): + """Run the optimizer for using :func:`scipy.optimize.basinhopping` + as the driver for the vectorized computation. This performs ``nhop`` + local optimization each with ``n`` iterations. + + Parameters + ---------- + n : int + Number of iterations per local optimization. + nhop : int + Number of local optimizations to hop between. + temperature : float, optional + H + options + Supplied to the inner :func:`scipy.optimize.minimize` call. + + Returns + ------- + tn_opt : TensorNetwork + """ from scipy.optimize import basinhopping try: @@ -1084,7 +1156,7 @@ def inner_callback(_): niter=nhop, minimizer_kwargs=dict( jac=True, - method=self.optimizer, + method=self._method, bounds=self.bounds, callback=inner_callback, options=dict(maxiter=n, **options) @@ -1099,4 +1171,4 @@ def inner_callback(_): finally: pbar.close() - return self.inject_res_vector_and_return_tn() + return self.get_tn_opt() From 40f3314e6f56152219dac22d6d5b0635036e036c Mon Sep 17 00:00:00 2001 From: Reza Haghshenas Date: Thu, 24 Jun 2021 10:19:15 -0700 Subject: [PATCH 14/64] developed --- docs/_static/my-styles.css | 28 +- docs/conf.py | 11 +- docs/develop.rst | 2 +- .../examples/ex_tn_qaoa_energy_bayesopt.ipynb | 117154 +++++++-------- docs/generate.rst | 30 +- docs/index_tn.rst | 1 + docs/installation.rst | 76 +- docs/tensor-drawing.ipynb | 42175 ++++++ quimb/__init__.py | 3 +- quimb/core.py | 11 +- quimb/gen/operators.py | 56 +- quimb/linalg/base_linalg.py | 4 + quimb/linalg/mpi_launcher.py | 118 +- quimb/linalg/scipy_linalg.py | 40 +- quimb/linalg/slepc_linalg.py | 7 +- quimb/tensor/array_ops.py | 6 +- quimb/tensor/circuit.py | 226 +- quimb/tensor/decomp.py | 89 +- quimb/tensor/drawing.py | 177 +- quimb/tensor/optimize.py | 267 +- quimb/tensor/tensor_1d.py | 6 +- quimb/tensor/tensor_2d.py | 1427 +- quimb/tensor/tensor_core.py | 936 +- quimb/tensor/tensor_gen.py | 17 +- quimb/utils.py | 13 +- setup.cfg | 4 + setup.py | 2 +- tests/test_core.py | 6 +- tests/test_gen/test_operators.py | 9 +- tests/test_linalg/test_approx_spectral.py | 7 +- tests/test_linalg/test_mpi_linalg.py | 17 +- tests/test_tensor/test_circuit.py | 23 + tests/test_tensor/test_optimizers.py | 140 + tests/test_tensor/test_tensor_2d.py | 66 +- 34 files changed, 102214 insertions(+), 60940 deletions(-) create mode 100644 docs/tensor-drawing.ipynb diff --git a/docs/_static/my-styles.css b/docs/_static/my-styles.css index 6497fcd9e..76d7a4033 100644 --- a/docs/_static/my-styles.css +++ b/docs/_static/my-styles.css @@ -24,27 +24,17 @@ code, kbd, pre, samp { font-family: 'Roboto Mono', monospace; } -.navbar-nav > .active > .nav-link { - color: var(--highlightcolor) !important; +.content { + width: 60em; } -.toc-entry > .nav-link.active { - color: var(--highlightcolor) !important; +.bd-sidebar { + padding-top: 0em; + padding-right: 2em; + padding-left: 2em; } -.bd-sidebar .nav > .active:hover > a, .bd-sidebar .nav > .active > a { - color: var(--highlightcolor) !important; -} - - -.toc-entry a { - padding: 0.4rem 1.0rem; -} - -.container, .container-lg, .container-md, .container-sm, .container-xl { - max-width: 1600px; -} - -.col-md-3 { - max-width: 20%; +.bd-sidebar +div.navbar_extra_footer { + font-size: .6em; } diff --git a/docs/conf.py b/docs/conf.py index adf93d876..bc20879d5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -132,13 +132,20 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = "pydata_sphinx_theme" +html_theme = "sphinx_book_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. html_theme_options = { "github_url": "https://github.com/jcmgray/quimb", + "repository_url": "https://github.com/jcmgray/quimb", + "use_repository_button": True, + "use_issues_button": True, + "use_edit_page_button": True, + "path_to_docs": "docs", + "use_fullscreen_button": False, + "use_download_button": False, } # Add any paths that contain custom themes here, relative to this directory. @@ -146,7 +153,7 @@ # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -# html_title = None +html_title = '' # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None diff --git a/docs/develop.rst b/docs/develop.rst index 45e52cd97..4e4abfcd7 100644 --- a/docs/develop.rst +++ b/docs/develop.rst @@ -28,7 +28,7 @@ The tests can also be run with pre-spawned mpi workers using the command ``quimb Building the docs locally ========================= -Building the docs requires `sphinx `_, `pydata-sphinx-theme `_, and `nbsphinx `_. +Building the docs requires `sphinx `_, `sphinx-book-theme `_, and `nbsphinx `_. 1. To start from scratch, remove ``quimb/docs/_autosummary`` and ``quimb/docs/_build``. 2. Run ``make html`` (``make.bat html`` on windows) in the ``quimb/docs`` folder. diff --git a/docs/examples/ex_tn_qaoa_energy_bayesopt.ipynb b/docs/examples/ex_tn_qaoa_energy_bayesopt.ipynb index c7ed9369f..3fd0ecbd1 100644 --- a/docs/examples/ex_tn_qaoa_energy_bayesopt.ipynb +++ b/docs/examples/ex_tn_qaoa_energy_bayesopt.ipynb @@ -118,16 +118,16 @@ "\n", "\n", - "\n", + "\n", " \n", " \n", " \n", " \n", - " 2020-11-20T15:33:25.048011\n", + " 2021-05-18T21:46:11.522565\n", " image/svg+xml\n", " \n", " \n", - " Matplotlib v3.3.2, https://matplotlib.org/\n", + " Matplotlib v3.3.4, https://matplotlib.org/\n", " \n", " \n", " \n", @@ -138,10667 +138,10667 @@ " \n", " \n", " \n", - " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", " \n", " \n", " \n", - " \n", " \n", @@ -10815,15 +10815,15 @@ "C -5 1.326016 -4.473168 2.597899 -3.535534 3.535534 \n", "C -2.597899 4.473168 -1.326016 5 0 5 \n", "z\n", - "\" id=\"mc64e93778a\" style=\"stroke:#56b4e9;\"/>\n", + "\" id=\"m516c05decc\" style=\"stroke:#56b4e9;\"/>\n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", + "\" id=\"m6e330cfc8b\" style=\"stroke:#e69f00;\"/>\n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", + "\" id=\"me87399f633\" style=\"stroke:#009e73;\"/>\n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", + "\" id=\"mabb0fb950a\" style=\"stroke:#d55e00;\"/>\n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", "\n" @@ -11108,16 +11108,16 @@ "\n", "\n", - "\n", + "\n", " \n", " \n", " \n", " \n", - " 2020-11-20T15:33:26.470441\n", + " 2021-05-18T21:46:13.302454\n", " image/svg+xml\n", " \n", " \n", - " Matplotlib v3.3.2, https://matplotlib.org/\n", + " Matplotlib v3.3.4, https://matplotlib.org/\n", " \n", " \n", " \n", @@ -11128,8 +11128,8 @@ " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", - " \n", - " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", - " \n", - " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", + " \n", + " \n", + " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", + " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", + " \n", + " \n", - " \n", + " \n", - " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", + " \n", + " \n", + " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", - " \n", + " \n", + " \n", - " \n", - " \n", + " \n", - " \n", - " \n", + " \n", - " \n", + " \n", - " \n", + " \n", - " \n", + " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", + " \n", - " \n", - " \n", + " \n", - " \n", + " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", " \n", @@ -17420,15 +17898,15 @@ "C -5 1.326016 -4.473168 2.597899 -3.535534 3.535534 \n", "C -2.597899 4.473168 -1.326016 5 0 5 \n", "z\n", - "\" id=\"m2c638956ea\" style=\"stroke:#56b4e9;\"/>\n", + "\" id=\"m6e8a53ea5a\" style=\"stroke:#56b4e9;\"/>\n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", + "\" id=\"m6c0b4a1236\" style=\"stroke:#e69f00;\"/>\n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", + "\" id=\"m8a1390b2be\" style=\"stroke:#009e73;\"/>\n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", + "\" id=\"m3f779c8252\" style=\"stroke:#d55e00;\"/>\n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", "\n" @@ -17719,9 +18197,7 @@ "name": "stderr", "output_type": "stream", "text": [ - " 0%| | 0/81 [00:00\n", " \n", " \n", - " 2020-11-20T15:41:27.547490\n", + " 2021-05-18T21:52:53.239778\n", " image/svg+xml\n", " \n", " \n", - " Matplotlib v3.3.2, https://matplotlib.org/\n", + " Matplotlib v3.3.4, https://matplotlib.org/\n", " \n", " \n", " \n", @@ -17798,15 +18274,15 @@ " \n", " \n", + "\" id=\"m9ba0e7c5fd\" style=\"stroke:#000000;stroke-width:0.8;\"/>\n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", + "\" id=\"m1cf0f4b0e9\" style=\"stroke:#000000;stroke-width:0.8;\"/>\n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -18546,88 +19064,88 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", " \n", " \n", @@ -18653,20 +19171,20 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", + "\" id=\"m0c3f2c6995\" style=\"stroke:#000000;stroke-width:0.8;\"/>\n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -18833,88 +19351,88 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", " \n", " \n", @@ -18940,7 +19458,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -19005,7 +19523,7 @@ "\n", " ZZ = qu.pauli('Z') & qu.pauli('Z')\n", " ens = [\n", - " circ.local_expectation(weight * ZZ, edge)\n", + " circ.local_expectation(weight * ZZ, edge, optimize=opt)\n", " for edge, weight in terms.items()\n", " ]\n", " \n", @@ -19061,7 +19579,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "100%|██████████| 100/100 [1:23:40<00:00, 50.21s/it]\n" + "100%|██████████| 100/100 [1:05:33<00:00, 39.33s/it]\n" ] } ], @@ -19088,11 +19606,11 @@ " \n", " \n", " \n", - " 2020-11-20T17:05:08.513045\n", + " 2021-05-18T22:58:26.876909\n", " image/svg+xml\n", " \n", " \n", - " Matplotlib v3.3.2, https://matplotlib.org/\n", + " Matplotlib v3.3.4, https://matplotlib.org/\n", " \n", " \n", " \n", @@ -19122,7 +19640,7 @@ " \n", " \n", " \n", - " \n", " \n", @@ -19130,10 +19648,10 @@ " \n", " \n", + "\" id=\"m6bfc67ee94\" style=\"stroke:#000000;stroke-width:0.8;\"/>\n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -19168,13 +19686,13 @@ " \n", " \n", " \n", - " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -19213,13 +19731,13 @@ " \n", " \n", " \n", - " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -19251,13 +19769,13 @@ " \n", " \n", " \n", - " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -19302,13 +19820,13 @@ " \n", " \n", " \n", - " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -19362,13 +19880,13 @@ " \n", " \n", " \n", - " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -19706,23 +20224,23 @@ " \n", " \n", " \n", - " \n", " \n", " \n", " \n", " \n", + "\" id=\"m3a0455cd85\" style=\"stroke:#000000;stroke-width:0.8;\"/>\n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", - " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", - " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", " \n", - " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", " \n", - " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -20092,101 +20524,101 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + "\" id=\"md978d77a65\" style=\"stroke:#3b528b;\"/>\n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", @@ -20330,7 +20762,7 @@ "L 383.782812 22.318125 \n", "\" style=\"fill:none;stroke:#000000;stroke-linecap:square;stroke-linejoin:miter;stroke-width:0.8;\"/>\n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -20445,7 +20877,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -20490,11 +20922,11 @@ " \n", " \n", " \n", - " 2020-11-20T17:05:43.095085\n", + " 2021-05-18T22:58:51.245039\n", " image/svg+xml\n", " \n", " \n", - " Matplotlib v3.3.2, https://matplotlib.org/\n", + " Matplotlib v3.3.4, https://matplotlib.org/\n", " \n", " \n", " \n", @@ -20527,10 +20959,10 @@ " \n", " \n", + "\" id=\"mdad5690e53\" style=\"stroke:#000000;stroke-width:0.8;\"/>\n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -20606,7 +21038,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -20653,7 +21085,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -20700,7 +21132,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -20754,7 +21186,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -20824,25 +21256,25 @@ " \n", " \n", + "\" id=\"md493432ac1\" style=\"stroke:#000000;stroke-width:0.8;\"/>\n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", + "\" id=\"mdcadd3effe\" style=\"stroke:#000000;stroke-width:0.8;\"/>\n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", " \n", " \n", " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", @@ -21199,52 +21571,52 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", " \n", - " \n", - " \n", + " \n", " \n", " \n", @@ -21285,179 +21657,567 @@ "\" style=\"fill:#ffffff;\"/>\n", " \n", " \n", - " \n", + "\" style=\"fill:#3a51a2;\"/>\n", " \n", " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", - " \n", + "\" style=\"fill:#e2f4f4;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", - " \n", + "\" style=\"fill:#fffebe;\"/>\n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + "\" style=\"fill:#e14430;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#bb1526;\"/>\n", " \n", - " \n", + " \n", " \n", " \n", + "\" id=\"m45370e70c6\"/>\n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", + "\" id=\"mda5e8f9807\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", + "\" id=\"mfbd505431d\" style=\"stroke:#000000;stroke-width:0.8;\"/>\n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -23054,13 +23227,13 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -23069,13 +23242,13 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -23084,13 +23257,13 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -23099,13 +23272,13 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -23114,7 +23287,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -23154,12 +23327,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -23169,12 +23342,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -23184,12 +23357,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -23199,12 +23372,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -23214,12 +23387,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -23228,7 +23401,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -23237,64 +23410,45 @@ " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -23318,52 +23472,52 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", " \n", - " \n", - " \n", + " \n", " \n", " \n", @@ -23402,1271 +23556,979 @@ "z\n", "\" style=\"fill:#ffffff;\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\" style=\"fill:#5588be;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", - " \n", + " \n", + " \n", + " \n", + "\" style=\"fill:#fffebe;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", + "\" style=\"fill:#fee294;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", + " \n", " \n", - " \n", - " \n", + " \n", " \n", - " \n", - " \n", + " \n", " \n", - " \n", - " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -25142,13 +24960,13 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -25157,13 +24975,13 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -25172,13 +24990,13 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -25187,13 +25005,13 @@ " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -25202,7 +25020,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -25240,537 +25058,243 @@ "z\n", "\" style=\"fill:#ffffff;\"/>\n", " \n", - " \n", - " \n", - " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + "\" style=\"fill:#3a51a2;\"/>\n", " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + "\" style=\"fill:#5588be;\"/>\n", + " \n", " \n", - " \n", - " \n", + " \n", - " \n", + " \n", + "\" style=\"fill:#83b9d8;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#b4deec;\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\" style=\"fill:#fee294;\"/>\n", " \n", " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\" style=\"fill:#e14430;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#bb1526;\"/>\n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -26818,12 +26620,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -26833,12 +26635,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -26848,12 +26650,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -26863,12 +26665,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -26878,12 +26680,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -26892,7 +26694,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -26901,64 +26703,45 @@ " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -26982,52 +26765,52 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", " \n", - " \n", - " \n", + " \n", " \n", " \n", @@ -27065,160 +26848,96 @@ "z\n", "\" style=\"fill:#ffffff;\"/>\n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", + "\" style=\"fill:#3c59a6;\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#9fd0e4;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", - " \n", + " \n", + " \n", + "\" style=\"fill:#daf0f6;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + "L 67.631203 428.725216 \n", + "L 67.561327 428.818466 \n", + "L 65.480329 431.780918 \n", + "L 64.505624 433.331223 \n", + "L 63.611255 434.836621 \n", + "L 61.997582 437.892324 \n", + "L 61.449921 439.069168 \n", + "L 60.615588 440.948027 \n", + "L 59.438012 444.00373 \n", + "L 58.439469 447.059433 \n", + "L 58.394218 447.223346 \n", + "L 57.623541 450.115136 \n", + "L 56.963031 453.170839 \n", + "L 56.448651 456.226542 \n", + "L 56.072713 459.282245 \n", + "L 55.828246 462.337948 \n", + "L 55.708977 465.393651 \n", + "L 55.709318 468.449353 \n", + "L 55.824353 471.505056 \n", + "L 56.049836 474.560759 \n", + "L 56.382182 477.616462 \n", + "L 56.818468 480.672165 \n", + "L 57.356435 483.727868 \n", + "L 57.994493 486.783571 \n", + "L 58.394218 488.44093 \n", + "L 58.73412 489.839274 \n", + "L 59.575323 492.894977 \n", + "L 60.515238 495.95068 \n", + "L 61.449921 498.699014 \n", + "L 61.555755 499.006383 \n", + "L 62.705532 502.062086 \n", + "L 63.958445 505.117788 \n", + "L 64.505624 506.353513 \n", + "L 65.32578 508.173491 \n", + "L 66.808507 511.229194 \n", + "L 67.561327 512.677207 \n", + "L 68.416199 514.284897 \n", + "L 70.153304 517.3406 \n", + "L 70.61703 518.108354 \n", + "L 72.038176 520.396303 \n", + "L 73.672733 522.868424 \n", + "L 74.071482 523.452006 \n", + "L 76.279784 526.507709 \n", + "L 76.728436 527.096304 \n", + "L 78.684236 529.563412 \n", + "L 79.784139 530.879765 \n", + "L 81.304288 532.619115 \n", + "L 82.839842 534.291743 \n", + "L 84.176115 535.674818 \n", + "L 85.895545 537.374921 \n", + "L 87.348008 538.730521 \n", + "L 88.951248 540.165033 \n", + "L 90.884623 541.786223 \n", + "L 92.00695 542.691725 \n", + "L 94.873113 544.841926 \n", + "z\n", + "\" style=\"fill:#feffc0;\"/>\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -28732,13 +28250,13 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -28747,13 +28265,13 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -28762,13 +28280,13 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -28777,13 +28295,13 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -28792,7 +28310,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -28830,521 +28348,921 @@ "z\n", "\" style=\"fill:#ffffff;\"/>\n", " \n", - " \n", - " \n", - " \n", - " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", - " \n", - " \n", " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", - " \n", + " \n", + " \n", + " \n", - " \n", + " \n", + "\" style=\"fill:#fdb769;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", " \n", - " \n", - " \n", + " \n", " \n", - " \n", - " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -30816,460 +29819,600 @@ "z\n", "\" style=\"fill:#ffffff;\"/>\n", " \n", - " \n", - " \n", - " \n", " \n", - " \n", + "L 433.634537 537.241091 \n", + "L 433.216469 538.730521 \n", + "L 432.286735 541.786223 \n", + "L 431.269849 544.841926 \n", + "L 430.578834 546.762744 \n", + "L 430.165082 547.897629 \n", + "z\n", + "\" style=\"fill:#3a51a2;\"/>\n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", - " \n", + "\" style=\"fill:#e2f4f4;\"/>\n", " \n", " \n", - " \n", + " \n", + " \n", + " \n", + " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\" style=\"fill:#fdb769;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + "\" style=\"fill:#f67f4b;\"/>\n", " \n", " \n", - " \n", + "\" style=\"fill:#e14430;\"/>\n", " \n", " \n", - " \n", + "\" style=\"fill:#bb1526;\"/>\n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -32657,12 +31380,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -32672,12 +31395,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -32687,12 +31410,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -32702,12 +31425,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -32717,12 +31440,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -32731,7 +31454,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -32740,64 +31463,45 @@ " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -32821,52 +31525,52 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", " \n", - " \n", - " \n", + " \n", " \n", " \n", @@ -32904,261 +31608,87 @@ "\" style=\"fill:#ffffff;\"/>\n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", + "\" style=\"fill:#3a54a4;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\" style=\"fill:#c5e6f0;\"/>\n", " \n", " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + "\" style=\"fill:#fff0a8;\"/>\n", + " \n", " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", + "\" style=\"fill:#fdc778;\"/>\n", " \n", " \n", - " \n", + "\" style=\"fill:#f88c51;\"/>\n", " \n", " \n", - " \n", + "\" style=\"fill:#e54e35;\"/>\n", " \n", " \n", - " \n", + "\" style=\"fill:#bd1726;\"/>\n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -34852,13 +33173,13 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -34868,13 +33189,13 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -34883,13 +33204,13 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -34898,13 +33219,13 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -34913,7 +33234,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -34974,72 +33295,20 @@ "z\n", "\" style=\"fill:#ffffff;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#3a54a4;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#90c3dd;\"/>\n", " \n", - " \n", - " \n", - " \n", + " \n", - " \n", + " \n", + "\" style=\"fill:#c5e6f0;\"/>\n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", + " \n", + " \n", + " \n", - " \n", - " \n", + "\" style=\"fill:#fdc778;\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + "\" style=\"fill:#e54e35;\"/>\n", " \n", " \n", - " \n", + "\" style=\"fill:#bd1726;\"/>\n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -36929,86 +34767,97 @@ "z\n", "\" style=\"fill:#ffffff;\"/>\n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", - " \n", + " \n", + "L 416.615465 559.814871 \n", + "L 418.356022 561.021428 \n", + "L 420.874428 562.870574 \n", + "L 421.411725 563.292464 \n", + "L 424.467428 565.821143 \n", + "L 424.588988 565.926277 \n", + "L 427.523131 568.723073 \n", + "L 427.782232 568.981979 \n", + "L 430.478677 572.037682 \n", + "L 430.578834 572.170029 \n", + "L 432.721818 575.093385 \n", + "z\n", + "\" style=\"fill:#6399c7;\"/>\n", " \n", - " \n", - " \n", - " \n", + " \n", + "L 400.82342 559.814871 \n", + "L 403.077507 561.155345 \n", + "L 405.841551 562.870574 \n", + "L 406.13321 563.061463 \n", + "L 409.188913 565.128827 \n", + "L 410.314661 565.926277 \n", + "L 412.244616 567.400305 \n", + "L 414.213782 568.981979 \n", + "L 415.300319 569.948383 \n", + "L 417.534635 572.037682 \n", + "L 418.356022 572.917616 \n", + "L 420.29441 575.093385 \n", + "L 421.411725 576.593569 \n", + "L 422.525158 578.149088 \n", + "L 424.260973 581.204791 \n", + "L 424.467428 581.688141 \n", + "L 425.541268 584.260494 \n", + "L 426.395254 587.316197 \n", + "L 426.853381 590.3719 \n", + "L 426.942827 593.427603 \n", + "L 426.687836 596.483306 \n", + "L 426.110883 599.539009 \n", + "L 425.234006 602.594712 \n", + "L 424.467428 604.640086 \n", + "L 424.083367 605.650414 \n", + "L 422.693102 608.706117 \n", + "L 421.411725 611.160776 \n", + "L 421.091118 611.76182 \n", + "L 419.324585 614.817523 \n", + "L 418.356022 616.399076 \n", + "L 417.432553 617.873226 \n", + "L 415.461305 620.928929 \n", + "L 415.300319 621.178999 \n", + "L 413.458137 623.984632 \n", + "L 412.244616 625.842437 \n", + "L 411.448182 627.040335 \n", + "L 409.450293 630.096038 \n", + "L 409.188913 630.504773 \n", + "L 407.467798 633.151741 \n", + "L 406.13321 635.200286 \n", + "L 405.464835 636.207444 \n", + "L 403.397343 639.263147 \n", + "L 403.077507 639.718595 \n", + "L 401.197157 642.318849 \n", + "L 400.021805 643.822787 \n", + "L 398.759976 645.374552 \n", + "L 396.966102 647.371294 \n", + "L 395.962299 648.430255 \n", + "L 393.910399 650.366156 \n", + "L 392.635743 651.485958 \n", + "L 390.854696 652.883196 \n", + "L 388.534748 654.541661 \n", + "L 387.798993 655.014745 \n", + "L 384.74329 656.808295 \n", + "L 383.231453 657.597364 \n", + "L 381.687587 658.335186 \n", + "L 378.631884 659.625734 \n", + "L 375.773625 660.653067 \n", + "L 375.576181 660.719618 \n", + "L 372.520478 661.623326 \n", + "L 369.464775 662.364453 \n", + "L 366.409072 662.952676 \n", + "L 363.35337 663.396736 \n", + "L 360.297667 663.704555 \n", + "L 360.227486 663.70877 \n", + "L 357.241964 663.886848 \n", + "L 354.186261 663.944733 \n", + "L 351.130558 663.882046 \n", + "L 348.196796 663.70877 \n", + "L 348.074855 663.70187 \n", + "L 345.019152 663.41597 \n", + "L 341.963449 663.020972 \n", + "L 338.907746 662.516789 \n", + "L 335.852043 661.901606 \n", + "L 332.79634 661.171711 \n", + "L 330.922139 660.653067 \n", + "L 329.740637 660.335334 \n", + "L 326.684935 659.40079 \n", + "L 323.629232 658.337521 \n", + "L 321.731866 657.597364 \n", + "L 320.573529 657.151229 \n", + "L 317.517826 655.846048 \n", + "L 314.796258 654.541661 \n", + "L 314.462123 654.380928 \n", + "L 314.462123 654.541661 \n", + "L 314.462123 657.597364 \n", + "L 314.462123 660.653067 \n", + "L 314.462123 662.519854 \n", + "L 317.26088 663.70877 \n", + "L 317.517826 663.820138 \n", + "L 320.573529 665.047196 \n", + "L 323.629232 666.142422 \n", + "L 325.557142 666.764473 \n", + "L 326.684935 667.141493 \n", + "L 329.740637 668.062468 \n", + "L 332.79634 668.87355 \n", + "L 335.852043 669.582142 \n", + "L 337.033074 669.820176 \n", + "z\n", + "\" style=\"fill:#9fd0e4;\"/>\n", " \n", - " \n", - " \n", + " \n", + "L 385.433417 559.814871 \n", + "L 387.798993 561.120211 \n", + "L 390.854696 562.842208 \n", + "L 390.904359 562.870574 \n", + "L 393.910399 564.687972 \n", + "L 395.914018 565.926277 \n", + "L 396.966102 566.622971 \n", + "L 400.021805 568.693743 \n", + "L 400.435616 568.981979 \n", + "L 403.077507 570.991641 \n", + "L 404.401225 572.037682 \n", + "L 406.13321 573.574616 \n", + "L 407.770855 575.093385 \n", + "L 409.188913 576.62799 \n", + "L 410.5294 578.149088 \n", + "L 412.244616 580.543337 \n", + "L 412.697044 581.204791 \n", + "L 414.293475 584.260494 \n", + "L 415.300319 587.096682 \n", + "L 415.375468 587.316197 \n", + "L 415.958452 590.3719 \n", + "L 416.09147 593.427603 \n", + "L 415.804494 596.483306 \n", + "L 415.300319 598.767275 \n", + "L 415.12595 599.539009 \n", + "L 414.080737 602.594712 \n", + "L 412.719619 605.650414 \n", + "L 412.244616 606.548599 \n", + "L 411.071256 608.706117 \n", + "L 409.199769 611.76182 \n", + "L 409.188913 611.778366 \n", + "L 407.147378 614.817523 \n", + "L 406.13321 616.26568 \n", + "L 404.986589 617.873226 \n", + "L 403.077507 620.509866 \n", + "L 402.770269 620.928929 \n", + "L 400.5411 623.984632 \n", + "L 400.021805 624.705268 \n", + "L 398.324366 627.040335 \n", + "L 396.966102 628.925744 \n", + "L 396.115564 630.096038 \n", + "L 393.910399 633.116414 \n", + "L 393.88427 633.151741 \n", + "L 391.580091 636.207444 \n", + "L 390.854696 637.123517 \n", + "L 389.106177 639.263147 \n", + "L 387.798993 640.742737 \n", + "L 386.337869 642.318849 \n", + "L 384.74329 643.87555 \n", + "L 383.096438 645.374552 \n", + "L 381.687587 646.522435 \n", + "L 379.106167 648.430255 \n", + "L 378.631884 648.744055 \n", + "L 375.576181 650.572069 \n", + "L 373.814173 651.485958 \n", + "L 372.520478 652.094632 \n", + "L 369.464775 653.334566 \n", + "L 366.409072 654.346449 \n", + "L 365.689385 654.541661 \n", + "L 363.35337 655.134151 \n", + "L 360.297667 655.729842 \n", + "L 357.241964 656.150233 \n", + "L 354.186261 656.406398 \n", + "L 351.130558 656.508167 \n", + "L 348.074855 656.463867 \n", + "L 345.019152 656.28007 \n", + "L 341.963449 655.961349 \n", + "L 338.907746 655.510052 \n", + "L 335.852043 654.926104 \n", + "L 334.216217 654.541661 \n", + "L 332.79634 654.213915 \n", + "L 329.740637 653.375373 \n", + "L 326.684935 652.395642 \n", + "L 324.220743 651.485958 \n", + "L 323.629232 651.267649 \n", + "L 320.573529 649.995048 \n", + "L 317.517826 648.545031 \n", + "L 317.298237 648.430255 \n", + "L 314.462123 646.912637 \n", + "L 314.462123 648.430255 \n", + "L 314.462123 651.485958 \n", + "L 314.462123 654.380928 \n", + "L 314.796258 654.541661 \n", + "L 317.517826 655.846048 \n", + "L 320.573529 657.151229 \n", + "L 321.731866 657.597364 \n", + "L 323.629232 658.337521 \n", + "L 326.684935 659.40079 \n", + "L 329.740637 660.335334 \n", + "L 330.922139 660.653067 \n", + "L 332.79634 661.171711 \n", + "L 335.852043 661.901606 \n", + "L 338.907746 662.516789 \n", + "L 341.963449 663.020972 \n", + "L 345.019152 663.41597 \n", + "L 348.074855 663.70187 \n", + "L 348.196796 663.70877 \n", + "z\n", + "\" style=\"fill:#daf0f6;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#fffebe;\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", - " \n", + "\" style=\"fill:#fba05b;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + "L 314.462123 620.937119 \n", + "L 316.721051 623.984632 \n", + "L 317.517826 624.863872 \n", + "L 319.775979 627.040335 \n", + "L 320.573529 627.698678 \n", + "L 323.629232 629.848645 \n", + "L 324.053882 630.096038 \n", + "L 326.684935 631.473672 \n", + "L 329.740637 632.752574 \n", + "L 331.00275 633.151741 \n", + "z\n", + "M 330.387447 623.984632 \n", + "L 329.740637 623.698664 \n", + "L 326.684935 621.779061 \n", + "L 325.639994 620.928929 \n", + "L 323.629232 618.847485 \n", + "L 322.860635 617.873226 \n", + "L 321.095343 614.817523 \n", + "L 320.573529 613.475451 \n", + "L 320.000051 611.76182 \n", + "L 319.375233 608.706117 \n", + "L 319.026867 605.650414 \n", + "L 318.826979 602.594712 \n", + "L 318.649261 599.539009 \n", + "L 318.387582 596.483306 \n", + "L 317.981169 593.427603 \n", + "L 317.517826 590.773744 \n", + "L 317.455196 590.3719 \n", + "L 316.993288 587.316197 \n", + "L 316.706536 584.260494 \n", + "L 316.834899 581.204791 \n", + "L 317.517826 578.60874 \n", + "L 317.673783 578.149088 \n", + "L 319.8521 575.093385 \n", + "L 320.573529 574.468342 \n", + "L 323.629232 572.561333 \n", + "L 324.951168 572.037682 \n", + "L 326.684935 571.53522 \n", + "L 329.740637 571.103979 \n", + "L 332.79634 571.094319 \n", + "L 335.852043 571.474828 \n", + "L 338.177133 572.037682 \n", + "L 338.907746 572.26982 \n", + "L 341.963449 573.654166 \n", + "L 344.483655 575.093385 \n", + "L 345.019152 575.504878 \n", + "L 347.995999 578.149088 \n", + "L 348.074855 578.243572 \n", + "L 350.335157 581.204791 \n", + "L 351.130558 582.558756 \n", + "L 352.077512 584.260494 \n", + "L 353.461178 587.316197 \n", + "L 354.186261 589.152024 \n", + "L 354.645101 590.3719 \n", + "L 355.674561 593.427603 \n", + "L 356.576862 596.483306 \n", + "L 357.241964 599.300137 \n", + "L 357.293877 599.539009 \n", + "L 357.732739 602.594712 \n", + "L 357.857797 605.650414 \n", + "L 357.618405 608.706117 \n", + "L 357.241964 610.461784 \n", + "L 356.954174 611.76182 \n", + "L 355.778397 614.817523 \n", + "L 354.186261 617.633657 \n", + "L 354.034968 617.873226 \n", + "L 351.364101 620.928929 \n", + "L 351.130558 621.135213 \n", + "L 348.074855 623.315907 \n", + "L 346.786118 623.984632 \n", + "L 345.019152 624.719552 \n", + "L 341.963449 625.491157 \n", + "L 338.907746 625.76293 \n", + "L 335.852043 625.555053 \n", + "L 332.79634 624.881348 \n", + "z\n", + "\" style=\"fill:#ea5739;\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -38866,53 +36285,12 @@ "z\n", "\" style=\"fill:#ffffff;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", + "\" style=\"fill:#d4edf4;\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#fff2ac;\"/>\n", + " \n", " \n", - " \n", - " \n", + " \n", - " \n", + " \n", + "\" style=\"fill:#fed283;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#fca85e;\"/>\n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", + "\" style=\"fill:#dd3d2d;\"/>\n", " \n", " \n", - " \n", + "\" style=\"fill:#b91326;\"/>\n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -40641,12 +38081,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -40657,12 +38097,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -40673,12 +38113,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -40688,12 +38128,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -40703,12 +38143,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -40717,7 +38157,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -40726,64 +38166,45 @@ " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -40807,52 +38228,52 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", " \n", - " \n", - " \n", + " \n", " \n", " \n", @@ -40888,239 +38309,601 @@ "z\n", "\" style=\"fill:#ffffff;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\" style=\"fill:#e2f4f4;\"/>\n", " \n", " \n", - " \n", - " \n", + "\" style=\"fill:#fffebe;\"/>\n", " \n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#fee294;\"/>\n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", " \n", - " \n", - " \n", + " \n", " \n", - " \n", - " \n", + " \n", " \n", - " \n", - " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -42880,13 +39986,13 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -42896,13 +40002,13 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -42911,13 +40017,13 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -42926,13 +40032,13 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -42941,7 +40047,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -42979,404 +40085,326 @@ "z\n", "\" style=\"fill:#ffffff;\"/>\n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", - " \n", + "\" style=\"fill:#394fa1;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", + " \n", + " \n", + " \n", - " \n", + " \n", + " \n", + " \n", - " \n", + " \n", + "\" style=\"fill:#a6d5e7;\"/>\n", " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + "\" style=\"fill:#f2fad6;\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\" style=\"fill:#fed283;\"/>\n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", + "\" style=\"fill:#f57245;\"/>\n", " \n", " \n", - " \n", + "\" style=\"fill:#dd3d2d;\"/>\n", " \n", " \n", - " \n", + "\" style=\"fill:#b91326;\"/>\n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -44690,129 +41748,64 @@ "z\n", "\" style=\"fill:#ffffff;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + "\" style=\"fill:#9fd0e4;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + "\" style=\"fill:#daf0f6;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\" style=\"fill:#feda8a;\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + "L 314.462123 751.885241 \n", + "L 314.541308 752.018584 \n", + "L 316.704787 755.074287 \n", + "L 317.517826 756.058741 \n", + "L 319.586394 758.12999 \n", + "L 320.573529 759.007619 \n", + "L 323.629232 761.162752 \n", + "L 323.672386 761.185693 \n", + "L 326.684935 762.66492 \n", + "L 329.740637 763.701932 \n", + "L 332.455345 764.241396 \n", + "z\n", + "M 327.427913 748.962881 \n", + "L 326.684935 748.495781 \n", + "L 323.963906 745.907178 \n", + "L 323.629232 745.505284 \n", + "L 321.992065 742.851475 \n", + "L 320.651387 739.795773 \n", + "L 320.573529 739.512064 \n", + "L 319.963251 736.74007 \n", + "L 319.69589 733.684367 \n", + "L 319.817902 730.628664 \n", + "L 320.314217 727.572961 \n", + "L 320.573529 726.660178 \n", + "L 321.280465 724.517258 \n", + "L 322.699626 721.461555 \n", + "L 323.629232 719.905432 \n", + "L 324.708319 718.405852 \n", + "L 326.684935 716.145476 \n", + "L 327.565611 715.350149 \n", + "L 329.740637 713.673726 \n", + "L 332.173257 712.294446 \n", + "L 332.79634 711.98402 \n", + "L 335.852043 711.026442 \n", + "L 338.907746 710.623114 \n", + "L 341.963449 710.789288 \n", + "L 345.019152 711.544521 \n", + "L 346.730052 712.294446 \n", + "L 348.074855 713.034417 \n", + "L 351.011593 715.350149 \n", + "L 351.130558 715.476055 \n", + "L 353.337107 718.405852 \n", + "L 354.186261 720.07792 \n", + "L 354.792954 721.461555 \n", + "L 355.576878 724.517258 \n", + "L 355.850639 727.572961 \n", + "L 355.662735 730.628664 \n", + "L 355.046274 733.684367 \n", + "L 354.186261 736.249475 \n", + "L 354.005751 736.74007 \n", + "L 352.456537 739.795773 \n", + "L 351.130558 741.848327 \n", + "L 350.390113 742.851475 \n", + "L 348.074855 745.444822 \n", + "L 347.577138 745.907178 \n", + "L 345.019152 747.941856 \n", + "L 343.33367 748.962881 \n", + "L 341.963449 749.691462 \n", + "L 338.907746 750.762916 \n", + "L 335.852043 751.233933 \n", + "L 332.79634 751.063436 \n", + "L 329.740637 750.196014 \n", + "z\n", + "\" style=\"fill:#ea5739;\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -46269,208 +43130,788 @@ "z\n", "\" style=\"fill:#ffffff;\"/>\n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#3a51a2;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", + " \n", - " \n", + "\" style=\"fill:#5588be;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", + " \n", + " \n", + " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + "\" style=\"fill:#fee294;\"/>\n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + "\" style=\"fill:#fdb769;\"/>\n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", + "\" style=\"fill:#e14430;\"/>\n", " \n", " \n", - " \n", + "\" style=\"fill:#bb1526;\"/>\n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -48226,35 +44736,36 @@ "z\n", "\" style=\"fill:#ffffff;\"/>\n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + "L 646.793908 690.904526 \n", + "L 643.866897 693.362438 \n", + "L 643.218076 693.960229 \n", + "L 640.811194 696.254905 \n", + "L 640.073494 697.015932 \n", + "L 637.755492 699.502811 \n", + "L 637.258099 700.071635 \n", + "L 634.702025 703.127338 \n", + "L 634.699789 703.130139 \n", + "L 632.374247 706.18304 \n", + "L 631.644086 707.192927 \n", + "L 630.204875 709.238743 \n", + "L 628.588383 711.667846 \n", + "L 628.174853 712.294446 \n", + "L 626.274208 715.350149 \n", + "L 625.53268 716.620097 \n", + "L 624.482223 718.405852 \n", + "L 622.806222 721.461555 \n", + "L 622.476977 722.112456 \n", + "L 621.242639 724.517258 \n", + "L 619.812263 727.572961 \n", + "L 619.421274 728.509419 \n", + "L 618.52693 730.628664 \n", + "L 617.410342 733.684367 \n", + "L 616.474279 736.74007 \n", + "L 616.365571 737.199237 \n", + "L 615.75582 739.795773 \n", + "L 615.265655 742.851475 \n", + "L 615.021461 745.907178 \n", + "L 615.047413 748.962881 \n", + "L 615.371484 752.018584 \n", + "L 616.025042 755.074287 \n", + "L 616.365571 756.12376 \n", + "L 617.108765 758.12999 \n", + "L 618.684898 761.185693 \n", + "L 619.421274 762.299898 \n", + "L 620.953869 764.241396 \n", + "L 622.476977 765.820682 \n", + "L 624.238785 767.297099 \n", + "L 625.53268 768.233768 \n", + "L 628.588383 769.973851 \n", + "L 629.457151 770.352802 \n", + "z\n", + "\" style=\"fill:#fba05b;\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -50010,12 +46318,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -50026,12 +46334,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -50042,12 +46350,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -50057,12 +46365,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -50072,12 +46380,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -50086,7 +46394,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -50095,64 +46403,45 @@ " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -50176,52 +46465,52 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", " \n", - " \n", - " \n", + " \n", " \n", " \n", @@ -50256,358 +46545,277 @@ "z\n", "\" style=\"fill:#ffffff;\"/>\n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", - " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", + " \n", - " \n", + "\" style=\"fill:#5c90c2;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", + "\" style=\"fill:#c5e6f0;\"/>\n", + " \n", " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", + "\" style=\"fill:#f0f9db;\"/>\n", + " \n", " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", + "\" style=\"fill:#fff0a8;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#fdc778;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#f88c51;\"/>\n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -52091,13 +48266,13 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -52107,13 +48282,13 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -52122,13 +48297,13 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -52137,13 +48312,13 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -52152,7 +48327,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -52190,1089 +48365,725 @@ "z\n", "\" style=\"fill:#ffffff;\"/>\n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", - " \n", + "\" style=\"fill:#3a51a2;\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + "\" style=\"fill:#5588be;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + "\" style=\"fill:#83b9d8;\"/>\n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\" style=\"fill:#fdb769;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#f67f4b;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#e14430;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#bb1526;\"/>\n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -54009,8 +50033,10 @@ "z\n", "\" style=\"fill:#ffffff;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#3a51a2;\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\" style=\"fill:#83b9d8;\"/>\n", + " \n", " \n", - " \n", - " \n", + " \n", - " \n", + " \n", + "\" style=\"fill:#b4deec;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#e2f4f4;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#fffebe;\"/>\n", " \n", - " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#fee294;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", + "\" style=\"fill:#e14430;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#bb1526;\"/>\n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -55478,164 +51627,225 @@ "z\n", "\" style=\"fill:#ffffff;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", + "\" style=\"fill:#3a51a2;\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", + "\" style=\"fill:#e2f4f4;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\" style=\"fill:#fee294;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#fdb769;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + "\" style=\"fill:#f67f4b;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#e14430;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#bb1526;\"/>\n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -57069,77 +53230,131 @@ "z\n", "\" style=\"fill:#ffffff;\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\" style=\"fill:#a6d5e7;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + "\" style=\"fill:#d1ecf4;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", + " \n", + "\" style=\"fill:#f2fad6;\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\" style=\"fill:#fed283;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#fca85e;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#f57245;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#dd3d2d;\"/>\n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -58732,780 +55070,811 @@ "z\n", "\" style=\"fill:#ffffff;\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + "\" style=\"fill:#5183bb;\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#78b0d3;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#a6d5e7;\"/>\n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\" style=\"fill:#fca85e;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#f57245;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#dd3d2d;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#b91326;\"/>\n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -60340,12 +56936,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -60356,12 +56952,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -60372,12 +56968,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -60387,12 +56983,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -60402,12 +56998,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -60416,7 +57012,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -60425,64 +57021,45 @@ " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -60506,52 +57083,52 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", " \n", - " \n", - " \n", + " \n", " \n", " \n", @@ -60581,292 +57158,449 @@ " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + "\" style=\"fill:#ffffff;\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", + " \n", - " \n", + " \n", + "\" style=\"fill:#5c90c2;\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", - " \n", + " \n", - " \n", - " \n", - " \n", + "\" style=\"fill:#c5e6f0;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + "\" style=\"fill:#f0f9db;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#fff0a8;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#fdc778;\"/>\n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -62301,12 +58925,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -62316,12 +58940,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -62331,12 +58955,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -62346,12 +58970,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -62360,7 +58984,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -62369,13 +58993,13 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -62385,13 +59009,13 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -62401,13 +59025,13 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -62416,13 +59040,13 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -62431,13 +59055,13 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -62446,7 +59070,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -62484,167 +59108,334 @@ "z\n", "\" style=\"fill:#ffffff;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", " \n", - " \n", - " \n", + " \n", - " \n", " \n", - " \n", - " \n", + " \n", - " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", - " \n", + " \n", + "\" style=\"fill:#fffebe;\"/>\n", " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", " \n", - " \n", - " \n", + " \n", " \n", - " \n", - " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -64110,12 +60765,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -64125,12 +60780,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -64140,12 +60795,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -64155,12 +60810,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -64169,7 +60824,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -64178,38 +60833,38 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -64244,8 +60899,8 @@ "z\n", "\" style=\"fill:#ffffff;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#3a54a4;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#5c90c2;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", + "\" style=\"fill:#8ec2dc;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#c5e6f0;\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", - " \n", + " \n", + "\" style=\"fill:#f0f9db;\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", + "\" style=\"fill:#e54e35;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#bd1726;\"/>\n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -65767,12 +62372,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -65782,12 +62387,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -65797,12 +62402,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -65812,12 +62417,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -65826,7 +62431,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -65835,38 +62440,38 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -65901,147 +62506,109 @@ "z\n", "\" style=\"fill:#ffffff;\"/>\n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", - " \n", + " \n", + "\" style=\"fill:#3c59a6;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", - " \n", + " \n", + " \n", + "\" style=\"fill:#6399c7;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\" style=\"fill:#daf0f6;\"/>\n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", - " \n", + "L 445.551778 1011.142192 \n", + "L 445.551778 1014.197895 \n", + "L 445.551778 1016.429863 \n", + "L 445.70373 1017.253597 \n", + "L 446.563685 1020.3093 \n", + "L 447.73196 1023.365003 \n", + "L 448.607481 1025.17773 \n", + "L 449.237277 1026.420706 \n", + "L 451.11064 1029.476409 \n", + "L 451.663184 1030.246914 \n", + "L 453.40063 1032.532112 \n", + "L 454.718887 1034.045303 \n", + "L 456.154769 1035.587815 \n", + "L 457.77459 1037.140273 \n", + "L 459.463741 1038.643518 \n", + "L 460.830293 1039.748437 \n", + "L 463.449233 1041.699221 \n", + "L 463.885996 1041.999387 \n", + "L 466.941698 1043.935083 \n", + "L 468.362983 1044.754924 \n", + "L 469.997401 1045.638374 \n", + "L 473.053104 1047.135775 \n", + "L 474.581403 1047.810627 \n", + "L 476.108807 1048.451224 \n", + "L 479.16451 1049.599742 \n", + "L 482.220213 1050.614022 \n", + "L 483.083768 1050.86633 \n", + "L 485.275916 1051.482926 \n", + "L 488.331619 1052.227159 \n", + "L 491.387322 1052.858957 \n", + "L 494.443025 1053.382625 \n", + "L 497.498728 1053.801795 \n", + "L 498.653692 1053.922032 \n", + "z\n", + "\" style=\"fill:#feda8a;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + "L 564.724192 984.817758 \n", + "L 563.977483 983.640865 \n", + "L 561.668489 980.629183 \n", + "L 561.632002 980.585162 \n", + "L 558.660403 977.52946 \n", + "L 558.612786 977.486833 \n", + "L 555.557083 974.987081 \n", + "L 554.860345 974.473757 \n", + "L 552.50138 972.929125 \n", + "L 549.894553 971.418054 \n", + "L 549.445677 971.182939 \n", + "L 546.389974 969.762728 \n", + "L 543.334271 968.541967 \n", + "L 542.811607 968.362351 \n", + "L 540.278568 967.560863 \n", + "L 537.222866 966.755003 \n", + "L 534.167163 966.104057 \n", + "L 531.11146 965.599623 \n", + "L 528.668954 965.306648 \n", + "L 528.055757 965.237569 \n", + "L 525.000054 965.015019 \n", + "L 521.944351 964.91223 \n", + "L 518.888648 964.924466 \n", + "L 515.832945 965.047699 \n", + "L 512.777242 965.278595 \n", + "L 512.523163 965.306648 \n", + "L 509.721539 965.62807 \n", + "L 506.665836 966.08555 \n", + "L 503.610133 966.648072 \n", + "L 500.554431 967.314638 \n", + "L 497.498728 968.084953 \n", + "L 496.528539 968.362351 \n", + "L 494.443025 968.988147 \n", + "L 491.387322 970.013131 \n", + "L 488.331619 971.147867 \n", + "L 487.667065 971.418054 \n", + "L 485.275916 972.448421 \n", + "L 482.220213 973.882788 \n", + "L 481.055724 974.473757 \n", + "L 479.16451 975.501745 \n", + "L 476.108807 977.291419 \n", + "L 475.728649 977.52946 \n", + "L 473.053104 979.345422 \n", + "L 471.342899 980.585162 \n", + "L 469.997401 981.656552 \n", + "L 467.652658 983.640865 \n", + "L 466.941698 984.312259 \n", + "L 464.554747 986.696568 \n", + "L 463.885996 987.456377 \n", + "L 461.966608 989.752271 \n", + "L 460.830293 991.335763 \n", + "L 459.82208 992.807974 \n", + "L 458.079533 995.863677 \n", + "L 457.77459 996.533226 \n", + "L 456.730848 998.91938 \n", + "L 455.734038 1001.975083 \n", + "L 455.07685 1005.030786 \n", + "L 454.760237 1008.086489 \n", + "L 454.787273 1011.142192 \n", + "L 455.163048 1014.197895 \n", + "L 455.89457 1017.253597 \n", + "L 456.990711 1020.3093 \n", + "L 457.77459 1021.949746 \n", + "L 458.493752 1023.365003 \n", + "L 460.439841 1026.420706 \n", + "L 460.830293 1026.933536 \n", + "L 462.913461 1029.476409 \n", + "L 463.885996 1030.497028 \n", + "L 465.991608 1032.532112 \n", + "L 466.941698 1033.342796 \n", + "L 469.825268 1035.587815 \n", + "L 469.997401 1035.708667 \n", + "L 473.053104 1037.66521 \n", + "L 474.756589 1038.643518 \n", + "L 476.108807 1039.357958 \n", + "L 479.16451 1040.801184 \n", + "L 481.319575 1041.699221 \n", + "L 482.220213 1042.050345 \n", + "L 485.275916 1043.096865 \n", + "L 488.331619 1043.996652 \n", + "L 491.380428 1044.754924 \n", + "z\n", + "M 491.537161 1035.587815 \n", + "L 491.387322 1035.547144 \n", + "L 488.331619 1034.542493 \n", + "L 485.275916 1033.346536 \n", + "L 483.486569 1032.532112 \n", + "L 482.220213 1031.890686 \n", + "L 479.16451 1030.11546 \n", + "L 478.191768 1029.476409 \n", + "L 476.108807 1027.910833 \n", + "L 474.341504 1026.420706 \n", + "L 473.053104 1025.137252 \n", + "L 471.447078 1023.365003 \n", + "L 469.997401 1021.389657 \n", + "L 469.273158 1020.3093 \n", + "L 467.706766 1017.253597 \n", + "L 466.941698 1015.088406 \n", + "L 466.650129 1014.197895 \n", + "L 466.085654 1011.142192 \n", + "L 465.95587 1008.086489 \n", + "L 466.250961 1005.030786 \n", + "L 466.941698 1002.067551 \n", + "L 466.964358 1001.975083 \n", + "L 468.149932 998.91938 \n", + "L 469.76205 995.863677 \n", + "L 469.997401 995.510222 \n", + "L 471.90237 992.807974 \n", + "L 473.053104 991.45177 \n", + "L 474.587812 989.752271 \n", + "L 476.108807 988.306383 \n", + "L 477.920871 986.696568 \n", + "L 479.16451 985.724479 \n", + "L 482.033334 983.640865 \n", + "L 482.220213 983.519102 \n", + "L 485.275916 981.680096 \n", + "L 487.259451 980.585162 \n", + "L 488.331619 980.045736 \n", + "L 491.387322 978.648141 \n", + "L 494.102344 977.52946 \n", + "L 494.443025 977.399796 \n", + "L 497.498728 976.368062 \n", + "L 500.554431 975.466194 \n", + "L 503.610133 974.694953 \n", + "L 504.672964 974.473757 \n", + "L 506.665836 974.086165 \n", + "L 509.721539 973.619943 \n", + "L 512.777242 973.283502 \n", + "L 515.832945 973.080543 \n", + "L 518.888648 973.015645 \n", + "L 521.944351 973.094267 \n", + "L 525.000054 973.322764 \n", + "L 528.055757 973.708395 \n", + "L 531.11146 974.259337 \n", + "L 532.02551 974.473757 \n", + "L 534.167163 975.030869 \n", + "L 537.222866 976.024587 \n", + "L 540.278568 977.233921 \n", + "L 540.917062 977.52946 \n", + "L 543.334271 978.800045 \n", + "L 546.249144 980.585162 \n", + "L 546.389974 980.685395 \n", + "L 549.445677 983.147161 \n", + "L 549.99214 983.640865 \n", + "L 552.50138 986.358469 \n", + "L 552.784197 986.696568 \n", + "L 554.850797 989.752271 \n", + "L 555.557083 991.119291 \n", + "L 556.362822 992.807974 \n", + "L 557.394324 995.863677 \n", + "L 558.0128 998.91938 \n", + "L 558.23625 1001.975083 \n", + "L 558.077461 1005.030786 \n", + "L 557.544456 1008.086489 \n", + "L 556.640877 1011.142192 \n", + "L 555.557083 1013.742988 \n", + "L 555.355119 1014.197895 \n", + "L 553.609197 1017.253597 \n", + "L 552.50138 1018.836831 \n", + "L 551.392665 1020.3093 \n", + "L 549.445677 1022.498299 \n", + "L 548.609363 1023.365003 \n", + "L 546.389974 1025.366092 \n", + "L 545.109927 1026.420706 \n", + "L 543.334271 1027.720826 \n", + "L 540.68508 1029.476409 \n", + "L 540.278568 1029.719953 \n", + "L 537.222866 1031.371209 \n", + "L 534.802476 1032.532112 \n", + "L 534.167163 1032.811801 \n", + "L 531.11146 1033.999389 \n", + "L 528.055757 1035.025737 \n", + "L 526.086625 1035.587815 \n", + "L 525.000054 1035.876394 \n", + "L 521.944351 1036.548472 \n", + "L 518.888648 1037.080686 \n", + "L 515.832945 1037.474896 \n", + "L 512.777242 1037.731941 \n", + "L 509.721539 1037.851654 \n", + "L 506.665836 1037.832861 \n", + "L 503.610133 1037.673389 \n", + "L 500.554431 1037.370065 \n", + "L 497.498728 1036.918708 \n", + "L 494.443025 1036.314125 \n", + "z\n", + "\" style=\"fill:#fba05b;\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -67378,12 +64066,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -67393,12 +64081,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -67408,12 +64096,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -67423,12 +64111,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -67437,7 +64125,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -67446,38 +64134,38 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -67512,20 +64200,25 @@ "z\n", "\" style=\"fill:#ffffff;\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + "\" style=\"fill:#5588be;\"/>\n", + " \n", " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", - " \n", + "\" style=\"fill:#b4deec;\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", + "\" style=\"fill:#e2f4f4;\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", - " \n", + " \n", + "\" style=\"fill:#fee294;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#fdb769;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", + "\" style=\"fill:#f67f4b;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#e14430;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#bb1526;\"/>\n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -69032,12 +65919,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -69048,12 +65935,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -69063,12 +65950,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -69078,12 +65965,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -69092,7 +65979,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -69101,38 +65988,38 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -69167,1020 +66054,941 @@ "z\n", "\" style=\"fill:#ffffff;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#3a51a2;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#5588be;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", + " \n", + " \n", + " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\" style=\"fill:#e2f4f4;\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", + "\" style=\"fill:#fffebe;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#fee294;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#fdb769;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#f67f4b;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#e14430;\"/>\n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -70800,12 +67824,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -70816,12 +67840,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -70831,12 +67855,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -70846,12 +67870,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -70860,7 +67884,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -70869,38 +67893,38 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -70935,219 +67959,229 @@ "z\n", "\" style=\"fill:#ffffff;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#3a51a2;\"/>\n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", - " \n", + " \n", + "\" style=\"fill:#5588be;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", + " \n", + " \n", + "\" style=\"fill:#83b9d8;\"/>\n", " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", + "\" style=\"fill:#b4deec;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#e2f4f4;\"/>\n", + " \n", " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", - " \n", + " \n", + " \n", + " \n", + "\" style=\"fill:#fdb769;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#f67f4b;\"/>\n", " \n", - " \n", - " \n", + " \n", + "\" style=\"fill:#e14430;\"/>\n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -72315,12 +69858,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -72331,12 +69874,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -72346,12 +69889,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -72361,12 +69904,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -72375,7 +69918,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -72384,38 +69927,38 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -72452,12 +69995,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -72468,12 +70011,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -72484,12 +70027,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -72499,12 +70042,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -72514,12 +70057,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -72528,7 +70071,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -72537,64 +70080,45 @@ " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -72618,52 +70142,52 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", " \n", - " \n", - " \n", + " \n", " \n", " \n", @@ -72689,112 +70213,112 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -72835,7 +70359,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.9" + "version": "3.8.8" } }, "nbformat": 4, diff --git a/docs/generate.rst b/docs/generate.rst index 16e665b58..7abac6d74 100644 --- a/docs/generate.rst +++ b/docs/generate.rst @@ -49,12 +49,21 @@ Operators - :func:`~quimb.gen.operators.swap` - :func:`~quimb.gen.operators.iswap` - :func:`~quimb.gen.operators.fsim` +- :func:`~quimb.gen.operators.fsimg` - :func:`~quimb.gen.operators.controlled` - :func:`~quimb.gen.operators.CNOT` - :func:`~quimb.gen.operators.cX` - :func:`~quimb.gen.operators.cY` - :func:`~quimb.gen.operators.cZ` +Most of these are cached (and immutable), so can be called repeatedly without creating any new objects: + +.. code-block:: py3 + + >>> pauli('Z') is pauli('Z') + True + + **Hamiltonians and related operators**: - :func:`~quimb.gen.operators.spin_operator` @@ -71,12 +80,25 @@ Operators - :func:`~quimb.gen.operators.num` - :func:`~quimb.gen.operators.ham_hubbard_hardcore` -Most of these are cached (and immutable), so can be called repeatedly without creating any new objects: +.. note:: -.. code-block:: py3 + The Hamiltonians are generally defined using spin operators rather than + Pauli matrices. Thus for example, the following spin-1/2 Hamiltonians would + be equivalent - >>> pauli('Z') is pauli('Z') - True + - in spin-operators: + + .. math:: + + \hat{H} = \sum J S^X_i S^X_{i + 1} + B S^Z_i + + - and in Pauli operators (with :math:`S^X=\dfrac{\sigma^X}{2}` etc.): + + .. math:: + + \hat{H} = \sum \dfrac{J}{4} \sigma^X_i \sigma^X_{i + 1} + \dfrac{B}{2} \sigma^Z_{i} + + note that interaction terms are scaled different than the single site terms. Random States & Operators diff --git a/docs/index_tn.rst b/docs/index_tn.rst index 1ec6d28ec..d147067c9 100644 --- a/docs/index_tn.rst +++ b/docs/index_tn.rst @@ -7,6 +7,7 @@ Tensor Network Guide :maxdepth: 2 tensor-basics + tensor-drawing tensor-1d tensor-2d tensor-circuit diff --git a/docs/installation.rst b/docs/installation.rst index 17aa630b3..77ad87cf0 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -59,72 +59,30 @@ Finally, fast and optionally distributed partial eigen-solving, SVD, exponentiat * `mpi4py `_ (v2.1.0+) * An MPI implementation (`OpenMPI `_ recommended, the 1.10.x series seems most robust for spawning processes) -It is recommended to compile and install these (apart from MPI if you are e.g. on a cluster) yourself (see below). - -For best performance of some routines, (e.g. shift invert eigen-solving), petsc must be configured with certain options. Here is a rough overview of the steps to installing the above in a directory ``$SRC_DIR``, with MPI and ``mpi4py`` already installed. ``$PATH_TO_YOUR_BLAS_LAPACK_LIB`` should point to e.g. `OpenBLAS `_ (``libopenblas.so``) or the MKL library (``libmkl_rt.so``). ``$COMPILE_FLAGS`` should be optimizations chosen for your compiler, e.g. for ``gcc`` ``"-O3 -march=native -s -DNDEBUG"``, or for ``icc`` ``"-O3 -xHost"`` etc. - - -Build PETSC -~~~~~~~~~~~ - -.. code-block:: bash - - cd $SRC_DIR - git clone https://gitlab.com/petsc/petsc.git - - export PETSC_DIR=$SRC_DIR/petsc - export PETSC_ARCH=arch-auto-complex - - cd petsc - python ./configure \ - --download-mumps \ - --download-scalapack \ - --download-parmetis \ - --download-metis \ - --download-ptscotch \ - --with-debugging=0 \ - --with-blas-lapack-lib=$PATH_TO_YOUR_BLAS_LAPACK_LIB \ - COPTFLAGS="$COMPILE_FLAGS" \ - CXXOPTFLAGS="$COMPILE_FLAGS" \ - FOPTFLAGS="$COMPILE_FLAGS" \ - --with-scalar-type=complex - make all - make test - make streams NPMAX=4 - - -Build SLEPC -~~~~~~~~~~~ +For best performance of some routines, (e.g. shift invert eigen-solving), petsc must be configured with certain options. +Pip can handle this compilation and installation, for example the following script installs everything necessary on Ubuntu: .. code-block:: bash - cd $SRC_DIR - git clone https://gitlab.com/slepc/slepc.git - export SLEPC_DIR=$SRC_DIR/slepc - cd slepc - python ./configure - make - make test - - -Build the python interfaces -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: bash + #!/bin/bash - cd $SRC_DIR - git clone https://bitbucket.org/petsc/petsc4py.git - git clone https://gitlab.com/slepc/slepc4py.git + # install build tools, OpenMPI, and OpenBLAS + sudo apt install -y openmpi-bin libopenmpi-dev gfortran bison flex cmake valgrind curl autoconf libopenblas-base libopenblas-dev - cd $SRC_DIR/petsc4py - python setup.py build - python setup.py install + # optimization flags, e.g. for intel you might want "-O3 -xHost" + export OPTFLAGS="-O3 -march=native -s -DNDEBUG" - cd $SRC_DIR/slepc4py - python setup.py build - python setup.py install + # petsc options, here configured for real + export PETSC_CONFIGURE_OPTIONS="--with-scalar-type=complex --download-mumps --download-scalapack --download-parmetis --download-metis --COPTFLAGS='$OPTFLAGS' --CXXOPTFLAGS='$OPTFLAGS' --FOPTFLAGS='$OPTFLAGS'" + # make sure using all the same version + export PETSC_VERSION=3.14.0 + pip install petsc==$PETSC_VERSION --no-binary :all: + pip install petsc4py==$PETSC_VERSION --no-binary :all: + pip install slepc==$PETSC_VERSION --no-binary :all: + pip install slepc4py==$PETSC_VERSION --no-binary :all: .. note:: - It is possible to compile several versions of PETSc/SLEPc side by side, for example a ``--with-scalar-type=real`` version, naming them with different values of ``PETSC_ARCH``. When loading PETSc/SLEPc, ``quimb`` respects ``PETSC_ARCH`` if it is set, but it cannot dynamically switch bewteen them. + For the most control and best performance it is recommended to compile and install these (apart from MPI if you are e.g. on a cluster) manually - see the `PETSc instructions `_. + It is possible to compile several versions of PETSc/SLEPc side by side, for example a ``--with-scalar-type=complex`` and/or a ``--with-precision=single`` version, naming them with different values of ``PETSC_ARCH``. When loading PETSc/SLEPc, ``quimb`` respects ``PETSC_ARCH`` if it is set, but it cannot dynamically switch between them. diff --git a/docs/tensor-drawing.ipynb b/docs/tensor-drawing.ipynb new file mode 100644 index 000000000..390ebb1d3 --- /dev/null +++ b/docs/tensor-drawing.ipynb @@ -0,0 +1,42175 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "6915a004", + "metadata": { + "raw_mimetype": "text/restructuredtext", + "tags": [] + }, + "source": [ + "#######################\n", + "Drawing Tensor Networks\n", + "#######################\n", + "\n", + "``quimb`` has a lot of functionality for drawing tensor networks that can be useful for debugging, interactive development, and producing figures etc. This page is a general overview of various options, mostly centered around the method :meth:`~quimb.tensor.tensor_core.TensorNetwork.draw`. Underneath this calls `networkx `_ which itself uses `matplotlib `_." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "35ff3c7d", + "metadata": {}, + "outputs": [], + "source": [ + "%config InlineBackend.figure_formats = ['svg']\n", + "import quimb.tensor as qtn" + ] + }, + { + "cell_type": "markdown", + "id": "08bd93de", + "metadata": {}, + "source": [ + "We'll use a 3D grid tensor network as our basic example." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "13b86f25", + "metadata": {}, + "outputs": [], + "source": [ + "Lx = Ly = Lz = 4\n", + "D = 2\n", + "tn = qtn.TN3D_rand(Lx, Ly, Lz, D=D)" + ] + }, + { + "cell_type": "markdown", + "id": "7f3dc448", + "metadata": {}, + "source": [ + "By default bonds are draw proportional to ``log2`` of their dimension, whereas nodes are fixed in size." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "08bbf5c0", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " 2021-04-22T15:56:12.965941\n", + " image/svg+xml\n", + " \n", + " \n", + " Matplotlib v3.3.4, https://matplotlib.org/\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "tn.draw()" + ] + }, + { + "cell_type": "markdown", + "id": "feec1dbc", + "metadata": {}, + "source": [ + "By default index names are not shown and tensor tags are only shown for small tensors, these can both be controlled manually like so:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "d4f2cfcc", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " 2021-04-22T15:56:13.152969\n", + " image/svg+xml\n", + " \n", + " \n", + " Matplotlib v3.3.4, https://matplotlib.org/\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "qtn.PEPS.rand(3, 3, D).draw(show_tags=True, show_inds=True)" + ] + }, + { + "cell_type": "markdown", + "id": "9ff08811", + "metadata": {}, + "source": [ + "If you want to see inner index names (bonds) as well as the outer index names you need to use ``show_inds='all'``:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "5c0d3646", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " 2021-04-22T15:56:13.629035\n", + " image/svg+xml\n", + " \n", + " \n", + " Matplotlib v3.3.4, https://matplotlib.org/\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "qtn.PEPS.rand(3, 3, D).draw(show_tags=False, show_inds='all')" + ] + }, + { + "cell_type": "markdown", + "id": "7bd381c8", + "metadata": {}, + "source": [ + "# Coloring\n", + "\n", + "The first argument to ``draw`` is ``color=``, which can either be a single tag or a sequence of tags. All tensors with each tag will be colored the same, with later tags taking priority:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "8ab334fc", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " 2021-04-22T15:56:14.004198\n", + " image/svg+xml\n", + " \n", + " \n", + " Matplotlib v3.3.4, https://matplotlib.org/\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# add the same tag to every tensor\n", + "tn.add_tag('CUBE')\n", + "\n", + "# color that tag and each corner of our TN\n", + "color = ['CUBE'] + [\n", + " f'I{i},{j},{k}' \n", + " for i in (0, Lx - 1) \n", + " for j in (0, Ly - 1) \n", + " for k in (0, Lz - 1)\n", + "]\n", + "\n", + "tn.draw(color=color)" + ] + }, + { + "cell_type": "markdown", + "id": "d1284be0", + "metadata": {}, + "source": [ + "If you have many tags or are simply only interested in the drawing the colors you can supply the ``legend=False`` option to turn off the legend. " + ] + }, + { + "cell_type": "raw", + "id": "3985f195", + "metadata": { + "raw_mimetype": "text/restructuredtext", + "tags": [] + }, + "source": [ + ".. hint::\n", + "\n", + " ``quimb`` tries to produce a sequence of colors that are reasonably locally distigushable \n", + " but also have some global ordering when using many colors. These are based on the palette \n", + " designed with color blindness in mind by `Bang Wong `_. \n", + " You can supply custom colors with the ``custom_colors=`` kwarg." + ] + }, + { + "cell_type": "markdown", + "id": "8f3c6eaa", + "metadata": {}, + "source": [ + "# Highlighting indices \n", + "\n", + "You can visualize a subset of indices by supplying a sequence of them to the ``highlight_inds=`` kwarg like so:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "61087a1c", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " 2021-04-22T15:56:14.771528\n", + " image/svg+xml\n", + " \n", + " \n", + " Matplotlib v3.3.4, https://matplotlib.org/\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# get a central tensor and its indices\n", + "tag = f\"I{Lx // 2},{Ly // 2},{Lz // 2}\"\n", + "t = tn[tag]\n", + "inds = t.inds\n", + "tn.draw(color=tag, highlight_inds=inds)" + ] + }, + { + "cell_type": "markdown", + "id": "0ed84e52", + "metadata": {}, + "source": [ + "The color can be controlled with ``highlight_inds_color``." + ] + }, + { + "cell_type": "markdown", + "id": "a305a65c", + "metadata": {}, + "source": [ + "# Highlighting ``tids``\n", + "\n", + "While tensors can carry arbitrary tags and can usually be identified by these, it is sometimes useful to be able to highlight tensors based on their underlying ``tids`` - each of which is a unique integer representing a node in the hypergraph." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "7ef52f04", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# get the first plane of tensor tids\n", + "tids = list(tn.tensor_map.keys())[:Lx * Ly]\n", + "tids" + ] + }, + { + "cell_type": "markdown", + "id": "829abdbc", + "metadata": {}, + "source": [ + "The color can be controlled with ``highlight_tids_color``:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "9f62c3d5", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " 2021-04-22T15:56:15.656343\n", + " image/svg+xml\n", + " \n", + " \n", + " Matplotlib v3.3.4, https://matplotlib.org/\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "tn.draw(highlight_tids=tids, highlight_tids_color=(1.0, 0.0, 0.5, 0.5))" + ] + }, + { + "cell_type": "markdown", + "id": "d7ca07a0", + "metadata": {}, + "source": [ + "# Positioning tensors\n", + "\n", + "## Automatic layouts\n", + "\n", + "The automatic layout strategy `quimb` adopts is to lay the tensors out using some relatively efficient scheme, before 'relaxing' the positions using a (slower) force repulsion algorithm into something usually more natural.\n", + "\n", + "The ``iterations`` kwarg controls the number of force repulsion steps, set this to zero to use only the initial layout algorithm (the default of which is ``'spectral'``):" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "89b3abdc", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " 2021-04-22T15:56:16.005187\n", + " image/svg+xml\n", + " \n", + " \n", + " Matplotlib v3.3.4, https://matplotlib.org/\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "tn.draw(iterations=0)" + ] + }, + { + "cell_type": "markdown", + "id": "612201f0", + "metadata": {}, + "source": [ + "Another good choice for the initial layout that you might try if ``'spectral'`` isn't producing good results is ``'kamada_kawai'``:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "7478a50e", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " 2021-04-22T15:56:16.435500\n", + " image/svg+xml\n", + " \n", + " \n", + " Matplotlib v3.3.4, https://matplotlib.org/\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "tn.draw(iterations=0, initial_layout='kamada_kawai')" + ] + }, + { + "cell_type": "markdown", + "id": "20991299", + "metadata": {}, + "source": [ + "You should be able to specify most of the [networkx layout algorithms](https://networkx.org/documentation/stable//reference/drawing.html#module-networkx.drawing.layout):" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "939df6dd", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " 2021-04-22T15:56:16.789582\n", + " image/svg+xml\n", + " \n", + " \n", + " Matplotlib v3.3.4, https://matplotlib.org/\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "tn.draw(iterations=0, initial_layout='spiral')" + ] + }, + { + "cell_type": "markdown", + "id": "d2ecaf67", + "metadata": {}, + "source": [ + "## Force Repulsion options\n", + "\n", + "For the [force repulsion layout](https://networkx.org/documentation/stable//reference/generated/networkx.drawing.layout.spring_layout.html#networkx.drawing.layout.spring_layout),\n", + "you can supply the spring constant ``k``, which can have a significant effect on the layout:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "3460d126", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " 2021-04-22T15:56:17.185194\n", + " image/svg+xml\n", + " \n", + " \n", + " Matplotlib v3.3.4, https://matplotlib.org/\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "tn.draw(iterations=100, k=0.01)" + ] + }, + { + "cell_type": "markdown", + "id": "89e723bf", + "metadata": {}, + "source": [ + "You can also fix specific tensors (by either a ``tid`` or set of tags that uniquely identifies that tensor):" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "47fc4dd9", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " 2021-04-22T15:56:17.582223\n", + " image/svg+xml\n", + " \n", + " \n", + " Matplotlib v3.3.4, https://matplotlib.org/\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fix = {\n", + " 'I0,0,0': (0, 0),\n", + " 'I0,0,1': (0, 1),\n", + " 'I1,0,0': (1, 0),\n", + " 'I1,0,1': (1, 1),\n", + "}\n", + "\n", + "# when fixing tensors you often have to play with ``k``\n", + "tn.draw(k=0.001, fix=fix, color=fix.keys())" + ] + }, + { + "cell_type": "markdown", + "id": "4dc0161b-8f85-40ea-8588-9b965b123958", + "metadata": { + "raw_mimetype": "text/restructuredtext", + "tags": [] + }, + "source": [ + "If you have [``forceatlas2`` (``fa2``) ](https://github.com/bhargavchippada/forceatlas2) installed then you can specify to use it rather than the slower networkx force repulsion algorithm at a certain threshold of nodes (by default 1000) with the option ``use_forceatlas2=1000``." + ] + }, + { + "cell_type": "markdown", + "id": "a419c56c", + "metadata": {}, + "source": [ + "## Manually Specifying\n", + "\n", + "You can also simply specify all positions manually using the ``fix`` kwarg. Here's that illustrated with a axonometric projection:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "3e9c35fe", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " 2021-04-22T15:56:17.939362\n", + " image/svg+xml\n", + " \n", + " \n", + " Matplotlib v3.3.4, https://matplotlib.org/\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import math\n", + "import itertools\n", + "\n", + "def get_3d_pos(i, j, k, a=22, b=45, p=0.2):\n", + " return (\n", + " + i * math.cos(math.pi * a / 180) + j * math.cos(math.pi * b / 180) / 2**p,\n", + " - i * math.sin(math.pi * a / 180) + j * math.sin(math.pi * b / 180) / 2**p + k \n", + " )\n", + "\n", + "pos = {\n", + " f'I{i},{j},{k}': get_3d_pos(i, j, k)\n", + " for i in range(Lx)\n", + " for j in range(Ly)\n", + " for k in range(Lz)\n", + "}\n", + "\n", + "\n", + "tn.draw(fix=pos, color=pos.keys(), legend=False)" + ] + }, + { + "cell_type": "markdown", + "id": "00b2f7e5", + "metadata": {}, + "source": [ + "If you want to retrieve an automatic positioning, e.g. for repeated use in an animation, you can pass the ``get='pos'`` option, which simply returns the positions as a dict mapping each ``tid`` to a 2D coordinate:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "2fa38264", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(array([0.61085382, 0.79747457]),\n", + " array([0.73864182, 0.512374 ]),\n", + " array([0.8714837 , 0.17681765]))" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pos = tn.draw(get='pos')\n", + "pos[0], pos[1], pos[2]" + ] + }, + { + "cell_type": "markdown", + "id": "8614708d", + "metadata": {}, + "source": [ + "# Hyper-edges\n", + "\n", + "Hyper edges (indices which appear on 3 or more tensors) are represented as separate 'nodes' of zero size - since they are equivalent to placing a multi-dimensional COPY-tensor is such locations." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "6eb72618", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " 2021-04-22T15:56:19.343716\n", + " image/svg+xml\n", + " \n", + " \n", + " Matplotlib v3.3.4, https://matplotlib.org/\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "htn = qtn.HTN3D_classical_ising_partition_function(3, 3, 3, beta=0.22)\n", + "htn.draw()" + ] + }, + { + "cell_type": "markdown", + "id": "b65c28e4", + "metadata": {}, + "source": [ + "Another way to visualize such hyperedges, using 'rubber bands', is provided by [`hypernetx`](https://github.com/pnnl/HyperNetX) - both the ``ind_map`` of a tensor network and the ``pos`` generate by ``draw`` are directly compatible:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "fd28a207", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " 2021-04-22T15:56:20.626964\n", + " image/svg+xml\n", + " \n", + " \n", + " Matplotlib v3.3.4, https://matplotlib.org/\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "import hypernetx\n", + "\n", + "H = hypernetx.Hypergraph(htn.ind_map)\n", + "hypernetx.draw(H, pos=htn.draw(get='pos'))" + ] + }, + { + "cell_type": "markdown", + "id": "8a30c4e1", + "metadata": {}, + "source": [ + "# Spanning trees\n", + "\n", + "Various algorithms in ``quimb`` make use of a tree generated by spanning out from a particular region." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "934cc101", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " 2021-04-22T15:56:20.843503\n", + " image/svg+xml\n", + " \n", + " \n", + " Matplotlib v3.3.4, https://matplotlib.org/\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "span_opts = {\n", + " 'max_distance': 3,\n", + " 'distance_sort': 'min',\n", + " 'ndim_sort': 'max',\n", + "}\n", + "\n", + "qtn.TN2D_rand(7, 7, 3).draw_tree_span(\n", + " tags=['I2,3', 'I2,2'], which='any', **span_opts\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "1a403c51", + "metadata": {}, + "source": [ + "# Interaction with ``matplotlib``\n", + "\n", + "You can either add other stuff to the figure that ``quimb`` creates, or you can supply a ``matplotlib`` axis \n", + "to add the tensor network drawing to directly.\n", + "\n", + "The ``return_fig=True`` option allows you to modify the figure or save it to file:" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "5c838af2", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " 2021-04-22T15:56:22.555972\n", + " image/svg+xml\n", + " \n", + " \n", + " Matplotlib v3.3.4, https://matplotlib.org/\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig = tn.draw(return_fig=True)\n", + "fig.set_facecolor('yellow')" + ] + }, + { + "cell_type": "markdown", + "id": "b73b3b6e", + "metadata": {}, + "source": [ + "This could be saved with e.g.:\n", + "```python\n", + "fig.savefig('my-tn-drawing.png', bbox_inches='tight', dpi=300)\n", + "```\n", + "\n", + "The ``ax=ax`` option allows you to add to an existing plot:" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "703445ef", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " 2021-04-22T15:56:30.137554\n", + " image/svg+xml\n", + " \n", + " \n", + " Matplotlib v3.3.4, https://matplotlib.org/\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "fig, axs = plt.subplots(10, 10)\n", + "\n", + "for ax in axs.flat:\n", + " tn = qtn.TN_rand_reg(n=12, reg=3, D=2)\n", + " tn.draw(tn.tags, ax=ax, legend=False, show_tags=False, node_size=10)\n", + " ax.axis('off')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/quimb/__init__.py b/quimb/__init__.py index 017ed842d..fc1a2042f 100644 --- a/quimb/__init__.py +++ b/quimb/__init__.py @@ -83,7 +83,7 @@ Lazy, ) from .linalg.rand_linalg import rsvd, estimate_rank -from .linalg.mpi_launcher import get_mpi_pool +from .linalg.mpi_launcher import get_mpi_pool, can_use_mpi_pool # Generating objects from .gen.operators import ( @@ -461,6 +461,7 @@ 'load_from_disk', 'get_thread_pool', 'get_mpi_pool', + 'can_use_mpi_pool', 'oset', 'LRU', ] diff --git a/quimb/core.py b/quimb/core.py index b01084923..356e8e672 100644 --- a/quimb/core.py +++ b/quimb/core.py @@ -1643,6 +1643,15 @@ def ikron(ops, dims, inds, sparse=None, stype=None, >>> A = rand_herm(5) >>> ikron(A, [2, -1, 2, -1, 2, -1], [1, 3, 5]).shape (1000, 1000) + + Create a two site interaction (note the coefficient `jx` we only need to + multiply into a single input operator): + + >>> Sx = spin_operator('X') + >>> jx = 0.123 + >>> jSxSx = ikron([jx * Sx, Sx], [2, 2, 2, 2], [0, 3]) + >>> np.allclose(jSxSx, jx * (Sx & eye(2) & eye(2) & Sx)) + True """ # TODO: test 2d+ dims and coos # TODO: simplify with compress coords? @@ -1867,7 +1876,7 @@ def pkron(op, dims, inds, **ikron_opts): dims_cur = (*dims_in, *dims_out) # find inverse permutation - ip = np.empty(n, dtype=np.int) + ip = np.empty(n, dtype=np.int32) ip[p] = np.arange(n) return permute(b, dims_cur, ip) diff --git a/quimb/gen/operators.py b/quimb/gen/operators.py index 8bfe11010..d9e5ff8ff 100644 --- a/quimb/gen/operators.py +++ b/quimb/gen/operators.py @@ -1,6 +1,5 @@ """Functions for generating quantum operators. """ -from operator import add import math import functools import itertools @@ -353,13 +352,16 @@ def fsim(theta, phi, dtype=complex, **kwargs): return gate -def fsimg(theta, Zeta, chi, gamma, phi, dtype=complex, **kwargs): - r"""The 'fermionic simulation' gate: - \theta is the iSWAP angle - \phi is the controlled-phase angle - \Zeta, \chi, \gamma are single-qubit phase angles +@functools.lru_cache(maxsize=256) +def fsimg(theta, zeta, chi, gamma, phi, dtype=complex, **kwargs): + r"""The 'fermionic simulation' gate, with: + + * :math:`\theta` is the iSWAP angle + * :math:`\phi` is the controlled-phase angle + * :math:`\zeta, \chi, \gamma` are single-qubit phase angles. + .. math:: - \mathrm{fsimg}(\theta, \Zeta, \chi, \gamma, \phi) = + \mathrm{fsimg}(\theta, \zeta, \chi, \gamma, \phi) = \begin{bmatrix} 1 & 0 & 0 & 0\\ 0 & \exp(-i(\gamma +\zeta )) \cos(\theta) & @@ -369,20 +371,20 @@ def fsimg(theta, Zeta, chi, gamma, phi, dtype=complex, **kwargs): 0 & 0 & 0 & \exp(-i (\phi +2 \gamma)) \end{bmatrix} - Note that ``theta`` ,``phi``, ``Zeta``, ``chi``, ``gamma`` - should be specified in radians and the sign - convention with this gate varies. Here for example, + See Equation 18 of https://arxiv.org/abs/2010.07965. Note that ``theta``, + ``phi``, ``zeta``, ``chi``, ``gamma`` should be specified in radians and + the sign convention with this gate varies. Here for example, ``fsimg(- pi / 2, 0, 0, 0,0) == iswap()``. """ from cmath import cos, sin, exp - a1 = exp(-1j * (gamma + Zeta)) * cos(theta) - a2 = exp(-1j * (gamma - Zeta)) * cos(theta) + a1 = exp(-1j * (gamma + zeta)) * cos(theta) + a2 = exp(-1j * (gamma - zeta)) * cos(theta) b1 = -1j * exp(-1j * (gamma - chi)) * sin(theta) b2 = -1j * exp(-1j * (gamma + chi)) * sin(theta) - c = exp(-1j * (phi + 2*gamma)) + c = exp(-1j * (phi + 2 * gamma)) gate = [[1, 0, 0, 0], [0, a1, b1, 0], @@ -578,7 +580,7 @@ def gen_term(i): if parallel: pool = get_thread_pool(nthreads) - ham = par_reduce(add, pool.map(gen_term, terms_needed)) + ham = par_reduce(operator.add, pool.map(gen_term, terms_needed)) else: ham = sum(map(gen_term, terms_needed)) @@ -798,7 +800,16 @@ def dh_terms(): @hamiltonian_builder def ham_heis_2D(n, m, j=1.0, bz=0.0, cyclic=False, parallel=False, ownership=None): - """Construct the 2D spin-1/2 heisenberg model hamiltonian. + r"""Construct the 2D spin-1/2 heisenberg model hamiltonian: + + .. math:: + + \hat{H} = \sum_{} + J_X S^X_i S^X_j + + J_Y S^Y_i S^Y_j + + J_Z S^Z_i S^Z_j + + where the sum runs over pairs :math:`` on a 2D square lattice. Parameters ---------- @@ -837,6 +848,8 @@ def ham_heis_2D(n, m, j=1.0, bz=0.0, cyclic=False, except (TypeError, ValueError): jx = jy = jz = j + js = {s: js for s, js in zip("xyz", [jx, jy, jz]) if js != 0.0} + dims = [[2] * m] * n # shape (n, m) sites = tuple(itertools.product(range(n), range(m))) @@ -851,21 +864,20 @@ def gen_pairs(): if cyclic or right != 0: yield ((i, j), (i, right)) + # generate all pairs of coordinates and directions + pairs_ss = tuple(itertools.product(gen_pairs(), js)) + # build the hamiltonian in sparse 'coo' format always for efficiency op_kws = {'sparse': True, 'stype': 'coo'} ikron_kws = {'sparse': True, 'stype': 'coo', 'coo_build': True, 'ownership': ownership} - # generate all pairs of coordinates and directions - pairs_ss = tuple(itertools.product(gen_pairs(), 'xyz')) - # generate XX, YY and ZZ interaction from # e.g. arg ([(3, 4), (3, 5)], 'z') def interactions(pair_s): pair, s = pair_s Sxyz = spin_operator(s, **op_kws) - J = {'x': jx, 'y': jy, 'z': jz}[s] - return ikron(J * Sxyz, dims, inds=pair, **ikron_kws) + return ikron([js[s] * Sxyz, Sxyz], dims, inds=pair, **ikron_kws) # generate Z field def fields(site): @@ -877,13 +889,13 @@ def fields(site): all_terms = itertools.chain( map(interactions, pairs_ss), map(fields, sites) if bz != 0.0 else ()) - H = sum(all_terms) + H = functools.reduce(operator.add, all_terms) else: pool = get_thread_pool() all_terms = itertools.chain( pool.map(interactions, pairs_ss), pool.map(fields, sites) if bz != 0.0 else ()) - H = par_reduce(add, all_terms) + H = par_reduce(operator.add, all_terms) return H diff --git a/quimb/linalg/base_linalg.py b/quimb/linalg/base_linalg.py index c146fd7cf..21f23cbf8 100644 --- a/quimb/linalg/base_linalg.py +++ b/quimb/linalg/base_linalg.py @@ -18,7 +18,9 @@ from .scipy_linalg import ( eigs_scipy, eigs_lobpcg, + eigs_primme, svds_scipy, + svds_primme, ) from . import SLEPC4PY_FOUND @@ -70,6 +72,7 @@ def choose_backend(A, k, int_eps=False, B=None): _EIGS_METHODS = { 'NUMPY': eigs_numpy, 'SCIPY': eigs_scipy, + 'PRIMME': eigs_primme, 'LOBPCG': eigs_lobpcg, 'SLEPC': eigs_slepc_spawn, 'SLEPC-NOMPI': eigs_slepc, @@ -366,6 +369,7 @@ def svd(A, return_vecs=True): 'SLEPC-NOMPI': svds_slepc, 'NUMPY': svds_numpy, 'SCIPY': svds_scipy, + 'PRIMME': svds_primme, } diff --git a/quimb/linalg/mpi_launcher.py b/quimb/linalg/mpi_launcher.py index ca0577eff..29c9486db 100644 --- a/quimb/linalg/mpi_launcher.py +++ b/quimb/linalg/mpi_launcher.py @@ -12,23 +12,31 @@ from ..core import _NUM_THREAD_WORKERS # Work out if already running as mpi -if ('OMPI_COMM_WORLD_SIZE' in os.environ) or ('PMI_SIZE' in os.environ): +if ( + ('OMPI_COMM_WORLD_SIZE' in os.environ) or # OpenMPI + ('PMI_SIZE' in os.environ) # MPICH +): + QUIMB_MPI_LAUNCHED = '_QUIMB_MPI_LAUNCHED' in os.environ ALREADY_RUNNING_AS_MPI = True - if '_QUIMB_MPI_LAUNCHED' not in os.environ: - raise RuntimeError( - "For the moment, quimb programs launched explicitly" - " using MPI need to use `quimb-mpi-python`.") USE_SYNCRO = "QUIMB_SYNCRO_MPI" in os.environ else: + QUIMB_MPI_LAUNCHED = False ALREADY_RUNNING_AS_MPI = False USE_SYNCRO = False +# default to not allowing mpi spawning capabilities +ALLOW_SPAWN = { + 'TRUE': True, 'ON': True, 'FALSE': False, 'OFF': False, +}[os.environ.get('QUIMB_MPI_SPAWN', 'False').upper()] + # Work out the desired total number of workers -for _NUM_MPI_WORKERS_VAR in ['QUIMB_NUM_MPI_WORKERS', - 'QUIMB_NUM_PROCS', - 'OMPI_COMM_WORLD_SIZE', - 'PMI_SIZE', - 'OMP_NUM_THREADS']: +for _NUM_MPI_WORKERS_VAR in ( + 'QUIMB_NUM_MPI_WORKERS', + 'QUIMB_NUM_PROCS', + 'OMPI_COMM_WORLD_SIZE', + 'PMI_SIZE', + 'OMP_NUM_THREADS' +): if _NUM_MPI_WORKERS_VAR in os.environ: NUM_MPI_WORKERS = int(os.environ[_NUM_MPI_WORKERS_VAR]) break @@ -38,6 +46,12 @@ NUM_MPI_WORKERS = psutil.cpu_count(logical=False) +def can_use_mpi_pool(): + """Function to determine whether we are allowed to call `get_mpi_pool`. + """ + return ALLOW_SPAWN or ALREADY_RUNNING_AS_MPI + + def bcast(result, comm, result_rank): """Broadcast a result to all workers, dispatching to proper MPI (rather than pickled) communication if the result is a numpy array. @@ -114,6 +128,10 @@ def cancel(): class SynchroMPIPool: + """An object that looks like a ``concurrent.futures`` executor but actually + distributes tasks in a round-robin fashion based to MPI workers, before + broadcasting the results to each other. + """ def __init__(self): import itertools @@ -121,7 +139,7 @@ def __init__(self): self.comm = MPI.COMM_WORLD self.size = self.comm.Get_size() self.rank = self.comm.Get_rank() - self.counter = itertools.cycle(range(0, NUM_MPI_WORKERS)) + self.counter = itertools.cycle(range(0, self.size)) self._max_workers = self.size def submit(self, fn, *args, **kwargs): @@ -179,9 +197,20 @@ def get_mpi_pool(num_workers=None, num_threads=1): from concurrent.futures import ProcessPoolExecutor return ProcessPoolExecutor(1) + if not QUIMB_MPI_LAUNCHED: + raise RuntimeError( + "For the moment, quimb programs using `get_mpi_pool` need to be " + "explicitly launched using `quimb-mpi-python`.") + if USE_SYNCRO: return SynchroMPIPool() + if not can_use_mpi_pool(): + raise RuntimeError( + "`get_mpi_pool()` cannot be explicitly called unless already " + "running under MPI, or you set the environment variable " + "`QUIMB_MPI_SPAWN=True`.") + from mpi4py.futures import MPIPoolExecutor return MPIPoolExecutor(num_workers, main=False, env={'OMP_NUM_THREADS': str(num_threads), @@ -199,21 +228,24 @@ class GetMPIBeforeCall(object): def __init__(self, fn): self.fn = fn - def __call__(self, *args, - comm_self=False, - wait_for_workers=None, - **kwargs): + def __call__( + self, + *args, + comm_self=False, + wait_for_workers=None, + **kwargs + ): """ Parameters ---------- - *args : + args Supplied to self.fn comm_self : bool, optional Whether to force use of MPI.COMM_SELF wait_for_workers : int, optional If set, wait for the communicator to have this many workers, this can help to catch some errors regarding expected worker numbers. - **kwargs : + kwargs Supplied to self.fn """ from mpi4py import MPI @@ -232,9 +264,7 @@ def __call__(self, *args, f"Timeout while waiting for {wait_for_workers} " f"workers to join comm {comm}.") - comm.Barrier() res = self.fn(*args, comm=comm, **kwargs) - comm.Barrier() return res @@ -250,37 +280,45 @@ class SpawnMPIProcessesFunc(object): def __init__(self, fn): self.fn = fn - def __call__(self, *args, - num_workers=None, - num_threads=1, - mpi_pool=None, - spawn_all=USE_SYNCRO or (not ALREADY_RUNNING_AS_MPI), - **kwargs): + def __call__( + self, + *args, + num_workers=None, + num_threads=1, + mpi_pool=None, + spawn_all=USE_SYNCRO or (not ALREADY_RUNNING_AS_MPI), + **kwargs + ): """ Parameters ---------- - *args - Supplied to `self.fn`. - num_workers : int, optional - How many total process should run function in parallel. - num_threads : int, optional - How many (OMP) threads each process should use - mpi_pool : pool-like, optional - If not None (default), submit function to this pool. - spawn_all : bool, optional - Whether all the parallel processes should be spawned (True), or - num_workers - 1, so that the current process can also do work. - **kwargs - Supplied to `self.fn`. + args + Supplied to `self.fn`. + num_workers : int, optional + How many total process should run function in parallel. + num_threads : int, optional + How many (OMP) threads each process should use + mpi_pool : pool-like, optional + If not None (default), submit function to this pool. + spawn_all : bool, optional + Whether all the parallel processes should be spawned (True), or + num_workers - 1, so that the current process can also do work. + kwargs + Supplied to `self.fn`. Returns ------- - `fn` output from the master process. + `fn` output from the master process. """ if num_workers is None: num_workers = NUM_MPI_WORKERS - if num_workers == 1: # no pool or communicator required + if ( + # use must explicitly run program as + (not can_use_mpi_pool()) or + # no pool or communicator needed + (num_workers == 1) + ): return self.fn(*args, comm_self=True, **kwargs) kwargs['wait_for_workers'] = num_workers diff --git a/quimb/linalg/scipy_linalg.py b/quimb/linalg/scipy_linalg.py index 4553dd0b5..dc92bb2b4 100644 --- a/quimb/linalg/scipy_linalg.py +++ b/quimb/linalg/scipy_linalg.py @@ -1,5 +1,6 @@ """Scipy based linear algebra. """ +import functools import numpy as np import scipy.sparse.linalg as spla @@ -20,7 +21,8 @@ def maybe_sort_and_project(lk, vk, P, sort=True): def eigs_scipy(A, k, *, B=None, which=None, return_vecs=True, sigma=None, - isherm=True, sort=True, P=None, tol=None, **eigs_opts): + isherm=True, sort=True, P=None, tol=None, backend=None, + **eigs_opts): """Returns a few eigenpairs from a possibly sparse hermitian operator Parameters @@ -45,6 +47,8 @@ def eigs_scipy(A, k, *, B=None, which=None, return_vecs=True, sigma=None, Perform the eigensolve in the subspace defined by this projector. sort : bool, optional Whether to ensure the eigenvalues are sorted in ascending value. + backend : None or 'primme', optional + Which backend to use. eigs_opts Supplied to :func:`scipy.sparse.linalg.eigsh` or :func:`scipy.sparse.linalg.eigs`. @@ -88,14 +92,26 @@ def eigs_scipy(A, k, *, B=None, which=None, return_vecs=True, sigma=None, 'tol': 0 if tol is None else tol } - eig_fn = spla.eigsh if isherm else spla.eigs + if backend is None: + eigs = spla.eigsh if isherm else spla.eigs + elif backend == 'primme': + import primme + if isherm: + eigs = primme.eigsh + else: + raise ValueError("Primme only for hermitian problems.") + + # primme requires a N * k initial space even if k == 1 + v0 = eigs_opts.get('v0', None) + if (v0 is not None) and (v0.ndim == 1): + eigs_opts['v0'] = v0.reshape(-1, 1) if return_vecs: - lk, vk = eig_fn(A, **settings, **eigs_opts) + lk, vk = eigs(A, **settings, **eigs_opts) vk = qu.qarray(vk) return maybe_sort_and_project(lk, vk, P, sort) else: - lk = eig_fn(A, **settings, **eigs_opts) + lk = eigs(A, **settings, **eigs_opts) return np.sort(lk) if sort else lk @@ -200,7 +216,7 @@ def eigs_lobpcg(A, k, *, B=None, v0=None, which=None, return_vecs=True, return np.sort(lk) if sort else lk -def svds_scipy(A, k=6, *, return_vecs=True, **svds_opts): +def svds_scipy(A, k=6, *, return_vecs=True, backend=None, **svds_opts): """Compute a number of singular value pairs Parameters @@ -227,10 +243,20 @@ def svds_scipy(A, k=6, *, return_vecs=True, **svds_opts): if isinstance(A, qu.qarray): A = A.A + if backend is None: + svds = spla.svds + elif backend == 'primme': + import primme + svds = primme.svds + if return_vecs: - uk, sk, vtk = spla.svds(A, **settings) + uk, sk, vtk = svds(A, **settings) so = np.argsort(-sk) return qu.qarray(uk[:, so]), sk[so], qu.qarray(vtk[so, :]) else: - sk = spla.svds(A, **settings) + sk = svds(A, **settings) return sk[np.argsort(-sk)] + + +eigs_primme = functools.partial(eigs_scipy, backend='primme') +svds_primme = functools.partial(svds_scipy, backend='primme') diff --git a/quimb/linalg/slepc_linalg.py b/quimb/linalg/slepc_linalg.py index b7ef653f7..1456bb913 100644 --- a/quimb/linalg/slepc_linalg.py +++ b/quimb/linalg/slepc_linalg.py @@ -82,10 +82,10 @@ def __init__(self, lo): self.real = lo.dtype in (float, np.float_) def mult(self, _, x, y): - y[:] = self.lo.matvec(x[:]) + y[:] = self.lo.matvec(x) def multHermitian(self, _, x, y): - y[:] = self.lo.rmatvec(x[:]) + y[:] = self.lo.rmatvec(x) def linear_operator_2_petsc_shell(lo, comm=None): @@ -678,7 +678,6 @@ def usv_getter(): else: res = np.asarray([svd_solver.getValue(i) for i in range(k)]) - comm.Barrier() svd_solver.destroy() return res if rank == 0 else None @@ -745,7 +744,6 @@ def mfn_multiply_slepc(mat, vec, # --> gather the (distributed) petsc vector to a numpy matrix on master all_out = gather_petsc_array(out, comm=comm, out_shape=(-1, 1)) - comm.Barrier() mfn.destroy() return all_out @@ -789,6 +787,5 @@ def ssolve_slepc(A, y, isherm=True, comm=None, maxiter=None, tol=None, f"{lookup_ksp_error(converged_reason)}") x = gather_petsc_array(x, comm=comm, out_shape=out_shape) - comm.Barrier() ksp.destroy() return x diff --git a/quimb/tensor/array_ops.py b/quimb/tensor/array_ops.py index 453cb98b8..9daf41bc5 100644 --- a/quimb/tensor/array_ops.py +++ b/quimb/tensor/array_ops.py @@ -174,7 +174,7 @@ def _numba_find_diag_axes(x, atol=1e-12): # pragma: no cover # enumerate through every array entry, eagerly invalidating axis pairs for index, val in numpy.ndenumerate(x): - for d1, d2 in diag_axes: + for d1, d2 in list(diag_axes): if (index[d1] != index[d2]) and (abs(val) > atol): diag_axes.remove((d1, d2)) @@ -265,7 +265,7 @@ def _numba_find_antidiag_axes(x, atol=1e-12): # pragma: no cover # enumerate through every array entry, eagerly invalidating axis pairs for index, val in numpy.ndenumerate(x): - for i, j in antidiag_axes: + for i, j in list(antidiag_axes): d = x.shape[i] if (index[i] != d - 1 - index[j]) and (abs(val) > atol): antidiag_axes.remove((i, j)) @@ -362,7 +362,7 @@ def _numba_find_columns(x, atol=1e-12): # pragma: no cover for index, val in numpy.ndenumerate(x): if abs(val) > atol: for ax, i in enumerate(index): - for pax, pi in column_pairs: + for pax, pi in list(column_pairs): if ax == pax and pi != i: column_pairs.remove((pax, pi)) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index d65449c59..d5276137d 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -409,18 +409,15 @@ def apply_fsim(psi, theta, phi, i, j, parametrize=False, **gate_opts): def fsimg_param_gen(params): - theta, Zeta, chi, gamma, phi = ( - params[0], - params[1], - params[2], - params[3], - params[4]) + theta, zeta, chi, gamma, phi = ( + params[0], params[1], params[2], params[3], params[4] + ) a11_re = do('cos', theta) a11_im = do('imag', a11_re) a11 = do('complex', a11_re, a11_im) - e11_im = -(gamma + Zeta) + e11_im = -(gamma + zeta) e11_re = do('imag', e11_im) e11 = do('exp', do('complex', e11_re, e11_im)) @@ -428,7 +425,7 @@ def fsimg_param_gen(params): a22_im = do('imag', a22_re) a22 = do('complex', a22_re, a22_im) - e22_im = -(gamma - Zeta) + e22_im = -(gamma - zeta) e22_re = do('imag', e22_im) e22 = do('exp', do('complex', e22_re, e22_im)) @@ -452,13 +449,13 @@ def fsimg_param_gen(params): img_im = do('imag', -1.j) img = do('complex', img_re, img_im) - c_im = -(2*gamma + phi) + c_im = -(2 * gamma + phi) c_re = do('imag', c_im) c = do('exp', do('complex', c_re, c_im)) data = [[[[1, 0], [0, 0]], - [[0, a11*e11], [a21*e21*img, 0]]], - [[[0, a12*e12*img], [a22*e22, 0]], + [[0, a11 * e11], [a21 * e21 * img, 0]]], + [[[0, a12 * e12 * img], [a22 * e22, 0]], [[0, 0], [0, c]]]] return do('array', data, like=params) @@ -466,15 +463,15 @@ def fsimg_param_gen(params): def apply_fsimg( psi, - theta, Zeta, chi, gamma, phi, + theta, zeta, chi, gamma, phi, i, j, parametrize=False, **gate_opts ): mtags = _merge_tags('FSIMG', gate_opts) if parametrize: - G = ops.PArray(fsimg_param_gen, (theta, Zeta, chi, gamma, phi)) + G = ops.PArray(fsimg_param_gen, (theta, zeta, chi, gamma, phi)) else: - G = qu.fsimg(theta, Zeta, chi, gamma, phi) + G = qu.fsimg(theta, zeta, chi, gamma, phi) psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) @@ -613,8 +610,7 @@ def apply_su4( ONE_QUBIT_PARAM_GATES = {'RX', 'RY', 'RZ', 'U3', 'U2', 'U1'} TWO_QUBIT_PARAM_GATES = { - 'CU3', 'CU2', 'CU1', 'FS', 'FSIM', 'FSIMG', - 'RZZ', 'SU4' + 'CU3', 'CU2', 'CU1', 'FS', 'FSIM', 'FSIMG', 'RZZ', 'SU4' } ALL_PARAM_GATES = ONE_QUBIT_PARAM_GATES | TWO_QUBIT_PARAM_GATES @@ -985,11 +981,9 @@ def fsim(self, theta, phi, i, j, gate_round=None, parametrize=False): self.apply_gate('FSIM', theta, phi, i, j, gate_round=gate_round, parametrize=parametrize) - def fsimg( - self, theta, Zeta, chi, - gamma, phi, i, j, gate_round=None, parametrize=False - ): - self.apply_gate('FSIMG', theta, Zeta, chi, gamma, phi, i, j, + def fsimg(self, theta, zeta, chi, gamma, phi, i, j, + gate_round=None, parametrize=False): + self.apply_gate('FSIMG', theta, zeta, chi, gamma, phi, i, j, gate_round=gate_round, parametrize=parametrize) def rzz(self, theta, i, j, gate_round=None, parametrize=False): @@ -1187,7 +1181,12 @@ def _get_sliced_contractor( self._storage[key] = sc return sc - def get_psi_simplified(self, seq='ADCRS', atol=1e-12): + def get_psi_simplified( + self, + seq='ADCRS', + atol=1e-12, + equalize_norms=False + ): """Get the full wavefunction post local tensor network simplification. Parameters @@ -1199,6 +1198,8 @@ def get_psi_simplified(self, seq='ADCRS', atol=1e-12): atol : float, optional The tolerance with which to compare to zero when applying :meth:`~quimb.tensor.tensor_core.TensorNetwork.full_simplify`. + equalize_norms : bool, optional + Actively renormalize tensor norms during simplification. Returns ------- @@ -1215,13 +1216,20 @@ def get_psi_simplified(self, seq='ADCRS', atol=1e-12): output_inds = tuple(map(psi.site_ind, range(self.N))) # simplify the state and cache it - psi.full_simplify_(seq=seq, atol=atol, output_inds=output_inds) + psi.full_simplify_(seq=seq, atol=atol, output_inds=output_inds, + equalize_norms=equalize_norms) self._storage[key] = psi # return a copy so we can modify it inplace return psi.copy() - def get_rdm_lightcone_simplified(self, where, seq='ADCRS', atol=1e-12): + def get_rdm_lightcone_simplified( + self, + where, + seq='ADCRS', + atol=1e-12, + equalize_norms=False, + ): """Get a simplified TN of the norm of the wavefunction, with gates outside reverse lightcone of ``where`` cancelled, and physical indices within ``where`` preserved so that they can be fixed (sliced) @@ -1240,6 +1248,8 @@ def get_rdm_lightcone_simplified(self, where, seq='ADCRS', atol=1e-12): atol : float, optional The tolerance with which to compare to zero when applying :meth:`~quimb.tensor.tensor_core.TensorNetwork.full_simplify`. + equalize_norms : bool, optional + Actively renormalize tensor norms during simplification. Returns ------- @@ -1261,7 +1271,8 @@ def get_rdm_lightcone_simplified(self, where, seq='ADCRS', atol=1e-12): output_inds = b_inds + k_inds # # simplify the norm and cache it - rho_lc.full_simplify_(seq=seq, atol=atol, output_inds=output_inds) + rho_lc.full_simplify_(seq=seq, atol=atol, output_inds=output_inds, + equalize_norms=equalize_norms) self._storage[key] = rho_lc # return a copy so we can modify it inplace @@ -1273,6 +1284,7 @@ def amplitude( optimize='auto-hq', simplify_sequence='ADCRS', simplify_atol=1e-12, + simplify_equalize_norms=False, backend='auto', dtype='complex128', target_size=None, @@ -1299,6 +1311,8 @@ def amplitude( simplify_atol : float, optional The tolerance with which to compare to zero when applying :meth:`~quimb.tensor.tensor_core.TensorNetwork.full_simplify`. + simplify_equalize_norms : bool, optional + Actively renormalize tensor norms during simplification. backend : str, optional Backend to perform the contraction with, e.g. ``'numpy'``, ``'cupy'`` or ``'jax'``. Passed to ``opt_einsum``. @@ -1309,10 +1323,10 @@ def amplitude( contraction involves tensors bigger than this, 'slice' the contraction into independent parts and sum them individually. Requires ``cotengra`` currently. - rehearse : bool, optional + rehearse : bool or "tn", optional If ``True``, generate and cache the simplified tensor network and contraction path but don't actually perform the contraction. - Returns a dict with keys ``'tn'`` and ``'info'`` with the tensor + Returns a dict with keys ``"tn"`` and ``'info'`` with the tensor network that will be contracted and the corresponding contraction path if so. """ @@ -1322,18 +1336,26 @@ def amplitude( raise ValueError(f"Bit-string {b} length does not " f"match number of qubits {self.N}.") + fs_opts = { + 'seq': simplify_sequence, + 'atol': simplify_atol, + 'equalize_norms': simplify_equalize_norms, + } + # get the full wavefunction simplified - psi_b = self.get_psi_simplified( - seq=simplify_sequence, atol=simplify_atol) + psi_b = self.get_psi_simplified(**fs_opts) # fix the output indices to the correct bitstring for i, x in zip(range(self.N), b): psi_b.isel_({psi_b.site_ind(i): int(x)}) # perform a final simplification and cast - psi_b.full_simplify_(seq=simplify_sequence, atol=simplify_atol) + psi_b.full_simplify_(**fs_opts) psi_b.astype_(dtype) + if rehearse == "tn": + return psi_b + # get the contraction path info info = psi_b.contract( all, output_inds=(), optimize=optimize, get='path-info' @@ -1360,8 +1382,10 @@ def amplitude_rehearse( b='random', simplify_sequence='ADCRS', simplify_atol=1e-12, + simplify_equalize_norms=False, optimize='auto-hq', dtype='complex128', + rehearse=True, ): """Perform just the tensor network simplifications and contraction path finding associated with computing a single amplitude (caching the @@ -1383,6 +1407,8 @@ def amplitude_rehearse( simplify_atol : float, optional The tolerance with which to compare to zero when applying :meth:`~quimb.tensor.tensor_core.TensorNetwork.full_simplify`. + simplify_equalize_norms : bool, optional + Actively renormalize tensor norms during simplification. backend : str, optional Backend to perform the marginal contraction with, e.g. ``'numpy'``, ``'cupy'`` or ``'jax'``. Passed to ``opt_einsum``. @@ -1399,8 +1425,13 @@ def amplitude_rehearse( b = [random.choice('01') for _ in range(self.N)] return self.amplitude( - b=b, optimize=optimize, dtype=dtype, rehearse=True, - simplify_sequence=simplify_sequence, simplify_atol=simplify_atol) + b=b, optimize=optimize, dtype=dtype, rehearse=rehearse, + simplify_sequence=simplify_sequence, + simplify_atol=simplify_atol, + simplify_equalize_norms=simplify_equalize_norms + ) + + amplitude_tn = functools.partialmethod(amplitude_rehearse, rehearse="tn") def partial_trace( self, @@ -1408,6 +1439,7 @@ def partial_trace( optimize='auto-hq', simplify_sequence='ADCRS', simplify_atol=1e-12, + simplify_equalize_norms=False, backend='auto', dtype='complex128', target_size=None, @@ -1442,6 +1474,8 @@ def partial_trace( simplify_atol : float, optional The tolerance with which to compare to zero when applying :meth:`~quimb.tensor.tensor_core.TensorNetwork.full_simplify`. + simplify_equalize_norms : bool, optional + Actively renormalize tensor norms during simplification. backend : str, optional Backend to perform the marginal contraction with, e.g. ``'numpy'``, ``'cupy'`` or ``'jax'``. Passed to ``opt_einsum``. @@ -1452,10 +1486,10 @@ def partial_trace( contraction involves tensors bigger than this, 'slice' the contraction into independent parts and sum them individually. Requires ``cotengra`` currently. - rehearse : bool, optional + rehearse : bool or "tn", optional If ``True``, generate and cache the simplified tensor network and contraction path but don't actually perform the contraction. - Returns a dict with keys ``'tn'`` and ``'info'`` with the tensor + Returns a dict with keys ``"tn"`` and ``'info'`` with the tensor network that will be contracted and the corresponding contraction path if so. @@ -1471,9 +1505,13 @@ def partial_trace( tuple(map(self.bra_site_ind, keep))) rho = self.get_rdm_lightcone_simplified( - keep, simplify_sequence, simplify_atol + where=keep, seq=simplify_sequence, atol=simplify_atol, + equalize_norms=simplify_equalize_norms, ).astype_(dtype) + if rehearse == "tn": + return rho + info = rho.contract( all, output_inds=output_inds, @@ -1500,6 +1538,8 @@ def partial_trace( partial_trace_rehearse = functools.partialmethod( partial_trace, rehearse=True) + partial_trace_tn = functools.partialmethod( + partial_trace, rehearse="tn") def local_expectation( self, @@ -1508,6 +1548,7 @@ def local_expectation( optimize='auto-hq', simplify_sequence='ADCRS', simplify_atol=1e-12, + simplify_equalize_norms=False, backend='auto', dtype='complex128', target_size=None, @@ -1542,6 +1583,8 @@ def local_expectation( simplify_atol : float, optional The tolerance with which to compare to zero when applying :meth:`~quimb.tensor.tensor_core.TensorNetwork.full_simplify`. + simplify_equalize_norms : bool, optional + Actively renormalize tensor norms during simplification. backend : str, optional Backend to perform the marginal contraction with, e.g. ``'numpy'``, ``'cupy'`` or ``'jax'``. Passed to ``opt_einsum``. @@ -1554,7 +1597,7 @@ def local_expectation( Requires ``cotengra`` currently. gate_opts : None or dict_like Options to use when applying ``G`` to the wavefunction. - rehearse : bool, optional + rehearse : bool or "tn", optional If ``True``, generate and cache the simplified tensor network and contraction path but don't actually perform the contraction. Returns a dict with keys ``'tn'`` and ``'info'`` with the tensor @@ -1568,16 +1611,20 @@ def local_expectation( if isinstance(where, numbers.Integral): where = (where,) - rho = self.get_rdm_lightcone_simplified( - where, simplify_sequence, simplify_atol - ) + fs_opts = { + 'seq': simplify_sequence, + 'atol': simplify_atol, + 'equalize_norms': simplify_equalize_norms, + } + + rho = self.get_rdm_lightcone_simplified(where=where, **fs_opts) k_inds = tuple(self.ket_site_ind(i) for i in where) b_inds = tuple(self.bra_site_ind(i) for i in where) if isinstance(G, (list, tuple)): # if we have multiple expectations create an extra indexed stack nG = len(G) - G_data = do('stack', G, like=G[0]) + G_data = do('stack', G) G_data = reshape(G_data, (nG,) + (2,) * 2 * len(where)) output_inds = (rand_uuid(),) else: @@ -1588,13 +1635,12 @@ def local_expectation( rhoG = rho | TG - rhoG.full_simplify_( - seq=simplify_sequence, - atol=simplify_atol, - output_inds=output_inds, - ) + rhoG.full_simplify_(output_inds=output_inds, **fs_opts) rhoG.astype_(dtype) + if rehearse == "tn": + return rhoG + info = rhoG.contract( all, output_inds=output_inds, @@ -1621,6 +1667,8 @@ def local_expectation( local_expectation_rehearse = functools.partialmethod( local_expectation, rehearse=True) + local_expectation_tn = functools.partialmethod( + local_expectation, rehearse="tn") def compute_marginal( self, @@ -1631,6 +1679,7 @@ def compute_marginal( dtype='complex64', simplify_sequence='ADCRS', simplify_atol=1e-6, + simplify_equalize_norms=True, target_size=None, rehearse=False, ): @@ -1660,7 +1709,9 @@ def compute_marginal( simplify_atol : float, optional The tolerance with which to compare to zero when applying :meth:`~quimb.tensor.tensor_core.TensorNetwork.full_simplify`. - rehearse : bool, optional + simplify_equalize_norms : bool, optional + Actively renormalize tensor norms during simplification. + rehearse : bool or "tn", optional Whether to perform the marginal contraction or just return the associated TN and contraction path information. target_size : None or int, optional @@ -1675,7 +1726,11 @@ def compute_marginal( # rho_ii -> p_i (i.e. insert a COPY tensor into the norm) output_inds = [self.ket_site_ind(i) for i in where] - fs_opts = dict(seq=simplify_sequence, atol=simplify_atol) + fs_opts = { + 'seq': simplify_sequence, + 'atol': simplify_atol, + 'equalize_norms': simplify_equalize_norms, + } # lightcone region is target qubit plus fixed qubits region = set(where) @@ -1707,15 +1762,19 @@ def compute_marginal( nm_lc.full_simplify_(output_inds=output_inds, **fs_opts) # for stability with very small probabilities, scale by average prob - nfact = 2**len(fix) - if final_marginal: - nm_lc.multiply_(nfact**0.5, spread_over='all') - else: - nm_lc.multiply_(nfact, spread_over='all') + if fix is not None: + nfact = 2**len(fix) + if final_marginal: + nm_lc.multiply_(nfact**0.5, spread_over='all') + else: + nm_lc.multiply_(nfact, spread_over='all') # cast to desired data type nm_lc.astype_(dtype) + if rehearse == "tn": + return nm_lc + # NB. the path isn't *neccesarily* the same each time due to the post # slicing full simplify, however there is also the lower level # contraction path cache if the structure generated *is* the same @@ -1726,7 +1785,7 @@ def compute_marginal( ) if rehearse: - return nm_lc, info + return rehearsal_dict(nm_lc, info) if target_size is not None: # perform the 'sliced' contraction restricted to ``target_size`` @@ -1744,7 +1803,15 @@ def compute_marginal( # we only did half the ket contraction so need to square p_marginal = p_marginal**2 - return p_marginal / nfact + if fix is not None: + p_marginal /= nfact + + return p_marginal + + compute_marginal_rehearse = functools.partialmethod( + compute_marginal, rehearse=True) + compute_marginal_tn = functools.partialmethod( + compute_marginal, rehearse="tn") def calc_qubit_ordering(self, qubits=None): """Get a order to measure ``qubits`` in, by greedily choosing whichever @@ -1825,6 +1892,7 @@ def sample( dtype='complex64', simplify_sequence='ADCRS', simplify_atol=1e-6, + simplify_equalize_norms=False, target_size=None, ): r"""Sample the circuit given by ``gates``, ``C`` times, using lightcone @@ -1909,6 +1977,8 @@ def sample( simplify_atol : float, optional The tolerance with which to compare to zero when applying :meth:`~quimb.tensor.tensor_core.TensorNetwork.full_simplify`. + simplify_equalize_norms : bool, optional + Actively renormalize tensor norms during simplification. target_size : None or int, optional The largest size of tensor to allow. If specified and any contraction involves tensors bigger than this, 'slice' the @@ -1952,6 +2022,7 @@ def sample( dtype=dtype, simplify_sequence=simplify_sequence, simplify_atol=simplify_atol, + simplify_equalize_norms=simplify_equalize_norms, target_size=target_size, ) p = do('to_numpy', p).astype('float64') @@ -1982,6 +2053,8 @@ def sample_rehearse( optimize='auto-hq', simplify_sequence='ADCRS', simplify_atol=1e-6, + simplify_equalize_norms=False, + rehearse=True, progbar=False, ): """Perform the preparations and contraction path findings for @@ -2017,6 +2090,8 @@ def sample_rehearse( simplify_atol : float, optional The tolerance with which to compare to zero when applying :meth:`~quimb.tensor.tensor_core.TensorNetwork.full_simplify`. + simplify_equalize_norms : bool, optional + Actively renormalize tensor norms during simplification. progbar : bool, optional Whether to show the progress of finding each contraction path. @@ -2041,22 +2116,24 @@ def sample_rehearse( tns_and_infos = {} for where in _progbar(groups, disable=not progbar): - tn, info = self.compute_marginal( + tns_and_infos[where] = self.compute_marginal( where=where, fix=fix, optimize=optimize, simplify_sequence=simplify_sequence, - rehearse=True, + simplify_atol=simplify_atol, + simplify_equalize_norms=simplify_equalize_norms, + rehearse=rehearse, ) - tns_and_infos[where] = rehearsal_dict(tn, info) - # set the result of qubit ``q`` arbitrarily for q in where: fix[q] = result[q] return tns_and_infos + sample_tns = functools.partialmethod(sample_rehearse, rehearse="tn") + def sample_chaotic( self, C, @@ -2068,6 +2145,7 @@ def sample_chaotic( dtype='complex64', simplify_sequence='ADCRS', simplify_atol=1e-6, + simplify_equalize_norms=False, target_size=None, ): r"""Sample from this circuit, *assuming* it to be chaotic. Which is to @@ -2122,6 +2200,8 @@ def sample_chaotic( simplify_atol : float, optional The tolerance with which to compare to zero when applying :meth:`~quimb.tensor.tensor_core.TensorNetwork.full_simplify`. + simplify_equalize_norms : bool, optional + Actively renormalize tensor norms during simplification. target_size : None or int, optional The largest size of tensor to allow. If specified and any contraction involves tensors bigger than this, 'slice' the @@ -2166,6 +2246,7 @@ def sample_chaotic( dtype=dtype, simplify_sequence=simplify_sequence, simplify_atol=simplify_atol, + simplify_equalize_norms=simplify_equalize_norms, target_size=target_size, ) p = do('to_numpy', p).astype('float64') @@ -2194,7 +2275,9 @@ def sample_chaotic_rehearse( optimize='auto-hq', simplify_sequence='ADCRS', simplify_atol=1e-6, + simplify_equalize_norms=False, dtype='complex64', + rehearse=True, ): """Rehearse chaotic sampling (perform just the TN simplifications and contraction path finding). @@ -2219,6 +2302,8 @@ def sample_chaotic_rehearse( simplify_atol : float, optional The tolerance with which to compare to zero when applying :meth:`~quimb.tensor.tensor_core.TensorNetwork.full_simplify`. + simplify_equalize_norms : bool, optional + Actively renormalize tensor norms during simplification. dtype : str, optional Data type to cast the TN to before contraction. @@ -2246,17 +2331,24 @@ def sample_chaotic_rehearse( else: fix = {q: result[q] for q in fix_qubits} - tn, info = self.compute_marginal( + rehs = self.compute_marginal( where=where, fix=fix, optimize=optimize, simplify_sequence=simplify_sequence, simplify_atol=simplify_atol, + simplify_equalize_norms=simplify_equalize_norms, dtype=dtype, - rehearse=True, + rehearse=rehearse, ) - return {where: rehearsal_dict(tn, info)} + if rehearse == "tn": + return next(iter(rehs.values())) + + return {where: rehs} + + sample_chaotic_tn = functools.partialmethod( + sample_chaotic_rehearse, rehearse="tn") def to_dense( self, @@ -2264,6 +2356,7 @@ def to_dense( optimize='auto-hq', simplify_sequence='R', simplify_atol=1e-12, + simplify_equalize_norms=False, backend='auto', dtype=None, target_size=None, @@ -2289,6 +2382,8 @@ def to_dense( simplify_atol : float, optional The tolerance with which to compare to zero when applying :meth:`~quimb.tensor.tensor_core.TensorNetwork.full_simplify`. + simplify_equalize_norms : bool, optional + Actively renormalize tensor norms during simplification. backend : str, optional Backend to perform the contraction with, e.g. ``'numpy'``, ``'cupy'`` or ``'jax'``. Passed to ``opt_einsum``. @@ -2311,11 +2406,17 @@ def to_dense( psi : qarray The densely represented wavefunction with ``dtype`` data. """ - psi = self.get_psi_simplified(simplify_sequence, simplify_atol) + psi = self.get_psi_simplified( + seq=simplify_sequence, atol=simplify_atol, + equalize_norms=simplify_equalize_norms + ) if dtype is not None: psi.astype_(dtype) + if rehearse == "tn": + return psi + output_inds = tuple(map(psi.site_ind, range(self.N))) if reverse: output_inds = output_inds[::-1] @@ -2348,6 +2449,7 @@ def to_dense( return k to_dense_rehearse = functools.partialmethod(to_dense, rehearse=True) + to_dense_tn = functools.partialmethod(to_dense, rehearse="tn") def simulate_counts(self, C, seed=None, reverse=False, **to_dense_opts): """Simulate measuring all qubits in the computational basis many times. diff --git a/quimb/tensor/decomp.py b/quimb/tensor/decomp.py index b77894165..73585ec40 100644 --- a/quimb/tensor/decomp.py +++ b/quimb/tensor/decomp.py @@ -294,7 +294,7 @@ def eig(x, cutoff=-1.0, cutoff_mode=3, max_bond=-1, absorb=0, renorm=0): max_bond, absorb, renorm) -@njit +@njit # pragma: no cover def svdvals_eig(x): # pragma: no cover """SVD-decomposition via eigen, but return singular values only. """ @@ -509,6 +509,33 @@ def _similarity_compress_eig_numba(X, max_bond, renorm): return Cl, Cr +def _similarity_compress_eigh(X, max_bond, renorm): + XX = (X + dag(X)) / 2 + el, ev = do('linalg.eigh', XX) + sel = do('argsort', do('abs', el))[-max_bond:] + Cl = ev[:, sel] + Cr = dag(Cl) + if renorm: + trace_old = do('trace', X) + trace_new = do('trace', Cr @ (X @ Cl)) + Cl = Cl * trace_old / trace_new + return Cl, Cr + + +@njit # pragma: no cover +def _similarity_compress_eigh_numba(X, max_bond, renorm): + XX = (X + dag_numba(X)) / 2 + el, ev = np.linalg.eigh(XX) + sel = np.argsort(-np.abs(el))[:max_bond] + Cl = ev[:, sel] + Cr = dag_numba(Cl) + if renorm: + trace_old = np.trace(X) + trace_new = np.trace(Cr @ (X @ Cl)) + Cl = Cl * trace_old / trace_new + return Cl, Cr + + def _similarity_compress_svd(X, max_bond, renorm, asymm): U, _, VH = do('linalg.svd', X) U = U[:, :max_bond] @@ -550,50 +577,72 @@ def _similarity_compress_svd_numba(X, max_bond, renorm, asymm): return Cl, Cr -def _similarity_compress_eigh2(X, max_bond, renorm): - EE = X @ dag(X) - _, ev = do('linalg.eigh', EE + dag(EE)) - Cl = ev[:, -max_bond:] - Cr = dag(Cl) +def _similarity_compress_biorthog(X, max_bond, renorm): + U, s, VH = do('linalg.svd', X) + + B = U[:, :max_bond] + AH = VH[:max_bond, :] + + Uab, sab, VHab = do('linalg.svd', AH @ B) + sab = (sab + 1e-12 * do('max', sab)) ** -0.5 + sab_inv = do('reshape', sab, (1, -1)) + P = Uab * sab_inv + Q = dag(VHab) * sab_inv + + Cl = B @ Q + Cr = dag(P) @ AH if renorm: trace_old = do('trace', X) trace_new = do('trace', Cr @ (X @ Cl)) - Cl = Cl * (trace_old / trace_new) + Cl = Cl * trace_old / trace_new return Cl, Cr @njit # pragma: no cover -def _similarity_compress_eigh2_numba(X, max_bond, renorm): - EE = X @ dag_numba(X) - _, ev = np.linalg.eigh(EE + dag_numba(EE)) - Cl = ev[:, -max_bond:] - Cr = dag_numba(Cl) +def _similarity_compress_biorthog_numba(X, max_bond, renorm): + U, s, VH = np.linalg.svd(X) + + B = U[:, :max_bond] + AH = VH[:max_bond, :] + + Uab, sab, VHab = np.linalg.svd(AH @ B) + + # smudge factor + sab += 1e-12 * np.max(sab) + sab **= -0.5 + + sab_inv = sab.reshape((1, -1)) + P = Uab * sab_inv + Q = dag_numba(VHab) * sab_inv + + Cl = B @ Q + Cr = dag_numba(P) @ AH + if renorm: trace_old = np.trace(X) trace_new = np.trace(Cr @ (X @ Cl)) - Cl = Cl * (trace_old / trace_new) + Cl = Cl * trace_old / trace_new + return Cl, Cr _similarity_compress_fns = { ('eig', False): _similarity_compress_eig, ('eig', True): _similarity_compress_eig_numba, + ('eigh', False): _similarity_compress_eigh, + ('eigh', True): _similarity_compress_eigh_numba, ('svd', False): functools.partial( _similarity_compress_svd, asymm=0), ('svd', True): functools.partial( _similarity_compress_svd_numba, asymm=0), - ('svd-asymm', False): functools.partial( - _similarity_compress_svd, asymm=1), - ('svd-asymm', True): functools.partial( - _similarity_compress_svd_numba, asymm=1), - ('eigh2', False): _similarity_compress_eigh2, - ('eigh2', True): _similarity_compress_eigh2_numba, + ('biorthog', False): _similarity_compress_biorthog, + ('biorthog', True): _similarity_compress_biorthog_numba, } -def similarity_compress(X, max_bond, renorm=True, method='eigh2'): +def similarity_compress(X, max_bond, renorm=True, method='eigh'): if method == 'eig': if get_dtype_name(X) == 'float64': X = astype(X, 'complex128') diff --git a/quimb/tensor/drawing.py b/quimb/tensor/drawing.py index 15c4a66ee..e5d177229 100644 --- a/quimb/tensor/drawing.py +++ b/quimb/tensor/drawing.py @@ -1,12 +1,16 @@ """Functionailty for drawing tensor networks. """ import textwrap +import importlib import numpy as np from ..utils import valmap +HAS_FA2 = importlib.util.find_spec('fa2') is not None + + def _add_or_merge_edge(G, u, v, attrs): if not G.has_edge(u, v): G.add_edge(u, v, **attrs) @@ -17,8 +21,11 @@ def _add_or_merge_edge(G, u, v, attrs): attrs0['color'] = tuple( (x + y) / 2 for x, y in zip(attrs0['color'], attrs['color'])) attrs0['ind'] += ' ' + attrs['ind'] - # adding log size == multiplying bond dim - attrs0['edge_size'] += attrs['edge_size'] + # hide original edge and instead track multiple bond sizes + attrs0['multiedge_inds'].append(attrs['ind']) + attrs0['multiedge_sizes'].append(attrs['edge_size']) + attrs0['spring_weight'] /= (attrs['edge_size'] + 1) + attrs0['edge_size'] = 0 def draw_tn( @@ -39,13 +46,22 @@ def draw_tn( k=None, iterations=200, initial_layout='spectral', + use_forceatlas2=1000, node_color=None, - outline_darkness=0.8, node_size=None, + node_shape='o', + node_outline_size=None, + node_outline_darkness=0.8, edge_color=None, edge_scale=1.0, edge_alpha=1 / 2, + multiedge_spread=0.1, + show_left_inds=True, + arrow_closeness=1.1, + arrow_length=0.1, label_color=None, + font_size=10, + font_size_inner=7, figsize=(6, 6), margin=None, xlims=None, @@ -70,10 +86,12 @@ def draw_tn( What color to use for ``highlight_inds`` nodes. highlight_tids_color : tuple[float], optional What color to use for ``highlight_tids`` nodes. - show_inds : {None, False, True, 'all'}, optional + show_inds : {None, False, True, 'all', 'bond-size'}, optional Explicitly turn on labels for each tensors indices. show_tags : {None, False, True}, optional Explicitly turn on labels for each tensors tags. + show_scalars : bool, optional + Whether to show scalar tensors (floating nodes with no edges). custom_colors : sequence of colors, optional Supply a custom sequence of colors to match the tags given in ``color``. @@ -91,25 +109,43 @@ def draw_tn( iterations : int, optional How many iterations to perform when when finding the best layout using node repulsion. Ramp this up if the graph is drawing messily. - initial_layout : {'spectral', 'kamada_kawai', 'circular', 'planar', + initial_layout : {'spectral', 'kamada_kawai', 'circular', 'planar', \\ 'random', 'shell', 'bipartite', ...}, optional The name of a networkx layout to use before iterating with the spring layout. Set ``iterations=0`` if you just want to use this layout only. + use_forceatlas2 : bool or int, optional + Whether to try and use ``forceatlas2`` (``fa2``) for the spring layout + relaxation instead of ``networkx``. If an integer, only try and use + beyond that many nodes (it can give messier results on smaller graphs). node_color : tuple[float], optional Default color of nodes. - outline_darkness : float, optional - Darkening of nodes outlines. - node_size : None + node_size : None or float, optional How big to draw the tensors. + node_outline_size : None or float, optional + The width of the border of each node. + node_outline_darkness : float, optional + Darkening of nodes outlines. edge_color : tuple[float], optional Default color of edges. edge_scale : float, optional How much to scale the width of the edges. edge_alpha : float, optional Set the alpha (opacity) of the drawn edges. + multiedge_spread : float, optional + How much to spread the lines of multi-edges. + show_left_inds : bool, optional + Whether to show ``tensor.left_inds`` as incoming arrows. + arrow_closeness : float, optional + How close to draw the arrow to its target. + arrow_length : float, optional + The size of the arrow with respect to the edge. label_color : tuple[float], optional Color to draw labels with. + font_size : int, optional + Font size for drawing tags and outer indices. + font_size_inner : int, optional + Font size for drawing inner indices. figsize : tuple of int The size of the drawing. margin : None or float, optional @@ -132,6 +168,7 @@ def draw_tn( import networkx as nx import matplotlib as mpl import matplotlib.pyplot as plt + import matplotlib.patches as patches from matplotlib.colors import to_rgb import math @@ -156,7 +193,8 @@ def draw_tn( # set the size of the nodes if node_size is None: node_size = 1000 / tn.num_tensors**0.7 - node_outline_size = min(3, node_size**0.5 / 5) + if node_outline_size is None: + node_outline_size = min(3, node_size**0.5 / 5) if label_color is None: label_color = mpl.rcParams['axes.labelcolor'] @@ -169,17 +207,24 @@ def draw_tn( edge_labels = dict() for ix, tids in tn.ind_map.items(): + # general information for this index edge_attrs = { 'color': (highlight_inds_color if ix in highlight_inds else edge_color), 'ind': ix, - 'edge_size': edge_scale * math.log2(tn.ind_size(ix)) + 'edge_size': edge_scale * math.log2(tn.ind_size(ix)), } + edge_attrs['multiedge_inds'] = [edge_attrs['ind']] + edge_attrs['multiedge_sizes'] = [edge_attrs['edge_size']] + edge_attrs['spring_weight'] = 1 / sum(t.ndim for t in tn._inds_get(ix)) + if len(tids) == 2: # standard edge _add_or_merge_edge(G, *tids, edge_attrs) if show_inds == 'all': edge_labels[tuple(tids)] = ix + elif show_inds == 'bond-size': + edge_labels[tuple(tids)] = tn.ind_size(ix) else: # hyper or outer edge - needs dummy 'node' shown with zero size hyperedges.append(ix) @@ -209,7 +254,7 @@ def draw_tn( color = highlight_tids_color G.nodes[tid]['color'] = color G.nodes[tid]['outline_color'] = tuple( - (1.0 if i == 3 else outline_darkness) * c + (1.0 if i == 3 else node_outline_darkness) * c for i, c in enumerate(color) ) if show_tags: @@ -227,12 +272,19 @@ def draw_tn( G.nodes[hix]['outline_color'] = (1.0, 1.0, 1.0, 1.0) if show_inds == 'all': node_labels[hix] = hix + elif show_inds == 'bond-size': + node_labels[hix] = tn.ind_size(hix) - if show_inds: + if show_inds == 'bond-size': + font_size = font_size_inner + for oix in tn.outer_inds(): + node_labels[oix] = tn.ind_size(oix) + elif show_inds: for oix in tn.outer_inds(): node_labels[oix] = oix - pos = _get_positions(tn, G, fix, initial_layout, k, iterations) + pos = _get_positions(tn, G, fix, initial_layout, + k, iterations, use_forceatlas2) if get == 'pos': return pos @@ -277,19 +329,87 @@ def draw_tn( alpha=edge_alpha, ax=ax, ) + + # draw multiedges + multiedge_centers = {} + for i, j, attrs in G.edges(data=True): + sizes = attrs['multiedge_sizes'] + multiplicity = len(sizes) + if multiplicity > 1: + rads = np.linspace( + multiplicity * -multiedge_spread, + multiplicity * +multiedge_spread, + multiplicity + ) + + xa, ya = pos[i] + xb, yb = pos[j] + xab, yab = (xa + xb) / 2., (ya + yb) / 2. + dx, dy = xb - xa, yb - ya + + inds = attrs['multiedge_inds'] + for sz, rad, ix in zip(sizes, rads, inds): + + # store the central point of the arc in case its needed by + # the arrow drawing functionality + cx, cy = xab + rad * dy * 0.5, yab - rad * dx * 0.5 + multiedge_centers[ix] = (cx, cy) + + ax.add_patch(patches.FancyArrowPatch( + (xa, ya), (xb, yb), + connectionstyle=patches.ConnectionStyle.Arc3(rad=rad), + alpha=edge_alpha, + linewidth=sz, + color=attrs['color'], + )) + nx.draw_networkx_nodes( G, pos, node_color=tuple(x[1]['color'] for x in G.nodes(data=True)), edgecolors=tuple(x[1]['outline_color'] for x in G.nodes(data=True)), node_size=tuple(x[1]['size'] for x in G.nodes(data=True)), linewidths=tuple(x[1]['outline_size'] for x in G.nodes(data=True)), + node_shape=node_shape, ax=ax, ) - if show_inds == 'all': + + # draw incomcing arrows for tensor left_inds + if show_left_inds: + for tid, t in tn.tensor_map.items(): + if t.left_inds is not None: + for ind in t.left_inds: + if ind in hyperedges: + tida = ind + else: + tida, = (x for x in tn.ind_map[ind] if x != tid) + tidb = tid + (xa, ya), (xb, yb) = pos[tida], pos[tidb] + + # arrow start and change + if ind in multiedge_centers: + x, y = multiedge_centers[ind] + else: + x = (xa + arrow_closeness * xb) / (1 + arrow_closeness) + y = (ya + arrow_closeness * yb) / (1 + arrow_closeness) + dx = (xb - xa) * arrow_length + dy = (yb - ya) * arrow_length + + ax.add_patch(patches.FancyArrow( + x, y, dx, dy, + width=0, # don't draw tail + length_includes_head=True, + head_width=(dx**2 + dy**2)**0.5, + head_length=(dx**2 + dy**2)**0.5, + color=edge_color, + alpha=edge_alpha, + fill=True, + )) + + if show_inds in {'all', 'bond-size'}: nx.draw_networkx_edge_labels( G, pos, edge_labels=edge_labels, - font_size=10, + font_size=font_size_inner, font_color=label_color, ax=ax, ) @@ -297,7 +417,7 @@ def draw_tn( nx.draw_networkx_labels( G, pos, labels=node_labels, - font_size=10, + font_size=font_size, font_color=label_color, ax=ax, ) @@ -444,7 +564,8 @@ def _massage_pos(pos, nangles=360, flatten=False): return dict(zip(pos, rxy0)) -def _get_positions(tn, G, fix, initial_layout, k, iterations): +def _get_positions(tn, G, fix, initial_layout, + k, iterations, use_forceatlas2): import networkx as nx if fix is None: @@ -489,8 +610,26 @@ def _get_positions(tn, G, fix, initial_layout, k, iterations): fixed = None # and then relax remaining using spring layout - pos = nx.spring_layout( - G, pos=pos0, fixed=fixed, k=k, iterations=iterations) + if iterations: + + if use_forceatlas2 is True: + use_forceatlas2 = 1 + elif use_forceatlas2 in (0, False): + use_forceatlas2 = float('inf') + + should_use_fa2 = ( + (fixed is None) and HAS_FA2 and (len(G) > use_forceatlas2) + ) + + if should_use_fa2: + from fa2 import ForceAtlas2 + pos = ForceAtlas2(verbose=False).forceatlas2_networkx_layout( + G, pos=pos0, iterations=iterations) + else: + pos = nx.spring_layout( + G, pos=pos0, fixed=fixed, k=k, iterations=iterations) + else: + pos = pos0 if not fix: # finally rotate them to cover a small vertical span diff --git a/quimb/tensor/optimize.py b/quimb/tensor/optimize.py index 2f4c08ed2..b42e13e14 100644 --- a/quimb/tensor/optimize.py +++ b/quimb/tensor/optimize.py @@ -2,6 +2,7 @@ automatically derive gradients for input to scipy optimizers. """ import re +import warnings import functools import importlib from collections.abc import Iterable @@ -57,6 +58,11 @@ def equivalent_complex_type(x): class Vectorizer: """Object for mapping a sequence of mixed real/complex n-dimensional arrays to a single numpy vector and back and forth. + + Parameters + ---------- + array : sequence of array + The set of arrays to map into a single real vector. """ def __init__(self, arrays): @@ -71,6 +77,9 @@ def __init__(self, arrays): self.pack(arrays) def pack(self, arrays, name='vector'): + """Take ``arrays`` and pack their values into attribute `.{name}`, by + default `.vector`. + """ # scipy's optimization routines require real, double data if not hasattr(self, name): @@ -121,37 +130,167 @@ def unpack(self, vector=None): return arrays -def parse_network_to_backend(tn, tags, constant_tags, to_constant): +_VARIABLE_TAG = "__VARIABLE{}__" +variable_finder = re.compile(r'__VARIABLE(\d+)__') + + +def _get_tensor_data(t): + """Simple function to extract tensor data. + """ + if isinstance(t, PTensor): + data = t.params + else: + data = t.data + + # jax doesn't like numpy.ndarray subclasses... + if isinstance(data, qarray): + data = data.A + + return data + + +def _parse_opt_in(tn, tags, shared_tags, to_constant): + """Parse a tensor network where tensors are assumed to be constant unless + tagged. + """ tn_ag = tn.copy() variables = [] - variable_tag = "__VARIABLE{}__" + # tags where each individual tensor should get a separate variable + individual_tags = tags - shared_tags + + # handle tagged tensors that are not shared + for t in tn_ag.select_tensors(individual_tags, 'any'): + # append the raw data but mark the corresponding tensor + # for reinsertion + data = _get_tensor_data(t) + variables.append(data) + t.add_tag(_VARIABLE_TAG.format(len(variables) - 1)) + + # handle shared tags + for tag in shared_tags: + + var_name = _VARIABLE_TAG.format(len(variables)) + test_data = None + + for t in tn_ag.select_tensors(tag): + data = _get_tensor_data(t) + + # detect that this tensor is already variable tagged and skip + # if it is + if any(variable_finder.match(tag) for tag in t.tags): + warnings.warn('TNOptimizer warning, tensor tagged with' + ' multiple `tags` or `shared_tags`.') + continue + + if test_data is None: + # create variable and store data + variables.append(data) + test_data = data + else: + # check that the shape of the variable's data matches the + # data of this new tensor + if test_data.shape != data.shape: + raise ValueError('TNOptimizer error, a `shared_tags` tag ' + 'covers tensors with different numbers of' + ' params.') + + # mark the corresponding tensor for reinsertion + t.add_tag(var_name) + + # iterate over tensors which *don't* have any of the given tags + for t in tn_ag.select_tensors(tags, which='!any'): + t.modify(apply=to_constant) + + return tn_ag, variables + + +def _parse_opt_out(tn, constant_tags, to_constant,): + """Parse a tensor network where tensors are assumed to be variables unless + tagged. + """ + tn_ag = tn.copy() + variables = [] for t in tn_ag: - # check if tensor has any of the constant tags + if t.tags & constant_tags: t.modify(apply=to_constant) continue - # if tags are specified only optimize those tagged - if tags and not (t.tags & tags): - t.modify(apply=to_constant) - continue + # append the raw data but mark the corresponding tensor + # for reinsertion + data = _get_tensor_data(t) + variables.append(data) + t.add_tag(_VARIABLE_TAG.format(len(variables) - 1)) - if isinstance(t, PTensor): - data = t.params - else: - data = t.data + return tn_ag, variables - # jax doesn't like numpy.ndarray subclasses... - if isinstance(data, qarray): - data = data.A - # append the raw data but mark the corresponding tensor for reinsertion - variables.append(data) - t.add_tag(variable_tag.format(len(variables) - 1)) +def parse_network_to_backend( + tn, + to_constant, + tags=None, + shared_tags=None, + constant_tags=None, +): + """ + Parse tensor network to: - return tn_ag, variables + - identify the dimension of the optimisation space and the initial + point of the optimisation from the current values in the tensor + network, + - add variable tags to individual tensors so that optimisation vector + values can be efficiently reinserted into the tensor network. + + There are two different modes: + + - 'opt in' : `tags` (and optionally `shared_tags`) are specified and + only these tensor tags will be optimised over. In this case + `constant_tags` is ignored if it is passed, + - 'opt out' : `tags` is not specified. In this case all tensors will be + optimised over, unless they have one of `constant_tags` tags. + + Parameters + ---------- + tn : TensorNetwork + The initial tensor network to parse. + to_constant : Callable + Function that fixes a tensor as constant. + tags : str, or sequence of str, optional + Set of opt-in tags to optimise. + shared_tags : str, or sequence of str, optional + Subset of opt-in tags to joint optimise i.e. all tensors with tag s in + shared_tags will correspond to the same optimisation variables. + constant_tags : str, or sequence of str, optional + Set of opt-out tags if `tags` not passed. + + Returns + ------- + tn_ag : TensorNetwork + Tensor network tagged for reinsertion of optimisation variable values. + variables : list + List of variables extracted from ``tn``. + """ + tags = tags_to_oset(tags) + shared_tags = tags_to_oset(shared_tags) + constant_tags = tags_to_oset(constant_tags) + + if tags | shared_tags: + # opt_in + if not (tags & shared_tags) == shared_tags: + tags = tags | shared_tags + warnings.warn('TNOptimizer warning, some `shared_tags` are missing' + ' from `tags`. Automatically adding these missing' + ' `shared_tags` to `tags`.') + if constant_tags: + warnings.warn('TNOptimizer warning, if `tags` or `shared_tags` are' + ' specified then `constant_tags` is ignored - ' + 'consider instead untagging those tensors.') + return _parse_opt_in(tn, tags, shared_tags, to_constant, ) + + # opt-out + return _parse_opt_out(tn, constant_tags, to_constant, ) def constant_t(t, to_constant): @@ -409,9 +548,6 @@ def value_and_grad(self, arrays): return self._value_and_grad_seq(arrays) -variable_finder = re.compile(r'__VARIABLE(\d+)__') - - def inject_(arrays, tn): for t in tn: for tag in t.tags: @@ -728,6 +864,9 @@ class TNOptimizer: are assumed to be simple options that don't need conversion). tags : str, or sequence of str, optional If supplied, only optimize tensors with any of these tags. + shared_tags : str, or sequence of str, optional + If supplied, each tag in ``shared_tags`` corresponds to a group of + tensors to be optimized together. constant_tags : str, or sequence of str, optional If supplied, skip optimizing tensors with any of these tags. loss_target : float, optional @@ -763,6 +902,7 @@ def __init__( loss_constants=None, loss_kwargs=None, tags=None, + shared_tags=None, constant_tags=None, loss_target=None, optimizer='L-BFGS-B', @@ -773,8 +913,9 @@ def __init__( **backend_opts ): self.progbar = progbar - self.tags = tags_to_oset(tags) - self.constant_tags = tags_to_oset(constant_tags) + self.tags = tags + self.shared_tags = shared_tags + self.constant_tags = constant_tags if autodiff_backend.upper() == 'AUTO': autodiff_backend = _DEFAULT_BACKEND @@ -812,7 +953,12 @@ def __init__( # work out which tensors to optimize and get the underlying data self.tn_opt, self.variables = parse_network_to_backend( - tn, self.tags, self.constant_tags, self.handler.to_constant) + tn, + tags=self.tags, + shared_tags=self.shared_tags, + constant_tags=self.constant_tags, + to_constant=self.handler.to_constant + ) # first we wrap the function to convert from array args to TN arg # (i.e. to autodiff library compatible form) @@ -871,25 +1017,67 @@ def nevals(self): @property def optimizer(self): + """The underlying optimizer that works with the vectorized functions. + """ return self._optimizer @optimizer.setter def optimizer(self, x): + if isinstance(x, str): + x = x.lower() self._optimizer = x if self.optimizer in _STOC_GRAD_METHODS: self._method = _STOC_GRAD_METHODS[self.optimizer]() else: self._method = self.optimizer - def inject_res_vector_and_return_tn(self): - arrays = self.vectorizer.unpack() + def get_tn_opt(self): + """Extract the optimized tensor network, this is a three part process: + + 1. inject the current optimized vector into the target tensor + network, + 2. run it through ``norm_fn``, + 3. drop any tags used to identify variables. + + Returns + ------- + tn_opt : TensorNetwork + """ + arrays = tuple(map(self.handler.to_constant, self.vectorizer.unpack())) inject_(arrays, self.tn_opt) tn = self.norm_fn(self.tn_opt.copy()) tn.drop_tags(t for t in tn.tags if variable_finder.match(t)) - tn.apply_to_arrays(to_numpy) + + for t in tn: + if isinstance(t, PTensor): + t.params = to_numpy(t.params) + else: + t.modify(data=to_numpy(t.data)) + return tn def optimize(self, n, tol=None, **options): + """Run the optimizer for ``n`` function evaluations, using + :func:`scipy.optimize.minimize` as the driver for the vectorized + computation. + + Parameters + ---------- + n : int + Notionally the maximum number of iterations for the optimizer, note + that depending on the optimizer being used, this may correspond to + number of function evaluations rather than just iterations. + tol : None or float, optional + Tolerance for convergence, note that various more specific + tolerances can usually be supplied to ``options``, depending on + the optimizer being used. + options + Supplied to :func:`scipy.optimize.minimize`. + + Returns + ------- + tn_opt : TensorNetwork + """ from scipy.optimize import minimize try: @@ -921,9 +1109,28 @@ def callback(_): finally: pbar.close() - return self.inject_res_vector_and_return_tn() + return self.get_tn_opt() def optimize_basinhopping(self, n, nhop, temperature=1.0, **options): + """Run the optimizer for using :func:`scipy.optimize.basinhopping` + as the driver for the vectorized computation. This performs ``nhop`` + local optimization each with ``n`` iterations. + + Parameters + ---------- + n : int + Number of iterations per local optimization. + nhop : int + Number of local optimizations to hop between. + temperature : float, optional + H + options + Supplied to the inner :func:`scipy.optimize.minimize` call. + + Returns + ------- + tn_opt : TensorNetwork + """ from scipy.optimize import basinhopping try: @@ -949,7 +1156,7 @@ def inner_callback(_): niter=nhop, minimizer_kwargs=dict( jac=True, - method=self.optimizer, + method=self._method, bounds=self.bounds, callback=inner_callback, options=dict(maxiter=n, **options) @@ -964,4 +1171,4 @@ def inner_callback(_): finally: pbar.close() - return self.inject_res_vector_and_return_tn() + return self.get_tn_opt() diff --git a/quimb/tensor/tensor_1d.py b/quimb/tensor/tensor_1d.py index cf796c41e..eb6e8c947 100644 --- a/quimb/tensor/tensor_1d.py +++ b/quimb/tensor/tensor_1d.py @@ -514,8 +514,6 @@ def L(self): def nsites(self): """The number of sites. """ - import warnings - warnings.warn('`tn.nsites` is deprecated in favor of `tn.L`.') return self._L def gen_site_coos(self): @@ -2243,8 +2241,8 @@ def bipartite_schmidt_state(self, sz_a, get='ket', cur_orthog=None): - 'ket': vector form as tensor. - 'rho': density operator form, i.e. vector outer product - - 'ket-dense': like 'ket' but return ``numpy.matrix``. - - 'rho-dense': like 'rho' but return ``numpy.matrix``. + - 'ket-dense': like 'ket' but return ``qarray``. + - 'rho-dense': like 'rho' but return ``qarray``. cur_orthog : int, optional If given, take as the current orthogonality center so as to diff --git a/quimb/tensor/tensor_2d.py b/quimb/tensor/tensor_2d.py index acce2b2b4..83b6f9837 100644 --- a/quimb/tensor/tensor_2d.py +++ b/quimb/tensor/tensor_2d.py @@ -24,8 +24,10 @@ TensorNetwork, tensor_contract, oset_union, + bonds_size, ) from .tensor_1d import maybe_factor_gate_into_tensor, rand_padder +from . import decomp def manhattan_distance(coo_a, coo_b): @@ -93,6 +95,124 @@ def gen_2d_bonds(Lx, Ly, steppers, coo_filter=None): yield (i, j), (i2, j2) +class Rotator2D: + """Object for rotating coordinates and various contraction functions so + that the core algorithms only have to written once, but nor does the actual + TN have to be modified. + """ + + def __init__(self, tn, xrange, yrange, from_which): + check_opt('from_which', from_which, {'bottom', 'top', 'left', 'right'}) + + if xrange is None: + xrange = (0, tn.Lx - 1) + if yrange is None: + yrange = (0, tn.Ly - 1) + + self.tn = tn + self.xrange = xrange + self.yrange = yrange + self.from_which = from_which + + if self.from_which in {'bottom', 'top'}: + # -> no rotation needed + self.imin, self.imax = sorted(xrange) + self.jmin, self.jmax = sorted(yrange) + self.row_tag = tn.row_tag + self.col_tag = tn.col_tag + self.site_tag = tn.site_tag + else: # {'left', 'right'} + # -> rotate 90deg + self.imin, self.imax = sorted(yrange) + self.jmin, self.jmax = sorted(xrange) + self.col_tag = tn.row_tag + self.row_tag = tn.col_tag + self.site_tag = lambda i, j: tn.site_tag(j, i) + + if self.from_which in {'bottom', 'left'}: + # -> sweeps are increasing + self.vertical_sweep = range(self.imin, self.imax + 1, +1) + self.istep = +1 + else: # {'top', 'right'} + # -> sweeps are decreasing + self.vertical_sweep = range(self.imax, self.imin - 1, -1) + self.istep = -1 + + def get_sweep_directions(self, compress_sweep=None): + """Get the default compress and canonize sweep directions. + """ + if compress_sweep is None: + compress_sweep = { + 'right': 'down', + 'left': 'up', + 'top': 'right', + 'bottom': 'left', + }[self.from_which] + canonize_sweep = { + 'up': 'down', + 'down': 'up', + 'left': 'right', + 'right': 'left', + }[compress_sweep] + return compress_sweep, canonize_sweep + + def get_sweep_fns(self, compress_sweep): + """Get functions that compress or canonize a single rotated, 'row'. + """ + comp_sweep, canz_sweep = self.get_sweep_directions(compress_sweep) + + if self.from_which in {'bottom', 'top'}: + canonize_fn = functools.partial( + self.tn.canonize_row, + sweep=canz_sweep, yrange=self.yrange) + compress_fn = functools.partial( + self.tn.compress_row, + sweep=comp_sweep, yrange=self.yrange) + else: # {'left', 'right'} + canonize_fn = functools.partial( + self.tn.canonize_column, + sweep=canz_sweep, xrange=self.xrange) + compress_fn = functools.partial( + self.tn.compress_column, + sweep=comp_sweep, xrange=self.xrange) + + return compress_fn, canonize_fn + + def get_contract_boundary_fn(self): + """Get the function that contracts the boundary in by a single step. + """ + if self.from_which in {'bottom', 'top'}: + + def fn(i, inext, **kwargs): + return self.tn.contract_boundary_from_( + xrange=(i, inext), yrange=self.yrange, + from_which=self.from_which, **kwargs) + + else: # {'left', 'right'} + + def fn(i, inext, **kwargs): + return self.tn.contract_boundary_from_( + yrange=(i, inext), xrange=self.xrange, + from_which=self.from_which, **kwargs) + + return fn + + def get_opposite_env_fn(self): + """Get the function and location label for contracting boundaries in + the opposite direction to main sweep. + """ + return { + 'bottom': (functools.partial(self.tn.compute_top_environments, + yrange=self.yrange), 'top'), + 'top': (functools.partial(self.tn.compute_bottom_environments, + yrange=self.yrange), 'bottom'), + 'left': (functools.partial(self.tn.compute_right_environments, + xrange=self.xrange), 'right'), + 'right': (functools.partial(self.tn.compute_left_environments, + xrange=self.xrange), 'left'), + }[self.from_which] + + class TensorNetwork2D(TensorNetwork): r"""Mixin class for tensor networks with a square lattice two-dimensional structure, indexed by ``[{row},{column}]`` so that:: @@ -165,6 +285,12 @@ def Ly(self): """ return self._Ly + @property + def nsites(self): + """The total number of sites. + """ + return self._Lx * self._Ly + @property def site_tag_id(self): """The string specifier for tagging each site of this 2D TN. @@ -672,34 +798,36 @@ def compress_column( self.compress_between((i, j), (i - 1, j), max_bond=max_bond, cutoff=cutoff, **compress_opts) - def _contract_boundary_from_bottom_single( + def _contract_boundary_single( self, xrange, yrange, + from_which, max_bond=None, cutoff=1e-10, canonize=True, - compress_sweep='left', + compress_sweep=None, layer_tag=None, compress_opts=None, ): - canonize_sweep = { - 'left': 'right', - 'right': 'left', - }[compress_sweep] + # rotate coordinates and sweeps rather than actual TN + r2d = Rotator2D(self, xrange, yrange, from_which) + jmin, jmax, istep = r2d.jmin, r2d.jmax, r2d.istep + site_tag = r2d.site_tag + compress_fn, canonize_fn = r2d.get_sweep_fns(compress_sweep) - for i in range(min(xrange), max(xrange)): + for i in r2d.vertical_sweep[:-1]: # # │ │ │ │ │ - # ●──●──●──●──● │ │ │ │ │ + # ●──●──●──●──● i+1 │ │ │ │ │ # │ │ │ │ │ --> ●══●══●══●══● - # ●──●──●──●──● + # ●──●──●──●──● i # - for j in range(min(yrange), max(yrange) + 1): - tag1, tag2 = self.site_tag(i, j), self.site_tag(i + 1, j) + for j in range(jmin, jmax + 1): + tag1, tag2 = site_tag(i, j), site_tag(i + istep, j) if layer_tag is None: - # contract any tensors with coordinates (i + 1, j), (i, j) + # contract *any* tensors with pair of coordinates self.contract_((tag1, tag2), which='any') else: # contract a specific pair (i.e. only one 'inner' layer) @@ -710,29 +838,35 @@ def _contract_boundary_from_bottom_single( # │ │ │ │ │ # ●══●══<══<══< # - self.canonize_row(i, sweep=canonize_sweep, yrange=yrange) + canonize_fn(i) # # │ │ │ │ │ --> │ │ │ │ │ --> │ │ │ │ │ # >──●══●══●══● --> >──>──●══●══● --> >──>──>──●══● # . . --> . . --> . . # - self.compress_row(i, sweep=compress_sweep, max_bond=max_bond, - cutoff=cutoff, yrange=yrange, - compress_opts=compress_opts) + compress_fn(i, max_bond=max_bond, cutoff=cutoff, + compress_opts=compress_opts) - def _contract_boundary_from_bottom_multi( + def _contract_boundary_multi( self, xrange, yrange, layer_tags, + from_which, max_bond=None, cutoff=1e-10, canonize=True, - compress_sweep='left', + compress_sweep=None, compress_opts=None, ): - for i in range(min(xrange), max(xrange)): + # rotate coordinates and sweeps rather than actual TN + r2d = Rotator2D(self, xrange, yrange, from_which) + jmin, jmax, istep = r2d.jmin, r2d.jmax, r2d.istep + site_tag = r2d.site_tag + contract_single = r2d.get_contract_boundary_fn() + + for i in r2d.vertical_sweep[:-1]: # make sure the exterior sites are a single tensor # # │ ││ ││ ││ ││ │ │ ││ ││ ││ ││ │ (for two layer tags) @@ -740,44 +874,300 @@ def _contract_boundary_from_bottom_multi( # │ ││ ││ ││ ││ │ ==> ╲│ ╲│ ╲│ ╲│ ╲│ # ●─○●─○●─○●─○●─○ ●══●══●══●══● # - for j in range(min(yrange), max(yrange) + 1): - self ^= (i, j) + for j in range(jmin, jmax + 1): + self ^= site_tag(i, j) for tag in layer_tags: # contract interior sites from layer ``tag`` # - # │ ││ ││ ││ ││ │ (first contraction if there are two tags) + # │ ││ ││ ││ ││ │ (first contraction if two layer tags) # │ ○──○──○──○──○ # │╱ │╱ │╱ │╱ │╱ # ●══<══<══<══< # - self._contract_boundary_from_bottom_single( - xrange=(i, i + 1), yrange=yrange, canonize=canonize, - compress_sweep=compress_sweep, layer_tag=tag, + contract_single( + i, i + istep, layer_tag=tag, max_bond=max_bond, cutoff=cutoff, + canonize=canonize, compress_sweep=compress_sweep, compress_opts=compress_opts) # so we can still uniqely identify 'inner' tensors, drop inner # site tag merged into outer tensor for all but last tensor - for j in range(min(yrange), max(yrange) + 1): - inner_tag = self.site_tag(i + 1, j) + for j in range(jmin, jmax + 1): + inner_tag = site_tag(i + istep, j) if len(self.tag_map[inner_tag]) > 1: - self[i, j].drop_tags(inner_tag) + self[site_tag(i, j)].drop_tags(inner_tag) + + def _contract_boundary_full_bond( + self, + xrange, + yrange, + from_which, + max_bond, + cutoff=0.0, + method='eigh', + renorm=False, + optimize='auto-hq', + opposite_envs=None, + contract_boundary_opts=None, + ): + """Contract the boundary of this 2D TN using the 'full bond' + environment information obtained from a boundary contraction in the + opposite direction. + + Parameters + ---------- + xrange : (int, int) or None, optional + The range of rows to contract and compress. + yrange : (int, int) + The range of columns to contract and compress. + from_which : {'bottom', 'left', 'top', 'right'} + Which direction to contract the rectangular patch from. + max_bond : int + The maximum boundary dimension, AKA 'chi'. By default used for the + opposite direction environment contraction as well. + cutoff : float, optional + Cut-off value to used to truncate singular values in the boundary + contraction - only for the opposite direction environment + contraction. + method : {'eigh', 'eig', 'svd', 'biorthog'}, optional + Which similarity decomposition method to use to compress the full + bond environment. + renorm : bool, optional + Whether to renormalize the isometric projection or not. + optimize : str or PathOptimize, optimize + Contraction optimizer to use for the exact contractions. + opposite_envs : dict, optional + If supplied, the opposite environments will be fetched or lazily + computed into this dict depending on whether they are missing. + contract_boundary_opts + Other options given to the opposite direction environment + contraction. + """ + contract_boundary_opts = ensure_dict(contract_boundary_opts) + contract_boundary_opts.setdefault('max_bond', max_bond) + contract_boundary_opts.setdefault('cutoff', cutoff) + + # rotate coordinates and sweeps rather than actual TN + r2d = Rotator2D(self, xrange, yrange, from_which) + jmin, jmax, istep = r2d.jmin, r2d.jmax, r2d.istep + col_tag, row_tag, site_tag = r2d.col_tag, r2d.row_tag, r2d.site_tag + opposite_env_fn, env_location = r2d.get_opposite_env_fn() + + if opposite_envs is None: + # storage for the top down environments - compute lazily so that a + # dict can be supplied *with or without* them precomputed + opposite_envs = {} + + # now contract in the other direction + for i in r2d.vertical_sweep[:-1]: + + # contract inwards, no compression + for j in range(jmin, jmax + 1): + # + # j j+1 ... + # │ │ │ │ │ │ + # =●===●===●───●───●───●─ i + 1 + # ... \ │ │ │ │ ... + # -> ●━━━●━━━●━━━●━ i + # + self.contract_([site_tag(i, j), + site_tag(i + istep, j)], which='any') + + # form strip of current row and approx top environment + # the canonicalization 'compresses' outer bonds + # + # ●━━━●━━━●━━━●━━━●━━━● i + 2 + # │ │ │ │ │ │ + # >--->===●===<===<---< i + 1 + # (jmax - jmin) // 2 + # + row = self.select(row_tag(i)) + row.canonize_around_(col_tag((jmax - jmin) // 2)) + + try: + env = opposite_envs[env_location, i + istep] + except KeyError: + # lazy computation of top environements (computes all at once) + # + # ●━━━●━━━●━━━●━━━●━━━●━ i + 1 + # │ │ │ │ │ │ │ ... + # v ●───●───●───●───●───●─ i + # │ │ │ │ │ │ + # ... + # + opposite_envs.update(opposite_env_fn(**contract_boundary_opts)) + env = opposite_envs[env_location, i + istep] + + ladder = row & env + + # for each pair to compress, form left and right envs from strip + # + # ╭─●━━━●─╮ + # lenvs[j] ● │ │ ● renvs[j + 1] + # ╰─●===●─╯ + # j j+1 + # + lenvs = {jmin + 1: ladder.select(col_tag(jmin))} + for j in range(jmin + 2, jmax): + lenvs[j] = ladder.select(col_tag(j - 1)) @ lenvs[j - 1] + + renvs = {jmax - 1: ladder.select(col_tag(jmax))} + for j in range(jmax - 2, jmin, -1): + renvs[j] = ladder.select(col_tag(j + 1)) @ renvs[j + 1] + + for j in range(jmin, jmax): + if bonds_size(self[site_tag(i, j)], + self[site_tag(i, j + 1)]) <= max_bond: + # no need to form env operator and compress + continue + + # for each compression pair make single loop - the bond env + # + # j j+1 + # ╭─●━━━●─╮ + # ● │ │ ● + # ╰─● ●─╯ + # lcut│ │rcut + # + tn_be = TensorNetwork([]) + if j in lenvs: + tn_be &= lenvs[j] + tn_be &= ladder.select_any([col_tag(j), col_tag(j + 1)]) + if j + 1 in renvs: + tn_be &= renvs[j + 1] + + lcut = rand_uuid() + rcut = rand_uuid() + tn_be.cut_between(site_tag(i, j), site_tag(i, j + 1), + left_ind=lcut, right_ind=rcut) + + # form dense environment and find symmetric compressors + E = tn_be.to_dense([rcut], [lcut], optimize=optimize) + + Cl, Cr = decomp.similarity_compress( + E, max_bond, method=method, renorm=renorm) + + # insert compressors back in base TN + # + # j j+1 + # ━●━━━━━━━━●━ i+1 + # │ │ + # =●=Cl──Cr=●= i + # <-- --> + # + self.insert_gauge( + Cr, [site_tag(i, j)], [site_tag(i, j + 1)], Cl) + + def contract_boundary_from( + self, + xrange, + yrange, + from_which, + max_bond=None, + *, + cutoff=1e-10, + canonize=True, + mode='mps', + layer_tags=None, + compress_sweep=None, + compress_opts=None, + inplace=False, + **contract_boundary_opts, + ): + """Unified entrypoint for contracting any rectangular patch of tensors + from any direction, with any boundary method. + """ + check_opt('mode', mode, {'mps', 'full-bond'}) + + tn = self if inplace else self.copy() + + # universal options + contract_boundary_opts["xrange"] = xrange + contract_boundary_opts["yrange"] = yrange + contract_boundary_opts["from_which"] = from_which + contract_boundary_opts["max_bond"] = max_bond + + if mode == 'full-bond': + tn._contract_boundary_full_bond(**contract_boundary_opts) + return tn + + # mps mode options + contract_boundary_opts["cutoff"] = cutoff + contract_boundary_opts["canonize"] = canonize + contract_boundary_opts["compress_sweep"] = compress_sweep + contract_boundary_opts["compress_opts"] = compress_opts + + if layer_tags is None: + tn._contract_boundary_single(**contract_boundary_opts) + else: + contract_boundary_opts['layer_tags'] = layer_tags + tn._contract_boundary_multi(**contract_boundary_opts) + + return tn + + contract_boundary_from_ = functools.partialmethod( + contract_boundary_from, inplace=True) def contract_boundary_from_bottom( self, xrange, yrange=None, max_bond=None, + *, cutoff=1e-10, canonize=True, - compress_sweep='left', + mode='mps', layer_tags=None, - inplace=False, + compress_sweep='left', compress_opts=None, + inplace=False, + **contract_boundary_opts, ): - """Contract a 2D tensor network inwards from the bottom, canonizing and - compressing (left to right) along the way. + r"""Contract a 2D tensor network inwards from the bottom, canonizing + and compressing (left to right) along the way. If + ``layer_tags is None`` this looks like: + + a) contract + + │ │ │ │ │ + ●──●──●──●──● │ │ │ │ │ + │ │ │ │ │ --> ●══●══●══●══● + ●──●──●──●──● + + b) optionally canonicalize + + │ │ │ │ │ + ●══●══<══<══< + + c) compress in opposite direction + + │ │ │ │ │ --> │ │ │ │ │ --> │ │ │ │ │ + >──●══●══●══● --> >──>──●══●══● --> >──>──>──●══● + . . --> . . --> . . + + If ``layer_tags`` is specified, each then each layer is contracted in + and compressed separately, resulting generally in a lower memory + scaling. For two layer tags this looks like: + + a) first flatten the outer boundary only + + │ ││ ││ ││ ││ │ │ ││ ││ ││ ││ │ + ●─○●─○●─○●─○●─○ ●─○●─○●─○●─○●─○ + │ ││ ││ ││ ││ │ ==> ╲│ ╲│ ╲│ ╲│ ╲│ + ●─○●─○●─○●─○●─○ ●══●══●══●══● + + b) contract and compress a single layer only + + │ ││ ││ ││ ││ │ + │ ○──○──○──○──○ + │╱ │╱ │╱ │╱ │╱ + ●══<══<══<══< + + c) contract and compress the next layer + + ╲│ ╲│ ╲│ ╲│ ╲│ + >══>══>══>══● Parameters ---------- @@ -795,150 +1185,105 @@ def contract_boundary_from_bottom( contraction. canonize : bool, optional Whether to sweep one way with canonization before compressing. - compress_sweep : {'left', 'right'}, optional - Which way to perform the compression sweep, which has an effect on - which tensors end up being canonized. + mode : {'mps', 'full-bond'}, optional + How to perform the compression on the boundary. layer_tags : None or sequence[str], optional If ``None``, all tensors at each coordinate pair ``[(i, j), (i + 1, j)]`` will be first contracted. If specified, then the outer tensor at ``(i, j)`` will be contracted with the tensor specified by ``[(i + 1, j), layer_tag]``, for each ``layer_tag`` in ``layer_tags``. - inplace : bool, optional - Whether to perform the contraction inplace or not. + compress_sweep : {'left', 'right'}, optional + Which way to perform the compression sweep, which has an effect on + which tensors end up being canonized. compress_opts : None or dict, optional Supplied to :meth:`~quimb.tensor.tensor_core.TensorNetwork.compress_between`. + inplace : bool, optional + Whether to perform the contraction inplace or not. See Also -------- contract_boundary_from_top, contract_boundary_from_left, contract_boundary_from_right """ - tn = self if inplace else self.copy() - - if yrange is None: - yrange = (0, self.Ly - 1) - - if layer_tags is None: - tn._contract_boundary_from_bottom_single( - xrange, yrange, canonize=canonize, max_bond=max_bond, - cutoff=cutoff, compress_sweep=compress_sweep, - compress_opts=compress_opts) - else: - tn._contract_boundary_from_bottom_multi( - xrange, yrange, layer_tags, canonize=canonize, - max_bond=max_bond, cutoff=cutoff, - compress_sweep=compress_sweep, compress_opts=compress_opts) - - return tn + return self.contract_boundary_from( + xrange=xrange, + yrange=yrange, + from_which="bottom", + max_bond=max_bond, + cutoff=cutoff, + canonize=canonize, + mode=mode, + layer_tags=layer_tags, + compress_sweep=compress_sweep, + compress_opts=compress_opts, + inplace=inplace, + **contract_boundary_opts, + ) contract_boundary_from_bottom_ = functools.partialmethod( contract_boundary_from_bottom, inplace=True) - def _contract_boundary_from_top_single( + def contract_boundary_from_top( self, xrange, - yrange, + yrange=None, max_bond=None, + *, cutoff=1e-10, canonize=True, + mode='mps', + layer_tags=None, + inplace=False, compress_sweep='right', - layer_tag=None, compress_opts=None, + **contract_boundary_opts, ): - canonize_sweep = { - 'left': 'right', - 'right': 'left', - }[compress_sweep] + r"""Contract a 2D tensor network inwards from the top, canonizing and + compressing (right to left) along the way. If + ``layer_tags is None`` this looks like: - for i in range(max(xrange), min(xrange), -1): - # - # ●──●──●──●──● - # | | | | | --> ●══●══●══●══● - # ●──●──●──●──● | | | | | - # | | | | | - # - for j in range(min(yrange), max(yrange) + 1): - tag1, tag2 = self.site_tag(i, j), self.site_tag(i - 1, j) - if layer_tag is None: - # contract any tensors with coordinates (i - 1, j), (i, j) - self.contract_((tag1, tag2), which='any') - else: - # contract a specific pair - self.contract_between(tag1, (tag2, layer_tag)) - if canonize: - # - # ●══●══<══<══< - # | | | | | - # - self.canonize_row(i, sweep=canonize_sweep, yrange=yrange) - # - # >──●══●══●══● --> >──>──●══●══● --> >──>──>──●══● - # | | | | | --> | | | | | --> | | | | | - # . . --> . . --> . . - # - self.compress_row(i, sweep=compress_sweep, max_bond=max_bond, - cutoff=cutoff, yrange=yrange, - compress_opts=compress_opts) + a) contract - def _contract_boundary_from_top_multi( - self, - xrange, - yrange, - layer_tags, - max_bond=None, - cutoff=1e-10, - canonize=True, - compress_sweep='left', - compress_opts=None, - ): - for i in range(max(xrange), min(xrange), -1): - # make sure the exterior sites are a single tensor - # - # ●─○●─○●─○●─○●─○ ●══●══●══●══● - # │ ││ ││ ││ ││ │ ==> ╱│ ╱│ ╱│ ╱│ ╱│ - # ●─○●─○●─○●─○●─○ ●─○●─○●─○●─○●─○ - # │ ││ ││ ││ ││ │ │ ││ ││ ││ ││ │ (for two layer tags) - # - for j in range(min(yrange), max(yrange) + 1): - self ^= (i, j) + ●──●──●──●──● + | | | | | --> ●══●══●══●══● + ●──●──●──●──● | | | | | + | | | | | - for tag in layer_tags: - # contract interior sites from layer ``tag`` - # - # ●══<══<══<══< - # │╲ │╲ │╲ │╲ │╲ - # │ ○──○──○──○──○ - # │ ││ ││ ││ ││ │ (first contraction if there are two tags) - # - self._contract_boundary_from_top_single( - xrange=(i, i - 1), yrange=yrange, canonize=canonize, - compress_sweep=compress_sweep, layer_tag=tag, - max_bond=max_bond, cutoff=cutoff, - compress_opts=compress_opts) + b) optionally canonicalize - # so we can still uniqely identify 'inner' tensors, drop inner - # site tag merged into outer tensor for all but last tensor - for j in range(min(yrange), max(yrange) + 1): - inner_tag = self.site_tag(i - 1, j) - if len(self.tag_map[inner_tag]) > 1: - self[i, j].drop_tags(inner_tag) + ●══●══<══<══< + | | | | | - def contract_boundary_from_top( - self, - xrange, - yrange=None, - max_bond=None, - cutoff=1e-10, - canonize=True, - compress_sweep='right', - layer_tags=None, - inplace=False, - compress_opts=None, - ): - """Contract a 2D tensor network inwards from the top, canonizing and - compressing (left to right) along the way. + c) compress in opposite direction + + >──●══●══●══● --> >──>──●══●══● --> >──>──>──●══● + | | | | | --> | | | | | --> | | | | | + . . --> . . --> . . + + If ``layer_tags`` is specified, each then each layer is contracted in + and compressed separately, resulting generally in a lower memory + scaling. For two layer tags this looks like: + + a) first flatten the outer boundary only + + ●─○●─○●─○●─○●─○ ●══●══●══●══● + │ ││ ││ ││ ││ │ ==> ╱│ ╱│ ╱│ ╱│ ╱│ + ●─○●─○●─○●─○●─○ ●─○●─○●─○●─○●─○ + │ ││ ││ ││ ││ │ │ ││ ││ ││ ││ │ + + b) contract and compress a single layer only + + ●══<══<══<══< + │╲ │╲ │╲ │╲ │╲ + │ ○──○──○──○──○ + │ ││ ││ ││ ││ │ + + c) contract and compress the next layer + + ●══●══●══●══● + ╱│ ╱│ ╱│ ╱│ ╱│ Parameters ---------- @@ -956,162 +1301,122 @@ def contract_boundary_from_top( contraction. canonize : bool, optional Whether to sweep one way with canonization before compressing. - compress_sweep : {'right', 'left'}, optional - Which way to perform the compression sweep, which has an effect on - which tensors end up being canonized. + mode : {'mps', 'full-bond'}, optional + How to perform the compression on the boundary. layer_tags : None or str, optional If ``None``, all tensors at each coordinate pair ``[(i, j), (i - 1, j)]`` will be first contracted. If specified, then the outer tensor at ``(i, j)`` will be contracted with the tensor specified by ``[(i - 1, j), layer_tag]``, for each ``layer_tag`` in ``layer_tags``. - inplace : bool, optional - Whether to perform the contraction inplace or not. + compress_sweep : {'right', 'left'}, optional + Which way to perform the compression sweep, which has an effect on + which tensors end up being canonized. compress_opts : None or dict, optional Supplied to :meth:`~quimb.tensor.tensor_core.TensorNetwork.compress_between`. + inplace : bool, optional + Whether to perform the contraction inplace or not. See Also -------- contract_boundary_from_bottom, contract_boundary_from_left, contract_boundary_from_right """ - tn = self if inplace else self.copy() - - if yrange is None: - yrange = (0, self.Ly - 1) - - if layer_tags is None: - tn._contract_boundary_from_top_single( - xrange, yrange, canonize=canonize, max_bond=max_bond, - cutoff=cutoff, compress_sweep=compress_sweep, - compress_opts=compress_opts) - else: - tn._contract_boundary_from_top_multi( - xrange, yrange, layer_tags, canonize=canonize, - max_bond=max_bond, cutoff=cutoff, - compress_sweep=compress_sweep, compress_opts=compress_opts) - - return tn + return self.contract_boundary_from( + xrange=xrange, + yrange=yrange, + from_which="top", + max_bond=max_bond, + cutoff=cutoff, + canonize=canonize, + mode=mode, + layer_tags=layer_tags, + compress_sweep=compress_sweep, + compress_opts=compress_opts, + inplace=inplace, + **contract_boundary_opts, + ) contract_boundary_from_top_ = functools.partialmethod( contract_boundary_from_top, inplace=True) - def _contract_boundary_from_left_single( - self, - yrange, - xrange, - max_bond=None, - cutoff=1e-10, - canonize=True, - compress_sweep='up', - layer_tag=None, - compress_opts=None, - ): - canonize_sweep = { - 'up': 'down', - 'down': 'up', - }[compress_sweep] - - for j in range(min(yrange), max(yrange)): - # - # ●──●── ●── - # │ │ ║ - # ●──●── ==> ●── - # │ │ ║ - # ●──●── ●── - # - for i in range(min(xrange), max(xrange) + 1): - tag1, tag2 = self.site_tag(i, j), self.site_tag(i, j + 1) - if layer_tag is None: - # contract any tensors with coordinates (i, j), (i, j + 1) - self.contract_((tag1, tag2), which='any') - else: - # contract a specific pair - self.contract_between(tag1, (tag2, layer_tag)) - if canonize: - # - # ●── v── - # ║ ║ - # ●── ==> v── - # ║ ║ - # ●── ●── - # - self.canonize_column(j, sweep=canonize_sweep, xrange=xrange) - # - # v── ●── - # ║ │ - # v── ==> ^── - # ║ │ - # ●── ^── - # - self.compress_column(j, sweep=compress_sweep, max_bond=max_bond, - cutoff=cutoff, xrange=xrange, - compress_opts=compress_opts) - - def _contract_boundary_from_left_multi( - self, - yrange, - xrange, - layer_tags, - max_bond=None, - cutoff=1e-10, - canonize=True, - compress_sweep='up', - compress_opts=None, - ): - for j in range(min(yrange), max(yrange)): - # make sure the exterior sites are a single tensor - # - # ○──○── ●──○── - # │╲ │╲ │╲ │╲ (for two layer tags) - # ●─○──○── ╰─●──○── - # ╲│╲╲│╲ ==> │╲╲│╲ - # ●─○──○── ╰─●──○── - # ╲│ ╲│ │ ╲│ - # ●──●── ╰──●── - # - for i in range(min(xrange), max(xrange) + 1): - self ^= (i, j) - - for tag in layer_tags: - # contract interior sites from layer ``tag`` - # - # ○── - # ╱╱ ╲ (first contraction if there are two tags) - # ●─── ○── - # ╲ ╱╱ ╲ - # ^─── ○── - # ╲ ╱╱ - # ^───── - # - self._contract_boundary_from_left_single( - yrange=(j, j + 1), xrange=xrange, canonize=canonize, - compress_sweep=compress_sweep, layer_tag=tag, - max_bond=max_bond, cutoff=cutoff, - compress_opts=compress_opts) - - # so we can still uniqely identify 'inner' tensors, drop inner - # site tag merged into outer tensor for all but last tensor - for i in range(min(xrange), max(xrange) + 1): - inner_tag = self.site_tag(i, j + 1) - if len(self.tag_map[inner_tag]) > 1: - self[i, j].drop_tags(inner_tag) - def contract_boundary_from_left( self, yrange, xrange=None, max_bond=None, + *, cutoff=1e-10, canonize=True, - compress_sweep='up', + mode='mps', layer_tags=None, - inplace=False, + compress_sweep='up', compress_opts=None, + inplace=False, + **contract_boundary_opts, ): - """Contract a 2D tensor network inwards from the left, canonizing and - compressing (top to bottom) along the way. + r"""Contract a 2D tensor network inwards from the left, canonizing and + compressing (bottom to top) along the way. If + ``layer_tags is None`` this looks like: + + a) contract + + ●──●── ●── + │ │ ║ + ●──●── ==> ●── + │ │ ║ + ●──●── ●── + + b) optionally canonicalize + + ●── v── + ║ ║ + ●── ==> v── + ║ ║ + ●── ●── + + c) compress in opposite direction + + v── ●── + ║ │ + v── ==> ^── + ║ │ + ●── ^── + + If ``layer_tags`` is specified, each then each layer is contracted in + and compressed separately, resulting generally in a lower memory + scaling. For two layer tags this looks like: + + a) first flatten the outer boundary only + + ○──○── ●──○── + │╲ │╲ │╲ │╲ + ●─○──○── ╰─●──○── + ╲│╲╲│╲ ==> │╲╲│╲ + ●─○──○── ╰─●──○── + ╲│ ╲│ │ ╲│ + ●──●── ╰──●── + + b) contract and compress a single layer only + + ○── + ╱╱ ╲ + ●─── ○── + ╲ ╱╱ ╲ + ^─── ○── + ╲ ╱╱ + ^───── + + c) contract and compress the next layer + + ●── + │╲ + ╰─●── + │╲ + ╰─●── + │ + ╰── Parameters ---------- @@ -1129,162 +1434,121 @@ def contract_boundary_from_left( contraction. canonize : bool, optional Whether to sweep one way with canonization before compressing. - compress_sweep : {'up', 'down'}, optional - Which way to perform the compression sweep, which has an effect on - which tensors end up being canonized. + mode : {'mps', 'full-bond'}, optional + How to perform the compression on the boundary. layer_tags : None or str, optional If ``None``, all tensors at each coordinate pair ``[(i, j), (i, j + 1)]`` will be first contracted. If specified, then the outer tensor at ``(i, j)`` will be contracted with the tensor specified by ``[(i + 1, j), layer_tag]``, for each ``layer_tag`` in ``layer_tags``. - inplace : bool, optional - Whether to perform the contraction inplace or not. + compress_sweep : {'up', 'down'}, optional + Which way to perform the compression sweep, which has an effect on + which tensors end up being canonized. compress_opts : None or dict, optional Supplied to :meth:`~quimb.tensor.tensor_core.TensorNetwork.compress_between`. + inplace : bool, optional + Whether to perform the contraction inplace or not. See Also -------- contract_boundary_from_bottom, contract_boundary_from_top, contract_boundary_from_right """ - tn = self if inplace else self.copy() - - if xrange is None: - xrange = (0, self.Lx - 1) - - if layer_tags is None: - tn._contract_boundary_from_left_single( - yrange, xrange, max_bond=max_bond, cutoff=cutoff, - canonize=canonize, compress_sweep=compress_sweep, - compress_opts=compress_opts) - else: - tn._contract_boundary_from_left_multi( - yrange, xrange, layer_tags, max_bond=max_bond, cutoff=cutoff, - canonize=canonize, compress_sweep=compress_sweep, - compress_opts=compress_opts) - - return tn + return self.contract_boundary_from( + xrange=xrange, + yrange=yrange, + from_which="left", + max_bond=max_bond, + cutoff=cutoff, + canonize=canonize, + mode=mode, + layer_tags=layer_tags, + compress_sweep=compress_sweep, + compress_opts=compress_opts, + inplace=inplace, + **contract_boundary_opts, + ) contract_boundary_from_left_ = functools.partialmethod( contract_boundary_from_left, inplace=True) - def _contract_boundary_from_right_single( - self, - yrange, - xrange, - max_bond=None, - cutoff=1e-10, - canonize=True, - compress_sweep='down', - layer_tag=None, - compress_opts=None, - ): - canonize_sweep = { - 'up': 'down', - 'down': 'up', - }[compress_sweep] - - for j in range(max(yrange), min(yrange), -1): - # - # ──●──● ──● - # │ │ ║ - # ──●──● ==> ──● - # │ │ ║ - # ──●──● ──● - # - for i in range(min(xrange), max(xrange) + 1): - tag1, tag2 = self.site_tag(i, j), self.site_tag(i, j - 1) - if layer_tag is None: - # contract any tensors with coordinates (i, j), (i, j - 1) - self.contract_((tag1, tag2), which='any') - else: - # contract a specific pair - self.contract_between(tag1, (tag2, layer_tag)) - if canonize: - # - # ──● ──v - # ║ ║ - # ──● ==> ──v - # ║ ║ - # ──● ──● - # - self.canonize_column(j, sweep=canonize_sweep, xrange=xrange) - # - # ──v ──● - # ║ │ - # ──v ==> ──^ - # ║ │ - # ──● ──^ - # - self.compress_column(j, sweep=compress_sweep, xrange=xrange, - max_bond=max_bond, cutoff=cutoff, - compress_opts=compress_opts) - - def _contract_boundary_from_right_multi( - self, - yrange, - xrange, - layer_tags, - max_bond=None, - cutoff=1e-10, - canonize=True, - compress_sweep='down', - compress_opts=None, - ): - for j in range(max(yrange), min(yrange), -1): - # make sure the exterior sites are a single tensor - # - # ──○──○ ──○──● - # ╱│ ╱│ ╱│ ╱│ (for two layer tags) - # ──○──○─● ──○──●─╯ - # ╱│╱╱│╱ ==> ╱│╱╱│ - # ──○──○─● ──○──●─╯ - # │╱ │╱ │╱ │ - # ──●──● ──●──╯ - # - for i in range(min(xrange), max(xrange) + 1): - self ^= (i, j) - - for tag in layer_tags: - # contract interior sites from layer ``tag`` - # - # ──○ - # ╱ ╲╲ (first contraction if there are two tags) - # ──○────v - # ╱ ╲╲ ╱ - # ──○────v - # ╲╲ ╱ - # ─────● - # - self._contract_boundary_from_right_single( - yrange=(j, j - 1), xrange=xrange, max_bond=max_bond, - cutoff=cutoff, canonize=canonize, - compress_sweep=compress_sweep, layer_tag=tag, - compress_opts=compress_opts) - - # so we can still uniqely identify 'inner' tensors, drop inner - # site tag merged into outer tensor for all but last tensor - for i in range(min(xrange), max(xrange) + 1): - inner_tag = self.site_tag(i, j - 1) - if len(self.tag_map[inner_tag]) > 1: - self[i, j].drop_tags(inner_tag) - def contract_boundary_from_right( self, yrange, xrange=None, max_bond=None, + *, cutoff=1e-10, canonize=True, - compress_sweep='down', + mode='mps', layer_tags=None, - inplace=False, + compress_sweep='down', compress_opts=None, + inplace=False, + **contract_boundary_opts, ): - """Contract a 2D tensor network inwards from the left, canonizing and - compressing (top to bottom) along the way. + r"""Contract a 2D tensor network inwards from the left, canonizing and + compressing (top to bottom) along the way. If + ``layer_tags is None`` this looks like: + + a) contract + + ──●──● ──● + │ │ ║ + ──●──● ==> ──● + │ │ ║ + ──●──● ──● + + b) optionally canonicalize + + ──● ──v + ║ ║ + ──● ==> ──v + ║ ║ + ──● ──● + + c) compress in opposite direction + + ──v ──● + ║ │ + ──v ==> ──^ + ║ │ + ──● ──^ + + If ``layer_tags`` is specified, each then each layer is contracted in + and compressed separately, resulting generally in a lower memory + scaling. For two layer tags this looks like: + + a) first flatten the outer boundary only + + ──○──○ ──○──● + ╱│ ╱│ ╱│ ╱│ + ──○──○─● ──○──●─╯ + ╱│╱╱│╱ ==> ╱│╱╱│ + ──○──○─● ──○──●─╯ + │╱ │╱ │╱ │ + ──●──● ──●──╯ + + b) contract and compress a single layer only + + ──○ + ╱ ╲╲ + ──○────v + ╱ ╲╲ ╱ + ──○────v + ╲╲ ╱ + ─────● + + c) contract and compress the next layer + + ╲ + ────v + ╲ ╱ + ────v + ╲ ╱ + ────● Parameters ---------- @@ -1302,43 +1566,42 @@ def contract_boundary_from_right( contraction. canonize : bool, optional Whether to sweep one way with canonization before compressing. - compress_sweep : {'down', 'up'}, optional - Which way to perform the compression sweep, which has an effect on - which tensors end up being canonized. + mode : {'mps', 'full-bond'}, optional + How to perform the compression on the boundary. layer_tags : None or str, optional If ``None``, all tensors at each coordinate pair ``[(i, j), (i, j - 1)]`` will be first contracted. If specified, then the outer tensor at ``(i, j)`` will be contracted with the tensor specified by ``[(i + 1, j), layer_tag]``, for each ``layer_tag`` in ``layer_tags``. - inplace : bool, optional - Whether to perform the contraction inplace or not. + compress_sweep : {'down', 'up'}, optional + Which way to perform the compression sweep, which has an effect on + which tensors end up being canonized. compress_opts : None or dict, optional Supplied to :meth:`~quimb.tensor.tensor_core.TensorNetwork.compress_between`. + inplace : bool, optional + Whether to perform the contraction inplace or not. See Also -------- contract_boundary_from_bottom, contract_boundary_from_top, contract_boundary_from_left """ - tn = self if inplace else self.copy() - - if xrange is None: - xrange = (0, self.Lx - 1) - - if layer_tags is None: - tn._contract_boundary_from_right_single( - yrange, xrange, max_bond=max_bond, cutoff=cutoff, - canonize=canonize, compress_sweep=compress_sweep, - compress_opts=compress_opts) - else: - tn._contract_boundary_from_right_multi( - yrange, xrange, layer_tags, max_bond=max_bond, cutoff=cutoff, - canonize=canonize, compress_sweep=compress_sweep, - compress_opts=compress_opts) - - return tn + return self.contract_boundary_from( + xrange=xrange, + yrange=yrange, + from_which="right", + max_bond=max_bond, + cutoff=cutoff, + canonize=canonize, + mode=mode, + layer_tags=layer_tags, + compress_sweep=compress_sweep, + compress_opts=compress_opts, + inplace=inplace, + **contract_boundary_opts, + ) contract_boundary_from_right_ = functools.partialmethod( contract_boundary_from_right, inplace=True) @@ -1347,8 +1610,10 @@ def contract_boundary( self, around=None, max_bond=None, + *, cutoff=1e-10, canonize=True, + mode='mps', layer_tags=None, max_separation=1, sequence=None, @@ -1387,6 +1652,8 @@ def contract_boundary( contraction. canonize : bool, optional Whether to sweep one way with canonization before compressing. + mode : {'mps', 'full-bond'}, optional + How to perform the compression on the boundary. layer_tags : None or sequence of str, optional If given, perform a multilayer contraction, contracting the inner sites in each layer into the boundary individually. @@ -1422,6 +1689,18 @@ def contract_boundary( """ tn = self if inplace else self.copy() + contract_boundary_opts['max_bond'] = max_bond + contract_boundary_opts['mode'] = mode + contract_boundary_opts['cutoff'] = cutoff + contract_boundary_opts['canonize'] = canonize + contract_boundary_opts['layer_tags'] = layer_tags + contract_boundary_opts['compress_opts'] = compress_opts + + if (mode == 'full-bond'): + # set shared storage for opposite direction boundary contractions, + # this will be lazily filled by _contract_boundary_full_bond + contract_boundary_opts.setdefault('opposite_envs', {}) + # set default starting borders if bottom is None: bottom = 0 @@ -1451,12 +1730,6 @@ def contract_boundary( # keep track of whether we have hit the ``around`` region. reached_stop = {direction: False for direction in sequence} - contract_boundary_opts['max_bond'] = max_bond - contract_boundary_opts['cutoff'] = cutoff - contract_boundary_opts['canonize'] = canonize - contract_boundary_opts['layer_tags'] = layer_tags - contract_boundary_opts['compress_opts'] = compress_opts - for direction in cycle(sequence): if direction == 'b': @@ -1518,34 +1791,128 @@ def contract_boundary( contract_boundary_ = functools.partialmethod( contract_boundary, inplace=True) - def compute_row_environments( + def compute_environments( self, + from_which, + xrange=None, + yrange=None, max_bond=None, + *, cutoff=1e-10, canonize=True, + mode='mps', layer_tags=None, dense=False, compress_opts=None, + envs=None, + **contract_boundary_opts + ): + """Compute the ``self.Lx`` 1D boundary tensor networks describing + the environments of rows and columns. + """ + tn = self.copy() + + r2d = Rotator2D(tn, xrange, yrange, from_which) + sweep, row_tag = r2d.vertical_sweep, r2d.row_tag + contract_boundary_fn = r2d.get_contract_boundary_fn() + + if envs is None: + envs = {} + + if mode == 'full-bond': + # set shared storage for opposite env contractions + contract_boundary_opts.setdefault('opposite_envs', {}) + + envs[from_which, sweep[0]] = TensorNetwork([]) + first_row = row_tag(sweep[0]) + if dense: + tn ^= first_row + envs[from_which, sweep[1]] = tn.select(first_row) + + for i in sweep[2:]: + iprevprev = i - 2 * sweep.step + iprev = i - sweep.step + if dense: + tn ^= (row_tag(iprevprev), row_tag(iprev)) + else: + contract_boundary_fn( + iprevprev, iprev, + max_bond=max_bond, + cutoff=cutoff, + mode=mode, + canonize=canonize, + layer_tags=layer_tags, + compress_opts=compress_opts, + **contract_boundary_opts, + ) + + envs[from_which, i] = tn.select(first_row) + + return envs + + compute_bottom_environments = functools.partialmethod( + compute_environments, from_which='bottom') + """Compute the ``self.Lx`` 1D boundary tensor networks describing + the lower environments of each row in this 2D tensor network. See + :meth:`~quimb.tensor.tensor_2d.TensorNetwork2D.compute_row_environments` + for full details. + """ + + compute_top_environments = functools.partialmethod( + compute_environments, from_which='top') + """Compute the ``self.Lx`` 1D boundary tensor networks describing + the upper environments of each row in this 2D tensor network. See + :meth:`~quimb.tensor.tensor_2d.TensorNetwork2D.compute_row_environments` + for full details. + """ + + compute_left_environments = functools.partialmethod( + compute_environments, from_which='left') + """Compute the ``self.Ly`` 1D boundary tensor networks describing + the left environments of each column in this 2D tensor network. See + :meth:`~quimb.tensor.tensor_2d.TensorNetwork2D.compute_col_environments` + for full details. + """ + + compute_right_environments = functools.partialmethod( + compute_environments, from_which='right') + """Compute the ``self.Ly`` 1D boundary tensor networks describing + the right environments of each column in this 2D tensor network. See + :meth:`~quimb.tensor.tensor_2d.TensorNetwork2D.compute_col_environments` + for full details. + """ + + def compute_row_environments( + self, + max_bond=None, + *, + cutoff=1e-10, + canonize=True, + dense=False, + mode='mps', + layer_tags=None, + compress_opts=None, + envs=None, **contract_boundary_opts ): r"""Compute the ``2 * self.Lx`` 1D boundary tensor networks describing the lower and upper environments of each row in this 2D tensor network, *assumed to represent the norm*. - The 'above' environment for row ``i`` will be a contraction of all + The 'top' environment for row ``i`` will be a contraction of all rows ``i + 1, i + 2, ...`` etc:: ●━━━●━━━●━━━●━━━●━━━●━━━●━━━●━━━●━━━● ╱ ╲ ╱ ╲ ╱ ╲ ╱ ╲ ╱ ╲ ╱ ╲ ╱ ╲ ╱ ╲ ╱ ╲ ╱ ╲ - The 'below' environment for row ``i`` will be a contraction of all + The 'bottom' environment for row ``i`` will be a contraction of all rows ``i - 1, i - 2, ...`` etc:: ╲ ╱ ╲ ╱ ╲ ╱ ╲ ╱ ╲ ╱ ╲ ╱ ╲ ╱ ╲ ╱ ╲ ╱ ╲ ╱ ●━━━●━━━●━━━●━━━●━━━●━━━●━━━●━━━●━━━● Such that - ``envs['above', i] & self.select(self.row_tag(i)) & envs['below', i]`` + ``envs['top', i] & self.select(self.row_tag(i)) & envs['bottom', i]`` would look like:: ●━━━●━━━●━━━●━━━●━━━●━━━●━━━●━━━●━━━● @@ -1567,17 +1934,21 @@ def compute_row_environments( contraction. canonize : bool, optional Whether to sweep one way with canonization before compressing. + dense : bool, optional + If true, contract the boundary in as a single dense tensor. + mode : {'mps', 'full-bond'}, optional + How to perform the boundary compression. layer_tags : None or sequence[str], optional If ``None``, all tensors at each coordinate pair ``[(i, j), (i + 1, j)]`` will be first contracted. If specified, then the outer tensor at ``(i, j)`` will be contracted with the tensor specified by ``[(i + 1, j), layer_tag]``, for each ``layer_tag`` in ``layer_tags``. - dense : bool, optional - If true, contract the boundary in as a single dense tensor. compress_opts : None or dict, optional Supplied to :meth:`~quimb.tensor.tensor_core.TensorNetwork.compress_between`. + envs : dict, optional + Supply an existing dictionary to store the environments in. contract_boundary_opts Supplied to :meth:`~quimb.tensor.tensor_2d.TensorNetwork2D.contract_boundary_from_bottom` @@ -1589,56 +1960,35 @@ def compute_row_environments( ------- row_envs : dict[(str, int), TensorNetwork] The two environment tensor networks of row ``i`` will be stored in - ``row_envs['below', i]`` and ``row_envs['above', i]``. + ``row_envs['bottom', i]`` and ``row_envs['top', i]``. """ contract_boundary_opts['max_bond'] = max_bond contract_boundary_opts['cutoff'] = cutoff contract_boundary_opts['canonize'] = canonize + contract_boundary_opts['mode'] = mode + contract_boundary_opts['dense'] = dense contract_boundary_opts['layer_tags'] = layer_tags contract_boundary_opts['compress_opts'] = compress_opts - row_envs = dict() + if envs is None: + envs = {} - # upwards pass - row_envs['below', 0] = TensorNetwork([]) - first_row = self.row_tag(0) - env_bottom = self.copy() - if dense: - env_bottom ^= first_row - row_envs['below', 1] = env_bottom.select(first_row) - for i in range(2, env_bottom.Lx): - if dense: - env_bottom ^= (self.row_tag(i - 2), self.row_tag(i - 1)) - else: - env_bottom.contract_boundary_from_bottom_( - (i - 2, i - 1), **contract_boundary_opts) - row_envs['below', i] = env_bottom.select(first_row) - - # downwards pass - row_envs['above', self.Lx - 1] = TensorNetwork([]) - last_row = self.row_tag(self.Lx - 1) - env_top = self.copy() - if dense: - env_top ^= last_row - row_envs['above', self.Lx - 2] = env_top.select(last_row) - for i in range(env_top.Lx - 3, -1, -1): - if dense: - env_top ^= (self.row_tag(i + 1), self.row_tag(i + 2)) - else: - env_top.contract_boundary_from_top_( - (i + 1, i + 2), **contract_boundary_opts) - row_envs['above', i] = env_top.select(last_row) + self.compute_top_environments(envs=envs, **contract_boundary_opts) + self.compute_bottom_environments(envs=envs, **contract_boundary_opts) - return row_envs + return envs def compute_col_environments( self, max_bond=None, + *, cutoff=1e-10, canonize=True, - layer_tags=None, dense=False, + mode='mps', + layer_tags=None, compress_opts=None, + envs=None, **contract_boundary_opts ): r"""Compute the ``2 * self.Ly`` 1D boundary tensor networks describing @@ -1694,14 +2044,16 @@ def compute_col_environments( contraction. canonize : bool, optional Whether to sweep one way with canonization before compressing. + dense : bool, optional + If true, contract the boundary in as a single dense tensor. + mode : {'mps', 'full-bond'}, optional + How to perform the boundary compression. layer_tags : None or sequence[str], optional If ``None``, all tensors at each coordinate pair ``[(i, j), (i + 1, j)]`` will be first contracted. If specified, then the outer tensor at ``(i, j)`` will be contracted with the tensor specified by ``[(i + 1, j), layer_tag]``, for each ``layer_tag`` in ``layer_tags``. - dense : bool, optional - If true, contract the boundary in as a single dense tensor. compress_opts : None or dict, optional Supplied to :meth:`~quimb.tensor.tensor_core.TensorNetwork.compress_between`. @@ -1721,37 +2073,18 @@ def compute_col_environments( contract_boundary_opts['max_bond'] = max_bond contract_boundary_opts['cutoff'] = cutoff contract_boundary_opts['canonize'] = canonize + contract_boundary_opts['mode'] = mode + contract_boundary_opts['dense'] = dense contract_boundary_opts['layer_tags'] = layer_tags contract_boundary_opts['compress_opts'] = compress_opts - col_envs = dict() + if envs is None: + envs = {} - # rightwards pass - col_envs['left', 0] = TensorNetwork([]) - first_column = self.col_tag(0) - env_right = self.copy() - if dense: - env_right ^= first_column - col_envs['left', 1] = env_right.select(first_column) - for j in range(2, env_right.Ly): - if dense: - env_right ^= (self.col_tag(j - 2), self.col_tag(j - 1)) - else: - env_right.contract_boundary_from_left_( - (j - 2, j - 1), **contract_boundary_opts) - col_envs['left', j] = env_right.select(first_column) - - # leftwards pass - last_column = self.col_tag(self.Ly - 1) - env_left = self.copy() - col_envs['right', self.Ly - 1] = TensorNetwork([]) - col_envs['right', self.Ly - 2] = env_left.select(last_column) - for j in range(self.Ly - 3, -1, -1): - env_left.contract_boundary_from_right_( - (j + 1, j + 2), **contract_boundary_opts) - col_envs['right', j] = env_left.select(last_column) - - return col_envs + self.compute_left_environments(envs=envs, **contract_boundary_opts) + self.compute_right_environments(envs=envs, **contract_boundary_opts) + + return envs def _compute_plaquette_environments_row_first( self, @@ -1788,9 +2121,9 @@ def _compute_plaquette_environments_row_first( # ●━━━●━━━●━━━●━━━●━━━●━━━●━━━●━━━●━━━● # row_i = TensorNetwork(( - row_envs['below', i], + row_envs['bottom', i], self.select_any([self.row_tag(i + x) for x in range(x_bsz)]), - row_envs['above', i + x_bsz - 1], + row_envs['top', i + x_bsz - 1], )).view_as_(TensorNetwork2D, like=self) # # y_bsz @@ -1836,9 +2169,9 @@ def _compute_plaquette_environments_row_first( right_tags = tuple( starmap(self.site_tag, filter(self.valid_coo, right_coos))) - below_coos = ((i0 - 1, j0 + x) for x in range(y_bsz)) - below_tags = tuple( - starmap(self.site_tag, filter(self.valid_coo, below_coos))) + bottom_coos = ((i0 - 1, j0 + x) for x in range(y_bsz)) + bottom_tags = tuple( + starmap(self.site_tag, filter(self.valid_coo, bottom_coos))) above_coos = ((i0 + x_bsz, j0 + x) for x in range(y_bsz)) above_tags = tuple( @@ -1847,8 +2180,8 @@ def _compute_plaquette_environments_row_first( env_ij = TensorNetwork(( col_envs[i0]['left', j0].select_any(left_tags), col_envs[i0]['right', j0 + y_bsz - 1].select_any(right_tags), - row_envs['below', i0].select_any(below_tags), - row_envs['above', i0 + x_bsz - 1].select_any(above_tags), + row_envs['bottom', i0].select_any(bottom_tags), + row_envs['top', i0 + x_bsz - 1].select_any(above_tags), )) # finally, absorb any rank-2 corner tensors @@ -1907,11 +2240,11 @@ def _compute_plaquette_environments_col_first( # y_bsz # <--> second_dense=True # ●──●──●──● ╭──●──╮ - # │ │ │ │ or │ ╱ ╲ │ 'above' + # │ │ │ │ or │ ╱ ╲ │ 'top' # . . . . ┬ # ┊ x_bsz # . . . . ┴ - # │ │ │ │ or │ ╲ ╱ │ 'below' + # │ │ │ │ or │ ╲ ╱ │ 'bottom' # ●──●──●──● ╰──●──╯ # row_envs[j] = col_j.compute_row_environments( @@ -1946,9 +2279,9 @@ def _compute_plaquette_environments_col_first( right_tags = tuple( starmap(self.site_tag, filter(self.valid_coo, right_coos))) - below_coos = ((i0 - 1, j0 + x) for x in range(- 1, y_bsz + 1)) - below_tags = tuple( - starmap(self.site_tag, filter(self.valid_coo, below_coos))) + bottom_coos = ((i0 - 1, j0 + x) for x in range(- 1, y_bsz + 1)) + bottom_tags = tuple( + starmap(self.site_tag, filter(self.valid_coo, bottom_coos))) above_coos = ((i0 + x_bsz, j0 + x) for x in range(- 1, y_bsz + 1)) above_tags = tuple( @@ -1957,8 +2290,8 @@ def _compute_plaquette_environments_col_first( env_ij = TensorNetwork(( col_envs['left', j0].select_any(left_tags), col_envs['right', j0 + y_bsz - 1].select_any(right_tags), - row_envs[j0]['below', i0].select_any(below_tags), - row_envs[j0]['above', i0 + x_bsz - 1].select_any(above_tags), + row_envs[j0]['bottom', i0].select_any(bottom_tags), + row_envs[j0]['top', i0 + x_bsz - 1].select_any(above_tags), )) # finally, absorb any rank-2 corner tensors @@ -1973,8 +2306,10 @@ def compute_plaquette_environments( x_bsz=2, y_bsz=2, max_bond=None, + *, cutoff=1e-10, canonize=True, + mode='mps', layer_tags=None, first_contract=None, second_dense=None, @@ -2013,6 +2348,8 @@ def compute_plaquette_environments( contraction. canonize : bool, optional Whether to sweep one way with canonization before compressing. + mode : {'mps', 'full-bond'}, optional + How to perform the boundary compression. layer_tags : None or sequence[str], optional If ``None``, all tensors at each coordinate pair ``[(i, j), (i + 1, j)]`` will be first contracted. If specified, @@ -2062,7 +2399,7 @@ def compute_plaquette_environments( return compute_env_fn( x_bsz=x_bsz, y_bsz=y_bsz, max_bond=max_bond, cutoff=cutoff, - canonize=canonize, layer_tags=layer_tags, + canonize=canonize, mode=mode, layer_tags=layer_tags, compress_opts=compress_opts, second_dense=second_dense, **compute_environment_opts) @@ -2421,7 +2758,7 @@ def gate( tensors, then contract the gate, split it and reabsorb each side. Much cheaper than ``'split'``. - The final three methods are relevant for two site gates only, for + The final two methods are relevant for two site gates only, for single site gates they use the ``contract=True`` option which also maintains the structure of the TN. See below for a pictorial description of each method. @@ -2545,7 +2882,7 @@ def gate( psi |= TG return psi - elif (contract is True) or (ng == 1): + if (contract is True) or (ng == 1): # # │╱ │╱ # ──GGGGG── @@ -2661,6 +2998,12 @@ def compute_norm( def compute_local_expectation( self, terms, + max_bond=None, + *, + cutoff=1e-10, + canonize=True, + mode='mps', + layer_tags=('KET', 'BRA'), normalized=False, autogroup=True, contract_optimize='auto-hq', @@ -2670,7 +3013,10 @@ def compute_local_expectation( **plaquette_env_options, ): r"""Compute the sum of many local expecations by essentially forming - the reduced density matrix of all required plaquettes. + the reduced density matrix of all required plaquettes. If you supply + ``normalized=True`` each expecation is locally normalized, which a) is + usually more accurate and b) doesn't require a separate normalization + boundary contraction. Parameters ---------- @@ -2681,6 +3027,20 @@ def compute_local_expectation( keys should either be a single coordinate - ``(i, j)`` - describing a single site operator, or a pair of coordinates - ``((i_a, j_a), (i_b, j_b))`` describing a two site operator. + max_bond : int, optional + The maximum boundary dimension, AKA 'chi'. The default of ``None`` + means truncation is left purely to ``cutoff`` and is not + recommended in 2D. + cutoff : float, optional + Cut-off value to used to truncate singular values in the boundary + contraction. + canonize : bool, optional + Whether to sweep one way with canonization before compressing. + mode : {'mps', 'full-bond'}, optional + How to perform the compression on the boundary. + layer_tags : None or sequence of str, optional + If given, perform a multilayer contraction, contracting the inner + sites in each layer into the boundary individually. normalized : bool, optional If True, normalize the value of each local expectation by the local norm: $\langle O_i \rangle = Tr[\rho_p O_i] / Tr[\rho_p]$. @@ -2713,8 +3073,11 @@ def compute_local_expectation( norm, ket, bra = self.make_norm(return_all=True) if plaquette_envs is None: - # set some sensible defaults - plaquette_env_options.setdefault('layer_tags', ('KET', 'BRA')) + plaquette_env_options["max_bond"] = max_bond + plaquette_env_options["cutoff"] = cutoff + plaquette_env_options["canonize"] = canonize + plaquette_env_options["mode"] = mode + plaquette_env_options["layer_tags"] = layer_tags plaquette_envs = dict() for x_bsz, y_bsz in calc_plaquette_sizes(terms.keys(), autogroup): @@ -2768,6 +3131,12 @@ def compute_local_expectation( def normalize( self, + max_bond=None, + *, + cutoff=1e-10, + canonize=True, + mode='mps', + layer_tags=('KET', 'BRA'), balance_bonds=False, equalize_norms=False, inplace=False, @@ -2777,24 +3146,40 @@ def normalize( Parameters ---------- - inplace : bool, optional - Whether to perform the normalization inplace or not. + max_bond : int, optional + The maximum boundary dimension, AKA 'chi'. The default of ``None`` + means truncation is left purely to ``cutoff`` and is not + recommended in 2D. + cutoff : float, optional + Cut-off value to used to truncate singular values in the boundary + contraction. + canonize : bool, optional + Whether to sweep one way with canonization before compressing. + mode : {'mps', 'full-bond'}, optional + How to perform the compression on the boundary. + layer_tags : None or sequence of str, optional + If given, perform a multilayer contraction, contracting the inner + sites in each layer into the boundary individually. balance_bonds : bool, optional Whether to balance the bonds after normalization, a form of conditioning. equalize_norms : bool, optional Whether to set all the tensor norms to the same value after normalization, another form of conditioning. + inplace : bool, optional + Whether to perform the normalization inplace or not. contract_boundary_opts Supplied to :meth:`~quimb.tensor.tensor_2d.TensorNetwork2D.contract_boundary`, by default, two layer contraction will be used. """ - norm = self.make_norm() - - # default to two layer contraction - contract_boundary_opts.setdefault('layer_tags', ('KET', 'BRA')) + contract_boundary_opts["max_bond"] = max_bond + contract_boundary_opts["cutoff"] = cutoff + contract_boundary_opts["canonize"] = canonize + contract_boundary_opts["mode"] = mode + contract_boundary_opts["layer_tags"] = layer_tags + norm = self.make_norm() nfact = norm.contract_boundary(**contract_boundary_opts) n_ket = self.multiply_each( @@ -3440,7 +3825,7 @@ def show_2d(tn_2d, show_lower=False, show_upper=False): bszs = [tn_2d.bond_size((i, j), (i + 1, j)) for j in range(tn_2d.Ly)] lines[-1] = lines[-1].format(*bszs) - # horizontal bonds below + # horizontal bonds bottom lines.append(' ┃' + (f'{ub}{{:^3}}┃' * (tn_2d.Ly - 1)) + f'{ub}') bszs = [tn_2d.bond_size((i + 1, j), (i + 1, j + 1)) for j in range(tn_2d.Ly - 1)] diff --git a/quimb/tensor/tensor_core.py b/quimb/tensor/tensor_core.py index ee3dbeb91..2147f8193 100644 --- a/quimb/tensor/tensor_core.py +++ b/quimb/tensor/tensor_core.py @@ -101,19 +101,16 @@ def _get_contract_path(eq, *shapes, **kwargs): """ # construct the internal opt_einsum data - lhs, rhs = eq.split('->') - terms = lhs.split(',') + lhs, output = eq.split('->') + inputs = lhs.split(',') # nothing to optimize in this case - nterms = len(terms) + nterms = len(inputs) if nterms <= 2: return (tuple(range(nterms)),) - inputs = list(map(set, terms)) - output = set(rhs) - size_dict = {} - for ix, d in zip(concat(terms), concat(shapes)): + for ix, d in zip(concat(inputs), concat(shapes)): size_dict[ix] = d # get the actual path generating function @@ -424,8 +421,7 @@ def _gen_output_inds(all_inds): """Generate the output, i.e. unique, indices from the set ``inds``. Raise if any index found more than twice. """ - cnts = frequencies(all_inds) - for ind, freq in cnts.items(): + for ind, freq in frequencies(all_inds).items(): if freq > 2: raise ValueError( f"The index {ind} appears more than twice! If this is " @@ -437,17 +433,29 @@ def _gen_output_inds(all_inds): @functools.lru_cache(2**12) -def _inds_to_eq(all_inds, inputs, output): - """``einsum`` need characters a-z,A-Z or equivalent numbers. - Do this early, and allow *any* index labels. +def get_symbol(i): + """Get the 'ith' symbol. + """ + return oe.get_symbol(i) + + +def empty_symbol_map(): + """Get a default dictionary that will populate with symbol entries as they + are accessed. + """ + return collections.defaultdict(map(get_symbol, itertools.count()).__next__) + + +@functools.lru_cache(2**12) +def _inds_to_eq(inputs, output): + """Turn input and output indices of any sort into a single 'equation' + string where each index is a single 'symbol' (unicode character). Parameters ---------- - all_inds : iterable - All of the input indices. - inputs : sequence of sequence + inputs : sequence of sequence of str The input indices per tensor. - output : list of int + output : sequence of str The output indices. Returns @@ -455,18 +463,23 @@ def _inds_to_eq(all_inds, inputs, output): eq : str The string to feed to einsum/contract. """ - amap = {ix: oe.get_symbol(i) for i, ix in enumerate(all_inds)} - in_str = ("".join(amap[i] for i in ix) for ix in inputs) - out_str = "".join(amap[o] for o in output) - - return ",".join(in_str) + "->" + out_str + symbol_get = empty_symbol_map().__getitem__ + in_str = ("".join(map(symbol_get, inds)) for inds in inputs) + out_str = "".join(map(symbol_get, output)) + return ",".join(in_str) + f"->{out_str}" _VALID_CONTRACT_GET = {None, 'expression', 'path', 'path-info', 'symbol-map'} -def tensor_contract(*tensors, output_inds=None, get=None, - backend=None, **contract_opts): +def tensor_contract( + *tensors, + output_inds=None, + get=None, + backend=None, + preserve_tensor=False, + **contract_opts +): """Efficiently contract multiple tensors, combining their tags. Parameters @@ -488,9 +501,12 @@ def tensor_contract(*tensors, output_inds=None, get=None, detailed information such as flop cost. The symbol-map is also added to the ``quimb_symbol_map`` attribute. - backend : {'numpy', 'cupy', 'tensorflow', 'theano', 'dask', ...}, optional + backend : {'auto', 'numpy', 'jax', 'cupy', 'tensorflow', ...}, optional Which backend to use to perform the contraction. Must be a valid ``opt_einsum`` backend with the relevant library installed. + preserve_tensor : bool, optional + Whether to return a tensor regardless of whether the output object + is a scalar (has no indices) or not. contract_opts Passed to ``opt_einsum.contract_expression`` or ``opt_einsum.contract_path``. @@ -499,52 +515,56 @@ def tensor_contract(*tensors, output_inds=None, get=None, ------- scalar or Tensor """ - check_opt('get', get, _VALID_CONTRACT_GET) - if backend is None: backend = get_contract_backend() - i_ix = tuple(t.inds for t in tensors) # input indices per tensor - total_ix = tuple(concat(i_ix)) # list of all input indices - all_ix = tuple(unique(total_ix)) + inds_i = tuple(t.inds for t in tensors) # input indices per tensor if output_inds is None: # sort output indices by input order for efficiency and consistency - o_ix = tuple(_gen_output_inds(total_ix)) + inds_out = tuple(_gen_output_inds(concat(inds_i))) else: - o_ix = tuple(output_inds) + inds_out = tuple(output_inds) # possibly map indices into the range needed by opt-einsum - eq = _inds_to_eq(all_ix, i_ix, o_ix) - - if get == 'symbol-map': - return {oe.get_symbol(i): ix for i, ix in enumerate(all_ix)} - - if get == 'path': - ops = (t.shape for t in tensors) - return get_contraction(eq, *ops, get='path', **contract_opts) - - if get == 'path-info': - ops = (t.shape for t in tensors) - path_info = get_contraction(eq, *ops, get='info', **contract_opts) - path_info.quimb_symbol_map = { - oe.get_symbol(i): ix for i, ix in enumerate(all_ix) - } - return path_info - - if get == 'expression': - # account for possible constant tensors - cnst = contract_opts.get('constants', ()) - ops = (t.data if i in cnst else t.shape for i, t in enumerate(tensors)) - expression = get_contraction(eq, *ops, **contract_opts) - return expression + eq = _inds_to_eq(inds_i, inds_out) + + if get is not None: + check_opt('get', get, _VALID_CONTRACT_GET) + + if get == 'symbol-map': + return { + get_symbol(i): ix + for i, ix in enumerate(unique(concat(inds_i))) + } + + if get == 'path': + ops = (t.shape for t in tensors) + return get_contraction(eq, *ops, get='path', **contract_opts) + + if get == 'path-info': + ops = (t.shape for t in tensors) + path_info = get_contraction(eq, *ops, get='info', **contract_opts) + path_info.quimb_symbol_map = { + get_symbol(i): ix + for i, ix in enumerate(unique(concat(inds_i))) + } + return path_info + + if get == 'expression': + # account for possible constant tensors + cnst = contract_opts.get('constants', ()) + ops = (t.data if i in cnst else t.shape + for i, t in enumerate(tensors)) + expression = get_contraction(eq, *ops, **contract_opts) + return expression # perform the contraction shapes = (t.shape for t in tensors) expression = get_contraction(eq, *shapes, **contract_opts) o_array = expression(*(t.data for t in tensors), backend=backend) - if not o_ix: + if not inds_out and not preserve_tensor: if isinstance(o_array, np.ndarray): o_array = realify_scalar(o_array.item(0)) return o_array @@ -552,7 +572,7 @@ def tensor_contract(*tensors, output_inds=None, get=None, # union of all tags o_tags = oset.union(*(t.tags for t in tensors)) - return Tensor(data=o_array, inds=o_ix, tags=o_tags) + return Tensor(data=o_array, inds=inds_out, tags=o_tags) # generate a random base to avoid collisions on difference processes ... @@ -1554,8 +1574,10 @@ def tensor_network_fit_als( # --------------------------------------------------------------------------- # class Tensor(object): - """A labelled, tagged ndarray. The index labels are used instead of - axis numbers to identify dimensions, and are preserved through operations. + """A labelled, tagged n-dimensional array. The index labels are used + instead of axis numbers to identify dimensions, and are preserved through + operations. The tags are used to identify the tensor within networks, and + are combined when tensors are contracted together. Parameters ---------- @@ -1858,7 +1880,7 @@ def conj(self, inplace=False): t = self if inplace else self.copy() data = t.data if iscomplex(data): - t.modify(data=conj(data)) + t.modify(apply=conj) return t conj_ = functools.partialmethod(conj, inplace=True) @@ -1950,9 +1972,9 @@ def transpose(self, *output_inds, inplace=False): f"{set(output_inds)}") current_ind_map = {ind: i for i, ind in enumerate(t.inds)} - out_shape = tuple(current_ind_map[i] for i in output_inds) + perm = tuple(current_ind_map[i] for i in output_inds) - t.modify(apply=lambda x: transpose(x, out_shape), inds=output_inds) + t.modify(apply=lambda x: transpose(x, perm), inds=output_inds) return t transpose_ = functools.partialmethod(transpose, inplace=True) @@ -2014,7 +2036,7 @@ def trace(self, ind1, ind2, inplace=False): new_inds.append(ix) old_inds, new_inds = tuple(old_inds), tuple(new_inds) - eq = _inds_to_eq(t.inds, (old_inds,), new_inds) + eq = _inds_to_eq((old_inds,), new_inds) t.modify(apply=lambda x: do('einsum', eq, x, like=x), inds=new_inds, left_inds=None) @@ -2048,11 +2070,12 @@ def collapse_repeated(self, inplace=False): """ t = self if inplace else self.copy() - new_inds = tuple(oset(t.inds)) - if len(t.inds) == len(new_inds): + old_inds = t.inds + new_inds = tuple(unique(old_inds)) + if len(old_inds) == len(new_inds): return t - eq = _inds_to_eq(new_inds, (t.inds,), new_inds) + eq = _inds_to_eq((old_inds,), new_inds) t.modify(apply=lambda x: do('einsum', eq, x, like=x), inds=new_inds, left_inds=None) @@ -2236,7 +2259,6 @@ def unfuse(self, unfuse_map, shape_map, inplace=False): case the output tensor's new inds will be ordered. In both cases the new indices are created at the old index's position of the tensor's shape - shape_map : dict_like or sequence of tuples Mapping like: ``{old_ind: new_ind_sizes, ...}`` or an ordered mapping like ``[(old_ind_1, new_ind_sizes_1), ...]``. @@ -3946,17 +3968,253 @@ def contract_between(self, tags1, tags2, **contract_opts): if tid1 == tid2: return - T1 = self._pop_tensor(tid1) - T2 = self._pop_tensor(tid2) - T12 = tensor_contract(T1, T2, **contract_opts) - self.add_tensor(T12, tid=tid2, virtual=True) + output_inds = self.compute_contracted_inds(tid1, tid2) + t1 = self._pop_tensor(tid1) + t2 = self._pop_tensor(tid2) + t12 = tensor_contract(t1, t2, output_inds=output_inds, + preserve_tensor=True, **contract_opts) + self.add_tensor(t12, tid=tid2, virtual=True) - def contract_ind(self, ind, **contract_opts): + def contract_ind(self, ind, output_inds=None, **contract_opts): """Contract tensors connected by ``ind``. """ - tids = self._get_tids_from_inds(ind) - ts = [self._pop_tensor(tid) for tid in tids] - self |= tensor_contract(*ts, **contract_opts) + tids = tuple(self._get_tids_from_inds(ind)) + output_inds = self.compute_contracted_inds( + *tids, output_inds=output_inds) + tnew = tensor_contract( + *map(self._pop_tensor, tids), output_inds=output_inds, + preserve_tensor=True, **contract_opts + ) + self.add_tensor(tnew, tid=tids[0], virtual=True) + + def gate_inds( + self, + G, + inds, + contract=False, + tags=None, + info=None, + inplace=False, + **compress_opts, + ): + """Apply the 'gate' ``G`` to indices ``inds``, propagating them to the + outside, as if applying ``G @ x``. + + Parameters + ---------- + G : array_ike + The gate array to apply, should match or be factorable into the + shape ``(*phys_dims, *phys_dims)``. + inds : str or sequence or str, + The index or indices to apply the gate to. + contract : {False, True, 'split', 'reduce-split'}, optional + How to apply the gate: + + - False: gate is added to network and nothing is contracted, + tensor network structure is thus not maintained. + - True: gate is contracted with all tensors involved, tensor + network structure is thus only maintained if gate acts on a + single site only. + - 'split': contract all involved tensors then split the result + back into two. + - 'reduce-split': factor the two physical indices into + 'R-factors' using QR decompositions on the original site + tensors, then contract the gate, split it and reabsorb each + side. Much cheaper than ``'split'``. + + The final two methods are relevant for two site gates only, for + single site gates they use the ``contract=True`` option which also + maintains the structure of the TN. See below for a pictorial + description of each method. + tags : str or sequence of str, optional + Tags to add to the new gate tensor. + info : None or dict, optional + Used to store extra optional information such as the singular + values if not absorbed. + inplace : bool, optional + Whether to perform the gate operation inplace on the tensor + network or not. + compress_opts + Supplied to :func:`~quimb.tensor.tensor_core.tensor_split` for any + ``contract`` methods that involve splitting. Ignored otherwise. + + Returns + ------- + G_tn : TensorNetwork + + Notes + ----- + + The ``contract`` options look like the following (for two site gates). + + ``contract=False``:: + + . . <- inds + │ │ + GGGGG + │╱ │╱ + ──●───●── + ╱ ╱ + + ``contract=True``:: + + │╱ │╱ + ──GGGGG── + ╱ ╱ + + ``contract='split'``:: + + │╱ │╱ │╱ │╱ + ──GGGGG── ==> ──G┄┄┄G── + ╱ ╱ ╱ ╱ + + + ``contract='reduce-split'``:: + + │ │ │ │ + GGGGG GGG │ │ + │╱ │╱ ==> ╱│ │ ╱ ==> ╱│ │ ╱ │╱ │╱ + ──●───●── ──>─●─●─<── ──>─GGG─<── ==> ──G┄┄┄G── + ╱ ╱ ╱ ╱ ╱ ╱ ╱ ╱ + + + For one site gates when one of the 'split' methods is supplied + ``contract=True`` is assumed. + """ + check_opt("contract", contract, (False, True, 'split', 'reduce-split')) + + tn = self if inplace else self.copy() + + if isinstance(inds, str): + inds = (inds,) + ng = len(inds) + + # new indices to join old physical sites to new gate + bnds = [rand_uuid() for _ in range(ng)] + reindex_map = dict(zip(inds, bnds)) + + # tensor representing the gate + tags = tags_to_oset(tags) + tG = Tensor(G, inds=inds + bnds, tags=tags, left_inds=bnds) + + if contract is False: + # + # │ │ <- site_ix + # GGGGG + # │╱ │╱ <- bnds + # ──●───●── + # ╱ ╱ + # + tn.reindex_(reindex_map) + tn |= tG + return tn + + tids = self._get_tids_from_inds(inds, 'any') + + if (contract is True) or (len(tids) == 1): + # + # │╱ │╱ + # ──GGGGG── + # ╱ ╱ + # + tn.reindex_(reindex_map) + + # get the sites that used to have the physical indices + site_tids = tn._get_tids_from_inds(bnds, which='any') + + # pop the sites, contract, then re-add + pts = [tn._pop_tensor(tid) for tid in site_tids] + tn |= tensor_contract(*pts, tG) + + return tn + + # get the two tensors and their current shared indices etc. + ixl, ixr = inds + tl, tr = tn._inds_get(ixl, ixr) + bnds_l, (bix,), bnds_r = group_inds(tl, tr) + + if contract == 'split': + # + # │╱ │╱ │╱ │╱ + # ──GGGGG── -> ──G~~~G── + # ╱ ╱ ╱ ╱ + # + + # contract with new gate tensor + tlGr = tensor_contract( + tl.reindex(reindex_map), + tr.reindex(reindex_map), + tG) + + # decompose back into two tensors + tln, *maybe_svals, trn = tlGr.split( + left_inds=bnds_l, right_inds=bnds_r, + bond_ind=bix, get='tensors', **compress_opts) + + if contract == 'reduce-split': + # move physical inds on reduced tensors + # + # │ │ │ │ + # GGGGG GGG + # │╱ │╱ -> ╱ │ │ ╱ + # ──●───●── ──>──●─●──<── + # ╱ ╱ ╱ ╱ + # + tmp_bix_l = rand_uuid() + tl_Q, tl_R = tl.split(left_inds=None, right_inds=[bix, ixl], + method='qr', bond_ind=tmp_bix_l) + tmp_bix_r = rand_uuid() + tr_L, tr_Q = tr.split(left_inds=[bix, ixr], right_inds=None, + method='lq', bond_ind=tmp_bix_r) + + # contract reduced tensors with gate tensor + # + # │ │ + # GGG │ │ + # ╱ │ │ ╱ -> ╱ │ │ ╱ + # ──>──●─●──<── ──>──LGR──<── + # ╱ ╱ ╱ ╱ + # + tlGr = tensor_contract( + tl_R.reindex(reindex_map), + tr_L.reindex(reindex_map), + tG) + + # split to find new reduced factors + # + # │ │ │ │ + # ╱ │ │ ╱ -> ╱ │ │ ╱ + # ──>──LGR──<── ──>──L=R──<── + # ╱ ╱ ╱ ╱ + # + tl_R, *maybe_svals, tr_L = tlGr.split( + left_inds=[tmp_bix_l, ixl], right_inds=[tmp_bix_r, ixr], + bond_ind=bix, get='tensors', **compress_opts) + + # absorb reduced factors back into site tensors + # + # │ │ │ │ + # ╱ │ │ ╱ │╱ │╱ + # ──>──L=R──<── -> ──●───●── + # ╱ ╱ ╱ ╱ + # + tln = tl_Q @ tl_R + trn = tr_L @ tr_Q + + # if singular values are returned (``absorb=None``) check if we should + # return them via ``info``, e.g. for ``SimpleUpdate` + if maybe_svals and info is not None: + s = next(iter(maybe_svals)).data + info['singular_values', tuple(sorted(inds))] = s + + # update original tensors + tl.modify(data=tln.transpose_like_(tl).data) + tr.modify(data=trn.transpose_like_(tr).data) + + return tn + + gate_inds_ = functools.partialmethod(gate_inds, inplace=True) + def _compute_bond_env( self, tid1, tid2, @@ -4399,7 +4657,7 @@ def get_tree_span( ndim_sort='max', distance_sort='min', sorter=None, - connectivity_weight_bonds=False, + connectivity_weight_bonds=True, inwards=True, ): """Generate a tree on the tensor network graph, fanning out from the @@ -4545,7 +4803,7 @@ def _draw_tree_span_tids( ndim_sort='max', distance_sort='min', sorter=None, - connectivity_weight_bonds=False, + connectivity_weight_bonds=True, color='order', colormap='Spectral', **draw_opts, @@ -4594,8 +4852,9 @@ def _draw_tree_span_tids( custom_colors = None draw_opts.setdefault('legend', False) - draw_opts.setdefault('custom_colors', custom_colors) + draw_opts.setdefault('edge_color', (0.85, 0.85, 0.85)) draw_opts.setdefault('highlight_inds', tix) + draw_opts.setdefault('custom_colors', custom_colors) return tn.draw(color=[f'D{d}' for d in sorted(ds)], **draw_opts) @@ -4609,7 +4868,7 @@ def draw_tree_span( exclude=None, ndim_sort='max', distance_sort='min', - connectivity_weight_bonds=False, + connectivity_weight_bonds=True, color='order', colormap='Spectral', **draw_opts, @@ -4888,7 +5147,7 @@ def gauge_all_simple( tid1, tid2 = tn.ind_map[ind] except (KeyError, ValueError): # fused multibond (removed) or not a bond (len(tids != 2)) - pass + continue t1 = tn.tensor_map[tid1] t2 = tn.tensor_map[tid2] @@ -5059,6 +5318,7 @@ def _contract_compressed_tid_sequence( canonize_after_distance=0, canonize_after_opts=None, gauge_boundary_only=False, + compress_late=False, compress_opts=None, compress_span=False, compress_exclude=None, @@ -5073,6 +5333,56 @@ def _contract_compressed_tid_sequence( # the boundary - the set of intermediate tensors boundary = oset() + def _do_contraction(tid1, tid2): + """The inner closure that contracts the two tensors identified by + ``tid1`` and ``tid``. + """ + if callback_pre_contract is not None: + callback_pre_contract(self, (tid1, tid2)) + + # pop out the pair of tensors + t1, t2 = self._pop_tensor(tid1), self._pop_tensor(tid2) + + # contract them + t_new = tensor_contract(t1, t2, preserve_tensor=True) + + # re-add the product, using the same identifier as the (inner) t2 + tid_new = tid2 + self.add_tensor(t_new, tid=tid_new, virtual=True) + + # maybe control norm blow-up by stripping the new tensor exponent + if equalize_norms: + self.strip_exponent(t_new, equalize_norms) + + # update the boundary + boundary.add(tid_new) + + if callback_post_contract is not None: + callback_post_contract(self, tid_new) + + return tid_new, t_new + + # keep track of pairs along the tree - often no point compressing these + # (potentially, on some complex graphs, one needs to compress) + if not compress_span: + dont_compress_pairs = {frozenset((s[0], s[1])) for s in seq} + else: + # else just exclude the next few upcoming contractions, starting + # with the first + dont_compress_pairs = {frozenset((seq[0][0], seq[0][1]))} + + def _should_skip_compression(i, tid1, tid2): + """The inner closure deciding whether we should compress between + ``tid1`` and tid2``. + """ + pair_key = frozenset((tid1, tid2)) + return ( + # explicitly excluded from compression + ((compress_exclude is not None) and (tid2 in compress_exclude)) + # or compressing pair that will be eventually contracted + or pair_key in dont_compress_pairs + ) + # options relating to locally canonizing around each compression if canonize_distance: canonize_opts = ensure_dict(canonize_opts) @@ -5107,90 +5417,33 @@ def chi_fn(d): def eps_fn(d): return cutoff - C = len(seq) - - # keep track of pairs along the tree - often no point compressing these - # (potentially, on some complex graphs, one needs to compress) - dont_compress_pairs = {frozenset((s[0], s[1])) for s in seq} - - if progbar: - import tqdm - max_size = 0.0 - pbar = tqdm.tqdm(total=C) - else: - max_size = pbar = None - - for i in range(C): - # tid1 -> tid2 is inwards on the contraction tree, ``d`` is the - # graph distance from the original region - tid1, tid2, d = seq[i] - - if callback_pre_contract is not None: - callback_pre_contract(self, (tid1, tid2)) - - # pop out the pair of tensors - t1, t2 = self._pop_tensor(tid1), self._pop_tensor(tid2) - - # contract them - t_new = t1 @ t2 - - if not isinstance(t_new, Tensor): - t_new = Tensor(t_new, tags=t1.tags | t2.tags) - - if progbar: - new_size = math.log2(t_new.size) - max_size = max(max_size, new_size) - pbar.set_description( - f"log2[SIZE]: {new_size:.2f}/{max_size:.2f}") - pbar.update() - - # re-add the product, using the same identifier as the (inner) t2 - tid_new = tid2 - self.add_tensor(t_new, tid=tid_new, virtual=True) - - # maybe control norm blow-up by stripping the new tensor exponent - if equalize_norms: - self.strip_exponent(tid_new, equalize_norms) - - # update the boundary - boundary.add(tid_new) - - if callback_post_contract is not None: - callback_post_contract(self, tid_new) - - # allow dynamically adjusting truncation settings based on distance + def _compress_neighbors(tid, t, d): + """Inner closure that compresses tensor ``t`` with identifier + ``tid`` at distance ``d``, with its neighbors. + """ chi = chi_fn(d) eps = eps_fn(d) - for tid_neighb in self._get_neighbor_tids(tid_new): + if max_bond is None and eps == 0.0: + # skip compression + return + + for tid_neighb in self._get_neighbor_tids(tid): # first just check for accumulation of small multi-bonds t_neighb = self.tensor_map[tid_neighb] - tensor_fuse_squeeze(t_new, t_neighb) - - pair_key = frozenset((tid_new, tid_neighb)) - if ( - # not allowed to compress - ((compress_exclude is not None) and - (tid_neighb in compress_exclude)) or - # compressing along span often pointless - ((not compress_span) and - (pair_key in dont_compress_pairs)) or - # always pointless if next contraction - (pair_key in {frozenset(s[:2]) for s in seq[i + 1:i + 2]}) - ): + tensor_fuse_squeeze(t, t_neighb) + + if _should_skip_compression(i, tid, tid_neighb): continue # check for compressing large shared (multi) bonds - if ( - (chi is None and eps != 0.0) or - (bonds_size(t_new, t_neighb) > chi) - ): + if bonds_size(t, t_neighb) > chi: if callback_pre_compress is not None: - callback_pre_compress(self, (tid_new, tid_neighb)) + callback_pre_compress(self, (tid, tid_neighb)) self._compress_between_tids( - tid_new, + tid, tid_neighb, max_bond=chi, cutoff=eps, @@ -5203,7 +5456,45 @@ def eps_fn(d): ) if callback_post_compress is not None: - callback_post_compress(self, (tid_new, tid_neighb)) + callback_post_compress(self, (tid, tid_neighb)) + + num_contractions = len(seq) + + if progbar: + import tqdm + max_size = 0.0 + pbar = tqdm.tqdm(total=num_contractions) + else: + max_size = pbar = None + + for i in range(num_contractions): + # tid1 -> tid2 is inwards on the contraction tree, ``d`` is the + # graph distance from the original region + tid1, tid2, d = seq[i] + + if compress_span: + # only keep track of the next few contractions to ignore + for s in seq[i + 1:i + 2]: + dont_compress_pairs.add(frozenset((s[0], s[1]))) + + if compress_late: + # we compress just before we have to contract involved tensors + t1, t2 = self._tids_get(tid1, tid2) + _compress_neighbors(tid1, t1, d) + _compress_neighbors(tid2, t2, d) + + tid_new, t_new = _do_contraction(tid1, tid2) + + if progbar: + new_size = math.log2(t_new.size) + max_size = max(max_size, new_size) + pbar.set_description( + f"log2[SIZE]: {new_size:.2f}/{max_size:.2f}") + pbar.update() + + if not compress_late: + # we compress as soon as we produce a new tensor + _compress_neighbors(tid_new, t_new, d) if callback is not None: callback(self, tid_new) @@ -5265,6 +5556,36 @@ def _contract_around_tids( equalize_norms=equalize_norms, **kwargs) + def most_central_tid(self): + import cotengra as ctg + hg = ctg.get_hypergraph( + {tid: t.inds for tid, t in self.tensor_map.items()} + ) + cents = hg.simple_centrality() + return max((score, tid) for tid, score in cents.items())[1] + + def least_central_tid(self): + import cotengra as ctg + hg = ctg.get_hypergraph( + {tid: t.inds for tid, t in self.tensor_map.items()} + ) + cents = hg.simple_centrality() + return min((score, tid) for tid, score in cents.items())[1] + + def contract_around_center(self, **opts): + tid_center = self.most_central_tid() + opts.setdefault("span_opts", {}) + opts["span_opts"].setdefault("distance_sort", "min") + opts["span_opts"].setdefault("ndim_sort", "max") + return self.copy()._contract_around_tids([tid_center], **opts) + + def contract_around_corner(self, **opts): + tid_corner = self.least_central_tid() + opts.setdefault("span_opts", {}) + opts["span_opts"].setdefault("distance_sort", "max") + opts["span_opts"].setdefault("ndim_sort", "min") + return self.copy()._contract_around_tids([tid_corner], **opts) + def contract_around( self, tags, @@ -5511,6 +5832,34 @@ def insert_operator(self, A, where1, where2, tags=None, inplace=False): insert_operator_ = functools.partialmethod(insert_operator, inplace=True) + def _insert_gauge_tids( + self, + U, + tid1, + tid2, + Uinv=None, + tol=1e-10, + bond=None, + ): + t1, t2 = self._tids_get(tid1, tid2) + + if bond is None: + bond, = t1.bonds(t2) + + if Uinv is None: + Uinv = do('linalg.inv', U) + + # if we get wildly larger inverse due to singular U, try pseudo-inv + if vdot(Uinv, Uinv) / vdot(U, U) > 1 / tol: + Uinv = do('linalg.pinv', U, rcond=tol**0.5) + + # if still wildly larger inverse raise an error + if vdot(Uinv, Uinv) / vdot(U, U) > 1 / tol: + raise np.linalg.LinAlgError("Ill conditioned inverse.") + + t1.gate_(Uinv.T, bond) + t2.gate_(U, bond) + def insert_gauge(self, U, where1, where2, Uinv=None, tol=1e-10): """Insert the gauge transformation ``U @ U^-1`` into the bond between the tensors, ``T1`` and ``T2``, defined by ``where1`` and ``where2``. @@ -5529,24 +5878,9 @@ def insert_gauge(self, U, where1, where2, Uinv=None, tol=1e-10): The inverse gauge, ``U @ Uinv == Uinv @ U == eye``, to insert. If not given will be calculated using :func:`numpy.linalg.inv`. """ - n1, = self._get_tids_from_tags(where1, which='all') - n2, = self._get_tids_from_tags(where2, which='all') - T1, T2 = self.tensor_map[n1], self.tensor_map[n2] - bnd, = T1.bonds(T2) - - if Uinv is None: - Uinv = do('linalg.inv', U) - - # if we get wildly larger inverse due to singular U, try pseudo-inv - if vdot(Uinv, Uinv) / vdot(U, U) > 1 / tol: - Uinv = do('linalg.pinv', U, rcond=tol**0.5) - - # if still wildly larger inverse raise an error - if vdot(Uinv, Uinv) / vdot(U, U) > 1 / tol: - raise np.linalg.LinAlgError("Ill conditioned inverse.") - - T1.gate_(Uinv.T, bnd) - T2.gate_(U, bnd) + tid1, = self._get_tids_from_tags(where1, which='all') + tid2, = self._get_tids_from_tags(where2, which='all') + self._insert_gauge_tids(U, tid1, tid2, Uinv=Uinv, tol=tol) # ----------------------- contracting the network ----------------------- # @@ -5556,8 +5890,9 @@ def contract_tags(self, tags, inplace=False, which='any', **opts): Parameters ---------- tags : sequence of str - The list of tags to filter the tensors by. Use ``...`` - (``Ellipsis``) to contract all. + The list of tags to filter the tensors by. Use ``all`` or ``...`` + (``Ellipsis``) to contract all tensors. ``...`` will try and use a + 'structured' contract method if possible. inplace : bool, optional Whether to perform the contraction inplace. which : {'all', 'any'} @@ -5576,13 +5911,16 @@ def contract_tags(self, tags, inplace=False, which='any', **opts): untagged_tn, tagged_ts = self.partition_tensors( tags, inplace=inplace, which=which) + contracting_all = untagged_tn is None if not tagged_ts: raise ValueError("No tags were found - nothing to contract. " "(Change this to a no-op maybe?)") - contracted = tensor_contract(*tagged_ts, **opts) + contracted = tensor_contract( + *tagged_ts, preserve_tensor=not contracting_all, **opts + ) - if untagged_tn is None: + if contracting_all: return contracted untagged_tn.add_tensor(contracted, virtual=True) @@ -5745,16 +6083,16 @@ def __matmul__(self, other): def aslinearoperator(self, left_inds, right_inds, ldims=None, rdims=None, backend=None, optimize='auto'): """View this ``TensorNetwork`` as a - :class:`~quimb.tensor.tensor_contract.TNLinearOperator`. + :class:`~quimb.tensor.tensor_core.TNLinearOperator`. """ return TNLinearOperator(self, left_inds, right_inds, ldims, rdims, optimize=optimize, backend=backend) - def trace(self, left_inds, right_inds): + def trace(self, left_inds, right_inds, **contract_opts): """Trace over ``left_inds`` joined with ``right_inds`` """ tn = self.reindex({u: l for u, l in zip(left_inds, right_inds)}) - return tn.contract_tags(...) + return tn.contract_tags(..., **contract_opts) def to_dense(self, *inds_seq, to_qarray=True, **contract_opts): """Convert this network into an dense array, with a single dimension @@ -5875,6 +6213,26 @@ def outer_dims_inds(self): """ return tuple((self.ind_size(i), i) for i in self._outer_inds) + def compute_contracted_inds(self, *tids, output_inds=None): + """Get the indices describing the tensor contraction of tensors + corresponding to ``tids``. + """ + if output_inds is None: + output_inds = self._outer_inds + + # number of times each index appears on tensors + freqs = frequencies(concat( + self.tensor_map[tid].inds for tid in tids + )) + + return tuple( + ix for ix, c in freqs.items() if + # ind also appears elsewhere -> keep + (c != len(self.ind_map[ix])) or + # explicitly in output -> keep + (ix in output_inds) + ) + def squeeze(self, fuse=False, inplace=False): """Drop singlet bonds and dimensions from this tensor network. If ``fuse=True`` also fuse all multibonds between tensors. @@ -6072,6 +6430,7 @@ def rank_simplify( output_inds=None, equalize_norms=False, cache=None, + max_combinations=500, inplace=False, ): """Simplify this tensor network by performing contractions that don't @@ -6134,11 +6493,15 @@ def rank_simplify( # sorted list of unique indices to check -> start with lowly connected def rank_weight(ind): - return (tn.ind_size(ind), - -sum(tn.tensor_map[tid].ndim for tid in tn.ind_map[ind])) + return (tn.ind_size(ind), -sum(tn.tensor_map[tid].ndim + for tid in tn.ind_map[ind])) queue = oset(sorted(count, key=rank_weight)) + # number of tensors for which there will be more pairwise combinations + # than max_combinations + combi_cutoff = int(0.5 * ((8 * max_combinations + 1)**0.5 + 1)) + while queue: # get next index ind = queue.popright() @@ -6165,6 +6528,13 @@ def rank_weight(ind): # otherwise check pairwise contractions cands = [] + combos_checked = 0 + + if len(tids) > combi_cutoff: + # sort size of the tensors so that when we are limited by + # max_combinations we check likely ones first + tids = sorted(tids, key=lambda tid: tn.tensor_map[tid].ndim) + for tid_a, tid_b in itertools.combinations(tids, 2): ta = tn.tensor_map[tid_a] @@ -6174,6 +6544,8 @@ def rank_weight(ind): if cache_key in cache: continue + combos_checked += 1 + # work out the output indices of candidate contraction involved = frequencies(itertools.chain(ta.inds, tb.inds)) out_ab = [] @@ -6195,15 +6567,16 @@ def rank_weight(ind): else: cache.add(cache_key) - if cands and trivial: + if cands and (trivial or combos_checked > max_combinations): # can do contractions in any order + # ... or hyperindex is very large, stop checking break if not cands: # none of the parwise contractions reduce rank continue - score, tid_a, tid_b, out_ab, deincr = min(cands) + _, tid_a, tid_b, out_ab, deincr = min(cands) ta = tn._pop_tensor(tid_a) tb = tn._pop_tensor(tid_b) tab = ta.contract(tb, output_inds=out_ab) @@ -6286,7 +6659,7 @@ def diagonal_reduce( cache = set() if output_inds is None: - output_inds = set(self.outer_inds()) + output_inds = set(tn._outer_inds) queue = list(tn.tensor_map) while queue: @@ -6523,14 +6896,12 @@ def split_simplify( continue found = False - for r in range(1, t.ndim): - for lix in itertools.combinations(t.inds, r): - tl, tr = t.split(lix, get='tensors', cutoff=atol) - new_size = max(tl.size, tr.size) - if new_size < t.size: - found = True - break - if found: + for lix, rix in gen_bipartitions(t.inds): + tl, tr = t.split(lix, right_inds=rix, + get='tensors', cutoff=atol) + new_size = max(tl.size, tr.size) + if new_size < t.size: + found = True break if found: @@ -6563,41 +6934,10 @@ def gen_loops(self, max_loop_length=None): ------ tuple[int] """ - # start paths beginning at every node - queue = [(tid,) for tid in self.tensor_map] - seen = set() - while queue: - path = queue.pop(0) - # consider all the ways to extend each path - for tid_next in self._get_neighbor_tids(path[-1]): - tid0 = path[0] - # check for valid loop ... - if ( - # is not trivial - (len(path) > 2) and - # begins where is starts - (tid_next == tid0) and - # and is not just a cyclic permutation of existing loop - (frozenset(path) not in seen) - ): - yield tuple(sorted(path)) - seen.add(frozenset(path)) - if max_loop_length is None: - # automatically set the max loop length - max_loop_length = len(path) + 1 - - # path hits itself too early - elif tid_next in path: - continue - - # keep extending path, but only if - elif ( - # we haven't found any loops yet - (max_loop_length is None) or - # or this loops is short - (len(path) < max_loop_length) - ): - queue.append(path + (tid_next,)) + from cotengra.core import get_hypergraph + inputs = {tid: t.inds for tid, t in self.tensor_map.items()} + hg = get_hypergraph(inputs, accel='auto') + return hg.compute_loops(max_loop_length) def tids_are_connected(self, tids): """Check whether nodes ``tids`` are connected. @@ -6628,9 +6968,106 @@ def tids_are_connected(self, tids): return len(set(groups.values())) == 1 + def pair_simplify( + self, + cutoff=1e-12, + output_inds=None, + max_inds=10, + cache=None, + equalize_norms=False, + max_combinations=500, + inplace=False, + **split_opts, + ): + tn = self if inplace else self.copy() + + if output_inds is None: + output_inds = tn._outer_inds + + queue = list(tn.ind_map) + + def gen_pairs(): + # number of tensors for which there will be more pairwise + # combinations than max_combinations + combi_cutoff = int(0.5 * ((8 * max_combinations + 1)**0.5 + 1)) + + while queue: + ind = queue.pop() + try: + tids = tn.ind_map[ind] + except KeyError: + continue + + if len(tids) > combi_cutoff: + # sort size of the tensors so that when we are limited by + # max_combinations we check likely ones first + tids = sorted( + tids, key=lambda tid: tn.tensor_map[tid].ndim) + + for _, (tid1, tid2) in zip( + range(max_combinations), + itertools.combinations(tids, 2), + ): + if (tid1 in tn.tensor_map) and (tid2 in tn.tensor_map): + yield tid1, tid2 + + for pair in gen_pairs(): + + if cache is not None: + key = ('pc', frozenset((tid, id(tn.tensor_map[tid].data)) + for tid in pair)) + if key in cache: + continue + + t1, t2 = tn._tids_get(*pair) + inds = self.compute_contracted_inds(*pair, output_inds=output_inds) + + if len(inds) > max_inds: + # don't check exponentially many bipartitions + continue + + t12 = tensor_contract(t1, t2, output_inds=inds, + preserve_tensor=True) + current_size = t1.size + t2.size + + cands = [] + for lix, rix in gen_bipartitions(inds): + tl, tr = t12.split(left_inds=lix, right_inds=rix, + get='tensors', cutoff=cutoff, **split_opts) + new_size = (tl.size + tr.size) + if new_size < current_size: + cands.append((new_size / current_size, pair, tl, tr)) + + if not cands: + # no decompositions decrease the size + if cache is not None: + cache.add(key) + continue + + # perform the decomposition that minimizes the new size + _, pair, tl, tr = min(cands, key=lambda x: x[0]) + for tid in tuple(pair): + tn._pop_tensor(tid) + tn |= tl + tn |= tr + + tensor_fuse_squeeze(tl, tr) + if equalize_norms: + tn.strip_exponent(tl, equalize_norms) + tn.strip_exponent(tr, equalize_norms) + + queue.extend(tl.inds) + queue.extend(tr.inds) + + return tn + + pair_simplify_ = functools.partialmethod(pair_simplify, inplace=True) + def loop_simplify( self, + output_inds=None, max_loop_length=None, + max_inds=10, cutoff=1e-12, loops=None, cache=None, @@ -6665,6 +7102,9 @@ def loop_simplify( """ tn = self if inplace else self.copy() + if output_inds is None: + output_inds = tn._outer_inds + if loops is None: loops = tuple(tn.gen_loops(max_loop_length)) elif callable(loops): @@ -6681,34 +7121,27 @@ def loop_simplify( if key in cache: continue - tn_loop = tn._select_tids(loop) - oix = oset(tn_loop.outer_inds()) - current_size = sum(tn_loop.tensor_map[tid].size for tid in loop) - cands = [] + oix = tn.compute_contracted_inds(*loop, output_inds=output_inds) + if len(oix) > max_inds: + continue + oix = oset(oix) + + ts = tuple(tn._tids_get(*loop)) + current_size = sum(t.size for t in ts) + tloop = tensor_contract(*ts, output_inds=oix) - for tid_lefts, tid_rights in gen_bipartitions(loop): + cands = [] + for left_inds, right_inds in gen_bipartitions(oix): if not ( - tn.tids_are_connected(tid_lefts) and - tn.tids_are_connected(tid_rights) + tn.tids_are_connected(self._get_tids_from_inds(left_inds)) + and + tn.tids_are_connected(self._get_tids_from_inds(right_inds)) ): - # only group indices if they are contiguous in the graph continue - left_inds = oset( - concat(t.inds for t in tn._tids_get(*tid_lefts)) - ) & oix - right_inds = oset( - concat(t.inds for t in tn._tids_get(*tid_rights)) - ) & oix - - if (not left_inds) or (not right_inds): - continue - - # cast the loop as an operator and split it - tnlo = tn_loop.aslinearoperator(left_inds, right_inds) tl, tr = tensor_split( - tnlo, left_inds, right_inds=right_inds, get='tensors', - cutoff=cutoff, **split_opts + tloop, left_inds=left_inds, right_inds=right_inds, + get='tensors', cutoff=cutoff, **split_opts ) new_size = (tl.size + tr.size) @@ -6728,6 +7161,7 @@ def loop_simplify( tn |= tl tn |= tr + tensor_fuse_squeeze(tl, tr) if equalize_norms: tn.strip_exponent(tl, equalize_norms) tn.strip_exponent(tr, equalize_norms) @@ -6848,7 +7282,13 @@ def full_simplify( tn.split_simplify_(atol=atol, cache=cache, equalize_norms=equalize_norms) elif meth == 'L': - tn.loop_simplify_(cutoff=atol, cache=cache, + tn.loop_simplify_(output_inds=ix_o, cutoff=atol, + cache=cache, + equalize_norms=equalize_norms, + **loop_simplify_opts) + elif meth == 'P': + tn.pair_simplify_(output_inds=ix_o, cutoff=atol, + cache=cache, equalize_norms=equalize_norms, **loop_simplify_opts) else: diff --git a/quimb/tensor/tensor_gen.py b/quimb/tensor/tensor_gen.py index 5b86ccc9c..c99c22b55 100644 --- a/quimb/tensor/tensor_gen.py +++ b/quimb/tensor/tensor_gen.py @@ -10,7 +10,7 @@ import opt_einsum as oe from ..core import make_immutable, ikron -from ..utils import check_opt, deprecated, unique, concat +from ..utils import deprecated, unique, concat from ..gen.operators import ( spin_operator, eye, _gen_mbl_random_factors, ham_heis ) @@ -1043,16 +1043,15 @@ def HTN_from_cnf(fname, mode='parafac'): for line in f: args = line.split() - # get global info + # global info, don't need if args[0] == 'p': - num_vars = int(args[2]) + # num_vars = int(args[2]) # num_clauses = int(args[3]) continue - # ignore empty lines, comments and info line - if (not args) or (args == ['0']) or (args[0] in 'c%'): - continue - + # translate mc2021 style weight to normal + if args[:3] == ['c', 'p', 'weight']: + args = ('w', *args[3:5]) # variable weight if args[0] == 'w': sgn_var, w = args[1:] @@ -1064,6 +1063,10 @@ def HTN_from_cnf(fname, mode='parafac'): weighted.add(var) continue + # ignore empty lines, other comments and info line + if (not args) or (args == ['0']) or (args[0][0] in 'c%'): + continue + # clause tensor clause = tuple(map(int, filter(None, args[:-1]))) diff --git a/quimb/utils.py b/quimb/utils.py index 2757891f3..d8f1c3fc5 100644 --- a/quimb/utils.py +++ b/quimb/utils.py @@ -414,9 +414,10 @@ def gen_bipartitions(it): ``(1, 2), (3, 4)`` is considered the same as ``(3, 4), (1, 2)``. """ n = len(it) - for i in range(1, 2**(n - 1)): - bitstring_repr = f'{i:0>{n}b}' - l, r = [], [] - for b, x in zip(bitstring_repr, it): - (l if b == '0' else r).append(x) - yield l, r + if n: + for i in range(1, 2**(n - 1)): + bitstring_repr = f'{i:0>{n}b}' + l, r = [], [] + for b, x in zip(bitstring_repr, it): + (l if b == '0' else r).append(x) + yield l, r diff --git a/setup.cfg b/setup.cfg index 480a45930..9e5e0e20f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -14,3 +14,7 @@ versionfile_source = quimb/_version.py versionfile_build = quimb/_version.py tag_prefix = '' parentdir_prefix = . + +[pylama] +ignore = C901 +skip = versioneer.py diff --git a/setup.py b/setup.py index 91e162890..41d39acde 100644 --- a/setup.py +++ b/setup.py @@ -53,7 +53,7 @@ def readme(): ], 'docs': [ 'sphinx>=2.0', - 'pydata-sphinx-theme>=0.4', + 'sphinx-book-theme>=0.1', 'nbsphinx>=0.4', 'ipython>=7.0', 'autoray>=0.2.0', diff --git a/tests/test_core.py b/tests/test_core.py index 0ccc0a78a..e83eceaea 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -33,7 +33,7 @@ def test_vector_create(self): x = [1, 2, 3j] p = qu.qu(x, qtype='ket') assert(type(p) == qu.qarray) - assert(p.dtype == np.complex) + assert(p.dtype == complex) assert(p.shape == (3, 1)) p = qu.qu(x, qtype='bra') assert(p.shape == (1, 3)) @@ -43,7 +43,7 @@ def test_dop_create(self): x = np.random.randn(3, 3) p = qu.qu(x, qtype='dop') assert(type(p) == qu.qarray) - assert(p.dtype == np.complex) + assert(p.dtype == complex) assert(p.shape == (3, 3)) def test_convert_vector_to_dop(self): @@ -75,7 +75,7 @@ def test_sparse_create(self): assert(type(p) == qu.qarray) p = qu.qu(x, 'dop', sparse=True) assert(type(p) == sp.csr_matrix) - assert(p.dtype == np.complex) + assert(p.dtype == complex) assert(p.nnz == 2) def test_sparse_convert_to_dop(self): diff --git a/tests/test_gen/test_operators.py b/tests/test_gen/test_operators.py index 432dc40e1..f5a9cc306 100644 --- a/tests/test_gen/test_operators.py +++ b/tests/test_gen/test_operators.py @@ -58,7 +58,7 @@ def simple_ham_complex(sparse=None, stype=None, dtype=None): assert qu.issparse(H) == sparse assert qu.isdense(H) != sparse if sparse: - assert H.format == stype + assert H.format == stype with pytest.raises(ValueError): # check immutability H[0, 0] = 100 @@ -165,9 +165,10 @@ def test_fsim(self): def test_fsimg(self): assert_allclose( - qu.fsimg(- qu.pi / 2, 0.0, 0.0, 0.0, 0.0), - qu.iswap(), atol=1e-12 - ) + qu.fsimg(- qu.pi / 2, 0.0, 0.0, 0.0, 0.0), + qu.iswap(), atol=1e-12 + ) + class TestHamHeis: def test_ham_heis_2(self): diff --git a/tests/test_linalg/test_approx_spectral.py b/tests/test_linalg/test_approx_spectral.py index 85978c164..8a95f6bee 100644 --- a/tests/test_linalg/test_approx_spectral.py +++ b/tests/test_linalg/test_approx_spectral.py @@ -14,6 +14,7 @@ logneg, negativity, entropy, + can_use_mpi_pool, ) from quimb.utils import last @@ -33,10 +34,10 @@ norm_fro, norm_fro_approx, ) -from quimb.linalg import SLEPC4PY_FOUND - -MPI_PARALLEL = [False] + ([True] if SLEPC4PY_FOUND else []) +MPI_PARALLEL = [False] +if can_use_mpi_pool(): + MPI_PARALLEL.append(True) np.random.seed(42) diff --git a/tests/test_linalg/test_mpi_linalg.py b/tests/test_linalg/test_mpi_linalg.py index e0ed1910d..6a9e9e196 100644 --- a/tests/test_linalg/test_mpi_linalg.py +++ b/tests/test_linalg/test_mpi_linalg.py @@ -6,6 +6,7 @@ rand_herm, rand_ket, eigh, + can_use_mpi_pool, ) from quimb.linalg import SLEPC4PY_FOUND @@ -20,18 +21,13 @@ NUM_MPI_WORKERS, ) +slepc4py_test = pytest.mark.skipif( + not SLEPC4PY_FOUND, reason="No SLEPc4py installation") -slepc4py_notfound_msg = "No SLEPc4py installation" -slepc4py_test = pytest.mark.skipif(not SLEPC4PY_FOUND, - reason=slepc4py_notfound_msg) +mpipooltest = pytest.mark.skipif( + not can_use_mpi_pool(), reason="Not allowed to use MPI pool.") - -num_workers_to_try = [ - None, - 1, - 2, - 3, -] +num_workers_to_try = [None, 1, 2, 3] @pytest.fixture @@ -102,6 +98,7 @@ def test_svds(self, num_workers): @slepc4py_test +@mpipooltest class TestMPIPool: def test_spawning_pool_in_pool(self, bigsparsemat): from quimb.linalg.mpi_launcher import get_mpi_pool diff --git a/tests/test_tensor/test_circuit.py b/tests/test_tensor/test_circuit.py index 9cd5a6ed6..03bb15b39 100644 --- a/tests/test_tensor/test_circuit.py +++ b/tests/test_tensor/test_circuit.py @@ -438,6 +438,29 @@ def test_swappy_local_expecs(self): assert_allclose(exs, aps) + @pytest.mark.parametrize( + "name, densefn, nparam, nqubit", + [ + ('rx', qu.Rx, 1, 1), + ('ry', qu.Ry, 1, 1), + ('rz', qu.Rz, 1, 1), + ('u3', qu.U_gate, 3, 1), + ('fsim', qu.fsim, 2, 2), + ('fsimg', qu.fsimg, 5, 2), + ] + ) + def test_parametrized_gates_rx(self, name, densefn, nparam, nqubit): + k0 = qu.rand_ket(2**nqubit) + params = qu.randn(nparam) + kf = densefn(*params) @ k0 + k0mps = qtn.MatrixProductState.from_dense(k0, [2] * nqubit) + circ = qtn.Circuit(psi0=k0mps, gate_opts={'contract': False}) + getattr(circ, name)(*params, *range(nqubit), parametrize=True) + tn = circ.psi + assert isinstance(tn['GATE_0'], qtn.PTensor) + assert_allclose(circ.to_dense(), kf) + + class TestCircuitGen: @pytest.mark.parametrize( diff --git a/tests/test_tensor/test_optimizers.py b/tests/test_tensor/test_optimizers.py index f192df199..41257ed63 100644 --- a/tests/test_tensor/test_optimizers.py +++ b/tests/test_tensor/test_optimizers.py @@ -2,12 +2,14 @@ import importlib import pytest +import numpy as np from numpy.testing import assert_allclose from autoray import real import opt_einsum as oe import quimb as qu import quimb.tensor as qtn +from quimb.tensor.optimize import parse_network_to_backend, _get_tensor_data found_torch = importlib.util.find_spec('torch') is not None @@ -34,6 +36,45 @@ not found_torch, reason='pytorch not installed')) +@pytest.fixture +def tagged_qaoa_tn(): + """ + make qaoa tensor network, with RZZ and RX tagged on a per-round basis + so that these tags can be used as shared_tags to TNOptimizer + """ + + n = 8 + depth = 4 + terms = [(i, (i+1) % n) for i in range(n)] + gammas = qu.randn(depth) + betas = qu.randn(depth) + + # make circuit + circuit_opts = {'gate_opts': {'contract': False}} + circ = qtn.Circuit(n, **circuit_opts) + + # layer of hadamards to get into plus state + for i in range(n): + circ.apply_gate('H', i, gate_round=0) + + for d in range(depth): + for (i, j) in terms: + circ.apply_gate('RZZ', -gammas[d], i, j, gate_round=d, + parametrize=True) + + for i in range(n): + circ.apply_gate('RX', betas[d] * 2, i, gate_round=d, + parametrize=True) + + # tag circuit for shared_tags + tn_tagged = circ.psi.copy() + for i in range(depth): + tn_tagged.select(['RZZ', f'ROUND_{i}']).add_tag(f'p{2 * i}') + tn_tagged.select(['RX', f'ROUND_{i}']).add_tag(f'p{2 * i + 1}') + + return n, depth, tn_tagged + + @pytest.fixture def heis_pbc(): L = 10 @@ -214,3 +255,102 @@ def test_multiloss(backend, executor): if executor is not None: executor.shutdown() + + +def test_parse_network_to_backend_shared_tags(tagged_qaoa_tn): + n, depth, psi0 = tagged_qaoa_tn + + def to_constant(x): + return np.asarray(x) + + tags = [f'p{i}' for i in range(2 * depth)] + tn_tagged, variabes = parse_network_to_backend(psi0, + tags=tags, + shared_tags=tags, + to_constant=to_constant, + ) + # test number of variables identified + assert len(variabes) == 2 * depth + # each variable tag should be in n tensors + for i in range(len(tags)): + var_tag = f"__VARIABLE{i}__" + assert len(tn_tagged.select(var_tag).tensors) == n + + +def test_parse_network_to_backend_individual_tags(tagged_qaoa_tn): + n, depth, psi0 = tagged_qaoa_tn + + def to_constant(x): + return np.asarray(x) + + tags = [f'p{i}' for i in range(2*depth)] + tn_tagged, variabes = parse_network_to_backend( + psi0, tags=tags, to_constant=to_constant) + # test number of variables identified + assert len(variabes) == 2 * depth * n + # each variable tag should only be in 1 tensors + for i in range(len(tags)): + var_tag = f"__VARIABLE{i}__" + assert len(tn_tagged.select_tensors(var_tag)) == 1 + + +def test_parse_network_to_backend_constant_tags(tagged_qaoa_tn): + n, depth, psi0 = tagged_qaoa_tn + + def to_constant(x): + return np.asarray(x) + + # constant tags, include shared variable tags for first QAOA layer + constant_tags = ['PSI0', 'H', 'p0', 'p1'] + tn_tagged, variabes = parse_network_to_backend( + psi0, constant_tags=constant_tags, to_constant=to_constant) + + # test number of variables identified + assert len(variabes) == 2 * (depth - 1) * n + # each variable tag should only be in 1 tensors + for i in range(len(variabes)): + var_tag = f"__VARIABLE{i}__" + assert len(tn_tagged.select(var_tag).tensors) == 1 + + +@pytest.mark.parametrize('backend', [jax_case, autograd_case, + tensorflow_case]) +def test_shared_tags(tagged_qaoa_tn, backend): + n, depth, psi0 = tagged_qaoa_tn + + H = qu.ham_heis(n, j=(0., 0., -1.), b=(1., 0., 0.), cyclic=True,) + gs = qu.groundstate(H) + T_gs = qtn.Dense1D(gs).astype(complex) # tensorflow needs all same dtype + + def loss(psi, target): + f = psi.H & target + f.rank_simplify_() + return -abs(f ^ all) + + tags = [f'p{i}' for i in range(2 * depth)] + tnopt = qtn.TNOptimizer( + psi0, + loss_fn=loss, + tags=tags, + shared_tags=tags, + loss_constants={'target': T_gs}, + autodiff_backend=backend, + # loss_target=-0.99, + ) + + # run optimisation and test output + psi_opt = tnopt.optimize_basinhopping(n=10, nhop=5) + # assert sum(loss < -0.99 for loss in tnopt.losses) == 1 + assert qu.fidelity(psi_opt.to_dense(), gs) > 0.99 + + # test dimension of optimisation space + assert tnopt.res.x.size == 2*depth + + # examine tensors inside optimised TN and check sharing was done + for tag in tags: + test_data = None + for t in psi_opt.select_tensors(tag): + if test_data is None: + test_data = _get_tensor_data(t) + else: + assert_allclose(test_data, _get_tensor_data(t)) diff --git a/tests/test_tensor/test_tensor_2d.py b/tests/test_tensor/test_tensor_2d.py index ba452eee5..f761d2a3d 100644 --- a/tests/test_tensor/test_tensor_2d.py +++ b/tests/test_tensor/test_tensor_2d.py @@ -164,51 +164,66 @@ class Test2DContract: def test_contract_2d_one_layer_boundary(self): psi = qtn.PEPS.rand(4, 4, 3, seed=42) - norm = psi.H & psi + norm = psi.make_norm() xe = norm.contract(all, optimize='auto-hq') xt = norm.contract_boundary(max_bond=9) assert xt == pytest.approx(xe, rel=1e-2) def test_contract_2d_two_layer_boundary(self): psi = qtn.PEPS.rand(4, 4, 3, seed=42, tags='KET') - norm = psi.retag({'KET': 'BRA'}).H | psi + norm = psi.make_norm() xe = norm.contract(all, optimize='auto-hq') xt = norm.contract_boundary(max_bond=27, layer_tags=['KET', 'BRA']) assert xt == pytest.approx(xe, rel=1e-2) - @pytest.mark.parametrize("two_layer", [False, True]) - def test_compute_row_envs(self, two_layer): + def test_contract_2d_full_bond(self): + psi = qtn.PEPS.rand(4, 4, 3, seed=42, tags='KET') + norm = psi.make_norm() + xe = norm.contract(all, optimize='auto-hq') + xt = norm.contract_boundary(max_bond=27, mode='full-bond') + assert xt == pytest.approx(xe, rel=1e-2) + + @pytest.mark.parametrize("mode,two_layer", [ + ('mps', False), + ('mps', True), + ('full-bond', False), + ]) + def test_compute_row_envs(self, mode, two_layer): psi = qtn.PEPS.rand(5, 4, 2, seed=42, tags='KET') - norm = psi.retag({'KET': 'BRA'}).H | psi + norm = psi.make_norm() ex = norm.contract(all) if two_layer: - compress_opts = {'cutoff': 1e-6, 'max_bond': 12, + compress_opts = {'cutoff': 1e-6, 'max_bond': 12, 'mode': mode, 'layer_tags': ['KET', 'BRA']} else: - compress_opts = {'cutoff': 1e-6, 'max_bond': 8} + compress_opts = {'cutoff': 1e-6, 'max_bond': 8, 'mode': mode} row_envs = norm.compute_row_environments(**compress_opts) for i in range(norm.Lx): norm_i = ( - row_envs['below', i] & + row_envs['bottom', i] & norm.select(norm.row_tag(i)) & - row_envs['above', i] + row_envs['top', i] ) x = norm_i.contract(all) assert x == pytest.approx(ex, rel=1e-2) - @pytest.mark.parametrize("two_layer", [False, True]) - def test_compute_col_envs(self, two_layer): + @pytest.mark.parametrize("mode,two_layer", [ + ('mps', False), + ('mps', True), + ('full-bond', False), + ]) + def test_compute_col_envs(self, mode, two_layer): psi = qtn.PEPS.rand(4, 5, 2, seed=42, tags='KET') norm = psi.retag({'KET': 'BRA'}).H | psi ex = norm.contract(all) if two_layer: - compress_opts = {'cutoff': 1e-6, 'max_bond': 12, + compress_opts = {'cutoff': 1e-6, 'max_bond': 12, 'mode': mode, 'layer_tags': ['KET', 'BRA']} else: - compress_opts = {'cutoff': 1e-6, 'max_bond': 8} + compress_opts = {'cutoff': 1e-6, 'max_bond': 8, 'mode': mode} col_envs = norm.compute_col_environments(**compress_opts) for j in range(norm.Lx): @@ -229,7 +244,8 @@ def test_normalize(self): assert norm == pytest.approx(1.0, rel=0.01) @pytest.mark.parametrize('normalized', [False, True]) - def test_compute_local_expectation_one_sites(self, normalized): + @pytest.mark.parametrize('mode', ['mps', 'full-bond']) + def test_compute_local_expectation_one_sites(self, mode, normalized): peps = qtn.PEPS.rand(4, 3, 2, seed=42, dtype='complex') # reference @@ -245,12 +261,13 @@ def test_compute_local_expectation_one_sites(self, normalized): opts = dict(cutoff=2e-3, max_bond=9, contract_optimize='random-greedy') e = peps.compute_local_expectation( - terms, normalized=normalized, **opts) + terms, mode=mode, normalized=normalized, **opts) assert e == pytest.approx(ex, rel=1e-2) @pytest.mark.parametrize('normalized', [False, True]) - def test_compute_local_expectation_two_sites(self, normalized): + @pytest.mark.parametrize('mode', ['mps', 'full-bond']) + def test_compute_local_expectation_two_sites(self, mode, normalized): H = qu.ham_heis_2D(4, 3, sparse=True) Hij = qu.ham_heis(2, cyclic=False) @@ -261,23 +278,26 @@ def test_compute_local_expectation_two_sites(self, normalized): qu.normalize(k) ex = qu.expec(H, k) - opts = dict(cutoff=2e-3, max_bond=9, contract_optimize='random-greedy') + opts = dict( + mode=mode, + normalized=normalized, + cutoff=2e-3, + max_bond=16, + contract_optimize='random-greedy' + ) # compute 2x1 and 1x2 plaquettes separately hterms = {coos: Hij for coos in peps.gen_horizontal_bond_coos()} vterms = {coos: Hij for coos in peps.gen_vertical_bond_coos()} - he = peps.compute_local_expectation( - hterms, normalized=normalized, **opts) - ve = peps.compute_local_expectation( - vterms, normalized=normalized, **opts) + he = peps.compute_local_expectation(hterms, **opts) + ve = peps.compute_local_expectation(vterms, **opts) assert he + ve == pytest.approx(ex, rel=1e-2) # compute all terms in 2x2 plaquettes terms_all = {**hterms, **vterms} - e = peps.compute_local_expectation( - terms_all, normalized=normalized, autogroup=False, **opts) + e = peps.compute_local_expectation(terms_all, autogroup=False, **opts) assert e == pytest.approx(ex, rel=1e-2) From 2ae66ee09168e072c936096992aefb2d89bde76e Mon Sep 17 00:00:00 2001 From: Reza Haghshenas Date: Wed, 25 Aug 2021 15:20:46 -0700 Subject: [PATCH 15/64] fixed remaining --- quimb/gen/operators.py | 43 +++++++++++++-------------- quimb/tensor/optimize.py | 64 +--------------------------------------- 2 files changed, 22 insertions(+), 85 deletions(-) diff --git a/quimb/gen/operators.py b/quimb/gen/operators.py index f16c8f657..6c58219de 100644 --- a/quimb/gen/operators.py +++ b/quimb/gen/operators.py @@ -352,7 +352,6 @@ def fsim(theta, phi, dtype=complex, **kwargs): return gate -<<<<<<< HEAD def fsimt(theta, dtype=complex, **kwargs): r"""The 'fermionic simulation' gate: @@ -398,27 +397,27 @@ def fsimg(theta, zeta, chi, gamma, phi, dtype=complex, **kwargs): ======= @functools.lru_cache(maxsize=256) def fsimg(theta, zeta, chi, gamma, phi, dtype=complex, **kwargs): - r"""The 'fermionic simulation' gate, with: - - * :math:`\theta` is the iSWAP angle - * :math:`\phi` is the controlled-phase angle - * :math:`\zeta, \chi, \gamma` are single-qubit phase angles. - - .. math:: - \mathrm{fsimg}(\theta, \zeta, \chi, \gamma, \phi) = - \begin{bmatrix} - 1 & 0 & 0 & 0\\ - 0 & \exp(-i(\gamma +\zeta )) \cos(\theta) & - -i \exp(-i(\gamma - \chi )) sin(\theta) & 0\\ - 0 & -i \exp(-i(\gamma + \chi )) sin(\theta) & - \exp(-i(\gamma - \zeta )) \cos(\theta) & 0\\ - 0 & 0 & 0 & \exp(-i (\phi +2 \gamma)) - \end{bmatrix} - - See Equation 18 of https://arxiv.org/abs/2010.07965. Note that ``theta``, - ``phi``, ``zeta``, ``chi``, ``gamma`` should be specified in radians and - the sign convention with this gate varies. Here for example, - ``fsimg(- pi / 2, 0, 0, 0,0) == iswap()``. +# r"The 'fermionic simulation' gate, with: + +# * :math:`\theta` is the iSWAP angle +# * :math:`\phi` is the controlled-phase angle +# * :math:`\zeta, \chi, \gamma` are single-qubit phase angles. + +# .. math:: +# \mathrm{fsimg}(\theta, \zeta, \chi, \gamma, \phi) = +# \begin{bmatrix} +# 1 & 0 & 0 & 0\\ +# 0 & \exp(-i(\gamma +\zeta )) \cos(\theta) & +# -i \exp(-i(\gamma - \chi )) sin(\theta) & 0\\ +# 0 & -i \exp(-i(\gamma + \chi )) sin(\theta) & +# \exp(-i(\gamma - \zeta )) \cos(\theta) & 0\\ +# 0 & 0 & 0 & \exp(-i (\phi +2 \gamma)) +# \end{bmatrix} + +# See Equation 18 of https://arxiv.org/abs/2010.07965. Note that ``theta``, +# ``phi``, ``zeta``, ``chi``, ``gamma`` should be specified in radians and +# the sign convention with this gate varies. Here for example, +# ``fsimg(- pi / 2, 0, 0, 0,0) == iswap()``. """ from cmath import cos, sin, exp diff --git a/quimb/tensor/optimize.py b/quimb/tensor/optimize.py index 46f42757e..dbc4b271a 100644 --- a/quimb/tensor/optimize.py +++ b/quimb/tensor/optimize.py @@ -1136,20 +1136,7 @@ def optimizer(self, x): else: self._method = self.optimizer -<<<<<<< HEAD -======= - @property - def bounds(self): - return self._bounds - - @bounds.setter - def bounds(self, x): - if x is not None: - self._bounds = np.array((x,) * self.vectorizer.d) - else: - self._bounds = None ->>>>>>> develop def get_tn_opt(self): """Extract the optimized tensor network, this is a three part process: @@ -1163,43 +1150,6 @@ def get_tn_opt(self): tn_opt : TensorNetwork """ arrays = tuple(map(self.handler.to_constant, self.vectorizer.unpack())) -<<<<<<< HEAD - inject_(arrays, self.tn_opt) - tn = self.norm_fn(self.tn_opt.copy()) - tn.drop_tags(t for t in tn.tags if variable_finder.match(t)) - - for t in tn: - if isinstance(t, PTensor): - t.params = to_numpy(t.params) - else: - t.modify(data=to_numpy(t.data)) - - return tn - - def optimize(self, n, tol=None, **options): - """Run the optimizer for ``n`` function evaluations, using - :func:`scipy.optimize.minimize` as the driver for the vectorized - computation. - - Parameters - ---------- - n : int - Notionally the maximum number of iterations for the optimizer, note - that depending on the optimizer being used, this may correspond to - number of function evaluations rather than just iterations. - tol : None or float, optional - Tolerance for convergence, note that various more specific - tolerances can usually be supplied to ``options``, depending on - the optimizer being used. - options - Supplied to :func:`scipy.optimize.minimize`. - - Returns - ------- - tn_opt : TensorNetwork - """ - from scipy.optimize import minimize -======= inject_(arrays, self._tn_opt) tn = self.norm_fn(self._tn_opt.copy()) tn.drop_tags(t for t in tn.tags if variable_finder.match(t)) @@ -1209,7 +1159,6 @@ def optimize(self, n, tol=None, **options): t.params = to_numpy(t.params) else: t.modify(data=to_numpy(t.data), left_inds=t.left_inds) ->>>>>>> develop return tn @@ -1275,9 +1224,6 @@ def optimize( return self.get_tn_opt() -<<<<<<< HEAD - def optimize_basinhopping(self, n, nhop, temperature=1.0, **options): -======= def optimize_basinhopping( self, n, @@ -1287,7 +1233,6 @@ def optimize_basinhopping( hessp=False, **options ): ->>>>>>> develop """Run the optimizer for using :func:`scipy.optimize.basinhopping` as the driver for the vectorized computation. This performs ``nhop`` local optimization each with ``n`` iterations. @@ -1321,12 +1266,8 @@ def optimize_basinhopping( x0=self.vectorizer.vector, niter=nhop, minimizer_kwargs=dict( -<<<<<<< HEAD - jac=True, -======= jac=jac, hessp=self.vectorized_hessp if hessp else None, ->>>>>>> develop method=self._method, bounds=self.bounds, options=dict(maxiter=n, **options) @@ -1500,7 +1441,4 @@ def optimize_nevergrad(self, n): return self.get_tn_opt() -<<<<<<< HEAD - return self.get_tn_opt() -======= ->>>>>>> develop + From 5ca3c0e3d5808f21107fef8e4d46702e30fb44ec Mon Sep 17 00:00:00 2001 From: Reza Haghshenas Date: Thu, 2 Sep 2021 09:37:21 -0700 Subject: [PATCH 16/64] fixed merge --- quimb/tensor/tensor_core.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/quimb/tensor/tensor_core.py b/quimb/tensor/tensor_core.py index 2dbec7100..628074432 100644 --- a/quimb/tensor/tensor_core.py +++ b/quimb/tensor/tensor_core.py @@ -4453,6 +4453,19 @@ def _compute_bond_env( **ensure_dict(contract_around_opts)) elif method == 'contract_compressed': + + +# print ("visualization") +# import sys +# sys.path.append('/home/reza/Dropbox/Prog/MERA/') +# from visarbgeom import vis_contract_compressed +# +# vis_contract_compressed(tn_env, max_bond=max_bond, cutoff=cutoff, +# **ensure_dict(contract_compressed_opts)) + + + + tn_env.contract_compressed_( max_bond=max_bond, cutoff=cutoff, **ensure_dict(contract_compressed_opts)) From 841d13c13147e9326ddb6c801828e94b1a9e7b92 Mon Sep 17 00:00:00 2001 From: Reza Haghshenas Date: Mon, 7 Mar 2022 13:56:27 -0800 Subject: [PATCH 17/64] tag_share added to circuit class --- quimb/tensor/circuit.py | 4 +- quimb/tensor/optimize.py | 98 +++++++++++++++++++++++----------------- 2 files changed, 59 insertions(+), 43 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index 5e88e1a58..ed79ac3ff 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -867,7 +867,7 @@ def apply_gate_raw(self, U, where, tags=None, self._psi.gate_(U, where, tags=tags, **opts) self.gates.append((id(U), *where)) - def apply_gate(self, gate_id, *gate_args, gate_round=None, **gate_opts): + def apply_gate(self, gate_id, *gate_args, gate_round=None,gate_shared=None, **gate_opts): """Apply a single gate to this tensor network quantum circuit. If ``gate_round`` is supplied the tensor(s) added will be tagged with ``'ROUND_{gate_round}'``. Alternatively, putting an integer first like @@ -896,6 +896,8 @@ def apply_gate(self, gate_id, *gate_args, gate_round=None, **gate_opts): # unique tag tags = tags_to_oset(f'GATE_{len(self.gates)}') + if (gate_shared is not None): + tags.add(f'{gate_shared}') # parse which 'round' of gates if (gate_round is not None): diff --git a/quimb/tensor/optimize.py b/quimb/tensor/optimize.py index dbc4b271a..f612a9305 100644 --- a/quimb/tensor/optimize.py +++ b/quimb/tensor/optimize.py @@ -1315,48 +1315,62 @@ def optimize_nlopt( """ import nlopt - try: - self._maybe_init_pbar(n) - - def f(x, grad): - self.vectorizer.vector[:] = x - arrays = self.vectorizer.unpack() - if grad.size > 0: - result, grads = self.handler.value_and_grad(arrays) - grad[:] = self.vectorizer.pack(grads, 'grad') - else: - result = self.handler.value(arrays) - self._n += 1 - self.loss = result.item() - self.losses.append(self.loss) - self._maybe_update_pbar() - return self.loss - - opt = nlopt.opt(getattr(nlopt, self.optimizer), self.d) - opt.set_min_objective(f) - opt.set_maxeval(n) - - if self.bounds is not None: - opt.set_lower_bounds(self.bounds[:, 0]) - opt.set_upper_bounds(self.bounds[:, 1]) - - if self.loss_target is not None: - opt.set_stopval(self.loss_target) - if ftol_rel is not None: - opt.set_ftol_rel(ftol_rel) - if ftol_abs is not None: - opt.set_ftol_abs(ftol_abs) - if xtol_rel is not None: - opt.set_xtol_rel(xtol_rel) - if xtol_abs is not None: - opt.set_xtol_abs(xtol_abs) - - self.vectorizer.vector[:] = opt.optimize(self.vectorizer.vector) - - except (KeyboardInterrupt, RuntimeError): - pass - finally: - self._maybe_close_pbar() + #try: + self._maybe_init_pbar(n) + + def f(x, grad): + self.vectorizer.vector[:] = x + arrays = self.vectorizer.unpack() + if grad.size > 0: + result, grads = self.handler.value_and_grad(arrays) + grad[:] = self.vectorizer.pack(grads, 'grad') + else: + result = self.handler.value(arrays) + self._n += 1 + self.loss = result.item() + self.losses.append(self.loss) + self._maybe_update_pbar() + return self.loss + + + #opt=opt = nlopt.opt(nlopt.LD_LBFGS, self.d) + #print ( self.optimizer.upper() ) + opt = nlopt.opt(getattr(nlopt, self.optimizer.upper()), self.d) + opt.set_min_objective(f) + opt.set_maxeval(n) + #opt.set_vector_storage(22) + opt.set_maxtime(-1) + print ( "M", self.optimizer.upper(), opt.get_vector_storage() , opt.get_maxeval(), opt.get_maxtime(), opt.get_xtol_rel(), opt.get_ftol_abs(), + opt.get_maxtime() ) + + + + if self.bounds is not None: + opt.set_lower_bounds(self.bounds[:, 0]) + opt.set_upper_bounds(self.bounds[:, 1]) + + if self.loss_target is not None: + opt.set_stopval(self.loss_target) + if ftol_rel is not None: + opt.set_ftol_rel(ftol_rel) + if ftol_abs is not None: + opt.set_ftol_abs(ftol_abs) + if xtol_rel is not None: + opt.set_xtol_rel(xtol_rel) + if xtol_abs is not None: + opt.set_xtol_abs(xtol_abs) + + print ("Hi") + self.vectorizer.vector[:] = opt.optimize(self.vectorizer.vector) + opt_val = opt.last_optimum_value() + result = opt.last_optimize_result() + print ("info", opt_val, result) + + + # except (KeyboardInterrupt, RuntimeError): + # pass + # finally: + # self._maybe_close_pbar() return self.get_tn_opt() From 05c45c68f21af0f79c66733c93d65f59c88b1c4e Mon Sep 17 00:00:00 2001 From: Reza Haghshenas Date: Tue, 15 Mar 2022 08:25:09 -0700 Subject: [PATCH 18/64] added FSIMT --- quimb/gen/operators.py | 8 ++++---- quimb/tensor/circuit.py | 23 ++++++++++------------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/quimb/gen/operators.py b/quimb/gen/operators.py index 7e9bc3c87..cf647460c 100644 --- a/quimb/gen/operators.py +++ b/quimb/gen/operators.py @@ -360,8 +360,8 @@ def fsimt(theta, dtype=complex, **kwargs): \mathrm{fsim}(\theta, \phi) = \begin{bmatrix} 1 & 0 & 0 & 0\\ - 0 & \cos(\theta) & -i sin(\theta) & 0\\ - 0 & -i sin(\theta) & \cos(\theta) & 0\\ + 0 & \cos(\theta) & sin(\theta) & 0\\ + 0 & - sin(\theta) & \cos(\theta) & 0\\ 0 & 0 & 0 & \exp(-i \phi) \end{bmatrix} @@ -372,10 +372,10 @@ def fsimt(theta, dtype=complex, **kwargs): from cmath import cos, sin, exp a = cos(theta) - b = 1j * sin(theta) + b = sin(theta) gate = [[1, 0, 0, 0], [0, a, b, 0], - [0, b, a, 0], + [0, -b, a, 0], [0, 0, 0, 1]] gate = qu(gate, dtype=dtype, **kwargs) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index e3ce4c1d6..9fa4169db 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -407,23 +407,19 @@ def fsimt_param_gen(params): a_im = do('imag', a_re) a = do('complex', a_re, a_im) - b_im = do('sin', theta) - b_re = do('imag', b_im) + b_re = do('sin', theta) + b_im = do('imag', b_re) b = do('complex', b_re, b_im) - - data = [[[[1, 0], [0, 0]], [[0, a], [b, 0]]], - [[[0, b], [a, 0]], + [[[0, -b], [a, 0]], [[0, 0], [0, 1]]]] return do('array', data, like=params) - - def apply_fsim(psi, theta, phi, i, j, parametrize=False, **gate_opts): mtags = _merge_tags('FSIM', gate_opts) if parametrize: @@ -433,7 +429,7 @@ def apply_fsim(psi, theta, phi, i, j, parametrize=False, **gate_opts): psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) -def apply_fsimt(psi,theta, i, j, parametrize=False, **gate_opts): +def apply_fsimt(psi, theta, i, j, parametrize=False, **gate_opts): mtags = _merge_tags('FSIMT', gate_opts) if parametrize: G = ops.PArray(fsimt_param_gen, (theta,)) @@ -442,9 +438,6 @@ def apply_fsimt(psi,theta, i, j, parametrize=False, **gate_opts): psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) - - - def fsimg_param_gen(params): theta, zeta, chi, gamma, phi = ( params[0], params[1], params[2], params[3], params[4] @@ -659,7 +652,7 @@ def apply_su4( ONE_QUBIT_PARAM_GATES = {'RX', 'RY', 'RZ', 'U3', 'U2', 'U1'} TWO_QUBIT_PARAM_GATES = { - 'CU3', 'CU2', 'CU1', 'FS', 'FSIM', 'FSIMG', 'RZZ', 'SU4' + 'CU3', 'CU2', 'CU1', 'FS', 'FSIM', 'FSIMT', 'FSIMG', 'RZZ', 'SU4' } ALL_PARAM_GATES = ONE_QUBIT_PARAM_GATES | TWO_QUBIT_PARAM_GATES @@ -868,7 +861,7 @@ def apply_gate_raw(self, U, where, tags=None, self._psi.gate_(U, where, tags=tags, **opts) self.gates.append((id(U), *where)) - def apply_gate(self, gate_id, *gate_args, gate_round=None,gate_shared=None, **gate_opts): + def apply_gate(self, gate_id, *gate_args, gate_round=None, gate_shared=None, **gate_opts): """Apply a single gate to this tensor network quantum circuit. If ``gate_round`` is supplied the tensor(s) added will be tagged with ``'ROUND_{gate_round}'``. Alternatively, putting an integer first like @@ -1047,6 +1040,10 @@ def fsim(self, theta, phi, i, j, gate_round=None, parametrize=False): self.apply_gate('FSIM', theta, phi, i, j, gate_round=gate_round, parametrize=parametrize) + def fsim(self, theta, i, j, gate_round=None, parametrize=False): + self.apply_gate('FSIMT', theta, i, j, + gate_round=gate_round, parametrize=parametrize) + def fsimg(self, theta, zeta, chi, gamma, phi, i, j, gate_round=None, parametrize=False): self.apply_gate('FSIMG', theta, zeta, chi, gamma, phi, i, j, From e819d22de156c93ed6a4ba73e9de874f5013af55 Mon Sep 17 00:00:00 2001 From: Reza Haghshenas Date: Tue, 22 Mar 2022 16:50:38 -0700 Subject: [PATCH 19/64] add self.copy() --- quimb/tensor/array_ops.py | 1 + 1 file changed, 1 insertion(+) diff --git a/quimb/tensor/array_ops.py b/quimb/tensor/array_ops.py index 1b179076f..73fa1e931 100644 --- a/quimb/tensor/array_ops.py +++ b/quimb/tensor/array_ops.py @@ -484,6 +484,7 @@ def __init__(self, fn, params, shape=None): self._shape_fn_id = id(fn) def copy(self): + self.data new = PArray(self.fn, self.params, self.shape) new._data = self._data # for efficiency return new From d8cda89f6858938dbe0df7202f15cc0dc74bb492 Mon Sep 17 00:00:00 2001 From: Reza Haghshenas Date: Tue, 29 Mar 2022 09:20:28 -0700 Subject: [PATCH 20/64] added qubit reuse and optimizer clean --- quimb/gen/operators.py | 60 +++++-------- quimb/tensor/circuit.py | 180 ++++++++++++++++++++++++++++++++++----- quimb/tensor/optimize.py | 6 +- 3 files changed, 185 insertions(+), 61 deletions(-) diff --git a/quimb/gen/operators.py b/quimb/gen/operators.py index cf647460c..082df9a36 100644 --- a/quimb/gen/operators.py +++ b/quimb/gen/operators.py @@ -383,55 +383,41 @@ def fsimt(theta, dtype=complex, **kwargs): return gate +@functools.lru_cache(maxsize=256) +def fsimg(theta, zeta, chi, gamma, phi, dtype=complex, **kwargs): + r"""The 'fermionic simulation' gate, with: + * :math:`\theta` is the iSWAP angle + * :math:`\phi` is the controlled-phase angle + * :math:`\zeta, \chi, \gamma` are single-qubit phase angles. + .. math:: + \mathrm{fsimg}(\theta, \zeta, \chi, \gamma, \phi) = + \begin{bmatrix} + 1 & 0 & 0 & 0\\ + 0 & \exp(-i(\gamma +\zeta )) \cos(\theta) & + -i \exp(-i(\gamma - \chi )) sin(\theta) & 0\\ + 0 & -i \exp(-i(\gamma + \chi )) sin(\theta) & + \exp(-i(\gamma - \zeta )) \cos(\theta) & 0\\ + 0 & 0 & 0 & \exp(-i (\phi +2 \gamma)) + \end{bmatrix} - - - -def fsimg(theta, zeta, chi, gamma, phi, dtype=complex, **kwargs): - r"""The 'fermionic simulation' gate: - \theta is the iSWAP angle - \phi is the controlled-phase angle - \Zeta, \chi, \gamma are single-qubit phase angles -======= -@functools.lru_cache(maxsize=256) -def fsimg(theta, zeta, chi, gamma, phi, dtype=complex, **kwargs): -# r"The 'fermionic simulation' gate, with: - -# * :math:`\theta` is the iSWAP angle -# * :math:`\phi` is the controlled-phase angle -# * :math:`\zeta, \chi, \gamma` are single-qubit phase angles. - -# .. math:: -# \mathrm{fsimg}(\theta, \zeta, \chi, \gamma, \phi) = -# \begin{bmatrix} -# 1 & 0 & 0 & 0\\ -# 0 & \exp(-i(\gamma +\zeta )) \cos(\theta) & -# -i \exp(-i(\gamma - \chi )) sin(\theta) & 0\\ -# 0 & -i \exp(-i(\gamma + \chi )) sin(\theta) & -# \exp(-i(\gamma - \zeta )) \cos(\theta) & 0\\ -# 0 & 0 & 0 & \exp(-i (\phi +2 \gamma)) -# \end{bmatrix} - -# See Equation 18 of https://arxiv.org/abs/2010.07965. Note that ``theta``, -# ``phi``, ``zeta``, ``chi``, ``gamma`` should be specified in radians and -# the sign convention with this gate varies. Here for example, -# ``fsimg(- pi / 2, 0, 0, 0,0) == iswap()``. + See Equation 18 of https://arxiv.org/abs/2010.07965. Note that ``theta``, + ``phi``, ``zeta``, ``chi``, ``gamma`` should be specified in radians and + the sign convention with this gate varies. Here for example, + ``fsimg(- pi / 2, 0, 0, 0,0) == iswap()``. """ from cmath import cos, sin, exp a1 = exp(-1j * (gamma + zeta)) * cos(theta) a2 = exp(-1j * (gamma - zeta)) * cos(theta) - a1 = exp(1j * (-gamma + phi + zeta)) * sin(theta) - a2 = -1 * exp(1j * -(gamma + phi + zeta)) * sin(theta) + b1 = -1j * exp(-1j * (gamma - chi)) * sin(theta) + b2 = -1j * exp(-1j * (gamma + chi)) * sin(theta) c = exp(-1j * (phi + 2 * gamma)) - c = exp(1j * (gamma - phi)) - - gate = [[a00, 0, 0, 0], + gate = [[1, 0, 0, 0], [0, a1, b1, 0], [0, b2, a2, 0], [0, 0, 0, c]] diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index 9fa4169db..dada28ccf 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -401,6 +401,7 @@ def fsim_param_gen(params): return ops.asarray(data) + def fsimt_param_gen(params): theta = params[0] a_re = do('cos', theta) @@ -413,13 +414,12 @@ def fsimt_param_gen(params): data = [[[[1, 0], [0, 0]], [[0, a], [b, 0]]], - [[[0, -b], [a, 0]], + [[[0, -b], [a, 0]], [[0, 0], [0, 1]]]] return do('array', data, like=params) - def apply_fsim(psi, theta, phi, i, j, parametrize=False, **gate_opts): mtags = _merge_tags('FSIM', gate_opts) if parametrize: @@ -443,15 +443,7 @@ def fsimg_param_gen(params): params[0], params[1], params[2], params[3], params[4] ) - e00_im = (gamma + phi) - e00_re = do('imag', e00_im) - e00 = do('exp', do('complex', e00_re, e00_im)) - - c_im = (gamma - phi) - c_re = do('imag', c_im) - c = do('exp', do('complex', c_re, c_im)) - - a11_re = do('sin', theta) + a11_re = do('cos', theta) a11_im = do('imag', a11_re) a11 = do('complex', a11_re, a11_im) @@ -459,8 +451,7 @@ def fsimg_param_gen(params): e11_re = do('imag', e11_im) e11 = do('exp', do('complex', e11_re, e11_im)) - - a22_re = -do('sin', theta) + a22_re = do('cos', theta) a22_im = do('imag', a22_re) a22 = do('complex', a22_re, a22_im) @@ -468,21 +459,19 @@ def fsimg_param_gen(params): e22_re = do('imag', e22_im) e22 = do('exp', do('complex', e22_re, e22_im)) - - - a21_re = do('cos', theta) + a21_re = do('sin', theta) a21_im = do('imag', a21_re) a21 = do('complex', a21_re, a21_im) - e21_im = -(gamma + phi + chi) + e21_im = -(gamma - chi) e21_re = do('imag', e21_im) e21 = do('exp', do('complex', e21_re, e21_im)) - a12_re = do('cos', theta) + a12_re = do('sin', theta) a12_im = do('imag', a12_re) a12 = do('complex', a12_re, a12_im) - e12_im = (-gamma + phi + chi) + e12_im = -(gamma + chi) e12_re = do('imag', e12_im) e12 = do('exp', do('complex', e12_re, e12_im)) @@ -861,7 +850,54 @@ def apply_gate_raw(self, U, where, tags=None, self._psi.gate_(U, where, tags=tags, **opts) self.gates.append((id(U), *where)) - def apply_gate(self, gate_id, *gate_args, gate_round=None, gate_shared=None, **gate_opts): + # the gates exist in psi with format "GATE_{}" + def partial_gates(self, psi): + rex = re.compile("GATE_{}".format(r"\d+")) + return list(filter(rex.match, psi.tags)) + + # map inds list to corresponding qubits + def qubit_map(self, psi, inds): + Q_l = [] + for i in inds: + tn = [psi.tensor_map[tid] for tid in psi.ind_map[i]] + rex = re.compile(self.psi.site_tag_id.format(r"\d+")) + tags_tn = list(filter(rex.match, tn[0].tags)) + temp = re.findall(r'\d+', *tags_tn) + res = list(map(int, temp)) + Q_l.append(res[0]) + return Q_l + + # map inds list to corresponding qubits + def qubits_in_light_cone(self, psi): + ind_open = psi.outer_inds() + ind_open_virtual = [x for x in ind_open if not x.startswith('k')] + ind_open_physical = list(oset(ind_open)-oset(ind_open_virtual)) + + q_virtual = self.qubit_map(psi, ind_open_virtual) + q_physical = self.qubit_map(psi, ind_open_physical) + + return q_virtual, q_physical + + + + + + + + def gate_map(self): + dic = {} + for i, gate in enumerate(self.gates): + if isinstance(gate[-2], numbers.Integral): + regs = tuple(gate[-2:]) + else: + regs = tuple(gate[-1:]) + + dic.update({f'GATE_{i}': regs}) + + return dic + + def apply_gate(self, gate_id, *gate_args, gate_round=None, + gate_shared=None, **gate_opts): """Apply a single gate to this tensor network quantum circuit. If ``gate_round`` is supplied the tensor(s) added will be tagged with ``'ROUND_{gate_round}'``. Alternatively, putting an integer first like @@ -1040,7 +1076,7 @@ def fsim(self, theta, phi, i, j, gate_round=None, parametrize=False): self.apply_gate('FSIM', theta, phi, i, j, gate_round=gate_round, parametrize=parametrize) - def fsim(self, theta, i, j, gate_round=None, parametrize=False): + def fsimt(self, theta, i, j, gate_round=None, parametrize=False): self.apply_gate('FSIMT', theta, i, j, gate_round=gate_round, parametrize=parametrize) @@ -1129,6 +1165,108 @@ def uni(self): ) return self.get_uni(transposed=True) + def get_reverse_lightcone_tags_partial(self, psi, where): + """Get the tags of gates in this partial circuit corresponding to the 'reverse' + lightcone propagating backwards from registers in ``where``. + + Parameters + ---------- + where : int or sequence of int + The register or register to get the reverse lightcone of. + + Returns + ------- + tuple[str] + The sequence of gate tags (``GATE_{i}``, ...) corresponding to the + lightcone. + """ + gate_cone = self.partial_gates(psi) + dic_gate = self.gate_map() + + if isinstance(where, numbers.Integral): + cone = {where} + else: + cone = set(where) + + lightcone_tags = [] + + for i, gate in reversed(tuple(enumerate(self.gates))): + + if f"GATE_{i}" in gate_cone: + if gate[0] == 'IDEN': + continue + + if gate[0] == 'SWAP': + i, j = gate[1:] + i_in_cone = i in cone + j_in_cone = j in cone + if i_in_cone: + cone.add(j) + else: + cone.discard(j) + if j_in_cone: + cone.add(i) + else: + cone.discard(i) + continue + + regs = set(dic_gate[f"GATE_{i}"]) + + if regs & cone: + lightcone_tags.append(f"GATE_{i}") + cone |= regs + + # initial state is always part of the lightcone + + lightcone_tags.append('PSI0') + lightcone_tags.reverse() + return tuple(lightcone_tags), tuple(cone) + + def get_psi_reverse_lightcone_partial(self, psi, where, keep_psi0=False): + """Get just the bit of the wavefunction in the reverse lightcone of + sites in ``where`` - i.e. causally linked. + + Parameters + ---------- + where : int, or sequence of int + The sites to propagate the the lightcone back from, supplied to + :meth:`~quimb.tensor.circuit.Circuit.get_reverse_lightcone_tags`. + keep_psi0 : bool, optional + Keep the tensors corresponding to the initial wavefunction + regardless of whether they are outside of the lightcone. + + Returns + ------- + psi_lc : TensorNetwork1DVector + """ + if isinstance(where, numbers.Integral): + where = (where,) + + # psi = self.psi + # psi = self.psi + lightcone_tags_partial, q_partial = self.get_reverse_lightcone_tags_partial(psi, where) + psi_lc = psi.select_any(lightcone_tags_partial).view_like_(psi) + + if not keep_psi0: + # these sites are in the lightcone regardless of being alone + site_inds = set(map(psi.site_ind, where)) + + for tid, t in tuple(psi_lc.tensor_map.items()): + # get all tensors connected to this tensor (incld itself) + neighbors = oset_union(psi_lc.ind_map[ix] for ix in t.inds) + + # lone tensor not attached to anything - drop it + # but only if it isn't directly in the ``where`` region + if (len(neighbors) == 1) and set(t.inds).isdisjoint(site_inds): + psi_lc._pop_tensor(tid) + + return psi_lc + + + + + + def get_reverse_lightcone_tags(self, where): """Get the tags of gates in this circuit corresponding to the 'reverse' lightcone propagating backwards from registers in ``where``. diff --git a/quimb/tensor/optimize.py b/quimb/tensor/optimize.py index 3239184f7..9f471d1f0 100644 --- a/quimb/tensor/optimize.py +++ b/quimb/tensor/optimize.py @@ -1355,7 +1355,7 @@ def f(x, grad): opt.set_maxeval(n) #opt.set_vector_storage(22) opt.set_maxtime(-1) - print ( "M", self.optimizer.upper(), opt.get_vector_storage() , opt.get_maxeval(), opt.get_maxtime(), opt.get_xtol_rel(), opt.get_ftol_abs(), + #print ( "M", self.optimizer.upper(), opt.get_vector_storage() , opt.get_maxeval(), opt.get_maxtime(), opt.get_xtol_rel(), opt.get_ftol_abs(), opt.get_maxtime() ) @@ -1375,11 +1375,11 @@ def f(x, grad): if xtol_abs is not None: opt.set_xtol_abs(xtol_abs) - print ("Hi") + # print ("Hi") self.vectorizer.vector[:] = opt.optimize(self.vectorizer.vector) opt_val = opt.last_optimum_value() result = opt.last_optimize_result() - print ("info", opt_val, result) + # print ("info", opt_val, result) # except (KeyboardInterrupt, RuntimeError): From 0be3bc30771f26a49bfb62d938da3b7d05caf55c Mon Sep 17 00:00:00 2001 From: Reza Haghshenas Date: Tue, 29 Mar 2022 09:37:39 -0700 Subject: [PATCH 21/64] fixed a bug --- quimb/tensor/optimize.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/quimb/tensor/optimize.py b/quimb/tensor/optimize.py index 9f471d1f0..ce85a1cf5 100644 --- a/quimb/tensor/optimize.py +++ b/quimb/tensor/optimize.py @@ -1355,8 +1355,8 @@ def f(x, grad): opt.set_maxeval(n) #opt.set_vector_storage(22) opt.set_maxtime(-1) - #print ( "M", self.optimizer.upper(), opt.get_vector_storage() , opt.get_maxeval(), opt.get_maxtime(), opt.get_xtol_rel(), opt.get_ftol_abs(), - opt.get_maxtime() ) + # print ( "M", self.optimizer.upper(), opt.get_vector_storage() , opt.get_maxeval(), opt.get_maxtime(), opt.get_xtol_rel(), opt.get_ftol_abs(), + # opt.get_maxtime() ) From 8c7fac7a9ebc45c3f6d1fd4f3da880f69e8b2bc3 Mon Sep 17 00:00:00 2001 From: Reza Haghshenas Date: Mon, 4 Apr 2022 13:36:52 -0700 Subject: [PATCH 22/64] Quimb to qiskit --- quimb/tensor/circuit.py | 84 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 76 insertions(+), 8 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index dada28ccf..7706255cf 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -18,6 +18,12 @@ from . import array_ops as ops +import qiskit + + + + + def _convert_ints_and_floats(x): if isinstance(x, str): try: @@ -756,7 +762,6 @@ def __init__( psi0_tag='PSI0', bra_site_ind_id='b{}', ): - if N is None and psi0 is None: raise ValueError("You must supply one of `N` or `psi0`.") @@ -774,6 +779,14 @@ def __init__( self.N = N self._psi = psi0.copy() + self.q_qiskit = [] + for i in range(self.N): + self.q_qiskit.append(qiskit.QuantumRegister(1, f"q{i}")) + + self.c_qiskit = [] + for i in range(self.N): + self.c_qiskit.append(qiskit.ClassicalRegister(1, f"c{i}")) + self._psi.add_tag(psi0_tag) if tags is not None: @@ -878,13 +891,7 @@ def qubits_in_light_cone(self, psi): return q_virtual, q_physical - - - - - - - def gate_map(self): + def gate_regs_map(self): dic = {} for i, gate in enumerate(self.gates): if isinstance(gate[-2], numbers.Integral): @@ -896,6 +903,67 @@ def gate_map(self): return dic + def gate_params_map(self): + dic = {} + for i, gate in enumerate(self.gates): + if isinstance(gate[-2], numbers.Integral): + params = tuple(gate[1:len(gate)-2]) + else: + params = tuple(gate[1:len(gate)-1]) + + dic.update({f'GATE_{i}': params}) + + return dic + + def gate_id_map(self): + dic = {} + for i, gate in enumerate(self.gates): + dic.update({f'GATE_{i}': gate[0]}) + + return dic + + def to_qiskit_gates(self, psi=None): + q_virtual, q_physical = self.qubits_in_light_cone(psi) + q_l = self.q_qiskit + c_l = self.c_qiskit + + q_p = [q_l[i] for i in q_physical] + [q_l[i] for i in q_virtual] + c_p = [c_l[i] for i in q_physical] + [c_l[i] for i in q_virtual] + + # qc = qiskit.QuantumCircuit(*q_p) + qc = qiskit.QuantumCircuit(*q_p, *c_p) + + gate_p = self.partial_gates(psi) + dic_id = self.gate_id_map() + dic_r = self.gate_regs_map() + dic_p = self.gate_params_map() + + for i in gate_p: + if dic_id[i] == "CZ": + t0, t1 = dic_r[i] + qc.cz(q_l[t0], q_l[t1]) + if dic_id[i] == "H": + t0, = dic_r[i] + qc.h(q_l[t0]) + if dic_id[i] == "RZZ": + t0, t1 = dic_r[i] + p0, = dic_p[i] + qc.rzz(p0*2., q_l[t0], q_l[t1]) + if dic_id[i] == "RY": + t0, = dic_r[i] + p0, = dic_p[i] + qc.ry(p0, q_l[t0]) + if dic_id[i] == "RX": + t0, = dic_r[i] + p0, = dic_p[i] + qc.rx(p0, q_l[t0]) + + return qc + + + + + def apply_gate(self, gate_id, *gate_args, gate_round=None, gate_shared=None, **gate_opts): """Apply a single gate to this tensor network quantum circuit. If From cbb4c2b2bc37737dadbb6f92cc9815f3757ec2ac Mon Sep 17 00:00:00 2001 From: rezahhh Date: Thu, 7 Apr 2022 21:30:23 -0700 Subject: [PATCH 23/64] qiskit circuit+ --- quimb/tensor/circuit.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index 7706255cf..edcc3603d 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -17,13 +17,9 @@ from .tensor_1d import TensorNetwork1DVector, Dense1D, TensorNetwork1DOperator from . import array_ops as ops - import qiskit - - - def _convert_ints_and_floats(x): if isinstance(x, str): try: @@ -781,11 +777,11 @@ def __init__( self.q_qiskit = [] for i in range(self.N): - self.q_qiskit.append(qiskit.QuantumRegister(1, f"q{i}")) + self.q_qiskit.append(qiskit.QuantumRegister(1, f"q_{i}")) self.c_qiskit = [] for i in range(self.N): - self.c_qiskit.append(qiskit.ClassicalRegister(1, f"c{i}")) + self.c_qiskit.append(qiskit.ClassicalRegister(1, f"c_{i}")) self._psi.add_tag(psi0_tag) @@ -928,7 +924,8 @@ def to_qiskit_gates(self, psi=None): c_l = self.c_qiskit q_p = [q_l[i] for i in q_physical] + [q_l[i] for i in q_virtual] - c_p = [c_l[i] for i in q_physical] + [c_l[i] for i in q_virtual] + # c_p = [c_l[i] for i in q_physical] + [c_l[i] for i in q_virtual] + c_p = [c_l[i] for i in q_physical] # qc = qiskit.QuantumCircuit(*q_p) qc = qiskit.QuantumCircuit(*q_p, *c_p) @@ -961,9 +958,6 @@ def to_qiskit_gates(self, psi=None): return qc - - - def apply_gate(self, gate_id, *gate_args, gate_round=None, gate_shared=None, **gate_opts): """Apply a single gate to this tensor network quantum circuit. If @@ -1249,7 +1243,7 @@ def get_reverse_lightcone_tags_partial(self, psi, where): lightcone. """ gate_cone = self.partial_gates(psi) - dic_gate = self.gate_map() + dic_gate = self.gate_regs_map() if isinstance(where, numbers.Integral): cone = {where} From 6633f8a4b1d7042f87b2d7c9d9f29c5c96e8d693 Mon Sep 17 00:00:00 2001 From: Reza Haghshenas Date: Fri, 13 May 2022 14:05:03 -0700 Subject: [PATCH 24/64] revise+ --- quimb/tensor/circuit.py | 88 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 85 insertions(+), 3 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index edcc3603d..1f65e27a0 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -859,11 +859,88 @@ def apply_gate_raw(self, U, where, tags=None, self._psi.gate_(U, where, tags=tags, **opts) self.gates.append((id(U), *where)) + def q_drop(self, psi, q_virtual, q_register): + q_l_len = [] + q_l = [] + gate_l = [] + gate_l_len = [] + for i in q_virtual: + where = (i, ) + gate, q = self.get_reverse_lightcone_tags_partial(psi, where) + q_l_len.append(len(q)) + gate_l.append(gate) + gate_l_len.append(len(gate)) + q_l.append(q) + # rearrange based on size of qubit needed + index_order = sorted(range(len(q_l_len)), key=lambda k: q_l_len[k]) + q_virtual = [q_virtual[i] for i in index_order] + gate_l = [gate_l[i] for i in index_order] + q_l = [q_l[i] for i in index_order] + q_l_len = [q_l_len[i] for i in index_order] + + l_gate_total = self.partial_gates(psi) + # print(q_virtual_order, gate_l_len, {*l_gate_total},"\n", {*gate_l[0]}) + + if 'PSI0' in psi.tags: + tag_slice = oset(l_gate_total)-oset(gate_l[0]) + tag_slice |= oset(['PSI0']) + else: + tag_slice = (oset(l_gate_total)-oset(gate_l[0])) - oset(['PSI0']) + + if 'PSI0' in psi.tags: + tag_partial = oset(gate_l[0]) + tag_partial |= oset(['PSI0']) + else: + tag_partial = oset(gate_l[0])-oset(['PSI0']) + + # print( tag_slice, len(tag_slice)) + psi_lc = psi.select(tag_slice, which='any') + # psi_lc = psi.select_any(tag_slice).view_like_(circ.psi) + + for tid, t in tuple(psi_lc.tensor_map.items()): + # get all tensors connected to this tensor (incld itself) + neighbors = oset_union(psi_lc.ind_map[ix] for ix in t.inds) + + # lone tensor not attached to anything - drop it + # but only if it isn't directly in the ``where`` region + if (len(neighbors) == 1): + psi_lc._pop_tensor(tid) + + # print( tag_slice, len(tag_slice)) + psi_p = psi.select(tag_partial, which='any') + # psi_lc = psi.select_any(tag_slice).view_like_(circ.psi) + + for tid, t in tuple(psi_p.tensor_map.items()): + # get all tensors connected to this tensor (incld itself) + neighbors = oset_union(psi_p.ind_map[ix] for ix in t.inds) + + # lone tensor not attached to anything - drop it + # but only if it isn't directly in the ``where`` region + if (len(neighbors) == 1): + psi_p._pop_tensor(tid) + + Q_reuse = q_virtual.pop(0) + q_l_register = [i for i in q_l[0] if i in q_register] + return psi_lc, q_virtual, Q_reuse, tag_slice-oset(['PSI0']), tag_partial-oset(['PSI0']), q_l_len[0], oset(q_l[0]), oset(q_l_register), psi_p + # the gates exist in psi with format "GATE_{}" def partial_gates(self, psi): rex = re.compile("GATE_{}".format(r"\d+")) return list(filter(rex.match, psi.tags)) + # find register qubits + def register_qubit_map(self, psi): + Q_l = [] + if "PSI0" in psi.tags: + tags = psi.select(["PSI0"]).tags + rex = re.compile(self.psi.site_tag_id.format(r"\d+")) + tags_tn = list(filter(rex.match, tags)) + for i in tags_tn: + temp = re.findall(r'\d+', i) + res = list(map(int, temp)) + Q_l.append(res[0]) + return Q_l + # map inds list to corresponding qubits def qubit_map(self, psi, inds): Q_l = [] @@ -874,18 +951,23 @@ def qubit_map(self, psi, inds): temp = re.findall(r'\d+', *tags_tn) res = list(map(int, temp)) Q_l.append(res[0]) - return Q_l + # if duplicate==True: + # import collections + # Q_l_rep = oset([item for item, count in collections.Counter(Q_l).items() if count > 1]) + # return oset(Q_l)-Q_l_rep + # elif duplicate==False: + return oset(Q_l) # map inds list to corresponding qubits def qubits_in_light_cone(self, psi): ind_open = psi.outer_inds() ind_open_virtual = [x for x in ind_open if not x.startswith('k')] ind_open_physical = list(oset(ind_open)-oset(ind_open_virtual)) - + q_virtual = self.qubit_map(psi, ind_open_virtual) q_physical = self.qubit_map(psi, ind_open_physical) - return q_virtual, q_physical + return list(q_virtual), list(q_physical) def gate_regs_map(self): dic = {} From 8d5f2cbf6137b672ea8924f4b74a2e40496a8327 Mon Sep 17 00:00:00 2001 From: Reza Haghshenas Date: Mon, 16 May 2022 13:44:07 -0700 Subject: [PATCH 25/64] added optimal qubit reuse/measure --- quimb/tensor/circuit.py | 65 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 6 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index 1f65e27a0..3f5a94c40 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -963,7 +963,7 @@ def qubits_in_light_cone(self, psi): ind_open = psi.outer_inds() ind_open_virtual = [x for x in ind_open if not x.startswith('k')] ind_open_physical = list(oset(ind_open)-oset(ind_open_virtual)) - + q_virtual = self.qubit_map(psi, ind_open_virtual) q_physical = self.qubit_map(psi, ind_open_physical) @@ -1000,16 +1000,60 @@ def gate_id_map(self): return dic - def to_qiskit_gates(self, psi=None): + def optimal_qubits(self, psi): + q_virtual, q_physical = self.qubits_in_light_cone(psi) + q_total = q_virtual + q_physical + q_register = self.register_qubit_map(psi) + tag_slice = self.partial_gates(psi) + psi = psi*1.0 + + # print("Total_gates", len(tag_slice)) + # print(q_virtual, q_physical, q_register, tag_slice) + + tag_step = [] + tag_partial_step = [] + q_step = [] + q_opt_step = [] + q_required_step = [] + q_actual_step = [] + q_register_step = [] + psi_step = [] + psi_p_step = [] + + while q_virtual: + psi_step.append(psi) + tag_step.append(tag_slice) + psi, q_virtual, q_reuse, tag_slice, tag_partial, q_required, q_actual, q_register, psi_p = self.q_drop(psi, q_virtual, q_register) + q_opt_step.append(q_reuse) + q_required_step.append(q_required) + psi_p_step.append(psi_p) + tag_partial_step.append(tag_partial) + q_actual_step.append(q_actual) + q_register_step.append(q_register) + + q_virtual, q_physical = self.qubits_in_light_cone(psi) + q_register = self.register_qubit_map(psi) + + q_opt_f = q_opt_step + [i for i in q_total if i not in q_opt_step] + return q_opt_f + + + def to_qiskit_gates(self, psi=None, optimal=False, measure=False, q_measure=[]): q_virtual, q_physical = self.qubits_in_light_cone(psi) + q_l = self.q_qiskit c_l = self.c_qiskit - - q_p = [q_l[i] for i in q_physical] + [q_l[i] for i in q_virtual] + q_p = [q_l[i] for i in q_virtual] + [q_l[i] for i in q_physical] # c_p = [c_l[i] for i in q_physical] + [c_l[i] for i in q_virtual] - c_p = [c_l[i] for i in q_physical] + if q_measure: + c_p = [c_l[i] for i in q_measure] + else: + c_p = [c_l[i] for i in q_physical] - # qc = qiskit.QuantumCircuit(*q_p) + if optimal: + q_opt = self.optimal_qubits(psi) + q_p = [q_l[i] for i in q_opt] + # index = [q_opt.index(i) for i in q_physical] qc = qiskit.QuantumCircuit(*q_p, *c_p) gate_p = self.partial_gates(psi) @@ -1037,6 +1081,15 @@ def to_qiskit_gates(self, psi=None): p0, = dic_p[i] qc.rx(p0, q_l[t0]) + if measure: + if q_measure: + for i in q_measure: + qc.measure(q_l[i], c_l[i]) + else: + for i in q_physical: + qc.measure(q_l[i], c_l[i]) + + return qc From 02267139fe17e36d9e8eacfb3bfafabdb82858d5 Mon Sep 17 00:00:00 2001 From: Reza Haghshenas Date: Wed, 18 May 2022 16:07:14 -0700 Subject: [PATCH 26/64] added measure X --- quimb/tensor/circuit.py | 42 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index 3f5a94c40..8c19eb195 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -1037,8 +1037,43 @@ def optimal_qubits(self, psi): q_opt_f = q_opt_step + [i for i in q_total if i not in q_opt_step] return q_opt_f + def optimal_reuse_tag(self, psi): + q_virtual, q_physical = self.qubits_in_light_cone(psi) + q_register = self.register_qubit_map(psi) + tag_slice = self.partial_gates(psi) + psi = psi*1.0 + + # print("Total_gates", len(tag_slice)) + # print(q_virtual, q_physical, q_register, tag_slice) + + tag_step = [] + tag_partial_step = [] + q_opt_step = [] + q_required_step = [] + q_actual_step = [] + q_register_step = [] + psi_step = [] + psi_p_step = [] + + while q_virtual: + psi_step.append(psi) + tag_step.append(tag_slice) + psi, q_virtual, q_reuse, tag_slice, tag_partial, q_required, q_actual, q_register, psi_p = self.q_drop(psi, q_virtual, q_register) + q_opt_step.append(q_reuse) + q_required_step.append(q_required) + psi_p_step.append(psi_p) + tag_partial_step.append(tag_partial) + q_actual_step.append(q_actual) + q_register_step.append(q_register) + + q_virtual, q_physical = self.qubits_in_light_cone(psi) + q_register = self.register_qubit_map(psi) - def to_qiskit_gates(self, psi=None, optimal=False, measure=False, q_measure=[]): + return tag_partial_step + + + + def to_qiskit_gates(self, psi=None, optimal=False, measure=False, q_measure=[], label_measure="Z"): q_virtual, q_physical = self.qubits_in_light_cone(psi) q_l = self.q_qiskit @@ -1084,12 +1119,15 @@ def to_qiskit_gates(self, psi=None, optimal=False, measure=False, q_measure=[]): if measure: if q_measure: for i in q_measure: + if label_measure == "X": + qc.h(q_l[i]) qc.measure(q_l[i], c_l[i]) else: for i in q_physical: + if label_measure == "X": + qc.h(q_l[i]) qc.measure(q_l[i], c_l[i]) - return qc From 8686cf19dbbb93d35e0958d8705a690662aed34c Mon Sep 17 00:00:00 2001 From: Reza Haghshenas Date: Fri, 20 May 2022 09:49:58 -0700 Subject: [PATCH 27/64] modified label_ --- quimb/tensor/circuit.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index 8c19eb195..836e03a7c 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -1116,16 +1116,21 @@ def to_qiskit_gates(self, psi=None, optimal=False, measure=False, q_measure=[], p0, = dic_p[i] qc.rx(p0, q_l[t0]) + if label_measure == "X": + if q_measure: + for i in q_measure: + qc.h(q_l[i]) + else: + for i in q_physical: + qc.h(q_l[i]) + + if measure: if q_measure: for i in q_measure: - if label_measure == "X": - qc.h(q_l[i]) qc.measure(q_l[i], c_l[i]) else: for i in q_physical: - if label_measure == "X": - qc.h(q_l[i]) qc.measure(q_l[i], c_l[i]) return qc From e35e2958a9acc5054e28bcc76f15e398859cdf3f Mon Sep 17 00:00:00 2001 From: Reza Haghshenas Date: Mon, 23 May 2022 13:30:44 -0700 Subject: [PATCH 28/64] fixed tags --- quimb/tensor/circuit.py | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index 836e03a7c..eb316b040 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -784,6 +784,9 @@ def __init__( self.c_qiskit.append(qiskit.ClassicalRegister(1, f"c_{i}")) self._psi.add_tag(psi0_tag) + for count, ele in enumerate(self._psi): + ele.add_tag(f"Qreg{count}") + if tags is not None: if isinstance(tags, str): @@ -1085,6 +1088,9 @@ def to_qiskit_gates(self, psi=None, optimal=False, measure=False, q_measure=[], else: c_p = [c_l[i] for i in q_physical] + if measure == "all": + c_p = [c_l[i] for i in q_virtual] + [c_l[i] for i in q_physical] + if optimal: q_opt = self.optimal_qubits(psi) q_p = [q_l[i] for i in q_opt] @@ -1097,6 +1103,7 @@ def to_qiskit_gates(self, psi=None, optimal=False, measure=False, q_measure=[], dic_p = self.gate_params_map() for i in gate_p: + # print("gate",i, dic_id[i]) if dic_id[i] == "CZ": t0, t1 = dic_r[i] qc.cz(q_l[t0], q_l[t1]) @@ -1106,7 +1113,7 @@ def to_qiskit_gates(self, psi=None, optimal=False, measure=False, q_measure=[], if dic_id[i] == "RZZ": t0, t1 = dic_r[i] p0, = dic_p[i] - qc.rzz(p0*2., q_l[t0], q_l[t1]) + qc.rzz(-p0*2., q_l[t0], q_l[t1]) if dic_id[i] == "RY": t0, = dic_r[i] p0, = dic_p[i] @@ -1115,6 +1122,10 @@ def to_qiskit_gates(self, psi=None, optimal=False, measure=False, q_measure=[], t0, = dic_r[i] p0, = dic_p[i] qc.rx(p0, q_l[t0]) + if dic_id[i] == "RZ": + t0, = dic_r[i] + p0, = dic_p[i] + qc.rz(p0, q_l[t0]) if label_measure == "X": if q_measure: @@ -1124,14 +1135,16 @@ def to_qiskit_gates(self, psi=None, optimal=False, measure=False, q_measure=[], for i in q_physical: qc.h(q_l[i]) - if measure: - if q_measure: - for i in q_measure: - qc.measure(q_l[i], c_l[i]) + if measure=="all": + qc.measure_all(add_bits=False) else: - for i in q_physical: - qc.measure(q_l[i], c_l[i]) + if q_measure: + for i in q_measure: + qc.measure(q_l[i], c_l[i]) + else: + for i in q_physical: + qc.measure(q_l[i], c_l[i]) return qc @@ -1167,7 +1180,8 @@ def apply_gate(self, gate_id, *gate_args, gate_round=None, # unique tag tags = tags_to_oset(f'GATE_{len(self.gates)}') if (gate_shared is not None): - tags.add(f'{gate_shared}') + gate_shared = tags_to_oset(gate_shared) + tags = tags | gate_shared # parse which 'round' of gates if (gate_round is not None): From c45fe9285fd380d162ecfde22aa4d9d22d157a46 Mon Sep 17 00:00:00 2001 From: Reza Haghshenas Date: Mon, 13 Jun 2022 15:26:45 -0700 Subject: [PATCH 29/64] added parity_error_mitigation --- quimb/tensor/circuit.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index eb316b040..a7b8b2f61 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -1076,15 +1076,17 @@ def optimal_reuse_tag(self, psi): - def to_qiskit_gates(self, psi=None, optimal=False, measure=False, q_measure=[], label_measure="Z"): + def to_qiskit_gates(self, psi=None, optimal=False, measure=False, q_measure=[], label_measure="Z", label_ancilla="parity"): q_virtual, q_physical = self.qubits_in_light_cone(psi) q_l = self.q_qiskit c_l = self.c_qiskit q_p = [q_l[i] for i in q_virtual] + [q_l[i] for i in q_physical] # c_p = [c_l[i] for i in q_physical] + [c_l[i] for i in q_virtual] - if q_measure: + if q_measure and label_ancilla != "parity": c_p = [c_l[i] for i in q_measure] + elif q_measure and label_ancilla == "parity": + c_p = [c_l[i] for i in q_virtual] + [c_l[i] for i in q_physical] else: c_p = [c_l[i] for i in q_physical] @@ -1096,6 +1098,12 @@ def to_qiskit_gates(self, psi=None, optimal=False, measure=False, q_measure=[], q_p = [q_l[i] for i in q_opt] # index = [q_opt.index(i) for i in q_physical] qc = qiskit.QuantumCircuit(*q_p, *c_p) + if label_ancilla == "parity": + q_ancilla = qiskit.QuantumRegister(1, "q_ancilla") + c_ancilla = qiskit.ClassicalRegister(1, "c_ancilla") + qc.add_register(q_ancilla) + qc.add_register(c_ancilla) + gate_p = self.partial_gates(psi) dic_id = self.gate_id_map() @@ -1127,7 +1135,7 @@ def to_qiskit_gates(self, psi=None, optimal=False, measure=False, q_measure=[], p0, = dic_p[i] qc.rz(p0, q_l[t0]) - if label_measure == "X": + if label_measure == "X" and label_ancilla != "parity": if q_measure: for i in q_measure: qc.h(q_l[i]) @@ -1135,13 +1143,22 @@ def to_qiskit_gates(self, psi=None, optimal=False, measure=False, q_measure=[], for i in q_physical: qc.h(q_l[i]) + if measure: - if measure=="all": + if measure == "all": qc.measure_all(add_bits=False) else: - if q_measure: + if q_measure and label_ancilla != "parity": for i in q_measure: qc.measure(q_l[i], c_l[i]) + elif q_measure and label_ancilla == "parity": + qc.h(q_ancilla) + for i in q_measure: + qc.cx(q_ancilla, q_l[i]) + qc.h(q_ancilla) + qc.measure(q_ancilla, c_ancilla) + for i in range(len(q_p)): + qc.measure(q_p[i], c_p[i]) else: for i in q_physical: qc.measure(q_l[i], c_l[i]) From d468991577a8521d64544a6f15398de0afabf2a5 Mon Sep 17 00:00:00 2001 From: Reza Haghshenas Date: Mon, 13 Jun 2022 17:54:50 -0700 Subject: [PATCH 30/64] added Qreg --- quimb/tensor/circuit.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index a7b8b2f61..ddc765501 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -1036,7 +1036,6 @@ def optimal_qubits(self, psi): q_virtual, q_physical = self.qubits_in_light_cone(psi) q_register = self.register_qubit_map(psi) - q_opt_f = q_opt_step + [i for i in q_total if i not in q_opt_step] return q_opt_f @@ -1075,10 +1074,8 @@ def optimal_reuse_tag(self, psi): return tag_partial_step - def to_qiskit_gates(self, psi=None, optimal=False, measure=False, q_measure=[], label_measure="Z", label_ancilla="parity"): q_virtual, q_physical = self.qubits_in_light_cone(psi) - q_l = self.q_qiskit c_l = self.c_qiskit q_p = [q_l[i] for i in q_virtual] + [q_l[i] for i in q_physical] @@ -1096,7 +1093,8 @@ def to_qiskit_gates(self, psi=None, optimal=False, measure=False, q_measure=[], if optimal: q_opt = self.optimal_qubits(psi) q_p = [q_l[i] for i in q_opt] - # index = [q_opt.index(i) for i in q_physical] + c_p = [c_l[i] for i in q_opt] + qc = qiskit.QuantumCircuit(*q_p, *c_p) if label_ancilla == "parity": q_ancilla = qiskit.QuantumRegister(1, "q_ancilla") @@ -1104,7 +1102,6 @@ def to_qiskit_gates(self, psi=None, optimal=False, measure=False, q_measure=[], qc.add_register(q_ancilla) qc.add_register(c_ancilla) - gate_p = self.partial_gates(psi) dic_id = self.gate_id_map() dic_r = self.gate_regs_map() @@ -1143,7 +1140,6 @@ def to_qiskit_gates(self, psi=None, optimal=False, measure=False, q_measure=[], for i in q_physical: qc.h(q_l[i]) - if measure: if measure == "all": qc.measure_all(add_bits=False) @@ -1533,11 +1529,6 @@ def get_psi_reverse_lightcone_partial(self, psi, where, keep_psi0=False): return psi_lc - - - - - def get_reverse_lightcone_tags(self, where): """Get the tags of gates in this circuit corresponding to the 'reverse' lightcone propagating backwards from registers in ``where``. @@ -1585,10 +1576,13 @@ def get_reverse_lightcone_tags(self, where): if regs & cone: lightcone_tags.append(f"GATE_{i}") + # lightcone_tags.append(f"Qreg{i}") cone |= regs # initial state is always part of the lightcone - lightcone_tags.append('PSI0') + cone_l = list(cone) + for i in cone_l: + lightcone_tags.append(f"Qreg{i}") lightcone_tags.reverse() return tuple(lightcone_tags) From 381951a81f5bf6bf2dd6c3ee1696cc898b6979cd Mon Sep 17 00:00:00 2001 From: Reza Haghshenas Date: Tue, 14 Jun 2022 08:38:16 -0700 Subject: [PATCH 31/64] fixed parity by adding H-gate --- quimb/tensor/circuit.py | 41 ++++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index ddc765501..e20c1faab 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -1096,7 +1096,7 @@ def to_qiskit_gates(self, psi=None, optimal=False, measure=False, q_measure=[], c_p = [c_l[i] for i in q_opt] qc = qiskit.QuantumCircuit(*q_p, *c_p) - if label_ancilla == "parity": + if label_ancilla == "parity" and label_measure == "X": q_ancilla = qiskit.QuantumRegister(1, "q_ancilla") c_ancilla = qiskit.ClassicalRegister(1, "c_ancilla") qc.add_register(q_ancilla) @@ -1136,9 +1136,11 @@ def to_qiskit_gates(self, psi=None, optimal=False, measure=False, q_measure=[], if q_measure: for i in q_measure: qc.h(q_l[i]) - else: - for i in q_physical: - qc.h(q_l[i]) + elif label_measure == "X" and label_ancilla == "parity": + for i in q_physical: + qc.h(q_l[i]) + elif label_measure == "Z" and label_ancilla == "parity": + pass if measure: if measure == "all": @@ -1148,13 +1150,30 @@ def to_qiskit_gates(self, psi=None, optimal=False, measure=False, q_measure=[], for i in q_measure: qc.measure(q_l[i], c_l[i]) elif q_measure and label_ancilla == "parity": - qc.h(q_ancilla) - for i in q_measure: - qc.cx(q_ancilla, q_l[i]) - qc.h(q_ancilla) - qc.measure(q_ancilla, c_ancilla) - for i in range(len(q_p)): - qc.measure(q_p[i], c_p[i]) + + if label_measure == "X": + + qc.h(q_ancilla) + for i in q_measure: + qc.cx(q_ancilla, q_l[i]) + qc.h(q_ancilla) + qc.measure(q_ancilla, c_ancilla) + + for i in q_opt: + if i not in q_measure: + qc.h(q_l[i]) + + for i in range(len(q_p)): + qc.measure(q_p[i], c_p[i]) + + elif label_measure == "Z": + for i in q_opt: + if i not in q_measure: + qc.h(q_l[i]) + + for i in range(len(q_p)): + qc.measure(q_p[i], c_p[i]) + else: for i in q_physical: qc.measure(q_l[i], c_l[i]) From 0d6f62d3dd690243fd73540690cefafaa47b809e Mon Sep 17 00:00:00 2001 From: Reza Haghshenas Date: Tue, 14 Jun 2022 09:25:04 -0700 Subject: [PATCH 32/64] remove measure on physical qubit --- quimb/tensor/circuit.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index e20c1faab..bb4a60f42 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -1156,6 +1156,7 @@ def to_qiskit_gates(self, psi=None, optimal=False, measure=False, q_measure=[], qc.h(q_ancilla) for i in q_measure: qc.cx(q_ancilla, q_l[i]) + qc.h(q_ancilla) qc.measure(q_ancilla, c_ancilla) @@ -1163,8 +1164,9 @@ def to_qiskit_gates(self, psi=None, optimal=False, measure=False, q_measure=[], if i not in q_measure: qc.h(q_l[i]) - for i in range(len(q_p)): - qc.measure(q_p[i], c_p[i]) + for i in q_opt: + if i not in q_measure: + qc.measure(q_l[i], c_l[i]) elif label_measure == "Z": for i in q_opt: From 3c07d8fb6236027497abf53f8ab5a0c0a06b2334 Mon Sep 17 00:00:00 2001 From: Reza Haghshenas Date: Tue, 14 Jun 2022 09:33:28 -0700 Subject: [PATCH 33/64] +bug --- quimb/tensor/circuit.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index bb4a60f42..2808f932e 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -1137,8 +1137,7 @@ def to_qiskit_gates(self, psi=None, optimal=False, measure=False, q_measure=[], for i in q_measure: qc.h(q_l[i]) elif label_measure == "X" and label_ancilla == "parity": - for i in q_physical: - qc.h(q_l[i]) + pass elif label_measure == "Z" and label_ancilla == "parity": pass @@ -1164,10 +1163,9 @@ def to_qiskit_gates(self, psi=None, optimal=False, measure=False, q_measure=[], if i not in q_measure: qc.h(q_l[i]) - for i in q_opt: - if i not in q_measure: - qc.measure(q_l[i], c_l[i]) - + for i in range(len(q_p)): + qc.measure(q_p[i], c_p[i]) + elif label_measure == "Z": for i in q_opt: if i not in q_measure: From 78e207118e80271d1ee9dc4462d11bbb40c54c46 Mon Sep 17 00:00:00 2001 From: Reza Haghshenas Date: Tue, 14 Jun 2022 14:50:38 -0700 Subject: [PATCH 34/64] +bug --- quimb/tensor/circuit.py | 76 ++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 43 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index 2808f932e..fc2fb4369 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -1073,9 +1073,9 @@ def optimal_reuse_tag(self, psi): return tag_partial_step - - def to_qiskit_gates(self, psi=None, optimal=False, measure=False, q_measure=[], label_measure="Z", label_ancilla="parity"): + def to_qiskit_gates(self, psi=None, optimal=False, q_measure=[], label_measure="Z", label_ancilla="parity"): q_virtual, q_physical = self.qubits_in_light_cone(psi) + q_opt = q_virtual, q_physical q_l = self.q_qiskit c_l = self.c_qiskit q_p = [q_l[i] for i in q_virtual] + [q_l[i] for i in q_physical] @@ -1084,16 +1084,14 @@ def to_qiskit_gates(self, psi=None, optimal=False, measure=False, q_measure=[], c_p = [c_l[i] for i in q_measure] elif q_measure and label_ancilla == "parity": c_p = [c_l[i] for i in q_virtual] + [c_l[i] for i in q_physical] - else: - c_p = [c_l[i] for i in q_physical] - - if measure == "all": - c_p = [c_l[i] for i in q_virtual] + [c_l[i] for i in q_physical] if optimal: q_opt = self.optimal_qubits(psi) q_p = [q_l[i] for i in q_opt] - c_p = [c_l[i] for i in q_opt] + if label_ancilla == "parity": + c_p = [c_l[i] for i in q_opt] + else: + c_p = [c_l[i] for i in q_measure] qc = qiskit.QuantumCircuit(*q_p, *c_p) if label_ancilla == "parity" and label_measure == "X": @@ -1141,42 +1139,34 @@ def to_qiskit_gates(self, psi=None, optimal=False, measure=False, q_measure=[], elif label_measure == "Z" and label_ancilla == "parity": pass - if measure: - if measure == "all": - qc.measure_all(add_bits=False) - else: - if q_measure and label_ancilla != "parity": - for i in q_measure: - qc.measure(q_l[i], c_l[i]) - elif q_measure and label_ancilla == "parity": - - if label_measure == "X": - - qc.h(q_ancilla) - for i in q_measure: - qc.cx(q_ancilla, q_l[i]) - - qc.h(q_ancilla) - qc.measure(q_ancilla, c_ancilla) - - for i in q_opt: - if i not in q_measure: - qc.h(q_l[i]) - - for i in range(len(q_p)): - qc.measure(q_p[i], c_p[i]) - - elif label_measure == "Z": - for i in q_opt: - if i not in q_measure: - qc.h(q_l[i]) - - for i in range(len(q_p)): - qc.measure(q_p[i], c_p[i]) - else: - for i in q_physical: - qc.measure(q_l[i], c_l[i]) + if q_measure and label_ancilla != "parity": + for i in q_measure: + qc.measure(q_l[i], c_l[i]) + elif q_measure and label_ancilla == "parity": + + if label_measure == "X": + qc.h(q_ancilla) + for i in q_measure: + qc.cx(q_ancilla, q_l[i]) + + qc.h(q_ancilla) + qc.measure(q_ancilla, c_ancilla) + + for i in q_opt: + if i not in q_measure: + qc.h(q_l[i]) + + for i in range(len(q_p)): + qc.measure(q_p[i], c_p[i]) + + elif label_measure == "Z": + for i in q_opt: + if i not in q_measure: + qc.h(q_l[i]) + + for i in range(len(q_p)): + qc.measure(q_p[i], c_p[i]) return qc From 0d1a476e6962d86b471bd97271e8bf9edece73e1 Mon Sep 17 00:00:00 2001 From: Reza Haghshenas Date: Wed, 15 Jun 2022 10:08:18 -0700 Subject: [PATCH 35/64] +bug --- quimb/tensor/circuit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index fc2fb4369..4ebb1330e 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -1075,7 +1075,7 @@ def optimal_reuse_tag(self, psi): def to_qiskit_gates(self, psi=None, optimal=False, q_measure=[], label_measure="Z", label_ancilla="parity"): q_virtual, q_physical = self.qubits_in_light_cone(psi) - q_opt = q_virtual, q_physical + q_opt = q_virtual+q_physical q_l = self.q_qiskit c_l = self.c_qiskit q_p = [q_l[i] for i in q_virtual] + [q_l[i] for i in q_physical] From 6914087bea2bc2175a04d778e9d1615b1091de31 Mon Sep 17 00:00:00 2001 From: rezahhh Date: Wed, 29 Jun 2022 17:12:50 -0700 Subject: [PATCH 36/64] added leakage --- quimb/tensor/circuit.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index 4ebb1330e..aac07cc32 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -1073,7 +1073,9 @@ def optimal_reuse_tag(self, psi): return tag_partial_step - def to_qiskit_gates(self, psi=None, optimal=False, q_measure=[], label_measure="Z", label_ancilla="parity"): + def to_qiskit_gates(self, psi=None, optimal=False, q_measure=[], + label_measure="Z", label_ancilla="parity", + label_leakage="leakage"): q_virtual, q_physical = self.qubits_in_light_cone(psi) q_opt = q_virtual+q_physical q_l = self.q_qiskit @@ -1100,6 +1102,12 @@ def to_qiskit_gates(self, psi=None, optimal=False, q_measure=[], label_measure=" qc.add_register(q_ancilla) qc.add_register(c_ancilla) + if label_leakage == "leakage": + q_ancilla_leakage = qiskit.QuantumRegister(len(q_physical), "q_ancilla_leakage") + c_ancilla_leakage = qiskit.ClassicalRegister(len(q_physical), "c_ancilla_leakage") + qc.add_register(q_ancilla_leakage) + qc.add_register(c_ancilla_leakage) + gate_p = self.partial_gates(psi) dic_id = self.gate_id_map() dic_r = self.gate_regs_map() @@ -1153,6 +1161,14 @@ def to_qiskit_gates(self, psi=None, optimal=False, q_measure=[], label_measure=" qc.h(q_ancilla) qc.measure(q_ancilla, c_ancilla) + for count, elem in enumerate(q_physical): + qc.h(q_ancilla_leakage[count]) + qc.rzz(math.pi/2, q_ancilla_leakage[count], q_l[elem]) + qc.rzz(math.pi/2, q_ancilla_leakage[count], q_l[elem]) + qc.h(q_ancilla_leakage[count]) + qc.x(q_ancilla_leakage[count]) + qc.measure(q_ancilla_leakage[count], c_ancilla_leakage[count]) + for i in q_opt: if i not in q_measure: qc.h(q_l[i]) From 1d59b10933d1ce5d6134b1dc3dd1653fa40d7a51 Mon Sep 17 00:00:00 2001 From: Reza Haghshenas Date: Wed, 29 Jun 2022 18:49:39 -0700 Subject: [PATCH 37/64] add adjacont qubits --- quimb/tensor/circuit.py | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index aac07cc32..b1656d4d6 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -1077,6 +1077,18 @@ def to_qiskit_gates(self, psi=None, optimal=False, q_measure=[], label_measure="Z", label_ancilla="parity", label_leakage="leakage"): q_virtual, q_physical = self.qubits_in_light_cone(psi) + q_leakage = [] + + for i in q_physical: + q_leakage.append(i) + if i % 2==0 and (i+1) not in q_physical: + if (i+1) in q_virtual: + q_leakage.append(i+1) + if i%2==1 and (i-1) not in q_physical: + if (i-1) in q_virtual: + q_leakage.append(i-1) + + q_opt = q_virtual+q_physical q_l = self.q_qiskit c_l = self.c_qiskit @@ -1103,8 +1115,8 @@ def to_qiskit_gates(self, psi=None, optimal=False, q_measure=[], qc.add_register(c_ancilla) if label_leakage == "leakage": - q_ancilla_leakage = qiskit.QuantumRegister(len(q_physical), "q_ancilla_leakage") - c_ancilla_leakage = qiskit.ClassicalRegister(len(q_physical), "c_ancilla_leakage") + q_ancilla_leakage = qiskit.QuantumRegister(len(q_leakage), "q_ancilla_leakage") + c_ancilla_leakage = qiskit.ClassicalRegister(len(q_leakage), "c_ancilla_leakage") qc.add_register(q_ancilla_leakage) qc.add_register(c_ancilla_leakage) @@ -1161,13 +1173,14 @@ def to_qiskit_gates(self, psi=None, optimal=False, q_measure=[], qc.h(q_ancilla) qc.measure(q_ancilla, c_ancilla) - for count, elem in enumerate(q_physical): - qc.h(q_ancilla_leakage[count]) - qc.rzz(math.pi/2, q_ancilla_leakage[count], q_l[elem]) - qc.rzz(math.pi/2, q_ancilla_leakage[count], q_l[elem]) - qc.h(q_ancilla_leakage[count]) - qc.x(q_ancilla_leakage[count]) - qc.measure(q_ancilla_leakage[count], c_ancilla_leakage[count]) + if label_leakage == "leakage": + for count, elem in enumerate(q_leakage): + qc.h(q_ancilla_leakage[count]) + qc.rzz(math.pi/2, q_ancilla_leakage[count], q_l[elem]) + qc.rzz(math.pi/2, q_ancilla_leakage[count], q_l[elem]) + qc.h(q_ancilla_leakage[count]) + qc.x(q_ancilla_leakage[count]) + qc.measure(q_ancilla_leakage[count], c_ancilla_leakage[count]) for i in q_opt: if i not in q_measure: From 7c590c5f179cad68e555a06bcc59671bfe8cb428 Mon Sep 17 00:00:00 2001 From: rezahhh Date: Thu, 30 Jun 2022 12:17:06 -0700 Subject: [PATCH 38/64] added leakage+ --- quimb/tensor/circuit.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index b1656d4d6..70e92dc0e 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -1078,17 +1078,15 @@ def to_qiskit_gates(self, psi=None, optimal=False, q_measure=[], label_leakage="leakage"): q_virtual, q_physical = self.qubits_in_light_cone(psi) q_leakage = [] - for i in q_physical: q_leakage.append(i) - if i % 2==0 and (i+1) not in q_physical: + if i % 2 == 0 and (i+1) not in q_physical: if (i+1) in q_virtual: q_leakage.append(i+1) - if i%2==1 and (i-1) not in q_physical: + if i % 2 == 1 and (i-1) not in q_physical: if (i-1) in q_virtual: q_leakage.append(i-1) - q_opt = q_virtual+q_physical q_l = self.q_qiskit c_l = self.c_qiskit @@ -1159,7 +1157,6 @@ def to_qiskit_gates(self, psi=None, optimal=False, q_measure=[], elif label_measure == "Z" and label_ancilla == "parity": pass - if q_measure and label_ancilla != "parity": for i in q_measure: qc.measure(q_l[i], c_l[i]) @@ -1173,13 +1170,21 @@ def to_qiskit_gates(self, psi=None, optimal=False, q_measure=[], qc.h(q_ancilla) qc.measure(q_ancilla, c_ancilla) + # if label_leakage == "leakage": + # for count, elem in enumerate(q_leakage): + # qc.h(q_ancilla_leakage[count]) + # qc.rzz(math.pi/2, q_ancilla_leakage[count], q_l[elem]) + # qc.rzz(math.pi/2, q_ancilla_leakage[count], q_l[elem]) + # qc.h(q_ancilla_leakage[count]) + # qc.x(q_ancilla_leakage[count]) + # qc.measure(q_ancilla_leakage[count], c_ancilla_leakage[count]) + if label_leakage == "leakage": for count, elem in enumerate(q_leakage): - qc.h(q_ancilla_leakage[count]) - qc.rzz(math.pi/2, q_ancilla_leakage[count], q_l[elem]) - qc.rzz(math.pi/2, q_ancilla_leakage[count], q_l[elem]) - qc.h(q_ancilla_leakage[count]) - qc.x(q_ancilla_leakage[count]) + qc.cx(q_l[elem], q_ancilla_leakage[count]) + qc.x(q_l[elem]) + qc.cx(q_l[elem], q_ancilla_leakage[count]) + qc.x(q_l[elem]) qc.measure(q_ancilla_leakage[count], c_ancilla_leakage[count]) for i in q_opt: From cae63ffb423d3779fc08958d3e61091533b07a18 Mon Sep 17 00:00:00 2001 From: rezah Date: Fri, 1 Jul 2022 14:01:56 -0700 Subject: [PATCH 39/64] added leakage-all --- quimb/tensor/circuit.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index 70e92dc0e..3850611f2 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -1075,17 +1075,23 @@ def optimal_reuse_tag(self, psi): def to_qiskit_gates(self, psi=None, optimal=False, q_measure=[], label_measure="Z", label_ancilla="parity", - label_leakage="leakage"): + label_leakage="off"): q_virtual, q_physical = self.qubits_in_light_cone(psi) q_leakage = [] - for i in q_physical: - q_leakage.append(i) - if i % 2 == 0 and (i+1) not in q_physical: - if (i+1) in q_virtual: - q_leakage.append(i+1) - if i % 2 == 1 and (i-1) not in q_physical: - if (i-1) in q_virtual: - q_leakage.append(i-1) + if label_leakage=="leakage": + for i in q_physical: + q_leakage.append(i) + if i % 2 == 0 and (i+1) not in q_physical: + if (i+1) in q_virtual: + q_leakage.append(i+1) + if i % 2 == 1 and (i-1) not in q_physical: + if (i-1) in q_virtual: + q_leakage.append(i-1) + elif label_leakage=="leakage-all": + q_total=q_virtual+q_physical + for i in q_total: + q_leakage.append(i) + q_opt = q_virtual+q_physical q_l = self.q_qiskit @@ -1112,7 +1118,7 @@ def to_qiskit_gates(self, psi=None, optimal=False, q_measure=[], qc.add_register(q_ancilla) qc.add_register(c_ancilla) - if label_leakage == "leakage": + if label_leakage == "leakage" or label_leakage == "leakage-all": q_ancilla_leakage = qiskit.QuantumRegister(len(q_leakage), "q_ancilla_leakage") c_ancilla_leakage = qiskit.ClassicalRegister(len(q_leakage), "c_ancilla_leakage") qc.add_register(q_ancilla_leakage) @@ -1179,7 +1185,7 @@ def to_qiskit_gates(self, psi=None, optimal=False, q_measure=[], # qc.x(q_ancilla_leakage[count]) # qc.measure(q_ancilla_leakage[count], c_ancilla_leakage[count]) - if label_leakage == "leakage": + if label_leakage == "leakage" or label_leakage == "leakage-all": for count, elem in enumerate(q_leakage): qc.cx(q_l[elem], q_ancilla_leakage[count]) qc.x(q_l[elem]) From a7253f9eae3bfbffa4b26129f6b0101511df183a Mon Sep 17 00:00:00 2001 From: rezahhh Date: Tue, 13 Sep 2022 11:15:39 -0700 Subject: [PATCH 40/64] added open-qasm --- quimb/tensor/circuit.py | 80 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 2 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index 70e92dc0e..f504d3fa0 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -18,7 +18,7 @@ from . import array_ops as ops import qiskit - +from math import pi def _convert_ints_and_floats(x): if isinstance(x, str): @@ -85,6 +85,73 @@ def parse_qasm(qasm): } +def parse_open_qasm(qasm): + """Parse qasm from a string. + + Parameters + ---------- + qasm : str + The full string of the qasm file. + + Returns + ------- + circuit_info : dict + Information about the circuit: + + - circuit_info['n']: the number of qubits + - circuit_info['n_gates']: the number of gates in total + - circuit_info['gates']: list[list[str]], list of gates, each of which + is a list of strings read from a line of the qasm file. + """ + + lines = qasm.split('\n') + print(pi, eval('pi/2'), eval('2* pi / 4')) + # turn into tuples of python types + gates = [] + qubits = [] + for count, line in enumerate(lines): + if line.startswith("qreg"): + match = re.findall("\d+", line) + qubits.append(int(match[0])) + elif line.startswith(("creg", "include", "measure", "OPENQASM", "barrier")): + continue + elif line: + gates.append(tuple(map(_convert_ints_and_floats, line.strip().split(" ")))) + + n = int(sum(qubits)) + gate_f = [] + for i in gates: + gate, q = i + q_l = [] + parameter_l = [] + gate_l = [] + gate_symbol = gate.split("(")[0] + + gate_l.append(gate_symbol) + match = re.findall("\d+", q) + if match: + match = [int(i) for i in match] + q_l = match + + match = re.findall('\(.*?\)', gate) + + if match: + match = [i.replace('(', '').replace(')', '') for i in match] + match = [float(eval(i)) for i in match] + parameter_l = match + + zip_all = (*gate_l, *parameter_l, *q_l) + gate_f.append(zip_all) + + round_specified = isinstance(gates[0][0], numbers.Integral) + return { + 'n': n, + 'gates': tuple(gate_f), + 'n_gates': len(gate_f), + 'round_specified': round_specified, + } + + def parse_qasm_file(fname, **kwargs): """Parse a qasm file. """ @@ -820,6 +887,15 @@ def __init__( self._storage = dict() self._sampled_conditionals = dict() + @classmethod + def from_open_qasm(cls, qasm, **quantum_circuit_opts): + """Generate a ``Circuit`` instance from a qasm string. + """ + info = parse_open_qasm(qasm) + qc = cls(info['n'], **quantum_circuit_opts) + qc.apply_gates(info['gates']) + return qc + @classmethod def from_qasm(cls, qasm, **quantum_circuit_opts): """Generate a ``Circuit`` instance from a qasm string. @@ -1276,7 +1352,7 @@ def apply_gates(self, gates): The sequence of gates to apply. """ for gate in gates: - self.apply_gate(*gate) + self.apply_gate(*gate, contract=False) self._psi.squeeze_() From 5ad66d02dccccda1c96b3c3276ec37c80f9d3e53 Mon Sep 17 00:00:00 2001 From: Reza Haghshenas Date: Tue, 13 Sep 2022 11:34:56 -0700 Subject: [PATCH 41/64] fixed RZZ & to_qiskit --- quimb/tensor/circuit.py | 71 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 63 insertions(+), 8 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index 3850611f2..ad4cccb95 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -510,8 +510,8 @@ def apply_fsimg( def rzz_param_gen(params): gamma = params[0] - c00 = c11 = do('complex', do('cos', gamma), do('sin', gamma)) - c01 = c10 = do('complex', do('cos', gamma), -do('sin', gamma)) + c00 = c11 = do('complex', do('cos', gamma / 2), -do('sin', gamma / 2)) + c01 = c10 = do('complex', do('cos', gamma / 2), do('sin', gamma / 2)) data = [[[[c00, 0], [0, 0]], [[0, c01], [0, 0]]], @@ -528,7 +528,7 @@ def rzz(gamma): .. math:: - \mathrm{RZZ}(\gamma) = \exp(-i \gamma Z_i Z_j) + \mathrm{RZZ}(\gamma) = \exp(-i (\gamma / 2.) Z_i Z_j) """ return rzz_param_gen(np.array([gamma])) @@ -1078,17 +1078,17 @@ def to_qiskit_gates(self, psi=None, optimal=False, q_measure=[], label_leakage="off"): q_virtual, q_physical = self.qubits_in_light_cone(psi) q_leakage = [] - if label_leakage=="leakage": + if label_leakage == "leakage": for i in q_physical: q_leakage.append(i) if i % 2 == 0 and (i+1) not in q_physical: if (i+1) in q_virtual: q_leakage.append(i+1) - if i % 2 == 1 and (i-1) not in q_physical: + if (i % 2) == 1 and (i-1) not in q_physical: if (i-1) in q_virtual: q_leakage.append(i-1) - elif label_leakage=="leakage-all": - q_total=q_virtual+q_physical + elif label_leakage == "leakage-all": + q_total = q_virtual+q_physical for i in q_total: q_leakage.append(i) @@ -1140,7 +1140,7 @@ def to_qiskit_gates(self, psi=None, optimal=False, q_measure=[], if dic_id[i] == "RZZ": t0, t1 = dic_r[i] p0, = dic_p[i] - qc.rzz(-p0*2., q_l[t0], q_l[t1]) + qc.rzz(p0, q_l[t0], q_l[t1]) if dic_id[i] == "RY": t0, = dic_r[i] p0, = dic_p[i] @@ -1211,6 +1211,61 @@ def to_qiskit_gates(self, psi=None, optimal=False, q_measure=[], return qc + def to_qiskit(self): + + q_l = self.q_qiskit + #c_l = self.c_qiskit + #qc = qiskit.QuantumCircuit(*q_l, *c_l) + qc = qiskit.QuantumCircuit(*q_l) + + gate_p = self.partial_gates(self.psi) + dic_id = self.gate_id_map() + dic_r = self.gate_regs_map() + dic_p = self.gate_params_map() + + for i in gate_p: + # print("gate",i, dic_id[i]) + if dic_id[i] == "CNOT": + t0, t1 = dic_r[i] + qc.cx(q_l[t0], q_l[t1]) + if dic_id[i] == "CZ": + t0, t1 = dic_r[i] + qc.cz(q_l[t0], q_l[t1]) + if dic_id[i] == "H": + t0, = dic_r[i] + qc.h(q_l[t0]) + if dic_id[i] == "RZZ": + t0, t1 = dic_r[i] + p0, = dic_p[i] + qc.rzz(p0, q_l[t0], q_l[t1]) + if dic_id[i] == "RY": + t0, = dic_r[i] + p0, = dic_p[i] + qc.ry(p0, q_l[t0]) + if dic_id[i] == "RX": + t0, = dic_r[i] + p0, = dic_p[i] + qc.rx(p0, q_l[t0]) + if dic_id[i] == "RZ": + t0, = dic_r[i] + p0, = dic_p[i] + qc.rz(p0, q_l[t0]) + if dic_id[i] == "X": + t0, = dic_r[i] + qc.x(q_l[t0]) + if dic_id[i] == "Y": + t0, = dic_r[i] + qc.y(q_l[t0]) + if dic_id[i] == "Z": + t0, = dic_r[i] + qc.z(q_l[t0]) + if dic_id[i] == "T": + t0, = dic_r[i] + qc.t(q_l[t0]) + + return qc + + def apply_gate(self, gate_id, *gate_args, gate_round=None, gate_shared=None, **gate_opts): """Apply a single gate to this tensor network quantum circuit. If From 6cb3c2a2118d94534d44a980c7c8a752989a7e64 Mon Sep 17 00:00:00 2001 From: Reza Haghshenas Date: Tue, 13 Sep 2022 12:33:44 -0700 Subject: [PATCH 42/64] fixed QuimbCircuit_to_open_qasm --- quimb/tensor/circuit.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index 592a2e9ae..a4311ea00 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -85,7 +85,7 @@ def parse_qasm(qasm): } -def parse_open_qasm(qasm): +def parse_open_qasm(qasm, symbol_q): """Parse qasm from a string. Parameters @@ -105,7 +105,7 @@ def parse_open_qasm(qasm): """ lines = qasm.split('\n') - print(pi, eval('pi/2'), eval('2* pi / 4')) + # turn into tuples of python types gates = [] qubits = [] @@ -128,18 +128,17 @@ def parse_open_qasm(qasm): gate_symbol = gate.split("(")[0] gate_l.append(gate_symbol) - match = re.findall("\d+", q) + match = re.findall(symbol_q+"\d+", q) if match: - match = [int(i) for i in match] - q_l = match - + match = [int(i.replace(symbol_q, '')) for i in match] + q_l = match match = re.findall('\(.*?\)', gate) if match: match = [i.replace('(', '').replace(')', '') for i in match] match = [float(eval(i)) for i in match] parameter_l = match - + zip_all = (*gate_l, *parameter_l, *q_l) gate_f.append(zip_all) @@ -888,10 +887,10 @@ def __init__( self._sampled_conditionals = dict() @classmethod - def from_open_qasm(cls, qasm, **quantum_circuit_opts): + def from_open_qasm(cls, qasm, symbol_q, **quantum_circuit_opts): """Generate a ``Circuit`` instance from a qasm string. """ - info = parse_open_qasm(qasm) + info = parse_open_qasm(qasm, symbol_q) qc = cls(info['n'], **quantum_circuit_opts) qc.apply_gates(info['gates']) return qc @@ -1301,6 +1300,9 @@ def to_qiskit(self): for i in gate_p: # print("gate",i, dic_id[i]) + if dic_id[i] == "CX": + t0, t1 = dic_r[i] + qc.cx(q_l[t0], q_l[t1]) if dic_id[i] == "CNOT": t0, t1 = dic_r[i] qc.cx(q_l[t0], q_l[t1]) From e1d9d6be1af45b79cc8b493789a908e2816d20ec Mon Sep 17 00:00:00 2001 From: Reza Haghshenas Date: Tue, 13 Sep 2022 18:28:01 -0700 Subject: [PATCH 43/64] fixed QuimbCircuit_to_open_qasm+ --- quimb/tensor/circuit.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index a4311ea00..2eb44a22f 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -113,8 +113,10 @@ def parse_open_qasm(qasm, symbol_q): if line.startswith("qreg"): match = re.findall("\d+", line) qubits.append(int(match[0])) - elif line.startswith(("creg", "include", "measure", "OPENQASM", "barrier")): + elif line.startswith(("creg", "include", "measure", "OPENQASM", "barrier", "reset", "if")): continue + elif line.startswith(("gate")): + print("warnning, gate is not used") elif line: gates.append(tuple(map(_convert_ints_and_floats, line.strip().split(" ")))) @@ -134,11 +136,16 @@ def parse_open_qasm(qasm, symbol_q): q_l = match match = re.findall('\(.*?\)', gate) + if match: match = [i.replace('(', '').replace(')', '') for i in match] - match = [float(eval(i)) for i in match] - parameter_l = match - + if "," in match[0]: + n_float = match[0].split(",") + parameter_l = [float(i) for i in n_float] + else: + match = [float(eval(i)) for i in match] + parameter_l = match + zip_all = (*gate_l, *parameter_l, *q_l) gate_f.append(zip_all) @@ -1316,6 +1323,10 @@ def to_qiskit(self): t0, t1 = dic_r[i] p0, = dic_p[i] qc.rzz(p0, q_l[t0], q_l[t1]) + if dic_id[i] == "U3": + t0, = dic_r[i] + p0, p1, p2 = dic_p[i] + qc.u3(p0, p1, p2, q_l[t0]) if dic_id[i] == "RY": t0, = dic_r[i] p0, = dic_p[i] From cf50cf01b3e9198fbb754c2e63ab8951a20c67fa Mon Sep 17 00:00:00 2001 From: rezahhh Date: Mon, 19 Sep 2022 11:20:33 -0700 Subject: [PATCH 44/64] +raw rho --- quimb/gen/operators.py | 2 +- quimb/tensor/circuit.py | 122 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 1 deletion(-) diff --git a/quimb/gen/operators.py b/quimb/gen/operators.py index 082df9a36..b60f02d42 100644 --- a/quimb/gen/operators.py +++ b/quimb/gen/operators.py @@ -362,7 +362,7 @@ def fsimt(theta, dtype=complex, **kwargs): 1 & 0 & 0 & 0\\ 0 & \cos(\theta) & sin(\theta) & 0\\ 0 & - sin(\theta) & \cos(\theta) & 0\\ - 0 & 0 & 0 & \exp(-i \phi) + 0 & 0 & 0 & 1 \end{bmatrix} Note that ``theta`` and ``phi`` should be specified in radians and the sign diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index 2eb44a22f..3173dc5b0 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -675,23 +675,40 @@ def apply_su4( GATE_FUNCTIONS = { # constant single qubit gates 'H': build_gate_1(qu.hadamard(), tags='H'), + 'H_dagg': build_gate_1(qu.hadamard().conj(), tags='H'), 'X': build_gate_1(qu.pauli('X'), tags='X'), + 'X_dagg': build_gate_1(qu.pauli('X').conj(), tags='X'), 'Y': build_gate_1(qu.pauli('Y'), tags='Y'), + 'Y_dagg': build_gate_1(qu.pauli('Y').conj(), tags='Y'), 'Z': build_gate_1(qu.pauli('Z'), tags='Z'), + 'Z_dagg': build_gate_1(qu.pauli('Z').conj(), tags='Z'), 'S': build_gate_1(qu.S_gate(), tags='S'), + 'S_dagg': build_gate_1(qu.S_gate().conj(), tags='S'), 'T': build_gate_1(qu.T_gate(), tags='T'), + 'T_dagg': build_gate_1(qu.T_gate().conj(), tags='T'), 'X_1_2': build_gate_1(qu.Xsqrt(), tags='X_1/2'), + 'X_1_2_dagg': build_gate_1(qu.Xsqrt().conj(), tags='X_1/2'), 'Y_1_2': build_gate_1(qu.Ysqrt(), tags='Y_1/2'), + 'Y_1_2_dagg': build_gate_1(qu.Ysqrt().conj(), tags='Y_1/2'), 'Z_1_2': build_gate_1(qu.Zsqrt(), tags='Z_1/2'), + 'Z_1_2_dagg': build_gate_1(qu.Zsqrt().conj(), tags='Z_1/2'), 'W_1_2': build_gate_1(qu.Wsqrt(), tags='W_1/2'), + 'W_1_2_dagg': build_gate_1(qu.Wsqrt().conj(), tags='W_1/2'), 'HZ_1_2': build_gate_1(qu.Wsqrt(), tags='W_1/2'), + 'HZ_1_2_dagg': build_gate_1(qu.Wsqrt().conj(), tags='W_1/2'), # constant two qubit gates 'CNOT': build_gate_2(qu.CNOT(), tags='CNOT'), + 'CNOT_dagg': build_gate_2(qu.CNOT().conj(), tags='CNOT'), 'CX': build_gate_2(qu.cX(), tags='CX'), + 'CX_dagg': build_gate_2(qu.cX().conj(), tags='CX'), 'CY': build_gate_2(qu.cY(), tags='CY'), + 'CY_dagg': build_gate_2(qu.cY().conj(), tags='CY'), 'CZ': build_gate_2(qu.cZ(), tags='CZ'), + 'CZ_dagg': build_gate_2(qu.cZ().conj(), tags='CZ'), 'IS': build_gate_2(qu.iswap(), tags='ISWAP'), + 'IS_dagg': build_gate_2(qu.iswap().conj(), tags='ISWAP'), 'ISWAP': build_gate_2(qu.iswap(), tags='ISWAP'), + 'ISWAP_dagg': build_gate_2(qu.iswap().conj(), tags='ISWAP'), # special non-tensor gates 'IDEN': lambda *args, **kwargs: None, 'SWAP': apply_swap, @@ -718,6 +735,9 @@ def apply_su4( TWO_QUBIT_PARAM_GATES = { 'CU3', 'CU2', 'CU1', 'FS', 'FSIM', 'FSIMT', 'FSIMG', 'RZZ', 'SU4' } +TWO_QUBIT_GATES = {'CNOT', 'CX', 'CY', 'CZ', 'IS', 'ISWAP'} +ONE_QUBIT_GATES = {'H', 'X', 'Y', 'Z', 'S', 'T', 'X_1_2', 'Y_1_2', 'Z_1_2', 'W_1_2', 'HZ_1_2'} + ALL_PARAM_GATES = ONE_QUBIT_PARAM_GATES | TWO_QUBIT_PARAM_GATES @@ -825,6 +845,7 @@ def __init__( self, N=None, psi0=None, + rho0=None, gate_opts=None, tags=None, psi0_dtype='complex128', @@ -835,8 +856,10 @@ def __init__( raise ValueError("You must supply one of `N` or `psi0`.") elif psi0 is None: + print("Hi") self.N = N self._psi = MPS_computational_state('0' * N, dtype=psi0_dtype) + self._rho = MPS_computational_state('0' * int(2 * N), dtype=psi0_dtype) elif N is None: self._psi = psi0.copy() @@ -848,6 +871,13 @@ def __init__( self.N = N self._psi = psi0.copy() + + if rho0 is None: + self._rho = MPS_computational_state('0' * int(2 * N), dtype=psi0_dtype) + else: + self._rho = MPS_computational_state('0' * int(2 * N), dtype=psi0_dtype) + + self.q_qiskit = [] for i in range(self.N): self.q_qiskit.append(qiskit.QuantumRegister(1, f"q_{i}")) @@ -860,12 +890,15 @@ def __init__( for count, ele in enumerate(self._psi): ele.add_tag(f"Qreg{count}") + for count, ele in enumerate(self._rho): + ele.add_tag(f"Qreg{count}") if tags is not None: if isinstance(tags, str): tags = (tags,) for tag in tags: self._psi.add_tag(tag) + self._rho.add_tag(tag) self.gate_opts = ensure_dict(gate_opts) self.gate_opts.setdefault('contract', 'auto-split-gate') @@ -929,6 +962,7 @@ def from_qasm_url(cls, url, **quantum_circuit_opts): qc.apply_gates(info['gates']) return qc + def apply_gate_raw(self, U, where, tags=None, gate_round=None, **gate_opts): """Apply the raw array ``U`` as a gate on qubits in ``where``. It will @@ -1417,6 +1451,72 @@ def apply_gate(self, gate_id, *gate_args, gate_round=None, # keep track of the gates applied self.gates.append((gate_id, *gate_args)) + all_param_gates = ONE_QUBIT_PARAM_GATES | TWO_QUBIT_PARAM_GATES + + if gate_id in all_param_gates: + where = [i for i in gate_args if isinstance(i, numbers.Integral)] + parameters_ = list(gate_args[:len(gate_args)-len(where)]) + + if gate_id in ONE_QUBIT_PARAM_GATES: + if gate_id=="U3": + parameters_dagger = [parameters_[0], -1. * parameters_[1], -1. *parameters_[2]] + elif gate_id=="RY": + parameters_dagger = [+1.*i for i in parameters_] + else: + parameters_dagger = [-1.*i for i in parameters_] + + + + print("gate_id", gate_id, len(parameters_), parameters_) + if gate_id in TWO_QUBIT_PARAM_GATES: + if gate_id=="SU4": + parameters_dagger = [parameters_[0], -1. * parameters_[1], -1. * parameters_[2] , + parameters_[3], -1. * parameters_[4], -1. * parameters_[5], + parameters_[6], -1. * parameters_[7], -1. * parameters_[8], + parameters_[9], -1. * parameters_[10], -1. * parameters_[11], + -1. * parameters_[12], 1. * parameters_[13], 1. * parameters_[14], + ] + # elif gate_id=="FSIM": + # parameters_dagger = [parameters_[0], -1.0 * parameters_[1]] + # elif gate_id=="FSIMT": + # parameters_dagger = [parameters_[0]] + # elif gate_id=="FSIMG": + # parameters_dagger = [parameters_[0], -1.0 * parameters_[1], -1.0 * parameters_[2], + # -1.0 * parameters_[3], -1.0 * parameters_[4]] + # else: + else: + parameters_dagger = [-1.*i for i in parameters_] + + print("where, parameters_", where, parameters_) + where_even = [2*i for i in where] + gate_args_even = parameters_ + where_even + print("gate_args_even", * parameters_ + where_even) + gate_fn(self._rho, * parameters_ + where_even, tags=tags, **opts) + where_odd = [2*i+1 for i in where] + gate_args_odd = parameters_dagger + where_odd + print("gate_args_odd", * parameters_dagger + where_odd) + gate_fn(self._rho, * parameters_dagger + where_odd, tags=tags, **opts) + + all_constant_gates = TWO_QUBIT_GATES | ONE_QUBIT_GATES + if gate_id in all_constant_gates: + gate_args_even = [2*i for i in gate_args] + print(gate_id, gate_args, gate_args_even) + gate_fn(self._rho, *gate_args_even, tags=tags, **opts) + gate_args_odd = [2*i+1 for i in gate_args] + print(gate_id, gate_args, gate_args_odd) + gate_fn_dagg = GATE_FUNCTIONS[gate_id+"_dagg"] + print(gate_id+"_dagg",GATE_FUNCTIONS[gate_id+"_dagg"]) + gate_fn_dagg(self._rho, *gate_args_odd, tags=tags, **opts) + +# ONE_QUBIT_PARAM_GATES = {'RX', 'RY', 'RZ', 'U3', 'U2', 'U1'} +# TWO_QUBIT_PARAM_GATES = { +# 'CU3', 'CU2', 'CU1', 'FS', 'FSIM', 'FSIMT', 'FSIMG', 'RZZ', 'SU4' +# } +# TWO_QUBIT_GATES = {'CNOT', 'CX' 'CY', 'CZ', 'IS', 'ISWAP'} +# ONE_QUBIT_GATES = {'H', 'X' 'Y', 'Z', 'S', 'T', 'X_1_2', 'Y_1_2', 'Z_1_2', 'W_1_2', 'HZ_1_2'} + + + def apply_gates(self, gates): """Apply a sequence of gates to this tensor network quantum circuit. @@ -1580,6 +1680,28 @@ def psi(self): psi.astype_(psi.dtype) return psi + + @property + def rho(self): + """Tensor network representation of the wavefunction. + """ + # make sure all same dtype and drop singlet dimensions + rho = self._rho.copy() + map_k = {f"k{2*i}": f"k{i}" for i in range(self.N)} + map_b = {f"k{2*i+1}": f"b{i}" for i in range(self.N)} + print(map_b, map_k,) + # for i in rho: + # print(i) + rho.reindex_(map_b) + rho.reindex_(map_k) + # for i in rho: + # print("new", i) + + rho.squeeze_() + rho.astype_(rho.dtype) + return rho + + def get_uni(self, transposed=False): """Tensor network representation of the unitary operator (i.e. with the initial state removed). From af9f1e96c94abb87303b6b5089065d1e12725f4f Mon Sep 17 00:00:00 2001 From: Reza Haghshenas Date: Mon, 19 Sep 2022 15:48:36 -0700 Subject: [PATCH 45/64] add_fun(conj)+tests pass --- quimb/tensor/circuit.py | 304 +++++++++++++++++++++++++++++----------- 1 file changed, 224 insertions(+), 80 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index 3173dc5b0..abb41e550 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -6,7 +6,7 @@ import itertools import numpy as np -from autoray import do, reshape +from autoray import do, reshape, conj import quimb as qu from ..utils import progbar as _progbar @@ -234,6 +234,18 @@ def apply_Rx(psi, theta, i, parametrize=False, **gate_opts): psi.gate_(G, int(i), tags=mtags, **gate_opts) +def apply_Rx_conj(psi, theta, i, parametrize=False, **gate_opts): + """Apply an X-rotation of ``theta`` to tensor network wavefunction ``psi``. + """ + mtags = _merge_tags('RX', gate_opts) + if parametrize: + G = ops.PArray(rx_gate_param_gen, (theta,)) + G.add_function(conj) + else: + G = qu.Rx(float(theta)).conj() + psi.gate_(G, int(i), tags=mtags, **gate_opts) + + def ry_gate_param_gen(params): phi = params[0] @@ -259,6 +271,18 @@ def apply_Ry(psi, theta, i, parametrize=False, **gate_opts): G = qu.Ry(float(theta)) psi.gate_(G, int(i), tags=mtags, **gate_opts) +def apply_Ry_conj(psi, theta, i, parametrize=False, **gate_opts): + """Apply a Y-rotation of ``theta`` to tensor network wavefunction ``psi``. + """ + mtags = _merge_tags('RY', gate_opts) + if parametrize: + G = ops.PArray(ry_gate_param_gen, (theta,)) + G.add_function(conj) + else: + G = qu.Ry(float(theta)).conj() + psi.gate_(G, int(i), tags=mtags, **gate_opts) + + def rz_gate_param_gen(params): phi = params[0] @@ -285,6 +309,16 @@ def apply_Rz(psi, theta, i, parametrize=False, **gate_opts): G = qu.Rz(float(theta)) psi.gate_(G, int(i), tags=mtags, **gate_opts) +def apply_Rz_conj(psi, theta, i, parametrize=False, **gate_opts): + """Apply a Z-rotation of ``theta`` to tensor network wavefunction ``psi``. + """ + mtags = _merge_tags('RZ', gate_opts) + if parametrize: + G = ops.PArray(rz_gate_param_gen, (theta,)) + G.add_function(conj) + else: + G = qu.Rz(float(theta)).conj() + psi.gate_(G, int(i), tags=mtags, **gate_opts) def u3_gate_param_gen(params): theta, phi, lamda = params[0], params[1], params[2] @@ -322,6 +356,15 @@ def apply_U3(psi, theta, phi, lamda, i, parametrize=False, **gate_opts): G = qu.U_gate(theta, phi, lamda) psi.gate_(G, int(i), tags=mtags, **gate_opts) +def apply_U3_conj(psi, theta, phi, lamda, i, parametrize=False, **gate_opts): + mtags = _merge_tags('U3', gate_opts) + if parametrize: + G = ops.PArray(u3_gate_param_gen, (theta, phi, lamda)) + G.add_function(conj) + else: + G = qu.U_gate(theta, phi, lamda).conj() + psi.gate_(G, int(i), tags=mtags, **gate_opts) + def u2_gate_param_gen(params): phi, lamda = params[0], params[1] @@ -354,6 +397,17 @@ def apply_U2(psi, phi, lamda, i, parametrize=False, **gate_opts): psi.gate_(G, int(i), tags=mtags, **gate_opts) + +def apply_U2_conj(psi, phi, lamda, i, parametrize=False, **gate_opts): + mtags = _merge_tags('U2', gate_opts) + if parametrize: + G = ops.PArray(u2_gate_param_gen, (phi, lamda)) + G.add_function(conj) + else: + G = qu.U_gate(np.pi / 2, phi, lamda).conj() + psi.gate_(G, int(i), tags=mtags, **gate_opts) + + def u1_gate_param_gen(params): lamda = params[0] @@ -374,6 +428,15 @@ def apply_U1(psi, lamda, i, parametrize=False, **gate_opts): G = qu.U_gate(0.0, 0.0, lamda) psi.gate_(G, int(i), tags=mtags, **gate_opts) +def apply_U1_conj(psi, lamda, i, parametrize=False, **gate_opts): + mtags = _merge_tags('U1', gate_opts) + if parametrize: + G = ops.PArray(u1_gate_param_gen, (lamda,)) + G.add_function(conj) + else: + G = qu.U_gate(0.0, 0.0, lamda).conj() + psi.gate_(G, int(i), tags=mtags, **gate_opts) + def cu3_param_gen(params): U3 = u3_gate_param_gen(params) @@ -400,6 +463,16 @@ def apply_cu3(psi, theta, phi, lamda, i, j, parametrize=False, **gate_opts): psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) +def apply_cu3_conj(psi, theta, phi, lamda, i, j, parametrize=False, **gate_opts): + mtags = _merge_tags('CU3', gate_opts) + if parametrize: + G = ops.PArray(cu3_param_gen, (theta, phi, lamda)) + G.add_function(conj) + else: + G = cu3(theta, phi, lamda).conj + psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) + + def cu2_param_gen(params): U2 = u2_gate_param_gen(params) @@ -425,6 +498,15 @@ def apply_cu2(psi, phi, lamda, i, j, parametrize=False, **gate_opts): psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) +def apply_cu2_conj(psi, phi, lamda, i, j, parametrize=False, **gate_opts): + mtags = _merge_tags('CU2', gate_opts) + if parametrize: + G = ops.PArray(cu2_param_gen, (phi, lamda)) + G.add_function(conj) + else: + G = cu2(phi, lamda).conj() + psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) + def cu1_param_gen(params): lamda = params[0] @@ -454,6 +536,17 @@ def apply_cu1(psi, lamda, i, j, parametrize=False, **gate_opts): psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) +def apply_cu1_conj(psi, lamda, i, j, parametrize=False, **gate_opts): + mtags = _merge_tags('CU1', gate_opts) + if parametrize: + G = ops.PArray(cu1_param_gen, (lamda,)) + G.add_function(conj) + else: + G = cu1(lamda).conj() + psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) + + + def fsim_param_gen(params): theta, phi = params[0], params[1] @@ -504,6 +597,17 @@ def apply_fsim(psi, theta, phi, i, j, parametrize=False, **gate_opts): psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) + +def apply_fsim_conj(psi, theta, phi, i, j, parametrize=False, **gate_opts): + mtags = _merge_tags('FSIM', gate_opts) + if parametrize: + G = ops.PArray(fsim_param_gen, (theta, phi)) + G.add_function(conj) + else: + G = qu.fsim(theta, phi).conj() + psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) + + def apply_fsimt(psi, theta, i, j, parametrize=False, **gate_opts): mtags = _merge_tags('FSIMT', gate_opts) if parametrize: @@ -513,6 +617,18 @@ def apply_fsimt(psi, theta, i, j, parametrize=False, **gate_opts): psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) + +def apply_fsimt_conj(psi, theta, i, j, parametrize=False, **gate_opts): + mtags = _merge_tags('FSIMT', gate_opts) + if parametrize: + G = ops.PArray(fsimt_param_gen, (theta,)) + G.add_function(conj) + else: + G = qu.fsimt(theta).conj() + psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) + + + def fsimg_param_gen(params): theta, zeta, chi, gamma, phi = ( params[0], params[1], params[2], params[3], params[4] @@ -580,6 +696,25 @@ def apply_fsimg( psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) +def apply_fsimg_conj( + psi, + theta, zeta, chi, gamma, phi, + i, j, parametrize=False, **gate_opts +): + + mtags = _merge_tags('FSIMG', gate_opts) + if parametrize: + G = ops.PArray(fsimg_param_gen, (theta, zeta, chi, gamma, phi)) + G.add_function(conj) + else: + G = qu.fsimg(theta, zeta, chi, gamma, phi).conj() + psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) + + + + + + def rzz_param_gen(params): gamma = params[0] @@ -616,6 +751,16 @@ def apply_rzz(psi, gamma, i, j, parametrize=False, **gate_opts): psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) +def apply_rzz_conj(psi, gamma, i, j, parametrize=False, **gate_opts): + mtags = _merge_tags('RZZ', gate_opts) + if parametrize: + G = ops.PArray(rzz_param_gen, (gamma,)) + G.add_function(conj) + else: + G = rzz(float(gamma)).conj() + psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) + + def su4_gate_param_gen(params): """See https://arxiv.org/abs/quant-ph/0308006 - Fig. 7. """ @@ -672,68 +817,113 @@ def apply_su4( psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) +def apply_su4_conj( + psi, + theta1, phi1, lamda1, + theta2, phi2, lamda2, + theta3, phi3, lamda3, + theta4, phi4, lamda4, + t1, t2, t3, + i, j, + parametrize=False, + **gate_opts +): + """See https://arxiv.org/abs/quant-ph/0308006 - Fig. 7. + """ + params = (theta1, phi1, lamda1, + theta2, phi2, lamda2, + theta3, phi3, lamda3, + theta4, phi4, lamda4, + t1, t2, t3,) + + mtags = _merge_tags('SU4', gate_opts) + if parametrize: + G = ops.PArray(su4_gate_param_gen, params) + G.add_function(conj) + else: + G = su4_gate_param_gen(params).conj() + + psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) + + + GATE_FUNCTIONS = { # constant single qubit gates 'H': build_gate_1(qu.hadamard(), tags='H'), - 'H_dagg': build_gate_1(qu.hadamard().conj(), tags='H'), + 'H_conj': build_gate_1(qu.hadamard().conj(), tags='H'), 'X': build_gate_1(qu.pauli('X'), tags='X'), - 'X_dagg': build_gate_1(qu.pauli('X').conj(), tags='X'), + 'X_conj': build_gate_1(qu.pauli('X').conj(), tags='X'), 'Y': build_gate_1(qu.pauli('Y'), tags='Y'), - 'Y_dagg': build_gate_1(qu.pauli('Y').conj(), tags='Y'), + 'Y_conj': build_gate_1(qu.pauli('Y').conj(), tags='Y'), 'Z': build_gate_1(qu.pauli('Z'), tags='Z'), - 'Z_dagg': build_gate_1(qu.pauli('Z').conj(), tags='Z'), + 'Z_conj': build_gate_1(qu.pauli('Z').conj(), tags='Z'), 'S': build_gate_1(qu.S_gate(), tags='S'), - 'S_dagg': build_gate_1(qu.S_gate().conj(), tags='S'), + 'S_conj': build_gate_1(qu.S_gate().conj(), tags='S'), 'T': build_gate_1(qu.T_gate(), tags='T'), - 'T_dagg': build_gate_1(qu.T_gate().conj(), tags='T'), + 'T_conj': build_gate_1(qu.T_gate().conj(), tags='T'), 'X_1_2': build_gate_1(qu.Xsqrt(), tags='X_1/2'), - 'X_1_2_dagg': build_gate_1(qu.Xsqrt().conj(), tags='X_1/2'), + 'X_1_2_conj': build_gate_1(qu.Xsqrt().conj(), tags='X_1/2'), 'Y_1_2': build_gate_1(qu.Ysqrt(), tags='Y_1/2'), - 'Y_1_2_dagg': build_gate_1(qu.Ysqrt().conj(), tags='Y_1/2'), + 'Y_1_2_conj': build_gate_1(qu.Ysqrt().conj(), tags='Y_1/2'), 'Z_1_2': build_gate_1(qu.Zsqrt(), tags='Z_1/2'), - 'Z_1_2_dagg': build_gate_1(qu.Zsqrt().conj(), tags='Z_1/2'), + 'Z_1_2_conj': build_gate_1(qu.Zsqrt().conj(), tags='Z_1/2'), 'W_1_2': build_gate_1(qu.Wsqrt(), tags='W_1/2'), - 'W_1_2_dagg': build_gate_1(qu.Wsqrt().conj(), tags='W_1/2'), + 'W_1_2_conj': build_gate_1(qu.Wsqrt().conj(), tags='W_1/2'), 'HZ_1_2': build_gate_1(qu.Wsqrt(), tags='W_1/2'), - 'HZ_1_2_dagg': build_gate_1(qu.Wsqrt().conj(), tags='W_1/2'), + 'HZ_1_2_conj': build_gate_1(qu.Wsqrt().conj(), tags='W_1/2'), # constant two qubit gates 'CNOT': build_gate_2(qu.CNOT(), tags='CNOT'), - 'CNOT_dagg': build_gate_2(qu.CNOT().conj(), tags='CNOT'), + 'CNOT_conj': build_gate_2(qu.CNOT().conj(), tags='CNOT'), 'CX': build_gate_2(qu.cX(), tags='CX'), - 'CX_dagg': build_gate_2(qu.cX().conj(), tags='CX'), + 'CX_conj': build_gate_2(qu.cX().conj(), tags='CX'), 'CY': build_gate_2(qu.cY(), tags='CY'), - 'CY_dagg': build_gate_2(qu.cY().conj(), tags='CY'), + 'CY_conj': build_gate_2(qu.cY().conj(), tags='CY'), 'CZ': build_gate_2(qu.cZ(), tags='CZ'), - 'CZ_dagg': build_gate_2(qu.cZ().conj(), tags='CZ'), + 'CZ_conj': build_gate_2(qu.cZ().conj(), tags='CZ'), 'IS': build_gate_2(qu.iswap(), tags='ISWAP'), - 'IS_dagg': build_gate_2(qu.iswap().conj(), tags='ISWAP'), + 'IS_conj': build_gate_2(qu.iswap().conj(), tags='ISWAP'), 'ISWAP': build_gate_2(qu.iswap(), tags='ISWAP'), - 'ISWAP_dagg': build_gate_2(qu.iswap().conj(), tags='ISWAP'), + 'ISWAP_conj': build_gate_2(qu.iswap().conj(), tags='ISWAP'), # special non-tensor gates 'IDEN': lambda *args, **kwargs: None, 'SWAP': apply_swap, # single parametrizable gates 'RX': apply_Rx, + 'RX_conj': apply_Rx_conj, 'RY': apply_Ry, + 'RY_conj': apply_Ry_conj, 'RZ': apply_Rz, + 'RZ_conj': apply_Rz_conj, 'U3': apply_U3, + 'U3_conj': apply_U3_conj, 'U2': apply_U2, + 'U2_conj': apply_U2_conj, 'U1': apply_U1, + 'U1_conj': apply_U1_conj, # two qubit parametrizable gates 'CU3': apply_cu3, + 'CU3_conj': apply_cu3_conj, 'CU2': apply_cu2, + 'CU2_conj': apply_cu2_conj, 'CU1': apply_cu1, + 'CU1_conj': apply_cu1_conj, 'FS': apply_fsim, + 'FS_conj': apply_fsim_conj, 'FSIM': apply_fsim, + 'FSIM_conj': apply_fsim_conj, 'FSIMT': apply_fsimt, + 'FSIMT_conj': apply_fsimt_conj, 'FSIMG': apply_fsimg, + 'FSIMG_conj': apply_fsimg_conj, 'RZZ': apply_rzz, + 'RZZ_conj': apply_rzz_conj, 'SU4': apply_su4, + 'SU4_conj': apply_su4_conj, } ONE_QUBIT_PARAM_GATES = {'RX', 'RY', 'RZ', 'U3', 'U2', 'U1'} TWO_QUBIT_PARAM_GATES = { - 'CU3', 'CU2', 'CU1', 'FS', 'FSIM', 'FSIMT', 'FSIMG', 'RZZ', 'SU4' + 'CU3', 'CU2', 'CU1', 'FS', 'FSIM', 'FSIMT', 'FSIMG', 'RZZ', 'SU4', } TWO_QUBIT_GATES = {'CNOT', 'CX', 'CY', 'CZ', 'IS', 'ISWAP'} ONE_QUBIT_GATES = {'H', 'X', 'Y', 'Z', 'S', 'T', 'X_1_2', 'Y_1_2', 'Z_1_2', 'W_1_2', 'HZ_1_2'} @@ -1433,6 +1623,7 @@ def apply_gate(self, gate_id, *gate_args, gate_round=None, gate_id = gate_id.upper() gate_fn = GATE_FUNCTIONS[gate_id] + gate_fn_dagg = GATE_FUNCTIONS[gate_id+"_conj"] # overide any default gate opts opts = {**self.gate_opts, **gate_opts} @@ -1451,71 +1642,24 @@ def apply_gate(self, gate_id, *gate_args, gate_round=None, # keep track of the gates applied self.gates.append((gate_id, *gate_args)) - all_param_gates = ONE_QUBIT_PARAM_GATES | TWO_QUBIT_PARAM_GATES + all_ONE_QUBIT_GATES = ONE_QUBIT_PARAM_GATES | ONE_QUBIT_GATES + all_TWO_QUBIT_GATES = TWO_QUBIT_PARAM_GATES | TWO_QUBIT_GATES - if gate_id in all_param_gates: - where = [i for i in gate_args if isinstance(i, numbers.Integral)] + if gate_id in all_ONE_QUBIT_GATES: + where = [int(gate_args[-1])] + parameters_ = list(gate_args[:len(gate_args)-len(where)]) + if gate_id in all_TWO_QUBIT_GATES: + where = [int(gate_args[-2]), int(gate_args[-1])] parameters_ = list(gate_args[:len(gate_args)-len(where)]) - - if gate_id in ONE_QUBIT_PARAM_GATES: - if gate_id=="U3": - parameters_dagger = [parameters_[0], -1. * parameters_[1], -1. *parameters_[2]] - elif gate_id=="RY": - parameters_dagger = [+1.*i for i in parameters_] - else: - parameters_dagger = [-1.*i for i in parameters_] - - - - print("gate_id", gate_id, len(parameters_), parameters_) - if gate_id in TWO_QUBIT_PARAM_GATES: - if gate_id=="SU4": - parameters_dagger = [parameters_[0], -1. * parameters_[1], -1. * parameters_[2] , - parameters_[3], -1. * parameters_[4], -1. * parameters_[5], - parameters_[6], -1. * parameters_[7], -1. * parameters_[8], - parameters_[9], -1. * parameters_[10], -1. * parameters_[11], - -1. * parameters_[12], 1. * parameters_[13], 1. * parameters_[14], - ] - # elif gate_id=="FSIM": - # parameters_dagger = [parameters_[0], -1.0 * parameters_[1]] - # elif gate_id=="FSIMT": - # parameters_dagger = [parameters_[0]] - # elif gate_id=="FSIMG": - # parameters_dagger = [parameters_[0], -1.0 * parameters_[1], -1.0 * parameters_[2], - # -1.0 * parameters_[3], -1.0 * parameters_[4]] - # else: - else: - parameters_dagger = [-1.*i for i in parameters_] - - print("where, parameters_", where, parameters_) - where_even = [2*i for i in where] - gate_args_even = parameters_ + where_even - print("gate_args_even", * parameters_ + where_even) - gate_fn(self._rho, * parameters_ + where_even, tags=tags, **opts) - where_odd = [2*i+1 for i in where] - gate_args_odd = parameters_dagger + where_odd - print("gate_args_odd", * parameters_dagger + where_odd) - gate_fn(self._rho, * parameters_dagger + where_odd, tags=tags, **opts) - - all_constant_gates = TWO_QUBIT_GATES | ONE_QUBIT_GATES - if gate_id in all_constant_gates: - gate_args_even = [2*i for i in gate_args] - print(gate_id, gate_args, gate_args_even) - gate_fn(self._rho, *gate_args_even, tags=tags, **opts) - gate_args_odd = [2*i+1 for i in gate_args] - print(gate_id, gate_args, gate_args_odd) - gate_fn_dagg = GATE_FUNCTIONS[gate_id+"_dagg"] - print(gate_id+"_dagg",GATE_FUNCTIONS[gate_id+"_dagg"]) - gate_fn_dagg(self._rho, *gate_args_odd, tags=tags, **opts) - -# ONE_QUBIT_PARAM_GATES = {'RX', 'RY', 'RZ', 'U3', 'U2', 'U1'} -# TWO_QUBIT_PARAM_GATES = { -# 'CU3', 'CU2', 'CU1', 'FS', 'FSIM', 'FSIMT', 'FSIMG', 'RZZ', 'SU4' -# } -# TWO_QUBIT_GATES = {'CNOT', 'CX' 'CY', 'CZ', 'IS', 'ISWAP'} -# ONE_QUBIT_GATES = {'H', 'X' 'Y', 'Z', 'S', 'T', 'X_1_2', 'Y_1_2', 'Z_1_2', 'W_1_2', 'HZ_1_2'} + # where = [i for i in gate_args if isinstance(i, numbers.Integral)] + # parameters_ = list(gate_args[:len(gate_args)-len(where)]) + + where_even = [2*i for i in where] + where_odd = [2*i+1 for i in where] + gate_fn(self._rho, * parameters_ + where_even, tags=tags, **opts) + gate_fn_dagg(self._rho, * parameters_ + where_odd, tags=tags, **opts) def apply_gates(self, gates): """Apply a sequence of gates to this tensor network quantum circuit. From 8a82aed64058b48935140e6ea058c0fe8650ce51 Mon Sep 17 00:00:00 2001 From: rezahhh Date: Tue, 20 Sep 2022 08:48:24 -0700 Subject: [PATCH 46/64] + more noisy models --- quimb/tensor/circuit.py | 98 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 92 insertions(+), 6 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index abb41e550..571ff21f3 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -171,6 +171,82 @@ def parse_qasm_url(url, **kwargs): return parse_qasm(request.urlopen(url).read().decode(), **kwargs) +# -------------------------- noisy channel ---------------------------- # + + +def Y_CHANN_OQ(e=[1.-0.25, 0.25]): + II = qu.pauli('I') & qu.pauli('I') + YY = qu.pauli('Y') & qu.pauli('Y').conj() + Super_opt = e[0] * II + e[1] * YY + return Super_opt + + +def Z_CHANN_OQ(e=[1.-0.25, 0.25]): + II = qu.pauli('I') & qu.pauli('I') + ZZ = qu.pauli('Z') & qu.pauli('Z') + Super_opt = e[0] * II + e[1] * ZZ + return Super_opt + + +def X_CHANN_OQ(e=[1.-0.25, 0.25]): + II = qu.pauli('I') & qu.pauli('I') + XX = qu.pauli('X') & qu.pauli('X') + Super_opt = e[0] * II + e[1] * XX + return Super_opt + + +def QD_CHANN_OQ(e=[1.-0.5, 0.5/3., 0.5/3, 0.5/3.]): + II = qu.pauli('I') & qu.pauli('I') + XX = qu.pauli('X') & qu.pauli('X') + YY = qu.pauli('Y') & qu.pauli('Y').conj() + ZZ = qu.pauli('Z') & qu.pauli('Z') + Super_opt = e[0] * II + e[1]*XX + e[2] * ZZ + e[3]*YY + return Super_opt + + +def QD_CHANN_TQ(e=[1-0.5, 0.5/15, 0.5/15, 0.5/15, 0.5/15, 0.5/15, + 0.5/15, 0.5/15, 0.5/15, 0.5/15, 0.5/15, 0.5/15, 0.5/15, + 0.5/15, 0.5/15, 0.5/15]): + l_term = [ + qu.pauli('I') & qu.pauli('I') & qu.pauli('I') & qu.pauli('I'), # II \rho II + qu.pauli('I') & qu.pauli('x') & qu.pauli('I') & qu.pauli('X'), # IX \rho IX + qu.pauli('I') & qu.pauli('Z') & qu.pauli('I') & qu.pauli('Z'), # IZ \rho IZ + qu.pauli('I') & qu.pauli('Y') & qu.pauli('I') & qu.pauli('Y').conj(), # IY \rho IY + + qu.pauli('X') & qu.pauli('I') & qu.pauli('X') & qu.pauli('I'), # XI \rho XI + qu.pauli('X') & qu.pauli('Y') & qu.pauli('X') & qu.pauli('Y').conj(), # XY \rho XY + qu.pauli('X') & qu.pauli('Z') & qu.pauli('X') & qu.pauli('Z'), # XZ \rho XZ + qu.pauli('X') & qu.pauli('X') & qu.pauli('X') & qu.pauli('X'), # XX \rho XX + + qu.pauli('Y') & qu.pauli('I') & qu.pauli('Y').conj() & qu.pauli('I'), # YI \rho YI + qu.pauli('Y') & qu.pauli('Y') & qu.pauli('Y').conj() & qu.pauli('Y').conj(), # YY \rho YY + qu.pauli('Y') & qu.pauli('Z') & qu.pauli('Y').conj() & qu.pauli('Z'), # YZ \rho YZ + qu.pauli('Y') & qu.pauli('X') & qu.pauli('Y').conj() & qu.pauli('X'), # YX \rho YX + + qu.pauli('Z') & qu.pauli('I') & qu.pauli('Z') & qu.pauli('I'), # ZI \rho ZI + qu.pauli('Z') & qu.pauli('Y') & qu.pauli('Z') & qu.pauli('Y').conj(), # ZY \rho ZY + qu.pauli('Z') & qu.pauli('Z') & qu.pauli('Z') & qu.pauli('Z'), # ZZ \rho ZZ + qu.pauli('Z') & qu.pauli('X') & qu.pauli('Z') & qu.pauli('X')] # ZX \rho ZX + + Super_opt = l_term[0] * 0. + for count, i in enumerate(l_term): + Super_opt += i * e[count] + + return Super_opt + + + +NOISE_FUNCTIONS = { + # single qubit channel + 'One_Qubit_Depolarizing': QD_CHANN_OQ, + 'One_Qubit_X': X_CHANN_OQ, + 'One_Qubit_Y': Y_CHANN_OQ, + 'One_Qubit_Z': Z_CHANN_OQ, + + # two qubit channel + 'Two_Qubit_Depolarizing': QD_CHANN_TQ + +} # -------------------------- core gate functions ---------------------------- # def _merge_tags(tags, gate_opts): @@ -1578,9 +1654,8 @@ def to_qiskit(self): return qc - def apply_gate(self, gate_id, *gate_args, gate_round=None, - gate_shared=None, **gate_opts): + gate_tags=None, **gate_opts): """Apply a single gate to this tensor network quantum circuit. If ``gate_round`` is supplied the tensor(s) added will be tagged with ``'ROUND_{gate_round}'``. Alternatively, putting an integer first like @@ -1609,9 +1684,9 @@ def apply_gate(self, gate_id, *gate_args, gate_round=None, # unique tag tags = tags_to_oset(f'GATE_{len(self.gates)}') - if (gate_shared is not None): - gate_shared = tags_to_oset(gate_shared) - tags = tags | gate_shared + if (gate_tags is not None): + gate_tags = tags_to_oset(gate_tags) + tags = tags | gate_tags # parse which 'round' of gates if (gate_round is not None): @@ -1648,10 +1723,11 @@ def apply_gate(self, gate_id, *gate_args, gate_round=None, if gate_id in all_ONE_QUBIT_GATES: where = [int(gate_args[-1])] parameters_ = list(gate_args[:len(gate_args)-len(where)]) + SO = QD_CHANN_OQ(e=[1-0.5, 0.5/3., 0.5/3., 0.5/3]) if gate_id in all_TWO_QUBIT_GATES: where = [int(gate_args[-2]), int(gate_args[-1])] parameters_ = list(gate_args[:len(gate_args)-len(where)]) - + SO = QD_CHANN_TQ() # where = [i for i in gate_args if isinstance(i, numbers.Integral)] # parameters_ = list(gate_args[:len(gate_args)-len(where)]) @@ -1661,6 +1737,16 @@ def apply_gate(self, gate_id, *gate_args, gate_round=None, gate_fn(self._rho, * parameters_ + where_even, tags=tags, **opts) gate_fn_dagg(self._rho, * parameters_ + where_odd, tags=tags, **opts) + if gate_id in all_ONE_QUBIT_GATES: + print(where_even+where_odd, SO) + self._rho.gate_(SO, where_even+where_odd, tags=["OQ_NOISE"]) + + if gate_id in all_TWO_QUBIT_GATES: + print(where_even+where_odd) + self._rho.gate_(SO, where_even+where_odd, tags=["TQ_NOISE"]) + + + def apply_gates(self, gates): """Apply a sequence of gates to this tensor network quantum circuit. From 2c2bfde63adffbac0dcdc68a32277e6cfe0a7183 Mon Sep 17 00:00:00 2001 From: rezahhh Date: Wed, 21 Sep 2022 11:10:04 -0700 Subject: [PATCH 47/64] noisy-model added fully --- quimb/tensor/circuit.py | 64 +++++++++++++++-------------------------- 1 file changed, 23 insertions(+), 41 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index 571ff21f3..aa728ea44 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -173,35 +173,13 @@ def parse_qasm_url(url, **kwargs): # -------------------------- noisy channel ---------------------------- # - -def Y_CHANN_OQ(e=[1.-0.25, 0.25]): - II = qu.pauli('I') & qu.pauli('I') - YY = qu.pauli('Y') & qu.pauli('Y').conj() - Super_opt = e[0] * II + e[1] * YY - return Super_opt - - -def Z_CHANN_OQ(e=[1.-0.25, 0.25]): - II = qu.pauli('I') & qu.pauli('I') - ZZ = qu.pauli('Z') & qu.pauli('Z') - Super_opt = e[0] * II + e[1] * ZZ - return Super_opt - - -def X_CHANN_OQ(e=[1.-0.25, 0.25]): - II = qu.pauli('I') & qu.pauli('I') - XX = qu.pauli('X') & qu.pauli('X') - Super_opt = e[0] * II + e[1] * XX - return Super_opt - - def QD_CHANN_OQ(e=[1.-0.5, 0.5/3., 0.5/3, 0.5/3.]): II = qu.pauli('I') & qu.pauli('I') XX = qu.pauli('X') & qu.pauli('X') YY = qu.pauli('Y') & qu.pauli('Y').conj() ZZ = qu.pauli('Z') & qu.pauli('Z') Super_opt = e[0] * II + e[1]*XX + e[2] * ZZ + e[3]*YY - return Super_opt + return Super_opt.reshape(2, 2, 2, 2) def QD_CHANN_TQ(e=[1-0.5, 0.5/15, 0.5/15, 0.5/15, 0.5/15, 0.5/15, @@ -230,23 +208,29 @@ def QD_CHANN_TQ(e=[1-0.5, 0.5/15, 0.5/15, 0.5/15, 0.5/15, 0.5/15, Super_opt = l_term[0] * 0. for count, i in enumerate(l_term): - Super_opt += i * e[count] + Super_opt += i * e[count] - return Super_opt + return Super_opt.reshape(2, 2, 2, 2, 2, 2, 2, 2) -NOISE_FUNCTIONS = { - # single qubit channel - 'One_Qubit_Depolarizing': QD_CHANN_OQ, - 'One_Qubit_X': X_CHANN_OQ, - 'One_Qubit_Y': Y_CHANN_OQ, - 'One_Qubit_Z': Z_CHANN_OQ, +def apply_Depolarizing(psi, where, e=None, **gate_opts): + """Apply an Depolarizing Channel to tensor network wavefunction ``psi ~ rho``. + """ + if len(where) == 4: + G = QD_CHANN_TQ(e = e) + psi.gate_(G, where, tags="depol_two", parametrize=False, contract=False) + if len(where) == 2: + G = QD_CHANN_OQ(e = e) + psi.gate_(G, where, tags="depol_one", parametrize=False, contract=False) - # two qubit channel - 'Two_Qubit_Depolarizing': QD_CHANN_TQ + +NOISE_FUNCTIONS = { + # single qubit channel + 'depol': apply_Depolarizing, } + # -------------------------- core gate functions ---------------------------- # def _merge_tags(tags, gate_opts): @@ -1654,7 +1638,7 @@ def to_qiskit(self): return qc - def apply_gate(self, gate_id, *gate_args, gate_round=None, + def apply_gate(self, gate_id, *gate_args, noise_id=None, noise_params=None, gate_round=None, gate_tags=None, **gate_opts): """Apply a single gate to this tensor network quantum circuit. If ``gate_round`` is supplied the tensor(s) added will be tagged with @@ -1737,13 +1721,11 @@ def apply_gate(self, gate_id, *gate_args, gate_round=None, gate_fn(self._rho, * parameters_ + where_even, tags=tags, **opts) gate_fn_dagg(self._rho, * parameters_ + where_odd, tags=tags, **opts) - if gate_id in all_ONE_QUBIT_GATES: - print(where_even+where_odd, SO) - self._rho.gate_(SO, where_even+where_odd, tags=["OQ_NOISE"]) - - if gate_id in all_TWO_QUBIT_GATES: - print(where_even+where_odd) - self._rho.gate_(SO, where_even+where_odd, tags=["TQ_NOISE"]) + + if noise_id: + noise_fn = NOISE_FUNCTIONS[noise_id] + noise_fn(self._rho, where_even+where_odd, e = noise_params, **opts) + From ad3139ac0e3b0303c6ab50020d36143260b1e6f4 Mon Sep 17 00:00:00 2001 From: rezahhh Date: Wed, 21 Sep 2022 14:59:09 -0700 Subject: [PATCH 48/64] fixed tags --- quimb/tensor/circuit.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index 21bbc5718..88ff39e8a 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -216,15 +216,17 @@ def QD_CHANN_TQ(e=[1-0.5, 0.5/15, 0.5/15, 0.5/15, 0.5/15, 0.5/15, -def apply_Depolarizing(psi, where, e=None, **gate_opts): +def apply_Depolarizing(psi, where, e=None, tags={}, **gate_opts): """Apply an Depolarizing Channel to tensor network wavefunction ``psi ~ rho``. """ if len(where) == 4: G = QD_CHANN_TQ(e = e) - psi.gate_(G, where, tags="depol_two", parametrize=False, contract=False) + tags.add("depol_two") + psi.gate_(G, where, tags=tags, parametrize=False,contract=False) if len(where) == 2: G = QD_CHANN_OQ(e = e) - psi.gate_(G, where, tags="depol_one", parametrize=False, contract=False) + tags.add("depol_one") + psi.gate_(G, where, tags=tags, parametrize=False, contract=False) @@ -1726,7 +1728,7 @@ def apply_gate(self, gate_id, *gate_args, noise_id=None, noise_params=None, gate if noise_id: noise_fn = NOISE_FUNCTIONS[noise_id] - noise_fn(self._rho, where_even+where_odd, e = noise_params, **opts) + noise_fn(self._rho, where_even+where_odd, e = noise_params, tags=tags, **opts) @@ -1903,14 +1905,8 @@ def rho(self): rho = self._rho.copy() map_k = {f"k{2*i}": f"k{i}" for i in range(self.N)} map_b = {f"k{2*i+1}": f"b{i}" for i in range(self.N)} - print(map_b, map_k,) - # for i in rho: - # print(i) rho.reindex_(map_b) rho.reindex_(map_k) - # for i in rho: - # print("new", i) - rho.squeeze_() rho.astype_(rho.dtype) return rho From 422f984ae5d1687f1697c7a3157239127d5b24b2 Mon Sep 17 00:00:00 2001 From: rezahhh Date: Thu, 22 Sep 2022 07:47:08 -0700 Subject: [PATCH 49/64] fixed retag for \rho --- quimb/tensor/circuit.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index 88ff39e8a..6dd45b1a8 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -1907,6 +1907,14 @@ def rho(self): map_b = {f"k{2*i+1}": f"b{i}" for i in range(self.N)} rho.reindex_(map_b) rho.reindex_(map_k) + + map_k = {f"Qreg{2*i}": f"l_{i}" for i in range(self.N)} + rho.retag_(map_k) + map_k = {f"Qreg{2*i+1}": f"Qreg{i}" for i in range(self.N)} + rho.retag_(map_k) + map_k = {f"l_{i}": f"Qreg{i}" for i in range(self.N)} + rho.retag_(map_k) + rho.squeeze_() rho.astype_(rho.dtype) return rho From ef997f0bfc64263abc22adcc1184667e9acac93f Mon Sep 17 00:00:00 2001 From: rezahhh Date: Fri, 23 Sep 2022 13:07:25 -0700 Subject: [PATCH 50/64] added rho_ket, clean_up rho --- quimb/tensor/circuit.py | 108 +++++++++++++----------------------- quimb/tensor/tensor_core.py | 30 +++++++--- 2 files changed, 60 insertions(+), 78 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index 6dd45b1a8..006eb7d64 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -176,54 +176,48 @@ def parse_qasm_url(url, **kwargs): # -------------------------- noisy channel ---------------------------- # def QD_CHANN_OQ(e=[1.-0.5, 0.5/3., 0.5/3, 0.5/3.]): - II = qu.pauli('I') & qu.pauli('I') - XX = qu.pauli('X') & qu.pauli('X') - YY = qu.pauli('Y') & qu.pauli('Y').conj() - ZZ = qu.pauli('Z') & qu.pauli('Z') - Super_opt = e[0] * II + e[1]*XX + e[2] * ZZ + e[3]*YY - return Super_opt.reshape(2, 2, 2, 2) + I = qu.pauli('I') + X = qu.pauli('X') + Y = qu.pauli('Y') + Z = qu.pauli('Z') + pauli_l = [I, X, Y, Z] + Super_opt = np.zeros([2, 2, 4], dtype="complex128") + for i in range(4): + Super_opt[:, :, i] = pauli_l[i] * (e[i]**0.5) -def QD_CHANN_TQ(e=[1-0.5, 0.5/15, 0.5/15, 0.5/15, 0.5/15, 0.5/15, - 0.5/15, 0.5/15, 0.5/15, 0.5/15, 0.5/15, 0.5/15, 0.5/15, - 0.5/15, 0.5/15, 0.5/15]): - l_term = [ - qu.pauli('I') & qu.pauli('I') & qu.pauli('I') & qu.pauli('I'), # II \rho II - qu.pauli('I') & qu.pauli('x') & qu.pauli('I') & qu.pauli('X'), # IX \rho IX - qu.pauli('I') & qu.pauli('Z') & qu.pauli('I') & qu.pauli('Z'), # IZ \rho IZ - qu.pauli('I') & qu.pauli('Y') & qu.pauli('I') & qu.pauli('Y').conj(), # IY \rho IY - qu.pauli('X') & qu.pauli('I') & qu.pauli('X') & qu.pauli('I'), # XI \rho XI - qu.pauli('X') & qu.pauli('Y') & qu.pauli('X') & qu.pauli('Y').conj(), # XY \rho XY - qu.pauli('X') & qu.pauli('Z') & qu.pauli('X') & qu.pauli('Z'), # XZ \rho XZ - qu.pauli('X') & qu.pauli('X') & qu.pauli('X') & qu.pauli('X'), # XX \rho XX + return Super_opt - qu.pauli('Y') & qu.pauli('I') & qu.pauli('Y').conj() & qu.pauli('I'), # YI \rho YI - qu.pauli('Y') & qu.pauli('Y') & qu.pauli('Y').conj() & qu.pauli('Y').conj(), # YY \rho YY - qu.pauli('Y') & qu.pauli('Z') & qu.pauli('Y').conj() & qu.pauli('Z'), # YZ \rho YZ - qu.pauli('Y') & qu.pauli('X') & qu.pauli('Y').conj() & qu.pauli('X'), # YX \rho YX - qu.pauli('Z') & qu.pauli('I') & qu.pauli('Z') & qu.pauli('I'), # ZI \rho ZI - qu.pauli('Z') & qu.pauli('Y') & qu.pauli('Z') & qu.pauli('Y').conj(), # ZY \rho ZY - qu.pauli('Z') & qu.pauli('Z') & qu.pauli('Z') & qu.pauli('Z'), # ZZ \rho ZZ - qu.pauli('Z') & qu.pauli('X') & qu.pauli('Z') & qu.pauli('X')] # ZX \rho ZX - - Super_opt = l_term[0] * 0. - for count, i in enumerate(l_term): - Super_opt += i * e[count] +def QD_CHANN_TQ(e=[1-0.5, 0.5/15, 0.5/15, 0.5/15, 0.5/15, 0.5/15, + 0.5/15, 0.5/15, 0.5/15, 0.5/15, 0.5/15, 0.5/15, 0.5/15, + 0.5/15, 0.5/15, 0.5/15]): - return Super_opt.reshape(2, 2, 2, 2, 2, 2, 2, 2) + I = qu.pauli('I') + X = qu.pauli('X') + Y = qu.pauli('Y') + Z = qu.pauli('Z') + pauli_l = [I & I, I & Z, I & X, I & Y, + X & I, X & Z, X & X, X & Y, + Z & I, Z & Z, Z & X, Z & Y, + Y & I, Y & Z, Y & X, Y & Y, + ] + Super_opt = np.zeros([4, 4, 16], dtype="complex128") + for i in range(16): + Super_opt[:, :, i] = pauli_l[i] * (e[i]**0.5) + return Super_opt.reshape(2,2,2,2,16) def apply_Depolarizing(psi, where, e=None, tags={}, **gate_opts): """Apply an Depolarizing Channel to tensor network wavefunction ``psi ~ rho``. """ - if len(where) == 4: + if len(where) == 2: G = QD_CHANN_TQ(e = e) tags.add("depol_two") psi.gate_(G, where, tags=tags, parametrize=False,contract=False) - if len(where) == 2: + if len(where) == 1: G = QD_CHANN_OQ(e = e) tags.add("depol_one") psi.gate_(G, where, tags=tags, parametrize=False, contract=False) @@ -1108,15 +1102,17 @@ def __init__( ): if N is None and psi0 is None: raise ValueError("You must supply one of `N` or `psi0`.") + if N is None and rho0 is None: + raise ValueError("You must supply one of `N` or `rho0`.") elif psi0 is None: - print("Hi") self.N = N self._psi = MPS_computational_state('0' * N, dtype=psi0_dtype) - self._rho = MPS_computational_state('0' * int(2 * N), dtype=psi0_dtype) + self._rho = MPS_computational_state('0' * N, dtype=psi0_dtype) elif N is None: self._psi = psi0.copy() + self._rho = rho0.copy() self.N = psi0.L else: @@ -1124,12 +1120,7 @@ def __init__( raise ValueError("`N` doesn't match `psi0`.") self.N = N self._psi = psi0.copy() - - - if rho0 is None: - self._rho = MPS_computational_state('0' * int(2 * N), dtype=psi0_dtype) - else: - self._rho = MPS_computational_state('0' * int(2 * N), dtype=psi0_dtype) + self._rho = rho0.copy() self.q_qiskit = [] @@ -1672,6 +1663,8 @@ def apply_gate(self, gate_id, *gate_args, noise_id=None, noise_params=None, gate # unique tag tags = tags_to_oset(f'GATE_{len(self.gates)}') + tags_noise = tags_to_oset(f'GATE_{len(self.gates)}') + if (gate_tags is not None): gate_tags = tags_to_oset(gate_tags) tags = tags | gate_tags @@ -1679,14 +1672,15 @@ def apply_gate(self, gate_id, *gate_args, noise_id=None, noise_params=None, gate # parse which 'round' of gates if (gate_round is not None): tags.add(f'ROUND_{gate_round}') + tags_noise.add(f'ROUND_{gate_round}') elif isinstance(gate_id, numbers.Integral) or gate_id.isdigit(): # gate round given as first entry of qasm line tags.add(f'ROUND_{gate_id}') + tags_noise.add(f'ROUND_{gate_id}') gate_id, gate_args = gate_args[0], gate_args[1:] gate_id = gate_id.upper() gate_fn = GATE_FUNCTIONS[gate_id] - gate_fn_dagg = GATE_FUNCTIONS[gate_id+"_conj"] # overide any default gate opts opts = {**self.gate_opts, **gate_opts} @@ -1701,35 +1695,23 @@ def apply_gate(self, gate_id, *gate_args, noise_id=None, noise_params=None, gate # gate the TN! gate_fn(self._psi, *gate_args, tags=tags, **opts) + gate_fn(self._rho, *gate_args, tags=tags, **opts) # keep track of the gates applied self.gates.append((gate_id, *gate_args)) + # add noisy channel to ket of rho! all_ONE_QUBIT_GATES = ONE_QUBIT_PARAM_GATES | ONE_QUBIT_GATES all_TWO_QUBIT_GATES = TWO_QUBIT_PARAM_GATES | TWO_QUBIT_GATES if gate_id in all_ONE_QUBIT_GATES: where = [int(gate_args[-1])] - parameters_ = list(gate_args[:len(gate_args)-len(where)]) - SO = QD_CHANN_OQ(e=[1-0.5, 0.5/3., 0.5/3., 0.5/3]) if gate_id in all_TWO_QUBIT_GATES: where = [int(gate_args[-2]), int(gate_args[-1])] - parameters_ = list(gate_args[:len(gate_args)-len(where)]) - SO = QD_CHANN_TQ() - # where = [i for i in gate_args if isinstance(i, numbers.Integral)] - # parameters_ = list(gate_args[:len(gate_args)-len(where)]) - - where_even = [2*i for i in where] - where_odd = [2*i+1 for i in where] - - gate_fn(self._rho, * parameters_ + where_even, tags=tags, **opts) - gate_fn_dagg(self._rho, * parameters_ + where_odd, tags=tags, **opts) - if noise_id: noise_fn = NOISE_FUNCTIONS[noise_id] - noise_fn(self._rho, where_even+where_odd, e = noise_params, tags=tags, **opts) - + noise_fn(self._rho, where, e = noise_params, tags=tags_noise, **opts) @@ -1903,18 +1885,6 @@ def rho(self): """ # make sure all same dtype and drop singlet dimensions rho = self._rho.copy() - map_k = {f"k{2*i}": f"k{i}" for i in range(self.N)} - map_b = {f"k{2*i+1}": f"b{i}" for i in range(self.N)} - rho.reindex_(map_b) - rho.reindex_(map_k) - - map_k = {f"Qreg{2*i}": f"l_{i}" for i in range(self.N)} - rho.retag_(map_k) - map_k = {f"Qreg{2*i+1}": f"Qreg{i}" for i in range(self.N)} - rho.retag_(map_k) - map_k = {f"l_{i}": f"Qreg{i}" for i in range(self.N)} - rho.retag_(map_k) - rho.squeeze_() rho.astype_(rho.dtype) return rho diff --git a/quimb/tensor/tensor_core.py b/quimb/tensor/tensor_core.py index f1cc18bce..c1d0e2a4a 100644 --- a/quimb/tensor/tensor_core.py +++ b/quimb/tensor/tensor_core.py @@ -2737,14 +2737,20 @@ def _tensor_network_gate_inds_basic( # new indices to join old physical sites to new gate bnds = [rand_uuid() for _ in range(ng)] - reindex_map = dict(zip(inds, bnds)) + reindex_map = dict(zip(inds, bnds)) + inds_G = (*inds, *bnds) # tensor representing the gate if isparam: TG = PTensor.from_parray( G, inds=(*inds, *bnds), tags=tags, left_inds=bnds) else: - TG = Tensor(G, inds=(*inds, *bnds), tags=tags, left_inds=bnds) + if len(inds_G) == len(G.shape): + TG = Tensor(G, inds=(*inds, *bnds), tags=tags, left_inds=bnds) + else: + inds_extra = abs(len(inds_G) - len(G.shape)) + bnds_extra = [rand_uuid() for _ in range(inds_extra)] + TG = Tensor(G, inds=(*inds, *bnds, *bnds_extra), tags=tags, left_inds=bnds) if contract is False: # @@ -3053,19 +3059,25 @@ def tensor_network_gate_inds( """ check_opt('contract', contract, _VALID_GATE_CONTRACT) + tn = self if inplace else self.copy() ng = len(inds) ndimG = ndim(G) dims = [tn.ind_size(ix) for ix in inds] - if ndimG != 2 * ng: - # gate supplied as matrix, factorize it - G = reshape(G, dims * 2) - - if not all(d == dims[i % ng] for i, d in enumerate(G.shape)): - raise ValueError(f"Gate with shape {G.shape} doesn't match " - f"indices {inds} with dimensions {dims}.") + try: + if ndimG != 2 * ng: + # gate supplied as matrix, factorize it + G = reshape(G, dims * 2) + except: + pass +# dims_rest = list(G.shape)[len(2*dims):] +# G = reshape(G, dims * 2 + dims_rest) + + # if not all(d == dims[i % ng] for i, d in enumerate(G.shape)): + # raise ValueError(f"Gate with shape {G.shape} doesn't match " + # f"indices {inds} with dimensions {dims}.") basic = (contract in _BASIC_GATE_CONTRACT) if (not basic) and (ng == 1): From 84887a4f83af925135ca1b52e78f27e4f91cfb88 Mon Sep 17 00:00:00 2001 From: Reza Haghshenas Date: Fri, 23 Sep 2022 15:23:47 -0700 Subject: [PATCH 51/64] fixed G, rho --- quimb/tensor/tensor_core.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/quimb/tensor/tensor_core.py b/quimb/tensor/tensor_core.py index c1d0e2a4a..ad258016c 100644 --- a/quimb/tensor/tensor_core.py +++ b/quimb/tensor/tensor_core.py @@ -2739,18 +2739,25 @@ def _tensor_network_gate_inds_basic( bnds = [rand_uuid() for _ in range(ng)] reindex_map = dict(zip(inds, bnds)) inds_G = (*inds, *bnds) + ndimG = ndim(G) # tensor representing the gate if isparam: - TG = PTensor.from_parray( - G, inds=(*inds, *bnds), tags=tags, left_inds=bnds) + if len(inds_G) == ndimG: + TG = PTensor.from_parray( + G, inds=(*inds, *bnds), tags=tags, left_inds=bnds) + else: + inds_extra = abs(len(inds_G) - ndimG) + bnds_extra = [rand_uuid() for _ in range(inds_extra)] + TG = PTensor.from_parray( + G, inds=(*inds, *bnds, *bnds_extra), tags=tags, left_inds=bnds+bnds_extra) else: - if len(inds_G) == len(G.shape): + if len(inds_G) == ndimG: TG = Tensor(G, inds=(*inds, *bnds), tags=tags, left_inds=bnds) else: - inds_extra = abs(len(inds_G) - len(G.shape)) + inds_extra = abs(len(inds_G) - ndimG) bnds_extra = [rand_uuid() for _ in range(inds_extra)] - TG = Tensor(G, inds=(*inds, *bnds, *bnds_extra), tags=tags, left_inds=bnds) + TG = Tensor(G, inds=(*inds, *bnds, *bnds_extra), tags=tags, left_inds=bnds+bnds_extra) if contract is False: # @@ -3059,7 +3066,6 @@ def tensor_network_gate_inds( """ check_opt('contract', contract, _VALID_GATE_CONTRACT) - tn = self if inplace else self.copy() ng = len(inds) @@ -3075,9 +3081,11 @@ def tensor_network_gate_inds( # dims_rest = list(G.shape)[len(2*dims):] # G = reshape(G, dims * 2 + dims_rest) - # if not all(d == dims[i % ng] for i, d in enumerate(G.shape)): - # raise ValueError(f"Gate with shape {G.shape} doesn't match " - # f"indices {inds} with dimensions {dims}.") + Gshape_ = list(G.shape)[:2 * ng] + + if not all(d == dims[i % ng] for i, d in enumerate(Gshape_)): + raise ValueError(f"Gate with shape {G.shape} doesn't match " + f"indices {inds} with dimensions {dims}.") basic = (contract in _BASIC_GATE_CONTRACT) if (not basic) and (ng == 1): From 53bf4752e515cffe26809b1281d115e63e0d9d78 Mon Sep 17 00:00:00 2001 From: rezahhh Date: Wed, 28 Sep 2022 15:18:42 -0600 Subject: [PATCH 52/64] added RXX gate --- quimb/tensor/circuit.py | 54 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index 006eb7d64..c425336d6 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -770,9 +770,6 @@ def apply_fsimg_conj( - - - def rzz_param_gen(params): gamma = params[0] @@ -787,6 +784,36 @@ def rzz_param_gen(params): return ops.asarray(data) +def rxx_param_gen(params): + gamma = params[0] + + c00 = do('cos', gamma / 2.) + c11 = do('sin', gamma / 2.) + + img_re = do('real', -1.j) + img_im = do('imag', -1.j) + img = do('complex', img_re, img_im) + + data = [[[[c00, 0], [0, img * c11]], + [[0, c00], [img *c11, 0]]], + [[[0, img *c11], [c00, 0]], + [[img *c11, 0], [0, c00]]]] + + return ops.asarray(data) + +@functools.lru_cache(maxsize=128) +def rxx(gamma): + r""" + The gate describing an Ising interaction evolution, or 'ZZ'-rotation. + + .. math:: + + \mathrm{RXX}(\gamma) = \exp(-i (\gamma / 2.) X_i X_j) + + """ + return rxx_param_gen(np.array([gamma])) + + @functools.lru_cache(maxsize=128) def rzz(gamma): r""" @@ -800,6 +827,16 @@ def rzz(gamma): return rzz_param_gen(np.array([gamma])) +def apply_rxx(psi, gamma, i, j, parametrize=False, **gate_opts): + mtags = _merge_tags('RXX', gate_opts) + if parametrize: + G = ops.PArray(rxx_param_gen, (gamma,)) + else: + G = rxx(float(gamma)) + psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) + + + def apply_rzz(psi, gamma, i, j, parametrize=False, **gate_opts): mtags = _merge_tags('RZZ', gate_opts) if parametrize: @@ -974,6 +1011,7 @@ def apply_su4_conj( 'FSIMG': apply_fsimg, 'FSIMG_conj': apply_fsimg_conj, 'RZZ': apply_rzz, + 'RXX': apply_rxx, 'RZZ_conj': apply_rzz_conj, 'SU4': apply_su4, 'SU4_conj': apply_su4_conj, @@ -981,7 +1019,7 @@ def apply_su4_conj( ONE_QUBIT_PARAM_GATES = {'RX', 'RY', 'RZ', 'U3', 'U2', 'U1'} TWO_QUBIT_PARAM_GATES = { - 'CU3', 'CU2', 'CU1', 'FS', 'FSIM', 'FSIMT', 'FSIMG', 'RZZ', 'SU4', + 'CU3', 'CU2', 'CU1', 'FS', 'FSIM', 'FSIMT', 'FSIMG', 'RZZ', 'SU4', 'RXX', } TWO_QUBIT_GATES = {'CNOT', 'CX', 'CY', 'CZ', 'IS', 'ISWAP'} ONE_QUBIT_GATES = {'H', 'X', 'Y', 'Z', 'S', 'T', 'X_1_2', 'Y_1_2', 'Z_1_2', 'W_1_2', 'HZ_1_2'} @@ -1502,6 +1540,10 @@ def to_qiskit_gates(self, psi=None, optimal=False, q_measure=[], t0, t1 = dic_r[i] p0, = dic_p[i] qc.rzz(p0, q_l[t0], q_l[t1]) + if dic_id[i] == "RXX": + t0, t1 = dic_r[i] + p0, = dic_p[i] + qc.rzz(p0, q_l[t0], q_l[t1]) if dic_id[i] == "RY": t0, = dic_r[i] p0, = dic_p[i] @@ -1847,6 +1889,10 @@ def rzz(self, theta, i, j, gate_round=None, parametrize=False): self.apply_gate('RZZ', theta, i, j, gate_round=gate_round, parametrize=parametrize) + def rxx(self, theta, i, j, gate_round=None, parametrize=False): + self.apply_gate('RXX', theta, i, j, + gate_round=gate_round, parametrize=parametrize) + def su4( self, theta1, phi1, lamda1, From ae4523cca557ecba33c44108a53ac91c9298edb2 Mon Sep 17 00:00:00 2001 From: rezahhh Date: Wed, 28 Sep 2022 21:00:42 -0600 Subject: [PATCH 53/64] added RYY gate --- quimb/tensor/circuit.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index c425336d6..bc7a8b248 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -814,6 +814,35 @@ def rxx(gamma): return rxx_param_gen(np.array([gamma])) +def ryy_param_gen(params): + gamma = params[0] + + c00 = do('cos', gamma / 2.) + c11 = do('sin', gamma / 2.) + + img_re = do('real', -1.j) + img_im = do('imag', -1.j) + img = do('complex', img_re, img_im) + + data = [[[[c00, 0], [0, img * c11]], + [[0, c00], [-img *c11, 0]]], + [[[0, -img *c11], [c00, 0]], + [[img *c11, 0], [0, c00]]]] + + return ops.asarray(data) + + +def ryy(gamma): + r""" + The gate describing an Ising interaction evolution, or 'ZZ'-rotation. + + .. math:: + + \mathrm{Ryy}(\gamma) = \exp(-i (\gamma / 2.) Y_i Y_j) + + """ + return ryy_param_gen(np.array([gamma])) + @functools.lru_cache(maxsize=128) def rzz(gamma): r""" @@ -835,6 +864,13 @@ def apply_rxx(psi, gamma, i, j, parametrize=False, **gate_opts): G = rxx(float(gamma)) psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) +def apply_ryy(psi, gamma, i, j, parametrize=False, **gate_opts): + mtags = _merge_tags('RYY', gate_opts) + if parametrize: + G = ops.PArray(ryy_param_gen, (gamma,)) + else: + G = ryy(float(gamma)) + psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) def apply_rzz(psi, gamma, i, j, parametrize=False, **gate_opts): @@ -1893,6 +1929,10 @@ def rxx(self, theta, i, j, gate_round=None, parametrize=False): self.apply_gate('RXX', theta, i, j, gate_round=gate_round, parametrize=parametrize) + def ryy(self, theta, i, j, gate_round=None, parametrize=False): + self.apply_gate('RYY', theta, i, j, + gate_round=gate_round, parametrize=parametrize) + def su4( self, theta1, phi1, lamda1, From c6449e637a1feb0142edaa102d9d18026b92e35b Mon Sep 17 00:00:00 2001 From: rezahhh Date: Wed, 28 Sep 2022 21:02:14 -0600 Subject: [PATCH 54/64] fixed RYY gate --- quimb/tensor/circuit.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index bc7a8b248..356ad60b9 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -1048,6 +1048,7 @@ def apply_su4_conj( 'FSIMG_conj': apply_fsimg_conj, 'RZZ': apply_rzz, 'RXX': apply_rxx, + 'RYY': apply_ryy, 'RZZ_conj': apply_rzz_conj, 'SU4': apply_su4, 'SU4_conj': apply_su4_conj, @@ -1055,7 +1056,7 @@ def apply_su4_conj( ONE_QUBIT_PARAM_GATES = {'RX', 'RY', 'RZ', 'U3', 'U2', 'U1'} TWO_QUBIT_PARAM_GATES = { - 'CU3', 'CU2', 'CU1', 'FS', 'FSIM', 'FSIMT', 'FSIMG', 'RZZ', 'SU4', 'RXX', + 'CU3', 'CU2', 'CU1', 'FS', 'FSIM', 'FSIMT', 'FSIMG', 'RZZ', 'SU4', 'RXX', 'RYY' } TWO_QUBIT_GATES = {'CNOT', 'CX', 'CY', 'CZ', 'IS', 'ISWAP'} ONE_QUBIT_GATES = {'H', 'X', 'Y', 'Z', 'S', 'T', 'X_1_2', 'Y_1_2', 'Z_1_2', 'W_1_2', 'HZ_1_2'} From 4654bb73320709d7dbb247474aafdd553d0f0499 Mon Sep 17 00:00:00 2001 From: rezahhh Date: Wed, 28 Sep 2022 21:07:11 -0600 Subject: [PATCH 55/64] fixed RYY gate --- quimb/tensor/circuit.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index 356ad60b9..383a0f506 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -813,15 +813,15 @@ def rxx(gamma): """ return rxx_param_gen(np.array([gamma])) - +@functools.lru_cache(maxsize=128) def ryy_param_gen(params): gamma = params[0] c00 = do('cos', gamma / 2.) c11 = do('sin', gamma / 2.) - img_re = do('real', -1.j) - img_im = do('imag', -1.j) + img_re = do('real', 1.j) + img_im = do('imag', 1.j) img = do('complex', img_re, img_im) data = [[[[c00, 0], [0, img * c11]], @@ -864,6 +864,7 @@ def apply_rxx(psi, gamma, i, j, parametrize=False, **gate_opts): G = rxx(float(gamma)) psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) + def apply_ryy(psi, gamma, i, j, parametrize=False, **gate_opts): mtags = _merge_tags('RYY', gate_opts) if parametrize: @@ -1581,6 +1582,11 @@ def to_qiskit_gates(self, psi=None, optimal=False, q_measure=[], t0, t1 = dic_r[i] p0, = dic_p[i] qc.rzz(p0, q_l[t0], q_l[t1]) + if dic_id[i] == "RYY": + t0, t1 = dic_r[i] + p0, = dic_p[i] + qc.rzz(p0, q_l[t0], q_l[t1]) + if dic_id[i] == "RY": t0, = dic_r[i] p0, = dic_p[i] From cd4630269132adf09763d683a475932057a51f50 Mon Sep 17 00:00:00 2001 From: rezahhh Date: Wed, 28 Sep 2022 21:10:31 -0600 Subject: [PATCH 56/64] fixed RYY gate --- quimb/tensor/circuit.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index 383a0f506..b0a4fc278 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -824,10 +824,15 @@ def ryy_param_gen(params): img_im = do('imag', 1.j) img = do('complex', img_re, img_im) + img_re_ = do('real', -1.j) + img_im_ = do('imag', -1.j) + img_ = do('complex', img_re_, img_im_) + + data = [[[[c00, 0], [0, img * c11]], - [[0, c00], [-img *c11, 0]]], - [[[0, -img *c11], [c00, 0]], - [[img *c11, 0], [0, c00]]]] + [[0, c00], [img_ * c11, 0]]], + [[[0, img_ * c11], [c00, 0]], + [[img * c11, 0], [0, c00]]]] return ops.asarray(data) From 7c7f24a62b355641a1b579e18bc8ecb0c2020998 Mon Sep 17 00:00:00 2001 From: rezahhh Date: Wed, 28 Sep 2022 21:12:40 -0600 Subject: [PATCH 57/64] fixed RYY gate --- quimb/tensor/circuit.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index b0a4fc278..df5a8912c 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -828,7 +828,6 @@ def ryy_param_gen(params): img_im_ = do('imag', -1.j) img_ = do('complex', img_re_, img_im_) - data = [[[[c00, 0], [0, img * c11]], [[0, c00], [img_ * c11, 0]]], [[[0, img_ * c11], [c00, 0]], @@ -848,6 +847,8 @@ def ryy(gamma): """ return ryy_param_gen(np.array([gamma])) +print("ryy", ryy(pi)) + @functools.lru_cache(maxsize=128) def rzz(gamma): r""" From ba7c75f3e78fc04a12d76c3346928e6f0260464c Mon Sep 17 00:00:00 2001 From: rezahhh Date: Wed, 28 Sep 2022 21:14:33 -0600 Subject: [PATCH 58/64] fixed RYY gate --- quimb/tensor/circuit.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index df5a8912c..a532ccc77 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -829,10 +829,11 @@ def ryy_param_gen(params): img_ = do('complex', img_re_, img_im_) data = [[[[c00, 0], [0, img * c11]], - [[0, c00], [img_ * c11, 0]]], - [[[0, img_ * c11], [c00, 0]], - [[img * c11, 0], [0, c00]]]] + [[0, c00], [img_ *c11, 0]]], + [[[0, img_ *c11], [c00, 0]], + [[img *c11, 0], [0, c00]]]] + print(data) return ops.asarray(data) From 7563903863cb43645f224de46b6ffa3889cea2be Mon Sep 17 00:00:00 2001 From: rezahhh Date: Wed, 28 Sep 2022 21:54:20 -0600 Subject: [PATCH 59/64] fixed RYY gate --- quimb/tensor/circuit.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index a532ccc77..ccdcf8f8f 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -813,42 +813,40 @@ def rxx(gamma): """ return rxx_param_gen(np.array([gamma])) -@functools.lru_cache(maxsize=128) def ryy_param_gen(params): gamma = params[0] c00 = do('cos', gamma / 2.) c11 = do('sin', gamma / 2.) - img_re = do('real', 1.j) - img_im = do('imag', 1.j) + img_re = do('real', -1.j) + img_im = do('imag', -1.j) img = do('complex', img_re, img_im) - img_re_ = do('real', -1.j) - img_im_ = do('imag', -1.j) + img_re_ = do('real', 1.j) + img_im_ = do('imag', 1.j) img_ = do('complex', img_re_, img_im_) - data = [[[[c00, 0], [0, img * c11]], - [[0, c00], [img_ *c11, 0]]], - [[[0, img_ *c11], [c00, 0]], - [[img *c11, 0], [0, c00]]]] + data = [[[[c00, 0], [0, img_ * c11]], + [[0, c00], [img * c11, 0]]], + [[[0, img * c11], [c00, 0]], + [[img_ * c11, 0], [0, c00]]]] - print(data) return ops.asarray(data) +@functools.lru_cache(maxsize=128) def ryy(gamma): r""" The gate describing an Ising interaction evolution, or 'ZZ'-rotation. .. math:: - \mathrm{Ryy}(\gamma) = \exp(-i (\gamma / 2.) Y_i Y_j) + \mathrm{RXX}(\gamma) = \exp(-i (\gamma / 2.) X_i X_j) """ return ryy_param_gen(np.array([gamma])) -print("ryy", ryy(pi)) @functools.lru_cache(maxsize=128) def rzz(gamma): From 0272fd59e708d4819c637b7d29b8d9c74abd4abd Mon Sep 17 00:00:00 2001 From: rezahhh Date: Fri, 30 Sep 2022 15:16:53 -0600 Subject: [PATCH 60/64] fixed gate_tags --- quimb/tensor/circuit.py | 98 +++++++++++++++++++---------------------- 1 file changed, 46 insertions(+), 52 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index ccdcf8f8f..5523b150f 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -194,16 +194,17 @@ def QD_CHANN_TQ(e=[1-0.5, 0.5/15, 0.5/15, 0.5/15, 0.5/15, 0.5/15, 0.5/15, 0.5/15, 0.5/15, 0.5/15, 0.5/15, 0.5/15, 0.5/15, 0.5/15, 0.5/15, 0.5/15]): - I = qu.pauli('I') - X = qu.pauli('X') - Y = qu.pauli('Y') + I = qu.pauli('I') + X = qu.pauli('X') + Y = qu.pauli('Y') Z = qu.pauli('Z') pauli_l = [I & I, I & Z, I & X, I & Y, - X & I, X & Z, X & X, X & Y, Z & I, Z & Z, Z & X, Z & Y, + X & I, X & Z, X & X, X & Y, Y & I, Y & Z, Y & X, Y & Y, - ] + ] + Super_opt = np.zeros([4, 4, 16], dtype="complex128") for i in range(16): Super_opt[:, :, i] = pauli_l[i] * (e[i]**0.5) @@ -769,12 +770,11 @@ def apply_fsimg_conj( psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) - def rzz_param_gen(params): gamma = params[0] - c00 = c11 = do('complex', do('cos', gamma / 2), -do('sin', gamma / 2)) - c01 = c10 = do('complex', do('cos', gamma / 2), do('sin', gamma / 2)) + c00 = c11 = do('complex', do('cos', gamma / 2.), -do('sin', gamma / 2.)) + c01 = c10 = do('complex', do('cos', gamma / 2.), do('sin', gamma / 2.)) data = [[[[c00, 0], [0, 0]], [[0, c01], [0, 0]]], @@ -784,6 +784,29 @@ def rzz_param_gen(params): return ops.asarray(data) +@functools.lru_cache(maxsize=128) +def rzz(gamma): + r""" + The gate describing an Ising interaction evolution, or 'ZZ'-rotation. + + .. math:: + + \mathrm{RZZ}(\gamma) = \exp(-i (\gamma / 2.) Z_i Z_j) + + """ + return rzz_param_gen(np.array([gamma])) + +def apply_rzz(psi, gamma, i, j, parametrize=False, **gate_opts): + mtags = _merge_tags('RZZ', gate_opts) + if parametrize: + G = ops.PArray(rzz_param_gen, (gamma,)) + else: + G = rzz(float(gamma)) + psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) + + + + def rxx_param_gen(params): gamma = params[0] @@ -813,6 +836,18 @@ def rxx(gamma): """ return rxx_param_gen(np.array([gamma])) +def apply_rxx(psi, gamma, i, j, parametrize=False, **gate_opts): + mtags = _merge_tags('RXX', gate_opts) + if parametrize: + G = ops.PArray(rxx_param_gen, (gamma,)) + else: + G = rxx(float(gamma)) + psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) + + + + + def ryy_param_gen(params): gamma = params[0] @@ -848,28 +883,6 @@ def ryy(gamma): return ryy_param_gen(np.array([gamma])) -@functools.lru_cache(maxsize=128) -def rzz(gamma): - r""" - The gate describing an Ising interaction evolution, or 'ZZ'-rotation. - - .. math:: - - \mathrm{RZZ}(\gamma) = \exp(-i (\gamma / 2.) Z_i Z_j) - - """ - return rzz_param_gen(np.array([gamma])) - - -def apply_rxx(psi, gamma, i, j, parametrize=False, **gate_opts): - mtags = _merge_tags('RXX', gate_opts) - if parametrize: - G = ops.PArray(rxx_param_gen, (gamma,)) - else: - G = rxx(float(gamma)) - psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) - - def apply_ryy(psi, gamma, i, j, parametrize=False, **gate_opts): mtags = _merge_tags('RYY', gate_opts) if parametrize: @@ -879,25 +892,6 @@ def apply_ryy(psi, gamma, i, j, parametrize=False, **gate_opts): psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) -def apply_rzz(psi, gamma, i, j, parametrize=False, **gate_opts): - mtags = _merge_tags('RZZ', gate_opts) - if parametrize: - G = ops.PArray(rzz_param_gen, (gamma,)) - else: - G = rzz(float(gamma)) - psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) - - -def apply_rzz_conj(psi, gamma, i, j, parametrize=False, **gate_opts): - mtags = _merge_tags('RZZ', gate_opts) - if parametrize: - G = ops.PArray(rzz_param_gen, (gamma,)) - G.add_function(conj) - else: - G = rzz(float(gamma)).conj() - psi.gate_(G, (int(i), int(j)), tags=mtags, **gate_opts) - - def su4_gate_param_gen(params): """See https://arxiv.org/abs/quant-ph/0308006 - Fig. 7. """ @@ -1055,7 +1049,6 @@ def apply_su4_conj( 'RZZ': apply_rzz, 'RXX': apply_rxx, 'RYY': apply_ryy, - 'RZZ_conj': apply_rzz_conj, 'SU4': apply_su4, 'SU4_conj': apply_su4_conj, } @@ -1762,11 +1755,11 @@ def apply_gate(self, gate_id, *gate_args, noise_id=None, noise_params=None, gate # parse which 'round' of gates if (gate_round is not None): tags.add(f'ROUND_{gate_round}') - tags_noise.add(f'ROUND_{gate_round}') + #tags_noise.add(f'ROUND_{gate_round}') elif isinstance(gate_id, numbers.Integral) or gate_id.isdigit(): # gate round given as first entry of qasm line tags.add(f'ROUND_{gate_id}') - tags_noise.add(f'ROUND_{gate_id}') + #tags_noise.add(f'ROUND_{gate_id}') gate_id, gate_args = gate_args[0], gate_args[1:] gate_id = gate_id.upper() @@ -1800,6 +1793,7 @@ def apply_gate(self, gate_id, *gate_args, noise_id=None, noise_params=None, gate where = [int(gate_args[-2]), int(gate_args[-1])] if noise_id: + print("noise_id", noise_id) noise_fn = NOISE_FUNCTIONS[noise_id] noise_fn(self._rho, where, e = noise_params, tags=tags_noise, **opts) From 0213f409d23ab99404bf78fe62b53dbdc525aca8 Mon Sep 17 00:00:00 2001 From: rezahhh Date: Fri, 30 Sep 2022 15:20:59 -0600 Subject: [PATCH 61/64] fixed gate_tags --- quimb/tensor/circuit.py | 1 - 1 file changed, 1 deletion(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index 5523b150f..e2ecf8578 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -1793,7 +1793,6 @@ def apply_gate(self, gate_id, *gate_args, noise_id=None, noise_params=None, gate where = [int(gate_args[-2]), int(gate_args[-1])] if noise_id: - print("noise_id", noise_id) noise_fn = NOISE_FUNCTIONS[noise_id] noise_fn(self._rho, where, e = noise_params, tags=tags_noise, **opts) From aca6d360ce431297013b4f619572c7c0956b3f30 Mon Sep 17 00:00:00 2001 From: rezahhh Date: Thu, 10 Nov 2022 14:25:07 -0800 Subject: [PATCH 62/64] added super_operator U --- quimb/tensor/circuit.py | 113 +++++++++++++++++++++++++++++----------- 1 file changed, 84 insertions(+), 29 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index e2ecf8578..a4db74017 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -981,76 +981,76 @@ def apply_su4_conj( GATE_FUNCTIONS = { # constant single qubit gates 'H': build_gate_1(qu.hadamard(), tags='H'), - 'H_conj': build_gate_1(qu.hadamard().conj(), tags='H'), + 'H_CONJ': build_gate_1(qu.hadamard().conj(), tags='H'), 'X': build_gate_1(qu.pauli('X'), tags='X'), - 'X_conj': build_gate_1(qu.pauli('X').conj(), tags='X'), + 'X_CONJ': build_gate_1(qu.pauli('X').conj(), tags='X'), 'Y': build_gate_1(qu.pauli('Y'), tags='Y'), - 'Y_conj': build_gate_1(qu.pauli('Y').conj(), tags='Y'), + 'Y_CONJ': build_gate_1(qu.pauli('Y').conj(), tags='Y'), 'Z': build_gate_1(qu.pauli('Z'), tags='Z'), - 'Z_conj': build_gate_1(qu.pauli('Z').conj(), tags='Z'), + 'Z_CONJ': build_gate_1(qu.pauli('Z').conj(), tags='Z'), 'S': build_gate_1(qu.S_gate(), tags='S'), - 'S_conj': build_gate_1(qu.S_gate().conj(), tags='S'), + 'S_CONJ': build_gate_1(qu.S_gate().conj(), tags='S'), 'T': build_gate_1(qu.T_gate(), tags='T'), - 'T_conj': build_gate_1(qu.T_gate().conj(), tags='T'), + 'T_CONJ': build_gate_1(qu.T_gate().conj(), tags='T'), 'X_1_2': build_gate_1(qu.Xsqrt(), tags='X_1/2'), - 'X_1_2_conj': build_gate_1(qu.Xsqrt().conj(), tags='X_1/2'), + 'X_1_2_CONJ': build_gate_1(qu.Xsqrt().conj(), tags='X_1/2'), 'Y_1_2': build_gate_1(qu.Ysqrt(), tags='Y_1/2'), - 'Y_1_2_conj': build_gate_1(qu.Ysqrt().conj(), tags='Y_1/2'), + 'Y_1_2_CONJ': build_gate_1(qu.Ysqrt().conj(), tags='Y_1/2'), 'Z_1_2': build_gate_1(qu.Zsqrt(), tags='Z_1/2'), - 'Z_1_2_conj': build_gate_1(qu.Zsqrt().conj(), tags='Z_1/2'), + 'Z_1_2_CONJ': build_gate_1(qu.Zsqrt().conj(), tags='Z_1/2'), 'W_1_2': build_gate_1(qu.Wsqrt(), tags='W_1/2'), - 'W_1_2_conj': build_gate_1(qu.Wsqrt().conj(), tags='W_1/2'), + 'W_1_2_CONJ': build_gate_1(qu.Wsqrt().conj(), tags='W_1/2'), 'HZ_1_2': build_gate_1(qu.Wsqrt(), tags='W_1/2'), - 'HZ_1_2_conj': build_gate_1(qu.Wsqrt().conj(), tags='W_1/2'), + 'HZ_1_2_CONJ': build_gate_1(qu.Wsqrt().conj(), tags='W_1/2'), # constant two qubit gates 'CNOT': build_gate_2(qu.CNOT(), tags='CNOT'), - 'CNOT_conj': build_gate_2(qu.CNOT().conj(), tags='CNOT'), + 'CNOT_CONJ': build_gate_2(qu.CNOT().conj(), tags='CNOT'), 'CX': build_gate_2(qu.cX(), tags='CX'), - 'CX_conj': build_gate_2(qu.cX().conj(), tags='CX'), + 'CX_CONJ': build_gate_2(qu.cX().conj(), tags='CX'), 'CY': build_gate_2(qu.cY(), tags='CY'), - 'CY_conj': build_gate_2(qu.cY().conj(), tags='CY'), + 'CY_CONJ': build_gate_2(qu.cY().conj(), tags='CY'), 'CZ': build_gate_2(qu.cZ(), tags='CZ'), - 'CZ_conj': build_gate_2(qu.cZ().conj(), tags='CZ'), + 'CZ_CONJ': build_gate_2(qu.cZ().conj(), tags='CZ'), 'IS': build_gate_2(qu.iswap(), tags='ISWAP'), - 'IS_conj': build_gate_2(qu.iswap().conj(), tags='ISWAP'), + 'IS_CONJ': build_gate_2(qu.iswap().conj(), tags='ISWAP'), 'ISWAP': build_gate_2(qu.iswap(), tags='ISWAP'), - 'ISWAP_conj': build_gate_2(qu.iswap().conj(), tags='ISWAP'), + 'ISWAP_CONJ': build_gate_2(qu.iswap().conj(), tags='ISWAP'), # special non-tensor gates 'IDEN': lambda *args, **kwargs: None, 'SWAP': apply_swap, # single parametrizable gates 'RX': apply_Rx, - 'RX_conj': apply_Rx_conj, + 'RX_CONJ': apply_Rx_conj, 'RY': apply_Ry, - 'RY_conj': apply_Ry_conj, + 'RY_CONJ': apply_Ry_conj, 'RZ': apply_Rz, - 'RZ_conj': apply_Rz_conj, + 'RZ_CONJ': apply_Rz_conj, 'U3': apply_U3, - 'U3_conj': apply_U3_conj, + 'U3_CONJ': apply_U3_conj, 'U2': apply_U2, 'U2_conj': apply_U2_conj, 'U1': apply_U1, 'U1_conj': apply_U1_conj, # two qubit parametrizable gates 'CU3': apply_cu3, - 'CU3_conj': apply_cu3_conj, + 'CU3_CONJ': apply_cu3_conj, 'CU2': apply_cu2, - 'CU2_conj': apply_cu2_conj, + 'CU2_CONJ': apply_cu2_conj, 'CU1': apply_cu1, - 'CU1_conj': apply_cu1_conj, + 'CU1_CONJ': apply_cu1_conj, 'FS': apply_fsim, - 'FS_conj': apply_fsim_conj, + 'FS_CONJ': apply_fsim_conj, 'FSIM': apply_fsim, - 'FSIM_conj': apply_fsim_conj, + 'FSIM_CONJ': apply_fsim_conj, 'FSIMT': apply_fsimt, - 'FSIMT_conj': apply_fsimt_conj, + 'FSIMT_CONJ': apply_fsimt_conj, 'FSIMG': apply_fsimg, - 'FSIMG_conj': apply_fsimg_conj, + 'FSIMG_CONJ': apply_fsimg_conj, 'RZZ': apply_rzz, 'RXX': apply_rxx, 'RYY': apply_ryy, 'SU4': apply_su4, - 'SU4_conj': apply_su4_conj, + 'SU4_CONJ': apply_su4_conj, } ONE_QUBIT_PARAM_GATES = {'RX', 'RY', 'RZ', 'U3', 'U2', 'U1'} @@ -2026,6 +2026,61 @@ def uni(self): ) return self.get_uni(transposed=True) + + def get_uni_(self, transposed=False): + """Tensor network representation of the unitary operator (i.e. with + the initial state removed). + """ + U = self.rho + + if transposed: + # rename the initial state rand_uuid bonds to 1D site inds + ixmap = {self.ket_site_ind(i): self.bra_site_ind(i) + for i in range(self.N)} + else: + ixmap = {} + + # the first `N` tensors should be the tensors of input state + tids = tuple(U.tensor_map)[:self.N] + for i, tid in enumerate(tids): + t = U._pop_tensor(tid) + old_ix, = t.inds + + if transposed: + ixmap[old_ix] = f'k{i}' + else: + ixmap[old_ix] = f'b{i}' + + U.reindex_(ixmap) + U.view_as_( + TensorNetwork1DOperator, + upper_ind_id=self._ket_site_ind_id, + lower_ind_id=self._bra_site_ind_id, + ) + + return U + + @property + def U_super(self): + import warnings + warnings.warn( + "In future the tensor network returned by ``circ.uni`` will not " + "be transposed as it is currently, to match the expectation from " + "``U = circ.uni.to_dense()`` behaving like ``U @ psi``. You can " + "retain this behaviour with ``circ.get_uni(transposed=True)``.", + FutureWarning + ) + return self.get_uni_(transposed=True) + + + + + + + + + + def get_reverse_lightcone_tags_partial(self, psi, where): """Get the tags of gates in this partial circuit corresponding to the 'reverse' lightcone propagating backwards from registers in ``where``. From e56526844c9a15848b0d674110fb6bf78d144500 Mon Sep 17 00:00:00 2001 From: Reza Haghshenas Date: Wed, 16 Nov 2022 11:22:56 -0800 Subject: [PATCH 63/64] fixed update_circuit_params --- quimb/tensor/circuit.py | 1 + 1 file changed, 1 insertion(+) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index a4db74017..7a018db03 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -3793,6 +3793,7 @@ def update_params_from(self, tn): # update the actual tensor self._psi[tag].params = t.params + self._rho[tag].params = t.params # update the gate entry if label in ONE_QUBIT_PARAM_GATES: From c90cab64f73f24572385762a011c466555012986 Mon Sep 17 00:00:00 2001 From: Reza Haghshenas Date: Wed, 23 Nov 2022 14:55:19 -0800 Subject: [PATCH 64/64] fixed update_params_from --- quimb/tensor/circuit.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/quimb/tensor/circuit.py b/quimb/tensor/circuit.py index 7a018db03..d7d6da9e0 100644 --- a/quimb/tensor/circuit.py +++ b/quimb/tensor/circuit.py @@ -3781,7 +3781,7 @@ def update_params_from(self, tn): for i, gate in enumerate(self.gates): label = gate[0] tag = f'GATE_{i}' - t = tn[tag] + t = tn[[tag]+[label]] # sanity check that tensor(s) `t` correspond to the correct gate if label not in get_tags(t): @@ -3792,8 +3792,8 @@ def update_params_from(self, tn): if isinstance(t, PTensor): # update the actual tensor - self._psi[tag].params = t.params - self._rho[tag].params = t.params + self._psi[[tag]+[label]].params = t.params + self._rho[[tag]+[label]].params = t.params # update the gate entry if label in ONE_QUBIT_PARAM_GATES: