Skip to content

Conversation

@minestarks
Copy link
Member

@minestarks minestarks commented Dec 31, 2025

Feature changes

Loops from user source are now grouped in circuit diagrams.

BEFORE:

image
[email protected]:4:20 ─ [email protected]:6:24 ─── [email protected]:6:24 ─── [email protected]:6:24 ──

AFTER:

image image
[email protected]:4:20 ─ [ [Main] ─── [ [loop: [email protected]:5:20] ── [ [(1)@test.qs:5:34] ─── [ [[email protected]:6:24] ─── [email protected]:11:20 ── [email protected]:12:20 ─── ] ──── ] ─── [ [(2)@test.qs:5:34] ─── [ [[email protected]:6:24] ─── [email protected]:11:20 ── [email protected]:12:20 ─── ] ──── ] ─── [ [(3)@test.qs:5:34] ─── [ [[email protected]:6:24] ─── [email protected]:11:20 ── [email protected]:12:20 ─── ] ──── ] ──── ] ──── ] ──
  • All loops (for, while, repeat/until) are supported. The label is the expression in the loop header, which can be, depending on the loop type, an iterable, a "while" condition, or an "until" condition. e.g.:

    • loop: 0..2
    • loop: i < 2
    • loop: i==2
  • Exception 1: If a loop only has a single iteration, we don't group.

  • Exception 2: If a loop is "vertical", meaning each iteration of the loop only interacts with a distinct set of qubits, then there is no grouping, since grouping in this case tends to look more confusing than helpful.

Internals

High level circuit diagram pipeline

To recap, this is how circuit diagrams get currently generated.

Q#/OpenQASM gets compiled to (via the compilation pipeline --qsc_frontend, qsc_lowererer FIR lowerer etc)...

...FIR gets evaluated and traced (via qsc_eval Evaluator)...

...Traces with raw stacks get captured and transformed to (via Circuit Tracer)...

...Traces with logical stacks get transformed to (via Circuit Tracer)...

...Circuit representation gets serialized to (via serde-json)...

...Circuit JSON object gets rendered as (via circuit-vis)

...SVG & HTML circuit diagram

Evaluator changes

  • Tracing in the Evaluator: The Tracer now captures the stack of Scopes in addition to the usual call stack (Frames) in the tracing calls. The scope stack includes the stack of current lexical scopes and loops, including iteration count. This allows the circuit tracer to build a comprehensive "logical" stack that includes both call frames and lexical scopes in the source.

  • The evaluator, when in DEBUG configuration, pushes LOOPs as a scope into the Scope stack. Before this change, we did track each Block as a scope, but not specifically loops. So now when we're in a loop, there is an extra Scope in the stack:


>  BEGIN LOOP SCOPE
>  for i in 0..4 
> 
>  BEGIN BLOCK SCOPE
>  {
>     H(qs[i]);
>  }
>  END BLOCK SCOPE
> 
>  END LOOP SCOPE

  • The loop scope is also associated with an iteration_count which the evaluator is instructed to increment every time it enters the body block of the loop.

FIR lowerer changes

  • FIR: In the execution graph, debug-only nodes are consolidated into a separate ExecGraphDebugNode enum, just for legibility.

  • FIR: during lowering, we add some extra data to the execution graph for loops: The a new PushLoopScope instruction to push loop scope, with an attached ExprId (used later by the circuit tracer to look up the loop source code and display the loop label) , and a LoopIteration instruction that is used to indicate a new iteration of the loop has started.

Circuit tracer changes

  • builder.rs / CircuitTracer: Introduces a LogicalStack which is a blended stack of call frames and lexical scopes. This struct is produced by transforming the stack traces passed down from the evaluator into a more "friendly" shape, and it corresponds to the structure that will ultimately be seen in the circuit diagram.

  • circuit.rs / Circuit : The SourceLocation::Unresolved enum is a now-unnecessary abstraction that is removed in this change.

  • in CircuitTracer, finish now takes both FIR and HIR store to resolve sources and scopes

circuit-vis HTML renderer

  • In the renderer, we now automatically expand any groups that only contain a single operation, which saves the user having to manually expand multiple levels of operations.

Test coverage

  • Native interpreter tests in compiler/qsc/src/interpret/circuit_tests.rs test direct all the way from Q# to the Circuit representation
  • Snapshot tests in npm/qsharp/test/circuits-cases test all the way from Q# to rendered SVG & HTML circuit diagram using the JS/WASM component
  • compiler/qsc_circuit/src/builder/tests/logical_stack_trace.rs - this exercises the Q# -> traces pipeline, validating that the evaluator returns expected "logical" stack traces

Base automatically changed from minestarks/source-links-for-groups to main January 7, 2026 19:42
@minestarks minestarks marked this pull request as ready for review January 8, 2026 22:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants