def __init__( self, symmetries: List[Pauli], sq_paulis: List[Pauli], sq_list: List[int], tapering_values: Optional[List[int]] = None, ): """ Args: symmetries: the list of Pauli objects representing the Z_2 symmetries sq_paulis: the list of single - qubit Pauli objects to construct the Clifford operators sq_list: the list of support of the single-qubit Pauli objects used to build the Clifford operators tapering_values: values determines the sector. Raises: OpflowError: Invalid paulis """ if len(symmetries) != len(sq_paulis): raise OpflowError( "Number of Z2 symmetries has to be the same as number " "of single-qubit pauli x." ) if len(sq_paulis) != len(sq_list): raise OpflowError( "Number of single-qubit pauli x has to be the same " "as length of single-qubit list." ) if tapering_values is not None: if len(sq_list) != len(tapering_values): raise OpflowError( "The length of single-qubit list has " "to be the same as length of tapering values." ) self._symmetries = symmetries self._sq_paulis = sq_paulis self._sq_list = sq_list self._tapering_values = tapering_values
def permute(self, permutation: List[int]) -> 'OperatorBase': """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: OpflowError: 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 OpflowError("New index must be defined for each qubit of the operator.") except ValueError: raise OpflowError("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=cyclic-import from ..primitive_ops.circuit_op import CircuitOp return CircuitOp(qc.reverse_ops()) @ new_self @ CircuitOp(qc)
def permute(self, permutation: List[int]) -> 'DictStateFn': new_num_qubits = max(permutation) + 1 if self.num_qubits != len(permutation): raise OpflowError("New index must be defined for each qubit of the operator.") # helper function to permute the key def perm(key): list_key = ['0'] * new_num_qubits for i, k in enumerate(permutation): list_key[k] = key[i] return ''.join(list_key) new_dict = {perm(key): value for key, value in self.primitive.items()} return DictStateFn(new_dict, coeff=self.coeff, is_measurement=self.is_measurement)
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: OpflowError: if indices do not define a new index for each qubit. """ set_perm = set(permutation) if len(set_perm) != len(permutation) or any(index < 0 for index in set_perm): raise OpflowError(f"List {permutation} is not a permutation.") if len(permutation) != self.num_qubits: raise OpflowError( "List of indices to permute must have the same size as Pauli Operator" ) length = max(permutation) + 1 if length > self.num_qubits: spop = self.primitive.tensor( SparsePauliOp(Pauli("I" * (length - self.num_qubits)))) else: spop = self.primitive.copy() permutation = [i for i in range(length) if i not in permutation ] + permutation permu_arr = np.arange(length)[np.argsort(permutation)] spop.paulis.x = spop.paulis.x[:, permu_arr] spop.paulis.z = spop.paulis.z[:, permu_arr] return PauliSumOp(spop, self.coeff)
def group_subops(cls, list_op: Union[ListOp, PauliSumOp]) -> ListOp: """Given a ListOp, attempt to group into Abelian ListOps of the same type. Args: list_op: The Operator to group into Abelian groups Returns: The grouped Operator. Raises: OpflowError: If any of list_op's sub-ops is not ``PauliOp``. """ if isinstance(list_op, ListOp): for op in list_op.oplist: if not isinstance(op, PauliOp): raise OpflowError( "Cannot determine Abelian groups if any Operator in list_op is not " f"`PauliOp`. E.g., {op} ({type(op)})") edges = cls._anti_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 = defaultdict(list) for idx, color in coloring_dict.items(): groups[color].append(idx) if isinstance(list_op, PauliSumOp): primitive = list_op.primitive return SummedOp( [ PauliSumOp(primitive[group], grouping_type="TPB") for group in groups.values() ], coeff=list_op.coeff, ) group_ops: List[ListOp] = [ list_op.__class__([list_op[idx] for idx in group], abelian=True) for group in groups.values() ] if len(group_ops) == 1: return group_ops[0].mul(list_op.coeff) return list_op.__class__(group_ops, coeff=list_op.coeff)
def to_circuit(self) -> QuantumCircuit: """Returns the quantum circuit, representing the tensored operator. Returns: The circuit representation of the tensored operator. Raises: OpflowError: for operators where a single underlying circuit can not be produced. """ circuit_op = self.to_circuit_op() # pylint: disable=cyclic-import from ..state_fns.circuit_state_fn import CircuitStateFn from ..primitive_ops.primitive_op import PrimitiveOp if isinstance(circuit_op, (PrimitiveOp, CircuitStateFn)): return circuit_op.to_circuit() raise OpflowError('Conversion to_circuit supported only for operators, where a single ' 'underlying circuit can be produced.')
def consistent_tapering(self, operator: PauliSumOp) -> OperatorBase: """ Tapering the `operator` with the same manner of how this tapered operator is created. i.e., using the same Cliffords and tapering values. Args: operator: the to-be-tapered operator Returns: The tapered operator Raises: OpflowError: The given operator does not commute with the symmetry """ for symmetry in self._symmetries: commutator_op = cast(PauliSumOp, commutator(operator, PauliOp(symmetry))) if not commutator_op.is_zero(): raise OpflowError( "The given operator does not commute with " "the symmetry, can not taper it." ) return self.taper(operator)
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 parameterized PrimitiveOps (in that case, OpflowError 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: OpflowError: if SummedOp can not be converted to MatrixOp (e.g. SummedOp is composed of parameterized PrimitiveOps). """ # pylint: disable=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 OpflowError("The SummedOp can not be converted to circuit, because to_matrix_op did " "not return a MatrixOp.")
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: OpflowError: 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 OpflowError("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(''.join(new_pauli_list)), self.coeff)
def taper(self, operator: PauliSumOp) -> OperatorBase: """ Taper an operator based on the z2_symmetries info and sector defined by `tapering_values`. The `tapering_values` will be stored into the resulted operator for a record. Args: operator: the to-be-tapered operator. Returns: If tapering_values is None: [:class`PauliSumOp`]; otherwise, :class:`PauliSumOp` Raises: OpflowError: Z2 symmetries, single qubit pauli and single qubit list cannot be empty """ if not self._symmetries or not self._sq_paulis or not self._sq_list: raise OpflowError( "Z2 symmetries, single qubit pauli and single qubit list cannot be empty." ) # If the operator is zero then we can skip the following. We still need to taper the # operator to reduce its size i.e. the number of qubits so for example 0*"IIII" could # taper to 0*"II" when symmetries remove two qubits. if not operator.is_zero(): for clifford in self.cliffords: operator = cast(PauliSumOp, clifford @ operator @ clifford) operator = operator.reduce(atol=0) if self._tapering_values is None: tapered_ops_list = [ self._taper(operator, list(coeff)) for coeff in itertools.product([1, -1], repeat=len(self._sq_list)) ] tapered_ops: OperatorBase = ListOp(tapered_ops_list) else: tapered_ops = self._taper(operator, self._tapering_values) return tapered_ops
def taper(self, operator: PauliSumOp) -> OperatorBase: """ Taper an operator based on the z2_symmetries info and sector defined by `tapering_values`. The `tapering_values` will be stored into the resulted operator for a record. Args: operator: the to-be-tapered operator. Returns: If tapering_values is None: [:class`PauliSumOp`]; otherwise, :class:`PauliSumOp` Raises: OpflowError: Z2 symmetries, single qubit pauli and single qubit list cannot be empty """ if not self._symmetries or not self._sq_paulis or not self._sq_list: raise OpflowError( "Z2 symmetries, single qubit pauli and single qubit list cannot be empty." ) if operator.is_zero(): logger.warning( "The operator is empty, return the empty operator directly.") return operator for clifford in self.cliffords: operator = cast(PauliSumOp, clifford @ operator @ clifford) if self._tapering_values is None: tapered_ops_list = [ self._taper(operator, list(coeff)) for coeff in itertools.product([1, -1], repeat=len(self._sq_list)) ] tapered_ops: OperatorBase = ListOp(tapered_ops_list) else: tapered_ops = self._taper(operator, self._tapering_values) return tapered_ops
def __init__( self, primitive: Union[OperatorBase] = None, alpha: float = 1.0, coeff: Union[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]. OpflowError: 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 OpflowError( "Input operator to CVaRMeasurement must be diagonal, but is not:", str(primitive)) super().__init__(primitive, coeff=coeff, is_measurement=True)
def to_instruction(self, massive: bool = False) -> Instruction: mat_op = self.to_matrix_op(massive=massive) if not isinstance(mat_op, MatrixOp): raise OpflowError("to_instruction is not allowed for ListOp.") return mat_op.to_instruction()
def sample_circuits( self, circuit_sfns: Optional[List[CircuitStateFn]] = None, param_bindings: Optional[List[Dict[Parameter, float]]] = None, ) -> Dict[int, 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: OpflowError: if extracted circuits are empty. """ if not circuit_sfns and not self._transpiled_circ_cache: raise OpflowError("CircuitStateFn is empty and there is no cache.") if circuit_sfns: self._transpiled_circ_templates = None if self._statevector or circuit_sfns[0].from_operator: 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, pass_manager=self.quantum_instance.unbound_pass_manager ) 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 # run transpiler passes on bound circuits if self._transpile_before_bind and self.quantum_instance.bound_pass_manager is not None: ready_circs = self.quantum_instance.transpile( ready_circs, pass_manager=self.quantum_instance.bound_pass_manager ) 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: avg = circ_results["expval_measurement"] # Will be replaced with just avg when eval is called later num_qubits = circuit_sfns[0].num_qubits result_sfn = DictStateFn( "0" * num_qubits, coeff=avg * op_c.coeff, is_measurement=op_c.is_measurement, from_operator=op_c.from_operator, ) 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 = DictStateFn( { b: (v / shots) ** 0.5 * op_c.coeff for (b, v) in results.get_counts(circ_index).items() }, is_measurement=op_c.is_measurement, from_operator=op_c.from_operator, ) 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 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: OpflowError: if extracted circuits are empty. """ # check if the operator should be cached op_id = operator.instance_id # op_id = id(operator) if op_id not in self._cached_ops.keys(): # delete cache if we only want to cache one operator if self._caching == "last": self.clear_cache() # convert to circuit and reduce operator_dicts_replaced = operator.to_circuit_op() self._reduced_op_cache = operator_dicts_replaced.reduce() # extract circuits self._circuit_ops_cache = {} self._extract_circuitstatefns(self._reduced_op_cache) if not self._circuit_ops_cache: raise OpflowError( "Circuits are empty. " "Check that the operator is an instance of CircuitStateFn or its ListOp." ) self._transpiled_circ_cache = None self._transpile_before_bind = True else: # load the cached circuits self._reduced_op_cache = self._cached_ops[op_id].reduced_op_cache self._circuit_ops_cache = self._cached_ops[op_id].circuit_ops_cache self._transpiled_circ_cache = self._cached_ops[op_id].transpiled_circ_cache self._transpile_before_bind = self._cached_ops[op_id].transpile_before_bind self._transpiled_circ_templates = self._cached_ops[op_id].transpiled_circ_templates return_as_list = False if params is not None and len(params.keys()) > 0: p_0 = list(params.values())[0] if isinstance(p_0, (list, np.ndarray)): num_parameterizations = len(p_0) param_bindings = [ {param: value_list[i] for param, value_list in params.items()} # type: ignore for i in range(num_parameterizations) ] return_as_list = True else: num_parameterizations = 1 param_bindings = [params] 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 # store the operator we constructed, if it isn't stored already if op_id not in self._cached_ops.keys(): op_cache = OperatorCache() op_cache.reduced_op_cache = self._reduced_op_cache op_cache.circuit_ops_cache = self._circuit_ops_cache op_cache.transpiled_circ_cache = self._transpiled_circ_cache op_cache.transpile_before_bind = self._transpile_before_bind op_cache.transpiled_circ_templates = self._transpiled_circ_templates self._cached_ops[op_id] = op_cache if return_as_list: 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)