Repository: QCHackers/tqec (python)

Issue:
Memory experiments are not valid as block graph
## Describe the bug
I'm not sure if this is the intended behavior but we cannot generate a simple memory experiment as `ZXGraph` and convert it to a `BlockGraph`.

## Steps to reproduce the behavior:
```python
cubes = [
    (Position3D(0, 0, 0), CubeType.ZXZ),
    (Position3D(0, 0, 1), CubeType.ZXZ),
]
pipes = [
    (cubes[0][0], cubes[1][0], PipeType.ZXO),
]
block_graph = BlockGraph(name="Memory Experiment")
for cube in cubes:
    block_graph.add_cube(*cube)
block_graph.add_pipe(*pipes[0])
zx_from_block_graph = block_graph.to_zx_graph()
new_block_graph = BlockGraph.from_zx_graph(zx_from_block_graph) #<--This fails
```

## Expected behavior
I would expect this to work. Unfortunately, [from_zx_graph](https://github.com/QCHackers/tqec/blob/3ce9649c528fc245ecdb85a5c8a52e94806f88b9/src/tqec/sketchup/block_graph.py#L496) is locking for a corner block.

## Screenshots and MP4s
![Figure_1](https://github.com/user-attachments/assets/d075b977-add7-4f3e-a166-b4708c9619e5)



Pull request description:
Choose orientation for simple `ZXGraph` when converting to `BlockGraph`
Fix #304 and #308.

When the `ZXGraph` has no corner, the non-virtual node with minimum position will be chosen and the orientation will be selected. The X axis will be intended to align across the spatial X boundares of the cube if possible.

Patch:
diff --git a/src/tqec/sketchup/block_graph.py b/src/tqec/sketchup/block_graph.py
index 9b5d8db5..0e390903 100644
--- a/src/tqec/sketchup/block_graph.py
+++ b/src/tqec/sketchup/block_graph.py
@@ -501,11 +501,32 @@ class BlockGraph:
                     corner_cube_in_component = corner_cubes[pos]
                     bfs_sources.append(corner_cube_in_component)
                     break
+            # No corner cube can be found, then choose the orientation
+            # of a non-virtual node with minimum position in the component
             if corner_cube_in_component is None:
-                raise TQECException(
-                    "There should be at least one corner node in each connected component of"
-                    "the ZX graph to infer the block structure."
+                sorted_positions: list[Position3D] = sorted(component)
+                aligned_node = next(
+                    ty.cast(ZXNode, zx_graph.get_node(pos))
+                    for pos in sorted_positions
+                    if not ty.cast(ZXNode, zx_graph.get_node(pos)).is_virtual
                 )
+                node_pos, node_type = aligned_node.position, aligned_node.node_type
+                edges_at_node = zx_graph.edges_at(node_pos)
+                if not edges_at_node:
+                    aligned_node_type = (
+                        CubeType.XZZ if node_type == NodeType.Z else CubeType.XZX
+                    )
+                else:
+                    edge_direction = edges_at_node[0].direction
+                    node_type_list = ["x", "z"]
+                    node_type_list.insert(
+                        edge_direction.axis_index,
+                        "x" if node_type == NodeType.X else "z",
+                    )
+                    aligned_node_type = CubeType("".join(node_type_list))
+                block_graph.add_cube(aligned_node.position, aligned_node_type)
+                nodes_to_handle.remove(aligned_node)
+                bfs_sources.append(Cube(aligned_node.position, aligned_node_type))
         for src_cube in bfs_sources:
             for p1, p2 in nx.bfs_edges(zx_graph.nx_graph, src_cube.position):
                 edge = zx_graph.get_edge(p1, p2)


Test patch:
diff --git a/src/tqec/sketchup/block_graph_test.py b/src/tqec/sketchup/block_graph_test.py
index fe271572..4bdc3bdc 100644
--- a/src/tqec/sketchup/block_graph_test.py
+++ b/src/tqec/sketchup/block_graph_test.py
@@ -73,13 +73,29 @@ def test_validity_at_turn_color_match() -> None:
 
 
 def test_convert_zx_graph_no_corner() -> None:
-    # Cannot resolve the orientation of a simple idle
     g = ZXGraph("Test ZX Graph Idle")
     g.add_z_node(Position3D(0, 0, 0))
     g.add_z_node(Position3D(0, 0, 1))
     g.add_edge(Position3D(0, 0, 0), Position3D(0, 0, 1))
-    with pytest.raises(TQECException, match="There should be at least one corner node"):
-        g.to_block_graph()
+    bg = g.to_block_graph()
+    c1 = bg.get_cube(Position3D(0, 0, 0))
+    assert c1 is not None
+    assert c1.cube_type == CubeType.XZZ
+    c2 = bg.get_cube(Position3D(0, 0, 1))
+    assert c2 is not None
+    assert c2.cube_type == CubeType.XZZ
+
+    g2 = ZXGraph("Horizontal line")
+    g2.add_z_node(Position3D(0, 0, 0))
+    g2.add_x_node(Position3D(1, 0, 0))
+    g2.add_edge(Position3D(0, 0, 0), Position3D(1, 0, 0), has_hadamard=True)
+    bg2 = g2.to_block_graph()
+    c3 = bg2.get_cube(Position3D(0, 0, 0))
+    assert c3 is not None
+    assert c3.cube_type == CubeType.ZXZ
+    c4 = bg2.get_cube(Position3D(1, 0, 0))
+    assert c4 is not None
+    assert c4.cube_type == CubeType.XZX
 
 
 def test_convert_zx_graph_roundtrip() -> None: