Skip to content

Commit

Permalink
[Feature] Implement constructor function for alternating layer ansatz (
Browse files Browse the repository at this point in the history
  • Loading branch information
ducthanh1991 authored Sep 23, 2024
1 parent ba71ad0 commit 82a0761
Show file tree
Hide file tree
Showing 3 changed files with 248 additions and 1 deletion.
3 changes: 2 additions & 1 deletion qadence/constructors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
exp_fourier_feature_map,
)

from .ansatze import hea
from .ansatze import hea, alt

from .iia import identity_initialized_ansatz

Expand All @@ -29,6 +29,7 @@
"feature_map",
"exp_fourier_feature_map",
"hea",
"alt",
"identity_initialized_ansatz",
"hamiltonian_factory",
"ising_hamiltonian",
Expand Down
176 changes: 176 additions & 0 deletions qadence/constructors/ansatze.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,3 +320,179 @@ def hea_bDAQC(*args: Any, **kwargs: Any) -> Any:

def hea_analog(*args: Any, **kwargs: Any) -> Any:
raise NotImplementedError


def alt(
n_qubits: int,
m_block_qubits: int,
depth: int = 1,
param_prefix: str = "theta",
support: tuple[int, ...] = None,
strategy: Strategy = Strategy.DIGITAL,
**strategy_args: Any,
) -> AbstractBlock:
"""
Factory function for the alternating layer ansatz (alt).
Args:
n_qubits: number of qubits in the block
m_block_qubits: number of qubits in the local entangling block
depth: number of layers of the alt
param_prefix: the base name of the variational parameters
support: qubit indexes where the alt is applied
strategy: Strategy.Digital or Strategy.DigitalAnalog
**strategy_args: see below
Keyword Arguments:
operations (list): list of operations to cycle through in the
digital single-qubit rotations of each layer. Valid for
Digital .
entangler (AbstractBlock):
- Digital: 2-qubit entangling operation. Supports CNOT, CZ,
CRX, CRY, CRZ, CPHASE. Controlled rotations will have variational
parameters on the rotation angles.
"""

if support is None:
support = tuple(range(n_qubits))

alt_func_dict = {
Strategy.DIGITAL: alt_digital,
Strategy.SDAQC: alt_sDAQC,
Strategy.BDAQC: alt_bDAQC,
Strategy.ANALOG: alt_analog,
}

try:
alt_func = alt_func_dict[strategy]
except KeyError:
raise KeyError(f"Strategy {strategy} not recognized.")

hea_block: AbstractBlock = alt_func(
n_qubits=n_qubits,
m_block_qubits=m_block_qubits,
depth=depth,
param_prefix=param_prefix,
support=support,
**strategy_args,
) # type: ignore

return hea_block


#################
## DIGITAL ALT ##
#################


def _entanglers_block_digital(
n_qubits: int,
m_block_qubits: int,
depth: int,
param_prefix: str = "theta",
support: tuple[int, ...] = None,
entangler: Type[DigitalEntanglers] = CNOT,
) -> list[AbstractBlock]:
if support is None:
support = tuple(range(n_qubits))
iterator = itertools.count()
ent_list: list[AbstractBlock] = []

for d in range(depth):
start_i = 0 if not d % 2 else -m_block_qubits // 2
ents = [
kron(
_entangler(
control=support[i + j],
target=support[i + j + 1],
param_str=param_prefix + f"_ent_{next(iterator)}",
op=entangler,
)
for j in range(start_j, m_block_qubits, 2)
for i in range(start_i, n_qubits, m_block_qubits)
if i + j + 1 < n_qubits and j + 1 < m_block_qubits and i + j >= 0
)
for start_j in [i for i in range(2) if m_block_qubits > 2 or i == 0]
]

ent_list.append(chain(*ents))
return ent_list


def alt_digital(
n_qubits: int,
m_block_qubits: int,
depth: int = 1,
support: tuple[int, ...] = None,
param_prefix: str = "theta",
operations: list[type[AbstractBlock]] = [RX, RY],
entangler: Type[DigitalEntanglers] = CNOT,
) -> AbstractBlock:
"""
Construct the digital alternating layer ansatz (ALT).
Args:
n_qubits (int): number of qubits in the ansatz.
m_block_qubits (int): number of qubits in the local entangling block.
depth (int): number of layers of the ALT.
param_prefix (str): the base name of the variational parameters
operations (list): list of operations to cycle through in the
digital single-qubit rotations of each layer.
support (tuple): qubit indexes where the ALT is applied.
entangler (AbstractBlock): 2-qubit entangling operation.
Supports CNOT, CZ, CRX, CRY, CRZ. Controlld rotations
will have variational parameters on the rotation angles.
"""

