1use std::fmt;
2use std::sync::Arc;
3
4use bloq_circuit::{Basis, PauliMap};
5use glam::IVec3;
6
7use super::{
8 DetectorBundleUse, InstanceMeasurement, NodeDetector, NodeRestart, SourceBlockRef, SubGraph,
9 TemplateInstance, TemplateInstanceId, TemporalPipeRef,
10};
11
12#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
14pub enum NodeProvenance {
15 BlockComponent {
18 members: Vec<SourceBlockRef>,
20 },
21 TemporalPipe {
26 pipe: TemporalPipeRef,
28 },
29 SpatialPortSubstitution {
33 source: IVec3,
35 role: bloq_utils::PortRole,
37 },
38 MemoryPadding {
44 pipe: TemporalPipeRef,
46 rounds: u32,
48 },
49 Generator {
53 ordinal: u32,
55 },
56 Action {
60 ordinal: u32,
62 },
63 BranchSelector {
65 name: String,
67 },
68 OutputFrame {
76 port: IVec3,
78 basis: Basis,
80 },
81 None,
83}
84
85impl fmt::Display for NodeProvenance {
90 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91 let coord3 = |f: &mut fmt::Formatter<'_>, p: IVec3| write!(f, "({},{},{})", p.x, p.y, p.z);
92 match self {
93 NodeProvenance::None => write!(f, "synthetic"),
94 NodeProvenance::BlockComponent { members } => {
95 write!(f, "blocks")?;
96 for member in members {
97 write!(f, " ")?;
98 coord3(f, member.pos)?;
99 }
100 Ok(())
101 }
102 NodeProvenance::TemporalPipe { pipe } => {
103 write!(f, "pipe ")?;
104 coord3(f, pipe.src)?;
105 write!(f, ">")?;
106 coord3(f, pipe.dst)?;
107 if pipe.hadamard {
108 write!(f, " H")?;
109 }
110 Ok(())
111 }
112 NodeProvenance::SpatialPortSubstitution { source, role } => {
113 write!(f, "spatial-port {} ", role.as_str())?;
114 coord3(f, *source)
115 }
116 NodeProvenance::MemoryPadding { pipe, rounds } => {
117 write!(f, "padding ")?;
118 coord3(f, pipe.src)?;
119 write!(f, ">")?;
120 coord3(f, pipe.dst)?;
121 write!(f, " rounds {rounds}")
122 }
123 NodeProvenance::Generator { ordinal } => write!(f, "generator {ordinal}"),
124 NodeProvenance::Action { ordinal } => write!(f, "action {ordinal}"),
125 NodeProvenance::BranchSelector { name } => write!(f, "selector {name}"),
126 NodeProvenance::OutputFrame { port, basis } => {
127 let basis = match basis {
128 Basis::X => "x",
129 Basis::Z => "z",
130 };
131 write!(f, "frame {basis} ")?;
132 coord3(f, *port)
133 }
134 }
135 }
136}
137
138#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
144pub struct BloqNode {
145 pub kind: BloqNodeKind,
147 pub provenance: NodeProvenance,
149 pub activation: Option<u32>,
153}
154
155#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
157pub enum BloqNodeKind {
158 Quantum(#[serde(deserialize_with = "deserialize_quantum")] Arc<QuantumNode>),
161 Classical(Arc<ClassicalNode>),
164 Region(RegionNode),
166}
167
168impl BloqNodeKind {
169 pub fn try_classical(&self) -> Option<&ClassicalNode> {
171 match self {
172 Self::Classical(data) => Some(data),
173 Self::Quantum(_) | Self::Region(_) => None,
174 }
175 }
176
177 pub fn try_classical_mut(&mut self) -> Option<&mut ClassicalNode> {
179 match self {
180 Self::Classical(data) => Some(Arc::make_mut(data)),
181 Self::Quantum(_) | Self::Region(_) => None,
182 }
183 }
184}
185
186pub(super) fn deserialize_quantum<'de, D: serde::Deserializer<'de>>(
187 deserializer: D,
188) -> Result<Arc<QuantumNode>, D::Error> {
189 <QuantumNode as serde::Deserialize<'de>>::deserialize(deserializer).map(Arc::new)
192}
193
194#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
197pub enum ClassicalNode {
198 Compute {
200 expr: ClassicalExpr,
202 },
203 Observable {
207 index: Option<u32>,
209 #[serde(with = "crate::binary::instance_measurements")]
211 measurements: Vec<InstanceMeasurement>,
212 operators: Vec<InstanceBoundaryOperator>,
214 },
215 Discard {
217 condition: ClassicalExpr,
219 },
220}
221
222impl ClassicalNode {
223 pub fn observable(index: u32) -> Self {
225 Self::Observable {
226 index: Some(index),
227 measurements: Vec::new(),
228 operators: Vec::new(),
229 }
230 }
231
232 pub fn observable_fragment(
234 measurements: Vec<InstanceMeasurement>,
235 operators: Vec<InstanceBoundaryOperator>,
236 ) -> Self {
237 Self::Observable {
238 index: None,
239 measurements,
240 operators,
241 }
242 }
243
244 pub fn fragment() -> Self {
246 Self::observable_fragment(Vec::new(), Vec::new())
247 }
248
249 pub fn measurements(&self) -> &[InstanceMeasurement] {
251 match self {
252 Self::Observable { measurements, .. } => measurements,
253 _ => &[],
254 }
255 }
256
257 pub fn operators(&self) -> &[InstanceBoundaryOperator] {
259 match self {
260 Self::Observable { operators, .. } => operators,
261 _ => &[],
262 }
263 }
264}
265
266#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
272pub enum ClassicalExpr {
273 In(u32),
275 Const(bool),
278 Not(Box<ClassicalExpr>),
280 Xor(Box<[ClassicalExpr]>),
282 And(Box<[ClassicalExpr]>),
284 Or(Box<[ClassicalExpr]>),
286 Select(Box<[ClassicalExpr; 3]>),
289 Parity {
293 inputs: Box<[u32]>,
295 constant: bool,
297 },
298}
299
300impl ClassicalExpr {
301 #[must_use]
303 pub fn parity(inputs: impl IntoIterator<Item = u32>, constant: bool) -> Self {
304 let inputs: Box<[_]> = inputs.into_iter().collect();
305 match inputs.as_ref() {
306 [] => Self::Const(constant),
307 [slot] if !constant => Self::In(*slot),
308 _ => Self::Parity { inputs, constant },
309 }
310 }
311
312 #[must_use]
319 pub fn xor(operands: impl IntoIterator<Item = Self>) -> Self {
320 let mut operands: Vec<_> = operands.into_iter().collect();
321 match operands.len() {
322 0 => Self::Const(false),
323 1 => operands
324 .pop()
325 .expect("the single operand was counted above"),
326 _ => Self::Xor(operands.into_boxed_slice()),
327 }
328 }
329
330 #[must_use]
333 pub fn select(condition: Self, when_false: Self, when_true: Self) -> Self {
334 match (when_false, when_true) {
335 (Self::Const(false), Self::Const(true)) => condition,
336 (Self::Const(true), Self::Const(false)) => Self::Not(Box::new(condition)),
337 (Self::Const(false), when_true) => Self::And(Box::new([condition, when_true])),
338 (when_false, Self::Const(true)) => Self::Or(Box::new([condition, when_false])),
339 (when_false, when_true) => Self::Select(Box::new([condition, when_false, when_true])),
341 }
342 }
343
344 pub fn operands(&self) -> &[Self] {
347 match self {
348 Self::In(_) | Self::Const(_) | Self::Parity { .. } => &[],
349 Self::Not(inner) => std::slice::from_ref(inner),
350 Self::Xor(operands) | Self::And(operands) | Self::Or(operands) => operands,
351 Self::Select(operands) => operands.as_ref(),
352 }
353 }
354
355 pub fn eval(&self, input: &mut impl FnMut(u32) -> Option<bool>) -> Option<bool> {
359 match self {
360 Self::In(slot) => input(*slot),
361 Self::Const(bit) => Some(*bit),
362 Self::Parity { inputs, constant } => inputs
363 .iter()
364 .try_fold(*constant, |value, &slot| Some(value ^ input(slot)?)),
365 Self::Not(inner) => inner.eval(input).map(|bit| !bit),
366 Self::Xor(operands) => operands
367 .iter()
368 .try_fold(false, |value, expr| Some(value ^ expr.eval(input)?)),
369 Self::And(operands) => operands
370 .iter()
371 .try_fold(true, |value, expr| Some(value & expr.eval(input)?)),
372 Self::Or(operands) => operands
373 .iter()
374 .try_fold(false, |value, expr| Some(value | expr.eval(input)?)),
375 Self::Select(operands) => {
376 let [condition, when_false, when_true] = operands.as_ref();
377 let condition = condition.eval(input)?;
378 let when_false = when_false.eval(input)?;
379 let when_true = when_true.eval(input)?;
380 Some(if condition { when_true } else { when_false })
381 }
382 }
383 }
384
385 pub fn for_each_input(&self, f: &mut impl FnMut(u32)) {
387 match self {
388 Self::In(slot) => f(*slot),
389 Self::Parity { inputs, .. } => inputs.iter().for_each(|&slot| f(slot)),
390 _ => {}
391 }
392 for operand in self.operands() {
393 operand.for_each_input(f);
394 }
395 }
396
397 pub(crate) fn replace_inputs(&mut self, f: &mut impl FnMut(u32) -> ClassicalExpr) {
403 match self {
404 Self::In(slot) => *self = f(*slot),
405 Self::Const(_) => {}
406 Self::Parity { inputs, constant } => {
407 let mut operands = Vec::with_capacity(inputs.len());
408 for &slot in inputs.iter() {
409 operands.push(f(slot));
410 }
411 if operands
412 .iter()
413 .all(|operand| matches!(operand, Self::In(_)))
414 {
415 for (slot, operand) in inputs.iter_mut().zip(operands) {
416 let Self::In(mapped) = operand else {
417 unreachable!("all replacement operands were checked as inputs")
418 };
419 *slot = mapped;
420 }
421 } else {
422 let expr = Self::xor(operands);
423 *self = if *constant {
424 Self::Not(Box::new(expr))
425 } else {
426 expr
427 };
428 }
429 }
430 Self::Not(inner) => inner.replace_inputs(f),
431 Self::Xor(operands) | Self::And(operands) | Self::Or(operands) => {
432 for operand in operands {
433 operand.replace_inputs(f);
434 }
435 }
436 Self::Select(operands) => {
437 for operand in operands.as_mut() {
438 operand.replace_inputs(f);
439 }
440 }
441 }
442 }
443
444 pub fn is_linear(&self) -> bool {
449 match self {
450 Self::In(_) | Self::Const(_) | Self::Parity { .. } => true,
451 Self::Not(inner) => inner.is_linear(),
452 Self::Xor(operands) => operands.iter().all(Self::is_linear),
453 Self::And(..) | Self::Or(..) | Self::Select(..) => false,
454 }
455 }
456
457 pub(crate) fn needs_affine_compaction(&self) -> bool {
460 if matches!(self, Self::Not(_) | Self::Xor(_)) && self.is_linear() {
461 return true;
462 }
463 self.operands().iter().any(Self::needs_affine_compaction)
464 }
465
466 pub(crate) fn compact_affine(&mut self) {
468 if matches!(self, Self::In(_) | Self::Const(_) | Self::Parity { .. }) {
469 return;
470 }
471 if self.is_linear() {
472 let constant = self
473 .eval(&mut |_| Some(false))
474 .expect("constant assignment");
475 let mut inputs = Vec::new();
476 self.for_each_input(&mut |slot| inputs.push(slot));
477 *self = Self::parity(inputs, constant);
478 return;
479 }
480 match self {
481 Self::Not(inner) => inner.compact_affine(),
482 Self::Xor(operands) | Self::And(operands) | Self::Or(operands) => {
483 for operand in operands {
484 operand.compact_affine();
485 }
486 }
487 Self::Select(operands) => {
488 for operand in operands.as_mut() {
489 operand.compact_affine();
490 }
491 }
492 _ => unreachable!("leaves returned above"),
493 }
494 }
495}
496
497#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
503pub enum BoundaryFace {
504 Input,
506 Output,
508}
509
510#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
516pub struct InstanceBoundaryOperator {
517 pub instance: TemplateInstanceId,
519 pub face: BoundaryFace,
521 pub operator: PauliMap,
523}
524
525#[derive(Debug, Clone)]
530pub enum RegionNode {
531 RepeatUntilSuccess {
540 body: SubGraph,
542 restart_condition: ClassicalExpr,
544 restart_source: Option<super::ValueRef>,
549 },
550}
551
552#[derive(serde::Serialize, serde::Deserialize)]
555enum RegionWire<B, E> {
556 Removed,
557 RepeatUntilSuccess {
558 body: B,
559 restart_condition: E,
560 restart_source: Option<super::ValueRef>,
561 },
562}
563
564impl serde::Serialize for RegionNode {
565 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
566 let Self::RepeatUntilSuccess {
567 body,
568 restart_condition,
569 restart_source,
570 } = self;
571 RegionWire::RepeatUntilSuccess {
572 body,
573 restart_condition,
574 restart_source: *restart_source,
575 }
576 .serialize(serializer)
577 }
578}
579
580impl<'de> serde::Deserialize<'de> for RegionNode {
581 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
582 match RegionWire::deserialize(deserializer)? {
583 RegionWire::Removed => Err(serde::de::Error::custom("removed region variant")),
584 RegionWire::RepeatUntilSuccess {
585 body,
586 restart_condition,
587 restart_source,
588 } => Ok(Self::RepeatUntilSuccess {
589 body,
590 restart_condition,
591 restart_source,
592 }),
593 }
594 }
595}
596
597#[derive(
599 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
600)]
601pub enum BodySelector {
602 Body,
604}
605
606impl RegionNode {
607 pub fn bodies(&self) -> impl Iterator<Item = (BodySelector, &SubGraph)> {
609 let Self::RepeatUntilSuccess { body, .. } = self;
610 std::iter::once((BodySelector::Body, body))
611 }
612
613 pub fn bodies_mut(&mut self) -> impl Iterator<Item = (BodySelector, &mut SubGraph)> {
615 let Self::RepeatUntilSuccess { body, .. } = self;
616 std::iter::once((BodySelector::Body, body))
617 }
618
619 pub fn kind_name(&self) -> &'static str {
621 "RepeatUntilSuccess"
622 }
623}
624
625#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
627pub struct QuantumTimeline {
628 pub layer_round_ends: Vec<u32>,
633}
634
635#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
644pub struct QuantumNode {
645 pub instances: Vec<TemplateInstance>,
647 pub detectors: Vec<NodeDetector>,
649 pub detector_bundles: Vec<DetectorBundleUse>,
651 pub restarts: Vec<NodeRestart>,
653 pub timeline: Option<QuantumTimeline>,
656 pub guards: Vec<QuantumGuard>,
660}
661
662#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
667pub struct QuantumGuard {
668 pub input: u32,
670 pub instances: Vec<TemplateInstanceId>,
672 pub detectors: Vec<u32>,
674 pub detector_bundles: Vec<u32>,
676 pub restarts: Vec<u32>,
678 pub detector_parities: Vec<(u32, crate::NodeDetectorParity)>,
680 pub restart_parities: Vec<(u32, crate::NodeDetectorParity)>,
682}
683
684impl QuantumNode {
685 pub(crate) fn stored_parities(&self) -> impl Iterator<Item = &crate::NodeDetectorParity> {
692 self.detectors
693 .iter()
694 .map(|detector| &detector.parity)
695 .chain(self.restarts.iter().map(|restart| &restart.parity))
696 .chain(self.guards.iter().flat_map(|guard| {
697 guard
698 .detector_parities
699 .iter()
700 .chain(&guard.restart_parities)
701 .map(|(_, parity)| parity)
702 }))
703 }
704}
705
706impl BloqNode {
707 pub(crate) fn kind_name(&self) -> &'static str {
708 match &self.kind {
709 BloqNodeKind::Quantum(_) => "Quantum",
710 BloqNodeKind::Region(region) => region.kind_name(),
711 BloqNodeKind::Classical(classical) => match classical.as_ref() {
712 ClassicalNode::Compute { .. } => "Compute",
713 ClassicalNode::Observable { .. } => "Observable",
714 ClassicalNode::Discard { .. } => "Discard",
715 },
716 }
717 }
718
719 pub fn from_members(members: Vec<SourceBlockRef>) -> Self {
721 Self::new_quantum(NodeProvenance::BlockComponent { members })
722 }
723
724 pub fn from_temporal_pipe(pipe: TemporalPipeRef) -> Self {
726 Self::new_quantum(NodeProvenance::TemporalPipe { pipe })
727 }
728
729 pub fn memory_padding(pipe: TemporalPipeRef, rounds: u32) -> Self {
732 Self::new_quantum(NodeProvenance::MemoryPadding { pipe, rounds })
733 }
734
735 fn new_quantum(provenance: NodeProvenance) -> Self {
736 Self::quantum(QuantumNode::default()).with_provenance(provenance)
737 }
738
739 pub fn quantum(quantum: QuantumNode) -> Self {
742 Self {
743 kind: BloqNodeKind::Quantum(quantum.into()),
744 provenance: NodeProvenance::None,
745 activation: None,
746 }
747 }
748
749 pub fn region(region: RegionNode) -> Self {
751 Self {
752 kind: BloqNodeKind::Region(region),
753 provenance: NodeProvenance::None,
754 activation: None,
755 }
756 }
757
758 pub fn classical(node: impl Into<Arc<ClassicalNode>>) -> Self {
763 Self {
764 kind: BloqNodeKind::Classical(node.into()),
765 provenance: NodeProvenance::None,
766 activation: None,
767 }
768 }
769
770 pub fn with_provenance(mut self, provenance: NodeProvenance) -> Self {
773 self.provenance = provenance;
774 self
775 }
776
777 pub fn expect_quantum(&self) -> &QuantumNode {
787 self.try_quantum()
788 .expect("expect_quantum() called on a non-quantum BloqNode")
789 }
790
791 pub fn expect_quantum_mut(&mut self) -> &mut QuantumNode {
798 match &mut self.kind {
799 BloqNodeKind::Quantum(quantum) => Arc::make_mut(quantum),
800 BloqNodeKind::Classical(_) | BloqNodeKind::Region(_) => {
801 panic!("expect_quantum_mut() called on a non-quantum BloqNode")
802 }
803 }
804 }
805
806 pub fn try_quantum(&self) -> Option<&QuantumNode> {
809 match &self.kind {
810 BloqNodeKind::Quantum(quantum) => Some(quantum),
811 BloqNodeKind::Classical(_) | BloqNodeKind::Region(_) => None,
812 }
813 }
814
815 pub fn try_classical(&self) -> Option<&ClassicalNode> {
817 self.kind.try_classical()
818 }
819
820 pub fn try_classical_mut(&mut self) -> Option<&mut ClassicalNode> {
822 self.kind.try_classical_mut()
823 }
824
825 pub fn try_region(&self) -> Option<&RegionNode> {
827 match &self.kind {
828 BloqNodeKind::Region(region) => Some(region),
829 BloqNodeKind::Quantum(_) | BloqNodeKind::Classical(_) => None,
830 }
831 }
832
833 pub fn block_members(&self) -> &[SourceBlockRef] {
835 match &self.provenance {
836 NodeProvenance::BlockComponent { members } => members,
837 _ => &[],
838 }
839 }
840
841 pub fn memory_rounds(&self) -> Option<u32> {
845 match &self.provenance {
846 NodeProvenance::MemoryPadding { rounds, .. } => Some(*rounds),
847 _ => None,
848 }
849 }
850
851 pub fn layer(&self) -> i64 {
857 match &self.provenance {
858 NodeProvenance::BlockComponent { members } => {
859 2 * i64::from(members.iter().map(|member| member.pos.z).min().unwrap_or(0))
860 }
861 NodeProvenance::TemporalPipe { pipe } | NodeProvenance::MemoryPadding { pipe, .. } => {
862 2 * i64::from(pipe.src.z.min(pipe.dst.z)) + 1
863 }
864 NodeProvenance::SpatialPortSubstitution { source, role } => {
866 2 * i64::from(source.z) + if role.has_input_boundary() { -1 } else { 1 }
867 }
868 NodeProvenance::Generator { .. }
870 | NodeProvenance::Action { .. }
871 | NodeProvenance::BranchSelector { .. }
872 | NodeProvenance::OutputFrame { .. }
873 | NodeProvenance::None => 0,
874 }
875 }
876}
877
878#[cfg(test)]
879mod tests {
880 use glam::ivec3;
881
882 use super::*;
883
884 fn map_inputs(expr: &ClassicalExpr, f: &mut impl FnMut(u32) -> ClassicalExpr) -> ClassicalExpr {
887 let mut mapped = expr.clone();
888 mapped.replace_inputs(f);
889 mapped
890 }
891
892 #[test]
893 fn affine_compaction_preserves_unknowns_order_and_substitution() {
894 use ClassicalExpr::{Const, In, Not, Parity, Xor};
895 let tree = Xor(Box::new([In(2), Not(Box::new(In(0))), Const(true), In(2)]));
896 let mut compact = tree.clone();
897 compact.compact_affine();
898 assert_eq!(
899 compact,
900 Parity {
901 inputs: Box::new([2, 0, 2]),
902 constant: false
903 }
904 );
905 assert_eq!(map_inputs(&compact, &mut In), compact);
906 for a in [None, Some(false), Some(true)] {
907 for b in [None, Some(false), Some(true)] {
908 let mut input = |slot| if slot == 0 { a } else { b };
909 assert_eq!(compact.eval(&mut input), tree.eval(&mut input));
910 for expr in [&compact, &tree] {
911 let mut visited = Vec::new();
912 let mapped = map_inputs(expr, &mut |slot| {
913 visited.push(slot);
914 Not(Box::new(In(slot)))
915 });
916 assert_eq!(visited, [2, 0, 2]);
917 assert_eq!(
918 mapped.eval(&mut input),
919 tree.eval(&mut |slot| input(slot).map(|bit| !bit))
920 );
921 }
922 }
923 }
924 }
925
926 #[test]
927 fn doubled_layers_cover_the_full_source_coordinate_range() {
928 let block = BloqNode::from_members(vec![SourceBlockRef {
929 pos: ivec3(0, 0, i32::MAX),
930 }]);
931 let pipe = BloqNode::from_temporal_pipe(TemporalPipeRef {
932 src: ivec3(0, 0, i32::MAX - 1),
933 dst: ivec3(0, 0, i32::MAX),
934 hadamard: true,
935 });
936
937 assert_eq!(block.layer(), 2 * i64::from(i32::MAX));
938 assert_eq!(pipe.layer(), 2 * i64::from(i32::MAX - 1) + 1);
939 }
940 #[test]
941 fn operand_lists_and_selection_preserve_strict_unknowns_and_input_order() {
942 use ClassicalExpr::{And, In, Or, Select, Xor};
943 let expressions = [
944 Xor(Box::new([In(0), In(1), In(2)])),
945 And(Box::new([In(0), In(1), In(2)])),
946 Or(Box::new([In(0), In(1), In(2)])),
947 Select(Box::new([In(0), In(1), In(2)])),
948 ];
949 for a in [None, Some(false), Some(true)] {
950 for b in [None, Some(false), Some(true)] {
951 for c in [None, Some(false), Some(true)] {
952 let expected = a.zip(b).zip(c).map(|((a, b), c)| {
953 [a ^ b ^ c, a & b & c, a | b | c, if a { c } else { b }]
954 });
955 for (index, expr) in expressions.iter().enumerate() {
956 assert_eq!(
957 expr.eval(&mut |slot| [a, b, c][slot as usize]),
958 expected.map(|bits| bits[index])
959 );
960 }
961 for literal in 0..27 {
962 let operands: [_; 3] = std::array::from_fn(|slot| {
963 match literal / 3usize.pow(slot as u32) % 3 {
964 0 => In(slot as u32),
965 value => ClassicalExpr::Const(value == 2),
966 }
967 });
968 let raw = Select(Box::new(operands.clone()));
969 let [condition, low, high] = operands;
970 let compact = ClassicalExpr::select(condition, low, high);
971 assert_eq!(
972 compact.eval(&mut |slot| [a, b, c][slot as usize]),
973 raw.eval(&mut |slot| [a, b, c][slot as usize])
974 );
975 }
976 }
977 }
978 }
979 for expr in &expressions {
980 let mut visited = Vec::new();
981 let mapped = map_inputs(expr, &mut |slot| {
982 visited.push(slot);
983 ClassicalExpr::Not(Box::new(In(2 - slot)))
984 });
985 assert_eq!(visited, [0, 1, 2]);
986 visited.clear();
987 mapped.for_each_input(&mut |slot| visited.push(slot));
988 assert_eq!(visited, [2, 1, 0]);
989 }
990 assert!(expressions[0].is_linear());
991 assert!(expressions[1..].iter().all(|expr| !expr.is_linear()));
992 for (expr, expected) in [
993 (Xor(Box::new([])), false),
994 (And(Box::new([])), true),
995 (Or(Box::new([])), false),
996 ] {
997 assert_eq!(
998 expr.eval(&mut |_| panic!("empty expression reads no input")),
999 Some(expected)
1000 );
1001 }
1002 for expr in [
1003 Xor(Box::new([In(0)])),
1004 And(Box::new([In(0)])),
1005 Or(Box::new([In(0)])),
1006 ] {
1007 for bit in [None, Some(false), Some(true)] {
1008 assert_eq!(expr.eval(&mut |_| bit), bit);
1009 }
1010 }
1011 #[cfg(target_pointer_width = "64")]
1012 assert_eq!(std::mem::size_of::<ClassicalExpr>(), 24);
1013 }
1014}