1#![allow(
7 missing_docs,
8 reason = "the ISA containers and gate vocabulary are documented at their owning types"
9)]
10
11pub type TaskId = u32;
13pub type BitId = u32;
15pub type QubitId = u32;
17pub type RecordId = u32;
19pub type ResourceId = u32;
21
22pub const DEFAULT_DECODER_LATENCY_ROUNDS: u32 = 10;
24#[derive(Debug, Clone, PartialEq, serde::Serialize)]
26pub struct Program {
27 pub qubit_count: u32,
29 pub bit_count: u32,
31 pub record_count: u32,
33 pub tasks: Vec<Task>,
35 pub task_origins: Vec<TaskOrigin>,
38 pub entry: Stream,
40 pub inputs: Vec<LogicalInput>,
42 pub outputs: Vec<LogicalOutput>,
44 pub decoder_latency_rounds: u32,
46}
47
48impl Default for Program {
49 fn default() -> Self {
50 Self {
51 qubit_count: 0,
52 bit_count: 0,
53 record_count: 0,
54 tasks: Vec::new(),
55 task_origins: Vec::new(),
56 entry: Stream::default(),
57 inputs: Vec::new(),
58 outputs: Vec::new(),
59 decoder_latency_rounds: DEFAULT_DECODER_LATENCY_ROUNDS,
60 }
61 }
62}
63
64#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
66pub struct TaskOrigin {
67 pub function: TaskFunction,
68 pub sites: Box<[[i32; 2]]>,
70 pub members: Box<[[i32; 3]]>,
72}
73
74#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
76#[serde(rename_all = "snake_case")]
77pub enum TaskFunction {
78 Classical,
79 Cube,
80 PreparedY,
81 Factory,
82 FactoryBody,
83 TemporalHadamard,
84 Memory,
85 AdaptiveMeasurement,
86 IdealBoundary,
87 Control,
88 #[default]
89 Synthetic,
90}
91
92#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
94pub struct LogicalInput {
95 pub port: [i32; 3],
96 pub task: TaskId,
98 pub data_qubits: Box<[QubitId]>,
99 pub stabilizers: Box<[PauliProduct]>,
100 pub x: PauliProduct,
101 pub z: PauliProduct,
102}
103
104#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
106pub struct LogicalOutput {
107 pub port: [i32; 3],
109 pub x: PauliProduct,
111 pub z: PauliProduct,
113 pub frame_x: BitId,
115 pub frame_z: BitId,
117}
118
119impl Program {
120 pub fn instructions(&self) -> impl Iterator<Item = &Instruction> {
122 self.tasks.iter().map(|task| &task.instruction)
123 }
124
125 #[must_use]
127 pub fn sources(&self) -> Vec<SourceSchedule> {
128 self.tasks
129 .iter()
130 .enumerate()
131 .filter_map(|(task, source)| {
132 source.source.map(|role| SourceSchedule {
133 task: task as TaskId,
134 role,
135 release: source.release,
136 duration: source.duration,
137 })
138 })
139 .collect()
140 }
141
142 pub fn to_json(&self) -> Result<String, serde_json::Error> {
150 serde_json::to_string(self)
151 }
152
153 pub fn to_json_pretty(&self) -> Result<String, serde_json::Error> {
161 serde_json::to_string_pretty(self)
162 }
163}
164
165#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize)]
168pub struct Stream {
169 pub tasks: Box<[TaskId]>,
170 pub value: Option<BitId>,
172 pub bindings: Box<[TaskId]>,
174}
175
176#[derive(Debug, Clone, PartialEq, serde::Serialize)]
178pub struct Task {
179 pub label: String,
181 pub release: f64,
183 pub source: Option<SourceRole>,
185 pub dependencies: Box<[TaskId]>,
188 pub activation: Option<BitId>,
191 pub output: Option<BitId>,
193 pub qubits: Box<[QubitId]>,
195 pub duration: f64,
197 pub instruction: Instruction,
198}
199
200#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
202#[serde(rename_all = "snake_case")]
203pub enum SourceRole {
204 Factory,
205 LogicalInput,
206 PreparedY,
207 Clifford,
208}
209
210#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
212pub struct SourceSchedule {
213 pub task: TaskId,
214 pub role: SourceRole,
215 pub release: f64,
216 pub duration: f64,
217}
218
219#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
221pub enum DecodeTiming {
222 Measurement,
223 FactoryCompletion,
224}
225
226#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize)]
233pub struct DecodeRequest {
234 pub output: bloq_ir::ObservableOutput,
236 pub observable: TaskId,
238 pub index: u32,
240 pub raw: BitId,
242 pub round_duration: f64,
244 pub timing: DecodeTiming,
246}
247
248#[derive(Debug, Clone, PartialEq, serde::Serialize)]
250pub enum Instruction {
251 Quantum(QuantumTask),
252 Eval(BoolOp),
253 Accumulate(RecordParity),
254 Bind(Box<[BoundaryBinding]>),
255 ReadoutRecipe {
257 bits: Box<[BitId]>,
258 bindings: Box<[TaskId]>,
259 },
260 Observable {
261 index: u32,
262 bits: Box<[BitId]>,
263 bindings: Box<[TaskId]>,
264 },
265 Decode(DecodeRequest),
267 Discard(BoolOp),
268 Rus {
270 body: Stream,
271 restart: BoolOp,
272 owned_qubits: Box<[QubitId]>,
273 attempt_bits: Box<[BitId]>,
274 attempt_records: Box<[RecordId]>,
275 resource: ResourceId,
276 retry_prepare: QuantumStream,
279 decoder_hold: Box<[MemoryCycle]>,
281 cultivation_exits: Box<[TaskId]>,
283 },
284 WaitFor {
287 until: Box<[TaskId]>,
289 memory: Box<[MemoryCycle]>,
290 },
291 SignalReady {
292 resource: ResourceId,
293 },
294 MemoryRounds {
298 rounds: u32,
299 stream: QuantumStream,
300 detectors: Box<[RecordParity]>,
301 },
302 Idle {
304 duration: f64,
305 noise_ratio: f64,
306 operations: Box<[QuantumOp]>,
307 },
308}
309
310#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
317pub enum BoolOp {
318 Const(bool),
319 Copy(BitId),
320 Not(Box<BoolOp>),
321 Parity {
322 inputs: Box<[BitId]>,
323 constant: bool,
324 },
325 Xor(Box<[BoolOp]>),
326 And(Box<[BoolOp]>),
327 Or(Box<[BoolOp]>),
328 Select {
329 condition: Box<BoolOp>,
330 when_false: Box<BoolOp>,
331 when_true: Box<BoolOp>,
332 },
333 Call {
338 body: std::sync::Arc<BoolOp>,
339 inputs: Box<[BitId]>,
340 },
341}
342
343#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize)]
345pub struct RecordParity {
346 pub records: std::sync::Arc<[RecordId]>,
347 pub constant: bool,
348}
349
350#[derive(Debug, Clone, Default, PartialEq, serde::Serialize)]
352pub struct QuantumTask {
353 pub alternatives: Box<[QuantumAlternative]>,
354}
355
356#[derive(Debug, Clone, Default, PartialEq, serde::Serialize)]
357pub struct QuantumAlternative {
358 pub when: Box<[(BitId, bool)]>,
360 pub stream: QuantumStream,
361 pub selected_records: Box<[RecordId]>,
363 pub excluded_records: Box<[RecordId]>,
365 pub detectors: Box<[RecordParity]>,
366 pub restarts: Box<[RecordParity]>,
367}
368
369#[derive(Debug, Clone, Default, PartialEq, serde::Serialize)]
370pub struct QuantumStream {
371 pub moments: Box<[Moment]>,
372}
373
374#[derive(Debug, Clone, Default, PartialEq, serde::Serialize)]
375pub struct Moment {
376 pub kind: Option<MomentKind>,
377 pub duration: f64,
378 pub operations: Box<[QuantumOp]>,
379}
380
381#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize)]
383pub enum MomentKind {
384 Reset,
385 Rotation,
386 Interaction,
387 Measurement,
388}
389
390#[derive(Debug, Clone, Default, PartialEq, serde::Serialize)]
393pub struct MemoryCycle {
394 pub stream: QuantumStream,
395 pub round_duration: f64,
396 pub detectors: Box<[RecordParity]>,
397 pub boundary_flows: Box<[BoundaryFlow]>,
398 pub initializers: Box<[FrontierInitializer]>,
401}
402
403#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
406pub struct FrontierInitializer {
407 pub record: RecordId,
408 pub parity: RecordParity,
409}
410
411#[derive(Debug, Clone, PartialEq, serde::Serialize)]
412pub struct BoundaryFlow {
413 pub start: PauliProduct,
414 pub end: PauliProduct,
415 pub parity: RecordParity,
416 pub frontier_record: Option<RecordId>,
419}
420
421#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
424pub struct BoundaryBinding {
425 pub input: bool,
426 pub operator: PauliProduct,
427}
428
429#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize)]
430pub enum Pauli {
431 X,
432 Y,
433 Z,
434}
435
436#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize)]
439pub struct PauliProduct {
440 pub negative: bool,
441 pub terms: Box<[(QubitId, Pauli)]>,
442}
443
444#[expect(
446 non_camel_case_types,
447 reason = "names match the source gate vocabulary"
448)]
449#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize)]
450pub enum Clifford1 {
451 H,
452 H_XY,
453 H_YZ,
454 H_NXY,
455 H_NXZ,
456 H_NYZ,
457 SQRT_X,
458 SQRT_X_DAG,
459 SQRT_Y,
460 SQRT_Y_DAG,
461 S,
462 S_DAG,
463 C_XYZ,
464 C_ZYX,
465 C_NXYZ,
466 C_XNYZ,
467 C_XYNZ,
468 C_NZYX,
469 C_ZNYX,
470 C_ZYNX,
471}
472
473#[derive(Debug, Clone, PartialEq, serde::Serialize)]
474pub enum QuantumOp {
475 Gate1 {
476 gate: Clifford1,
477 qubit: QubitId,
478 },
479 Gate2 {
480 control_basis: Pauli,
481 target_basis: Pauli,
482 control: QubitId,
483 target: QubitId,
484 },
485 Pauli {
486 basis: Pauli,
487 qubit: QubitId,
488 },
489 T {
490 basis: Pauli,
491 qubit: QubitId,
492 adjoint: bool,
493 },
494 Measure {
495 observable: PauliProduct,
496 records: Box<[RecordId]>,
498 flip_probability: f64,
499 },
500 Reset {
501 basis: Pauli,
502 qubit: QubitId,
503 },
504 ConditionalPauli {
505 basis: Pauli,
506 qubit: QubitId,
507 control: RecordId,
508 },
509 Depolarize1 {
510 probability: f64,
511 qubits: Box<[QubitId]>,
512 },
513 Depolarize2 {
514 probability: f64,
515 pairs: Box<[(QubitId, QubitId)]>,
516 },
517 PauliError {
518 probability: f64,
519 basis: Pauli,
520 qubits: Box<[QubitId]>,
521 },
522}
523
524#[cfg(test)]
525mod tests {
526 use serde_json::json;
527
528 use super::*;
529
530 #[test]
531 fn program_json_exposes_physical_stream_and_schedule_metadata() {
532 let quantum = Task {
533 label: "source".into(),
534 release: 2.0,
535 source: Some(SourceRole::Clifford),
536 dependencies: Box::new([]),
537 activation: None,
538 output: None,
539 qubits: Box::new([0]),
540 duration: 1.0,
541 instruction: Instruction::Quantum(QuantumTask {
542 alternatives: Box::new([QuantumAlternative {
543 stream: QuantumStream {
544 moments: Box::new([Moment {
545 kind: Some(MomentKind::Rotation),
546 duration: 1.0,
547 operations: Box::new([QuantumOp::Gate1 {
548 gate: Clifford1::H,
549 qubit: 0,
550 }]),
551 }]),
552 },
553 ..QuantumAlternative::default()
554 }]),
555 }),
556 };
557 let dependent = Task {
558 label: "ready".into(),
559 release: 0.0,
560 source: None,
561 dependencies: Box::new([0]),
562 activation: None,
563 output: None,
564 qubits: Box::new([]),
565 duration: 0.0,
566 instruction: Instruction::SignalReady { resource: 0 },
567 };
568 let decode = Task {
569 label: "decode".into(),
570 release: 0.0,
571 source: None,
572 dependencies: Box::new([0]),
573 activation: None,
574 output: Some(0),
575 qubits: Box::new([]),
576 duration: 0.0,
577 instruction: Instruction::Decode(DecodeRequest {
578 output: bloq_ir::ObservableOutput::Corrected,
579 observable: 1,
580 index: 0,
581 raw: 1,
582 round_duration: 1.5,
583 timing: DecodeTiming::Measurement,
584 }),
585 };
586 let product = PauliProduct {
587 negative: false,
588 terms: Box::new([(0, Pauli::Z)]),
589 };
590 let program = Program {
591 qubit_count: 1,
592 bit_count: 2,
593 tasks: vec![quantum, dependent, decode],
594 task_origins: vec![
595 TaskOrigin {
596 function: TaskFunction::Cube,
597 sites: Box::new([[3, 4]]),
598 members: Box::new([[3, 4, 5]]),
599 },
600 TaskOrigin::default(),
601 TaskOrigin::default(),
602 ],
603 entry: Stream {
604 tasks: Box::new([0, 1, 2]),
605 ..Stream::default()
606 },
607 inputs: vec![LogicalInput {
608 port: [3, 4, 5],
609 task: 0,
610 data_qubits: Box::new([0]),
611 stabilizers: Box::new([]),
612 x: product.clone(),
613 z: product.clone(),
614 }],
615 outputs: vec![LogicalOutput {
616 port: [3, 4, 6],
617 x: product.clone(),
618 z: product,
619 frame_x: 0,
620 frame_z: 1,
621 }],
622 ..Program::default()
623 };
624
625 let value: serde_json::Value = serde_json::from_str(&program.to_json().unwrap()).unwrap();
626 assert_eq!(value["entry"]["tasks"], json!([0, 1, 2]));
627 assert_eq!(value["tasks"][0]["source"], "clifford");
628 assert_eq!(value["tasks"][1]["dependencies"], json!([0]));
629 assert_eq!(
630 value["tasks"][0]["instruction"]["Quantum"]["alternatives"][0]["stream"]["moments"][0]
631 ["operations"][0]["Gate1"],
632 json!({ "gate": "H", "qubit": 0 })
633 );
634 assert_eq!(
637 value["tasks"][2]["instruction"]["Decode"],
638 json!({
639 "output": "Corrected",
640 "observable": 1,
641 "index": 0,
642 "raw": 1,
643 "round_duration": 1.5,
644 "timing": "Measurement",
645 })
646 );
647 assert_eq!(value["task_origins"][0]["function"], "cube");
648 assert_eq!(value["inputs"][0]["task"], 0);
649 assert_eq!(value["outputs"][0]["port"], json!([3, 4, 6]));
650 }
651}