Source code for saoovqe.ansatz

"""API to enable fast creation of ansatzes.

Module containing the class representing an ansatz for SA-OO-VQE method, where all the inner
coefficients are either 1 or -1.
"""

from enum import Enum, auto
from typing import Tuple
from qiskit.circuit import ParameterExpression
from qiskit.circuit.library import TwoLocal, NLocal
from qiskit_nature.second_q.circuit.library import UCC
from qiskit_nature.second_q.mappers.fermionic_mapper import FermionicMapper
from sympy import simplify

from .vqe_optimization import ProblemSet
from .logger_config import log


[docs] class NoValueEnum(Enum): """ Utility class for printing settings of AnsatzType elements. """ def __repr__(self): return f"<{self.__class__.__name__}.{self.name}>"
[docs] class AnsatzType(NoValueEnum): """ Enumerator representing the set of available ansatz structures. """ TWO_LOCAL = auto() UCCS = auto() UCCD = auto() GUCCD = auto() UCCSD = auto() GUCCSD = auto()
[docs] class Ansatz(NLocal): """ Ansatz prepared for SA-OO-VQE with all inner coefficients set to either 1 or -1. """ def __new__( cls, ansatz: AnsatzType, n_spatial_orbs: int, n_qubits: int, n_particle: Tuple[int, int], repetitions: int, rotation_blocks="ry", entanglement_blocks="cx", entanglement="linear", fermionic_mapper: FermionicMapper = None ): match ansatz: case AnsatzType.TWO_LOCAL: circuit = TwoLocal( num_qubits=n_qubits, rotation_blocks=rotation_blocks, entanglement_blocks=entanglement_blocks, entanglement=entanglement, reps=repetitions, skip_unentangled_qubits=False, skip_final_rotation_layer=False, insert_barriers=True, initial_state=None, ) case AnsatzType.UCCS: circuit = UCC( num_spatial_orbitals=n_spatial_orbs, qubit_mapper=fermionic_mapper, num_particles=n_particle, excitations="s", alpha_spin=True, beta_spin=True, max_spin_excitation=None, generalized=False, preserve_spin=True, reps=repetitions, initial_state=None, ) case AnsatzType.UCCD: circuit = UCC( num_spatial_orbitals=n_spatial_orbs, qubit_mapper=fermionic_mapper, num_particles=n_particle, excitations="d", alpha_spin=True, beta_spin=True, max_spin_excitation=None, generalized=False, preserve_spin=True, reps=repetitions, initial_state=None, ) case AnsatzType.UCCSD: circuit = UCC( num_spatial_orbitals=n_spatial_orbs, qubit_mapper=fermionic_mapper, num_particles=n_particle, excitations="sd", alpha_spin=True, beta_spin=True, max_spin_excitation=None, generalized=False, preserve_spin=True, reps=repetitions, initial_state=None, ) case AnsatzType.GUCCD: circuit = UCC( num_spatial_orbitals=n_spatial_orbs, qubit_mapper=fermionic_mapper, num_particles=n_particle, excitations="d", alpha_spin=True, beta_spin=True, max_spin_excitation=None, generalized=True, preserve_spin=True, reps=repetitions, initial_state=None, ) case AnsatzType.GUCCSD: circuit = UCC( num_spatial_orbitals=n_spatial_orbs, qubit_mapper=fermionic_mapper, num_particles=n_particle, excitations="sd", alpha_spin=True, beta_spin=True, max_spin_excitation=None, generalized=True, preserve_spin=True, reps=repetitions, initial_state=None, ) case _: raise ValueError( "Provided ansatz name is not supported! It has to be an " "element of AnsatzType." ) # Let's rewrite all gate coefficients to 1 or -1, because of gradients # # First of all we'll decompose our ansatz circuit enough to see the # separate rotation gates circuit = circuit.decompose().decompose().decompose() # Now we'll iterate over all gates and choose the parametrized ones for i, g in enumerate(circuit): params = g.operation.params if params: # Obtain the SymPy symbol (i.e. independent variable w.r.t. # which we compute derivatives) symbol = list(params[0]._parameter_symbols.values())[0] # Rewrite the SymPy expression to multiplication by 1 or -1 # (maintaining the sign) circuit[i].operation.params[0] = ParameterExpression( params[0]._parameter_symbols, -symbol if simplify(params[0]._symbol_expr).args[0] < 0 else symbol, ) log.info("Ansatz was created.") return circuit
[docs] @classmethod def from_problem_set( cls, ansatz: AnsatzType, problem: ProblemSet, repetitions: int, qubit_mapper: FermionicMapper = None, ): """ Alternative constructor creating Ansatz instance directly from instances of :class:`problem.ProblemSet` class. :param ansatz: Type of ansatz :param problem: :class:`problem.ProblemSetProblemSet` instance :param repetitions: Number of block repetitions :param qubit_mapper: Mapper used to encode operators to qubits :return: An instance of Ansatz class """ return cls( ansatz, problem.as_problem.num_spatial_orbitals, problem.as_operators[0].register_length, problem.as_problem.num_particles, repetitions, fermionic_mapper=qubit_mapper, )