Skip to content

qubic.job_rpc_server

Functions for running an RPC job server on a workstation that is on the same LAN as the ZCU216. Serves as an intermediary between the client (user) machine and the QubiC board. See the Getting Started Guide for details on how to configure.

SoCClient

Connect to the SoC via RPC client to run single circuits; expose interface for batched circuit running and single-circuit ADC trace acquisition

Source code in qubic/job_rpc_server.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
class SoCClient:
    """
    Connect to the SoC via RPC client to run single circuits; expose interface for batched 
    circuit running and single-circuit ADC trace acquisition
    """

    def __init__(self, soc_ip, soc_port):
        self.soc_ip = soc_ip
        self.soc_port = soc_port

    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 raw_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.

        TODO: consider throwing some version of all the args here into a BatchedCircuitRun or somesuch
        object

        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
                number of values per shot per channel to read back from accbuf. Unless
                there is mid-circuit measurement involved this is typically 1
            timeout_per_shot : float
                maximum allowable wall clock 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)
        """
        channels = set().union(*list(set(prog.keys()) for prog in raw_asm_list)) # union of all proc channels in batch
        self.proxy = xmlrpc.client.ServerProxy('http://' + self.soc_ip + ':' + str(self.soc_port), allow_none=True)
        s11 = {ch: bytes() for ch in channels}
        #TODO: using the channels in the first raw_asm_list elem is hacky, should figure out
        # a better way to initialize
        for i, raw_asm in enumerate(raw_asm_list):
            logging.getLogger(__name__).info(f'starting circuit {i}/{len(raw_asm_list)-1}')
            if i==0:
                self.proxy.load_circuit(raw_asm, True, True, True, True)
            else:
                self.proxy.load_circuit(raw_asm, zero_between_reload, reload_cmd, reload_freq, reload_env)

            s11_i = self.proxy.run_circuit(n_total_shots, reads_per_shot, timeout_per_shot, True)
            for ch in s11_i.keys():
                s11[ch] += s11_i[ch].data

        logging.getLogger(__name__).info('batch finished')
        return s11

    def load_and_run_acq(self, raw_asm_prog, n_total_shots=1, nsamples=8192, acq_chans=['0'], 
                        trig_delay=0, decimator=0, return_acc=False, from_server=True):
        self.proxy = xmlrpc.client.ServerProxy('http://' + self.soc_ip + ':' + str(self.soc_port), allow_none=True)
        return self.proxy.load_and_run_acq(raw_asm_prog, n_total_shots, nsamples, acq_chans, trig_delay, decimator, return_acc, True)

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 raw_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.

TODO: consider throwing some version of all the args here into a BatchedCircuitRun or somesuch object

Returns:

Type Description
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/job_rpc_server.py
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
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 raw_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.

    TODO: consider throwing some version of all the args here into a BatchedCircuitRun or somesuch
    object

    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
            number of values per shot per channel to read back from accbuf. Unless
            there is mid-circuit measurement involved this is typically 1
        timeout_per_shot : float
            maximum allowable wall clock 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)
    """
    channels = set().union(*list(set(prog.keys()) for prog in raw_asm_list)) # union of all proc channels in batch
    self.proxy = xmlrpc.client.ServerProxy('http://' + self.soc_ip + ':' + str(self.soc_port), allow_none=True)
    s11 = {ch: bytes() for ch in channels}
    #TODO: using the channels in the first raw_asm_list elem is hacky, should figure out
    # a better way to initialize
    for i, raw_asm in enumerate(raw_asm_list):
        logging.getLogger(__name__).info(f'starting circuit {i}/{len(raw_asm_list)-1}')
        if i==0:
            self.proxy.load_circuit(raw_asm, True, True, True, True)
        else:
            self.proxy.load_circuit(raw_asm, zero_between_reload, reload_cmd, reload_freq, reload_env)

        s11_i = self.proxy.run_circuit(n_total_shots, reads_per_shot, timeout_per_shot, True)
        for ch in s11_i.keys():
            s11[ch] += s11_i[ch].data

    logging.getLogger(__name__).info('batch finished')
    return s11

run_job_server(host_ip, host_port, soc_ip, soc_port)

Run the job server.

Source code in qubic/job_rpc_server.py
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
def run_job_server(host_ip: str, host_port: int, soc_ip: str, soc_port: int):
    """
    Run the job server.

    Parameters
    ----------
        host_ip: str
        host_port: int
        soc_ip: str
        soc_port: int
    """ 
    soc_client = SoCClient(soc_ip, soc_port)
    job_server = xmlrpc.server.SimpleXMLRPCServer((host_ip, host_port), logRequests=True, allow_none=True)
    job_server.register_function(soc_client.run_circuit_batch)
    job_server.register_function(soc_client.load_and_run_acq)

    print('connected to client {}:{}'.format(soc_ip, soc_port))
    print('RPC job server running on {}:{}'.format(host_ip, host_port))
    job_server.serve_forever()