Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[Feature] Implement constructor function for alternating layer ansatz #565

Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
678bb9e
20240821.1 added the eblock entanglmer and the digital ALT functions
ducthanh1991 Aug 21, 2024
271bef4
Merge branch 'pasqal-io:main' into feature/digital_alternating_layer_…
ducthanh1991 Aug 23, 2024
16eea4c
Merge branch 'pasqal-io:main' into feature/digital_alternating_layer_…
ducthanh1991 Aug 23, 2024
5bb29e9
20240823.1 delete unecessary display
ducthanh1991 Aug 23, 2024
47a3c11
Merge branch 'feature/digital_alternating_layer_ansatz_function' of h…
ducthanh1991 Aug 23, 2024
9fa438f
20240825.1 first implementation of precommit suggestion
ducthanh1991 Aug 25, 2024
c8ea3a9
Merge branch 'pasqal-io:main' into feature/digital_alternating_layer_…
ducthanh1991 Aug 27, 2024
9e3cdf1
Merge branch 'pasqal-io:main' into feature/digital_alternating_layer_…
ducthanh1991 Aug 27, 2024
0d2fc79
20240827.1 added code documentation
ducthanh1991 Aug 27, 2024
2c60e31
Merge branch 'pasqal-io:main' into feature/digital_alternating_layer_…
ducthanh1991 Aug 27, 2024
06d96c9
200827.2 added the factory function for ALT
ducthanh1991 Aug 27, 2024
799a37c
20240828.2 corrected alt function typo
ducthanh1991 Aug 28, 2024
7c00a22
Merge branch 'pasqal-io:main' into feature/digital_alternating_layer_…
ducthanh1991 Aug 31, 2024
6c59ab2
Merge branch 'pasqal-io:main' into feature/digital_alternating_layer_…
ducthanh1991 Sep 4, 2024
076ffc0
added test
ducthanh1991 Sep 4, 2024
1f3676f
Merge branch 'pasqal-io:main' into feature/digital_alternating_layer_…
ducthanh1991 Sep 6, 2024
556ca18
Merge branch 'pasqal-io:main' into feature/digital_alternating_layer_…
ducthanh1991 Sep 6, 2024
1ef8dfb
Merge branch 'pasqal-io:main' into feature/digital_alternating_layer_…
ducthanh1991 Sep 8, 2024
1284210
corrected typo in docstrings of alt function
ducthanh1991 Sep 9, 2024
8bd433e
Merge branch 'pasqal-io:main' into feature/digital_alternating_layer_…
ducthanh1991 Sep 16, 2024
a6ce131
20240918.1 removed repeating piece of code in the entangle_block_digi…
ducthanh1991 Sep 18, 2024
e65ec14
Merge branch 'pasqal-io:main' into feature/digital_alternating_layer_…
ducthanh1991 Sep 19, 2024
433478b
20240919.1 implemented suggestion of change
ducthanh1991 Sep 19, 2024
370f616
forgot linting
ducthanh1991 Sep 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
219 changes: 219 additions & 0 deletions qadence/constructors/ansatze.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,3 +320,222 @@ 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
chMoussa marked this conversation as resolved.
Show resolved Hide resolved
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):
ents = []
if not d % 2:
ents.append(
kron(
_entangler(
control=support[i + j],
target=support[i + j + 1],
param_str=param_prefix + f"_ent_{next(iterator)}",
op=entangler,
)
for i in range(0, n_qubits, m_block_qubits)
for j in range(0, m_block_qubits, 2)
if i + j + 1 < n_qubits and j + 1 < m_block_qubits
)
)
if m_block_qubits > 2:
ents.append(
kron(
_entangler(
control=support[i + j],
target=support[i + j + 1],
param_str=param_prefix + f"_ent_{next(iterator)}",
op=entangler,
)
for i in range(0, n_qubits, m_block_qubits)
for j in range(1, m_block_qubits, 2)
if i + j + 1 < n_qubits and j + 1 < m_block_qubits
)
)

elif d % 2:
chMoussa marked this conversation as resolved.
Show resolved Hide resolved
ents.append(
kron(
_entangler(
control=support[i + j],
target=support[i + j + 1],
param_str=param_prefix + f"_ent_{next(iterator)}",
op=entangler,
)
for i in range(-m_block_qubits // 2, n_qubits, m_block_qubits)
for j in range(0, m_block_qubits, 2)
if i + j + 1 < n_qubits and j + 1 < m_block_qubits and i + j >= 0
)
)
if m_block_qubits > 2:
chMoussa marked this conversation as resolved.
Show resolved Hide resolved
ents.append(
kron(
_entangler(
control=support[i + j],
target=support[i + j + 1],
param_str=param_prefix + f"_ent_{next(iterator)}",
op=entangler,
)
for i in range(-m_block_qubits // 2, n_qubits, m_block_qubits)
for j in range(1, m_block_qubits, 2)
if i + j + 1 < n_qubits and j + 1 < m_block_qubits and i + j >= 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])