Skip to content

qubic.rpc_client

Contains a RPC client implementation of AbstractCircuitRunner submitting and running circuits over a network.

CircuitRunnerClient

Bases: AbstractCircuitRunner

CircuitRunner instance that can be run from a remote machine (i.e. not on the RFSoC ARM core) over RPC. Used for submitting compiled circuits to a QubiC board and receiving the resulting (integrated IQ or ADC timestream) data. Should be a drop-in replacement for CircuitRunner for most experiments. Exposes the following methods from CircuitRunner:

run_circuit
run_circuit_batch
load_circuit
load_and_run_acq
Source code in qubic/rpc_client.py
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
class CircuitRunnerClient(AbstractCircuitRunner):
    """
    CircuitRunner instance that can be run from a remote machine (i.e. not on
    the RFSoC ARM core) over RPC. Used for submitting compiled circuits to a QubiC board
    and receiving the resulting (integrated IQ or ADC timestream) data. Should be a drop-in 
    replacement for CircuitRunner for most experiments. Exposes the following methods from
    CircuitRunner:

        run_circuit
        run_circuit_batch
        load_circuit
        load_and_run_acq
    """

    def __init__(self, ip, port=9095):
        self.proxy = xmlrpc.client.ServerProxy('http://' + ip + ':' + str(port), allow_none=True)

    def run_circuit(self, n_total_shots: int, reads_per_shot: int | dict = 1, timeout_per_shot: float = 8) -> Dict:
        """
        Run the currently loaded program and acquire integrated IQ shots. Program is
        run `n_total_shots` times, in batches of size `shots_per_run` (i.e. `shots_per_run` runs of the program
        are executed in logic before each readback/restart cycle). The current gateware 
        is limited to ~1000 reads in its IQ buffer, which generally means 
        shots_per_run = 1000//reads_per_shot

        Parameters
        ----------
        n_total_shots: int
            number of shots to run. Program is restarted from the beginning 
            for each new shot
        reads_per_shot: int | dict
            number of values per shot per channel to read back from accbuf. If `dict`, indexed
            by `str(channel_number)` (same indices as `raw_asm_list`). If `int`, assumed to be 
            the same across channels. Unless multiple circuits were rastered pre-compilation or 
            there is mid-circuit measurement involved this is typically 1
        timeout_per_shot: float
            job will time out if time to take a single shot exceeds this value in seconds 
            (this likely means the job is hanging due to timing issues in the program or gateware)
        delay_per_shot: float (WILL BE REMOVED)
            delay time (in seconds) per single shot of the circuit

        Returns
        -------
        dict:
            Complex IQ shots for each accbuf in chanlist; each array has 
            shape `(n_total_shots, reads_per_shot)`
        """
        s11 = self.proxy.run_circuit(n_total_shots, reads_per_shot, float(timeout_per_shot), True)

        if isinstance(reads_per_shot, int):
            reads_per_shot = {chan: reads_per_shot for chan in s11.keys()}

        for ch in s11.keys():
            s11[ch] = np.reshape(np.frombuffer(s11[ch].data, dtype=np.complex128), 
                                 (n_total_shots, reads_per_shot[ch]))
        return s11

    def run_circuit_batch(self, 
                          raw_asm_list: List[Dict], 
                          n_total_shots: int, 
                          reads_per_shot: int | Dict = 1, 
                          timeout_per_shot: float = 8,
                          reload_cmd: bool = True, 
                          reload_freq: bool = True, 
                          reload_env: bool = True, 
                          zero_between_reload: bool = True) -> Dict:
        """
        Runs a batch of circuits given by a list of compiled ASM binaries. Each circuit is run n_total_shots
        times. `reads_per_shot`, `n_total_shots`, and `delay_per_shot` are passed directly into `run_circuit`, and must
        be the same for all circuits in the batch. The parameters `reload_cmd`, `reload_freq`, `reload_env`, and 
        `zero_between_reload` control which of these fields is rewritten circuit-to-circuit (everything is 
        rewritten initially). Leave these all at `True` (default) for maximum safety, to ensure that QubiC 
        is in a clean state before each run. Depending on the circuits, some of these can be turned off 
        to save time.

        Parameters
        ----------
        raw_asm_list: list
            list of raw_asm binaries to run
        n_total_shots: int
            number of shots per circuit
        reads_per_shot: int | dict
            number of values per shot per channel to read back from accbuf. If `dict`, indexed
            by `str(channel_number)` (same indices as `raw_asm_list`). If `int`, assumed to be 
            the same across channels. Unless multiple circuits were rastered pre-compilation or 
            there is mid-circuit measurement involved this is typically 1
        timeout_per_shot: float
            job will time out if time to take a single shot exceeds this value in seconds 
            (this likely means the job is hanging due to timing issues in the program or gateware)
        delay_per_shot: float (WILL BE REMOVED)
            delay time (in seconds) per single shot of the circuit
        reload_cmd: bool
            if True, reload command buffer between circuits
        reload_freq: bool
            if True, reload freq buffer between circuits
        reload_env: bool
            if True, reload env buffer between circuits
        Returns
        -------
        dict:
            Complex IQ shots for each accbuf in chanlist; each array has 
            shape `(len(raw_asm_list), n_total_shots, reads_per_shot)`
        """
        # TODO: consider throwing some version of all the args here into a BatchedCircuitRun or somesuch
        # object
        channels = set().union(*list(set(prog.keys()) for prog in raw_asm_list)) # union of all proc channels in batch
        if isinstance(reads_per_shot, int):
            reads_per_shot = {chan: reads_per_shot for chan in channels}

        s11 = self.proxy.run_circuit_batch(raw_asm_list, n_total_shots, reads_per_shot, float(timeout_per_shot),
                                     reload_cmd, reload_freq, reload_env, zero_between_reload)
        for ch in s11.keys():
            s11[ch] = np.reshape(np.frombuffer(s11[ch].data, dtype=np.complex128), 
                                 (len(raw_asm_list), n_total_shots, reads_per_shot[ch]))
        return s11

    def load_circuit(self, 
                     rawasm: Dict, 
                     zero=True, 
                     load_commands: bool = True, 
                     load_freqs: bool = True, 
                     load_envs: bool = True):
        """
        Load circuit described by rawasm "binary", which is the output of 
        the final distributed proc assembler stage. Loads command memory, env memory
        and freq buffer memory, according to specified input parameters. Before circuit is loaded, 
        if `zero=True`, all channels are zeroed out using `zero_command_buf()`

        Parameters
        ----------
        rawasm: dict
            keys are channels to load. For each channel, there should be:

                - 'cmd_buf' : byte array containing compiled program binary
                - 'env_buffers' : dict of env buffers for that channel:
                    0 : qdrv buffer
                    1 : rdrv buffer
                    2 : rdlo buffer
                - 'freq_buffers' : dict of freq buffers for that channel:
                    0 : qdrv buffer
                    1 : rdrv buffer
                    2 : rdlo buffer
        zero: bool
            if True, (default), zero out all cmd buffers before loading circuit
        load_commands: bool
            if True, (default), load command buffers
        load_freqs: bool
            if True, (default), load freq buffers
        load_envs: bool
            if True, (default), load env buffers
        """
        self.proxy.load_circuit(rawasm, zero, load_commands, load_freqs, load_envs)

    def load_and_run_acq(self, 
                         raw_asm_prog: List[Dict], 
                         n_total_shots: int = 1, 
                         nsamples: int = 8192, 
                         acq_chans: Dict = {'0': 0, '1': 1}, 
                         trig_delay: int = 0, 
                         decimator: int = 0, 
                         return_acc: bool = False) -> tuple | Dict:
        """
        Load the program given by raw_asm_prog and acquire raw (or downconverted) adc traces.

        Parameters
        ----------
        raw_asm_prog: dict
            ASM binary to run. See load_circuit for details.
        n_total_shots: int
            number of shots to run. Program is restarted from the beginning 
            for each new shot
        nsamples: int
            number of samples to read from the acq buffer
        acq_chans: dict
            current channel mapping is:

                '0': ADC_237_2 (main readout ADC)
                '1': ADC_237_0 (other ADC connected in gateware)
                TODO: figure out DLO channels, etc and what they mean
        trig_delay: float
            time to delay acquisition, relative to circuit start.
            NOTE: this value, when converted to units of clock cycles, is a 
            16-bit value. So, it maxes out at CLK_PERIOD*(2**16) = 131.072e-6
        decimator: int
            decimation interval when sampling. e.g. 0 means full sample rate, 1
            means capture every other sample, 2 means capture every third sample, etc
        return_acc: bool
            if True, return a single acc (integrated + accumulated readout) value per shot,
            on each loaded channel. Default is False.

        Returns
        -------
        tuple | Dict
            - if `return_acc` is `False`:

                - dict:
                    array of acq samples for each channel in acq_chans with shape `(n_total_shots, nsamples)`

            - if `return_acc` is `True`:

                - tuple:
                    - dict:
                        array of acq samples for each channel in `acq_chans` with shape `(n_total_shots, nsamples)`
                    - dict:
                        array of acc values for each loaded channel with length `n_total_shots`

        """
        data = self.proxy.load_and_run_acq(raw_asm_prog, n_total_shots, nsamples, acq_chans, trig_delay, decimator, return_acc, True)

        if return_acc:
            acq_data = data[0]
            acc_data = data[1]
        else:
            acq_data = data
            acc_data = {}

        for ch in acq_data.keys():
            acq_data[ch] = np.reshape(np.frombuffer(acq_data[ch].data, dtype=np.int32), (n_total_shots, nsamples))
        for ch in acc_data.keys():
            acc_data[ch] = np.frombuffer(acc_data[ch].data, dtype=np.complex128)

        if return_acc:
            return acq_data, acc_data
        else:
            return acq_data

