1use crate::circuit::gate::{Directive, Instruction, StandardGate};
24use crate::circuit::{
25 Circuit, ClassicalControlOp, ClassicalDataOp, ClassicalExpr, ClassicalExprKind, Operation,
26 Qubit,
27};
28use crate::visualization::VisualizationError;
29use crate::visualization::circuit::ir::{
30 VisualChildren, VisualCircuit, VisualCondition, VisualControlFlowKind, VisualOpStyle,
31 VisualOperation,
32};
33use crate::visualization::circuit::params::{ParameterFormatOptions, ParameterFormatter};
34use std::collections::HashMap;
35
36#[derive(Debug, Clone, Copy)]
40pub struct VisualBuildOptions {
41 pub decompose_circuit_gates: bool,
43 pub reserve_full_span_for_multi_qubit: bool,
45 pub parameter_format: ParameterFormatOptions,
47}
48
49impl Default for VisualBuildOptions {
50 fn default() -> Self {
51 Self {
52 decompose_circuit_gates: false,
53 reserve_full_span_for_multi_qubit: true,
54 parameter_format: ParameterFormatOptions::default(),
55 }
56 }
57}
58
59pub fn build_visual_circuit(
85 circuit: &Circuit,
86 options: &VisualBuildOptions,
87) -> Result<VisualCircuit, VisualizationError> {
88 let owned;
89 let source = if options.decompose_circuit_gates {
90 owned = circuit.decompose()?;
91 &owned
92 } else {
93 circuit
94 };
95
96 build_visual_circuit_from_ops(source, &source.qubits(), source.operations(), options)
97}
98
99fn build_visual_circuit_from_ops(
100 source: &Circuit,
101 qubits: &[Qubit],
102 ops: &[Operation],
103 options: &VisualBuildOptions,
104) -> Result<VisualCircuit, VisualizationError> {
105 let qubit_to_lane: HashMap<Qubit, usize> = qubits
106 .iter()
107 .enumerate()
108 .map(|(idx, q)| (*q, idx))
109 .collect();
110
111 let mut operations = Vec::with_capacity(ops.len());
112 let mut next_free = vec![0usize; qubits.len()];
113 let mut num_columns = 0usize;
114 let parameter_formatter = ParameterFormatter::new(options.parameter_format);
115
116 for op in ops {
117 let lanes = map_lanes(op, &qubit_to_lane)?;
118 let params = op
119 .params
120 .iter()
121 .map(|param| parameter_formatter.format_circuit_param(source, param))
122 .collect::<Result<Vec<_>, _>>()?;
123 let (style, label, span_box) = classify_instruction(&op.instruction)?;
124 let children = build_children_for_instruction(source, qubits, &op.instruction, options)?;
125 let span_cols = estimate_span_cols(&style, children.as_ref());
126 let covered_lanes = compute_covered_lanes(
127 &lanes,
128 &style,
129 qubits.len(),
130 options.reserve_full_span_for_multi_qubit,
131 );
132
133 let column = compute_column(&covered_lanes, &next_free);
134 for lane in &covered_lanes {
135 next_free[*lane] = column + span_cols;
136 }
137 num_columns = num_columns.max(column + span_cols);
138
139 operations.push(VisualOperation {
140 column,
141 lanes,
142 covered_lanes,
143 label,
144 params,
145 style,
146 span_box,
147 children,
148 span_cols,
149 });
150 }
151
152 Ok(VisualCircuit {
153 qubits: qubits.to_vec(),
154 operations,
155 num_columns,
156 })
157}
158
159fn build_children_for_instruction(
164 source: &Circuit,
165 parent_qubits: &[Qubit],
166 instruction: &Instruction,
167 options: &VisualBuildOptions,
168) -> Result<Option<VisualChildren>, VisualizationError> {
169 match instruction {
170 Instruction::ClassicalControl(flow) => {
171 build_children_for_flow(source, parent_qubits, flow, options)
172 }
173 _ => Ok(None),
174 }
175}
176
177fn build_children_for_flow(
182 source: &Circuit,
183 parent_qubits: &[Qubit],
184 flow: &ClassicalControlOp,
185 options: &VisualBuildOptions,
186) -> Result<Option<VisualChildren>, VisualizationError> {
187 match flow {
188 ClassicalControlOp::If(gate) => {
189 let then_circuit = build_visual_circuit_from_ops(
190 source,
191 parent_qubits,
192 gate.then_body().operations(),
193 options,
194 )?;
195 let else_circuit = if let Some(body) = gate.else_body() {
196 Some(Box::new(build_visual_circuit_from_ops(
197 source,
198 parent_qubits,
199 body.operations(),
200 options,
201 )?))
202 } else {
203 None
204 };
205 Ok(Some(VisualChildren::IfElse {
206 then_circuit: Box::new(then_circuit),
207 else_circuit,
208 }))
209 }
210 ClassicalControlOp::While(gate) => {
211 let body_circuit = build_visual_circuit_from_ops(
212 source,
213 parent_qubits,
214 gate.body().operations(),
215 options,
216 )?;
217 Ok(Some(VisualChildren::While {
218 body_circuit: Box::new(body_circuit),
219 }))
220 }
221 ClassicalControlOp::For(gate) => {
222 let body_circuit = build_visual_circuit_from_ops(
223 source,
224 parent_qubits,
225 gate.body().operations(),
226 options,
227 )?;
228 Ok(Some(VisualChildren::For {
229 body_circuit: Box::new(body_circuit),
230 }))
231 }
232 ClassicalControlOp::Switch(gate) => {
233 let mut case_circuits = Vec::with_capacity(gate.cases().len());
234 for case in gate.cases() {
235 case_circuits.push((
236 case.value().to_string(),
237 Box::new(build_visual_circuit_from_ops(
238 source,
239 parent_qubits,
240 case.body().operations(),
241 options,
242 )?),
243 ));
244 }
245 let default_circuit = if let Some(body) = gate.default() {
246 Some(Box::new(build_visual_circuit_from_ops(
247 source,
248 parent_qubits,
249 body.operations(),
250 options,
251 )?))
252 } else {
253 None
254 };
255 Ok(Some(VisualChildren::Switch {
256 case_circuits,
257 default_circuit,
258 }))
259 }
260 ClassicalControlOp::Break | ClassicalControlOp::Continue => Ok(None),
261 }
262}
263
264fn estimate_span_cols(style: &VisualOpStyle, children: Option<&VisualChildren>) -> usize {
270 if !matches!(style, VisualOpStyle::ControlFlow { .. }) {
271 return 1;
272 }
273 const MARGIN: usize = 2;
274 const SEP: usize = 2;
275
276 match children {
277 Some(VisualChildren::IfElse {
278 then_circuit,
279 else_circuit,
280 }) => {
281 let then_cols = then_circuit.num_columns.max(1);
282 let else_cols = else_circuit
283 .as_ref()
284 .map(|c| c.num_columns.max(1))
285 .unwrap_or(1);
286 MARGIN + then_cols + SEP + else_cols + MARGIN
287 }
288 Some(VisualChildren::While { body_circuit }) => {
289 let body_cols = body_circuit.num_columns.max(1);
290 MARGIN + body_cols + MARGIN
291 }
292 Some(VisualChildren::For { body_circuit }) => {
293 let body_cols = body_circuit.num_columns.max(1);
294 MARGIN + body_cols + MARGIN
295 }
296 Some(VisualChildren::Switch {
297 case_circuits,
298 default_circuit,
299 }) => {
300 let case_cols = case_circuits
301 .iter()
302 .map(|(_, circuit)| circuit.num_columns.max(1))
303 .sum::<usize>();
304 let default_cols = default_circuit
305 .as_ref()
306 .map(|circuit| circuit.num_columns.max(1))
307 .unwrap_or(0);
308 let branch_count = case_circuits.len() + usize::from(default_circuit.is_some());
309 let separators = branch_count.saturating_sub(1) * SEP;
310 MARGIN + case_cols + default_cols + separators + MARGIN
311 }
312 None => 3,
313 }
314}
315
316fn map_lanes(
321 op: &Operation,
322 qubit_to_lane: &HashMap<Qubit, usize>,
323) -> Result<Vec<usize>, VisualizationError> {
324 let mut lanes = Vec::with_capacity(op.qubits.len());
325 for qubit in &op.qubits {
326 let lane = qubit_to_lane
327 .get(qubit)
328 .ok_or(VisualizationError::UnknownQubit(qubit.id()))?;
329 lanes.push(*lane);
330 }
331 Ok(lanes)
332}
333
334fn classify_instruction(
339 instruction: &Instruction,
340) -> Result<(VisualOpStyle, String, bool), VisualizationError> {
341 match instruction {
342 Instruction::Standard(gate) => {
343 if *gate == StandardGate::SWAP {
344 return Ok((VisualOpStyle::Swap, "SWAP".to_string(), false));
345 }
346 if *gate == StandardGate::CZ {
347 return Ok((VisualOpStyle::Cz, "CZ".to_string(), false));
348 }
349 let num_ctrls = gate.num_ctrl_qubits();
350 if num_ctrls > 0 {
351 Ok((
352 VisualOpStyle::Controlled {
353 num_controls: num_ctrls,
354 },
355 controlled_target_label_for_standard(*gate),
356 false,
357 ))
358 } else {
359 Ok((VisualOpStyle::Gate, standard_gate_label(*gate), false))
360 }
361 }
362 Instruction::McGate(gate) => {
363 if gate.num_ctrl_qubits() == 1 && *gate.base_gate() == StandardGate::Z {
364 Ok((VisualOpStyle::Cz, "CZ".to_string(), false))
365 } else {
366 Ok((
367 VisualOpStyle::Controlled {
368 num_controls: gate.num_ctrl_qubits(),
369 },
370 standard_gate_label(*gate.base_gate()),
371 false,
372 ))
373 }
374 }
375 Instruction::UnitaryGate(gate) => {
376 let label = fallback_if_empty(gate.label(), "Unitary");
377 Ok((VisualOpStyle::Gate, label, true))
378 }
379 Instruction::CircuitGate(gate) => {
380 let label = fallback_if_empty(gate.name(), "Gate");
381 Ok((VisualOpStyle::Gate, label, true))
382 }
383 Instruction::Directive(Directive::Barrier) => {
384 Ok((VisualOpStyle::Barrier, "B".to_string(), false))
385 }
386 Instruction::Directive(Directive::Measure) => {
387 Ok((VisualOpStyle::Measure, "M".to_string(), false))
388 }
389 Instruction::Directive(Directive::Reset) => {
390 Ok((VisualOpStyle::Reset, "R".to_string(), false))
391 }
392 Instruction::Delay => Ok((VisualOpStyle::Delay, "D".to_string(), false)),
393 Instruction::ClassicalControl(flow) => {
394 let (style, label) = classify_control_flow(flow);
395 Ok((style, label, false))
396 }
397 Instruction::ClassicalData(ClassicalDataOp::MeasureBit { .. })
398 | Instruction::ClassicalData(ClassicalDataOp::MeasureBits { .. }) => {
399 Ok((VisualOpStyle::Measure, "M".to_string(), false))
400 }
401 Instruction::ClassicalData(ClassicalDataOp::Store { .. }) => {
402 Ok((VisualOpStyle::Gate, "STORE".to_string(), true))
403 }
404 }
405}
406
407fn fallback_if_empty(label: &str, default_label: &str) -> String {
409 let trimmed = label.trim();
410 if trimmed.is_empty() {
411 default_label.to_string()
412 } else {
413 trimmed.to_string()
414 }
415}
416
417fn classify_control_flow(flow: &ClassicalControlOp) -> (VisualOpStyle, String) {
419 match flow {
420 ClassicalControlOp::If(gate) => {
421 let condition = VisualCondition {
422 label: classical_expr_label(gate.condition()),
423 };
424 let has_false_branch = gate.else_body().is_some();
425 (
426 VisualOpStyle::ControlFlow {
427 kind: VisualControlFlowKind::IfElseBlock {
428 has_false_branch,
429 condition: condition.clone(),
430 },
431 },
432 format!("IF {}", condition.label),
433 )
434 }
435 ClassicalControlOp::While(gate) => {
436 let condition = VisualCondition {
437 label: classical_expr_label(gate.condition()),
438 };
439 (
440 VisualOpStyle::ControlFlow {
441 kind: VisualControlFlowKind::WhileBlock {
442 condition: condition.clone(),
443 },
444 },
445 format!("WH {}", condition.label),
446 )
447 }
448 ClassicalControlOp::For(gate) => {
449 let range = VisualCondition {
450 label: format!(
451 "range({},{})",
452 classical_expr_label(gate.start()),
453 classical_expr_label(gate.stop()),
454 ),
455 };
456 (
457 VisualOpStyle::ControlFlow {
458 kind: VisualControlFlowKind::ForBlock {
459 range: range.clone(),
460 },
461 },
462 format!("FOR {}", range.label),
463 )
464 }
465 ClassicalControlOp::Switch(gate) => {
466 let target = VisualCondition {
467 label: classical_expr_label(gate.target()),
468 };
469 (
470 VisualOpStyle::ControlFlow {
471 kind: VisualControlFlowKind::SwitchBlock {
472 target: target.clone(),
473 },
474 },
475 format!("SW {}", target.label),
476 )
477 }
478 ClassicalControlOp::Break => (
479 VisualOpStyle::ControlFlow {
480 kind: VisualControlFlowKind::Break,
481 },
482 "Break".to_string(),
483 ),
484 ClassicalControlOp::Continue => (
485 VisualOpStyle::ControlFlow {
486 kind: VisualControlFlowKind::Continue,
487 },
488 "Continue".to_string(),
489 ),
490 }
491}
492
493fn classical_expr_label(expr: &ClassicalExpr) -> String {
498 match expr.kind() {
499 ClassicalExprKind::BoolLiteral(value) => value.to_string(),
500 ClassicalExprKind::BitLiteral(value) => format!("bit({value})"),
501 ClassicalExprKind::UIntLiteral { value, .. } => value.to_string(),
502 ClassicalExprKind::BitVecLiteral { value, .. } => format!("bits({value})"),
503 _ => format!("{expr:?}"),
504 }
505}
506
507fn controlled_target_label_for_standard(gate: StandardGate) -> String {
509 match gate {
510 StandardGate::CX | StandardGate::CCX => "X".to_string(),
511 StandardGate::CY => "Y".to_string(),
512 StandardGate::CRX => "RX".to_string(),
513 StandardGate::CRY => "RY".to_string(),
514 StandardGate::CRZ => "RZ".to_string(),
515 _ => strip_control_prefix(&standard_gate_label(gate)),
516 }
517}
518
519fn compute_covered_lanes(
525 lanes: &[usize],
526 style: &VisualOpStyle,
527 num_qubits: usize,
528 reserve_full_span: bool,
529) -> Vec<usize> {
530 if num_qubits == 0 {
531 return Vec::new();
532 }
533
534 if matches!(style, VisualOpStyle::Barrier) {
535 if lanes.is_empty() {
536 return (0..num_qubits).collect();
537 }
538 if reserve_full_span && lanes.len() > 1 {
539 let min_lane = lanes.iter().copied().min().unwrap_or(0);
540 let max_lane = lanes.iter().copied().max().unwrap_or(min_lane);
541 return (min_lane..=max_lane).collect();
542 }
543 return lanes.to_vec();
544 }
545
546 if matches!(style, VisualOpStyle::ControlFlow { .. }) {
547 return (0..num_qubits).collect();
548 }
549
550 if lanes.is_empty() {
551 return vec![0];
552 }
553
554 if reserve_full_span && lanes.len() > 1 {
555 let min_lane = lanes.iter().copied().min().unwrap_or(0);
556 let max_lane = lanes.iter().copied().max().unwrap_or(min_lane);
557 return (min_lane..=max_lane).collect();
558 }
559
560 lanes.to_vec()
561}
562
563fn compute_column(covered_lanes: &[usize], next_free: &[usize]) -> usize {
565 let mut column = 0usize;
566 for lane in covered_lanes {
567 column = column.max(next_free[*lane]);
568 }
569 column
570}
571
572fn standard_gate_label(gate: StandardGate) -> String {
574 match gate {
575 StandardGate::SDG => "SD".to_string(),
576 StandardGate::TDG => "TD".to_string(),
577 StandardGate::Phase | StandardGate::GPhase => "P".to_string(),
578 _ => gate.to_string(),
579 }
580}
581
582fn strip_control_prefix(label: &str) -> String {
584 let mut out = label.to_string();
585 while out.starts_with('C') && out.len() > 1 {
586 out.remove(0);
587 }
588 out
589}