def convert( self, operator: OperatorBase, params: Optional[Union[ParameterExpression, ParameterVector, List[ParameterExpression], Tuple[ParameterExpression, ParameterExpression], List[Tuple[ParameterExpression, ParameterExpression]]]] = None ) -> OperatorBase: """ Args: operator: The operator corresponding to our quantum state we are taking the gradient of: |ψ(ω)〉 params: The parameters we are taking the gradient wrt: ω If a ParameterExpression, ParameterVector or List[ParameterExpression] is given, then the 1st order derivative of the operator is calculated. If a Tuple[ParameterExpression, ParameterExpression] or List[Tuple[ParameterExpression, ParameterExpression]] is given, then the 2nd order derivative of the operator is calculated. Returns: An operator corresponding to the gradient resp. Hessian. The order is in accordance with the order of the given parameters. Raises: AquaError: If the parameters are given in an invalid format. """ if isinstance(params, (ParameterExpression, ParameterVector)): return self._parameter_shift(operator, params) elif isinstance(params, tuple): return self._parameter_shift( self._parameter_shift(operator, params[0]), params[1]) elif isinstance(params, Iterable): if all(isinstance(param, ParameterExpression) for param in params): return self._parameter_shift(operator, params) elif all(isinstance(param, tuple) for param in params): return ListOp([ self._parameter_shift( self._parameter_shift(operator, pair[0]), pair[1]) for pair in params ]) else: raise AquaError( 'The linear combination gradient does only support the computation ' 'of 1st gradients and 2nd order gradients.') else: raise AquaError( 'The linear combination gradient does only support the computation ' 'of 1st gradients and 2nd order gradients.')
def permute(self, permutation: List[int]) -> "PauliSumOp": """Permutes the sequence of ``PauliSumOp``. Args: permutation: A list defining where each Pauli should be permuted. The Pauli at index j of the primitive should be permuted to position permutation[j]. Returns: A new PauliSumOp representing the permuted operator. For operator (X ^ Y ^ Z) and indices=[1,2,4], it returns (X ^ I ^ Y ^ Z ^ I). Raises: AquaError: if indices do not define a new index for each qubit. """ if len(permutation) != self.num_qubits: raise AquaError( "List of indices to permute must have the same size as Pauli Operator" ) length = max(permutation) + 1 spop = self.primitive.tensor( # type:ignore SparsePauliOp(Pauli(label="I" * (length - self.num_qubits)))) permutation = [i for i in range(length) if i not in permutation ] + permutation permutation = np.arange(length)[np.argsort(permutation)] permutation = np.hstack([permutation, permutation + length]) # type: ignore spop.table.array = spop.table.array[:, permutation] return PauliSumOp(spop, self.coeff)
def trim_circuit(circuit: QuantumCircuit, reference_gate: Gate) -> QuantumCircuit: """Trim the given quantum circuit before the reference gate. Args: circuit: The circuit to be trimmed. reference_gate: The gate where the circuit is supposed to be trimmed. Returns: The trimmed circuit. Raises: AquaError: If the reference gate is not part of the given circuit. """ parameterized_gates = [] for _, elements in circuit._parameter_table.items(): for element in elements: parameterized_gates.append(element[0]) for i, op in enumerate(circuit.data): if op[0] == reference_gate: trimmed_circuit = QuantumCircuit(*circuit.qregs) trimmed_circuit.data = circuit.data[:i] return trimmed_circuit raise AquaError( 'The reference gate is not in the given quantum circuit.')
def permute(self, permutation: List[int]) -> 'PauliOp': """Permutes the sequence of Pauli matrices. Args: permutation: A list defining where each Pauli should be permuted. The Pauli at index j of the primitive should be permuted to position permutation[j]. Returns: A new PauliOp representing the permuted operator. For operator (X ^ Y ^ Z) and indices=[1,2,4], it returns (X ^ I ^ Y ^ Z ^ I). Raises: AquaError: if indices do not define a new index for each qubit. """ pauli_string = self.primitive.__str__() length = max(permutation ) + 1 # size of list must be +1 larger then its max index new_pauli_list = ['I'] * length if len(permutation) != self.num_qubits: raise AquaError( "List of indices to permute must have the same size as Pauli Operator" ) for i, index in enumerate(permutation): new_pauli_list[-index - 1] = pauli_string[-i - 1] return PauliOp(Pauli(label=''.join(new_pauli_list)), self.coeff)
def evaluate_with_result(self, result, statevector_mode=True, use_simulator_snapshot_mode=None, circuit_name_prefix=''): """ Use the executed result with operator to get the evaluated value. Args: result (qiskit.Result): the result from the backend statevector_mode (bool): mode use_simulator_snapshot_mode (bool): uses simulator operator mode circuit_name_prefix (str, optional): a prefix of circuit name Returns: float: the mean value float: the standard deviation Raises: AquaError: if Operator is empty """ if self.is_empty(): raise AquaError("Operator is empty, check the operator.") del use_simulator_snapshot_mode # unused avg, std_dev = 0.0, 0.0 quantum_state = np.asarray( result.get_statevector(circuit_name_prefix + 'psi')) avg = np.vdot(quantum_state, self._matrix.dot(quantum_state)) return avg, std_dev
def _unroll_param_dict( value_dict: Dict[Union[ParameterExpression, ParameterVector], Union[Number, List[Number]]] ) -> Union[Dict[ParameterExpression, Number], List[Dict[ ParameterExpression, Number]]]: """ Unrolls the ParameterVectors in a param_dict into separate Parameters, and unrolls parameterization value lists into separate param_dicts without list nesting. """ unrolled_value_dict = {} for (param, value) in value_dict.items(): if isinstance(param, ParameterExpression): unrolled_value_dict[param] = value if isinstance(param, ParameterVector): if not len(param) == len(value): # type: ignore raise ValueError( 'ParameterVector {} has length {}, which differs from value list {} of ' 'len {}'.format(param, len(param), value, len(value))) # type: ignore unrolled_value_dict.update(zip(param, value)) # type: ignore if isinstance(list(unrolled_value_dict.values())[0], list): # check that all are same length unrolled_value_dict_list = [] try: for i in range(len(list( unrolled_value_dict.values())[0])): # type: ignore unrolled_value_dict_list.append( OperatorBase._get_param_dict_for_index( unrolled_value_dict, # type: ignore i)) return unrolled_value_dict_list except IndexError as ex: raise AquaError( 'Parameter binding lists must all be the same length.' ) from ex return unrolled_value_dict # type: ignore
def to_tpb_grouped_weighted_pauli_operator( operator: Union[WeightedPauliOperator, TPBGroupedWeightedPauliOperator, MatrixOperator], grouping_func: Callable, **kwargs: int) -> TPBGroupedWeightedPauliOperator: """ Args: operator: one of supported operator type grouping_func: a callable function that grouped the paulis in the operator. kwargs: other setting for `grouping_func` function Returns: the converted tensor-product-basis grouped weighted pauli operator Raises: AquaError: Unsupported type to convert """ if operator.__class__ == WeightedPauliOperator: return grouping_func(operator, **kwargs) elif operator.__class__ == TPBGroupedWeightedPauliOperator: # different tpb grouping approach is asked op_tpb = cast(TPBGroupedWeightedPauliOperator, operator) if grouping_func != op_tpb.grouping_func and kwargs != op_tpb.kwargs: return grouping_func(op_tpb, **kwargs) else: return op_tpb elif operator.__class__ == MatrixOperator: op = to_weighted_pauli_operator(operator) return grouping_func(op, **kwargs) else: raise AquaError( "Unsupported type to convert to TPBGroupedWeightedPauliOperator: " "{}".format(operator.__class__))
def permute(self, permutation: Optional[List[int]] = None) -> 'MatrixOp': """Creates a new MatrixOp that acts on the permuted qubits. Args: permutation: A list defining where each qubit should be permuted. The qubit at index j should be permuted to position permutation[j]. Returns: A new MatrixOp representing the permuted operator. Raises: AquaError: if indices do not define a new index for each qubit. """ new_self = self new_matrix_size = max(permutation) + 1 if self.num_qubits != len(permutation): raise AquaError( "New index must be defined for each qubit of the operator.") if self.num_qubits < new_matrix_size: # pad the operator with identities new_self = self._expand_dim(new_matrix_size - self.num_qubits) qc = QuantumCircuit(new_matrix_size) # extend the indices to match the size of the new matrix permutation \ = list(filter(lambda x: x not in permutation, range(new_matrix_size))) + permutation # decompose permutation into sequence of transpositions transpositions = arithmetic.transpositions(permutation) for trans in transpositions: qc.swap(trans[0], trans[1]) matrix = CircuitOp(qc).to_matrix() return MatrixOp(matrix.transpose()) @ new_self @ MatrixOp( matrix) # type: ignore
def to_matrix_operator( operator: Union[WeightedPauliOperator, TPBGroupedWeightedPauliOperator, MatrixOperator])\ -> MatrixOperator: """ Converting a given operator to `MatrixOperator` Args: operator: one of supported operator type Returns: the converted matrix operator Raises: AquaError: Unsupported type to convert """ if operator.__class__ == WeightedPauliOperator: op_w = cast(WeightedPauliOperator, operator) if op_w.is_empty(): return MatrixOperator(None) hamiltonian = 0 for weight, pauli in op_w.paulis: hamiltonian += weight * pauli.to_spmatrix() return MatrixOperator(matrix=hamiltonian, z2_symmetries=op_w.z2_symmetries, name=op_w.name) elif operator.__class__ == TPBGroupedWeightedPauliOperator: op_tpb = cast(TPBGroupedWeightedPauliOperator, operator) op = WeightedPauliOperator(paulis=op_tpb.paulis, z2_symmetries=op_tpb.z2_symmetries, name=op_tpb.name) return to_matrix_operator(op) elif operator.__class__ == MatrixOperator: return cast(MatrixOperator, operator) else: raise AquaError("Unsupported type to convert to MatrixOperator: " "{}".format(operator.__class__))
def adjoint(self) -> OperatorBase: """The adjoint of a CVaRMeasurement is not defined. Returns: Does not return anything, raises an error. Raises: AquaError: The adjoint of a CVaRMeasurement is not defined. """ raise AquaError('Adjoint of a CVaR measurement not defined')
def permute(self, permutation: List[int]) -> 'ListOp': """Permute the qubits of the operator. Args: permutation: A list defining where each qubit should be permuted. The qubit at index j should be permuted to position permutation[j]. Returns: A new ListOp representing the permuted operator. Raises: AquaError: if indices do not define a new index for each qubit. """ new_self = self circuit_size = max(permutation) + 1 try: if self.num_qubits != len(permutation): raise AquaError("New index must be defined for each qubit of the operator.") except ValueError: raise AquaError("Permute is only possible if all operators in the ListOp have the " "same number of qubits.") from ValueError if self.num_qubits < circuit_size: # pad the operator with identities new_self = self._expand_dim(circuit_size - self.num_qubits) qc = QuantumCircuit(circuit_size) # extend the indices to match the size of the circuit permutation \ = list(filter(lambda x: x not in permutation, range(circuit_size))) + permutation # decompose permutation into sequence of transpositions transpositions = arithmetic.transpositions(permutation) for trans in transpositions: qc.swap(trans[0], trans[1]) # pylint: disable=import-outside-toplevel,cyclic-import from ..primitive_ops.circuit_op import CircuitOp return CircuitOp(qc.reverse_ops()) @ new_self @ CircuitOp(qc)
def get_parameter_expression(circuit, param): if len(circuit._parameter_table[param]) > 1: raise NotImplementedError( "OverlapDiag does not yet support multiple " "gates parameterized by a single parameter. For such " "circuits use LinCombFull") gate = circuit._parameter_table[param][0][0] if len(gate.params) > 1: raise AquaError( "OverlapDiag cannot yet support gates with more than one " "parameter.") param_value = gate.params[0] return param_value
def group_subops(cls, list_op: ListOp, fast: Optional[bool] = None, use_nx: Optional[bool] = None) -> ListOp: """Given a ListOp, attempt to group into Abelian ListOps of the same type. Args: list_op: The Operator to group into Abelian groups fast: Ignored - parameter will be removed in future release use_nx: Ignored - parameter will be removed in future release Returns: The grouped Operator. Raises: AquaError: If any of list_op's sub-ops is not ``PauliOp``. """ if fast is not None or use_nx is not None: warnings.warn('Options `fast` and `use_nx` of `AbelianGrouper.group_subops` are ' 'no longer used and are now deprecated and will be removed no ' 'sooner than 3 months following the 0.8.0 release.') # TODO: implement direct way if isinstance(list_op, PauliSumOp): list_op = list_op.to_pauli_op() for op in list_op.oplist: if not isinstance(op, PauliOp): raise AquaError( 'Cannot determine Abelian groups if any Operator in list_op is not ' '`PauliOp`. E.g., {} ({})'.format(op, type(op))) edges = cls._commutation_graph(list_op) nodes = range(len(list_op)) graph = rx.PyGraph() graph.add_nodes_from(nodes) graph.add_edges_from_no_data(edges) # Keys in coloring_dict are nodes, values are colors coloring_dict = rx.graph_greedy_color(graph) groups = {} # type: Dict # sort items so that the output is consistent with all options (fast and use_nx) for idx, color in sorted(coloring_dict.items()): groups.setdefault(color, []).append(list_op[idx]) group_ops = [list_op.__class__(group, abelian=True) for group in groups.values()] if len(group_ops) == 1: return group_ops[0] * list_op.coeff # type: ignore return list_op.__class__(group_ops, coeff=list_op.coeff) # type: ignore
def to_circuit(self) -> QuantumCircuit: """Returns the quantum circuit, representing the tensored operator. Returns: The circuit representation of the tensored operator. Raises: AquaError: for operators where a single underlying circuit can not be produced. """ # pylint: disable=import-outside-toplevel,cyclic-import from ..state_fns.circuit_state_fn import CircuitStateFn circuit_op = self.to_circuit_op() if isinstance(circuit_op, (PrimitiveOp, CircuitStateFn)): return circuit_op.to_circuit() raise AquaError( 'Conversion to_circuit supported only for operators, where a single ' 'underlying circuit can be produced.')
def evaluate_with_statevector(self, quantum_state): """ Args: quantum_state (numpy.ndarray): quantum state Returns: float: the mean value float: the standard deviation Raises: AquaError: if Operator is empty """ if self.is_empty(): raise AquaError("Operator is empty, check the operator.") avg = np.vdot(quantum_state, self._matrix.dot(quantum_state)) return avg, 0.0
def __init__( self, primitive: Union[OperatorBase] = None, alpha: float = 1.0, coeff: Union[int, float, complex, ParameterExpression] = 1.0) -> None: """ Args: primitive: The ``OperatorBase`` which defines the diagonal operator measurement. coeff: A coefficient by which to multiply the state function alpha: A real-valued parameter between 0 and 1 which specifies the fraction of observed samples to include when computing the objective value. alpha = 1 corresponds to a standard observable expectation value. alpha = 0 corresponds to only using the single sample with the lowest energy. alpha = 0.5 corresponds to ranking each observation by lowest energy and using the best Raises: ValueError: TODO remove that this raises an error ValueError: If alpha is not in [0, 1]. AquaError: If the primitive is not diagonal. """ if primitive is None: raise ValueError if not 0 <= alpha <= 1: raise ValueError('The parameter alpha must be in [0, 1].') self._alpha = alpha if not _check_is_diagonal(primitive): raise AquaError( 'Input operator to CVaRMeasurement must be diagonal, but is not:', str(primitive)) super().__init__(primitive, coeff=coeff, is_measurement=True)
def to_circuit(self) -> QuantumCircuit: """Returns the quantum circuit, representing the SummedOp. In the first step, the SummedOp is converted to MatrixOp. This is straightforward for most operators, but it is not supported for operators containing parametrized PrimitiveOps (in that case, AquaError is raised). In the next step, the MatrixOp representation of SummedOp is converted to circuit. In most cases, if the summands themselves are unitary operators, the SummedOp itself is non-unitary and can not be converted to circuit. In that case, ExtensionError is raised in the underlying modules. Returns: The circuit representation of the summed operator. Raises: AquaError: if SummedOp can not be converted to MatrixOp (e.g. SummedOp is composed of parametrized PrimitiveOps). """ # pylint: disable=import-outside-toplevel,cyclic-import from ..primitive_ops.matrix_op import MatrixOp matrix_op = self.to_matrix_op() if isinstance(matrix_op, MatrixOp): return matrix_op.to_circuit() raise AquaError( "The SummedOp can not be converted to circuit, because to_matrix_op did " "not return a MatrixOp.")
def sample_circuits(self, circuit_sfns: Optional[List[CircuitStateFn]] = None, param_bindings: Optional[List[Dict[Parameter, float]]] = None ) -> Dict[int, Union[StateFn, List[StateFn]]]: r""" Samples the CircuitStateFns and returns a dict associating their ``id()`` values to their replacement DictStateFn or VectorStateFn. If param_bindings is provided, the CircuitStateFns are broken into their parameterizations, and a list of StateFns is returned in the dict for each circuit ``id()``. Note that param_bindings is provided here in a different format than in ``convert``, and lists of parameters within the dict is not supported, and only binding dicts which are valid to be passed into Terra can be included in this list. Args: circuit_sfns: The list of CircuitStateFns to sample. param_bindings: The parameterizations to bind to each CircuitStateFn. Returns: The dictionary mapping ids of the CircuitStateFns to their replacement StateFns. Raises: AquaError: if extracted circuits are empty. """ if not circuit_sfns and not self._transpiled_circ_cache: raise AquaError('CircuitStateFn is empty and there is no cache.') if circuit_sfns: self._transpiled_circ_templates = None if self._statevector: circuits = [op_c.to_circuit(meas=False) for op_c in circuit_sfns] else: circuits = [op_c.to_circuit(meas=True) for op_c in circuit_sfns] try: self._transpiled_circ_cache = self.quantum_instance.transpile(circuits) except QiskitError: logger.debug(r'CircuitSampler failed to transpile circuits with unbound ' r'parameters. Attempting to transpile only when circuits are bound ' r'now, but this can hurt performance due to repeated transpilation.') self._transpile_before_bind = False self._transpiled_circ_cache = circuits else: circuit_sfns = list(self._circuit_ops_cache.values()) if param_bindings is not None: if self._param_qobj: start_time = time() ready_circs = self._prepare_parameterized_run_config(param_bindings) end_time = time() logger.debug('Parameter conversion %.5f (ms)', (end_time - start_time) * 1000) else: start_time = time() ready_circs = [circ.assign_parameters(_filter_params(circ, binding)) for circ in self._transpiled_circ_cache for binding in param_bindings] end_time = time() logger.debug('Parameter binding %.5f (ms)', (end_time - start_time) * 1000) else: ready_circs = self._transpiled_circ_cache results = self.quantum_instance.execute(ready_circs, had_transpiled=self._transpile_before_bind) if param_bindings is not None and self._param_qobj: self._clean_parameterized_run_config() # Wipe parameterizations, if any # self.quantum_instance._run_config.parameterizations = None sampled_statefn_dicts = {} for i, op_c in enumerate(circuit_sfns): # Taking square root because we're replacing a statevector # representation of probabilities. reps = len(param_bindings) if param_bindings is not None else 1 c_statefns = [] for j in range(reps): circ_index = (i * reps) + j circ_results = results.data(circ_index) if 'expval_measurement' in circ_results.get('snapshots', {}).get( 'expectation_value', {}): snapshot_data = results.data(circ_index)['snapshots'] avg = snapshot_data['expectation_value']['expval_measurement'][0]['value'] if isinstance(avg, (list, tuple)): # Aer versions before 0.4 use a list snapshot format # which must be converted to a complex value. avg = avg[0] + 1j * avg[1] # Will be replaced with just avg when eval is called later num_qubits = circuit_sfns[0].num_qubits result_sfn = DictStateFn('0' * num_qubits, is_measurement=op_c.is_measurement) * avg elif self._statevector: result_sfn = StateFn(op_c.coeff * results.get_statevector(circ_index), is_measurement=op_c.is_measurement) else: shots = self.quantum_instance._run_config.shots result_sfn = StateFn({b: (v / shots) ** 0.5 * op_c.coeff for (b, v) in results.get_counts(circ_index).items()}, is_measurement=op_c.is_measurement) if self._attach_results: result_sfn.execution_results = circ_results c_statefns.append(result_sfn) sampled_statefn_dicts[id(op_c)] = c_statefns return sampled_statefn_dicts
def evolution_instruction(pauli_list, evo_time, num_time_slices, controlled=False, power=1, use_basis_gates=True, shallow_slicing=False, barrier=False): """ Construct the evolution circuit according to the supplied specification. Args: pauli_list (list([[complex, Pauli]])): The list of pauli terms corresponding to a single time slice to be evolved evo_time (Union(complex, float, Parameter, ParameterExpression)): The evolution time num_time_slices (int): The number of time slices for the expansion controlled (bool, optional): Controlled circuit or not power (int, optional): The power to which the unitary operator is to be raised use_basis_gates (bool, optional): boolean flag for indicating only using basis gates when building circuit. shallow_slicing (bool, optional): boolean flag for indicating using shallow qc.data reference repetition for slicing barrier (bool, optional): whether or not add barrier for every slice Returns: Instruction: The Instruction corresponding to specified evolution. Raises: AquaError: power must be an integer and greater or equal to 1 ValueError: Unrecognized pauli """ if not isinstance(power, (int, np.int)) or power < 1: raise AquaError("power must be an integer and greater or equal to 1.") state_registers = QuantumRegister(pauli_list[0][1].num_qubits) if controlled: inst_name = 'Controlled-Evolution^{}'.format(power) ancillary_registers = QuantumRegister(1) qc_slice = QuantumCircuit(state_registers, ancillary_registers, name=inst_name) else: inst_name = 'Evolution^{}'.format(power) qc_slice = QuantumCircuit(state_registers, name=inst_name) # for each pauli [IXYZ]+, record the list of qubit pairs needing CX's cnot_qubit_pairs = [None] * len(pauli_list) # for each pauli [IXYZ]+, record the highest index of the nontrivial pauli gate (X,Y, or Z) top_xyz_pauli_indices = [-1] * len(pauli_list) for pauli_idx, pauli in enumerate(reversed(pauli_list)): n_qubits = pauli[1].num_qubits # changes bases if necessary nontrivial_pauli_indices = [] for qubit_idx in range(n_qubits): # pauli I if not pauli[1].z[qubit_idx] and not pauli[1].x[qubit_idx]: continue if cnot_qubit_pairs[pauli_idx] is None: nontrivial_pauli_indices.append(qubit_idx) if pauli[1].x[qubit_idx]: # pauli X if not pauli[1].z[qubit_idx]: if use_basis_gates: qc_slice.h(state_registers[qubit_idx]) else: qc_slice.h(state_registers[qubit_idx]) # pauli Y elif pauli[1].z[qubit_idx]: if use_basis_gates: qc_slice.u(pi / 2, -pi / 2, pi / 2, state_registers[qubit_idx]) else: qc_slice.rx(pi / 2, state_registers[qubit_idx]) # pauli Z elif pauli[1].z[qubit_idx] and not pauli[1].x[qubit_idx]: pass else: raise ValueError('Unrecognized pauli: {}'.format(pauli[1])) if nontrivial_pauli_indices: top_xyz_pauli_indices[pauli_idx] = nontrivial_pauli_indices[-1] # insert lhs cnot gates if cnot_qubit_pairs[pauli_idx] is None: cnot_qubit_pairs[pauli_idx] = list(zip( sorted(nontrivial_pauli_indices)[:-1], sorted(nontrivial_pauli_indices)[1:] )) for pair in cnot_qubit_pairs[pauli_idx]: qc_slice.cx(state_registers[pair[0]], state_registers[pair[1]]) # insert Rz gate if top_xyz_pauli_indices[pauli_idx] >= 0: # Because Parameter does not support complexity number operation; thus, we do # the following tricks to generate parameterized instruction. # We assume the coefficient in the pauli is always real. and can not do imaginary time # evolution if isinstance(evo_time, (Parameter, ParameterExpression)): lam = 2.0 * pauli[0] / num_time_slices lam = lam.real if lam.imag == 0 else lam lam = lam * evo_time else: lam = (2.0 * pauli[0] * evo_time / num_time_slices).real if not controlled: if use_basis_gates: qc_slice.p(lam, state_registers[top_xyz_pauli_indices[pauli_idx]]) else: qc_slice.rz(lam, state_registers[top_xyz_pauli_indices[pauli_idx]]) else: if use_basis_gates: qc_slice.p(lam / 2, state_registers[top_xyz_pauli_indices[pauli_idx]]) qc_slice.cx(ancillary_registers[0], state_registers[top_xyz_pauli_indices[pauli_idx]]) qc_slice.p(-lam / 2, state_registers[top_xyz_pauli_indices[pauli_idx]]) qc_slice.cx(ancillary_registers[0], state_registers[top_xyz_pauli_indices[pauli_idx]]) else: qc_slice.crz(lam, ancillary_registers[0], state_registers[top_xyz_pauli_indices[pauli_idx]]) # insert rhs cnot gates for pair in reversed(cnot_qubit_pairs[pauli_idx]): qc_slice.cx(state_registers[pair[0]], state_registers[pair[1]]) # revert bases if necessary for qubit_idx in range(n_qubits): if pauli[1].x[qubit_idx]: # pauli X if not pauli[1].z[qubit_idx]: if use_basis_gates: qc_slice.h(state_registers[qubit_idx]) else: qc_slice.h(state_registers[qubit_idx]) # pauli Y elif pauli[1].z[qubit_idx]: if use_basis_gates: qc_slice.u(-pi / 2, -pi / 2, pi / 2, state_registers[qubit_idx]) else: qc_slice.rx(-pi / 2, state_registers[qubit_idx]) # repeat the slice if shallow_slicing: logger.info('Under shallow slicing mode, the qc.data reference is repeated shallowly. ' 'Thus, changing gates of one slice of the output circuit might affect ' 'other slices.') if barrier: qc_slice.barrier(state_registers) qc_slice.data *= (num_time_slices * power) qc = qc_slice else: qc = QuantumCircuit(name=inst_name) for _ in range(num_time_slices * power): qc += qc_slice if barrier: qc.barrier(state_registers) return qc.to_instruction()
def get_hessian( self, operator: OperatorBase, params: Optional[Union[Tuple[ParameterExpression, ParameterExpression], List[Tuple[ParameterExpression, ParameterExpression]]]] = None ) -> OperatorBase: """Get the Hessian for the given operator w.r.t. the given parameters Args: operator: Operator w.r.t. which we take the Hessian. params: Parameters w.r.t. which we compute the Hessian. Returns: Operator which represents the gradient w.r.t. the given params. Raises: ValueError: If ``params`` contains a parameter not present in ``operator``. AquaError: If the coefficient of the operator could not be reduced to 1. AquaError: If the differentiation of a combo_fn requires JAX but the package is not installed. TypeError: If the operator does not include a StateFn given by a quantum circuit TypeError: If the parameters were given in an unsupported format. Exception: Unintended code is reached """ def is_coeff_c(coeff, c): if isinstance(coeff, ParameterExpression): expr = coeff._symbol_expr return expr == c return coeff == c if isinstance(params, (ParameterVector, list)): # Case: a list of parameters were given, compute the Hessian for all param pairs if all(isinstance(param, ParameterExpression) for param in params): return ListOp([ ListOp([ self.get_hessian(operator, (p0, p1)) for p1 in params ]) for p0 in params ]) # Case: a list was given containing tuples of parameter pairs. # Compute the Hessian entries corresponding to these pairs of parameters. elif all(isinstance(param, tuple) for param in params): return ListOp([ self.get_hessian(operator, param_pair) for param_pair in params ]) # If a gradient is requested w.r.t a single parameter, then call the # Gradient().get_gradient method. if isinstance(params, ParameterExpression): return Gradient(grad_method=self._hess_method).get_gradient( operator, params) if (not isinstance(params, tuple)) or (not len(params) == 2): raise TypeError("Parameters supplied in unsupported format.") # By this point, it's only one parameter tuple p_0 = params[0] p_1 = params[1] # Handle Product Rules if not is_coeff_c(operator._coeff, 1.0): # Separate the operator from the coefficient coeff = operator._coeff op = operator / coeff # Get derivative of the operator (recursively) d0_op = self.get_hessian(op, p_0) d1_op = self.get_hessian(op, p_1) # ..get derivative of the coeff d0_coeff = self.parameter_expression_grad(coeff, p_0) d1_coeff = self.parameter_expression_grad(coeff, p_1) dd_op = self.get_hessian(op, params) dd_coeff = self.parameter_expression_grad(d0_coeff, p_1) grad_op = 0 # Avoid creating operators that will evaluate to zero if dd_op != ~Zero @ One and not is_coeff_c(coeff, 0): grad_op += coeff * dd_op if d0_op != ~Zero @ One and not is_coeff_c(d1_coeff, 0): grad_op += d1_coeff * d0_op if d1_op != ~Zero @ One and not is_coeff_c(d0_coeff, 0): grad_op += d0_coeff * d1_op if not is_coeff_c(dd_coeff, 0): grad_op += dd_coeff * op if grad_op == 0: return ~Zero @ One return grad_op # Base Case, you've hit a ComposedOp! # Prior to execution, the composite operator was standardized and coefficients were # collected. Any operator measurements were converted to Pauli-Z measurements and rotation # circuits were applied. Additionally, all coefficients within ComposedOps were collected # and moved out front. if isinstance(operator, ComposedOp): if not is_coeff_c(operator.coeff, 1.): raise AquaError( 'Operator pre-processing failed. Coefficients were not properly ' 'collected inside the ComposedOp.') # Do some checks to make sure operator is sensible # TODO enable compatibility with sum of CircuitStateFn operators if not isinstance(operator[-1], CircuitStateFn): raise TypeError( 'The gradient framework is compatible with states that are given as ' 'CircuitStateFn') return self.hess_method.convert(operator, params) # This is the recursive case where the chain rule is handled elif isinstance(operator, ListOp): # These operators correspond to (d_op/d θ0,θ1) for op in operator.oplist # and params = (θ0,θ1) dd_ops = [self.get_hessian(op, params) for op in operator.oplist] # Note that this check to see if the ListOp has a default combo_fn # will fail if the user manually specifies the default combo_fn. # I.e operator = ListOp([...], combo_fn=lambda x:x) will not pass this check and # later on jax will try to differentiate it and fail. # An alternative is to check the byte code of the operator's combo_fn against the # default one. # This will work but look very ugly and may have other downsides I'm not aware of if operator.combo_fn == ListOp([]).combo_fn: return ListOp(oplist=dd_ops) elif isinstance(operator, SummedOp): return SummedOp(oplist=dd_ops) elif isinstance(operator, TensoredOp): return TensoredOp(oplist=dd_ops) # These operators correspond to (d g_i/d θ0)•(d g_i/d θ1) for op in operator.oplist # and params = (θ0,θ1) d1d0_ops = ListOp([ ListOp([ Gradient(grad_method=self._hess_method).convert(op, param) for param in params ], combo_fn=np.prod) for op in operator.oplist ]) if operator.grad_combo_fn: first_partial_combo_fn = operator.grad_combo_fn if _HAS_JAX: second_partial_combo_fn = jit( grad(lambda x: first_partial_combo_fn(x)[0], holomorphic=True)) else: raise AquaError( 'This automatic differentiation function is based on JAX. Please ' 'install jax and use `import jax.numpy as jnp` instead of ' '`import numpy as np` when defining a combo_fn.') else: if _HAS_JAX: first_partial_combo_fn = jit( grad(operator.combo_fn, holomorphic=True)) second_partial_combo_fn = jit( grad(lambda x: first_partial_combo_fn(x)[0], holomorphic=True)) else: raise AquaError( 'This automatic differentiation function is based on JAX. Please install ' 'jax and use `import jax.numpy as jnp` instead of `import numpy as np` when' 'defining a combo_fn.') # For a general combo_fn F(g_0, g_1, ..., g_k) # dF/d θ0,θ1 = sum_i: (∂F/∂g_i)•(d g_i/ d θ0,θ1) + (∂F/∂^2 g_i)•(d g_i/d θ0)•(d g_i/d # θ1) # term1 = (∂F/∂g_i)•(d g_i/ d θ0,θ1) term1 = ListOp([ ListOp(operator.oplist, combo_fn=first_partial_combo_fn), ListOp(dd_ops) ], combo_fn=lambda x: np.dot(x[1], x[0])) # term2 = (∂F/∂^2 g_i)•(d g_i/d θ0)•(d g_i/d θ1) term2 = ListOp([ ListOp(operator.oplist, combo_fn=second_partial_combo_fn), d1d0_ops ], combo_fn=lambda x: np.dot(x[1], x[0])) return SummedOp([term1, term2]) elif isinstance(operator, StateFn): if not operator.is_measurement: return self.hess_method.convert(operator, params) else: raise TypeError( 'The computation of Hessians is only supported for Operators which ' 'represent expectation values or quantum states.') else: raise TypeError( 'The computation of Hessians is only supported for Operators which ' 'represent expectation values.')
def convert(self, operator: OperatorBase, params: Optional[Dict[Parameter, Union[float, List[float], List[List[float]]]]] = None ) -> OperatorBase: r""" Converts the Operator to one in which the CircuitStateFns are replaced by DictStateFns or VectorStateFns. Extracts the CircuitStateFns out of the Operator, caches them, calls ``sample_circuits`` below to get their converted replacements, and replaces the CircuitStateFns in operator with the replacement StateFns. Args: operator: The Operator to convert params: A dictionary mapping parameters to either single binding values or lists of binding values. Returns: The converted Operator with CircuitStateFns replaced by DictStateFns or VectorStateFns. Raises: AquaError: if extracted circuits are empty. """ if self._last_op is None or id(operator) != id(self._last_op): # Clear caches self._last_op = operator self._reduced_op_cache = None self._circuit_ops_cache = None self._transpiled_circ_cache = None self._transpile_before_bind = True if not self._reduced_op_cache: operator_dicts_replaced = operator.to_circuit_op() self._reduced_op_cache = operator_dicts_replaced.reduce() if not self._circuit_ops_cache: self._circuit_ops_cache = {} self._extract_circuitstatefns(self._reduced_op_cache) if not self._circuit_ops_cache: raise AquaError( 'Circuits are empty. ' 'Check that the operator is an instance of CircuitStateFn or its ListOp.' ) if params is not None and len(params.keys()) > 0: p_0 = list(params.values())[0] # type: ignore if isinstance(p_0, (list, np.ndarray)): num_parameterizations = len(cast(List, p_0)) param_bindings = [{param: value_list[i] # type: ignore for (param, value_list) in params.items()} for i in range(num_parameterizations)] else: num_parameterizations = 1 param_bindings = [params] # type: ignore else: param_bindings = None num_parameterizations = 1 # Don't pass circuits if we have in the cache, the sampling function knows to use the cache circs = list(self._circuit_ops_cache.values()) if not self._transpiled_circ_cache else None p_b = cast(List[Dict[Parameter, float]], param_bindings) sampled_statefn_dicts = self.sample_circuits(circuit_sfns=circs, param_bindings=p_b) def replace_circuits_with_dicts(operator, param_index=0): if isinstance(operator, CircuitStateFn): return sampled_statefn_dicts[id(operator)][param_index] elif isinstance(operator, ListOp): return operator.traverse(partial(replace_circuits_with_dicts, param_index=param_index)) else: return operator if params: return ListOp([replace_circuits_with_dicts(self._reduced_op_cache, param_index=i) for i in range(num_parameterizations)]) else: return replace_circuits_with_dicts(self._reduced_op_cache, param_index=0)
def to_weighted_pauli_operator( operator: Union[WeightedPauliOperator, TPBGroupedWeightedPauliOperator, MatrixOperator]) \ -> WeightedPauliOperator: """ Converting a given operator to `WeightedPauliOperator` Args: operator: one of supported operator type Returns: The converted weighted pauli operator Raises: AquaError: Unsupported type to convert Warnings: Converting time from a MatrixOperator to a Pauli-type Operator grows exponentially. If you are converting a system with large number of qubits, it will take time. You can turn on DEBUG logging to check the progress. """ if operator.__class__ == WeightedPauliOperator: return cast(WeightedPauliOperator, operator) elif operator.__class__ == TPBGroupedWeightedPauliOperator: # destroy the grouping but keep z2 symmetries info op_tpb = cast(TPBGroupedWeightedPauliOperator, operator) return WeightedPauliOperator(paulis=op_tpb.paulis, z2_symmetries=op_tpb.z2_symmetries, name=op_tpb.name) elif operator.__class__ == MatrixOperator: op_m = cast(MatrixOperator, operator) if op_m.is_empty(): return WeightedPauliOperator(paulis=[]) if op_m.num_qubits > 10: logger.warning( "Converting time from a MatrixOperator to a Pauli-type Operator grows " "exponentially. If you are converting a system with large number of " "qubits, it will take time. And now you are converting a %s-qubit " "Hamiltonian. You can turn on DEBUG logging to check the progress." "", op_m.num_qubits) num_qubits = op_m.num_qubits coeff = 2**(-num_qubits) paulis = [] possible_basis = 'IXYZ' if op_m.dia_matrix is not None: possible_basis = 'IZ' if logger.isEnabledFor(logging.DEBUG): logger.debug( "Converting a MatrixOperator to a Pauli-type Operator:") TextProgressBar(sys.stderr) results = parallel_map(_conversion, list( itertools.product(possible_basis, repeat=num_qubits)), task_kwargs={"matrix": op_m._matrix}, num_processes=aqua_globals.num_processes) for trace_value, pauli in results: weight = trace_value * coeff if weight != 0.0 and np.abs(weight) > op_m.atol: paulis.append([weight, pauli]) return WeightedPauliOperator(paulis, z2_symmetries=operator.z2_symmetries, name=operator.name) else: raise AquaError( "Unsupported type to convert to WeightedPauliOperator: " "{}".format(operator.__class__))
def evolve(self, state_in, evo_time=0, num_time_slices=0, expansion_mode='trotter', expansion_order=1): """ Carry out the eoh evolution for the operator under supplied specifications. Args: state_in (Union(list,numpy.array)): A vector representing the initial state for the evolution evo_time (Union(complex, float)): The evolution time num_time_slices (int): The number of time slices for the expansion expansion_mode (str): The mode under which the expansion is to be done. Currently support 'trotter', which follows the expansion as discussed in http://science.sciencemag.org/content/273/5278/1073, and 'suzuki', which corresponds to the discussion in https://arxiv.org/pdf/quant-ph/0508139.pdf expansion_order (int): The order for suzuki expansion Returns: numpy.array: Return the matrix vector multiplication result. Raises: ValueError: Invalid arguments AquaError: if Operator is empty """ # pylint: disable=import-outside-toplevel from .op_converter import to_weighted_pauli_operator if self.is_empty(): raise AquaError("Operator is empty, check the operator.") # pylint: disable=no-member if num_time_slices < 0 or not isinstance(num_time_slices, int): raise ValueError( 'Number of time slices should be a non-negative integer.') if expansion_mode not in ['trotter', 'suzuki']: raise ValueError( 'Expansion mode {} not supported.'.format(expansion_mode)) if num_time_slices == 0: return scila.expm( -1.j * evo_time * self._matrix.tocsc()) @ state_in else: pauli_op = to_weighted_pauli_operator(self) pauli_list = pauli_op.reorder_paulis() if len(pauli_list) == 1: approx_matrix_slice = scila.expm( -1.j * evo_time / num_time_slices * pauli_list[0][0] * pauli_list[0][1].to_spmatrix().tocsc()) else: if expansion_mode == 'trotter': approx_matrix_slice = reduce(lambda x, y: x @ y, [ scila.expm(-1.j * evo_time / num_time_slices * c * p.to_spmatrix().tocsc()) for c, p in pauli_list ]) # suzuki expansion elif expansion_mode == 'suzuki': approx_matrix_slice = MatrixOperator._suzuki_expansion_slice_matrix( pauli_list, -1.j * evo_time / num_time_slices, expansion_order) else: raise ValueError('Unrecognized expansion mode {}.'.format( expansion_mode)) return reduce(lambda x, y: x @ y, [approx_matrix_slice] * num_time_slices) @ state_in
def get_gradient( self, operator: OperatorBase, params: Union[ParameterExpression, ParameterVector, List[ParameterExpression]] ) -> OperatorBase: """Get the gradient for the given operator w.r.t. the given parameters Args: operator: Operator w.r.t. which we take the gradient. params: Parameters w.r.t. which we compute the gradient. Returns: Operator which represents the gradient w.r.t. the given params. Raises: ValueError: If ``params`` contains a parameter not present in ``operator``. AquaError: If the coefficient of the operator could not be reduced to 1. AquaError: If the differentiation of a combo_fn requires JAX but the package is not installed. TypeError: If the operator does not include a StateFn given by a quantum circuit Exception: Unintended code is reached """ def is_coeff_c(coeff, c): if isinstance(coeff, ParameterExpression): expr = coeff._symbol_expr return expr == c return coeff == c if isinstance(params, (ParameterVector, list)): param_grads = [ self.get_gradient(operator, param) for param in params ] # If get_gradient returns None, then the corresponding parameter was probably not # present in the operator. This needs to be looked at more carefully as other things can # probably trigger a return of None. absent_params = [ params[i] for i, grad_ops in enumerate(param_grads) if grad_ops is None ] if len(absent_params) > 0: raise ValueError( 'The following parameters do not appear in the provided operator: ', absent_params) return ListOp(param_grads) # By now params is a single parameter param = params # Handle Product Rules if not is_coeff_c(operator._coeff, 1.0): # Separate the operator from the coefficient coeff = operator._coeff op = operator / coeff # Get derivative of the operator (recursively) d_op = self.get_gradient(op, param) # ..get derivative of the coeff d_coeff = self.parameter_expression_grad(coeff, param) grad_op = 0 if d_op != ~Zero @ One and not is_coeff_c(coeff, 0.0): grad_op += coeff * d_op if op != ~Zero @ One and not is_coeff_c(d_coeff, 0.0): grad_op += d_coeff * op if grad_op == 0: grad_op = ~Zero @ One return grad_op # Base Case, you've hit a ComposedOp! # Prior to execution, the composite operator was standardized and coefficients were # collected. Any operator measurements were converted to Pauli-Z measurements and rotation # circuits were applied. Additionally, all coefficients within ComposedOps were collected # and moved out front. if isinstance(operator, ComposedOp): # Gradient of an expectation value if not is_coeff_c(operator._coeff, 1.0): raise AquaError( 'Operator pre-processing failed. Coefficients were not properly ' 'collected inside the ComposedOp.') # Do some checks to make sure operator is sensible # TODO add compatibility with sum of circuit state fns if not isinstance(operator[-1], CircuitStateFn): raise TypeError( 'The gradient framework is compatible with states that are given as ' 'CircuitStateFn') return self.grad_method.convert(operator, param) elif isinstance(operator, CircuitStateFn): # Gradient of an a state's sampling probabilities if not is_coeff_c(operator._coeff, 1.0): raise AquaError( 'Operator pre-processing failed. Coefficients were not properly ' 'collected inside the ComposedOp.') return self.grad_method.convert(operator, param) # Handle the chain rule elif isinstance(operator, ListOp): grad_ops = [self.get_gradient(op, param) for op in operator.oplist] # Note: this check to see if the ListOp has a default combo_fn # will fail if the user manually specifies the default combo_fn. # I.e operator = ListOp([...], combo_fn=lambda x:x) will not pass this check and # later on jax will try to differentiate it and raise an error. # An alternative is to check the byte code of the operator's combo_fn against the # default one. if operator._combo_fn == ListOp([])._combo_fn: return ListOp(oplist=grad_ops) elif isinstance(operator, SummedOp): return SummedOp( oplist=[grad for grad in grad_ops if grad != ~Zero @ One]).reduce() elif isinstance(operator, TensoredOp): return TensoredOp(oplist=grad_ops) if operator.grad_combo_fn: grad_combo_fn = operator.grad_combo_fn else: if _HAS_JAX: grad_combo_fn = jit( grad(operator._combo_fn, holomorphic=True)) else: raise AquaError( 'This automatic differentiation function is based on JAX. Please install ' 'jax and use `import jax.numpy as jnp` instead of `import numpy as np` when' 'defining a combo_fn.') def chain_rule_combo_fn(x): result = np.dot(x[1], x[0]) if isinstance(result, np.ndarray): result = list(result) return result return ListOp([ ListOp(operator.oplist, combo_fn=grad_combo_fn), ListOp(grad_ops) ], combo_fn=chain_rule_combo_fn)