load_and_run_acq(raw_asm_prog, n_total_shots=1, nsamples=8192, acq_chans={'0': 0, '1': 1}, trig_delay=0, decimator=0, return_acc=False)

Load the program given by raw_asm_prog and acquire raw (or downconverted) adc traces.

Parameters:

Name Type Description Default
raw_asm_prog List[Dict]

ASM binary to run. See load_circuit for details.

required
n_total_shots int

number of shots to run. Program is restarted from the beginning for each new shot

1
nsamples int

number of samples to read from the acq buffer

8192
acq_chans Dict

current channel mapping is:

'0': ADC_237_2 (main readout ADC)
'1': ADC_237_0 (other ADC connected in gateware)
TODO: figure out DLO channels, etc and what they mean
{'0': 0, '1': 1}
trig_delay int

time to delay acquisition, relative to circuit start. NOTE: this value, when converted to units of clock cycles, is a 16-bit value. So, it maxes out at CLK_PERIOD(2*16) = 131.072e-6

0
decimator int

decimation interval when sampling. e.g. 0 means full sample rate, 1 means capture every other sample, 2 means capture every third sample, etc

0
return_acc bool

if True, return a single acc (integrated + accumulated readout) value per shot, on each loaded channel. Default is False.

False

Returns:

Type Description
tuple | Dict
  • if return_acc is False:

    • dict: array of acq samples for each channel in acq_chans with shape (n_total_shots, nsamples)
  • if return_acc is True:

    • tuple:
      • dict: array of acq samples for each channel in acq_chans with shape (n_total_shots, nsamples)
      • dict: array of acc values for each loaded channel with length n_total_shots
Source code in qubic/rpc_client.py
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
def load_and_run_acq(self, 
                     raw_asm_prog: List[Dict], 
                     n_total_shots: int = 1, 
                     nsamples: int = 8192, 
                     acq_chans: Dict = {'0': 0, '1': 1}, 
                     trig_delay: int = 0, 
                     decimator: int = 0, 
                     return_acc: bool = False) -> tuple | Dict:
    """
    Load the program given by raw_asm_prog and acquire raw (or downconverted) adc traces.

    Parameters
    ----------
    raw_asm_prog: dict
        ASM binary to run. See load_circuit for details.
    n_total_shots: int
        number of shots to run. Program is restarted from the beginning 
        for each new shot
    nsamples: int
        number of samples to read from the acq buffer
    acq_chans: dict
        current channel mapping is:

            '0': ADC_237_2 (main readout ADC)
            '1': ADC_237_0 (other ADC connected in gateware)
            TODO: figure out DLO channels, etc and what they mean
    trig_delay: float
        time to delay acquisition, relative to circuit start.
        NOTE: this value, when converted to units of clock cycles, is a 
        16-bit value. So, it maxes out at CLK_PERIOD*(2**16) = 131.072e-6
    decimator: int
        decimation interval when sampling. e.g. 0 means full sample rate, 1
        means capture every other sample, 2 means capture every third sample, etc
    return_acc: bool
        if True, return a single acc (integrated + accumulated readout) value per shot,
        on each loaded channel. Default is False.

    Returns
    -------
    tuple | Dict
        - if `return_acc` is `False`:

            - dict:
                array of acq samples for each channel in acq_chans with shape `(n_total_shots, nsamples)`

        - if `return_acc` is `True`:

            - tuple:
                - dict:
                    array of acq samples for each channel in `acq_chans` with shape `(n_total_shots, nsamples)`
                - dict:
                    array of acc values for each loaded channel with length `n_total_shots`

    """
    data = self.proxy.load_and_run_acq(raw_asm_prog, n_total_shots, nsamples, acq_chans, trig_delay, decimator, return_acc, True)

    if return_acc:
        acq_data = data[0]
        acc_data = data[1]
    else:
        acq_data = data
        acc_data = {}

    for ch in acq_data.keys():
        acq_data[ch] = np.reshape(np.frombuffer(acq_data[ch].data, dtype=np.int32), (n_total_shots, nsamples))
    for ch in acc_data.keys():
        acc_data[ch] = np.frombuffer(acc_data[ch].data, dtype=np.complex128)

    if return_acc:
        return acq_data, acc_data
    else:
        return acq_data

