def test_sampler_expectation_value() -> None: c = Circuit(2) c.H(0) c.CX(0, 1) op = QubitPauliOperator({ QubitPauliString({ Qubit(0): Pauli.Z, Qubit(1): Pauli.Z }): 1.0, QubitPauliString({ Qubit(0): Pauli.X, Qubit(1): Pauli.X }): 0.3, QubitPauliString({ Qubit(0): Pauli.Z, Qubit(1): Pauli.Y }): 0.8j, QubitPauliString({Qubit(0): Pauli.Y}): -0.4j, }) b = MySampler() b.compile_circuit(c) expectation = get_operator_expectation_value(c, op, b, n_shots=2000, seed=0) assert (np.real(expectation), np.imag(expectation)) == pytest.approx( (1.3, 0.0), abs=0.1)
def test_expectation_value() -> None: c = Circuit(2) c.H(0) c.CX(0, 1) op = QubitPauliOperator({ QubitPauliString({ Qubit(0): Pauli.Z, Qubit(1): Pauli.Z }): 1.0, QubitPauliString({ Qubit(0): Pauli.X, Qubit(1): Pauli.X }): 0.3, QubitPauliString({ Qubit(0): Pauli.Z, Qubit(1): Pauli.Y }): 0.8j, QubitPauliString({Qubit(0): Pauli.Y}): -0.4j, }) b = MyBackend() b.compile_circuit(c) assert get_operator_expectation_value(c, op, b) == pytest.approx(1.3)
def tk_to_mycircuit(tkc: Circuit) -> MyCircuit: """Convert a pytket Circuit to a MyCircuit object. Supports Rz, Rx, Ry, and ZZMax gates. :param tkc: The Circuit to convert :type tkc: Circuit :return: An equivalent MyCircuit object :rtype: MyCircuit """ circ = MyCircuit(tkc.qubits) for command in tkc: optype = command.op.type if optype == OpType.Rx: circ.add_gate(QubitPauliString(command.args[0], Pauli.X), np.pi * command.op.params[0]) elif optype == OpType.Ry: circ.add_gate(QubitPauliString(command.args[0], Pauli.Y), np.pi * command.op.params[0]) elif optype == OpType.Rz: circ.add_gate(QubitPauliString(command.args[0], Pauli.Z), np.pi * command.op.params[0]) elif optype == OpType.ZZMax: circ.add_gate(QubitPauliString(command.args, [Pauli.Z, Pauli.Z]), np.pi * 0.5) else: raise ValueError("Cannot convert optype to MyCircuit: ", optype) return circ
def test_aer_placed_expectation() -> None: # bug TKET-695 n_qbs = 3 c = Circuit(n_qbs, n_qbs) c.X(0) c.CX(0, 2) c.CX(1, 2) c.H(1) # c.measure_all() b = AerBackend() operator = QubitPauliOperator({ QubitPauliString(Qubit(0), Pauli.Z): 1.0, QubitPauliString(Qubit(1), Pauli.X): 0.5, }) assert b.get_operator_expectation_value(c, operator) == (-0.5 + 0j) with open(os.path.join(sys.path[0], "ibmqx2_properties.pickle"), "rb") as f: properties = pickle.load(f) noise_model = NoiseModel.from_backend(properties) noise_b = AerBackend(noise_model) with pytest.raises(RuntimeError) as errorinfo: noise_b.get_operator_expectation_value(c, operator) assert "not supported with noise model" in str(errorinfo.value) c.rename_units({Qubit(1): Qubit("node", 1)}) with pytest.raises(ValueError) as errorinfoCirc: b.get_operator_expectation_value(c, operator) assert "default register Qubits" in str(errorinfoCirc.value)
def test_hadamard() -> None: c = MyCircuit([Qubit(0), Qubit(1)], [Bit(0), Bit(1)]) c.add_gate(QubitPauliString(Qubit(0), Pauli.Z), np.pi / 2) c.add_gate(QubitPauliString(Qubit(0), Pauli.X), np.pi / 2) c.add_gate(QubitPauliString(Qubit(0), Pauli.Z), np.pi / 2) c.add_measure(Qubit(0), Bit(0)) counts = get_counts(c, n_shots=100, seed=11) assert counts == {(0, 0): 50, (0, 1): 50}
def test_basic_ordering() -> None: # Test that final bit readouts are in the intended order (DLO) c = MyCircuit([Qubit(0)], [Bit(0), Bit(1), Bit(2)]) c.add_measure(Qubit(0), Bit(0)) c.add_gate(QubitPauliString(Qubit(0), Pauli.X), np.pi) c.add_measure(Qubit(0), Bit(2)) c.add_gate(QubitPauliString(Qubit(0), Pauli.X), np.pi / 2) c.add_measure(Qubit(0), Bit(1)) counts = get_counts(c, n_shots=100, seed=11) assert counts == {(1, 0, 0): 50, (1, 1, 0): 50}
def test_operator_statevector(qvm: None, quilc: None) -> None: c = Circuit(2, 2) c.Rz(0.5, 0) b = ForestStateBackend() zi = QubitPauliString(Qubit(0), Pauli.Z) iz = QubitPauliString(Qubit(1), Pauli.Z) op = QubitPauliOperator({zi: 0.3, iz: -0.1}) assert get_operator_expectation_value(c, op, b) == pytest.approx(0.2) c.X(0) assert get_operator_expectation_value(c, op, b) == pytest.approx(-0.4)
def test_overwrite() -> None: # Test that classical data is overwritten by later measurements appropriately c = MyCircuit([Qubit(0)], [Bit(0), Bit(1)]) c.add_measure(Qubit(0), Bit(0)) c.add_gate(QubitPauliString(Qubit(0), Pauli.X), np.pi) c.add_measure(Qubit(0), Bit(0)) c.add_measure(Qubit(0), Bit(1)) c.add_gate(QubitPauliString(Qubit(0), Pauli.X), np.pi / 2) c.add_measure(Qubit(0), Bit(1)) counts = get_counts(c, n_shots=100, seed=11) assert counts == {(0, 1): 50, (1, 1): 50}
def test_conditional_rotation() -> None: c = MyCircuit([Qubit(0)], [Bit(0), Bit(1)]) # Randomise qubit state c.add_gate(QubitPauliString(Qubit(0), Pauli.X), np.pi / 2) c.add_measure(Qubit(0), Bit(0)) # Correct qubit state to |1> - this should only happen when the measurement was 0 c.add_gate(QubitPauliString(Qubit(0), Pauli.X), np.pi, {Bit(0): 0, Bit(1): 0}) # Randomise final measurement - this should never happen c.add_gate(QubitPauliString(Qubit(0), Pauli.X), np.pi / 2, {Bit(0): 0, Bit(1): 1}) c.add_measure(Qubit(0), Bit(1)) counts = get_counts(c, n_shots=100, seed=11) assert counts == {(1, 0): 50, (1, 1): 50}
def test_operator_sim(qvm: None, quilc: None) -> None: c = Circuit(2, 2) c.Rz(0.5, 0) b = ForestBackend("9q-square") zi = QubitPauliString(Qubit(0), Pauli.Z) iz = QubitPauliString(Qubit(1), Pauli.Z) op = QubitPauliOperator({zi: 0.3, iz: -0.1}) assert get_operator_expectation_value(c, op, b, 10) == pytest.approx(0.2, rel=0.001) c.X(0) assert get_operator_expectation_value(c, op, b, 10) == pytest.approx(-0.4, rel=0.001)
def test_expectation() -> None: b = BraketBackend(local=True) assert b.supports_expectation c = Circuit(2, 2) c.Rz(0.5, 0) zi = QubitPauliString(Qubit(0), Pauli.Z) iz = QubitPauliString(Qubit(1), Pauli.Z) op = QubitPauliOperator({zi: 0.3, iz: -0.1}) assert get_pauli_expectation_value(c, zi, b) == 1 assert get_operator_expectation_value(c, op, b) == pytest.approx(0.2) c.X(0) assert get_pauli_expectation_value(c, zi, b) == -1 assert get_operator_expectation_value(c, op, b) == pytest.approx(-0.4)
def test_simulator() -> None: b = BraketBackend( s3_bucket=S3_BUCKET, s3_folder=S3_FOLDER, device_type="quantum-simulator", provider="amazon", device="sv1", ) assert b.supports_shots c = Circuit(2).H(0).CX(0, 1) b.compile_circuit(c) n_shots = 100 h0, h1 = b.process_circuits([c, c], n_shots) res0 = b.get_result(h0) readouts = res0.get_shots() assert all(readouts[i][0] == readouts[i][1] for i in range(n_shots)) res1 = b.get_result(h1) counts = res1.get_counts() assert len(counts) <= 2 assert sum(counts.values()) == n_shots zi = QubitPauliString(Qubit(0), Pauli.Z) assert b.get_pauli_expectation_value( c, zi, poll_timeout_seconds=60, poll_interval_seconds=1 ) == pytest.approx(0) # Circuit with unused qubits c = Circuit(3).H(1).CX(1, 2) b.compile_circuit(c) h = b.process_circuit(c, 1) res = b.get_result(h) readout = res.get_shots()[0] assert readout[1] == readout[2]
def qps_from_openfermion(paulis): # translate from openfermion format to a QubitPauliString qlist = [] plist = [] for q, p in paulis: qlist.append(Qubit(q)) plist.append(pauli_sym[p]) return QubitPauliString(qlist, plist)
def test_device() -> None: c = circuit_gen(False) b = IBMQBackend("ibmq_santiago", hub="ibm-q", group="open", project="main") assert b._max_per_job == 75 operator = QubitPauliOperator({ QubitPauliString(Qubit(0), Pauli.Z): 1.0, QubitPauliString(Qubit(0), Pauli.X): 0.5, }) val = get_operator_expectation_value(c, operator, b, 8000) print(val) c1 = circuit_gen(True) c2 = circuit_gen(True) b.compile_circuit(c1) b.compile_circuit(c2) print(b.get_shots(c1, n_shots=10)) print(b.get_shots(c2, n_shots=10))
def test_operator() -> None: c = circuit_gen() b = ProjectQBackend() zz = QubitPauliOperator( {QubitPauliString([Qubit(0), Qubit(1)], [Pauli.Z, Pauli.Z]): 1.0}) assert np.isclose(get_operator_expectation_value(c, zz, b), complex(1.0)) c.X(0) assert np.isclose(get_operator_expectation_value(c, zz, b), complex(-1.0))
def test_pauli_statevector(qvm: None, quilc: None) -> None: c = Circuit(2, 2) c.Rz(0.5, 0) b = ForestStateBackend() zi = QubitPauliString(Qubit(0), Pauli.Z) assert get_pauli_expectation_value(c, zi, b) == 1 c.X(0) assert get_pauli_expectation_value(c, zi, b) == -1
def test_operator() -> None: for b in [AerBackend(), AerStateBackend()]: c = circuit_gen() zz = QubitPauliOperator( {QubitPauliString([Qubit(0), Qubit(1)], [Pauli.Z, Pauli.Z]): 1.0}) assert cmath.isclose(get_operator_expectation_value(c, zz, b), 1.0) c.X(0) assert cmath.isclose(get_operator_expectation_value(c, zz, b), -1.0)
def test_pauli() -> None: c = Circuit(2) c.Rz(0.5, 0) b = ProjectQBackend() zi = QubitPauliString({Qubit(0): Pauli.Z}) assert np.isclose(get_pauli_expectation_value(c, zi, b), complex(1)) c.X(0) assert np.isclose(get_pauli_expectation_value(c, zi, b), complex(-1))
def qps_from_openfermion(paulis: QubitOperator) -> QubitPauliString: """ Utility function to translate from openfermion format to a QubitPauliString """ qlist = [] plist = [] for q, p in paulis: qlist.append(Qubit(q)) plist.append(pauli_sym[p]) return QubitPauliString(qlist, plist)
def test_pauli_statevector() -> None: c = Circuit(2) c.Rz(0.5, 0) Transform.OptimisePostRouting().apply(c) b = AerStateBackend() zi = QubitPauliString(Qubit(0), Pauli.Z) assert get_pauli_expectation_value(c, zi, b) == 1 c.X(0) assert get_pauli_expectation_value(c, zi, b) == -1
def test_pauli() -> None: for b in [AerBackend(), AerStateBackend()]: c = Circuit(2) c.Rz(0.5, 0) b.compile_circuit(c) zi = QubitPauliString(Qubit(0), Pauli.Z) assert cmath.isclose(get_pauli_expectation_value(c, zi, b), 1) c.X(0) assert cmath.isclose(get_pauli_expectation_value(c, zi, b), -1)
def test_pauli_sim(qvm: None, quilc: None) -> None: c = Circuit(2, 2) c.Rz(0.5, 0) b = ForestBackend("9q-square") zi = QubitPauliString(Qubit(0), Pauli.Z) energy = get_pauli_expectation_value(c, zi, b, 10) assert abs(energy - 1) < 0.001 c.X(0) energy = get_pauli_expectation_value(c, zi, b, 10) assert abs(energy + 1) < 0.001
def test_pauli_sim() -> None: c = Circuit(2, 2) c.Rz(0.5, 0) Transform.OptimisePostRouting().apply(c) b = AerBackend() zi = QubitPauliString(Qubit(0), Pauli.Z) energy = get_pauli_expectation_value(c, zi, b, 8000) assert abs(energy - 1) < 0.001 c.X(0) energy = get_pauli_expectation_value(c, zi, b, 8000) assert abs(energy + 1) < 0.001
def test_conditional_measurement() -> None: c = MyCircuit([Qubit(0), Qubit(1)], [Bit(0), Bit(1), Bit(2), Bit(3)]) # Get a random number c.add_gate(QubitPauliString(Qubit(0), Pauli.X), np.pi / 2) c.add_measure(Qubit(0), Bit(0)) # If random number is 0 then flip to give |1> # Otherwise, randomly generate |+i> or |-i> c.add_gate(QubitPauliString(Qubit(0), Pauli.X), np.pi / 2) c.add_measure(Qubit(0), Bit(1), {Bit(0): 1}) c.add_gate(QubitPauliString(Qubit(0), Pauli.X), np.pi / 2) # Deterministic if Bit(0) == 0, random if Bit(0) == 1 c.add_measure(Qubit(0), Bit(2)) # Test end-of-circuit conditions by copying Bit(2) to Bit(3) c.add_gate(QubitPauliString(Qubit(1), Pauli.X), np.pi) c.add_measure(Qubit(1), Bit(3), {Bit(2): 1}) counts = get_counts(c, n_shots=10000, seed=11) assert counts[(1, 1, 0, 0)] == pytest.approx(5000, rel=0.02) assert counts[(0, 0, 0, 1)] == pytest.approx(1250, rel=0.02) assert counts[(1, 1, 0, 1)] == pytest.approx(1250, rel=0.02) assert counts[(0, 0, 1, 1)] == pytest.approx(1250, rel=0.02) assert counts[(1, 1, 1, 1)] == pytest.approx(1250, rel=0.02)
def test_variance() -> None: b = BraketBackend(local=True) assert b.supports_variance # - Prepare a state (1/sqrt(2), 1/sqrt(2)). # - Measure w.r.t. the operator Z which has evcs (1,0) (evl=+1) and (0,1) (evl=-1). # - Get +1 with prob. 1/2 and -1 with prob. 1/2. c = Circuit(1).H(0) z = QubitPauliString(Qubit(0), Pauli.Z) assert b.get_pauli_expectation_value(c, z) == pytest.approx(0) assert b.get_pauli_variance(c, z) == pytest.approx(1) op = QubitPauliOperator({z: 3}) assert b.get_operator_expectation_value(c, op) == pytest.approx(0) assert b.get_operator_variance(c, op) == pytest.approx(9)
def test_moments_with_shots() -> None: b = BraketBackend(local=True) c = Circuit(1).H(0) z = QubitPauliString(Qubit(0), Pauli.Z) e = b.get_pauli_expectation_value(c, z, n_shots=10) assert abs(e) <= 1 v = b.get_pauli_variance(c, z, n_shots=10) assert v <= 1 op = QubitPauliOperator({z: 3}) e = b.get_operator_expectation_value(c, op, n_shots=10) assert abs(e) <= 3 v = b.get_operator_variance(c, op, n_shots=10) assert v <= 9
def tk_to_mymeasures(tkc: Circuit) -> Tuple[MyCircuit, Dict[Qubit, Bit]]: """Convert a pytket Circuit to a MyCircuit object and a measurement map. Supports Rz, Rx, Ry, and ZZMax gates, as well as end-of-circuit measurements. :param tkc: The Circuit to convert :type tkc: Circuit :return: An equivalent MyCircuit object and a map from measured Qubit to the Bit containing the result :rtype: Tuple[MyCircuit, Dict[Qubit, Bit]] """ circ = MyCircuit(tkc.qubits) measure_map = dict() measured_units = ( set() ) # Track measured Qubits/used Bits to identify mid-circuit measurement for command in tkc: for u in command.args: if u in measured_units: raise ValueError("Circuit contains a mid-circuit measurement") optype = command.op.type if optype == OpType.Rx: circ.add_gate(QubitPauliString(command.args[0], Pauli.X), np.pi * command.op.params[0]) elif optype == OpType.Ry: circ.add_gate(QubitPauliString(command.args[0], Pauli.Y), np.pi * command.op.params[0]) elif optype == OpType.Rz: circ.add_gate(QubitPauliString(command.args[0], Pauli.Z), np.pi * command.op.params[0]) elif optype == OpType.ZZMax: circ.add_gate(QubitPauliString(command.args, [Pauli.Z, Pauli.Z]), np.pi * 0.5) elif optype == OpType.Measure: measure_map[command.args[0]] = command.args[1] measured_units.add(command.args[0]) measured_units.add(command.args[1]) else: raise ValueError("Cannot convert optype to MyCircuit: ", optype) return circ, measure_map
def test_bell() -> None: c = MyCircuit([Qubit(0), Qubit(1)], [Bit(0), Bit(1)]) # Hadamard Q0 c.add_gate(QubitPauliString(Qubit(0), Pauli.Z), np.pi / 2) c.add_gate(QubitPauliString(Qubit(0), Pauli.X), np.pi / 2) c.add_gate(QubitPauliString(Qubit(0), Pauli.Z), np.pi / 2) # CX Q0 Q1 c.add_gate(QubitPauliString(Qubit(0), Pauli.Z), -np.pi / 2) c.add_gate(QubitPauliString(Qubit(1), Pauli.X), -np.pi / 2) c.add_gate(QubitPauliString([Qubit(0), Qubit(1)], [Pauli.Z, Pauli.X]), np.pi / 2) c.add_measure(Qubit(0), Bit(0)) c.add_measure(Qubit(1), Bit(1)) counts = get_counts(c, n_shots=100, seed=11) assert counts == {(0, 0): 50, (1, 1): 50}
from pytket.utils import QubitPauliOperator from pytket.utils.expectations import get_operator_expectation_value from pytket.extensions.qiskit import AerBackend, AerStateBackend # First, let's get some results on a toy circuit without using any measurement reduction: shots_backend = AerBackend() n_shots = 10000 c = Circuit(5) c.H(4) c.V(2) shots_backend.compile_circuit(c) op = QubitPauliOperator({ QubitPauliString([Qubit(0)], [Pauli.Z]): 0.1, QubitPauliString( [Qubit(0), Qubit(1), Qubit(2), Qubit(3), Qubit(4)], [Pauli.Y, Pauli.Z, Pauli.X, Pauli.X, Pauli.Y], ): 0.4, QubitPauliString([Qubit(0), Qubit(1)], [Pauli.X, Pauli.X]): 0.2, }) shots_result = get_operator_expectation_value(c, op, shots_backend, n_shots) print(shots_result) # The result should be around 0.1, although as the shot simulator is stochastic this will be inexact. Let's test to check what the exact result should be using the statevector simulator:
def sample(self, n_shots: int, seed: Optional[int] = None) -> np.ndarray: """Sample from the final classical distribution. For each sample, will pick a branch for each internal measurement and traverse the simulation tree until the end-of-circuit measurements are reached. The tree caches the state of the simulation for each branch to reuse for later shots. :param n_shots: The number of samples to take :type n_shots: int :param seed: Seed for the random sampling, defaults to None :type seed: Optional[int], optional :return: Shot table with each row corresponding to a shot. Columns correspond to Bits in decreasing lexicographical order, i.e. [0, 1] corresponds to {Bit(0) : 1, Bit(1) : 0} :rtype: np.ndarray """ if seed is not None: np.random.seed(seed) table = np.zeros((n_shots, len(self.bits)), dtype=int) for s in range(n_shots): # Uniformly select a random point from the measurement distribution point = np.random.uniform(0.0, 1.0) # Traverse the tree until we reach the end-of-circuit measurements current_node = self.tree # The range of values `point` could take to end up in the current node current_lower = 0.0 current_upper = 1.0 while not isinstance(current_node.data, CompleteNode): if isinstance(current_node.data, IncompleteNode): # When at an IncompleteNode, there are no future branches already considered # but we still have computation to simulate on this branch # Simulate new gates until either a mid-circuit measurement # or we reach the end of the internal gates while True: try: next_gate = next(current_node.data.gate_iter) except StopIteration: # There are no more internal gates, so cumulative probabilities for # the final measurements of every qubit cum_probs = ( current_node.data.qstate * current_node.data.qstate.conjugate() ).cumsum() current_node.data = CompleteNode( cum_probs, current_node.data.cstate ) break # Skip the gate if the classical condition is not met if next_gate.condition: condition_met = True for b, v in next_gate.condition.items(): bi = self.bits.index(b) if current_node.data.cstate[bi] != v: condition_met = False break if not condition_met: continue if isinstance(next_gate, Rotation): # Apply the rotation to the quantum state and continue to the next gate pauli_tensor = next_gate.qps.to_sparse_matrix(self.qubits) exponent = -0.5 * next_gate.angle current_node.data.qstate = np.cos( exponent ) * current_node.data.qstate + 1j * np.sin( exponent ) * pauli_tensor.dot( current_node.data.qstate ) else: # Otherwise, we have a measurement # Compute the states after a 0-outcome and a 1-outcome and make a branch # Project into measurement subspaces identity = QubitPauliString().to_sparse_matrix(self.qubits) z_op = QubitPauliString( next_gate.qubit, Pauli.Z ).to_sparse_matrix(self.qubits) zero_proj = 0.5 * (identity + z_op) one_proj = 0.5 * (identity - z_op) zero_state = zero_proj.dot(current_node.data.qstate) one_state = one_proj.dot(current_node.data.qstate) # Find probability of measurement and normalise zero_prob = np.vdot(zero_state, zero_state) if zero_prob >= 1e-10: # Prevent divide-by-zero errors zero_state *= 1 / np.sqrt(zero_prob) if 1 - zero_prob >= 1e-10: one_state *= 1 / np.sqrt(1 - zero_prob) # Update the classical state for each outcome bit_index = self.bits.index(next_gate.bit) zero_cstate = copy(current_node.data.cstate) zero_cstate[bit_index] = 0 one_cstate = current_node.data.cstate one_cstate[bit_index] = 1 # Replace current node in the tree by a branch, with each outcome as children zero_node = Node(0) zero_node.data = IncompleteNode( zero_state, zero_cstate, copy(current_node.data.gate_iter), ) one_node = Node(0) one_node.data = IncompleteNode( one_state, one_cstate, current_node.data.gate_iter ) current_node.data = InternalNode(zero_prob) current_node.left = zero_node current_node.right = one_node break else: # Reached an internal measurement, so randomly pick a branch to traverse current_decision = ( current_lower + (current_upper - current_lower) * current_node.data.zero_prob ) if point < current_decision: current_node = current_node.left current_upper = current_decision else: current_node = current_node.right current_lower = current_decision # Finally reached the end of the circuit # Randomly sample from the final measurements index = np.searchsorted( current_node.data.cum_probs, point / (current_upper - current_lower) ) bitstring = bin(index)[2:].zfill(len(self.qubits)) # Update the classical state with final measurement outcomes table[s] = current_node.data.cstate for g in self.end_measures: if g.condition: # If final measurements are conditioned, the classical state may not be updated condition_met = True for b, v in g.condition.items(): bi = self.bits.index(b) if current_node.data.cstate[bi] != v: condition_met = False break if not condition_met: continue qi = self.qubits.index(g.qubit) bi = self.bits.index(g.bit) table[s, bi] = int(bitstring[qi]) return table