Skip to content

Commit 3751bef

Browse files
authored
Allow OBSERVABLE_INCLUDE to target Pauli terms (#853)
There are two use cases driving this change: 1. Magic state injection needing requiring partially deterministic observables 2. Simulating all observables of a code without needing to add noiseless ancilla qubits to the circuit This change allows observables to be split into pieces (e.g. obs 1 for the first half of the circuit and obs 2 for the second half, with the "true" observable their xor). The flip of each individual piece can even be recovered when using flip simulation, if stabilizer randomization is disabled. This change allows finer control over the logical labels that appear in the detector error model. For example: ``` import stim assert stim.Circuit(""" OBSERVABLE_INCLUDE(0) X0 OBSERVABLE_INCLUDE(1) Y0 OBSERVABLE_INCLUDE(2) Z0 X_ERROR(0.125) 0 Y_ERROR(0.25) 0 Z_ERROR(0.375) 0 OBSERVABLE_INCLUDE(0) X0 OBSERVABLE_INCLUDE(1) Y0 OBSERVABLE_INCLUDE(2) Z0 """).detector_error_model() == stim.DetectorErrorModel(""" error(0.375) L0 L1 error(0.25) L0 L2 error(0.125) L1 L2 """) ```
1 parent a33df80 commit 3751bef

29 files changed

+495
-320
lines changed

.github/workflows/ci.yml

+4-4
Original file line numberDiff line numberDiff line change
@@ -453,16 +453,16 @@ jobs:
453453
- run: pytest glue/zx
454454
- run: dev/doctest_proper.py --module stimzx
455455
test_stimjs:
456-
runs-on: ubuntu-latest
456+
runs-on: ubuntu-22.04
457457
steps:
458458
- uses: actions/checkout@v3
459459
- uses: mymindstorm/setup-emsdk@v14
460460
with:
461-
version: 2.0.18
461+
version: 4.0.1
462462
actions-cache-folder: 'emsdk-cache'
463-
- uses: actions/setup-node@v1
463+
- uses: actions/setup-node@v4
464464
with:
465-
node-version: 16.x
465+
node-version: 18.x
466466
- run: npm install
467467
- run: bash glue/javascript/build_wasm.sh
468468
- run: node puppeteer_run_tests.js

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,11 @@ venv
3535
.cmake/*
3636
coverage/*
3737
cmake-build-debug-coverage/*
38-
# clangd generated files
3938
compile_commands.json
4039
.cache
4140
build.ninja
4241
.ninja_deps
4342
.ninja_log
4443
.pytest_cache
4544
node_modules
45+
MODULE.bazel.lock

MODULE.bazel.lock

-116
This file was deleted.

doc/gates.md

+25
Original file line numberDiff line numberDiff line change
@@ -3344,6 +3344,15 @@ determining the expected value, and `X_ERROR` is noise, causing deviations from
33443344
It is not recommended for the measurement set of an observable to have inconsistent parity. For example, the
33453345
circuit-to-detector-error-model conversion will refuse to operate on circuits containing such observables.
33463346

3347+
In addition to targeting measurements, observables can target Pauli operators. This has no effect when running the
3348+
quantum computation, but is used when configuring the decoder. For example, when performing a logical Z initialization,
3349+
it allows a logical X operator to be introduced (by marking its Pauli terms) despite the fact that it anticommutes
3350+
with the initialization. In practice, when physically sampling a circuit or simulating sampling its measurements and
3351+
then computing the observables from the measurements, these Pauli terms are effectively ignored. However, they affect
3352+
detection event simulations and affect whether the observable is included in errors in the detector error model. This
3353+
makes it easier to benchmark all observables of a code, without having to introduce noiseless qubits entangled with the
3354+
logical qubit to avoid the testing of the X observable anticommuting with the testing of the Z observable.
3355+
33473356
Parens Arguments:
33483357

33493358
A non-negative integer specifying the index of the logical observable to add the measurement records to.
@@ -3370,6 +3379,22 @@ Example:
33703379
# ...and the one before that.
33713380
OBSERVABLE_INCLUDE(1) rec[-2]
33723381

3382+
# Unphysically tracking two anticommuting observables of a 2x2 surface code.
3383+
QUBIT_COORDS(0, 0) 0
3384+
QUBIT_COORDS(1, 0) 1
3385+
QUBIT_COORDS(0, 1) 2
3386+
QUBIT_COORDS(1, 1) 3
3387+
OBSERVABLE_INCLUDE(0) X0 X1
3388+
OBSERVABLE_INCLUDE(1) Z0 Z2
3389+
MPP X0*X1*X2*X3 Z0*Z1 Z2*Z3
3390+
DEPOLARIZE1(0.001) 0 1 2 3
3391+
MPP X0*X1*X2*X3 Z0*Z1 Z2*Z3
3392+
DETECTOR rec[-1] rec[-4]
3393+
DETECTOR rec[-2] rec[-5]
3394+
DETECTOR rec[-3] rec[-6]
3395+
OBSERVABLE_INCLUDE(0) X0 X1
3396+
OBSERVABLE_INCLUDE(1) Z0 Z2
3397+
33733398
<a name="QUBIT_COORDS"></a>
33743399
### The 'QUBIT_COORDS' Instruction
33753400

src/stim/circuit/circuit.test.cc

+2
Original file line numberDiff line numberDiff line change
@@ -1807,12 +1807,14 @@ Circuit stim::generate_test_circuit_with_all_operations() {
18071807
DETECTOR(1, 2, 3) rec[-1]
18081808
OBSERVABLE_INCLUDE(0) rec[-1]
18091809
MPAD 0 1 0
1810+
OBSERVABLE_INCLUDE(1) Z2 Z3
18101811
TICK
18111812
18121813
# Inverted measurements.
18131814
MRX !0
18141815
MY !1
18151816
MZZ !2 3
1817+
OBSERVABLE_INCLUDE(1) rec[-1]
18161818
MYY !4 !5
18171819
MPP X6*!Y7*Z8
18181820
TICK

src/stim/circuit/circuit_instruction.cc

+11-3
Original file line numberDiff line numberDiff line change
@@ -228,9 +228,17 @@ void CircuitInstruction::validate() const {
228228
valid_target_mask |= TARGET_RECORD_BIT | TARGET_SWEEP_BIT;
229229
}
230230
if (gate.flags & GATE_ONLY_TARGETS_MEASUREMENT_RECORD) {
231-
for (GateTarget q : targets) {
232-
if (!(q.data & TARGET_RECORD_BIT)) {
233-
throw std::invalid_argument("Gate " + std::string(gate.name) + " only takes rec[-k] targets.");
231+
if (gate.flags & GATE_TARGETS_PAULI_STRING) {
232+
for (GateTarget q : targets) {
233+
if (!q.is_measurement_record_target() && !q.is_pauli_target()) {
234+
throw std::invalid_argument("Gate " + std::string(gate.name) + " only takes measurement record targets and Pauli targets (rec[-k], Xk, Yk, Zk).");
235+
}
236+
}
237+
} else {
238+
for (GateTarget q : targets) {
239+
if (!q.is_measurement_record_target()) {
240+
throw std::invalid_argument("Gate " + std::string(gate.name) + " only takes measurement record targets (rec[-k]).");
241+
}
234242
}
235243
}
236244
} else if (gate.flags & GATE_TARGETS_PAULI_STRING) {

src/stim/cmd/command_m2d.cc

-14
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,3 @@
1-
// Copyright 2021 Google LLC
2-
//
3-
// Licensed under the Apache License, Version 2.0 (the "License");
4-
// you may not use this file except in compliance with the License.
5-
// You may obtain a copy of the License at
6-
//
7-
// http://www.apache.org/licenses/LICENSE-2.0
8-
//
9-
// Unless required by applicable law or agreed to in writing, software
10-
// distributed under the License is distributed on an "AS IS" BASIS,
11-
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12-
// See the License for the specific language governing permissions and
13-
// limitations under the License.
14-
151
#include "stim/cmd/command_m2d.h"
162

173
#include "command_help.h"

src/stim/cmd/command_m2d.test.cc

+27
Original file line numberDiff line numberDiff line change
@@ -154,3 +154,30 @@ TEST(command_m2d, m2d_obs_size_misalign_11_obs) {
154154
trim(std::string(1024, '0') + "\n"));
155155
ASSERT_EQ(tmp_obs.read_contents(), "00000000000\n");
156156
}
157+
158+
TEST(command_m2d, unphysical_observable_annotations) {
159+
RaiiTempNamedFile tmp_circuit(R"CIRCUIT(
160+
QUBIT_COORDS(0, 0) 0
161+
QUBIT_COORDS(1, 0) 1
162+
QUBIT_COORDS(0, 1) 2
163+
QUBIT_COORDS(1, 1) 3
164+
OBSERVABLE_INCLUDE(0) X0 X1
165+
OBSERVABLE_INCLUDE(1) Z0 Z2
166+
MPP X0*X1*X2*X3 Z0*Z1 Z2*Z3
167+
DEPOLARIZE1(0.001) 0 1 2 3
168+
MPP X0*X1*X2*X3 Z0*Z1 Z2*Z3
169+
DETECTOR rec[-1] rec[-4]
170+
DETECTOR rec[-2] rec[-5]
171+
DETECTOR rec[-3] rec[-6]
172+
OBSERVABLE_INCLUDE(0) X0 X1
173+
OBSERVABLE_INCLUDE(1) Z0 Z2
174+
)CIRCUIT");
175+
RaiiTempNamedFile tmp_obs;
176+
177+
ASSERT_EQ(
178+
trim(run_captured_stim_main(
179+
{"m2d", "--in_format=01", "--obs_out", tmp_obs.path.c_str(), "--circuit", tmp_circuit.path.c_str()},
180+
"000000\n100100\n000110\n")),
181+
trim("000\n000\n011\n"));
182+
ASSERT_EQ(tmp_obs.read_contents(), "00\n00\n00\n");
183+
}

src/stim/diagram/circuit_timeline_helper.cc

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
#include "stim/diagram/circuit_timeline_helper.h"
22

3-
#include "stim/diagram/diagram_util.h"
4-
53
using namespace stim;
64
using namespace stim_draw_internal;
75

8-
void CircuitTimelineHelper::skip_loop_iterations(CircuitTimelineLoopData loop_data, uint64_t skipped_reps) {
6+
void CircuitTimelineHelper::skip_loop_iterations(const CircuitTimelineLoopData &loop_data, uint64_t skipped_reps) {
97
if (loop_data.num_repetitions > 0) {
108
vec_pad_add_mul(cur_coord_shift, loop_data.shift_per_iteration, skipped_reps);
119
measure_offset += loop_data.measurements_per_iteration * skipped_reps;
@@ -95,6 +93,11 @@ GateTarget CircuitTimelineHelper::rec_to_qubit(const GateTarget &target) {
9593
}
9694

9795
GateTarget CircuitTimelineHelper::pick_pseudo_target_representing_measurements(const CircuitInstruction &op) {
96+
for (const auto &t : op.targets) {
97+
if (t.is_qubit_target() || t.is_pauli_target()) {
98+
return t;
99+
}
100+
}
98101
// First check if coordinates prefix-match a qubit's coordinates.
99102
if (!op.args.empty()) {
100103
auto coords = shifted_coordinates_in_workspace(op.args);

src/stim/diagram/circuit_timeline_helper.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ struct CircuitTimelineHelper {
6969

7070
stim::GateTarget rec_to_qubit(const stim::GateTarget &target);
7171
stim::GateTarget pick_pseudo_target_representing_measurements(const stim::CircuitInstruction &op);
72-
void skip_loop_iterations(CircuitTimelineLoopData loop_data, uint64_t skipped_reps);
72+
void skip_loop_iterations(const CircuitTimelineLoopData &loop_data, uint64_t skipped_reps);
7373
void do_record_measure_result(uint32_t target_qubit);
7474
void do_repeat_block(const stim::Circuit &circuit, const stim::CircuitInstruction &op);
7575
void do_next_operation(const stim::Circuit &circuit, const stim::CircuitInstruction &op);

0 commit comments

Comments
 (0)