load_circuit(rawasm, zero=True, load_commands=True, load_freqs=True, load_envs=True)

Load circuit described by rawasm "binary", which is the output of the final distributed proc assembler stage. Loads command memory, env memory and freq buffer memory, according to specified input parameters. Before circuit is loaded, if zero=True, all channels are zeroed out using zero_command_buf()

Parameters:

Name Type Description Default
rawasm Dict

keys are channels to load. For each channel, there should be:

- 'cmd_buf' : byte array containing compiled program binary
- 'env_buffers' : dict of env buffers for that channel:
    0 : qdrv buffer
    1 : rdrv buffer
    2 : rdlo buffer
- 'freq_buffers' : dict of freq buffers for that channel:
    0 : qdrv buffer
    1 : rdrv buffer
    2 : rdlo buffer
required
zero

if True, (default), zero out all cmd buffers before loading circuit

True
load_commands bool

if True, (default), load command buffers

True
load_freqs bool

if True, (default), load freq buffers

True
load_envs bool

if True, (default), load env buffers

True
Source code in qubic/rpc_client.py
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
def load_circuit(self, 
                 rawasm: Dict, 
                 zero=True, 
                 load_commands: bool = True, 
                 load_freqs: bool = True, 
                 load_envs: bool = True):
    """
    Load circuit described by rawasm "binary", which is the output of 
    the final distributed proc assembler stage. Loads command memory, env memory
    and freq buffer memory, according to specified input parameters. Before circuit is loaded, 
    if `zero=True`, all channels are zeroed out using `zero_command_buf()`

    Parameters
    ----------
    rawasm: dict
        keys are channels to load. For each channel, there should be:

            - 'cmd_buf' : byte array containing compiled program binary
            - 'env_buffers' : dict of env buffers for that channel:
                0 : qdrv buffer
                1 : rdrv buffer
                2 : rdlo buffer
            - 'freq_buffers' : dict of freq buffers for that channel:
                0 : qdrv buffer
                1 : rdrv buffer
                2 : rdlo buffer
    zero: bool
        if True, (default), zero out all cmd buffers before loading circuit
    load_commands: bool
        if True, (default), load command buffers
    load_freqs: bool
        if True, (default), load freq buffers
    load_envs: bool
        if True, (default), load env buffers
    """
    self.proxy.load_circuit(rawasm, zero, load_commands, load_freqs, load_envs)

