@@ -816,11 +857,11 @@ def stim_circuit_html_viewer(
handleLayerIndexChange();
});
document.addEventListener('keydown', ev => {
- if (ev.code == "KeyA" && !ev.getModifierState("Control")) {
+ if (ev.code == "KeyQ" && !ev.getModifierState("Control")) {
layer_index -= 1;
ev.preventDefault();
handleLayerIndexChange();
- } else if (ev.code == "KeyD") {
+ } else if (ev.code == "KeyE") {
layer_index += 1;
ev.preventDefault();
handleLayerIndexChange();
diff --git a/src/gen/_surf/_viz_gltf_3d.py b/src/gen/_viz_gltf_3d.py
similarity index 87%
rename from src/gen/_surf/_viz_gltf_3d.py
rename to src/gen/_viz_gltf_3d.py
index a19e8f5..7409a66 100644
--- a/src/gen/_surf/_viz_gltf_3d.py
+++ b/src/gen/_viz_gltf_3d.py
@@ -1,17 +1,3 @@
-# Copyright 2023 Google LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
import base64
import collections
from typing import Iterable, Sequence
@@ -125,7 +111,7 @@ def gltf_model_from_colored_triangle_data(
colored_line_data = ColoredLineData.fused(colored_line_data)
gltf = pygltflib.GLTF2()
- gltf.asset = None
+ gltf.asset = {'version': '2.0'}
material_INDICES = {}
for data in colored_triangle_data:
@@ -138,7 +124,7 @@ def gltf_model_from_colored_triangle_data(
material.alphaMode = None
material.alphaCutoff = None
material.doubleSided = True
- material_INDICES[data.rgba] = len(gltf.materials)
+ material_INDICES[(data.rgba, 'tri')] = len(gltf.materials)
gltf.materials.append(material)
for data in colored_line_data:
material = pygltflib.Material()
@@ -149,7 +135,7 @@ def gltf_model_from_colored_triangle_data(
material.emissiveFactor = None
material.alphaMode = None
material.alphaCutoff = None
- material_INDICES[data.rgba] = len(gltf.materials)
+ material_INDICES[(data.rgba, 'edge')] = len(gltf.materials)
gltf.materials.append(material)
shared_buffer = pygltflib.Buffer()
@@ -182,7 +168,7 @@ def gltf_model_from_colored_triangle_data(
bufferView.byteLength = byte_length
byte_offset += byte_length
bufferView.target = pygltflib.ARRAY_BUFFER
- buffer_view_INDICES[data.rgba] = len(gltf.bufferViews)
+ buffer_view_INDICES[(data.rgba, 'tri')] = len(gltf.bufferViews)
gltf.bufferViews.append(bufferView)
for data in colored_line_data:
bufferView = pygltflib.BufferView()
@@ -192,44 +178,44 @@ def gltf_model_from_colored_triangle_data(
bufferView.byteLength = byte_length
byte_offset += byte_length
bufferView.target = pygltflib.ARRAY_BUFFER
- buffer_view_INDICES[data.rgba] = len(gltf.bufferViews)
+ buffer_view_INDICES[(data.rgba, 'edge')] = len(gltf.bufferViews)
gltf.bufferViews.append(bufferView)
accessor_INDICES = {}
for data in colored_triangle_data:
accessor = pygltflib.Accessor()
- accessor.bufferView = buffer_view_INDICES[data.rgba]
+ accessor.bufferView = buffer_view_INDICES[(data.rgba, 'tri')]
accessor.byteOffset = 0
accessor.componentType = pygltflib.FLOAT
accessor.count = data.triangle_list.shape[0]
accessor.type = pygltflib.VEC3
accessor.max = [float(e) for e in np.max(data.triangle_list, axis=0)]
accessor.min = [float(e) for e in np.min(data.triangle_list, axis=0)]
- accessor_INDICES[data.rgba] = len(gltf.accessors)
+ accessor_INDICES[(data.rgba, 'tri')] = len(gltf.accessors)
gltf.accessors.append(accessor)
for data in colored_line_data:
accessor = pygltflib.Accessor()
- accessor.bufferView = buffer_view_INDICES[data.rgba]
+ accessor.bufferView = buffer_view_INDICES[(data.rgba, 'edge')]
accessor.byteOffset = 0
accessor.componentType = pygltflib.FLOAT
accessor.count = data.edge_list.shape[0]
accessor.type = pygltflib.VEC3
accessor.max = [float(e) for e in np.max(data.edge_list, axis=0)]
accessor.min = [float(e) for e in np.min(data.edge_list, axis=0)]
- accessor_INDICES[data.rgba] = len(gltf.accessors)
+ accessor_INDICES[(data.rgba, 'edge')] = len(gltf.accessors)
gltf.accessors.append(accessor)
mesh0 = pygltflib.Mesh()
for data in colored_triangle_data:
primitive = pygltflib.Primitive()
- primitive.material = material_INDICES[data.rgba]
- primitive.attributes.POSITION = accessor_INDICES[data.rgba]
+ primitive.material = material_INDICES[(data.rgba, 'tri')]
+ primitive.attributes.POSITION = accessor_INDICES[(data.rgba, 'tri')]
primitive.mode = pygltflib.TRIANGLES
mesh0.primitives.append(primitive)
for data in colored_line_data:
primitive = pygltflib.Primitive()
- primitive.material = material_INDICES[data.rgba]
- primitive.attributes.POSITION = accessor_INDICES[data.rgba]
+ primitive.material = material_INDICES[(data.rgba, 'edge')]
+ primitive.attributes.POSITION = accessor_INDICES[(data.rgba, 'edge')]
primitive.mode = pygltflib.LINES
mesh0.primitives.append(primitive)
mesh0_INDEX = len(gltf.meshes)
@@ -266,7 +252,7 @@ def viz_3d_gltf_model_html(model: pygltflib.GLTF2) -> str:
+ model_data_uri
+ r"""">Download 3D Model as .GLTF File
Mouse Wheel = Zoom. Left Drag = Orbit. Right Drag = Strafe.
-
+
@@ -274,8 +260,11 @@ def viz_3d_gltf_model_html(model: pygltflib.GLTF2) -> str:
/// BEGIN TERRIBLE HACK.
/// Get the object by ID then change the ID.
/// This is a workaround for https://github.com/jupyter/notebook/issues/6598
+ let outerContainer = document.getElementById("stim-outer-container");
let container = document.getElementById("stim-3d-viewer-scene-container");
container.id = "stim-3d-viewer-scene-container-USED";
+ outerContainer.id = "stim-outer-container-USED";
+ outerContainer.style.height = `${window.innerHeight-100}px`;
let downloadLink = document.getElementById("stim-3d-viewer-download-link");
downloadLink.id = "stim-3d-viewer-download-link-USED";
/// END TERRIBLE HACK.
@@ -287,7 +276,7 @@ def viz_3d_gltf_model_html(model: pygltflib.GLTF2) -> str:
///
/// What this SHOULD be is:
///
- /// import {Box3, Scene, Color, PerspectiveCamera, WebGLRenderer, DirectionalLight} from "three";
+ /// import {Box3, Scene, Color, OrthographicCamera, PerspectiveCamera, WebGLRenderer, DirectionalLight} from "three";
/// import {OrbitControls} from "three-orbitcontrols";
/// import {GLTFLoader} from "three-gltf-loader";
///
@@ -337,14 +326,20 @@ def viz_3d_gltf_model_html(model: pygltflib.GLTF2) -> str:
scene.add(gltf.scene);
// Point the camera at the center, far enough back to see everything.
- let camera = new PerspectiveCamera(35, container.clientWidth / container.clientHeight, 0.1, 100000);
- let controls = new OrbitControls(camera, container);
let bounds = new Box3().setFromObject(scene);
+ let w = container.clientWidth;
+ let h = container.clientHeight;
+ let camera = new OrthographicCamera(-w/2, w/2, h/2, -h/2, 0.1, 100000);
+ let controls = new OrbitControls(camera, container);
let mid = new Vector3(
(bounds.min.x + bounds.max.x) * 0.5,
(bounds.min.y + bounds.max.y) * 0.5,
(bounds.min.z + bounds.max.z) * 0.5,
);
+ let max_dx = bounds.max.x - bounds.min.x;
+ let max_dy = bounds.max.y - bounds.min.y;
+ let max_dz = bounds.max.z - bounds.min.z;
+ let max_d = Math.sqrt(max_dx*max_dx + max_dy*max_dy + max_dz*max_dz);
let boxPoints = [];
for (let dx of [0, 0.5, 1]) {
for (let dy of [0, 0.5, 1]) {
@@ -360,14 +355,16 @@ def viz_3d_gltf_model_html(model: pygltflib.GLTF2) -> str:
let isInView = p => {
p = new Vector3(p.x, p.y, p.z);
p.project(camera);
- return Math.abs(p.x) < 1 && Math.abs(p.y) < 1 && p.z >= 0 && p.z < 1;
+ return Math.abs(p.x) < 1 && Math.abs(p.y) < 1;
};
let unit = new Vector3(0.3, 0.4, -1.8);
unit.normalize();
let setCameraDistance = d => {
controls.target.copy(mid);
camera.position.copy(mid);
- camera.position.addScaledVector(unit, d);
+ camera.position.addScaledVector(unit, max_d);
+ camera.zoom = 1/d;
+ camera.updateProjectionMatrix();
controls.update();
return boxPoints.every(isInView);
};
@@ -407,8 +404,14 @@ def viz_3d_gltf_model_html(model: pygltflib.GLTF2) -> str:
// Render whenever any important changes have occurred.
requestAnimationFrame(() => renderer.render(scene, camera));
new ResizeObserver(() => {
- camera.aspect = container.clientWidth / container.clientHeight;
+ let w = container.clientWidth;
+ let h = container.clientHeight;
+ camera.left = -w/2;
+ camera.right = w/2;
+ camera.top = h/2;
+ camera.bottom = -h/2;
camera.updateProjectionMatrix();
+ controls.update();
renderer.setSize(container.clientWidth, container.clientHeight);
renderer.render(scene, camera);
}).observe(container);
diff --git a/src/gen/_surf/_viz_gltf_3d_test.py b/src/gen/_viz_gltf_3d_test.py
similarity index 86%
rename from src/gen/_surf/_viz_gltf_3d_test.py
rename to src/gen/_viz_gltf_3d_test.py
index 1bd7092..944cbf0 100644
--- a/src/gen/_surf/_viz_gltf_3d_test.py
+++ b/src/gen/_viz_gltf_3d_test.py
@@ -1,22 +1,8 @@
-# Copyright 2023 Google LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
import json
import numpy as np
-from gen._surf._viz_gltf_3d import (
+from gen._viz_gltf_3d import (
ColoredTriangleData,
gltf_model_from_colored_triangle_data,
viz_3d_gltf_model_html,
@@ -77,6 +63,7 @@ def test_gltf_model_from_colored_triangle_data():
{"buffer": 0, "byteOffset": 0, "byteLength": 36, "target": 34962},
{"buffer": 0, "byteOffset": 36, "byteLength": 36, "target": 34962},
],
+ 'asset': {'version': '2.0'},
"buffers": [
{
"uri": "data:application/octet-stream;base64,AACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AACAPwAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/",
diff --git a/src/gen/_viz_patch_svg.py b/src/gen/_viz_patch_svg.py
index 7cb23bd..7c58e12 100644
--- a/src/gen/_viz_patch_svg.py
+++ b/src/gen/_viz_patch_svg.py
@@ -1,28 +1,15 @@
-# Copyright 2023 Google LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
+import collections
import math
-from typing import Iterable, Union, Literal, TYPE_CHECKING, Sequence, Callable
+from typing import Iterable, Union, TYPE_CHECKING, Sequence, Callable, \
+ Any
-from gen._core._patch import Patch, Tile
from gen._core._util import min_max_complex
if TYPE_CHECKING:
import gen
-def is_colinear(a: complex, b: complex, c: complex) -> bool:
+def is_collinear(a: complex, b: complex, c: complex) -> bool:
d1 = b - a
d2 = c - a
return abs(d1.real * d2.imag - d2.real * d1.imag) < 1e-4
@@ -110,7 +97,7 @@ def _path_commands_for_points_with_many_points(
prev_q = pts[k - 1]
q = pts[k]
next_q = pts[(k + 1) % len(pts)]
- if (is_colinear(prev_q, q, next_q) or is_colinear(prev_prev_q, prev_q, q)):
+ if is_collinear(prev_q, q, next_q) or is_collinear(prev_prev_q, prev_q, q):
prev_pt = draw_coord(prev_q)
cur_pt = draw_coord(q)
d = cur_pt - prev_pt
@@ -126,7 +113,7 @@ def _path_commands_for_points_with_many_points(
def svg_path_directions_for_tile(
- *, tile: "gen.Tile", draw_coord: Callable[[complex], complex]
+ *, tile: "gen.Tile", draw_coord: Callable[[complex], complex], contract_towards: complex | None = None,
) -> str | None:
hint_point = tile.measurement_qubit
if any(abs(q - hint_point) < 1e-4 for q in tile.data_set):
@@ -158,6 +145,10 @@ def svg_path_directions_for_tile(
)
)
+ if contract_towards is not None:
+ c = 0.85
+ points = [p*c + (1-c)*contract_towards for p in points]
+
return " ".join(
_path_commands_for_points_with_many_points(
pts=points,
@@ -166,145 +157,154 @@ def svg_path_directions_for_tile(
)
-BASE_COLORS = {"X": "#FF8080", "Z": "#8080FF", "Y": "#80FF80", None: "gray"}
-
-
-def _data_span_sort(tile: "gen.Tile") -> float:
- min_c, max_c = min_max_complex(tile.data_set, default=0)
- return max_c.real - min_c.real + max_c.imag - min_c.imag
-
-
-def _patch_svg_viewer_helper_single_patch_wraparound_clip(
- *,
- patch: Patch,
- transform_pt: Callable[[complex], complex],
- out_lines: list[str],
- show_order: str,
- opacity: float,
- show_data_qubits: bool,
- show_measure_qubits: bool,
- available_qubits: frozenset[complex],
- extra_used_coords: frozenset[complex],
- clip_path_id_ptr: list[int],
-):
- if len(patch.without_wraparound_tiles().tiles) == len(patch.tiles):
- _patch_svg_viewer_helper_single_patch(
- patch=patch,
- transform_pt=transform_pt,
- out_lines=out_lines,
- show_order=show_order,
- opacity=opacity,
- show_data_qubits=show_data_qubits,
- show_measure_qubits=show_measure_qubits,
- clip_path_id_ptr=clip_path_id_ptr,
- available_qubits=available_qubits,
- extra_used_coords=extra_used_coords,
- )
- return
-
- p_min, p_max = min_max_complex(patch.data_set, default=0)
- w = p_max.real - p_min.real
- h = p_max.imag - p_min.imag
- left = p_min.real + w * 0.1
- right = p_min.real + w * 0.9
- top = p_min.imag + h * 0.1
- bot = p_min.imag + h * 0.9
- pad_w = 1
- pad_h = 1
- pad_shift = -pad_w - 1j*pad_h
- pad_shift /= 2
- w += pad_w
- h += pad_h
- def new_transform(q: complex) -> complex:
- q -= p_min
- q /= (p_max - p_min).real
- q *= w
- q += p_min
- return transform_pt(q)
-
- def is_normal_tile(tile: Tile) -> bool:
- t_min, t_max = min_max_complex(tile.data_set, default=0)
- if t_min.real < left and t_max.real > right:
- return False
- if t_min.imag < top and t_max.imag > bot:
- return False
- return True
-
- def unwraparound(tile: Tile):
- t_min, t_max = min_max_complex(tile.data_set, default=0)
- dw = w
- dh = h
- if not (t_min.real < left and t_max.real > right):
- dw = 0
- if not (t_min.imag < top and t_max.imag > bot):
- dh = 0
- def trans(q: complex) -> complex:
- if q.real < w / 2:
- q += dw
- if q.imag < h / 2:
- q += dh*1j
- return q
- return trans
-
- new_tiles = []
- for tile in patch.tiles:
- if is_normal_tile(tile):
- new_tiles.append(tile)
- continue
- new_tile_1 = tile.with_transformed_coords(unwraparound(tile))
- new_tile_2 = new_tile_1.with_transformed_coords(lambda q: q - w)
- new_tile_3 = new_tile_1.with_transformed_coords(lambda q: q - h*1j)
- new_tile_4 = new_tile_1.with_transformed_coords(lambda q: q - w - h*1j)
- new_tiles.append(new_tile_1)
- new_tiles.append(new_tile_2)
- new_tiles.append(new_tile_3)
- new_tiles.append(new_tile_4)
-
- clip_ip = clip_path_id_ptr[0]
- clip_path_id_ptr[0] += 1
- out_lines.append(f'''
''')
- tl = transform_pt(p_min)
- br = transform_pt(p_max)
- out_lines.append(f''' ''')
- out_lines.append(f'''''')
- _patch_svg_viewer_helper_single_patch(
- patch=Patch(new_tiles).with_transformed_coords(lambda q: q + pad_shift),
- transform_pt=new_transform,
- out_lines=out_lines,
- show_order=show_order,
- opacity=opacity,
- show_data_qubits=show_data_qubits,
- show_measure_qubits=show_measure_qubits,
- clip_path_id_ptr=clip_path_id_ptr,
- available_qubits=frozenset([q + pad_shift for q in available_qubits]),
- extra_used_coords=frozenset([q + pad_shift for q in extra_used_coords]),
- extra_clip_path_arg=f'clip-path="url(#clipPath{clip_ip})" ',
- )
-
-
-def _patch_svg_viewer_helper_single_patch(
- *,
- patch: Patch,
- transform_pt: Callable[[complex], complex],
- out_lines: list[str],
- show_order: str,
- opacity: float,
- show_data_qubits: bool,
- show_measure_qubits: bool,
- clip_path_id_ptr: list[int],
- available_qubits: frozenset[complex],
- extra_used_coords: frozenset[complex],
- extra_clip_path_arg: str = '',
+def _draw_patch(
+ *,
+ patch: Union['gen.Patch', 'gen.StabilizerCode', 'gen.ChunkInterface'],
+ q2p: Callable[[complex], complex],
+ show_coords: bool,
+ show_obs: bool,
+ opacity: float,
+ show_data_qubits: bool,
+ show_measure_qubits: bool,
+ expected_points: frozenset[complex],
+ clip_path_id_ptr: list[int],
+ out_lines: list[str],
+ show_order: bool,
+ find_logical_err_max_weight: int | None,
+ tile_color_func: Callable[['gen.Tile'], str] | None = None
):
layer_1q2 = []
layer_1q = []
fill_layer2q = []
- stroke_layer2q = []
fill_layer_mq = []
stroke_layer_mq = []
- stroke_width = abs(transform_pt(0.02) - transform_pt(0))
+ scale_factor = abs(q2p(1) - q2p(0))
+
+ from gen._core._stabilizer_code import StabilizerCode
+ from gen._flows._chunk_interface import ChunkInterface
+ if isinstance(patch, ChunkInterface):
+ patch = patch.to_code()
+
+ labels: list[tuple[complex, str, dict[str, Any]]] = []
+ if isinstance(patch, StabilizerCode):
+ if find_logical_err_max_weight is not None:
+ err = patch.find_logical_error(max_search_weight=find_logical_err_max_weight)
+ for e in err:
+ for loc in e.circuit_error_locations:
+ for loc2 in loc.flipped_pauli_product:
+ r, i = loc2.coords
+ q = r + 1j*i
+ p = loc2.gate_target.pauli_type
+ labels.append((q, p + '!', {
+ 'text-anchor': 'middle',
+ 'dominant-baseline': 'central',
+ 'font-size': scale_factor * 1.1,
+ 'fill': BASE_COLORS_DARK[p],
+ }))
+
+ if show_obs:
+ k = 0
+ while True:
+ if k < 10:
+ suffix = "₀₁₂₃₄₅₆₇₈₉"[k]
+ else:
+ suffix = str(k)
+ work = False
+ if k < len(patch.observables_x):
+ for q, basis2 in patch.observables_x[k].qubits.items():
+ if not patch.observables_z:
+ label = basis2 + suffix
+ elif basis2 != 'X':
+ label = 'X' + suffix + '[' + basis2 + ']'
+ else:
+ label = 'X' + suffix
+ labels.append((q, label, {
+ 'text-anchor': 'end',
+ 'dominant-baseline': 'hanging',
+ 'font-size': scale_factor * 0.6,
+ 'fill': BASE_COLORS_DARK[basis2],
+ }))
+ work = True
+ if k < len(patch.observables_z):
+ for q, basis2 in patch.observables_z[k].qubits.items():
+ if basis2 != 'Z':
+ basis_suffix = '[' + basis2 + ']'
+ else:
+ basis_suffix = ''
+ labels.append((q, 'Z' + suffix + basis_suffix, {
+ 'text-anchor': "start",
+ 'dominant-baseline': 'bottom',
+ 'font-size': scale_factor * 0.6,
+ 'fill': BASE_COLORS_DARK[basis2],
+ }))
+ work = True
+ if not work:
+ break
+ k += 1
+ for q, s, ts in labels:
+ loc2 = q2p(q)
+ terms = {
+ 'x': loc2.real,
+ 'y': loc2.imag,
+ **ts,
+ }
+ layer_1q2.append(
+ "
{s}"
+ )
+
+ all_points = patch.used_set | expected_points
+ if show_coords and all_points:
+ all_x = sorted({q.real for q in all_points})
+ all_y = sorted({q.imag for q in all_points})
+ left = min(all_x) - 1
+ top = min(all_y) - 1
+
+ for x in all_x:
+ if x == int(x):
+ x = int(x)
+ loc2 = q2p(x + top*1j)
+ stroke_layer_mq.append(
+ "
{x}"
+ )
+ for y in all_y:
+ if y == int(y):
+ y = int(y)
+ loc2 = q2p(y*1j + left)
+ stroke_layer_mq.append(
+ "
{y}i"
+ )
+
+ sorted_tiles = sorted(patch.tiles, key=tile_data_span, reverse=True)
+ d2tiles = collections.defaultdict(list)
+
+ def contraction_point(tile) -> complex | None:
+ if len(tile.data_set) <= 2:
+ return None
+ for d in tile.data_set:
+ for other_tile in d2tiles[d]:
+ if other_tile is not tile:
+ if tile.data_set < other_tile.data_set or (tile.data_set == other_tile.data_set and tile.basis < other_tile.basis):
+ return sum(other_tile.data_set) / len(other_tile.data_set)
+ return None
+
+ for tile in sorted_tiles:
+ for d in tile.data_set:
+ d2tiles[d].append(tile)
- sorted_tiles = sorted(patch.tiles, key=_data_span_sort, reverse=True)
for tile in sorted_tiles:
c = tile.measurement_qubit
if any(abs(q - c) < 1e-4 for q in tile.data_set):
@@ -315,359 +315,276 @@ def _patch_svg_viewer_helper_single_patch(
)
if not dq:
continue
- common_basis = tile.basis
- fill_color = BASE_COLORS[common_basis]
-
+ fill_color = BASE_COLORS[tile.basis]
+ if tile_color_func is not None:
+ fill_color = tile_color_func(tile)
+
+ if len(tile.data_set) == 1:
+ fl = layer_1q
+ sl = stroke_layer_mq
+ elif len(tile.data_set) == 2:
+ fl = fill_layer2q
+ sl = stroke_layer_mq
+ else:
+ fl = fill_layer_mq
+ sl = stroke_layer_mq
+ cp = contraction_point(tile)
path_directions = svg_path_directions_for_tile(
tile=tile,
- draw_coord=transform_pt,
+ draw_coord=q2p,
+ contract_towards=cp,
)
- path_cmd_start = None
if path_directions is not None:
- if len(tile.data_set) == 1:
- fl = layer_1q
- sl = layer_1q2
- elif len(tile.data_set) == 2:
- fl = fill_layer2q
- sl = stroke_layer2q
- else:
- fl = fill_layer_mq
- sl = stroke_layer_mq
fl.append(
f'''
"""
-
+ f''' />'''
)
-
- # # Draw lines from data qubits to measurement qubit.
- # for d in tile.data_set:
- # pd = transform_pt(d)
- # pc = transform_pt(c)
- # sl.append(
- # f'''
"""
- # )
-
- if show_order != "undirected":
+ if cp is None:
sl.append(
f'''
"""
)
- else:
- cur_pt = transform_pt(dq[-1])
- path_cmd_start = f'
'
- )
- if show_order != "undirected":
- stroke_layer_mq.append(
- f"{path_cmd_start} "
- f'stroke-width="{stroke_width}" '
- f'stroke="black" '
- f""" {extra_clip_path_arg} """
- f'fill="none" />'
+
+ # Add basis glows around data qubits in multi-basis stabilizers.
+ if path_directions is not None and tile.basis is None and tile_color_func is None:
+ clip_path_id_ptr[0] += 1
+ fl.append(f'
')
+ fl.append(f''' ''')
+ fl.append(f"")
+ for k, q in enumerate(tile.ordered_data_qubits):
+ if q is None:
+ continue
+ v = q2p(q)
+ fl.append(
+ f"
'
)
if show_measure_qubits:
m = tile.measurement_qubit
- if show_order == "undirected":
- m = m * 0.8 + c * 0.2
- p = transform_pt(m)
+ loc2 = q2p(m)
layer_1q2.append(
f"
"""
+ f'stroke-width="{scale_factor * 0.02}" '
+ f"""stroke="black" />"""
)
+
if show_data_qubits:
for d in tile.data_set:
- p = transform_pt(d)
+ loc2 = q2p(d)
layer_1q2.append(
f"
"""
+ f"""stroke="none" />"""
)
- if common_basis is None and path_cmd_start is not None:
- clip_path_id_ptr[0] += 1
- fill_layer_mq.append(f'
')
- fill_layer_mq.append(f" {path_cmd_start} />")
- fill_layer_mq.append(f"")
- for k, q in enumerate(tile.ordered_data_qubits):
- if q is None:
- continue
- v = transform_pt(q)
- fill_layer_mq.append(
+ for q in all_points:
+ if q not in patch.data_set and q not in patch.measure_set:
+ loc2 = q2p(q)
+ layer_1q2.append(
f"
'
+ f'cx="{loc2.real}" '
+ f'cy="{loc2.imag}" '
+ f'r="{scale_factor * 0.1}" '
+ f'fill="black" '
+ f"""stroke="none" />"""
)
- out_lines.extend(fill_layer_mq)
- out_lines.extend(stroke_layer_mq)
- out_lines.extend(fill_layer2q)
- out_lines.extend(stroke_layer2q)
- out_lines.extend(layer_1q)
- out_lines.extend(layer_1q2)
-
- if available_qubits | extra_used_coords:
- for q in available_qubits | (patch.used_set | extra_used_coords):
- fill_color = "black" if q in available_qubits else "orange"
- if (
- not show_measure_qubits
- and not q in available_qubits
- and q in patch.measure_set
- ):
- continue
- q2 = transform_pt(q)
- out_lines.append(
- f"
"
+
+ out_lines += fill_layer_mq
+ out_lines += stroke_layer_mq
+ out_lines += fill_layer2q
+ out_lines += layer_1q
+ out_lines += layer_1q2
+
+ # Draw each element's measurement order as a zig zag arrow.
+ if show_order:
+ for tile in patch.tiles:
+ _draw_tile_order_arrow(
+ q2p=q2p,
+ tile=tile,
+ out_lines=out_lines,
)
+BASE_COLORS = {"X": "#FF8080", "Z": "#8080FF", "Y": "#80FF80", None: "gray"}
+BASE_COLORS_DARK = {"X": "#B01010", "Z": "#1010B0", "Y": "#10B010", None: "black"}
+
+
+def tile_data_span(tile: "gen.Tile") -> Any:
+ min_c, max_c = min_max_complex(tile.data_set, default=0)
+ return max_c.real - min_c.real + max_c.imag - min_c.imag, tile.bases
+
+
+def _draw_tile_order_arrow(
+ *,
+ tile: 'gen.Tile',
+ q2p: Callable[[complex], complex],
+ out_lines: list[str],
+):
+ scale_factor = abs(q2p(1) - q2p(0))
+
+ c = tile.measurement_qubit
+ if len(tile.data_set) == 3 or c in tile.data_set:
+ c = 0
+ for q in tile.data_set:
+ c += q
+ c /= len(tile.data_set)
+ pts: list[complex] = []
+
+ path_cmd_start = f'
'
+ )
+ delay = 0
+ prev = v
+ else:
+ delay += 1
+ path_cmd_start = path_cmd_start.strip()
+ path_cmd_start += (
+ f'" fill="none" '
+ f'stroke-width="{scale_factor * 0.02}" '
+ f'stroke="{arrow_color}" />'
+ )
+ out_lines.append(path_cmd_start)
+
+ # Draw arrow at end of arrow.
+ if len(pts) > 1:
+ p = pts[-1]
+ d2 = p - pts[-2]
+ if d2:
+ d2 /= abs(d2)
+ d2 *= 4 * scale_factor * 0.02
+ a = p + d2
+ b = p + d2 * 1j
+ c = p + d2 * -1j
+ out_lines.append(
+ f"
'
+ )
+
+
def patch_svg_viewer(
- patches: Iterable[Patch],
+ patches: Iterable[Union['gen.Patch', 'gen.StabilizerCode', 'gen.ChunkInterface']],
*,
canvas_height: int = 500,
- show_order: Union[bool, Literal["undirected", "3couplerspecial"]] = True,
+ show_order: bool = True,
+ show_obs: bool = True,
opacity: float = 1,
show_measure_qubits: bool = True,
show_data_qubits: bool = False,
- available_qubits: Iterable[complex] = (),
+ expected_points: Iterable[complex] = (),
extra_used_coords: Iterable[complex] = (),
- wraparound_clip: bool = False,
+ show_coords: bool = True,
+ find_logical_err_max_weight: int | None = None,
+ rows: int | None = None,
+ cols: int | None = None,
+ tile_color_func: Callable[['gen.Tile'], str] | None = None
) -> str:
"""Returns a picture of the stabilizers measured by various plan."""
+ expected_points = frozenset(expected_points)
- available_qubits = frozenset(available_qubits)
extra_used_coords = frozenset(extra_used_coords)
patches = tuple(patches)
- min_c, max_c = min_max_complex(
- [
- q
- for plan in patches
- for q in plan.used_set | available_qubits | extra_used_coords
- ],
- default=0,
- )
+ all_points = {
+ q
+ for patch in patches
+ for q in patch.used_set | expected_points | extra_used_coords
+ }
+ min_c, max_c = min_max_complex(all_points, default=0)
min_c -= 1 + 1j
max_c += 1 + 1j
+ if show_coords:
+ min_c -= 1 + 1j
box_width = max_c.real - min_c.real
box_height = max_c.imag - min_c.imag
pad = max(box_width, box_height) * 0.1 + 1
- box_width += pad
- box_height += pad
- columns = math.ceil(math.sqrt(len(patches) + 2))
- rows = math.ceil(len(patches) / max(1, columns))
- total_height = max(1.0, box_height * rows - pad)
- total_width = max(1.0, box_width * columns + 1)
- scale_factor = canvas_height / max(total_height + 1, 1)
+ box_x_pitch = box_width + pad
+ box_y_pitch = box_height + pad
+ if cols is None and rows is None:
+ cols = math.ceil(math.sqrt(len(patches)))
+ rows = math.ceil(len(patches) / max(1, cols))
+ elif cols is None:
+ cols = math.ceil(len(patches) / max(1, rows))
+ elif rows is None:
+ rows = math.ceil(len(patches) / max(1, cols))
+ else:
+ assert cols * rows >= len(patches)
+ total_height = max(1.0, box_y_pitch * rows - pad)
+ total_width = max(1.0, box_x_pitch * cols - pad)
+ scale_factor = canvas_height / max(total_height, 1)
canvas_width = int(math.ceil(canvas_height * (total_width / total_height)))
- def transform_pt(plan_i2: int, pt2: complex) -> complex:
- pt2 += box_width * (plan_i2 % columns)
- pt2 += box_height * (plan_i2 // columns) * 1j
- pt2 += pad * (0.5 + 0.5j)
- pt2 += pad
- pt2 -= min_c + 1 + 1j
- pt2 *= scale_factor
- return pt2
-
- def transform_dif(dif: complex) -> complex:
- return dif * scale_factor
-
- def pt(plan_i2: int, q2: complex) -> str:
- return f"{transform_pt(plan_i2, q2).real},{transform_pt(plan_i2, q2).imag}"
+ def patch_q2p(patch_index: int, q: complex) -> complex:
+ q -= min_c
+ q += box_x_pitch * (patch_index % cols)
+ q += box_y_pitch * (patch_index // cols) * 1j
+ q *= scale_factor
+ return q
lines = [
f"""