Skip to content

Commit 414a8c7

Browse files
authored
Add coordinate filtering to detector slice diagrams (#386)
* Add coordinate filtering to detector slice diagrams - Add `--filter_coords` argument to `stim diagram` - Add `filter_coords` argument to `stim.Circuit.diagram` - Add anchors to getting started notebook - Broke down and wrote a split method - Broke down and wrote a parse_exact_double_from_string method - Added a stim diagram documentation example of making an animated gif
1 parent 0335d60 commit 414a8c7

34 files changed

+1711
-219
lines changed

doc/getting_started.ipynb

Lines changed: 44 additions & 23 deletions
Large diffs are not rendered by default.

doc/python_api_reference_vDev.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1251,6 +1251,7 @@ def diagram(
12511251
*,
12521252
type: 'Literal["detector-slice-text"]',
12531253
tick: int,
1254+
filter_coords: Optional[Iterable[Iterable[float]]] = None,
12541255
) -> 'stim._DiagramHelper':
12551256
pass
12561257
@overload
@@ -1259,13 +1260,15 @@ def diagram(
12591260
*,
12601261
type: 'Literal["detector-slice-svg"]',
12611262
tick: int,
1263+
filter_coords: Optional[Iterable[Iterable[float]]] = None,
12621264
) -> 'stim._DiagramHelper':
12631265
pass
12641266
def diagram(
12651267
self,
12661268
*,
12671269
type: str,
12681270
tick: Optional[int] = None,
1271+
filter_coords: Optional[Iterable[Iterable[float]]] = None,
12691272
) -> 'stim._DiagramHelper':
12701273
"""Returns a diagram of the circuit, from a variety of options.
12711274
@@ -1309,6 +1312,9 @@ def diagram(
13091312
instruction to slice at. Note that the first TICK in the
13101313
circuit is tick=1. The value tick=0 refers to the very start
13111314
of the circuit.
1315+
filter_coords: A set of acceptable coordinate prefixes. For
1316+
detector slice diagrams, only detectors whose coordinates
1317+
begin with one of these filters will be included.
13121318
13131319
Returns:
13141320
An object whose `__str__` method returns the diagram, so that

doc/stim.pyi

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -769,6 +769,7 @@ class Circuit:
769769
*,
770770
type: 'Literal["detector-slice-text"]',
771771
tick: int,
772+
filter_coords: Optional[Iterable[Iterable[float]]] = None,
772773
) -> 'stim._DiagramHelper':
773774
pass
774775
@overload
@@ -777,13 +778,15 @@ class Circuit:
777778
*,
778779
type: 'Literal["detector-slice-svg"]',
779780
tick: int,
781+
filter_coords: Optional[Iterable[Iterable[float]]] = None,
780782
) -> 'stim._DiagramHelper':
781783
pass
782784
def diagram(
783785
self,
784786
*,
785787
type: str,
786788
tick: Optional[int] = None,
789+
filter_coords: Optional[Iterable[Iterable[float]]] = None,
787790
) -> 'stim._DiagramHelper':
788791
"""Returns a diagram of the circuit, from a variety of options.
789792
@@ -827,6 +830,9 @@ class Circuit:
827830
instruction to slice at. Note that the first TICK in the
828831
circuit is tick=1. The value tick=0 refers to the very start
829832
of the circuit.
833+
filter_coords: A set of acceptable coordinate prefixes. For
834+
detector slice diagrams, only detectors whose coordinates
835+
begin with one of these filters will be included.
830836
831837
Returns:
832838
An object whose `__str__` method returns the diagram, so that

doc/usage_command_line.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,7 @@ NAME
557557
558558
SYNOPSIS
559559
stim diagram \
560+
[--filter_coords float.seperatedby(',').seperatedby(':')] \
560561
[--in filepath] \
561562
[--out filepath] \
562563
[--remove_noise] \
@@ -566,7 +567,25 @@ SYNOPSIS
566567
DESCRIPTION
567568
Produces various kinds of diagrams.
568569
570+
569571
OPTIONS
572+
--filter_coords
573+
Specifies coordinate filters that determine what appears in the diagram.
574+
575+
A coordinate is a double precision floating point number.
576+
A point is a tuple of coordinates.
577+
The coordinates of a point are separate by commas (',').
578+
A filter is a set of points.
579+
Points are separated by colons (':').
580+
581+
For example, in a detector slice diagram, specifying
582+
"--filter-coords 2,3:4,5,6" means that only detectors whose
583+
first two coordinates are (2,3), or whose first three coordinate
584+
are (4,5,6), should be included in the diagram. Note that the
585+
filters are always prefix matches, so a detector with coordinates
586+
(2,3,4) matches the filter 2,3.
587+
588+
570589
--in
571590
Where to read the object to diagram from.
572591
@@ -690,6 +709,44 @@ EXAMPLES
690709
q0: -H-@-
691710
|
692711
q1: ---X-
712+
713+
714+
Example #2
715+
>>> # Making a video of detector slices moving around
716+
717+
>>> # First, make a circuit to animate.
718+
>>> stim gen \
719+
--code surface_code \
720+
--task rotated_memory_x \
721+
--distance 5 \
722+
--rounds 100 \
723+
> surface_code.stim
724+
725+
>>> # Second, use gnu-parallel and stim diagram to make video frames.
726+
>>> parallel stim diagram \
727+
--filter_coords 2,2:4,2 \
728+
--type detector-slice-svg \
729+
--tick {} \
730+
--in surface_code.stim \
731+
--out video_frame_{}.svg \
732+
::: {50..150}
733+
734+
>>> # Third, use ffmpeg to turn the frames into a GIF.
735+
>>> # (note: the complex filter argument is optional; it turns the background white)
736+
>>> ffmpeg output_animation.gif \
737+
-framerate 5 \
738+
-pattern_type glob -i 'video_frame_*.svg' \
739+
-pix_fmt rgb8 \
740+
-filter_complex "[0]split=2[bg][fg];[bg]drawbox=c=white@1:t=fill[bg];[bg][fg]overlay=format=auto"
741+
742+
>>> # Alternatively, make an MP4 video instead of a GIF.
743+
>>> ffmpeg output_video.mp4 \
744+
-framerate 5 \
745+
-pattern_type glob -i 'video_frame_*.svg' \
746+
-vf scale=1024:-1 \
747+
-c:v libx264 \
748+
-vf format=yuv420p \
749+
-vf "pad=ceil(iw/2)*2:ceil(ih/2)*2"
693750
```
694751

695752
<a name="explain_errors"></a>

glue/python/src/stim/__init__.pyi

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -769,6 +769,7 @@ class Circuit:
769769
*,
770770
type: 'Literal["detector-slice-text"]',
771771
tick: int,
772+
filter_coords: Optional[Iterable[Iterable[float]]] = None,
772773
) -> 'stim._DiagramHelper':
773774
pass
774775
@overload
@@ -777,13 +778,15 @@ class Circuit:
777778
*,
778779
type: 'Literal["detector-slice-svg"]',
779780
tick: int,
781+
filter_coords: Optional[Iterable[Iterable[float]]] = None,
780782
) -> 'stim._DiagramHelper':
781783
pass
782784
def diagram(
783785
self,
784786
*,
785787
type: str,
786788
tick: Optional[int] = None,
789+
filter_coords: Optional[Iterable[Iterable[float]]] = None,
787790
) -> 'stim._DiagramHelper':
788791
"""Returns a diagram of the circuit, from a variety of options.
789792
@@ -827,6 +830,9 @@ class Circuit:
827830
instruction to slice at. Note that the first TICK in the
828831
circuit is tick=1. The value tick=0 refers to the very start
829832
of the circuit.
833+
filter_coords: A set of acceptable coordinate prefixes. For
834+
detector slice diagrams, only detectors whose coordinates
835+
begin with one of these filters will be included.
830836
831837
Returns:
832838
An object whose `__str__` method returns the diagram, so that

glue/sample/src/sinter/_decoding.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
1-
import os
21

32
import contextlib
43
import functools
54
import pathlib
6-
import subprocess
75
import tempfile
86

97
import math
108
import time
11-
from typing import Optional, Dict, Callable, Tuple, Iterable
9+
from typing import Optional, Dict, Callable, Tuple
1210
from typing import Union
1311

1412
import numpy as np

src/stim/arg_parse.cc

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include <algorithm>
1818
#include <array>
19+
#include <cmath>
1920
#include <cstdio>
2021
#include <cstdlib>
2122
#include <cstring>
@@ -389,3 +390,26 @@ ostream_else_cout stim::find_output_stream_argument(
389390
}
390391
return {std::move(f)};
391392
}
393+
394+
std::vector<std::string> stim::split(char splitter, const std::string &text) {
395+
std::vector<std::string> result;
396+
size_t start = 0;
397+
for (size_t k = 0; k < text.size(); k++) {
398+
if (text[k] == splitter) {
399+
result.push_back(text.substr(start, k - start));
400+
start = k + 1;
401+
}
402+
}
403+
result.push_back(text.substr(start, text.size() - start));
404+
return result;
405+
}
406+
407+
double stim::parse_exact_double_from_string(const std::string &text) {
408+
char *end = nullptr;
409+
const char *c = text.c_str();
410+
double d = strtod(c, &end);
411+
if (isspace(*c) || end == c || end != c + text.size() || std::isinf(d) || std::isnan(d)) {
412+
throw std::invalid_argument("Not an exact double: '" + text + "'");
413+
}
414+
return d;
415+
}

src/stim/arg_parse.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,10 @@ struct ostream_else_cout {
260260
/// Command line argument isn't present and default_std_out is false.
261261
ostream_else_cout find_output_stream_argument(const char *name, bool default_std_out, int argc, const char **argv);
262262

263+
std::vector<std::string> split(char splitter, const std::string &text);
264+
265+
double parse_exact_double_from_string(const std::string &text);
266+
263267
} // namespace stim
264268

265269
#endif

src/stim/arg_parse.test.cc

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,9 @@ TEST(arg_parse, find_open_file_argument) {
260260

261261
args = {""};
262262
ASSERT_THROW_MSG(
263-
{ find_open_file_argument("-arg", nullptr, "rb", args.size(), args.data()); }, std::invalid_argument, "Missing");
263+
{ find_open_file_argument("-arg", nullptr, "rb", args.size(), args.data()); },
264+
std::invalid_argument,
265+
"Missing");
264266
args = {""};
265267
ASSERT_EQ(find_open_file_argument("-arg", tmp, "rb", args.size(), args.data()), tmp);
266268

@@ -292,3 +294,33 @@ TEST(arg_parse, find_open_file_argument) {
292294

293295
fclose(tmp);
294296
}
297+
298+
TEST(arg_parse, split) {
299+
ASSERT_EQ(split(',', ""), (std::vector<std::string>{""}));
300+
ASSERT_EQ(split(',', "abc"), (std::vector<std::string>{"abc"}));
301+
ASSERT_EQ(split(',', ","), (std::vector<std::string>{"", ""}));
302+
ASSERT_EQ(split(',', "abc,"), (std::vector<std::string>{"abc", ""}));
303+
ASSERT_EQ(split(',', ",abc"), (std::vector<std::string>{"", "abc"}));
304+
ASSERT_EQ(split(',', "abc,def,ghi"), (std::vector<std::string>{"abc", "def", "ghi"}));
305+
ASSERT_EQ(split(',', "abc,def,ghi,"), (std::vector<std::string>{"abc", "def", "ghi", ""}));
306+
}
307+
308+
TEST(arg_parse, parse_exact_double_from_string) {
309+
ASSERT_THROW({ parse_exact_double_from_string(""); }, std::invalid_argument);
310+
ASSERT_THROW({ parse_exact_double_from_string("a"); }, std::invalid_argument);
311+
ASSERT_THROW({ parse_exact_double_from_string(" 1"); }, std::invalid_argument);
312+
ASSERT_THROW({ parse_exact_double_from_string("\t1"); }, std::invalid_argument);
313+
ASSERT_THROW({ parse_exact_double_from_string("1 "); }, std::invalid_argument);
314+
ASSERT_THROW({ parse_exact_double_from_string("banana"); }, std::invalid_argument);
315+
ASSERT_THROW({ parse_exact_double_from_string("1.2.3"); }, std::invalid_argument);
316+
ASSERT_THROW({ parse_exact_double_from_string("INFINITY"); }, std::invalid_argument);
317+
ASSERT_THROW({ parse_exact_double_from_string("inf"); }, std::invalid_argument);
318+
ASSERT_THROW({ parse_exact_double_from_string("nan"); }, std::invalid_argument);
319+
320+
ASSERT_EQ(parse_exact_double_from_string("0"), 0);
321+
ASSERT_EQ(parse_exact_double_from_string("1"), 1);
322+
ASSERT_EQ(parse_exact_double_from_string("+1"), 1);
323+
ASSERT_EQ(parse_exact_double_from_string("1e3"), 1000);
324+
ASSERT_EQ(parse_exact_double_from_string("1.5"), 1.5);
325+
ASSERT_EQ(parse_exact_double_from_string("-1.5"), -1.5);
326+
}

src/stim/circuit/circuit.pybind.cc

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1808,6 +1808,7 @@ void stim_pybind::pybind_circuit_methods(pybind11::module &, pybind11::class_<Ci
18081808
pybind11::kw_only(),
18091809
pybind11::arg("type") = "timeline-text",
18101810
pybind11::arg("tick") = pybind11::none(),
1811+
pybind11::arg("filter_coords") = pybind11::none(),
18111812
clean_doc_string(u8R"DOC(
18121813
@overload def diagram(self, *, type: 'Literal["timeline-text"]') -> 'stim._DiagramHelper':
18131814
@overload def diagram(self, *, type: 'Literal["timeline-svg"]') -> 'stim._DiagramHelper':
@@ -1816,9 +1817,9 @@ void stim_pybind::pybind_circuit_methods(pybind11::module &, pybind11::class_<Ci
18161817
@overload def diagram(self, *, type: 'Literal["match-graph-svg"]') -> 'stim._DiagramHelper':
18171818
@overload def diagram(self, *, type: 'Literal["match-graph-3d"]') -> 'stim._DiagramHelper':
18181819
@overload def diagram(self, *, type: 'Literal["match-graph-3d-html"]') -> 'stim._DiagramHelper':
1819-
@overload def diagram(self, *, type: 'Literal["detector-slice-text"]', tick: int) -> 'stim._DiagramHelper':
1820-
@overload def diagram(self, *, type: 'Literal["detector-slice-svg"]', tick: int) -> 'stim._DiagramHelper':
1821-
@signature def diagram(self, *, type: str, tick: Optional[int] = None) -> 'stim._DiagramHelper':
1820+
@overload def diagram(self, *, type: 'Literal["detector-slice-text"]', tick: int, filter_coords: Optional[Iterable[Iterable[float]]] = None) -> 'stim._DiagramHelper':
1821+
@overload def diagram(self, *, type: 'Literal["detector-slice-svg"]', tick: int, filter_coords: Optional[Iterable[Iterable[float]]] = None) -> 'stim._DiagramHelper':
1822+
@signature def diagram(self, *, type: str, tick: Optional[int] = None, filter_coords: Optional[Iterable[Iterable[float]]] = None) -> 'stim._DiagramHelper':
18221823
Returns a diagram of the circuit, from a variety of options.
18231824
18241825
Args:
@@ -1861,6 +1862,9 @@ void stim_pybind::pybind_circuit_methods(pybind11::module &, pybind11::class_<Ci
18611862
instruction to slice at. Note that the first TICK in the
18621863
circuit is tick=1. The value tick=0 refers to the very start
18631864
of the circuit.
1865+
filter_coords: A set of acceptable coordinate prefixes. For
1866+
detector slice diagrams, only detectors whose coordinates
1867+
begin with one of these filters will be included.
18641868
18651869
Returns:
18661870
An object whose `__str__` method returns the diagram, so that

0 commit comments

Comments
 (0)