run_circuit(n_total_shots, reads_per_shot=1, timeout_per_shot=8)

Run the currently loaded program and acquire integrated IQ shots. Program is run n_total_shots times, in batches of size shots_per_run (i.e. shots_per_run runs of the program are executed in logic before each readback/restart cycle). The current gateware is limited to ~1000 reads in its IQ buffer, which generally means shots_per_run = 1000//reads_per_shot

Parameters:

Name Type Description Default
n_total_shots int

number of shots to run. Program is restarted from the beginning for each new shot

required
reads_per_shot int | dict

number of values per shot per channel to read back from accbuf. If dict, indexed by str(channel_number) (same indices as raw_asm_list). If int, assumed to be the same across channels. Unless multiple circuits were rastered pre-compilation or there is mid-circuit measurement involved this is typically 1

1
timeout_per_shot float

job will time out if time to take a single shot exceeds this value in seconds (this likely means the job is hanging due to timing issues in the program or gateware)

8
delay_per_shot

delay time (in seconds) per single shot of the circuit

required

Returns:

Name Type Description
dict Dict

Complex IQ shots for each accbuf in chanlist; each array has shape (n_total_shots, reads_per_shot)

Source code in qubic/rpc_client.py
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
def run_circuit(self, n_total_shots: int, reads_per_shot: int | dict = 1, timeout_per_shot: float = 8) -> Dict:
    """
    Run the currently loaded program and acquire integrated IQ shots. Program is
    run `n_total_shots` times, in batches of size `shots_per_run` (i.e. `shots_per_run` runs of the program
    are executed in logic before each readback/restart cycle). The current gateware 
    is limited to ~1000 reads in its IQ buffer, which generally means 
    shots_per_run = 1000//reads_per_shot

    Parameters
    ----------
    n_total_shots: int
        number of shots to run. Program is restarted from the beginning 
        for each new shot
    reads_per_shot: int | dict
        number of values per shot per channel to read back from accbuf. If `dict`, indexed
        by `str(channel_number)` (same indices as `raw_asm_list`). If `int`, assumed to be 
        the same across channels. Unless multiple circuits were rastered pre-compilation or 
        there is mid-circuit measurement involved this is typically 1
    timeout_per_shot: float
        job will time out if time to take a single shot exceeds this value in seconds 
        (this likely means the job is hanging due to timing issues in the program or gateware)
    delay_per_shot: float (WILL BE REMOVED)
        delay time (in seconds) per single shot of the circuit

    Returns
    -------
    dict:
        Complex IQ shots for each accbuf in chanlist; each array has 
        shape `(n_total_shots, reads_per_shot)`
    """
    s11 = self.proxy.run_circuit(n_total_shots, reads_per_shot, float(timeout_per_shot), True)

    if isinstance(reads_per_shot, int):
        reads_per_shot = {chan: reads_per_shot for chan in s11.keys()}

    for ch in s11.keys():
        s11[ch] = np.reshape(np.frombuffer(s11[ch].data, dtype=np.complex128), 
                             (n_total_shots, reads_per_shot[ch]))
    return s11

run_circuit_batch(raw_asm_list, n_total_shots, reads_per_shot=1, timeout_per_shot=8, reload_cmd=True, reload_freq=True, reload_env=True, zero_between_reload=True)

Runs a batch of circuits given by a list of compiled ASM binaries. Each circuit is run n_total_shots times. reads_per_shot, n_total_shots, and delay_per_shot are passed directly into run_circuit, and must be the same for all circuits in the batch. The parameters reload_cmd, reload_freq, reload_env, and zero_between_reload control which of these fields is rewritten circuit-to-circuit (everything is rewritten initially). Leave these all at True (default) for maximum safety, to ensure that QubiC is in a clean state before each run. Depending on the circuits, some of these can be turned off to save time.

