diff --git a/cirq-core/cirq/circuits/circuit.py b/cirq-core/cirq/circuits/circuit.py index 98918bee0ff..be290565cce 100644 --- a/cirq-core/cirq/circuits/circuit.py +++ b/cirq-core/cirq/circuits/circuit.py @@ -387,40 +387,26 @@ def reachable_frontier_from( ) -> Dict['cirq.Qid', int]: """Determines how far can be reached into a circuit under certain rules. - The location L = (qubit, moment_index) is *reachable* if and only if: - - a) There is not a blocking operation covering L. - - AND - - [ - b1) qubit is in start frontier and moment_index = - max(start_frontier[qubit], 0). - - OR - - b2) There is no operation at L and prev(L) = (qubit, - moment_index-1) is reachable. - - OR - - b3) There is an (non-blocking) operation P covering L such that - (q', moment_index - 1) is reachable for every q' on which P - acts. - ] - - An operation in moment moment_index is blocking if - - a) `is_blocker` returns a truthy value. - - OR - - b) The operation acts on a qubit not in start_frontier. - - OR - - c) The operation acts on a qubit q such that start_frontier[q] > - moment_index. + The location L = (qubit, moment_index) is *reachable* if and only if the + following all hold true: + + - There is not a blocking operation covering L. + - At least one of the following holds: + - qubit is in start frontier and moment_index = + max(start_frontier[qubit], 0). + - There is no operation at L and prev(L) = (qubit, + moment_index-1) is reachable. + - There is an (non-blocking) operation P covering L such that + (q', moment_index - 1) is reachable for every q' on which P + acts. + + An operation in moment moment_index is blocking if at least one of the + following hold: + + - `is_blocker` returns a truthy value. + - The operation acts on a qubit not in start_frontier. + - The operation acts on a qubit q such that start_frontier[q] > + moment_index. In other words, the reachable region extends forward through time along each qubit in start_frontier until it hits a blocking operation. Any @@ -433,12 +419,16 @@ def reachable_frontier_from( Examples: - If start_frontier is { + If start_frontier is + + { cirq.LineQubit(0): 6, cirq.LineQubit(1): 2, - cirq.LineQubit(2): 2, - } then the reachable wire locations in the following circuit are - highlighted with '█' characters: + cirq.LineQubit(2): 2 + } + + then the reachable wire locations in the following circuit are + highlighted with '█' characters: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 0: ───H───@─────────────────█████████████████████─@───H─── @@ -449,30 +439,32 @@ def reachable_frontier_from( │ │ 3: ───────────────────────@───H───@─────────────────────── - And the computed end_frontier is { + And the computed end_frontier is + + { cirq.LineQubit(0): 11, cirq.LineQubit(1): 9, cirq.LineQubit(2): 6, } - Note that the frontier indices (shown above the circuit) are - best thought of (and shown) as happening *between* moment indices. + Note that the frontier indices (shown above the circuit) are + best thought of (and shown) as happening *between* moment indices. - If we specify a blocker as follows: + If we specify a blocker as follows: - is_blocker=lambda: op == cirq.CZ(cirq.LineQubit(1), - cirq.LineQubit(2)) + is_blocker=lambda: op == cirq.CZ(cirq.LineQubit(1), + cirq.LineQubit(2)) - and use this start_frontier: + and use this start_frontier: - { + { cirq.LineQubit(0): 0, cirq.LineQubit(1): 0, cirq.LineQubit(2): 0, cirq.LineQubit(3): 0, - } + } - Then this is the reachable area: + Then this is the reachable area: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 0: ─██H███@██████████████████████████████████████─@───H─── @@ -483,14 +475,14 @@ def reachable_frontier_from( │ │ 3: ─█████████████████████─@───H───@─────────────────────── - and the computed end_frontier is: + and the computed end_frontier is: - { - cirq.LineQubit(0): 11, - cirq.LineQubit(1): 3, - cirq.LineQubit(2): 3, - cirq.LineQubit(3): 5, - } + { + cirq.LineQubit(0): 11, + cirq.LineQubit(1): 3, + cirq.LineQubit(2): 3, + cirq.LineQubit(3): 5, + } Args: start_frontier: A starting set of reachable locations. @@ -608,39 +600,28 @@ def findall_operations_until_blocked( ) -> List[Tuple[int, 'cirq.Operation']]: """Finds all operations until a blocking operation is hit. - An operation is considered blocking if - - a) It is in the 'light cone' of start_frontier. - - AND - - ( + An operation is considered blocking if both of the following hold: - 1) is_blocker returns a truthy value. - - OR - - 2) It acts on a blocked qubit. - ) + - It is in the 'light cone' of start_frontier. + - `is_blocker` returns a truthy value, or it acts on a blocked qubit Every qubit acted on by a blocking operation is thereafter itself blocked. - The notion of reachability here differs from that in reachable_frontier_from in two respects: - 1) An operation is not considered blocking only because it is in a + - An operation is not considered blocking only because it is in a moment before the start_frontier of one of the qubits on which it acts. - 2) Operations that act on qubits not in start_frontier are not + - Operations that act on qubits not in start_frontier are not automatically blocking. For every (moment_index, operation) returned: - 1) moment_index >= min((start_frontier[q] for q in operation.qubits + - moment_index >= min((start_frontier[q] for q in operation.qubits if q in start_frontier), default=0) - 2) set(operation.qubits).intersection(start_frontier) + - set(operation.qubits).intersection(start_frontier) Below are some examples, where on the left the opening parentheses show `start_frontier` and on the right are the operations included (with @@ -649,43 +630,43 @@ def findall_operations_until_blocked( the gates; `M` indicates that it doesn't matter. - ─(─F───F─────── ┄(─F───F─)┄┄┄┄┄ - │ │ │ │ - ─(─F───F───T─── => ┄(─F───F─)┄┄┄┄┄ - │ ┊ - ───────────T─── ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ + ─(─F───F─────── ┄(─F───F─)┄┄┄┄┄ + │ │ │ │ + ─(─F───F───T─── => ┄(─F───F─)┄┄┄┄┄ + │ ┊ + ───────────T─── ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ - ───M─────(─F─── ┄┄┄┄┄┄┄┄┄(─F─)┄┄ - │ │ ┊ │ - ───M───M─(─F─── ┄┄┄┄┄┄┄┄┄(─F─)┄┄ - │ => ┊ - ───────M───M─── ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ - │ ┊ - ───────────M─── ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ + ───M─────(─F─── ┄┄┄┄┄┄┄┄┄(─F─)┄┄ + │ │ ┊ │ + ───M───M─(─F─── ┄┄┄┄┄┄┄┄┄(─F─)┄┄ + │ => ┊ + ───────M───M─── ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ + │ ┊ + ───────────M─── ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ - ───M─(─────M─── ┄┄┄┄┄()┄┄┄┄┄┄┄┄ - │ │ ┊ ┊ - ───M─(─T───M─── ┄┄┄┄┄()┄┄┄┄┄┄┄┄ - │ => ┊ - ───────T───M─── ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ - │ ┊ - ───────────M─── ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ + ───M─(─────M─── ┄┄┄┄┄()┄┄┄┄┄┄┄┄ + │ │ ┊ ┊ + ───M─(─T───M─── ┄┄┄┄┄()┄┄┄┄┄┄┄┄ + │ => ┊ + ───────T───M─── ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ + │ ┊ + ───────────M─── ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ - ─(─F───F─── ┄(─F───F─)┄ - │ │ => │ │ - ───F─(─F─── ┄(─F───F─)┄ + ─(─F───F─── ┄(─F───F─)┄ + │ │ => │ │ + ───F─(─F─── ┄(─F───F─)┄ - ─(─F─────────── ┄(─F─)┄┄┄┄┄┄┄┄┄ - │ │ - ───F───F─────── ┄(─F─)┄┄┄┄┄┄┄┄┄ - │ => ┊ - ───────F───F─── ┄┄┄┄┄┄┄┄┄(─F─)┄ - │ │ - ─(─────────F─── ┄┄┄┄┄┄┄┄┄(─F─)┄ + ─(─F─────────── ┄(─F─)┄┄┄┄┄┄┄┄┄ + │ │ + ───F───F─────── ┄(─F─)┄┄┄┄┄┄┄┄┄ + │ => ┊ + ───────F───F─── ┄┄┄┄┄┄┄┄┄(─F─)┄ + │ │ + ─(─────────F─── ┄┄┄┄┄┄┄┄┄(─F─)┄ Args: start_frontier: A starting set of reachable locations. @@ -1377,33 +1358,34 @@ def zip( at the same moment index. Examples: - >>> import cirq - >>> a, b, c, d = cirq.LineQubit.range(4) - >>> circuit1 = cirq.Circuit(cirq.H(a), cirq.CNOT(a, b)) - >>> circuit2 = cirq.Circuit(cirq.X(c), cirq.Y(c), cirq.Z(c)) - >>> circuit3 = cirq.Circuit(cirq.Moment(), cirq.Moment(cirq.S(d))) - >>> print(circuit1.zip(circuit2)) - 0: ───H───@─────── - │ - 1: ───────X─────── - - 2: ───X───Y───Z─── - >>> print(circuit1.zip(circuit2, circuit3)) - 0: ───H───@─────── - │ - 1: ───────X─────── - - 2: ───X───Y───Z─── - - 3: ───────S─────── - >>> print(cirq.Circuit.zip(circuit3, circuit2, circuit1)) - 0: ───H───@─────── - │ - 1: ───────X─────── - - 2: ───X───Y───Z─── - - 3: ───────S─────── + + >>> import cirq + >>> a, b, c, d = cirq.LineQubit.range(4) + >>> circuit1 = cirq.Circuit(cirq.H(a), cirq.CNOT(a, b)) + >>> circuit2 = cirq.Circuit(cirq.X(c), cirq.Y(c), cirq.Z(c)) + >>> circuit3 = cirq.Circuit(cirq.Moment(), cirq.Moment(cirq.S(d))) + >>> print(circuit1.zip(circuit2)) + 0: ───H───@─────── + │ + 1: ───────X─────── + + 2: ───X───Y───Z─── + >>> print(circuit1.zip(circuit2, circuit3)) + 0: ───H───@─────── + │ + 1: ───────X─────── + + 2: ───X───Y───Z─── + + 3: ───────S─────── + >>> print(cirq.Circuit.zip(circuit3, circuit2, circuit1)) + 0: ───H───@─────── + │ + 1: ───────X─────── + + 2: ───X───Y───Z─── + + 3: ───────S─────── """ n = max([len(c) for c in circuits], default=0) diff --git a/cirq-core/cirq/circuits/circuit_dag.py b/cirq-core/cirq/circuits/circuit_dag.py index fa021e87d6b..bb0aec09176 100644 --- a/cirq-core/cirq/circuits/circuit_dag.py +++ b/cirq-core/cirq/circuits/circuit_dag.py @@ -32,9 +32,10 @@ class Unique(Generic[T]): For example: 5 == 5 but Unique(5) != Unique(5). Unique is used by CircuitDag to wrap operations because nodes in a graph - are considered the same node if they compare equal to each other. X(q0) - in one moment of a Circuit and X(q0) in another moment of the Circuit are - wrapped by Unique(X(q0)) so they are distinct nodes in the graph. + are considered the same node if they compare equal to each other. For + example, `X(q0)` in one moment of a circuit, and `X(q0)` in another moment + of the circuit are wrapped by `cirq.Unique(X(q0))` so they are distinct + nodes in the graph. """ def __init__(self, val: T) -> None: