Skip to content

Conversation

@codrut3
Copy link
Contributor

@codrut3 codrut3 commented Dec 14, 2025

Scans forward from a given insert location and inserts an operation into the latest possible moment. If no moment is available, inserts the operation in a new moment.

Fixes #7611

Scans forward from a given insert location and inserts an operation
into the latest possible moment. If no moment is available, inserts
the operation in a new moment.
@codrut3 codrut3 requested review from a team and vtomole as code owners December 14, 2025 14:06
@codrut3 codrut3 requested a review from dabacon December 14, 2025 14:06
@github-actions github-actions bot added the size: M 50< lines changed <250 label Dec 14, 2025
@codrut3
Copy link
Contributor Author

codrut3 commented Dec 14, 2025

I updated circuit.insert to support the LATEST strategy.

Handling a single operation is rather straightforward. However, insert allows inserting an operation tree, so I spent some time thinking the best way to do this:

  • I reverse the order in which operations are inserted (starting from last). Otherwise for something like [cirq.X(q[0]), cirq.Y(q[0])] the result of inserting in direct order with LATEST would be [cirq.Y(q[0]), cirq.X(q[0])] which doesn't make sense to me.
  • The insert method still returns the moment index after the latest inserted operation. If someone calls insert repeatedly for consecutive operations, using this value as the new insert index makes sense.
  • Moments or operations are never inserted before the starting index k. This is different from EARLIEST, where the first batch is inserted in moments up to k, but the next batch can potentially be put in moment k+1, the second in moment k+2, and so on. If I can't insert at k, I add a new moment (which will then be the moment at index k).

@codecov
Copy link

codecov bot commented Dec 14, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.57%. Comparing base (c8d141b) to head (07a3649).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #7804   +/-   ##
=======================================
  Coverage   99.57%   99.57%           
=======================================
  Files        1102     1102           
  Lines       98638    98710   +72     
=======================================
+ Hits        98214    98290   +76     
+ Misses        424      420    -4     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Collaborator

@NoureldinYosri NoureldinYosri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM but I would like @pavoljuhas to take a look since this is a big change to circuit construction

the right is available, or `len(self._moments)` if `start_moment_index` equals it.
"""
if start_moment_index is None:
start_moment_index = 0
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why note set the default to zero ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 Since _latest_available_moment is an internal method with only one in-class caller, let us make the start_moment_index argument mandatory.

if moment.operates_on(op_qubits):
return k - 1
moment_measurement_keys = moment._measurement_key_objs_()
if (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[super optional] nit: turn the not a or not b or not c into not (a and b and c)

Copy link
Collaborator

@pavoljuhas pavoljuhas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with a couple of smallish adjustments, please see inline comments.

Thank you for taking care of this!

the right is available, or `len(self._moments)` if `start_moment_index` equals it.
"""
if start_moment_index is None:
start_moment_index = 0
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 Since _latest_available_moment is an internal method with only one in-class caller, let us make the start_moment_index argument mandatory.

def _insert_latest(self, k: int, batches: list[list[_MOMENT_OR_OP]]) -> int:
"""Inserts batches of moments or operations using LATEST strategy.
Batches are inserted into reverse order.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Batches are inserted into reverse order.
Batches are processed in reverse order.

Comment on lines +2215 to +2217
batches = batches[::-1]
max_latest_index = -1 # Maximum index of a changed moment
for batch in batches:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with a couple of suggestions:

  1. the corner case c.insert(index, []) returns index with the EARLIEST strategy, but 0 with the LATEST. Let us make this consistent, ie, return index for either strategy. Please also add a test for this case.

  2. I found it a bit tricky to follow the _insert_latest code with Moment and Operation blocks interspersed. Would you mind applying the patch below which regroups them to (a) Moment-only and (b) Operation only blocks?

diff --git a/cirq-core/cirq/circuits/circuit.py b/cirq-core/cirq/circuits/circuit.py
index 74c08354..6ecfcb41 100644
--- a/cirq-core/cirq/circuits/circuit.py
+++ b/cirq-core/cirq/circuits/circuit.py
@@ -2214,5 +2214,4 @@ class Circuit(AbstractCircuit):
         """
-        batches = batches[::-1]
         max_latest_index = -1  # Maximum index of a changed moment
-        for batch in batches:
+        for batch in reversed(batches):
             for moment_or_op in batch:
@@ -2220,22 +2219,22 @@ class Circuit(AbstractCircuit):
                 if isinstance(moment_or_op, Moment):
-                    p = k
+                    self._moments.insert(k, moment_or_op)
+                    max_latest_index = max(k, max_latest_index + 1)
                 else:
+                    end_moment_index = len(self.moments)
                     p = self._latest_available_moment(moment_or_op, start_moment_index=k)
                     if p < k:
-                        self._moments.insert(k, Moment())
-                        # All later moments shift by 1, so increase the max index
-                        max_latest_index += 1
-                        p = k
-                # Place
-                if isinstance(moment_or_op, Moment):
-                    self._moments.insert(p, moment_or_op)
-                    # All later moments shift by 1, so increase the max index
-                    max_latest_index += 1
-                elif p == len(self._moments):
-                    self._moments.append(Moment(moment_or_op))
-                else:
-                    self._moments[p] = self._moments[p].with_operation(moment_or_op)
-                max_latest_index = max(p, max_latest_index)
-        self._mutated(preserve_placement_cache=False)
-        return max_latest_index + 1
+                        self._moments.insert(k, Moment.from_ops(moment_or_op))
+                        max_latest_index = max(k, max_latest_index + 1)
+                    elif p < end_moment_index:
+                        self._moments[p] = self._moments[p].with_operation(moment_or_op)
+                        max_latest_index = max(p, max_latest_index)
+                    else:
+                        assert p == end_moment_index
+                        self._moments.append(Moment.from_ops(moment_or_op))
+                        max_latest_index = end_moment_index
+        # handle returned position index for empty batch
+        pos = k if max_latest_index == -1 else max_latest_index + 1
+        if max_latest_index != -1:
+            self._mutated(preserve_placement_cache=False)
+        return pos
 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size: M 50< lines changed <250

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: Add LATEST InsertStrategy to complement EARLIEST

3 participants