try:
if entangler not in [CNOT, CZ, CRX, CRY, CRZ, CPHASE]:
raise ValueError(
"Please provide a valid two-qubit entangler operation for digital ALT."
)
except TypeError:
raise ValueError("Please provide a valid two-qubit entangler operation for digital ALT.")

rot_list = _rotations_digital(
n_qubits=n_qubits,
depth=depth,
support=support,
param_prefix=param_prefix,
operations=operations,
)

ent_list = _entanglers_block_digital(
n_qubits,
m_block_qubits,
param_prefix=param_prefix + "_ent",
depth=depth,
support=support,
entangler=entangler,
)

layers = []
for d in range(depth):
layers.append(rot_list[d])
layers.append(ent_list[d])

return tag(chain(*layers), "ALT")


#################
## sdaqc ALT ##
#################
def alt_sDAQC(*args: Any, **kwargs: Any) -> Any:
raise NotImplementedError


#################
## bdaqc ALT ##
#################
def alt_bDAQC(*args: Any, **kwargs: Any) -> Any:
raise NotImplementedError


#################
## analog ALT ##
#################
def alt_analog(*args: Any, **kwargs: Any) -> Any:
raise NotImplementedError
70 changes: 70 additions & 0 deletions tests/constructors/test_ansatz.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
QuantumModel,
VariationalParameter,
Z,
alt,
chain,
hamiltonian_factory,
hea,
Expand Down Expand Up @@ -169,3 +170,72 @@ def test_iia_sDAQC(n_qubits: int, depth: int, hamiltonian: str) -> None:
assert not has_duplicate_vparams(iia)
if hamiltonian == "parametric_local":
assert has_duplicate_vparams(iia)


@pytest.mark.parametrize("n_qubits", [10, 11])
@pytest.mark.parametrize("m_block_qubits", [2, 3, 4])
@pytest.mark.parametrize("depth", [2, 3])
@pytest.mark.parametrize("entangler", [CNOT, CRX])
def test_alt_duplicate_params(
n_qubits: int, m_block_qubits: int, depth: int, entangler: AbstractBlock
) -> None:
"""Tests that ALTs are initialized with correct parameter namings."""
common_params = {
"n_qubits": n_qubits,
"m_block_qubits": m_block_qubits,
"depth": depth,
"operations": [RZ, RX, RZ],
"entangler": entangler,
}
alt1 = alt(
n_qubits=n_qubits,
m_block_qubits=m_block_qubits,
depth=depth,
operations=[RZ, RX, RZ],
entangler=entangler,
)
alt2 = alt(
n_qubits=n_qubits,
m_block_qubits=m_block_qubits,
depth=depth,
operations=[RZ, RX, RZ],
entangler=entangler,
)
block1 = chain(alt1, alt2)
assert has_duplicate_vparams(block1)
alt1 = alt(
n_qubits=n_qubits,
m_block_qubits=m_block_qubits,
depth=depth,
operations=[RZ, RX, RZ],
entangler=entangler,
param_prefix="0",
)
alt2 = alt(
n_qubits=n_qubits,
m_block_qubits=m_block_qubits,
depth=depth,
operations=[RZ, RX, RZ],
entangler=entangler,
param_prefix="1",
)
block2 = chain(alt1, alt2)
assert not has_duplicate_vparams(block2)


@pytest.mark.parametrize("n_qubits", [10, 11])
@pytest.mark.parametrize("m_block_qubits", [2, 3, 4])
@pytest.mark.parametrize("depth", [2, 3])
def test_alt_forward(n_qubits: int, m_block_qubits: int, depth: int) -> None:
alt1 = alt(
n_qubits=n_qubits,
m_block_qubits=m_block_qubits,
depth=depth,
operations=[RZ, RX, RZ],
param_prefix="0",
)
circuit = QuantumCircuit(n_qubits, alt1)
model = QuantumModel(circuit)

wf = model.run({})
assert wf.shape == Size([1, 2**n_qubits])

0 comments on commit 82a0761

Please sign in to comment.