Constructors for common quantum circuits
exp_fourier_feature_map(n_qubits, support=None, param='x', feature_range=None)
Section titled “
exp_fourier_feature_map(n_qubits, support=None, param='x', feature_range=None)
”Exponential fourier feature map.
| PARAMETER | DESCRIPTION |
|---|---|
n_qubits
|
number of qubits in the feature
TYPE:
|
support
|
qubit support
TYPE:
|
param
|
name of feature
TYPE:
|
feature_range
|
min and max value of the feature, as floats in a Tuple
TYPE:
|
Source code in qadence/constructors/feature_maps.py
202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234def exp_fourier_feature_map( n_qubits: int, support: tuple[int, ...] = None, param: str = "x", feature_range: tuple[float, float] = None,) -> AbstractBlock: """ Exponential fourier feature map.
Args: n_qubits: number of qubits in the feature support: qubit support param: name of feature `Parameter` feature_range: min and max value of the feature, as floats in a Tuple """
if feature_range is None: feature_range = (0.0, 2.0**n_qubits)
support = tuple(range(n_qubits)) if support is None else support hlayer = kron(H(qubit) for qubit in support) rlayer = feature_map( n_qubits, support=support, param=param, op=RZ, fm_type=BasisSet.FOURIER, reupload_scaling=ReuploadScaling.EXP, feature_range=feature_range, target_range=(0.0, 2 * PI), ) rlayer.tag = None return tag(chain(hlayer, rlayer), f"ExpFourierFM({param})")
feature_map(n_qubits, support=None, param='phi', op=RX, fm_type=BasisSet.FOURIER, reupload_scaling=ReuploadScaling.CONSTANT, feature_range=None, target_range=None, multiplier=None, param_prefix=None)
Section titled “
feature_map(n_qubits, support=None, param='phi', op=RX, fm_type=BasisSet.FOURIER, reupload_scaling=ReuploadScaling.CONSTANT, feature_range=None, target_range=None, multiplier=None, param_prefix=None)
”Construct a feature map of a given type.
| PARAMETER | DESCRIPTION |
|---|---|
n_qubits
|
Number of qubits the feature map covers. Results in
TYPE:
|
support
|
Puts one feature-encoding rotation gate on every qubit in
TYPE:
|
param
|
Parameter of the feature map; you can pass a string or Parameter; it will be set as non-trainable (FeatureParameter) regardless.
TYPE:
|
op
|
Rotation operation of the feature map; choose from RX, RY, RZ or PHASE.
TYPE:
|
fm_type
|
Basis set for data encoding; choose from |
reupload_scaling
|
how the feature map scales the data that is re-uploaded for each qubit.
choose from
TYPE:
|
feature_range
|
range of data that the input data provided comes from. Used to map input data to the correct domain of the feature-encoding function.
TYPE:
|
target_range
|
range of data the data encoder assumes as the natural range. For example, in Chebyshev polynomials it is (-1, 1), while for Fourier it may be chosen as (0, 2*PI). Used to map data to the correct domain of the feature-encoding function.
TYPE:
|
multiplier
|
overall multiplier; this is useful for reuploading the feature map serially with different scalings; can be a number or parameter/expression.
TYPE:
|
param_prefix
|
string prefix to create trainable parameters multiplying the feature parameter inside the feature-encoding function. Note that currently this does not take into account the domain of the feature-encoding function.
TYPE:
|
Example:
from qadence import feature_map, BasisSet, ReuploadScaling
fm = feature_map(3, fm_type=BasisSet.FOURIER)print(f"{fm = }")
fm = feature_map(3, fm_type=BasisSet.CHEBYSHEV)print(f"{fm = }")
fm = feature_map(3, fm_type=BasisSet.FOURIER, reupload_scaling = ReuploadScaling.TOWER)print(f"{fm = }")fm = KronBlock(0,1,2) [tag: Constant Fourier FM]├── RX(0) [params: ['phi']]├── RX(1) [params: ['phi']]└── RX(2) [params: ['phi']]fm = KronBlock(0,1,2) [tag: Constant Chebyshev FM]├── RX(0) [params: ['acos(phi)']]├── RX(1) [params: ['acos(phi)']]└── RX(2) [params: ['acos(phi)']]fm = KronBlock(0,1,2) [tag: Tower Fourier FM]├── RX(0) [params: ['1_0*phi']]├── RX(1) [params: ['2_0*phi']]└── RX(2) [params: ['3_0*phi']]Source code in qadence/constructors/feature_maps.py
110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199def feature_map( n_qubits: int, support: tuple[int, ...] | None = None, param: Parameter | str = "phi", op: RotationTypes = RX, fm_type: BasisSet | Callable | str = BasisSet.FOURIER, reupload_scaling: ReuploadScaling | Callable | str = ReuploadScaling.CONSTANT, feature_range: tuple[float, float] | None = None, target_range: tuple[float, float] | None = None, multiplier: Parameter | TParameter | None = None, param_prefix: str | None = None,) -> KronBlock: """Construct a feature map of a given type.
Arguments: n_qubits: Number of qubits the feature map covers. Results in `support=range(n_qubits)`. support: Puts one feature-encoding rotation gate on every qubit in `support`. n_qubits in this case specifies the total overall qubits of the circuit, which may be wider than the support itself, but not narrower. param: Parameter of the feature map; you can pass a string or Parameter; it will be set as non-trainable (FeatureParameter) regardless. op: Rotation operation of the feature map; choose from RX, RY, RZ or PHASE. fm_type: Basis set for data encoding; choose from `BasisSet.FOURIER` for Fourier encoding, or `BasisSet.CHEBYSHEV` for Chebyshev polynomials of the first kind. reupload_scaling: how the feature map scales the data that is re-uploaded for each qubit. choose from `ReuploadScaling` enumeration or provide your own function with a single int as input and int or float as output. feature_range: range of data that the input data provided comes from. Used to map input data to the correct domain of the feature-encoding function. target_range: range of data the data encoder assumes as the natural range. For example, in Chebyshev polynomials it is (-1, 1), while for Fourier it may be chosen as (0, 2*PI). Used to map data to the correct domain of the feature-encoding function. multiplier: overall multiplier; this is useful for reuploading the feature map serially with different scalings; can be a number or parameter/expression. param_prefix: string prefix to create trainable parameters multiplying the feature parameter inside the feature-encoding function. Note that currently this does not take into account the domain of the feature-encoding function.
Example: ```python exec="on" source="material-block" result="json" from qadence import feature_map, BasisSet, ReuploadScaling
fm = feature_map(3, fm_type=BasisSet.FOURIER) print(f"{fm = }")
fm = feature_map(3, fm_type=BasisSet.CHEBYSHEV) print(f"{fm = }")
fm = feature_map(3, fm_type=BasisSet.FOURIER, reupload_scaling = ReuploadScaling.TOWER) print(f"{fm = }") ``` """
# Process input if support is None: support = tuple(range(n_qubits)) elif len(support) != n_qubits: raise ValueError("Wrong qubit support supplied")
if op not in ROTATIONS: raise ValueError( f"Operation {op} not supported. " f"Please provide one from {[rot.__name__ for rot in ROTATIONS]}." )
scaled_fparam = fm_parameter_scaling( fm_type, param, feature_range=feature_range, target_range=target_range )
transform_func = fm_parameter_func(fm_type)
basis_tag = fm_type.value if isinstance(fm_type, BasisSet) else str(fm_type) rs_func, rs_tag = fm_reupload_scaling_fn(reupload_scaling)
# Set overall multiplier multiplier = 1 if multiplier is None else Parameter(multiplier)
# Build feature map op_list = [] fparam = scaled_fparam for i, qubit in enumerate(support): if param_prefix is not None: train_param = VariationalParameter(param_prefix + f"_{i}") fparam = train_param * scaled_fparam op_list.append(op(qubit, multiplier * rs_func(i) * transform_func(fparam))) fm = kron(*op_list)
fm.tag = rs_tag + " " + basis_tag + " FM"
return fm
hea(n_qubits, depth=1, param_prefix='theta', support=None, strategy=Strategy.DIGITAL, **strategy_args)
Section titled “
hea(n_qubits, depth=1, param_prefix='theta', support=None, strategy=Strategy.DIGITAL, **strategy_args)
”Factory function for the Hardware Efficient Ansatz (HEA).
| PARAMETER | DESCRIPTION |
|---|---|
n_qubits
|
number of qubits in the circuit
TYPE:
|
depth
|
number of layers of the HEA
TYPE:
|
param_prefix
|
the base name of the variational parameters
TYPE:
|
support
|
qubit indices where the HEA is applied
TYPE:
|
strategy
|
Strategy for the ansatz. One of the Strategy variants. |
**strategy_args
|
see below
TYPE:
|
| PARAMETER | DESCRIPTION |
|---|---|
operations |
list of operations to cycle through in the digital single-qubit rotations of each layer. Valid for Digital and DigitalAnalog HEA.
TYPE:
|
periodic |
if the qubits should be linked periodically. periodic=False is not supported in emu-c. Valid for only for Digital HEA.
TYPE:
|
entangler |
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
AbstractBlock
|
The Hardware Efficient Ansatz (HEA) circuit. |
Examples:
from qadence import RZ, RXfrom qadence import hea
# create the circuitn_qubits, depth = 2, 4ansatz = hea( n_qubits=n_qubits, depth=depth, strategy="sDAQC", operations=[RZ,RX,RZ])Source code in qadence/constructors/hea.py
1415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990def hea( n_qubits: int, depth: int = 1, param_prefix: str = "theta", support: tuple[int, ...] | None = None, strategy: Strategy = Strategy.DIGITAL, **strategy_args: Any,) -> AbstractBlock: """ Factory function for the Hardware Efficient Ansatz (HEA).
Args: n_qubits: number of qubits in the circuit depth: number of layers of the HEA param_prefix: the base name of the variational parameters support: qubit indices where the HEA is applied strategy: Strategy for the ansatz. One of the Strategy variants. **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 and DigitalAnalog HEA. periodic (bool): if the qubits should be linked periodically. periodic=False is not supported in emu-c. Valid for only for Digital HEA. entangler (AbstractBlock): - Digital: 2-qubit entangling operation. Supports CNOT, CZ, CRX, CRY, CRZ, CPHASE. Controlled rotations will have variational parameters on the rotation angles. - SDAQC | Analog: Hamiltonian generator for the analog entangling layer. Defaults to global ZZ Hamiltonian. Time parameter is considered variational.
Returns: The Hardware Efficient Ansatz (HEA) circuit.
Examples: ```python exec="on" source="material-block" result="json" from qadence import RZ, RX from qadence import hea
# create the circuit n_qubits, depth = 2, 4 ansatz = hea( n_qubits=n_qubits, depth=depth, strategy="sDAQC", operations=[RZ,RX,RZ] ) ``` """
if support is None: support = tuple(range(n_qubits))
hea_func_dict = { Strategy.DIGITAL: hea_digital, Strategy.SDAQC: hea_sDAQC, Strategy.BDAQC: hea_bDAQC, Strategy.ANALOG: hea_analog, }
try: hea_func = hea_func_dict[strategy] except KeyError: raise KeyError(f"Strategy {strategy} not recognized.")
hea_block: AbstractBlock = hea_func( n_qubits=n_qubits, depth=depth, param_prefix=param_prefix, support=support, **strategy_args, ) # type: ignore
return hea_block
hea_digital(n_qubits, depth=1, param_prefix='theta', support=None, periodic=False, operations=[RX, RY, RX], entangler=CNOT)
Section titled “
hea_digital(n_qubits, depth=1, param_prefix='theta', support=None, periodic=False, operations=[RX, RY, RX], entangler=CNOT)
”Construct the Digital Hardware Efficient Ansatz (HEA).
| PARAMETER | DESCRIPTION |
|---|---|
n_qubits
|
number of qubits in the cricuit.
TYPE:
|
depth
|
number of layers of the HEA.
TYPE:
|
param_prefix
|
the base name of the variational parameters
TYPE:
|
support
|
qubit indices where the HEA is applied.
TYPE:
|
periodic
|
if the qubits should be linked periodically. periodic=False is not supported in emu-c.
TYPE:
|
operations
|
list of operations to cycle through in the digital single-qubit rotations of each layer. |
entangler
|
2-qubit entangling operation. Supports CNOT, CZ, CRX, CRY, CRZ. Controlld rotations will have variational parameters on the rotation angles.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
AbstractBlock
|
The digital Hardware Efficient Ansatz (HEA) circuit. |
Source code in qadence/constructors/hea.py
218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275def hea_digital( n_qubits: int, depth: int = 1, param_prefix: str = "theta", support: tuple[int, ...] | None = None, periodic: bool = False, operations: list[type[AbstractBlock]] = [RX, RY, RX], entangler: Type[DigitalEntanglers] = CNOT,) -> AbstractBlock: """ Construct the Digital Hardware Efficient Ansatz (HEA).
Args: n_qubits (int): number of qubits in the cricuit. depth (int): number of layers of the HEA. param_prefix (str): the base name of the variational parameters support (tuple): qubit indices where the HEA is applied. periodic (bool): if the qubits should be linked periodically. periodic=False is not supported in emu-c. operations (list): list of operations to cycle through in the digital single-qubit rotations of each layer. entangler (AbstractBlock): 2-qubit entangling operation. Supports CNOT, CZ, CRX, CRY, CRZ. Controlld rotations will have variational parameters on the rotation angles.
Returns: The digital Hardware Efficient Ansatz (HEA) circuit. """ try: if entangler not in [CNOT, CZ, CRX, CRY, CRZ, CPHASE]: raise ValueError( "Please provide a valid two-qubit entangler operation for digital HEA." ) except TypeError: raise ValueError("Please provide a valid two-qubit entangler operation for digital HEA.")
rot_list = _rotations_digital( n_qubits=n_qubits, depth=depth, param_prefix=param_prefix, support=support, operations=operations, )
ent_list = _entanglers_digital( n_qubits=n_qubits, depth=depth, param_prefix=param_prefix, support=support, periodic=periodic, entangler=entangler, )
layers = [] for d in range(depth): layers.append(rot_list[d]) layers.append(ent_list[d]) return tag(chain(*layers), "HEA")
hea_sDAQC(n_qubits, depth=1, param_prefix='theta', support=None, operations=[RX, RY, RX], entangler=None)
Section titled “
hea_sDAQC(n_qubits, depth=1, param_prefix='theta', support=None, operations=[RX, RY, RX], entangler=None)
”Construct the Hardware Efficient Ansatz (HEA) with analog entangling layers.
It uses step-wise digital-analog computation.
| PARAMETER | DESCRIPTION |
|---|---|
n_qubits
|
number of qubits in the circuit.
TYPE:
|
depth
|
number of layers of the HEA.
TYPE:
|
param_prefix
|
the base name of the variational parameters
TYPE:
|
support
|
qubit indices where the HEA is applied.
TYPE:
|
operations
|
list of operations to cycle through in the digital single-qubit rotations of each layer. |
entangler
|
Hamiltonian generator for the analog entangling layer. Defaults to global ZZ Hamiltonian. Time parameter is considered variational.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
AbstractBlock
|
The step-wise digital-analog Hardware Efficient Ansatz (sDA HEA) circuit. |
Source code in qadence/constructors/hea.py
302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361def hea_sDAQC( n_qubits: int, depth: int = 1, param_prefix: str = "theta", support: tuple[int, ...] | None = None, operations: list[type[AbstractBlock]] = [RX, RY, RX], entangler: AbstractBlock | None = None,) -> AbstractBlock: """ Construct the Hardware Efficient Ansatz (HEA) with analog entangling layers.
It uses step-wise digital-analog computation.
Args: n_qubits (int): number of qubits in the circuit. depth (int): number of layers of the HEA. param_prefix (str): the base name of the variational parameters support (tuple): qubit indices where the HEA is applied. operations (list): list of operations to cycle through in the digital single-qubit rotations of each layer. entangler (AbstractBlock): Hamiltonian generator for the analog entangling layer. Defaults to global ZZ Hamiltonian. Time parameter is considered variational.
Returns: The step-wise digital-analog Hardware Efficient Ansatz (sDA HEA) circuit. """
# TODO: Add qubit support if entangler is None: entangler = hamiltonian_factory(n_qubits, interaction=Interaction.NN) try: if not block_is_qubit_hamiltonian(entangler): raise ValueError( "Please provide a valid Pauli Hamiltonian generator for digital-analog HEA." ) except NotImplementedError: raise ValueError( "Please provide a valid Pauli Hamiltonian generator for digital-analog HEA." )
rot_list = _rotations_digital( n_qubits=n_qubits, depth=depth, param_prefix=param_prefix, support=support, operations=operations, )
ent_list = _entanglers_analog( depth=depth, param_prefix=param_prefix, entangler=entangler, )
layers = [] for d in range(depth): layers.append(rot_list[d]) layers.append(ent_list[d]) return tag(chain(*layers), "HEA-sDA")
identity_initialized_ansatz(n_qubits, depth=1, param_prefix='iia', strategy=Strategy.DIGITAL, rotations=[RX, RY], entangler=None, periodic=False)
Section titled “
identity_initialized_ansatz(n_qubits, depth=1, param_prefix='iia', strategy=Strategy.DIGITAL, rotations=[RX, RY], entangler=None, periodic=False)
”Identity block for barren plateau mitigation.
The initial configuration of this block is equal to an identity unitary but can be trained in the same fashion as other ansatzes, reaching same level of expressivity.
| PARAMETER | DESCRIPTION |
|---|---|
n_qubits
|
number of qubits in the block
TYPE:
|
depth
|
number of layers of the HEA
TYPE:
|
param_prefix
|
The base name of the variational parameter. Defaults to "iia".
TYPE:
|
strategy
|
(Strategy) Strategy.DIGITAL for fully digital or Strategy.SDAQC for digital-analog. |
rotations
|
single-qubit rotations with trainable parameters |
entangler
|
For Digital: 2-qubit entangling operation. Supports CNOT, CZ, CRX, CRY, CRZ, CPHASE. Controlled rotations will have variational parameters on the rotation angles. Defaults to CNOT. For Digital-analog: Hamiltonian generator for the analog entangling layer. Time parameter is considered variational. Defaults to a global NN Hamiltonain.
TYPE:
|
periodic
|
if the qubits should be linked periodically. Valid only for digital.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
AbstractBlock
|
The identity initialized ansatz circuit. |
Source code in qadence/constructors/iia.py
99100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242def identity_initialized_ansatz( n_qubits: int, depth: int = 1, param_prefix: str = "iia", strategy: Strategy = Strategy.DIGITAL, rotations: Any = [RX, RY], entangler: Any = None, periodic: bool = False,) -> AbstractBlock: """ Identity block for barren plateau mitigation.
The initial configuration of this block is equal to an identity unitary but can be trained in the same fashion as other ansatzes, reaching same level of expressivity.
Args: n_qubits: number of qubits in the block depth: number of layers of the HEA param_prefix (str): The base name of the variational parameter. Defaults to "iia". strategy: (Strategy) Strategy.DIGITAL for fully digital or Strategy.SDAQC for digital-analog. rotations (list of AbstractBlocks): single-qubit rotations with trainable parameters entangler (AbstractBlock): For Digital: 2-qubit entangling operation. Supports CNOT, CZ, CRX, CRY, CRZ, CPHASE. Controlled rotations will have variational parameters on the rotation angles. Defaults to CNOT. For Digital-analog: Hamiltonian generator for the analog entangling layer. Time parameter is considered variational. Defaults to a global NN Hamiltonain. periodic (bool): if the qubits should be linked periodically. Valid only for digital.
Returns: The identity initialized ansatz circuit. """ initialized_layers = [] for layer in range(depth): alpha = 2 * PI * torch.rand(n_qubits * len(rotations)) gamma = torch.zeros(n_qubits) beta = -alpha
left_rotations = _rotations( n_qubits=n_qubits, layer=layer, side="left", param_str=f"{param_prefix}_α", values=alpha, ops=rotations, )
if strategy == Strategy.DIGITAL: if entangler is None: entangler = CNOT
if entangler not in [CNOT, CZ, CRZ, CRY, CRX, CPHASE]: raise ValueError( "Please provide a valid two-qubit entangler operation for digital IIA." )
ent_param_prefix = f"{param_prefix}_θ_ent_" if not periodic: left_entanglers = [ chain( _entangler( control=n, target=n + 1, param_str=ent_param_prefix + f"_{layer}{n}", entangler=entangler, ) for n in range(n_qubits - 1) ) ] else: left_entanglers = [ chain( _entangler( control=n, target=(n + 1) % n_qubits, param_str=ent_param_prefix + f"_{layer}{n}", entangler=entangler, ) for n in range(n_qubits) ) ]
elif strategy == Strategy.SDAQC: if entangler is None: entangler = hamiltonian_factory(n_qubits, interaction=Interaction.NN)
if not block_is_qubit_hamiltonian(entangler): raise ValueError( "Please provide a valid Pauli Hamiltonian generator for digital-analog IIA." )
ent_param_prefix = f"{param_prefix}_ent_t"
left_entanglers = [ chain( _entangler_analog( param_str=f"{ent_param_prefix}_{layer}", generator=entangler, ) ) ]
else: raise NotImplementedError
centre_rotations = [ kron( RX( target=n, parameter=Parameter(name=f"{param_prefix}_γ" + f"_{layer}{n}", value=gamma[n]), ) for n in range(n_qubits) ) ]
right_entanglers = reversed(*left_entanglers)
right_rotations = _rotations( n_qubits=n_qubits, layer=layer, side="right", param_str=f"{param_prefix}_β", values=beta, ops=rotations, )
krons = [ *left_rotations, *left_entanglers, *centre_rotations, *right_entanglers, *right_rotations, ]
initialized_layers.append(tag(chain(*krons), tag=f"BPMA-{layer}"))
return chain(*initialized_layers)
ala(n_qubits, m_block_qubits, depth=1, param_prefix='theta', support=None, strategy=Strategy.DIGITAL, **strategy_args)
Section titled “
ala(n_qubits, m_block_qubits, depth=1, param_prefix='theta', support=None, strategy=Strategy.DIGITAL, **strategy_args)
”Factory function for the alternating layer ansatz (ala).
| PARAMETER | DESCRIPTION |
|---|---|
n_qubits
|
number of qubits in the circuit
TYPE:
|
m_block_qubits
|
number of qubits in the local entangling block
TYPE:
|
depth
|
number of layers of the alternating layer ansatz
TYPE:
|
param_prefix
|
the base name of the variational parameters
TYPE:
|
support
|
qubit indices where the ala is applied
TYPE:
|
strategy
|
Strategy for the ansatz. One of the Strategy variants. |
**strategy_args
|
see below
TYPE:
|
| PARAMETER | DESCRIPTION |
|---|---|
operations |
list of operations to cycle through in the digital single-qubit rotations of each layer. Valid for Digital .
TYPE:
|
entangler |
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
AbstractBlock
|
The Alternating Layer Ansatz (ALA) circuit. |
Source code in qadence/constructors/ala.py
1314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374def ala( n_qubits: int, m_block_qubits: int, depth: int = 1, param_prefix: str = "theta", support: tuple[int, ...] | None = None, strategy: Strategy = Strategy.DIGITAL, **strategy_args: Any,) -> AbstractBlock: """ Factory function for the alternating layer ansatz (ala).
Args: n_qubits: number of qubits in the circuit m_block_qubits: number of qubits in the local entangling block depth: number of layers of the alternating layer ansatz param_prefix: the base name of the variational parameters support: qubit indices where the ala is applied strategy: Strategy for the ansatz. One of the Strategy variants. **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. - SDAQC | BDAQC: Hamiltonian generator for the analog entangling layer. Must be an m-qubit operator where m is the size of the local entangling block. Defaults to a ZZ interaction.
Returns: The Alternating Layer Ansatz (ALA) circuit. """
if support is None: support = tuple(range(n_qubits))
ala_func_dict = { Strategy.DIGITAL: ala_digital, Strategy.SDAQC: ala_sDAQC, Strategy.BDAQC: ala_bDAQC, Strategy.ANALOG: ala_analog, }
try: ala_func = ala_func_dict[strategy] except KeyError: raise KeyError(f"Strategy {strategy} not recognized.")
ala_block: AbstractBlock = ala_func( n_qubits=n_qubits, m_block_qubits=m_block_qubits, depth=depth, param_prefix=param_prefix, support=support, **strategy_args, ) # type: ignore
return ala_block
ala_digital(n_qubits, m_block_qubits, depth=1, param_prefix='theta', support=None, operations=[RX, RY], entangler=CNOT)
Section titled “
ala_digital(n_qubits, m_block_qubits, depth=1, param_prefix='theta', support=None, operations=[RX, RY], entangler=CNOT)
”Construct the digital alternating layer ansatz (ALA).
| PARAMETER | DESCRIPTION |
|---|---|
n_qubits
|
number of qubits in the circuit.
TYPE:
|
m_block_qubits
|
number of qubits in the local entangling block.
TYPE:
|
depth
|
number of layers of the ALA.
TYPE:
|
param_prefix
|
the base name of the variational parameters
TYPE:
|
support
|
qubit indices where the ALA is applied.
TYPE:
|
operations
|
list of operations to cycle through in the digital single-qubit rotations of each layer. |
entangler
|
2-qubit entangling operation. Supports CNOT, CZ, CRX, CRY, CRZ. Controlld rotations will have variational parameters on the rotation angles.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
AbstractBlock
|
The digital Alternating Layer Ansatz (ALA) circuit. |
Source code in qadence/constructors/ala.py
191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249def ala_digital( n_qubits: int, m_block_qubits: int, depth: int = 1, param_prefix: str = "theta", support: tuple[int, ...] | None = None, operations: list[type[AbstractBlock]] = [RX, RY], entangler: Type[DigitalEntanglers] = CNOT,) -> AbstractBlock: """ Construct the digital alternating layer ansatz (ALA).
Args: n_qubits (int): number of qubits in the circuit. m_block_qubits (int): number of qubits in the local entangling block. depth (int): number of layers of the ALA. param_prefix (str): the base name of the variational parameters support (tuple): qubit indices where the ALA is applied. operations (list): list of operations to cycle through in the digital single-qubit rotations of each layer. entangler (AbstractBlock): 2-qubit entangling operation. Supports CNOT, CZ, CRX, CRY, CRZ. Controlld rotations will have variational parameters on the rotation angles.
Returns: The digital Alternating Layer Ansatz (ALA) circuit. """
try: if entangler not in [CNOT, CZ, CRX, CRY, CRZ, CPHASE]: raise ValueError( "Please provide a valid two-qubit entangler operation for digital ALA." ) except TypeError: raise ValueError("Please provide a valid two-qubit entangler operation for digital ALA.")
rot_list = _rotations_digital( n_qubits=n_qubits, depth=depth, support=support, param_prefix=param_prefix, operations=operations, )
ent_list = _entanglers_ala_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), "ALA")
ObservableConfig(interaction=None, detuning=None, scale=1.0, shift=0.0, trainable_transform=None, tag=None)
dataclass
Section titled “
ObservableConfig(interaction=None, detuning=None, scale=1.0, shift=0.0, trainable_transform=None, tag=None)
dataclass
”ObservableConfig is a configuration class for defining the parameters of an observable Hamiltonian.
detuning = None
class-attribute
instance-attribute
Section titled “
detuning = None
class-attribute
instance-attribute
”Single qubit detuning of the observable Hamiltonian.
Accepts single-qubit operator N, X, Y, or Z.
interaction = None
class-attribute
instance-attribute
Section titled “
interaction = None
class-attribute
instance-attribute
”The type of interaction.
Available options from the Interaction enum are
Alternatively, a custom interaction function can be defined. Example:
def custom_int(i: int, j: int): return X(i) @ X(j) + Y(i) @ Y(j)
n_qubits = 2
observable_config = ObservableConfig(interaction=custom_int, scale = 1.0, shift = 0.0) observable = create_observable(register=4, config=observable_config)
scale = 1.0
class-attribute
instance-attribute
Section titled “
scale = 1.0
class-attribute
instance-attribute
”The scale by which to multiply the output of the observable.
shift = 0.0
class-attribute
instance-attribute
Section titled “
shift = 0.0
class-attribute
instance-attribute
”The shift to add to the output of the observable.
tag = None
class-attribute
instance-attribute
Section titled “
tag = None
class-attribute
instance-attribute
”String to indicate the name tag of the observable.
Defaults to None, in which case no tag will be applied.
trainable_transform = None
class-attribute
instance-attribute
Section titled “
trainable_transform = None
class-attribute
instance-attribute
”Whether to have a trainable transformation on the output of the observable.
If None, the scale and shift are numbers. If True, the scale and shift are VariationalParameter. If False, the scale and shift are FeatureParameter.
hamiltonian_factory(register, interaction=None, detuning=None, interaction_strength=None, detuning_strength=None, random_strength=False, use_all_node_pairs=False)
Section titled “
hamiltonian_factory(register, interaction=None, detuning=None, interaction_strength=None, detuning_strength=None, random_strength=False, use_all_node_pairs=False)
”General Hamiltonian creation function.
Can be used to create Hamiltonians with 2-qubit interactions and single-qubit detunings, both with arbitrary strength or parameterized.
| PARAMETER | DESCRIPTION |
|---|---|
register
|
register of qubits with a specific graph topology, or number of qubits. When passing a number of qubits a register with all-to-all connectivity is created.
TYPE:
|
interaction
|
Interaction.ZZ, Interaction.NN, Interaction.XY, or Interacton.XYZ.
TYPE:
|
detuning
|
single-qubit operator N, X, Y, or Z.
TYPE:
|
interaction_strength
|
list of values to be used as the interaction strength for each
pair of qubits. Should be ordered following the order of
TYPE:
|
detuning_strength
|
list of values to be used as the detuning strength for each qubit.
Alternatively, some string "x" can be passed, which will create a parameterized
detuning for each qubit, each labelled as
TYPE:
|
random_strength
|
set random interaction and detuning strengths between -1 and 1.
TYPE:
|
use_all_node_pairs
|
computes an interaction term for every pair of nodes in the graph, independent of the edge topology in the register. Useful for defining Hamiltonians where the interaction strength decays with the distance.
TYPE:
|
Examples:
from qadence import hamiltonian_factory, Interaction, Register, Z
n_qubits = 3
# Constant total magnetization observable:observable = hamiltonian_factory(n_qubits, detuning = Z)
# Parameterized total magnetization observable:observable = hamiltonian_factory(n_qubits, detuning = Z, detuning_strength = "z")
# Random all-to-all XY Hamiltonian generator:generator = hamiltonian_factory( n_qubits, interaction = Interaction.XY, random_strength = True, )
# Parameterized NN Hamiltonian generator with a square grid interaction topology:register = Register.square(qubits_side = n_qubits)generator = hamiltonian_factory( register, interaction = Interaction.NN, interaction_strength = "theta" )Source code in qadence/constructors/hamiltonians.py
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166def hamiltonian_factory( register: Register | int, interaction: Interaction | Callable | None = None, detuning: TDetuning | None = None, interaction_strength: TArray | str | None = None, detuning_strength: TArray | str | None = None, random_strength: bool = False, use_all_node_pairs: bool = False,) -> AbstractBlock: """ General Hamiltonian creation function.
Can be used to create Hamiltonians with 2-qubit interactions and single-qubit detunings, both with arbitrary strength or parameterized.
Arguments: register: register of qubits with a specific graph topology, or number of qubits. When passing a number of qubits a register with all-to-all connectivity is created. interaction: Interaction.ZZ, Interaction.NN, Interaction.XY, or Interacton.XYZ. detuning: single-qubit operator N, X, Y, or Z. interaction_strength: list of values to be used as the interaction strength for each pair of qubits. Should be ordered following the order of `Register(n_qubits).edges`. Alternatively, some string "x" can be passed, which will create a parameterized interactions for each pair of qubits, each labelled as `"x_ij"`. detuning_strength: list of values to be used as the detuning strength for each qubit. Alternatively, some string "x" can be passed, which will create a parameterized detuning for each qubit, each labelled as `"x_i"`. random_strength: set random interaction and detuning strengths between -1 and 1. use_all_node_pairs: computes an interaction term for every pair of nodes in the graph, independent of the edge topology in the register. Useful for defining Hamiltonians where the interaction strength decays with the distance.
Examples: ```python exec="on" source="material-block" result="json" from qadence import hamiltonian_factory, Interaction, Register, Z
n_qubits = 3
# Constant total magnetization observable: observable = hamiltonian_factory(n_qubits, detuning = Z)
# Parameterized total magnetization observable: observable = hamiltonian_factory(n_qubits, detuning = Z, detuning_strength = "z")
# Random all-to-all XY Hamiltonian generator: generator = hamiltonian_factory( n_qubits, interaction = Interaction.XY, random_strength = True, )
# Parameterized NN Hamiltonian generator with a square grid interaction topology: register = Register.square(qubits_side = n_qubits) generator = hamiltonian_factory( register, interaction = Interaction.NN, interaction_strength = "theta" ) ``` """
if interaction is None and detuning is None: raise ValueError("Please provide an interaction and/or detuning for the Hamiltonian.")
# If number of qubits is given, creates all-to-all register register = Register(register) if isinstance(register, int) else register
# Get interaction function if interaction is not None: if callable(interaction): int_fn = interaction try: if not block_is_qubit_hamiltonian(interaction(0, 1)): raise ValueError("Custom interactions must be composed of Pauli operators.") except TypeError: raise TypeError( "Please use a custom interaction function signed with two integer parameters." ) else: int_fn = INTERACTION_DICT.get(interaction, None) # type: ignore [arg-type] if int_fn is None: raise KeyError(f"Interaction {interaction} not supported.")
# Check single-qubit detuning if (detuning is not None) and (detuning not in DETUNINGS): raise TypeError(f"Detuning of type {type(detuning)} not supported.")
# Pre-process detuning and interaction strengths and update register detuning_strength_array = _preprocess_strengths( register, detuning_strength, "nodes", random_strength )
edge_str = "all_node_pairs" if use_all_node_pairs else "edges" interaction_strength_array = _preprocess_strengths( register, interaction_strength, edge_str, random_strength )
# Create single-qubit detunings: single_qubit_terms: List[AbstractBlock] = [] if detuning is not None: for strength, node in zip(detuning_strength_array, register.nodes): single_qubit_terms.append(strength * detuning(node))
# Create two-qubit interactions: two_qubit_terms: List[AbstractBlock] = [] edge_data = register.all_node_pairs if use_all_node_pairs else register.edges if interaction is not None and int_fn is not None: for strength, edge in zip(interaction_strength_array, edge_data): two_qubit_terms.append(strength * int_fn(*edge))
return add(*single_qubit_terms, *two_qubit_terms)
interaction_nn(i, j)
Section titled “
interaction_nn(i, j)
”Ising NN interaction.
Source code in qadence/constructors/hamiltonians.py
252627def interaction_nn(i: int, j: int) -> AbstractBlock: """Ising NN interaction.""" return N(i) @ N(j)
interaction_xy(i, j)
Section titled “
interaction_xy(i, j)
”XY interaction.
Source code in qadence/constructors/hamiltonians.py
303132def interaction_xy(i: int, j: int) -> AbstractBlock: """XY interaction.""" return X(i) @ X(j) + Y(i) @ Y(j)
interaction_xyz(i, j)
Section titled “
interaction_xyz(i, j)
”Heisenberg XYZ interaction.
Source code in qadence/constructors/hamiltonians.py
353637def interaction_xyz(i: int, j: int) -> AbstractBlock: """Heisenberg XYZ interaction.""" return X(i) @ X(j) + Y(i) @ Y(j) + Z(i) @ Z(j)
interaction_zz(i, j)
Section titled “
interaction_zz(i, j)
”Ising ZZ interaction.
Source code in qadence/constructors/hamiltonians.py
202122def interaction_zz(i: int, j: int) -> AbstractBlock: """Ising ZZ interaction.""" return Z(i) @ Z(j)
qft(n_qubits, support=None, inverse=False, reverse_in=False, swaps_out=False, strategy=Strategy.DIGITAL, gen_build=None)
Section titled “
qft(n_qubits, support=None, inverse=False, reverse_in=False, swaps_out=False, strategy=Strategy.DIGITAL, gen_build=None)
”The Quantum Fourier Transform.
Depending on the application, user should be careful with qubit ordering in the input and output. This can be controlled with reverse_in and swaps_out arguments.
| PARAMETER | DESCRIPTION |
|---|---|
n_qubits
|
number of qubits in the QFT
TYPE:
|
support
|
qubit support to use
TYPE:
|
inverse
|
True performs the inverse QFT
TYPE:
|
reverse_in
|
Reverses the input qubits to account for endianness
TYPE:
|
swaps_out
|
Performs swaps on the output qubits to match the "textbook" QFT.
TYPE:
|
strategy
|
Strategy.Digital or Strategy.sDAQC |
gen_build
|
building block Ising Hamiltonian for the DAQC transform. Defaults to constant all-to-all Ising.
TYPE:
|
Examples:
from qadence import qft
n_qubits = 3
qft_circuit = qft(n_qubits, strategy = "sDAQC")Source code in qadence/constructors/qft.py
15161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384def qft( n_qubits: int, support: tuple[int, ...] = None, inverse: bool = False, reverse_in: bool = False, swaps_out: bool = False, strategy: Strategy = Strategy.DIGITAL, gen_build: AbstractBlock | None = None,) -> AbstractBlock: """ The Quantum Fourier Transform.
Depending on the application, user should be careful with qubit ordering in the input and output. This can be controlled with reverse_in and swaps_out arguments.
Args: n_qubits: number of qubits in the QFT support: qubit support to use inverse: True performs the inverse QFT reverse_in: Reverses the input qubits to account for endianness swaps_out: Performs swaps on the output qubits to match the "textbook" QFT. strategy: Strategy.Digital or Strategy.sDAQC gen_build: building block Ising Hamiltonian for the DAQC transform. Defaults to constant all-to-all Ising.
Examples: ```python exec="on" source="material-block" result="json" from qadence import qft
n_qubits = 3
qft_circuit = qft(n_qubits, strategy = "sDAQC") ``` """
if support is None: support = tuple(range(n_qubits))
assert len(support) <= n_qubits, "Wrong qubit support supplied"
if reverse_in: support = support[::-1]
qft_layer_dict = { Strategy.DIGITAL: _qft_layer_digital, Strategy.SDAQC: _qft_layer_sDAQC, Strategy.BDAQC: _qft_layer_bDAQC, Strategy.ANALOG: _qft_layer_analog, }
try: layer_func = qft_layer_dict[strategy] except KeyError: raise KeyError(f"Strategy {strategy} not recognized.")
qft_layers = reversed(range(n_qubits)) if inverse else range(n_qubits)
qft_circ = chain( layer_func( n_qubits=n_qubits, support=support, layer=layer, inverse=inverse, gen_build=gen_build ) # type: ignore for layer in qft_layers )
if swaps_out: swap_ops = [SWAP(support[i], support[n_qubits - i - 1]) for i in range(n_qubits // 2)] qft_circ = chain(*swap_ops, qft_circ) if inverse else chain(qft_circ, *swap_ops)
return tag(qft_circ, tag="iQFT") if inverse else tag(qft_circ, tag="QFT")Hardware efficient ansatz for Rydberg atom arrays
Section titled “Hardware efficient ansatz for Rydberg atom arrays”
rydberg_hea(register, n_layers=1, addressable_detuning=True, addressable_drive=False, tunable_phase=False, additional_prefix=None)
Section titled “
rydberg_hea(register, n_layers=1, addressable_detuning=True, addressable_drive=False, tunable_phase=False, additional_prefix=None)
”Hardware efficient ansatz for neutral atom (Rydberg) platforms.
This constructor implements a variational ansatz which is very close to what is implementable on 2nd generation PASQAL quantum devices. In particular, it implements evolution over a specific Hamiltonian which can be realized on the device. This Hamiltonian contains:
-
an interaction term given by the standard NN interaction and determined starting from the positions in the input register: Hᵢₙₜ = ∑ᵢⱼ C₆/rᵢⱼ⁶ nᵢnⱼ
-
a detuning term which corresponding to a n_i = (1+sigma_i^z)/2 applied to all the qubits. If the
addressable_detuningflag is set to True, the routine effectively a local n_i = (1+sigma_i^z)/2 term in the evolved Hamiltonian with a different coefficient for each atom. These coefficients determine a local addressing pattern for the detuning on a subset of the qubits. In this routine, the coefficients are variational parameters and they will therefore be optimized at each optimizer step -
a drive term which corresponding to a sigma^x evolution operation applied to all the qubits. If the
addressable_driveflag is set to True, the routine effectively a local sigma_i^x term in the evolved Hamiltonian with a different coefficient for each atom. These coefficients determine a local addressing pattern for the drive on a subset of the qubits. In this routine, the coefficients are variational parameters and they will therefore be optimized at each optimizer step -
if the
tunable_phaseflag is set to True, the drive term is modified in the following way: drive = cos(phi) * sigma^x - sin(phi) * sigma^y The addressable pattern above is maintained and the phase is considered just as an additional variational parameter which is optimized with the rest
Notice that, on real devices, the coefficients assigned to each qubit in both the detuning and drive patterns should be non-negative and they should always sum to 1. This is not the case for the implementation in this routine since the coefficients (weights) do not have any constraint. Therefore, this HEA is not completely realizable on neutral atom devices.
| PARAMETER | DESCRIPTION |
|---|---|
register
|
the input atomic register with Cartesian coordinates.
TYPE:
|
n_layers
|
number layers in the HEA, each layer includes a drive, detuning and pure interaction pulses whose is a variational parameter
TYPE:
|
addressable_detuning
|
whether to turn on the trainable semi-local addressing pattern on the detuning (n_i terms in the Hamiltonian)
TYPE:
|
addressable_drive
|
whether to turn on the trainable semi-local addressing pattern on the drive (sigma_i^x terms in the Hamiltonian)
TYPE:
|
tunable_phase
|
whether to have a tunable phase to get both sigma^x and sigma^y rotations in the drive term. If False, only a sigma^x term will be included in the drive part of the Hamiltonian generator
TYPE:
|
additional_prefix
|
an additional prefix to attach to the parameter names
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
ChainBlock
|
The Rydberg HEA block |
Source code in qadence/constructors/rydberg_hea.py
104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191def rydberg_hea( register: qd.Register, n_layers: int = 1, addressable_detuning: bool = True, addressable_drive: bool = False, tunable_phase: bool = False, additional_prefix: str = None,) -> qd.blocks.ChainBlock: """Hardware efficient ansatz for neutral atom (Rydberg) platforms.
This constructor implements a variational ansatz which is very close to what is implementable on 2nd generation PASQAL quantum devices. In particular, it implements evolution over a specific Hamiltonian which can be realized on the device. This Hamiltonian contains:
* an interaction term given by the standard NN interaction and determined starting from the positions in the input register: Hᵢₙₜ = ∑ᵢⱼ C₆/rᵢⱼ⁶ nᵢnⱼ
* a detuning term which corresponding to a n_i = (1+sigma_i^z)/2 applied to all the qubits. If the `addressable_detuning` flag is set to True, the routine effectively a local n_i = (1+sigma_i^z)/2 term in the evolved Hamiltonian with a different coefficient for each atom. These coefficients determine a local addressing pattern for the detuning on a subset of the qubits. In this routine, the coefficients are variational parameters and they will therefore be optimized at each optimizer step
* a drive term which corresponding to a sigma^x evolution operation applied to all the qubits. If the `addressable_drive` flag is set to True, the routine effectively a local sigma_i^x term in the evolved Hamiltonian with a different coefficient for each atom. These coefficients determine a local addressing pattern for the drive on a subset of the qubits. In this routine, the coefficients are variational parameters and they will therefore be optimized at each optimizer step
* if the `tunable_phase` flag is set to True, the drive term is modified in the following way: drive = cos(phi) * sigma^x - sin(phi) * sigma^y The addressable pattern above is maintained and the phase is considered just as an additional variational parameter which is optimized with the rest
Notice that, on real devices, the coefficients assigned to each qubit in both the detuning and drive patterns should be non-negative and they should always sum to 1. This is not the case for the implementation in this routine since the coefficients (weights) do not have any constraint. Therefore, this HEA is not completely realizable on neutral atom devices.
Args: register: the input atomic register with Cartesian coordinates. n_layers: number layers in the HEA, each layer includes a drive, detuning and pure interaction pulses whose is a variational parameter addressable_detuning: whether to turn on the trainable semi-local addressing pattern on the detuning (n_i terms in the Hamiltonian) addressable_drive: whether to turn on the trainable semi-local addressing pattern on the drive (sigma_i^x terms in the Hamiltonian) tunable_phase: whether to have a tunable phase to get both sigma^x and sigma^y rotations in the drive term. If False, only a sigma^x term will be included in the drive part of the Hamiltonian generator additional_prefix: an additional prefix to attach to the parameter names
Returns: The Rydberg HEA block """ n_qubits = register.n_qubits prefix = "" if additional_prefix is None else "_" + additional_prefix
detunings = None # add a detuning pattern locally addressing the atoms if addressable_detuning: detunings = [qd.VariationalParameter(f"detmap_{j}") for j in range(n_qubits)]
drives = None # add a drive pattern locally addressing the atoms if addressable_drive: drives = [qd.VariationalParameter(f"drivemap_{j}") for j in range(n_qubits)]
phase = None if tunable_phase: phase = qd.VariationalParameter("phase")
return chain( rydberg_hea_layer( register, VariationalParameter(f"At{prefix}_{layer}"), VariationalParameter(f"Omega{prefix}_{layer}"), VariationalParameter(f"wait{prefix}_{layer}"), detunings=detunings, drives=drives, phase=phase, ) for layer in range(n_layers) )
rydberg_hea_layer(register, tevo_drive, tevo_det, tevo_wait, phase=None, detunings=None, drives=None, drive_scaling=1.0)
Section titled “
rydberg_hea_layer(register, tevo_drive, tevo_det, tevo_wait, phase=None, detunings=None, drives=None, drive_scaling=1.0)
”A single layer of the Rydberg hardware efficient ansatz.
| PARAMETER | DESCRIPTION |
|---|---|
register
|
the input register with atomic coordinates needed to build the interaction.
TYPE:
|
tevo_drive
|
a variational parameter for the duration of the drive term of the Hamiltonian generator, including optional semi-local addressing
TYPE:
|
tevo_det
|
a variational parameter for the duration of the detuning term of the Hamiltonian generator, including optional semi-local addressing
TYPE:
|
tevo_wait
|
a variational parameter for the duration of the waiting time with interaction only
TYPE:
|
phase
|
a variational parameter representing the global phase. If None, the global phase is set to 0 which results in a drive term in sigma^x only. Otherwise both sigma^x and sigma^y terms will be present
TYPE:
|
detunings
|
a list of parameters with the weights of the locally addressed detuning terms. These are variational parameters which are tuned by the optimizer
TYPE:
|
drives
|
a list of parameters with the weights of the locally addressed drive terms. These are variational parameters which are tuned by the optimizer
TYPE:
|
drive_scaling
|
a scaling term to be added to the drive Hamiltonian generator
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
ChainBlock
|
A block with a single layer of Rydberg HEA |
Source code in qadence/constructors/rydberg_hea.py
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99100101def rydberg_hea_layer( register: qd.Register, tevo_drive: Parameter | float, tevo_det: Parameter | float, tevo_wait: Parameter | float, phase: Parameter | float | None = None, detunings: list[Parameter] | list[float] | None = None, drives: list[Parameter] | list[float] | None = None, drive_scaling: float = 1.0,) -> ChainBlock: """A single layer of the Rydberg hardware efficient ansatz.
Args: register: the input register with atomic coordinates needed to build the interaction. tevo_drive: a variational parameter for the duration of the drive term of the Hamiltonian generator, including optional semi-local addressing tevo_det: a variational parameter for the duration of the detuning term of the Hamiltonian generator, including optional semi-local addressing tevo_wait: a variational parameter for the duration of the waiting time with interaction only phase: a variational parameter representing the global phase. If None, the global phase is set to 0 which results in a drive term in sigma^x only. Otherwise both sigma^x and sigma^y terms will be present detunings: a list of parameters with the weights of the locally addressed detuning terms. These are variational parameters which are tuned by the optimizer drives: a list of parameters with the weights of the locally addressed drive terms. These are variational parameters which are tuned by the optimizer drive_scaling: a scaling term to be added to the drive Hamiltonian generator
Returns: A block with a single layer of Rydberg HEA """ n_qubits = register.n_qubits
drive_x = _amplitude_map(n_qubits, qd.X, weights=drives) drive_y = _amplitude_map(n_qubits, qd.Y, weights=drives) detuning = _amplitude_map(n_qubits, qd.N, weights=detunings) interaction = hamiltonian_factory(register, qd.Interaction.NN)
# drive and interaction are not commuting thus they need to be # added directly into the final Hamiltonian generator if phase is not None: generator = ( drive_scaling * sympy.cos(phase) * drive_x - drive_scaling * sympy.sin(phase) * drive_y + interaction ) else: generator = drive_scaling * drive_x + interaction
return chain( qd.HamEvo(generator, tevo_drive), # detuning and interaction are commuting, so they # can be ordered arbitrarily and treated separately qd.HamEvo(interaction, tevo_wait), qd.HamEvo(detuning, tevo_det), )The DAQC Transform
Section titled “The DAQC Transform”
daqc_transform(n_qubits, gen_target, t_f, gen_build=None, zero_tol=1e-08, strategy=Strategy.SDAQC, ignore_global_phases=False)
Section titled “
daqc_transform(n_qubits, gen_target, t_f, gen_build=None, zero_tol=1e-08, strategy=Strategy.SDAQC, ignore_global_phases=False)
”Implements the DAQC transform for representing an arbitrary 2-body Hamiltonian.
The result is another fixed 2-body Hamiltonian.
Reference for universality of 2-body Hamiltonians:
-- https://arxiv.org/abs/quant-ph/0106064
Based on the transformation for Ising (ZZ) interactions, as described in the paper
-- https://arxiv.org/abs/1812.03637
The transform translates a target weighted generator of the type:
`gen_target = add(g_jk * kron(op(j), op(k)) for j < k)`To a circuit using analog evolutions with a fixed building block generator:
`gen_build = add(f_jk * kron(op(j), op(k)) for j < k)`where op = Z or op = N.
| PARAMETER | DESCRIPTION |
|---|---|
n_qubits
|
total number of qubits to use.
TYPE:
|
gen_target
|
target generator built with the structure above. The type of the generator will be automatically evaluated when parsing.
TYPE:
|
t_f
|
total time for the gen_target evolution.
TYPE:
|
gen_build
|
fixed generator to act as a building block. Defaults to constant NN: add(1.0 * kron(N(j), N(k)) for j < k). The type of the generator will be automatically evaluated when parsing.
TYPE:
|
zero_tol
|
default "zero" for a missing interaction. Included for numerical reasons, see notes below.
TYPE:
|
strategy
|
sDAQC or bDAQC, following definitions in the reference paper. |
ignore_global_phases
|
if
TYPE:
|
Notes:
The paper follows an index convention of running from 1 to N. A few functionshere also use that convention to be consistent with the paper. However, for qadencerelated things the indices are converted to [0, N-1].
The case for `n_qubits = 4` is an edge case where the sign matrix is not invertible.There is a workaround for this described in the paper, but it is currently not implemented.
The current implementation may result in evolution times that are both positive ornegative. In practice, both can be represented by simply changing the signs of theinteractions. However, for a real implementation where the interactions should remainfixed, the paper discusses a workaround that is not currently implemented.
The transformation works by representing each interaction in the target hamiltonian bya set of evolutions using the build hamiltonian. As a consequence, some care must betaken when choosing the build hamiltonian. Some cases:
- The target hamiltonian can have any interaction, as long as it is sufficientlyrepresented in the build hamiltonian. E.g., if the interaction `g_01 * kron(Z(0), Z(1))`is in the target hamiltonian, the corresponding interaction `f_01 * kron(Z(0), Z(1))`needs to be in the build hamiltonian. This is checked when the generators are parsed.
- The build hamiltonian can have any interaction, irrespectively of it being neededfor the target hamiltonian. This is especially useful for designing local operationsthrough the repeated evolution of a "global" hamiltonian.
- The parameter `zero_tol` controls what it means for an interaction to be "missing".Any interaction strength smaller than `zero_tol` in the build hamiltonian will not beconsidered, and thus that interaction is missing.
- The various ratios `g_jk / f_jk` will influence the time parameter for the variousevolution slices, meaning that if there is a big discrepancy in the interaction strengthfor a given qubit pair (j, k), the output circuit may require the usage of hamiltonianevolutions with very large times.
- A warning will be issued for evolution times larger than `1/sqrt(zero_tol)`. Evolutiontimes smaller than `zero_tol` will not be represented.Examples:
from qadence import Z, N, daqc_transform
n_qubits = 3
gen_build = 0.5 * (N(0)@N(1)) + 0.7 * (N(1)@N(2)) + 0.2 * (N(0)@N(2))
gen_target = 0.1 * (Z(1)@Z(2))
t_f = 2.0
transformed_circuit = daqc_transform( n_qubits = n_qubits, gen_target = gen_target, t_f = t_f, gen_build = gen_build,)Source code in qadence/constructors/daqc/daqc.py
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234def daqc_transform( n_qubits: int, gen_target: AbstractBlock, t_f: float, gen_build: AbstractBlock | None = None, zero_tol: float = 1e-08, strategy: Strategy = Strategy.SDAQC, ignore_global_phases: bool = False,) -> AbstractBlock: """ Implements the DAQC transform for representing an arbitrary 2-body Hamiltonian.
The result is another fixed 2-body Hamiltonian.
Reference for universality of 2-body Hamiltonians:
-- https://arxiv.org/abs/quant-ph/0106064
Based on the transformation for Ising (ZZ) interactions, as described in the paper
-- https://arxiv.org/abs/1812.03637
The transform translates a target weighted generator of the type:
`gen_target = add(g_jk * kron(op(j), op(k)) for j < k)`
To a circuit using analog evolutions with a fixed building block generator:
`gen_build = add(f_jk * kron(op(j), op(k)) for j < k)`
where `op = Z` or `op = N`.
Args: n_qubits: total number of qubits to use. gen_target: target generator built with the structure above. The type of the generator will be automatically evaluated when parsing. t_f (float): total time for the gen_target evolution. gen_build: fixed generator to act as a building block. Defaults to constant NN: add(1.0 * kron(N(j), N(k)) for j < k). The type of the generator will be automatically evaluated when parsing. zero_tol: default "zero" for a missing interaction. Included for numerical reasons, see notes below. strategy: sDAQC or bDAQC, following definitions in the reference paper. ignore_global_phases: if `True` the transform does not correct the global phases coming from the mapping between ZZ and NN interactions.
Notes:
The paper follows an index convention of running from 1 to N. A few functions here also use that convention to be consistent with the paper. However, for qadence related things the indices are converted to [0, N-1].
The case for `n_qubits = 4` is an edge case where the sign matrix is not invertible. There is a workaround for this described in the paper, but it is currently not implemented.
The current implementation may result in evolution times that are both positive or negative. In practice, both can be represented by simply changing the signs of the interactions. However, for a real implementation where the interactions should remain fixed, the paper discusses a workaround that is not currently implemented.
The transformation works by representing each interaction in the target hamiltonian by a set of evolutions using the build hamiltonian. As a consequence, some care must be taken when choosing the build hamiltonian. Some cases:
- The target hamiltonian can have any interaction, as long as it is sufficiently represented in the build hamiltonian. E.g., if the interaction `g_01 * kron(Z(0), Z(1))` is in the target hamiltonian, the corresponding interaction `f_01 * kron(Z(0), Z(1))` needs to be in the build hamiltonian. This is checked when the generators are parsed.
- The build hamiltonian can have any interaction, irrespectively of it being needed for the target hamiltonian. This is especially useful for designing local operations through the repeated evolution of a "global" hamiltonian.
- The parameter `zero_tol` controls what it means for an interaction to be "missing". Any interaction strength smaller than `zero_tol` in the build hamiltonian will not be considered, and thus that interaction is missing.
- The various ratios `g_jk / f_jk` will influence the time parameter for the various evolution slices, meaning that if there is a big discrepancy in the interaction strength for a given qubit pair (j, k), the output circuit may require the usage of hamiltonian evolutions with very large times.
- A warning will be issued for evolution times larger than `1/sqrt(zero_tol)`. Evolution times smaller than `zero_tol` will not be represented.
Examples: ```python exec="on" source="material-block" result="json" from qadence import Z, N, daqc_transform
n_qubits = 3
gen_build = 0.5 * (N(0)@N(1)) + 0.7 * (N(1)@N(2)) + 0.2 * (N(0)@N(2))
gen_target = 0.1 * (Z(1)@Z(2))
t_f = 2.0
transformed_circuit = daqc_transform( n_qubits = n_qubits, gen_target = gen_target, t_f = t_f, gen_build = gen_build, ) ``` """
################## # Input controls # ##################
if strategy != Strategy.SDAQC: raise NotImplementedError("Currently only the sDAQC transform is implemented.")
if n_qubits == 4: raise NotImplementedError("DAQC transform 4-qubit edge case not implemented.")
if gen_build is None: gen_build = hamiltonian_factory(n_qubits, interaction=Interaction.NN)
try: if (not block_is_qubit_hamiltonian(gen_target)) or ( not block_is_qubit_hamiltonian(gen_build) ): raise ValueError( "Generator block is not a qubit Hamiltonian. Only ZZ or NN interactions allowed." ) except NotImplementedError: # Happens when block_is_qubit_hamiltonian is called on something that is not a block. raise TypeError( "Generator block is not a qubit Hamiltonian. Only ZZ or NN interactions allowed." )
##################### # Generator parsing # #####################
g_jk_target, mat_jk_target, target_type = _parse_generator(n_qubits, gen_target, 0.0) g_jk_build, mat_jk_build, build_type = _parse_generator(n_qubits, gen_build, zero_tol)
# Get the global phase hamiltonian and single-qubit detuning hamiltonian if build_type == GenDAQC.NN: h_phase_build, h_sq_build = _nn_phase_and_detunings(n_qubits, mat_jk_build)
if target_type == GenDAQC.NN: h_phase_target, h_sq_target = _nn_phase_and_detunings(n_qubits, mat_jk_target)
# Time re-scalings if build_type == GenDAQC.ZZ and target_type == GenDAQC.NN: t_star = t_f / 4.0 elif build_type == GenDAQC.NN and target_type == GenDAQC.ZZ: t_star = 4.0 * t_f else: t_star = t_f
# Check if target Hamiltonian can be mapped with the build Hamiltonian assert _check_compatibility(g_jk_target, g_jk_build, zero_tol)
################## # DAQC Transform # ##################
# Section III A of https://arxiv.org/abs/1812.03637:
# Matrix M for the linear system, exemplified in Table I: matrix_M = _build_matrix_M(n_qubits)
# Linear system mapping interaction ratios -> evolution times. t_slices = torch.linalg.solve(matrix_M, g_jk_target / g_jk_build) * t_star
# ZZ-DAQC with ZZ or NN build Hamiltonian daqc_slices = [] for m in range(2, n_qubits + 1): for n in range(1, m): alpha = _ix_map(n_qubits, n, m) t = t_slices[alpha - 1] if abs(t) > zero_tol: if abs(t) > (1 / (zero_tol**0.5)): logger.warning( """Transformed circuit with very long evolution time.Make sure your target interactions are sufficientlyrepresented in the build Hamiltonian.""" ) x_gates = kron(X(n - 1), X(m - 1)) analog_evo = HamEvo(gen_build, t) # TODO: Fix repeated X-gates if build_type == GenDAQC.NN: # Local detuning at each DAQC layer for NN build Hamiltonian sq_detuning_build = HamEvo(h_sq_build, t) daqc_slices.append(chain(x_gates, sq_detuning_build, analog_evo, x_gates)) elif build_type == GenDAQC.ZZ: daqc_slices.append(chain(x_gates, analog_evo, x_gates))
daqc_circuit = chain(*daqc_slices)
######################## # Phases and Detunings # ########################
if target_type == GenDAQC.NN: # Local detuning given a NN target Hamiltonian sq_detuning_target = HamEvo(h_sq_target, t_f).dagger() daqc_circuit = chain(sq_detuning_target, daqc_circuit)
if not ignore_global_phases: if build_type == GenDAQC.NN: # Constant global phase given a NN build Hamiltonian global_phase_build = HamEvo(h_phase_build, t_slices.sum()) daqc_circuit = chain(global_phase_build, daqc_circuit)
if target_type == GenDAQC.NN: # Constant global phase and given a NN target Hamiltonian global_phase_target = HamEvo(h_phase_target, t_f).dagger() daqc_circuit = chain(global_phase_target, daqc_circuit)
return daqc_circuitSome utility functions
Section titled “Some utility functions”
build_idx_fms(basis, fm_pauli, multivariate_strategy, n_features, n_qubits, reupload_scaling)
Section titled “
build_idx_fms(basis, fm_pauli, multivariate_strategy, n_features, n_qubits, reupload_scaling)
”Builds the index feature maps based on the given parameters.
| PARAMETER | DESCRIPTION |
|---|---|
basis
|
Type of basis chosen for the feature map.
TYPE:
|
fm_pauli
|
The chosen Pauli rotation type.
TYPE:
|
multivariate_strategy
|
The strategy used for encoding the multivariate feature map.
TYPE:
|
n_features
|
The number of features.
TYPE:
|
n_qubits
|
The number of qubits.
TYPE:
|
reupload_scaling
|
The chosen scaling for the reupload.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
list[KronBlock]
|
List[KronBlock]: The list of index feature maps. |
Source code in qadence/constructors/utils.py
3435363738394041424344454647484950515253545556575859606162636465666768def build_idx_fms( basis: BasisSet, fm_pauli: Type[RY], multivariate_strategy: MultivariateStrategy, n_features: int, n_qubits: int, reupload_scaling: ReuploadScaling,) -> list[KronBlock]: """Builds the index feature maps based on the given parameters.
Args: basis (BasisSet): Type of basis chosen for the feature map. fm_pauli (PrimitiveBlock type): The chosen Pauli rotation type. multivariate_strategy (MultivariateStrategy): The strategy used for encoding the multivariate feature map. n_features (int): The number of features. n_qubits (int): The number of qubits. reupload_scaling (ReuploadScaling): The chosen scaling for the reupload.
Returns: List[KronBlock]: The list of index feature maps. """ idx_fms = [] for i in range(n_features): target_qubits = get_fm_qubits(multivariate_strategy, i, n_qubits, n_features) param = FeatureParameter(f"x{i}") block = kron( *[ fm_pauli(qubit, generator_prefactor(reupload_scaling, j) * basis_func(basis, param)) for j, qubit in enumerate(target_qubits) ] ) idx_fm = block idx_fms.append(idx_fm) return idx_fms
generator_prefactor(reupload_scaling, qubit_index)
Section titled “
generator_prefactor(reupload_scaling, qubit_index)
”Converts a spectrum string, e.g. tower or exponential.
The result is the correct generator prefactor.
Source code in qadence/constructors/utils.py
1314151617181920212223def generator_prefactor(reupload_scaling: ReuploadScaling, qubit_index: int) -> float | int: """Converts a spectrum string, e.g. tower or exponential.
The result is the correct generator prefactor. """ conversion_dict: dict[str, float | int] = { ReuploadScaling.CONSTANT: 1, ReuploadScaling.TOWER: qubit_index + 1, ReuploadScaling.EXP: 2 * PI / (2 ** (qubit_index + 1)), } return conversion_dict[reupload_scaling]
get_fm_qubits(multivariate_strategy, i, n_qubits, n_features)
Section titled “
get_fm_qubits(multivariate_strategy, i, n_qubits, n_features)
”Returns the list of target qubits for the given feature map strategy and feature index.
| PARAMETER | DESCRIPTION |
|---|---|
multivariate_strategy
|
The strategy used for encoding the multivariate feature map.
TYPE:
|
i
|
The feature index.
TYPE:
|
n_qubits
|
The number of qubits.
TYPE:
|
n_features
|
The number of features.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Iterable
|
List[int]: The list of target qubits. |
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If the feature map strategy is not implemented. |
Source code in qadence/constructors/utils.py
7172737475767778798081828384858687888990919293949596def get_fm_qubits( multivariate_strategy: MultivariateStrategy, i: int, n_qubits: int, n_features: int) -> Iterable: """Returns the list of target qubits for the given feature map strategy and feature index.
Args: multivariate_strategy (MultivariateStrategy): The strategy used for encoding the multivariate feature map. i (int): The feature index. n_qubits (int): The number of qubits. n_features (int): The number of features.
Returns: List[int]: The list of target qubits.
Raises: ValueError: If the feature map strategy is not implemented. """ if multivariate_strategy == MultivariateStrategy.PARALLEL: n_qubits_per_feature = int(n_qubits / n_features) target_qubits = range(i * n_qubits_per_feature, (i + 1) * n_qubits_per_feature) elif multivariate_strategy == MultivariateStrategy.SERIES: target_qubits = range(0, n_qubits) else: raise ValueError(f"Multivariate strategy {multivariate_strategy} not implemented.") return target_qubits