Parameters:

Name Type Description Default
raw_asm_list List[Dict]

list of raw_asm binaries to run

required
n_total_shots int

number of shots per circuit

required
reads_per_shot int | Dict

number of values per shot per channel to read back from accbuf. If dict, indexed by str(channel_number) (same indices as raw_asm_list). If int, assumed to be the same across channels. Unless multiple circuits were rastered pre-compilation or there is mid-circuit measurement involved this is typically 1

1
timeout_per_shot float

job will time out if time to take a single shot exceeds this value in seconds (this likely means the job is hanging due to timing issues in the program or gateware)

8
delay_per_shot

delay time (in seconds) per single shot of the circuit

required
reload_cmd bool

if True, reload command buffer between circuits

True
reload_freq bool

if True, reload freq buffer between circuits

True
reload_env bool

if True, reload env buffer between circuits

True

Returns:

Name Type Description
dict Dict

Complex IQ shots for each accbuf in chanlist; each array has shape (len(raw_asm_list), n_total_shots, reads_per_shot)

Source code in qubic/rpc_client.py
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
def run_circuit_batch(self, 
                      raw_asm_list: List[Dict], 
                      n_total_shots: int, 
                      reads_per_shot: int | Dict = 1, 
                      timeout_per_shot: float = 8,
                      reload_cmd: bool = True, 
                      reload_freq: bool = True, 
                      reload_env: bool = True, 
                      zero_between_reload: bool = True) -> Dict:
    """
    Runs a batch of circuits given by a list of compiled ASM binaries. Each circuit is run n_total_shots
    times. `reads_per_shot`, `n_total_shots`, and `delay_per_shot` are passed directly into `run_circuit`, and must
    be the same for all circuits in the batch. The parameters `reload_cmd`, `reload_freq`, `reload_env`, and 
    `zero_between_reload` control which of these fields is rewritten circuit-to-circuit (everything is 
    rewritten initially). Leave these all at `True` (default) for maximum safety, to ensure that QubiC 
    is in a clean state before each run. Depending on the circuits, some of these can be turned off 
    to save time.

    Parameters
    ----------
    raw_asm_list: list
        list of raw_asm binaries to run
    n_total_shots: int
        number of shots per circuit
    reads_per_shot: int | dict
        number of values per shot per channel to read back from accbuf. If `dict`, indexed
        by `str(channel_number)` (same indices as `raw_asm_list`). If `int`, assumed to be 
        the same across channels. Unless multiple circuits were rastered pre-compilation or 
        there is mid-circuit measurement involved this is typically 1
    timeout_per_shot: float
        job will time out if time to take a single shot exceeds this value in seconds 
        (this likely means the job is hanging due to timing issues in the program or gateware)
    delay_per_shot: float (WILL BE REMOVED)
        delay time (in seconds) per single shot of the circuit
    reload_cmd: bool
        if True, reload command buffer between circuits
    reload_freq: bool
        if True, reload freq buffer between circuits
    reload_env: bool
        if True, reload env buffer between circuits
    Returns
    -------
    dict:
        Complex IQ shots for each accbuf in chanlist; each array has 
        shape `(len(raw_asm_list), n_total_shots, reads_per_shot)`
    """
    # TODO: consider throwing some version of all the args here into a BatchedCircuitRun or somesuch
    # object
    channels = set().union(*list(set(prog.keys()) for prog in raw_asm_list)) # union of all proc channels in batch
    if isinstance(reads_per_shot, int):
        reads_per_shot = {chan: reads_per_shot for chan in channels}

    s11 = self.proxy.run_circuit_batch(raw_asm_list, n_total_shots, reads_per_shot, float(timeout_per_shot),
                                 reload_cmd, reload_freq, reload_env, zero_between_reload)
    for ch in s11.keys():
        s11[ch] = np.reshape(np.frombuffer(s11[ch].data, dtype=np.complex128), 
                             (len(raw_asm_list), n_total_shots, reads_per_shot[ch]))
    return s11