1use crate::{
3 Basis, BitBinding, BitOutput, BitRef, Block, BlockGraph, BlockGraphError, BlockKind, Expr,
4 InstancePort, ModuleCertificationError, ModuleCertificationLimits, ModuleError, ModuleInstance,
5 ModuleRotation, PortDirection, PortRole, QuantumConnection, QuantumPort, checked_add_position,
6 flatten_module_definition,
7};
8use glam::IVec3;
9use std::collections::{BTreeSet, HashSet};
10use std::sync::Arc;
11
12fn invalid(message: impl Into<String>) -> BlockGraphError {
13 source_error(ModuleError::InvalidModule {
14 module: BlockGraph::ENTRY_MODULE.into(),
15 message: message.into(),
16 span: None,
17 })
18}
19fn source_error(error: ModuleError) -> BlockGraphError {
20 BlockGraphError::ModuleSource(Arc::new(error))
21}
22fn materialization(error: ModuleCertificationError) -> BlockGraphError {
23 BlockGraphError::ModuleMaterialization(Arc::new(error))
24}
25macro_rules! fail { ($($arg:tt)*) => { return Err(invalid(format!($($arg)*))) }; }
26macro_rules! ensure { ($condition:expr, $($arg:tt)*) => { if !$condition { fail!($($arg)*); } }; }
27
28#[derive(Debug, Clone, Copy)]
30pub struct ModuleJoinOptions {
31 pub align: bool,
33 pub compact: bool,
35 pub hadamard: bool,
37}
38impl Default for ModuleJoinOptions {
39 fn default() -> Self {
40 Self {
41 align: true,
42 compact: true,
43 hadamard: false,
44 }
45 }
46}
47
48impl BlockGraph {
49 pub fn place_module(&mut self, name: &str, definition: &str) -> Result<IVec3, BlockGraphError> {
56 self.place_module_with(name, definition, None, ModuleRotation::IDENTITY)
57 }
58
59 pub fn place_module_with(
65 &mut self,
66 name: &str,
67 definition: &str,
68 translation: Option<IVec3>,
69 rotation: ModuleRotation,
70 ) -> Result<IVec3, BlockGraphError> {
71 let (next, position) = place_module(self, name, definition, translation, rotation)?;
72 *self = next;
73 Ok(position)
74 }
75
76 pub fn connect_modules(&mut self, output: &str, input: &str) -> Result<usize, BlockGraphError> {
83 self.connect_modules_with(output, input, ModuleJoinOptions::default())
84 }
85
86 pub fn connect_modules_with(
91 &mut self,
92 output: &str,
93 input: &str,
94 options: ModuleJoinOptions,
95 ) -> Result<usize, BlockGraphError> {
96 self.validate_with_limits(ModuleCertificationLimits::DEFAULT)
97 .map_err(source_error)?;
98 let result = connect_ports(
99 self,
100 endpoint(output)?,
101 endpoint(input)?,
102 options.hadamard,
103 options.align,
104 options.compact,
105 )?;
106 let count = result.connections;
107 *self = result.program;
108 Ok(count)
109 }
110
111 pub fn bind_modules(&mut self, source: &str, target: &str) -> Result<(), BlockGraphError> {
117 self.validate_with_limits(ModuleCertificationLimits::DEFAULT)
118 .map_err(source_error)?;
119 let target = endpoint(target)?;
120 let source = if source.contains('.') {
121 let source = endpoint(source)?;
122 BitRef {
123 instance: Some(source.instance),
124 bit: source.port,
125 }
126 } else {
127 BitRef {
128 instance: None,
129 bit: source.into(),
130 }
131 };
132 let mut modules = self
133 .modules()
134 .map(BlockGraph::clone_local_definition)
135 .collect::<Vec<_>>();
136 let root = modules
137 .iter_mut()
138 .find(|m| m.name == Self::ENTRY_MODULE)
139 .expect("validated program has main");
140 let binding = root
141 .bit_bindings
142 .iter_mut()
143 .find(|b| b.target_instance == target.instance && b.target_bit == target.port)
144 .ok_or_else(|| invalid("unknown child classical input"))?;
145 let old = std::mem::replace(&mut binding.source, source);
146 prune_unused_input(root, &old)?;
147 *self = BlockGraph::from_definitions(modules).map_err(source_error)?;
148 Ok(())
149 }
150
151 pub fn translate_module(
157 &mut self,
158 name: &str,
159 offset: IVec3,
160 compact: bool,
161 ) -> Result<IVec3, BlockGraphError> {
162 self.validate_with_limits(ModuleCertificationLimits::DEFAULT)
163 .map_err(source_error)?;
164 let result = translate_module(self, name, offset, compact)?;
165 let offset = result.offset;
166 *self = result.program;
167 Ok(offset)
168 }
169}
170fn endpoint(text: &str) -> Result<InstancePort, BlockGraphError> {
171 let (instance, port) = text
172 .split_once('.')
173 .filter(|(instance, port)| !instance.is_empty() && !port.is_empty() && !port.contains('.'))
174 .ok_or_else(|| invalid("expected a quantum port name 'instance.port'"))?;
175 Ok(InstancePort {
176 instance: instance.into(),
177 port: port.into(),
178 })
179}
180
181pub fn place_module(
182 program: &BlockGraph,
183 name: &str,
184 definition: &str,
185 translation: Option<IVec3>,
186 rotation: ModuleRotation,
187) -> Result<(BlockGraph, IVec3), BlockGraphError> {
188 program
189 .validate_with_limits(ModuleCertificationLimits::DEFAULT)
190 .map_err(source_error)?;
191 ensure!(
192 definition != BlockGraph::ENTRY_MODULE,
193 "A module cannot contain itself"
194 );
195 let mut modules = program
196 .modules()
197 .map(BlockGraph::clone_local_definition)
198 .collect::<Vec<_>>();
199 let child = program
200 .module(definition)
201 .ok_or_else(|| invalid("Module no longer exists"))?;
202 let child_graph = flatten_module_definition(program, child, "")?.graph;
203 ensure!(
204 !child_graph.is_empty(),
205 "Build some geometry before placing this module"
206 );
207 let translation = match translation {
208 Some(position) => position,
209 None => position_beside(
210 &program.flatten().map_err(materialization)?,
211 &child_graph,
212 rotation,
213 )?,
214 };
215 let root = modules
216 .iter_mut()
217 .find(|m| m.name == BlockGraph::ENTRY_MODULE)
218 .expect("validated program has main");
219 root.interface_declared = true;
220 ensure!(
221 !root.instances.iter().any(|i| i.name == name),
222 "Instance '{name}' already exists"
223 );
224 let instance = ModuleInstance {
225 name: name.into(),
226 definition: definition.into(),
227 translation,
228 rotation,
229 };
230 for port in &child.interface.quantum_ports {
231 expose_port(program, root, &instance, port)?;
232 }
233 for input in &child.interface.bit_inputs {
234 let name = fresh_interface_name(root, &format!("{}_{}", instance.name, input));
235 root.interface.bit_inputs.push(name.clone());
236 root.bit_bindings.push(BitBinding {
237 source: BitRef {
238 instance: None,
239 bit: name,
240 },
241 target_instance: instance.name.clone(),
242 target_bit: input.clone(),
243 });
244 }
245 for output in &child.interface.bit_outputs {
246 root.interface.bit_outputs.push(BitOutput {
247 name: fresh_interface_name(root, &format!("{}_{}", instance.name, output.name)),
248 expr: Expr::Var(format!("{}.{}", instance.name, output.name)),
249 });
250 }
251 root.instances.push(instance);
252
253 Ok((
254 BlockGraph::from_definitions(modules).map_err(source_error)?,
255 translation,
256 ))
257}
258
259#[derive(Debug)]
260pub struct ModuleTranslation {
261 pub program: BlockGraph,
262 pub offset: IVec3,
263 pub connections: usize,
264 pub compacted: bool,
265}
266
267#[derive(Clone, Debug)]
268pub struct ConnectionSeam {
269 pub output: InstancePort,
270 pub input: InstancePort,
271 pub hadamard: bool,
272 pub cube: Option<IVec3>,
273}
274
275pub fn connection_seams(root: &BlockGraph) -> Vec<ConnectionSeam> {
278 let mut seams = Vec::new();
279 for connection in &root.quantum_connections {
280 match connection {
281 QuantumConnection::Pipe {
282 output,
283 input,
284 hadamard,
285 } => seams.push(ConnectionSeam {
286 output: output.clone(),
287 input: input.clone(),
288 hadamard: *hadamard,
289 cube: None,
290 }),
291 QuantumConnection::Output {
292 output,
293 block,
294 hadamard,
295 } => {
296 if !root
297 .get_block(*block)
298 .is_some_and(|block| block.kind().is_cube())
299 || !root.neighbor_positions(*block).is_empty()
300 || root.shown_branch_at(*block).is_some()
301 {
302 continue;
303 }
304 let bindings = root
305 .quantum_connections
306 .iter()
307 .filter(|connection| bound_block(connection) == Some(*block))
308 .collect::<Vec<_>>();
309 if bindings.len() == 2
310 && let Some((input, input_h)) =
311 bindings.iter().find_map(|binding| match binding {
312 QuantumConnection::Input {
313 input, hadamard, ..
314 } => Some((input, *hadamard)),
315 _ => None,
316 })
317 {
318 seams.push(ConnectionSeam {
319 output: output.clone(),
320 input: input.clone(),
321 hadamard: *hadamard ^ input_h,
322 cube: Some(*block),
323 });
324 }
325 }
326 _ => {}
327 }
328 }
329 seams
330}
331
332pub fn connected_instances(root: &BlockGraph, start: &str) -> BTreeSet<String> {
333 let seams = connection_seams(root);
334 let mut group = BTreeSet::from([start.to_owned()]);
335 let mut pending = vec![start.to_owned()];
336 while let Some(name) = pending.pop() {
337 for seam in &seams {
338 let other = if seam.output.instance == name {
339 &seam.input.instance
340 } else if seam.input.instance == name {
341 &seam.output.instance
342 } else {
343 continue;
344 };
345 if group.insert(other.clone()) {
346 pending.push(other.clone());
347 }
348 }
349 }
350 group
351}
352
353struct OpenPort {
354 endpoint: InstancePort,
355 port: QuantumPort,
356 neighbor: IVec3,
357 bases: [Option<Basis>; 3],
358 hadamard: bool,
359}
360
361struct PortJoin<'a> {
362 output: &'a OpenPort,
363 input: &'a OpenPort,
364 position: IVec3,
365 compact_step: Option<IVec3>,
366 hadamard: bool,
367}
368
369impl PortJoin<'_> {
370 fn cube_kind(&self) -> Result<BlockKind, BlockGraphError> {
371 for kind in BlockKind::all_kinds() {
372 let BlockKind::Cube(cube) = kind else {
373 continue;
374 };
375 let matches = [(self.output, false), (self.input, self.hadamard)]
376 .into_iter()
377 .all(|(port, flip)| {
378 port.bases
379 .iter()
380 .zip(cube.bases())
381 .all(|(face, cube_face)| {
382 face.is_none_or(
383 |face| if flip { face.flip() } else { face } == cube_face,
384 )
385 })
386 });
387 if matches {
388 return Ok(kind);
389 }
390 }
391 fail!("Port faces do not match any connection cube; rotate the module or choose Hadamard")
392 }
393}
394
395fn open_ports(program: &BlockGraph) -> Result<Vec<OpenPort>, BlockGraphError> {
396 let graph = program.flatten().map_err(materialization)?;
397 let mut ports = Vec::new();
398 for (endpoint, public) in exposed_ports(program) {
399 let position = public.position;
400 let neighbors = graph.neighbor_positions(position);
401 let [neighbor] = neighbors.as_slice() else {
402 fail!("Port needs one incident pipe")
403 };
404 let pipe = graph
405 .get_pipe(position, *neighbor)
406 .expect("the neighbor came from this position's incident pipes");
407 ports.push(OpenPort {
408 endpoint,
409 port: public.clone(),
410 neighbor: *neighbor,
411 bases: graph.infer_pipe_basis_from_endpoint(pipe, position),
412 hadamard: pipe.is_hadamard(),
413 });
414 }
415 Ok(ports)
416}
417
418pub fn translate_module(
419 program: &BlockGraph,
420 name: &str,
421 offset: IVec3,
422 compact: bool,
423) -> Result<ModuleTranslation, BlockGraphError> {
424 translate_and_connect(program, name, offset, compact, None)
425}
426
427pub fn connect_ports(
428 program: &BlockGraph,
429 output: InstancePort,
430 input: InstancePort,
431 hadamard: bool,
432 align: bool,
433 compact: bool,
434) -> Result<ModuleTranslation, BlockGraphError> {
435 ensure!(
436 output.instance != input.instance,
437 "Choose ports on different instances"
438 );
439 let output_port = exposed_port(program.root(), &output)
440 .ok_or_else(|| invalid("Output is already connected"))?;
441 let input_port = exposed_port(program.root(), &input)
442 .ok_or_else(|| invalid("Input is already connected"))?;
443 ensure!(
444 output_port.direction == PortDirection::Output
445 && input_port.direction == PortDirection::Input,
446 "Connect an output to an input"
447 );
448 let offset = if align {
449 checked_difference(output_port.position, input_port.position)?
450 } else {
451 IVec3::ZERO
452 };
453 let name = input.instance.clone();
454 translate_and_connect(
455 program,
456 &name,
457 offset,
458 compact,
459 Some((output, input, hadamard)),
460 )
461}
462
463fn translate_and_connect(
464 program: &BlockGraph,
465 name: &str,
466 offset: IVec3,
467 compact: bool,
468 requested: Option<(InstancePort, InstancePort, bool)>,
469) -> Result<ModuleTranslation, BlockGraphError> {
470 ensure!(
471 program
472 .root()
473 .instances
474 .iter()
475 .any(|instance| instance.name == name),
476 "Instance no longer exists"
477 );
478 let group = connected_instances(program.root(), name);
479 let ports = open_ports(program)?;
480 let mut pairs = Vec::new();
481 for moving in ports
482 .iter()
483 .filter(|port| group.contains(&port.endpoint.instance))
484 {
485 let position = checked_add_position(moving.port.position, offset)?;
486 for fixed in ports
487 .iter()
488 .filter(|port| !group.contains(&port.endpoint.instance))
489 {
490 if position != fixed.port.position {
491 continue;
492 }
493 ensure!(
494 moving.port.direction != fixed.port.direction,
495 "Ports '{}.{}' and '{}.{}' must join an output to an input",
496 moving.endpoint.instance,
497 moving.endpoint.port,
498 fixed.endpoint.instance,
499 fixed.endpoint.port
500 );
501 ensure!(
502 moving.port.resource_type == fixed.port.resource_type,
503 "Touching ports have different resource types"
504 );
505 let (output, input) = if moving.port.direction == PortDirection::Output {
506 (moving, fixed)
507 } else {
508 (fixed, moving)
509 };
510 let hadamard = requested
511 .as_ref()
512 .is_some_and(|(a, b, h)| *h && a == &output.endpoint && b == &input.endpoint);
513 let step = checked_difference(moving.port.position, moving.neighbor)?;
514 pairs.push(PortJoin {
515 output,
516 input,
517 hadamard,
518 position: fixed.port.position,
519 compact_step: (fixed.neighbor - fixed.port.position == step).then_some(step),
520 });
521 }
522 }
523 if let Some((output, input, _)) = requested {
524 ensure!(
525 pairs
526 .iter()
527 .any(|join| join.output.endpoint == output && join.input.endpoint == input),
528 "Ports do not meet; enable Align input instance or move them together first"
529 );
530 }
531 let build = |delta: IVec3, compacted: bool| -> Result<BlockGraph, BlockGraphError> {
532 let mut modules = program
533 .modules()
534 .map(BlockGraph::clone_local_definition)
535 .collect::<Vec<_>>();
536 let root = modules
537 .iter_mut()
538 .find(|module| module.name == BlockGraph::ENTRY_MODULE)
539 .expect("validated program contains the entry module");
540 for join in &pairs {
541 consume_exposed_port(root, &join.output.endpoint)?;
542 consume_exposed_port(root, &join.input.endpoint)?;
543 }
544 translate_group(root, &group, delta)?;
545 for join in &pairs {
546 let PortJoin {
547 output,
548 input,
549 hadamard,
550 ..
551 } = join;
552 if compacted {
553 root.quantum_connections.push(QuantumConnection::Pipe {
554 output: output.endpoint.clone(),
555 input: input.endpoint.clone(),
556 hadamard: output.hadamard ^ input.hadamard ^ *hadamard,
557 });
558 } else {
559 let block = join.position;
560 root.try_add_block(Block::new(block, join.cube_kind()?))?;
561 root.quantum_connections.extend([
562 QuantumConnection::Output {
563 output: output.endpoint.clone(),
564 block,
565 hadamard: false,
566 },
567 QuantumConnection::Input {
568 block,
569 input: input.endpoint.clone(),
570 hadamard: *hadamard,
571 },
572 ]);
573 }
574 }
575 ensure!(
576 root.actions() == program.root().local_body().actions()
577 && root.branch_definitions() == program.root().local_body().branch_definitions(),
578 "Move would change parent actions or branches; edit the owning definition instead"
579 );
580 BlockGraph::from_definitions(modules).map_err(source_error)
581 };
582 if compact
583 && let Some(step) = pairs.first().and_then(|join| join.compact_step)
584 && pairs.iter().all(|join| join.compact_step == Some(step))
585 && let Ok(compact_offset) = checked_add_position(offset, step)
586 {
587 match build(compact_offset, true) {
588 Ok(program) => {
589 return Ok(ModuleTranslation {
590 program,
591 offset: compact_offset,
592 connections: pairs.len(),
593 compacted: true,
594 });
595 }
596 Err(error) => {
597 let mut cause: Option<&dyn std::error::Error> = Some(&error);
598 while let Some(current) = cause {
599 if matches!(
600 current.downcast_ref::<ModuleCertificationError>(),
601 Some(ModuleCertificationError::ResourceLimited { .. })
602 ) {
603 return Err(error);
604 }
605 cause = current.source();
606 }
607 }
608 }
609 }
610 Ok(ModuleTranslation {
611 program: build(offset, false)?,
612 offset,
613 connections: pairs.len(),
614 compacted: false,
615 })
616}
617
618pub fn translate_group(
623 graph: &mut BlockGraph,
624 group: &BTreeSet<String>,
625 offset: IVec3,
626) -> Result<(), BlockGraphError> {
627 let mut root = graph.clone();
628 let mut positions = connection_seams(&root)
629 .iter()
630 .filter(|seam| {
631 group.contains(&seam.output.instance) && group.contains(&seam.input.instance)
632 })
633 .filter_map(|seam| seam.cube)
634 .collect::<HashSet<_>>();
635 for connection in &root.quantum_connections {
636 let endpoint = match connection {
637 QuantumConnection::Input {
638 input: endpoint, ..
639 }
640 | QuantumConnection::Output {
641 output: endpoint, ..
642 } => endpoint,
643 _ => continue,
644 };
645 if group.contains(&endpoint.instance)
646 && let Some(port) = exposed_port(&root, endpoint)
647 {
648 positions.insert(port.position);
649 }
650 }
651 let mut blocks = positions
652 .iter()
653 .map(|position| {
654 root.get_block(*position)
655 .expect("validated interface ports and binds sit on body blocks")
656 .clone()
657 })
658 .collect::<Vec<_>>();
659 blocks.sort_by_key(|block| block.pos().to_array());
660 for position in &positions {
661 root.remove_block(*position);
662 }
663 for block in blocks {
664 root.try_add_block(block.try_with_shift(offset)?)?;
665 }
666 for port in &mut root.interface.quantum_ports {
667 if positions.contains(&port.position) {
668 port.position = checked_add_position(port.position, offset)?;
669 }
670 }
671 for connection in &mut root.quantum_connections {
672 if let QuantumConnection::Input { block, .. } | QuantumConnection::Output { block, .. } =
673 connection
674 && positions.contains(block)
675 {
676 *block = checked_add_position(*block, offset)?;
677 }
678 }
679 for instance in &mut root.instances {
680 if group.contains(&instance.name) {
681 instance.translation = checked_add_position(instance.translation, offset)?;
682 }
683 }
684 *graph = root;
685 Ok(())
686}
687
688pub fn unique_name(base: &str, used: &HashSet<String>) -> String {
689 if !used.contains(base) {
690 return base.to_owned();
691 }
692 (2..)
693 .map(|i| format!("{base}_{i}"))
694 .find(|name| !used.contains(name))
695 .expect("an unbounded counter always yields a name outside a finite set")
696}
697
698pub fn fresh_interface_name(module: &BlockGraph, base: &str) -> String {
699 let used = module
700 .interface
701 .quantum_ports
702 .iter()
703 .map(|p| p.name.clone())
704 .chain(module.interface.bit_inputs.iter().cloned())
705 .chain(module.interface.bit_outputs.iter().map(|p| p.name.clone()))
706 .collect();
707 unique_name(base, &used)
708}
709
710pub fn expose_port(
711 program: &BlockGraph,
712 root: &mut BlockGraph,
713 instance: &ModuleInstance,
714 port: &QuantumPort,
715) -> Result<(), BlockGraphError> {
716 let mut public = port.clone();
717 public.position = instance.try_transform_position(port.position)?;
718 public.name = fresh_interface_name(root, &format!("{}_{}", instance.name, port.name));
719 let prototype = program
720 .module(&instance.definition)
721 .expect("validated program resolves every instance definition")
722 .get_block(port.position)
723 .expect("validated interface port sits on a body block");
724 let role = if prototype.port_role() == Some(PortRole::Multiplex) {
725 PortRole::Multiplex
726 } else {
727 match port.direction {
728 PortDirection::Input => PortRole::Input,
729 PortDirection::Output => PortRole::Output,
730 }
731 };
732 root.try_add_block(
733 prototype
734 .try_with_shift(checked_difference(public.position, port.position)?)?
735 .with_port_role(role)?
736 .with_tag(public.name.clone())?,
737 )?;
738 let endpoint = InstancePort {
739 instance: instance.name.clone(),
740 port: port.name.clone(),
741 };
742 root.quantum_connections.push(match port.direction {
743 PortDirection::Input => QuantumConnection::Input {
744 block: public.position,
745 input: endpoint,
746 hadamard: false,
747 },
748 PortDirection::Output => QuantumConnection::Output {
749 block: public.position,
750 output: endpoint,
751 hadamard: false,
752 },
753 });
754 root.interface.quantum_ports.push(public);
755 Ok(())
756}
757
758pub fn exposed_port<'a>(root: &'a BlockGraph, endpoint: &InstancePort) -> Option<&'a QuantumPort> {
759 let position = root
760 .quantum_connections
761 .iter()
762 .find_map(|connection| match connection {
763 QuantumConnection::Input {
764 block,
765 input: port,
766 hadamard: false,
767 }
768 | QuantumConnection::Output {
769 block,
770 output: port,
771 hadamard: false,
772 } if port == endpoint => Some(*block),
773 _ => None,
774 })?;
775 (root.neighbor_positions(position).is_empty()
776 && root
777 .quantum_connections
778 .iter()
779 .filter(|c| bound_block(c) == Some(position))
780 .count()
781 == 1)
782 .then(|| {
783 root.interface
784 .quantum_ports
785 .iter()
786 .find(|p| p.position == position)
787 })
788 .flatten()
789}
790
791pub fn exposed_ports(program: &BlockGraph) -> impl Iterator<Item = (InstancePort, &QuantumPort)> {
793 program.root().instances.iter().flat_map(move |instance| {
794 program
795 .module(&instance.definition)
796 .expect("validated program resolves every instance definition")
797 .interface
798 .quantum_ports
799 .iter()
800 .filter_map(move |port| {
801 let endpoint = InstancePort {
802 instance: instance.name.clone(),
803 port: port.name.clone(),
804 };
805 exposed_port(program.root(), &endpoint).map(|port| (endpoint, port))
806 })
807 })
808}
809
810fn consume_exposed_port(
811 root: &mut BlockGraph,
812 endpoint: &InstancePort,
813) -> Result<(), BlockGraphError> {
814 let position = exposed_port(root, endpoint)
815 .ok_or_else(|| invalid("Port is already connected or bound to local geometry"))?
816 .position;
817 root.quantum_connections
818 .retain(|c| bound_block(c) != Some(position));
819 remove_public_port(root, position);
820 Ok(())
821}
822
823pub fn remove_public_port(root: &mut BlockGraph, position: IVec3) {
824 if root
825 .interface
826 .quantum_ports
827 .iter()
828 .any(|p| p.position == position)
829 {
830 root.interface
831 .quantum_ports
832 .retain(|p| p.position != position);
833 root.remove_block(position);
834 }
835}
836
837pub fn bound_block(connection: &QuantumConnection) -> Option<IVec3> {
838 match connection {
839 QuantumConnection::Input { block, .. } | QuantumConnection::Output { block, .. } => {
840 Some(*block)
841 }
842 QuantumConnection::Pipe { .. } => None,
843 }
844}
845
846fn checked_difference(a: IVec3, b: IVec3) -> Result<IVec3, BlockGraphError> {
847 let error = || BlockGraphError::CoordinateOverflow {
848 position: a,
849 offset: b,
850 };
851 Ok(IVec3::new(
852 a.x.checked_sub(b.x).ok_or_else(error)?,
853 a.y.checked_sub(b.y).ok_or_else(error)?,
854 a.z.checked_sub(b.z).ok_or_else(error)?,
855 ))
856}
857
858fn position_beside(
859 graph: &BlockGraph,
860 child: &BlockGraph,
861 rotation: ModuleRotation,
862) -> Result<IVec3, BlockGraphError> {
863 let Some((x, _, _)) = graph.spans() else {
864 return Ok(IVec3::ZERO);
865 };
866 let (child_x, child_y, child_z) = child
867 .spans()
868 .ok_or_else(|| invalid("Module has no geometry"))?;
869 let mut min_x = i32::MAX;
870 for cx in [*child_x.start(), *child_x.end()] {
871 for cy in [*child_y.start(), *child_y.end()] {
872 for cz in [*child_z.start(), *child_z.end()] {
873 min_x = min_x.min(rotation.try_rotate_position(IVec3::new(cx, cy, cz))?.x);
874 }
875 }
876 }
877 let x = x
878 .end()
879 .checked_sub(min_x)
880 .and_then(|x| x.checked_add(3))
881 .ok_or_else(|| invalid("No coordinate space beside this composition"))?;
882 Ok(IVec3::new(x, 0, 0))
883}
884
885pub fn prune_unused_input(root: &mut BlockGraph, source: &BitRef) -> Result<(), BlockGraphError> {
886 if source.instance.is_none()
887 && !root.bit_bindings.iter().any(|b| b.source == *source)
888 && !root
889 .actions()
890 .iter()
891 .any(|action| action.referenced_names().contains(source.bit.as_str()))
892 && !root
893 .interface
894 .bit_outputs
895 .iter()
896 .any(|output| expr_uses(&output.expr, &source.bit))
897 {
898 let inputs = root
899 .action_graph()
900 .inputs()
901 .filter(|name| *name != source.bit)
902 .map(str::to_owned)
903 .collect::<Vec<_>>();
904 root.set_actions_deferred_with_inputs(root.actions(), inputs)?;
905 root.interface.bit_inputs.retain(|name| name != &source.bit);
906 }
907 Ok(())
908}
909
910fn expr_uses(expr: &Expr, name: &str) -> bool {
911 match expr {
912 Expr::Var(value) => value == name,
913 Expr::Not(value) => expr_uses(value, name),
914 Expr::Binary(_, a, b) => expr_uses(a, name) || expr_uses(b, name),
915 }
916}
917
918#[cfg(test)]
919mod tests {
920 use super::*;
921 use crate::{GalleryItem, UDirection};
922
923 fn cnot_program() -> BlockGraph {
924 let mut definition = GalleryItem::CNOT.build();
925 definition.name = "CNOT".into();
926 BlockGraph::from_definitions(vec![definition, BlockGraph::new()]).unwrap()
927 }
928
929 #[test]
930 fn placement_docking_and_group_translation_preserve_the_library() {
931 let mut graph = cnot_program();
932 let original = graph.module("CNOT").unwrap().to_blog_text();
933 for index in 0..3 {
934 graph
935 .place_module(&format!("child{index}"), "CNOT")
936 .unwrap();
937 }
938 assert_eq!(
939 graph
940 .connect_modules("child0.control_out", "child1.control_in")
941 .unwrap(),
942 2
943 );
944 assert_eq!(
945 graph
946 .connect_modules("child1.control_out", "child2.control_in")
947 .unwrap(),
948 2
949 );
950 assert_eq!(graph.interface.quantum_ports.len(), 4);
951 assert_eq!(
952 graph
953 .instances
954 .iter()
955 .map(|i| i.translation)
956 .collect::<Vec<_>>(),
957 [IVec3::ZERO, IVec3::new(0, 0, 2), IVec3::new(0, 0, 4)]
958 );
959 assert_eq!(graph.module("CNOT").unwrap().to_blog_text(), original);
960 assert_eq!(
961 graph
962 .translate_module("child0", IVec3::Y * 3, true)
963 .unwrap(),
964 IVec3::Y * 3
965 );
966 assert!(graph.instances.iter().all(|i| i.translation.y == 3));
967 assert_eq!(graph.module("CNOT").unwrap().to_blog_text(), original);
968 let restored = BlockGraph::from_text(&graph.to_blog_text()).unwrap();
969 assert_eq!(restored.interface, graph.interface);
970 assert_eq!(restored.instances, graph.instances);
971 assert_eq!(restored.quantum_connections, graph.quantum_connections);
972 for block in graph.blocks() {
973 assert_eq!(restored.get_block(block.pos()), Some(block));
974 }
975 }
976
977 #[test]
978 fn failed_group_translation_preserves_the_graph() {
979 let mut graph = cnot_program();
980 graph.place_module("child", "CNOT").unwrap();
981 graph.add_block(Block::new(IVec3::X * 10, BlockKind::Port));
982 let before = graph.to_blog_text();
983 let group = BTreeSet::from(["child".to_owned()]);
984 for offset in [IVec3::splat(i32::MAX), IVec3::X * 10] {
985 translate_group(&mut graph, &group, offset)
986 .expect_err("shifted boundary coordinates overflow or collide");
987 assert_eq!(graph.to_blog_text(), before);
988 }
989 }
990
991 #[test]
992 fn failed_edits_are_atomic_and_rotated_placement_stays_beside_the_graph() {
993 let mut graph = cnot_program();
994 graph.place_module("child0", "CNOT").unwrap();
995 graph
996 .place_module_with(
997 "child1",
998 "CNOT",
999 None,
1000 ModuleRotation::new(UDirection::Z, 2),
1001 )
1002 .unwrap();
1003 let before = graph.to_blog_text();
1004 for (a, b) in [
1005 ("child0.control_in", "child1.control_in"),
1006 ("child0.control_out", "missing.control_in"),
1007 ("child0", "child1.control_in"),
1008 ] {
1009 graph
1010 .connect_modules(a, b)
1011 .expect_err("invalid endpoints must fail");
1012 assert_eq!(graph.to_blog_text(), before);
1013 }
1014 graph
1015 .place_module("child0", "CNOT")
1016 .expect_err("duplicate instance must fail");
1017 graph
1018 .place_module("new", "missing")
1019 .expect_err("missing definition must fail");
1020 graph
1021 .place_module_with(
1022 "new",
1023 "CNOT",
1024 Some(IVec3::splat(i32::MAX)),
1025 ModuleRotation::IDENTITY,
1026 )
1027 .expect_err("coordinate overflow must fail");
1028 assert_eq!(graph.to_blog_text(), before);
1029 }
1030}