Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
240 changes: 111 additions & 129 deletions cirq-core/cirq/circuits/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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───
Expand All @@ -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───
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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───────
<BLANKLINE>
2: ───X───Y───Z───
>>> print(circuit1.zip(circuit2, circuit3))
0: ───H───@───────
1: ───────X───────
<BLANKLINE>
2: ───X───Y───Z───
<BLANKLINE>
3: ───────S───────
>>> print(cirq.Circuit.zip(circuit3, circuit2, circuit1))
0: ───H───@───────
1: ───────X───────
<BLANKLINE>
2: ───X───Y───Z───
<BLANKLINE>
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───────
<BLANKLINE>
2: ───X───Y───Z───
>>> print(circuit1.zip(circuit2, circuit3))
0: ───H───@───────
1: ───────X───────
<BLANKLINE>
2: ───X───Y───Z───
<BLANKLINE>
3: ───────S───────
>>> print(cirq.Circuit.zip(circuit3, circuit2, circuit1))
0: ───H───@───────
1: ───────X───────
<BLANKLINE>
2: ───X───Y───Z───
<BLANKLINE>
3: ───────S───────
"""
n = max([len(c) for c in circuits], default=0)

Expand Down
7 changes: 4 additions & 3 deletions cirq-core/cirq/circuits/circuit_dag.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down