2
$\begingroup$

I am currently trying to use stim to evaluate the performance of decoders other than the ones that are inbuilt into the framework (mwpm and fusion-blossom). I need to get the logical error -rate of these decoders. Suppose I implement these decoders as a python function which takes an array of syndromes as input and gives the erroneous data qubits as output, is there a way to integrate such an arbitrary function in stim? If yes, an example would help.

Thanks!

$\endgroup$

1 Answer 1

1
$\begingroup$

Since v1.11, you can add a custom decoder by implementing a child class of sinter.Decoder. You tell the sinter.collect method about this decoder via its custom_decoders= argument. (On the command line, you tell sinter collect about your custom decoder via --custom_decoders_module_function.)

Here's an example of a custom decoder. It predicts whether observables are flipped based on nonsense, so it performs terribly, but it satisfies the interface:

import math
import pathlib
import numpy as np
import sinter
import stim


class CustomDecoder(sinter.Decoder):
    def decode_via_files(self,
                         *,
                         num_shots: int,
                         num_dets: int,
                         num_obs: int,
                         dem_path: pathlib.Path,
                         dets_b8_in_path: pathlib.Path,
                         obs_predictions_b8_out_path: pathlib.Path,
                         tmp_dir: pathlib.Path,
                       ) -> None:
        # Read input data. Note it's not a great idea to store it in memory all at once like this.
        detector_error_model = stim.DetectorErrorModel.from_file(dem_path)
        packed_detection_event_data = np.fromfile(dets_b8_in_path, dtype=np.uint8)
        packed_detection_event_data.shape = (num_shots, math.ceil(num_dets / 8))

        # Make predictions.
        all_predictions = []
        for shot in packed_detection_event_data:
            unpacked = np.unpackbits(shot, bitorder='little')
            shot_predictions = []

            # This is a terrible way to make the prediction.
            # It doesn't even look at the error model!
            excitations = np.count_nonzero(unpacked)
            for k in range(num_obs):
                shot_predictions.append(excitations % (k + 2) == 0)

            all_predictions.append(shot_predictions)

        # Write predictions.
        np.packbits(all_predictions, axis=1, bitorder='little').tofile(obs_predictions_b8_out_path)

    def compile_decoder_for_dem(self, *, dem: stim.DetectorErrorModel) -> 'sinter.CompiledDecoder':
        # This will be added in v1.12 and sinter will prefer it, as it avoids the disk as a bottleneck.
        #
        # You have to return an object with this method:
        #    def decode_shots_bit_packed(
        #                self,
        #                *,
        #                bit_packed_detection_event_data: np.ndarray,
        #        ) -> np.ndarray:
        raise NotImplementedError()

You can put an instance of this decoder into a dictionary and pass that dictionary to sinter.collect in order to use it. The name of the decoder is its key in the dictionary. For example, custom_decoders={'MY_AMAZING_CUSTOM_DECODER_WOW': CustomDecoder()} means the decoder is called "MY_AMAZING_CUSTOM_DECODER_WOW":

tasks = [
    sinter.Task(
        circuit=stim.Circuit.generated(
            "repetition_code:memory",
            distance=d,
            rounds=d*3,
            before_round_data_depolarization=1e-2,
        ),
        json_metadata={'d': d},
    )
    for d in [3, 5, 7]
]

results = sinter.collect(
    num_workers=2,
    tasks=tasks,
    decoders=['pymatching', 'MY_AMAZING_CUSTOM_DECODER_WOW'],
    custom_decoders={'MY_AMAZING_CUSTOM_DECODER_WOW': CustomDecoder()},
    max_shots=10000,
)
for r in results:
    print(r)
     10000,      9475,         0,   0.070,MY_AMAZING_CUSTOM_DECODER_WOW,20a39c883e35c1d6ed09bf73ed2e1690f485446078e1ab4a286a0689ce5a6331,"{""d"":3}"
     10000,        16,         0,   0.219,pymatching,0c79d325cecbb8e67646ce4f7783d673495256bad9b292d39fc1602fa5028018,"{""d"":3}"
     10000,      9100,         0,   0.039,MY_AMAZING_CUSTOM_DECODER_WOW,c5d0906714ba3678300e2583bd57333205be3f110d84aa32b496f8e02daed9c1,"{""d"":5}"
     10000,         0,         0,   0.016,pymatching,1a3b24de16955403277eec3011fef54a7afde9bc9e59f9de1ef8d3763fa0b066,"{""d"":7}"
     10000,      8784,         0,   0.039,MY_AMAZING_CUSTOM_DECODER_WOW,eed26933e8a5da7e0171d7a287bbb1bfc27170dded2fdf5b0f21e1a00578c117,"{""d"":7}"
     10000,         0,         0,   0.235,pymatching,cee9d73fed658154fad401aad43fc37109e3486db697d8e8a191726195521e97,"{""d"":5}"

From the command line it's much more awkward to use a custom decoder, but still possible. You have to pass the argument --custom_decoders_module_function with an argument like my_custom_module:CustomDecoder. This requires your custom decoder to be in a python module my_custom_module (or whatever you gave to the argument) that python can import e.g. due to it being in a location in PYTHONPATH.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.