Skip to content

Commit

Permalink
feat: permit the disabling of Sz conservation (#52)
Browse files Browse the repository at this point in the history
  • Loading branch information
mrossinek authored Jan 6, 2025
1 parent 2651094 commit 83f9e6d
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 36 deletions.
7 changes: 1 addition & 6 deletions qiskit_addon_mpf/backends/tenpy_layers/model.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is a Qiskit project.
#
# (C) Copyright IBM 2024.
# (C) Copyright IBM 2024, 2025.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
Expand Down Expand Up @@ -37,9 +37,6 @@ def init_sites(self, model_params: Config) -> Site:
See :external:meth:`~tenpy.models.model.CouplingMPOModel.init_sites` for more details.
.. caution::
Currently, this enforces ``Sz`` conservation on all sites.
Args:
model_params: the model parameters.
Expand All @@ -48,8 +45,6 @@ def init_sites(self, model_params: Config) -> Site:
"""
# WARN: we use our own default to NOT sort charges (contrary to TeNPy default: `True`)
sort_charge = model_params.get("sort_charge", False, bool)
# TODO: currently, this default Sz conservation is coupled to the LegCharge definition in
# MPOState.initialize_from_lattice. Not conserving Sz errors with 'Different ChargeInfo'.
conserve = model_params.get("conserve", "Sz", str)
return SpinHalfSite(conserve=conserve, sort_charge=sort_charge)

Expand Down
41 changes: 21 additions & 20 deletions qiskit_addon_mpf/backends/tenpy_tebd/state.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is a Qiskit project.
#
# (C) Copyright IBM 2024.
# (C) Copyright IBM 2024, 2025.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
Expand Down Expand Up @@ -32,14 +32,17 @@ class MPOState(MPO, State):
"""

@classmethod
def initialize_from_lattice(cls, lat: Lattice) -> MPOState:
def initialize_from_lattice(cls, lat: Lattice, *, conserve: bool = True) -> MPOState:
"""Construct an identity :class:`MPOState` instance matching the provided lattice shape.
Given a lattice, this method constructs a new MPO identity matching the shape of the
lattice.
Args:
lat: the lattice describing the MPO sites.
conserve: whether to conserve ``Sz``. This is a simplified version of the more elaborate
``conserve`` property of :class:`~tenpy.networks.site.SpinHalfSite`. The boolean
value simply indicates ``Sz`` (``True``) or ``None`` conservation (``False``)
Returns:
An identity MPO.
Expand All @@ -53,24 +56,22 @@ def initialize_from_lattice(cls, lat: Lattice) -> MPOState:

labels = ["wL", "p", "p*", "wR"]

# creates a list of tensor leg charge objects encoding charges + conjugations for tensor
# legs (i.e. dimensions)
leg_charge = [
# e.g. charge information for tensor leg / dimension [1] and label ["2*Sz"]
# creates a LegCharge object from the flattened list of charges
# one for each of four legs or dimensions on B
npc.LegCharge.from_qflat(npc.ChargeInfo([1], ["2*Sz"]), [1], qconj=1),
npc.LegCharge.from_qflat(npc.ChargeInfo([1], ["2*Sz"]), [1, -1], qconj=1),
npc.LegCharge.from_qflat(npc.ChargeInfo([1], ["2*Sz"]), [1, -1], qconj=-1),
npc.LegCharge.from_qflat(npc.ChargeInfo([1], ["2*Sz"]), [1], qconj=-1),
]

B_array = npc.Array.from_ndarray(B, legcharges=leg_charge, labels=labels)

# FIXME: the following is supposed to allow `conserve=None` for our `SpinHalfSite` but it
# does not seem to work, even in the `conserve="Sz"` case.
#
# B_array = npc.Array.from_ndarray_trivial(B, labels=labels)
if conserve:
# creates a list of tensor leg charge objects encoding charges + conjugations for tensor
# legs (i.e. dimensions)
leg_charge = [
# e.g. charge information for tensor leg / dimension [1] and label ["2*Sz"]
# creates a LegCharge object from the flattened list of charges
# one for each of four legs or dimensions on B
npc.LegCharge.from_qflat(npc.ChargeInfo([1], ["2*Sz"]), [1], qconj=1),
npc.LegCharge.from_qflat(npc.ChargeInfo([1], ["2*Sz"]), [1, -1], qconj=1),
npc.LegCharge.from_qflat(npc.ChargeInfo([1], ["2*Sz"]), [1, -1], qconj=-1),
npc.LegCharge.from_qflat(npc.ChargeInfo([1], ["2*Sz"]), [1], qconj=-1),
]

B_array = npc.Array.from_ndarray(B, legcharges=leg_charge, labels=labels)
else:
B_array = npc.Array.from_ndarray_trivial(B, labels=labels)

num_sites = lat.N_sites
# initialize the MPO psi with the wavepacket and an identity operator
Expand Down
8 changes: 8 additions & 0 deletions releasenotes/notes/tenpy-conserve-none-f34f73b38b89111c.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
features:
- |
TeNPy can disable ``Sz`` conservation. Previously, the
:meth:`~qiskit_addon_mpf.backends.tenpy_tebd.MPOState.initialize_from_lattice`
method did not support this, but a new keyword argument ``conserve`` has
been added which allows the disabling of ``Sz`` conservation. The default
remains to be ``True``.
35 changes: 30 additions & 5 deletions test/backends/tenpy_layers/test_e2e.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is a Qiskit project.
#
# (C) Copyright IBM 2024.
# (C) Copyright IBM 2024, 2025.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
Expand All @@ -24,6 +24,7 @@
from qiskit_addon_mpf.backends.tenpy_layers import LayerModel, LayerwiseEvolver
from qiskit_addon_mpf.backends.tenpy_tebd import MPOState, MPS_neel_state, TEBDEvolver
from tenpy.models import XXZChain2
from tenpy.networks.site import SpinHalfSite


def gen_ext_field_layer(n, hz):
Expand Down Expand Up @@ -52,32 +53,51 @@ def gen_even_coupling_layer(n, Jxx, Jz, J):
return qc


class ConserveXXZChain2(XXZChain2):
"""TeNPy's XXZChain2 hard-codes Sz conservation. This subclass makes it configurable."""

def init_sites(self, model_params):
conserve = model_params.get("conserve", "Sz", bool)
sort_charge = model_params.get("sort_charge", True, bool)
return SpinHalfSite(conserve=conserve, sort_charge=sort_charge) # use predefined Site


@pytest.mark.skipif(not HAS_TENPY, reason="TeNPy is required for these unittests")
class TestEndToEnd:
@pytest.mark.parametrize(
["time", "expected_A", "expected_b", "expected_coeffs"],
["time", "expected_A", "expected_b", "expected_coeffs", "conserve"],
[
(
0.5,
[[1.0, 0.9997562], [0.9997562, 1.0]],
[0.99944125, 0.99857914],
[2.2680558, -1.26805551],
"None",
),
(
0.5,
[[1.0, 0.9997562], [0.9997562, 1.0]],
[0.99944125, 0.99857914],
[2.2680558, -1.26805551],
"Sz",
),
(
1.0,
[[1.0, 0.99189288], [0.99189288, 1.0]],
[0.98478594, 0.9676077],
[1.55870387, -0.55870387],
"Sz",
),
(
1.5,
[[1.0, 0.95352741], [0.95352741, 1.0]],
[0.9032996, 0.71967399],
[2.47563369, -1.47563369],
"Sz",
),
],
)
def test_end_to_end(self, time, expected_A, expected_b, expected_coeffs):
def test_end_to_end(self, time, expected_A, expected_b, expected_coeffs, conserve):
np.random.seed(0)

# constants
Expand All @@ -97,13 +117,14 @@ def test_end_to_end(self, time, expected_A, expected_b, expected_coeffs):

# This is the full model that we want to simulate. It is used for the "exact" time evolution
# (which is approximated via a fourth-order Suzuki-Trotter formula).
exact_model = XXZChain2(
exact_model = ConserveXXZChain2(
{
"L": L,
"Jz": 4.0 * Jz * J,
"Jxx": 4.0 * Jxx * J,
"hz": 2.0 * hz,
"bc_MPS": "finite",
"conserve": conserve,
"sort_charge": False,
}
)
Expand All @@ -112,22 +133,26 @@ def test_end_to_end(self, time, expected_A, expected_b, expected_coeffs):
# Trotter circuit and sliced it using `qiskit_addon_utils.slicing`.
odd_coupling_layer = LayerModel.from_quantum_circuit(
gen_odd_coupling_layer(L, Jxx, Jz, J),
conserve=conserve,
bc_MPS="finite",
)
even_coupling_layer = LayerModel.from_quantum_circuit(
gen_even_coupling_layer(L, Jxx, Jz, 2.0 * J), # factor 2 because its the central layer
conserve=conserve,
bc_MPS="finite",
)
odd_onsite_layer = LayerModel.from_quantum_circuit(
gen_ext_field_layer(L, hz),
keep_only_odd=True,
scaling_factor=0.5,
conserve=conserve,
bc_MPS="finite",
)
even_onsite_layer = LayerModel.from_quantum_circuit(
gen_ext_field_layer(L, hz),
keep_only_odd=False,
scaling_factor=0.5,
conserve=conserve,
bc_MPS="finite",
)
# Our layers combine to form a second-order Suzuki-Trotter formula as follows:
Expand Down Expand Up @@ -159,7 +184,7 @@ def test_end_to_end(self, time, expected_A, expected_b, expected_coeffs):
model = setup_dynamic_lse(
[4, 3],
time,
partial(MPOState.initialize_from_lattice, exact_model.lat),
partial(MPOState.initialize_from_lattice, exact_model.lat, conserve=conserve == "Sz"),
partial(
TEBDEvolver,
model=exact_model,
Expand Down
31 changes: 26 additions & 5 deletions test/backends/tenpy_tebd/test_e2e.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is a Qiskit project.
#
# (C) Copyright IBM 2024.
# (C) Copyright IBM 2024, 2025.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
Expand All @@ -21,34 +21,54 @@
if HAS_TENPY:
from qiskit_addon_mpf.backends.tenpy_tebd import MPOState, MPS_neel_state, TEBDEvolver
from tenpy.models import XXZChain2
from tenpy.networks.site import SpinHalfSite


class ConserveXXZChain2(XXZChain2):
"""TeNPy's XXZChain2 hard-codes Sz conservation. This subclass makes it configurable."""

def init_sites(self, model_params):
conserve = model_params.get("conserve", "Sz", bool)
sort_charge = model_params.get("sort_charge", True, bool)
return SpinHalfSite(conserve=conserve, sort_charge=sort_charge) # use predefined Site


@pytest.mark.skipif(not HAS_TENPY, reason="TeNPy is required for these unittests")
class TestEndToEnd:
@pytest.mark.parametrize(
["time", "expected_A", "expected_b", "expected_coeffs"],
["time", "expected_A", "expected_b", "expected_coeffs", "conserve"],
[
(
0.5,
[[1.0, 0.9997562], [0.9997562, 1.0]],
[0.99944125, 0.99857914],
[2.26805572, -1.26805543],
"None",
),
(
0.5,
[[1.0, 0.9997562], [0.9997562, 1.0]],
[0.99944125, 0.99857914],
[2.26805572, -1.26805543],
"Sz",
),
(
1.0,
[[1.0, 0.99189288], [0.99189288, 1.0]],
[0.98478594, 0.9676077],
[1.55870386, -0.55870386],
"Sz",
),
(
1.5,
[[1.0, 0.95352741], [0.95352741, 1.0]],
[0.93918471, 0.71967399],
[2.8617227, -1.8617227],
"Sz",
),
],
)
def test_end_to_end(self, time, expected_A, expected_b, expected_coeffs):
def test_end_to_end(self, time, expected_A, expected_b, expected_coeffs, conserve):
np.random.seed(0)

# constants
Expand All @@ -68,13 +88,14 @@ def test_end_to_end(self, time, expected_A, expected_b, expected_coeffs):

# This is the full model that we want to simulate. It is used for the "exact" time evolution
# (which is approximated via a fourth-order Suzuki-Trotter formula).
exact_model = XXZChain2(
exact_model = ConserveXXZChain2(
{
"L": L,
"Jz": 4.0 * Jz * J,
"Jxx": 4.0 * Jxx * J,
"hz": 2.0 * hz,
"bc_MPS": "finite",
"conserve": conserve,
"sort_charge": False,
}
)
Expand All @@ -98,7 +119,7 @@ def test_end_to_end(self, time, expected_A, expected_b, expected_coeffs):
model = setup_dynamic_lse(
[4, 3],
time,
partial(MPOState.initialize_from_lattice, exact_model.lat),
partial(MPOState.initialize_from_lattice, exact_model.lat, conserve=conserve == "Sz"),
partial(
TEBDEvolver,
model=exact_model,
Expand Down

0 comments on commit 83f9e6d

Please sign in to comment.