Skip to content

Commit 1267a0d

Browse files
authored
Circuit diagrams: Show source code links for grouped operations (#2826)
Enables source code links for grouped operations, just as with simple gates. <img width="826" height="612" alt="image" src="https://github.com/user-attachments/assets/30d39007-1709-4410-a50d-4443b69fd83b" />
1 parent 20c2fb1 commit 1267a0d

14 files changed

+2534
-2418
lines changed

source/compiler/qsc_circuit/src/builder.rs

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,7 +1113,7 @@ impl OperationListBuilder {
11131113
}
11141114
}
11151115

1116-
fn push_op(&mut self, mut op: OperationOrGroup, unfiltered_call_stack: Vec<LocationMetadata>) {
1116+
fn push_op(&mut self, op: OperationOrGroup, unfiltered_call_stack: Vec<LocationMetadata>) {
11171117
if self.max_ops_exceeded || self.operations.len() >= self.max_ops {
11181118
// Stop adding gates and leave the circuit as is
11191119
self.max_ops_exceeded = true;
@@ -1126,21 +1126,13 @@ impl OperationListBuilder {
11261126
vec![]
11271127
};
11281128

1129-
if self.source_locations
1130-
&& let Some(called_at) = op_call_stack.last()
1131-
{
1132-
op.set_location(called_at.source_location());
1133-
}
1134-
11351129
add_scoped_op(
11361130
&mut self.operations,
11371131
&ScopeStack::top(),
11381132
op,
1139-
if self.group_by_scope {
1140-
&op_call_stack
1141-
} else {
1142-
&[]
1143-
},
1133+
&op_call_stack,
1134+
self.group_by_scope,
1135+
self.source_locations,
11441136
);
11451137
}
11461138

@@ -1299,9 +1291,17 @@ impl LexicalScope {
12991291
fn add_scoped_op(
13001292
current_container: &mut Vec<OperationOrGroup>,
13011293
current_scope_stack: &ScopeStack,
1302-
op: OperationOrGroup,
1294+
mut op: OperationOrGroup,
13031295
op_call_stack: &[LocationMetadata],
1296+
group_by_scope: bool,
1297+
set_source_location: bool,
13041298
) {
1299+
if set_source_location && let Some(called_at) = op_call_stack.last() {
1300+
op.set_location(called_at.source_location());
1301+
}
1302+
1303+
let op_call_stack = if group_by_scope { op_call_stack } else { &[] };
1304+
13051305
let relative_stack = strip_scope_stack_prefix(
13061306
op_call_stack,
13071307
current_scope_stack,
@@ -1321,7 +1321,14 @@ fn add_scoped_op(
13211321
let last_op_children = last_op.children_mut().expect("operation should be a group");
13221322

13231323
// Recursively add to the children
1324-
add_scoped_op(last_op_children, &last_scope_stack, op, op_call_stack);
1324+
add_scoped_op(
1325+
last_op_children,
1326+
&last_scope_stack,
1327+
op,
1328+
op_call_stack,
1329+
group_by_scope,
1330+
set_source_location,
1331+
);
13251332

13261333
return;
13271334
}
@@ -1338,7 +1345,14 @@ fn add_scoped_op(
13381345
.1
13391346
.to_vec();
13401347
// Recursively add the new scope group to the current container
1341-
add_scoped_op(current_container, current_scope_stack, scope_group, &parent);
1348+
add_scoped_op(
1349+
current_container,
1350+
current_scope_stack,
1351+
scope_group,
1352+
&parent,
1353+
group_by_scope,
1354+
set_source_location,
1355+
);
13421356

13431357
return;
13441358
}

source/compiler/qsc_circuit/src/builder/tests.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,3 +499,35 @@ fn measurement_target_propagated_to_group() {
499499
.find(|reg| *reg == &measurement_op.results[0])
500500
.expect("expected measurement result in group operation's targets");
501501
}
502+
503+
#[test]
504+
fn source_locations_for_groups() {
505+
let mut c = FakeCompilation::default();
506+
let mut builder = CircuitTracer::new(
507+
TracerConfig {
508+
max_operations: 10,
509+
source_locations: true,
510+
group_by_scope: true,
511+
prune_classical_qubits: false,
512+
},
513+
&FakeCompilation::user_package_ids(),
514+
);
515+
516+
builder.qubit_allocate(&[], 0);
517+
518+
builder.gate(
519+
&[c.user_code_frame("Main", 10), c.user_code_frame("Foo", 10)],
520+
"X",
521+
false,
522+
&[0],
523+
&[],
524+
None,
525+
);
526+
527+
let circuit = builder.finish(&c);
528+
529+
expect![[r#"
530+
q_0 ─ [ [Main] ─── [ [Foo@user_code.qs:0:10] ── X@user_code.qs:0:10 ─── ] ──── ] ──
531+
"#]]
532+
.assert_eq(&circuit.display_with_groups().to_string());
533+
}

source/compiler/qsc_circuit/src/circuit.rs

Lines changed: 17 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,7 @@ pub struct Qubit {
345345
}
346346

347347
#[derive(Clone, Serialize, Deserialize, Debug)]
348+
#[serde(rename_all = "camelCase")]
348349
/// The schema of `Metadata` may change and its contents
349350
/// are never meant to be persisted in a .qsc file.
350351
pub struct Metadata {
@@ -424,32 +425,30 @@ impl Row {
424425
self.add(column, CircuitObject::Object(gate_label.clone()));
425426
}
426427

427-
fn add_gate(
428-
&mut self,
429-
column: usize,
430-
gate: &str,
431-
args: &[String],
432-
is_adjoint: bool,
433-
source: Option<&SourceLocation>,
434-
) {
428+
fn add_gate(&mut self, column: usize, operation: &Operation) {
429+
let gate_label = self.operation_label(operation);
430+
431+
self.add_object(column, gate_label.as_str());
432+
}
433+
434+
fn operation_label(&self, operation: &Operation) -> String {
435435
let mut gate_label = String::new();
436-
gate_label.push_str(gate);
437-
if is_adjoint {
436+
gate_label.push_str(&operation.gate());
437+
if operation.is_adjoint() {
438438
gate_label.push('\'');
439439
}
440440

441-
if !args.is_empty() {
442-
let args = args.join(", ");
441+
if !operation.args().is_empty() {
442+
let args = operation.args().join(", ");
443443
let _ = write!(&mut gate_label, "({args})");
444444
}
445445

446446
if self.render_locations
447-
&& let Some(SourceLocation::Resolved(loc)) = source
447+
&& let Some(SourceLocation::Resolved(loc)) = operation.source_location()
448448
{
449449
let _ = write!(&mut gate_label, "@{}:{}:{}", loc.file, loc.line, loc.column);
450450
}
451-
452-
self.add_object(column, gate_label.as_str());
451+
gate_label
453452
}
454453

455454
fn add_vertical(&mut self, column: usize) {
@@ -897,13 +896,7 @@ fn add_operation_to_rows(
897896
{
898897
row.start_classical(column);
899898
} else {
900-
row.add_gate(
901-
column,
902-
&operation.gate(),
903-
&operation.args(),
904-
operation.is_adjoint(),
905-
operation.source_location(),
906-
);
899+
row.add_gate(column, operation);
907900
}
908901
}
909902

@@ -944,15 +937,8 @@ fn add_box_start(operation: &Operation, rows: &mut [Row], target_rows: &[usize],
944937
for i in target_rows {
945938
if first {
946939
first = false;
947-
rows[*i].add_object(
948-
column,
949-
format!(
950-
"[ [{}{}]",
951-
operation.gate(),
952-
if operation.is_adjoint() { "'" } else { "" },
953-
)
954-
.as_str(),
955-
);
940+
let label = rows[*i].operation_label(operation);
941+
rows[*i].add_object(column, format!("[ [{label}]").as_str());
956942
} else {
957943
rows[*i].add_object(column, "[");
958944
}

0 commit comments

Comments
 (0)