def solve( self, problem: BaseProblem, aux_operators: Optional[List[Union[SecondQuantizedOp, PauliSumOp]]] = None, ) -> EigenstateResult: """Compute Ground and Excited States properties. Args: problem: a class encoding a problem to be solved. aux_operators: Additional auxiliary operators to evaluate. Raises: NotImplementedError: If an operator in ``aux_operators`` is not of type ``FermionicOperator``. Returns: An interpreted :class:`~.EigenstateResult`. For more information see also :meth:`~.BaseProblem.interpret`. """ # get the operator and auxiliary operators, and transform the provided auxiliary operators # note that ``aux_operators`` contains not only the transformed ``aux_operators`` passed # by the user but also additional ones from the transformation second_q_ops = problem.second_q_ops() main_operator = self._qubit_converter.convert( second_q_ops[0], num_particles=problem.num_particles, sector_locator=problem.symmetry_sector_locator, ) aux_ops = self._qubit_converter.convert_match(second_q_ops[1:]) if aux_operators is not None: for aux_op in aux_operators: if isinstance(aux_op, SecondQuantizedOp): aux_ops.append( self._qubit_converter.convert_match(aux_op, True)) else: aux_ops.append(aux_op) if isinstance(self._solver, EigensolverFactory): # this must be called after transformation.transform solver = self._solver.get_solver(problem) else: solver = self._solver # if the eigensolver does not support auxiliary operators, reset them if not solver.supports_aux_operators(): aux_ops = None raw_es_result = solver.compute_eigenvalues(main_operator, aux_ops) eigenstate_result = EigenstateResult() eigenstate_result.raw_result = raw_es_result eigenstate_result.eigenenergies = raw_es_result.eigenvalues eigenstate_result.eigenstates = raw_es_result.eigenstates eigenstate_result.aux_operator_eigenvalues = raw_es_result.aux_operator_eigenvalues result = problem.interpret(eigenstate_result) return result
def build_hamiltonian( current_problem: BaseProblem) -> SecondQuantizedOp: """Returns the SecondQuantizedOp H(R) where H is the electronic hamiltonian and R is the current nuclear coordinates. This gives the electronic energies.""" hamiltonian_name = current_problem.main_property_name hamiltonian_op = current_problem.second_q_ops().get( hamiltonian_name, None) return hamiltonian_op
def get_qubit_operators( self, problem: BaseProblem, aux_operators: Optional[ListOrDictType[Union[SecondQuantizedOp, PauliSumOp]]] = None, ) -> Tuple[PauliSumOp, Optional[ListOrDictType[PauliSumOp]]]: """Gets the operator and auxiliary operators, and transforms the provided auxiliary operators""" # Note that ``aux_ops`` contains not only the transformed ``aux_operators`` passed by the # user but also additional ones from the transformation second_q_ops = problem.second_q_ops() aux_second_q_ops: ListOrDictType[SecondQuantizedOp] if isinstance(second_q_ops, list): main_second_q_op = second_q_ops[0] aux_second_q_ops = second_q_ops[1:] elif isinstance(second_q_ops, dict): name = problem.main_property_name main_second_q_op = second_q_ops.pop(name, None) if main_second_q_op is None: raise ValueError( f"The main `SecondQuantizedOp` associated with the {name} property cannot be " "`None`." ) aux_second_q_ops = second_q_ops main_operator = self._qubit_converter.convert( main_second_q_op, num_particles=problem.num_particles, sector_locator=problem.symmetry_sector_locator, ) aux_ops = self._qubit_converter.convert_match(aux_second_q_ops) if aux_operators is not None: wrapped_aux_operators: ListOrDict[Union[SecondQuantizedOp, PauliSumOp]] = ListOrDict( aux_operators ) for name_aux, aux_op in iter(wrapped_aux_operators): if isinstance(aux_op, SecondQuantizedOp): converted_aux_op = self._qubit_converter.convert_match(aux_op, True) else: converted_aux_op = aux_op if isinstance(aux_ops, list): aux_ops.append(converted_aux_op) elif isinstance(aux_ops, dict): if name_aux in aux_ops.keys(): raise QiskitNatureError( f"The key '{name_aux}' is already taken by an internally constructed " "auxiliary operator! Please use a different name for your custom " "operator." ) aux_ops[name_aux] = converted_aux_op if isinstance(self._solver, EigensolverFactory): # this must be called after transformation.transform self._solver = self._solver.get_solver(problem) # if the eigensolver does not support auxiliary operators, reset them if not self._solver.supports_aux_operators(): aux_ops = None return main_operator, aux_ops
def solve( self, problem: BaseProblem, aux_operators: Optional[ListOrDictType[Union[SecondQuantizedOp, PauliSumOp]]] = None, ) -> EigenstateResult: """Compute Ground State properties. Args: problem: a class encoding a problem to be solved. aux_operators: Additional auxiliary operators to evaluate. Raises: ValueError: if the grouped property object returned by the driver does not contain a main property as requested by the problem being solved (`problem.main_property_name`) QiskitNatureError: if the user-provided `aux_operators` contain a name which clashes with an internally constructed auxiliary operator. Note: the names used for the internal auxiliary operators correspond to the `Property.name` attributes which generated the respective operators. Returns: An interpreted :class:`~.EigenstateResult`. For more information see also :meth:`~.BaseProblem.interpret`. """ # get the operator and auxiliary operators, and transform the provided auxiliary operators # note that ``aux_ops`` contains not only the transformed ``aux_operators`` passed by the # user but also additional ones from the transformation second_q_ops = problem.second_q_ops() aux_second_q_ops: ListOrDictType[SecondQuantizedOp] if isinstance(second_q_ops, list): main_second_q_op = second_q_ops[0] aux_second_q_ops = second_q_ops[1:] elif isinstance(second_q_ops, dict): name = problem.main_property_name main_second_q_op = second_q_ops.pop(name, None) if main_second_q_op is None: raise ValueError( f"The main `SecondQuantizedOp` associated with the {name} property cannot be " "`None`.") aux_second_q_ops = second_q_ops main_operator = self._qubit_converter.convert( main_second_q_op, num_particles=problem.num_particles, sector_locator=problem.symmetry_sector_locator, ) aux_ops = self._qubit_converter.convert_match(aux_second_q_ops) if aux_operators is not None: wrapped_aux_operators: ListOrDict[Union[ SecondQuantizedOp, PauliSumOp]] = ListOrDict(aux_operators) for name, aux_op in iter(wrapped_aux_operators): if isinstance(aux_op, SecondQuantizedOp): converted_aux_op = self._qubit_converter.convert_match( aux_op, True) else: converted_aux_op = aux_op if isinstance(aux_ops, list): aux_ops.append(converted_aux_op) elif isinstance(aux_ops, dict): if name in aux_ops.keys(): raise QiskitNatureError( f"The key '{name}' is already taken by an internally constructed " "auxliliary operator! Please use a different name for your custom " "operator.") aux_ops[name] = converted_aux_op if isinstance(self._solver, MinimumEigensolverFactory): # this must be called after transformation.transform self._solver = self._solver.get_solver(problem, self._qubit_converter) # if the eigensolver does not support auxiliary operators, reset them if not self._solver.supports_aux_operators(): aux_ops = None raw_mes_result = self._solver.compute_minimum_eigenvalue( main_operator, aux_ops) result = problem.interpret(raw_mes_result) return result
def solve( self, problem: BaseProblem, aux_operators: Optional[List[SecondQuantizedOp]] = None ) -> EigenstateResult: """Run the excited-states calculation. Construct and solves the EOM pseudo-eigenvalue problem to obtain the excitation energies and the excitation operators expansion coefficients. Args: problem: a class encoding a problem to be solved. aux_operators: Additional auxiliary operators to evaluate. Returns: An interpreted :class:`~.EigenstateResult`. For more information see also :meth:`~.BaseProblem.interpret`. """ if aux_operators is not None: logger.warning( "With qEOM the auxiliary operators can currently only be " "evaluated on the ground state.") # 1. Run ground state calculation groundstate_result = self._gsc.solve(problem) # 2. Prepare the excitation operators self._untapered_qubit_op_main = self._gsc._qubit_converter.map( problem.second_q_ops()[0]) matrix_operators_dict, size = self._prepare_matrix_operators(problem) # 3. Evaluate eom operators measurement_results = self._gsc.evaluate_operators( groundstate_result.eigenstates[0], matrix_operators_dict) measurement_results = cast(Dict[str, List[float]], measurement_results) # 4. Post-process ground_state_result to construct eom matrices m_mat, v_mat, q_mat, w_mat, m_mat_std, v_mat_std, q_mat_std, w_mat_std = \ self._build_eom_matrices(measurement_results, size) # 5. solve pseudo-eigenvalue problem energy_gaps, expansion_coefs = self._compute_excitation_energies( m_mat, v_mat, q_mat, w_mat) qeom_result = QEOMResult() qeom_result.ground_state_raw_result = groundstate_result.raw_result qeom_result.expansion_coefficients = expansion_coefs qeom_result.excitation_energies = energy_gaps qeom_result.m_matrix = m_mat qeom_result.v_matrix = v_mat qeom_result.q_matrix = q_mat qeom_result.w_matrix = w_mat qeom_result.m_matrix_std = m_mat_std qeom_result.v_matrix_std = v_mat_std qeom_result.q_matrix_std = q_mat_std qeom_result.w_matrix_std = w_mat_std eigenstate_result = EigenstateResult() eigenstate_result.eigenstates = groundstate_result.eigenstates eigenstate_result.aux_operator_eigenvalues = groundstate_result.aux_operator_eigenvalues eigenstate_result.raw_result = qeom_result eigenstate_result.eigenenergies = np.append( groundstate_result.eigenenergies, np.asarray([ groundstate_result.eigenenergies[0] + gap for gap in energy_gaps ])) result = problem.interpret(eigenstate_result) return result
def solve( self, problem: BaseProblem, aux_operators: Optional[List[Union[SecondQuantizedOp, PauliSumOp]]] = None, ) -> "AdaptVQEResult": """Computes the ground state. Args: problem: a class encoding a problem to be solved. aux_operators: Additional auxiliary operators to evaluate. Raises: QiskitNatureError: if a solver other than VQE or a ansatz other than UCCSD is provided or if the algorithm finishes due to an unforeseen reason. Returns: An AdaptVQEResult which is an ElectronicStructureResult but also includes runtime information about the AdaptVQE algorithm like the number of iterations, finishing criterion, and the final maximum gradient. """ second_q_ops = problem.second_q_ops() self._main_operator = self._qubit_converter.convert( second_q_ops[0], num_particles=problem.num_particles, sector_locator=problem.symmetry_sector_locator, ) aux_ops = self._qubit_converter.convert_match(second_q_ops[1:]) if aux_operators is not None: for aux_op in aux_operators: if isinstance(aux_op, SecondQuantizedOp): aux_ops.append( self._qubit_converter.convert_match(aux_op, True)) else: aux_ops.append(aux_op) if isinstance(self._solver, MinimumEigensolverFactory): vqe = self._solver.get_solver(problem, self._qubit_converter) else: vqe = self._solver if not isinstance(vqe, VQE): raise QiskitNatureError( "The AdaptVQE algorithm requires the use of the VQE solver") if not isinstance(vqe.ansatz, UCC): raise QiskitNatureError( "The AdaptVQE algorithm requires the use of the UCC ansatz") # We construct the ansatz once to be able to extract the full set of excitation operators. self._ansatz = copy.deepcopy(vqe.ansatz) self._ansatz._build() self._excitation_pool = copy.deepcopy(self._ansatz.operators) threshold_satisfied = False alternating_sequence = False max_iterations_exceeded = False prev_op_indices: List[int] = [] theta: List[float] = [] max_grad: Tuple[float, Optional[PauliSumOp]] = (0.0, None) iteration = 0 while self._max_iterations is None or iteration < self._max_iterations: iteration += 1 logger.info("--- Iteration #%s ---", str(iteration)) # compute gradients cur_grads = self._compute_gradients(theta, vqe) # pick maximum gradient max_grad_index, max_grad = max(enumerate(cur_grads), key=lambda item: np.abs(item[1][0])) # store maximum gradient's index for cycle detection prev_op_indices.append(max_grad_index) # log gradients if logger.isEnabledFor(logging.INFO): gradlog = "\nGradients in iteration #{}".format(str(iteration)) gradlog += "\nID: Excitation Operator: Gradient <(*) maximum>" for i, grad in enumerate(cur_grads): gradlog += "\n{}: {}: {}".format(str(i), str(grad[1]), str(grad[0])) if grad[1] == max_grad[1]: gradlog += "\t(*)" logger.info(gradlog) if np.abs(max_grad[0]) < self._threshold: logger.info( "Adaptive VQE terminated successfully with a final maximum gradient: %s", str(np.abs(max_grad[0])), ) threshold_satisfied = True break # check indices of picked gradients for cycles if self._check_cyclicity(prev_op_indices): logger.info("Alternating sequence found. Finishing.") logger.info("Final maximum gradient: %s", str(np.abs(max_grad[0]))) alternating_sequence = True break # add new excitation to self._ansatz self._excitation_list.append(max_grad[1]) theta.append(0.0) # run VQE on current Ansatz self._ansatz.operators = self._excitation_list vqe.ansatz = self._ansatz vqe.initial_point = theta raw_vqe_result = vqe.compute_minimum_eigenvalue( self._main_operator) theta = raw_vqe_result.optimal_point.tolist() else: # reached maximum number of iterations max_iterations_exceeded = True logger.info("Maximum number of iterations reached. Finishing.") logger.info("Final maximum gradient: %s", str(np.abs(max_grad[0]))) # once finished evaluate auxiliary operators if any if aux_ops is not None: aux_values = self.evaluate_operators(raw_vqe_result.eigenstate, aux_ops) else: aux_values = None raw_vqe_result.aux_operator_eigenvalues = aux_values if threshold_satisfied: finishing_criterion = "Threshold converged" elif alternating_sequence: finishing_criterion = "Aborted due to cyclicity" elif max_iterations_exceeded: finishing_criterion = "Maximum number of iterations reached" else: raise QiskitNatureError( "The algorithm finished due to an unforeseen reason!") electronic_result = problem.interpret(raw_vqe_result) result = AdaptVQEResult() result.combine(electronic_result) result.num_iterations = iteration result.final_max_gradient = max_grad[0] result.finishing_criterion = finishing_criterion logger.info("The final energy is: %s", str(result.computed_energies[0])) return result
def solve( self, problem: BaseProblem, aux_operators: Optional[ListOrDictType[Union[SecondQuantizedOp, PauliSumOp]]] = None, ) -> "AdaptVQEResult": """Computes the ground state. Args: problem: a class encoding a problem to be solved. aux_operators: Additional auxiliary operators to evaluate. Raises: QiskitNatureError: if a solver other than VQE or a ansatz other than UCCSD is provided or if the algorithm finishes due to an unforeseen reason. ValueError: if the grouped property object returned by the driver does not contain a main property as requested by the problem being solved (`problem.main_property_name`) QiskitNatureError: if the user-provided `aux_operators` contain a name which clashes with an internally constructed auxiliary operator. Note: the names used for the internal auxiliary operators correspond to the `Property.name` attributes which generated the respective operators. Returns: An AdaptVQEResult which is an ElectronicStructureResult but also includes runtime information about the AdaptVQE algorithm like the number of iterations, finishing criterion, and the final maximum gradient. """ second_q_ops = problem.second_q_ops() aux_second_q_ops: ListOrDictType[SecondQuantizedOp] if isinstance(second_q_ops, list): main_second_q_op = second_q_ops[0] aux_second_q_ops = second_q_ops[1:] elif isinstance(second_q_ops, dict): name = problem.main_property_name main_second_q_op = second_q_ops.pop(name, None) if main_second_q_op is None: raise ValueError( f"The main `SecondQuantizedOp` associated with the {name} property cannot be " "`None`." ) aux_second_q_ops = second_q_ops self._main_operator = self._qubit_converter.convert( main_second_q_op, num_particles=problem.num_particles, sector_locator=problem.symmetry_sector_locator, ) aux_ops = self._qubit_converter.convert_match(aux_second_q_ops) if aux_operators is not None: wrapped_aux_operators: ListOrDict[Union[SecondQuantizedOp, PauliSumOp]] = ListOrDict( aux_operators ) for name_aux, aux_op in iter(wrapped_aux_operators): if isinstance(aux_op, SecondQuantizedOp): converted_aux_op = self._qubit_converter.convert_match(aux_op, True) else: converted_aux_op = aux_op if isinstance(aux_ops, list): aux_ops.append(converted_aux_op) elif isinstance(aux_ops, dict): if name_aux in aux_ops.keys(): raise QiskitNatureError( f"The key '{name_aux}' is already taken by an internally constructed " "auxiliary operator! Please use a different name for your custom " "operator." ) aux_ops[name_aux] = converted_aux_op if isinstance(self._solver, MinimumEigensolverFactory): vqe = self._solver.get_solver(problem, self._qubit_converter) else: vqe = self._solver if not isinstance(vqe, VQE): raise QiskitNatureError("The AdaptVQE algorithm requires the use of the VQE solver") if not isinstance(vqe.ansatz, UCC): raise QiskitNatureError("The AdaptVQE algorithm requires the use of the UCC ansatz") # We construct the ansatz once to be able to extract the full set of excitation operators. self._ansatz = copy.deepcopy(vqe.ansatz) self._ansatz._build() self._excitation_pool = copy.deepcopy(self._ansatz.operators) threshold_satisfied = False alternating_sequence = False max_iterations_exceeded = False prev_op_indices: List[int] = [] theta: List[float] = [] max_grad: Tuple[float, Optional[PauliSumOp]] = (0.0, None) iteration = 0 while self._max_iterations is None or iteration < self._max_iterations: iteration += 1 logger.info("--- Iteration #%s ---", str(iteration)) # compute gradients cur_grads = self._compute_gradients(theta, vqe) # pick maximum gradient max_grad_index, max_grad = max( enumerate(cur_grads), key=lambda item: np.abs(item[1][0]) ) # store maximum gradient's index for cycle detection prev_op_indices.append(max_grad_index) # log gradients if logger.isEnabledFor(logging.INFO): gradlog = f"\nGradients in iteration #{str(iteration)}" gradlog += "\nID: Excitation Operator: Gradient <(*) maximum>" for i, grad in enumerate(cur_grads): gradlog += f"\n{str(i)}: {str(grad[1])}: {str(grad[0])}" if grad[1] == max_grad[1]: gradlog += "\t(*)" logger.info(gradlog) if np.abs(max_grad[0]) < self._threshold: logger.info( "Adaptive VQE terminated successfully with a final maximum gradient: %s", str(np.abs(max_grad[0])), ) threshold_satisfied = True break # check indices of picked gradients for cycles if self._check_cyclicity(prev_op_indices): logger.info("Alternating sequence found. Finishing.") logger.info("Final maximum gradient: %s", str(np.abs(max_grad[0]))) alternating_sequence = True break # add new excitation to self._ansatz self._excitation_list.append(max_grad[1]) theta.append(0.0) # run VQE on current Ansatz self._ansatz.operators = self._excitation_list vqe.ansatz = self._ansatz vqe.initial_point = theta raw_vqe_result = vqe.compute_minimum_eigenvalue(self._main_operator) theta = raw_vqe_result.optimal_point.tolist() else: # reached maximum number of iterations max_iterations_exceeded = True logger.info("Maximum number of iterations reached. Finishing.") logger.info("Final maximum gradient: %s", str(np.abs(max_grad[0]))) # once finished evaluate auxiliary operators if any if aux_ops is not None: aux_values = self.evaluate_operators(raw_vqe_result.eigenstate, aux_ops) else: aux_values = None raw_vqe_result.aux_operator_eigenvalues = aux_values if threshold_satisfied: finishing_criterion = "Threshold converged" elif alternating_sequence: finishing_criterion = "Aborted due to cyclicity" elif max_iterations_exceeded: finishing_criterion = "Maximum number of iterations reached" else: raise QiskitNatureError("The algorithm finished due to an unforeseen reason!") electronic_result = problem.interpret(raw_vqe_result) result = AdaptVQEResult() result.combine(electronic_result) result.num_iterations = iteration result.final_max_gradient = max_grad[0] result.finishing_criterion = finishing_criterion logger.info("The final energy is: %s", str(result.computed_energies[0])) return result