Commit c1ca1a2
Show source code locations on circuit diagrams (#2761)
This PR adds a feature to circuit diagrams to display Q#/QASM source
code locations for each qubit and gate as clickable links in VS Code.
They can also be displayed as hover text in the Python circuit widget.
https://github.com/user-attachments/assets/fd8203a6-e488-439a-ab65-7e5e18c0928d
## Notes
### Operation call sites
Operation boxes are clickable and show hover text with the location of
the call site.
<img width="240" height="170" alt="image"
src="https://github.com/user-attachments/assets/6272cd37-b03c-49be-a693-313b5ff65963"
/>
We always show the nearest user code location to the actual call site.
For example, if the user code shows `ForEach(MResetZ, qs)`, even though
the actual `MResetZ` gate is applied in the Std library, the location in
the circuit diagram will point to the `ForEach` expression.
In Python, "user code" includes both cell contents and any real files
pulled via `qsharp.init(project_root=...)`
### Qubit declaration sites
Qubit labels are clickable and show hover text with a location.
<img width="240" height="170" alt="image"
src="https://github.com/user-attachments/assets/6eb69116-e3cf-4842-8639-1021d7cd76fe"
/>
This location corresponds to the `use` statement that allocated the
qubit. Example:
use q = Qubit();
There is an exception to this when the circuit diagram is generated for
an operation which takes qubit input arguments.
In this case, the declaration locations are the input arguments to the
operation. Example:
operation Test(q1: Qubit, q2: Qubit) : Result[] {}
Here, `q0` is declared at 1:16 and `q1` is declared at 1:27.
Qubits can have multiple declaration sites. Example:
operation Main() : Unit {
{
use q1 = Qubit();
X(q1);
Reset(q1);
}
{
use q2 = Qubit();
Y(q2);
Reset(q2);
}
}
The circuit diagram for this code only shows one qubit wire, but it maps
to both `q1` and `q2`. So clicking on that qubit label will show both
locations:
<img width="659" height="350" alt="image"
src="https://github.com/user-attachments/assets/5d5e7d9e-cbad-4748-b620-a4e6d10e7f29"
/>
### Circuit JSON schema
Components now have a `source` field.
{
"kind": "unitary",
"gate": "H",
"targets": [{ "qubit": 0 }],
"source": { "file": "line_0", "line": 0, "column": 43 }
}
Qubits have a `declarations` field.
{
"id": 0,
"numResults": 0,
"declarations": [{ "file": "foo.ts", "line": 0, "column": 26 }]
}
These fields don't exist in the .qsc file schema. Even though we don't
have a user scenario that writes circuits out to editable .qsc files
yet, if we ever do, these fields should be excluded as they lose their
meaning when the generated circuit is divorced from its originating
source code.
### VS Code
In VS code, clicking on an element with a source code location will
navigate to the source code.
<img width="1090" height="320" alt="image"
src="https://github.com/user-attachments/assets/eacae368-7909-45ee-97ad-cd7a7da31d7e"
/>
### Python
Source code locations can be enabled in Python by passing in
`source_locations=True` into `qsharp.circuit()`.
<img width="646" height="351" alt="image"
src="https://github.com/user-attachments/assets/03bbea91-4636-49ee-b99f-b187f899cc03"
/>
While the hover text shows the location, clicking on the gates does
nothing.
Source code locations are not enabled by default in Python. This is
because today, the locations for Q#/QASM code evaluated via
`qsharp.eval` don't look very meaningful (`line_1:4:2` etc). If a
`qsharp.json` is used, then the files will have proper disk locations,
but this is a relatively uncommon scenario.
### ASCII art update to show source code location labels, e.g.
ASCII art update to show source code location labels, e.g.
[email protected]:4:20 ──────── ● ────────
[email protected]:4:20 ──────── ● ────────
[email protected]:4:20 ─ [email protected]:5:20 ──
These labels are disabled in the Python string rendering (the only place
this ASCII art would be shown to the user) . It's useful in test
snapshots (see `circuit_tests.rs` ), but at the moment they look too
ugly for public consumption.
### Configuration and enablement
Since there is a perf cost to capturing call stacks during circuit
tracing, we have to be conservative about when to enable circuit
tracing.
At this point circuit tracing is only enabled when:
- Explicitly generating a circuit, via the "show circuit" command in VS
code, or the `circuit()` API in Python
- Debugging in VS Code PR forthcoming to decouple the "Run" command from
the debugger to avoid the perf hit)
- When the Python library is initialized with `trace_circuit=True`
Additionally source location capturing is configurable via a user
setting. When it's disabled, the Q#/QASM evaluator skips call stack
captures.
In VS Code, new `settings.json` configurations have been added to tweak
circuit generation options.
"Q#.circuits.config": {
"maxOperations": 10001,
"generationMethod": "classicalEval",
"sourceLocations": true
}
While `maxOperations` and `generationMethod` are not new features, they
weren't configurable by the user.
In Python, these configuration options are exposed in the `circuit()`
function:
generation_method: Optional[CircuitGenerationMethod] = None,
max_operations: Optional[int] = None,
source_locations: bool = False,
### Internals
#### Evaluator and circuit tracer changes
Introduced a `Tracer` trait, now implemented by the circuit tracer
(a.k.a. the circuit builder). This trait diverges from `Backend` mainly
in that it takes a call stack parameter (`stack: &[Frame]`), which
allows the circuit tracer implementation to capture source code metadata
in the circuit. The `TracingBackend` composes a regular `Backend`, a
`Tracer`, or both, so backends that do not care about tracing remain
unchanged.
Every time the evaluator makes an intrinsic call, it captures the call
stack and passes it to tracer (unless source location capturing is
disabled).
Also removed `Result` associated type from `Backend` as it made passing
this type around needlessly complicated. Implementors can simply return
`val::Result`, as the `impl Into<Result>` was enforced everywhere
anyway.
The mechanics of the circuit tracer is largely unchanged, but it is
refactored to make way for future changes. `WireMapBuilder` and
`OperationBuilder` are separated out for manageability; these will
continue evolving as we implement further circuit diagram features.
#### Entry expressions for operation circuits
Note that when we generate an circuit for an operation that takes qubits
(as opposed to an Entrypoint, which has no input parameters), we have to
do some additional processing to figure out qubit declaration locations.
Since in this scenario we use a generated entry expression, the real
qubit declarations would live there. Example:
{
use qs = Qubit[2];
(Test.Test)(qs[0], qs[1]);
let r: Result[] = [];
r
}
We can't use these as the qubit declaration locations since they don't
map to user code, and don't provide any meaningful insight to the user.
#### VS Code command
A new generic `qsharp-vscode.gotoLocations` command was added to jump to
a specific location in source. The built-in VS Code commands didn't play
nicely with our UX.
#### Circuit rendering
If operations or gate labels contain source code locations, they are
rendered as clickable SVG elements. The hover text should show the
source code location as well.
Minor fixes in rendering and CSS (dashed lines were invisible, SWAP
gates had an error in the bounding box calculation.)
Changed the signature of `draw` to better organize the increasing number
of options.
---------
Co-authored-by: Mine Starks <>
Co-authored-by: Stefan J. Wernli <[email protected]>1 parent e7bd0d9 commit c1ca1a2
File tree
58 files changed
+4735
-1591
lines changed- library/src
- samples/notebooks
- source
- compiler
- qsc_circuit
- src
- builder
- circuit
- qsc_eval/src
- intrinsic
- qsc_partial_eval/src
- qsc/src
- interpret
- language_service/src
- npm/qsharp
- src
- compiler
- data-structures
- test
- circuits-cases
- ux
- circuit-vis
- formatters
- pip
- qsharp
- openqasm
- src
- tests
- resource_estimator/src
- samples_test/src
- vscode
- src
- azure
- wasm/src
- widgets/js
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
58 files changed
+4735
-1591
lines changedSome generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
20 | | - | |
| 20 | + | |
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
| |||
57 | 57 | | |
58 | 58 | | |
59 | 59 | | |
60 | | - | |
| 60 | + | |
61 | 61 | | |
62 | 62 | | |
63 | 63 | | |
| |||
106 | 106 | | |
107 | 107 | | |
108 | 108 | | |
109 | | - | |
| 109 | + | |
110 | 110 | | |
111 | 111 | | |
112 | 112 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
23 | | - | |
| 23 | + | |
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
| |||
35 | 35 | | |
36 | 36 | | |
37 | 37 | | |
38 | | - | |
39 | | - | |
40 | | - | |
41 | | - | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
42 | 48 | | |
43 | 49 | | |
44 | 50 | | |
| |||
47 | 53 | | |
48 | 54 | | |
49 | 55 | | |
50 | | - | |
| 56 | + | |
51 | 57 | | |
52 | 58 | | |
53 | 59 | | |
| |||
67 | 73 | | |
68 | 74 | | |
69 | 75 | | |
70 | | - | |
71 | | - | |
72 | | - | |
73 | | - | |
74 | | - | |
75 | | - | |
76 | | - | |
77 | | - | |
78 | | - | |
79 | | - | |
80 | | - | |
81 | | - | |
82 | | - | |
83 | | - | |
84 | | - | |
85 | | - | |
86 | | - | |
87 | | - | |
88 | | - | |
89 | | - | |
90 | | - | |
91 | | - | |
92 | | - | |
93 | | - | |
94 | | - | |
95 | | - | |
96 | | - | |
97 | | - | |
98 | | - | |
99 | | - | |
100 | | - | |
101 | | - | |
102 | | - | |
103 | | - | |
104 | | - | |
105 | | - | |
106 | | - | |
107 | | - | |
108 | | - | |
109 | | - | |
110 | 76 | | |
111 | 77 | | |
112 | 78 | | |
| |||
251 | 217 | | |
252 | 218 | | |
253 | 219 | | |
254 | | - | |
255 | | - | |
256 | | - | |
257 | | - | |
258 | | - | |
259 | | - | |
260 | | - | |
261 | | - | |
262 | | - | |
263 | | - | |
264 | | - | |
265 | | - | |
266 | | - | |
267 | | - | |
| 220 | + | |
268 | 221 | | |
269 | | - | |
| 222 | + | |
270 | 223 | | |
271 | 224 | | |
272 | 225 | | |
| |||
275 | 228 | | |
276 | 229 | | |
277 | 230 | | |
278 | | - | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
279 | 234 | | |
280 | 235 | | |
281 | 236 | | |
282 | 237 | | |
283 | 238 | | |
284 | | - | |
| 239 | + | |
285 | 240 | | |
286 | 241 | | |
287 | 242 | | |
| |||
295 | 250 | | |
296 | 251 | | |
297 | 252 | | |
298 | | - | |
| 253 | + | |
299 | 254 | | |
300 | 255 | | |
301 | 256 | | |
| |||
0 commit comments