示例#1
0
def gen_cnf(filename, graph):
    """
    Generate QuickBB input file for the graph.
    This function ALWAYS expects a simple Graph (not MultiGraph)
    without self loops,
    because QuickBB does not understand these situations.

    Parameters
    ----------
    filename : str
           Output file name
    graph : networkx.Graph
           Undirected graphical model
    """
    v = graph.number_of_nodes()
    e = graph.number_of_edges()
    log.info(f"generating config file {filename}")
    cnf = "c a configuration of -qtree simulator\n"
    cnf += f"p cnf {v} {e}\n"

    # Convert possible MultiGraph to Graph (avoid repeated edges)
    for edge in graph.edges():
        u, v = edge
        # print only if this is not a self-loop
        # Node numbering in QuickBB is 1-based
        if u != v:
            cnf += '{} {} 0\n'.format(int(u) + 1, int(v) + 1)

    # print("cnf file:",cnf)
    with open(filename, 'w+') as fp:
        fp.write(cnf)
示例#2
0
def run_quickbb(cnffile,
                command=defs.QUICKBB_COMMAND,
                outfile='output/quickbb_out.qbb',
                statfile='output/quickbb_stat.qbb',
                extra_args=" --min-fill-ordering --time 60 "):
    """
    Run QuickBB program and collect its output

    Parameters
    ----------
    cnffile : str
         Path to the QuickBB input file
    command : str, optional
         QuickBB command name
    outfile : str, optional
         QuickBB output file
    statfile : str, optional
         QuickBB stat file
    extra_args : str, optional
         Optional commands to QuickBB. Default: --min-fill-ordering --time 60
    Returns
    -------
    output : str
         Process output
    """
    # try:
    #     os.remove(outfile)
    #     os.remove(statfile)
    # except FileNotFoundError as e:
    #     log.warn(e)
    #     pass
    sh = command + " "
    # this makes Docker process too slow and sometimes fails
    # sh += f"--outfile {outfile} --statfile {statfile} "
    sh += f"--cnffile {cnffile} "
    if extra_args is not None:
        sh += extra_args

    log.info("excecuting quickbb: " + sh)
    process = subprocess.Popen(sh.split(), stdout=subprocess.PIPE)
    output, error = process.communicate()
    if error:
        log.error(error)
    # log.info(output)
    # with open(outfile, 'r') as fp:
    #     log.info("OUTPUT:\n"+fp.read())
    # with open(statfile, 'r') as fp:
    #     log.info("STAT:\n"+fp.read())

    return output
示例#3
0
def make_clique_on(old_graph, clique_nodes, name_prefix='C'):
    """
    Adds a clique on the specified indices. No checks is
    done whether some edges exist in the clique. The name
    of the clique is formed from the name_prefix and the
    lowest element in the clique_nodes

    Parameters
    ----------
    graph : networkx.Graph or networkx.MultiGraph
            graph to modify
    clique_nodes : list
            list of nodes to include into clique
    name_prefix : str
            prefix for the clique name
    Returns
    -------
    new_graph : type(graph)
            New graph with clique
    """
    clique_nodes = tuple(int(var) for var in clique_nodes)
    graph = copy.deepcopy(old_graph)

    if len(clique_nodes) == 0:
        return graph

    edges = [
        tuple(sorted(edge))
        for edge in itertools.combinations(clique_nodes, 2)
    ]
    node_idx = min(clique_nodes)
    graph.add_edges_from(edges,
                         tensor={
                             'name': name_prefix + f'{node_idx}',
                             'indices': clique_nodes,
                             'data_key': None
                         })
    clique_size = len(clique_nodes)
    log.info(f"Clique of size {clique_size} on vertices: {clique_nodes}")

    return graph
示例#4
0
def get_amplitudes_from_cirq(filename, initial_state=0):
    """
    Calculates amplitudes for a circuit in file filename using Cirq
    """
    import cirq
    n_qubits, circuit = ops.read_circuit_file(filename)

    cirq_circuit = cirq.Circuit()

    for layer in circuit:
        cirq_circuit.append(op.to_cirq_1d_circ_op() for op in layer)

    print("Circuit:")
    print(cirq_circuit)
    simulator = cirq.Simulator()

    result = simulator.simulate(cirq_circuit, initial_state=initial_state)
    log.info("Simulation completed\n")

    # Cirq for some reason computes all amplitudes with phase -1j
    return result.final_state_vector
示例#5
0
def run_quickbb(cnffile,
                wait_time=60,
                command=defs.QUICKBB_COMMAND,
                cwd=None,
                extra_args=" --min-fill-ordering"):
    """
    Run QuickBB program and collect its output

    Parameters
    ----------
    cnffile : str
         Path to the QuickBB input file
    wait_time : int, default 60
    command : str, optional
         QuickBB command name
    cwd : str, default None
         Current work directory
    extra_args : str, optional
         Optional commands to QuickBB.
         Default: --min-fill-ordering --time 60
    Returns
    -------
    output : str
         Process output
    """
    if command is None:
        raise ValueError('No QuickBB command given.'
                         ' Did you install QuickBB?')
    sh = command + f" --time {int(wait_time)} "
    sh += f"--cnffile {cnffile} "
    if extra_args is not None:
        sh += extra_args

    log.info("excecuting quickbb: " + sh)
    process = subprocess.Popen(sh.split(), stdout=subprocess.PIPE, cwd=cwd)
    output, error = process.communicate()
    if error:
        log.error(error)

    return output
示例#6
0
文件: splitters.py 项目: qbit-/qtree
def split_graph_random(old_graph, n_var_parallel=0):
    """
    Splits a graphical model with randomly chosen nodes
    to parallelize over.

    Parameters
    ----------
    old_graph : networkx.Graph
                graph to contract (after eliminating variables which
                are parallelized over)
    n_var_parallel : int
                number of variables to eliminate by parallelization

    Returns
    -------
    idx_parallel : list of Idx
          variables removed by parallelization
    graph : networkx.Graph
          new graph without parallelized variables
    """
    graph = copy.deepcopy(old_graph)

    indices = [var for var in graph.nodes(data=False)]
    idx_parallel = np.random.choice(
        indices, size=n_var_parallel, replace=False)

    idx_parallel_var = [Var(var, size=graph.nodes[var])
                        for var in idx_parallel]

    for idx in idx_parallel:
        remove_node(graph, idx)

    log.info("Removed indices by parallelization:\n{}".format(idx_parallel))
    log.info("Removed {} variables".format(len(idx_parallel)))
    peo, treewidth = get_peo(graph)

    return sorted(idx_parallel_var, key=int), graph
示例#7
0
def read_circuit_stream(stream, max_depth=None):
    """
    Read circuit file and return quantum circuit in the
    form of a list of lists

    Parameters
    ----------
    filename : str
             circuit file in the format of Sergio Boixo
    max_depth : int
             maximal depth of gates to read

    Returns
    -------
    qubit_count : int
            number of qubits in the circuit
    circuit : list of lists
            quantum circuit as a list of layers of gates
    """

    operation_search_patt = r'(?P<operation>' + r'|'.join(
        LABEL_TO_GATE_DICT.keys()) + r')(?P<qubits>( \d+(?!\.))+)'
    params_search_patt_1 = r'(?P<operation>' + r'|'.join(
        LABEL_TO_GATE_DICT.keys(
        )) + r')(?P<qubits>( \d+)+)\ (?P<alpha>-?\d+\.\d+)$'
    params_search_patt_2 = r'(?P<operation>' + r'|'.join(
        LABEL_TO_GATE_DICT.keys()
    ) + r')(?P<qubits>( \d+)+)\ (?P<alpha>-?\d+\.\d+) (?P<beta>-?\d+\.\d+)$'

    circuit = []
    circuit_layer = []

    qubit_count = int(stream.readline())
    log.info("There are {:d} qubits in circuit".format(qubit_count))
    n_ignored_layers = 0
    current_layer = 0

    for idx, line in enumerate(stream):
        if not line or line == '\n':
            break

        m = re.search(r'(?P<layer>[0-9]+) (?=[a-z])', line)
        if m is None:
            raise Exception("file format error at line {}: {}".format(
                idx, line))
        # Read circuit layer by layer
        layer_num = int(m.group('layer'))

        if max_depth is not None and layer_num > max_depth:
            n_ignored_layers = layer_num - max_depth
            continue

        if layer_num > current_layer:
            circuit.append(circuit_layer)
            circuit_layer = []
            current_layer = layer_num

        op_str = line[m.end():]
        m = re.search(operation_search_patt, op_str)
        if m is None:
            raise Exception("file format error in {}".format(op_str))

        op_identif = m.group('operation')

        q_idx = tuple(int(qq) for qq in m.group('qubits').split())
        op_cls = LABEL_TO_GATE_DICT[op_identif]
        # A conditional on using simplification of fsim gate.
        if op_identif == 'fs':
            if False:
                circuit_layer.append(cZ(*q_idx))
                circuit_layer.append(H(q_idx[0]))
                circuit_layer.append(H(q_idx[1]))
                circuit_layer.append(cZ(*q_idx))
            else:
                circuit_layer.append(cZ(*q_idx))
                circuit_layer.append(SWAP(*q_idx))
        else:
            if issubclass(op_cls, ParametricGate):
                if op_cls.parameter_count == 1:
                    m = re.search(params_search_patt_1, op_str)
                    if m is None:
                        raise Exception(
                            f'Could not find parameter for gate. `{line})')
                    q_idx = tuple(int(qq) for qq in m.group('qubits').split())
                    alpha = m.group('alpha')
                    try:
                        op = op_cls(*q_idx, alpha=float(alpha))
                    except:
                        print('Error in creating gate for line', line)
                        raise
                elif op_cls.parameter_count == 2:
                    m = re.search(params_search_patt_2, op_str)
                    if m is None:
                        raise Exception(
                            f'Could not find parameter for gate. `{line})')
                    q_idx = tuple(int(qq) for qq in m.group('qubits').split())
                    alpha = m.group('alpha')
                    beta = m.group('beta')
                    op = op_cls(*q_idx, alpha=float(alpha), beta=float(beta))
            else:
                op = op_cls(*q_idx)
            circuit_layer.append(op)

    circuit.append(circuit_layer)  # last layer

    if n_ignored_layers > 0:
        log.info("Ignored {} layers".format(n_ignored_layers))

    return qubit_count, circuit
示例#8
0
def read_circuit_file(filename, max_depth=None):
    log.info("reading file {}".format(filename))
    with open(filename, "r") as fp:
        n, qc = read_circuit_stream(fp, max_depth=max_depth)
    return n, qc
示例#9
0
def circ2graph(qubit_count,
               circuit,
               pdict={},
               max_depth=None,
               omit_terminals=True):
    """
    Constructs a graph from a circuit in the form of a
    list of lists.

    Parameters
    ----------
    qubit_count : int
            number of qubits in the circuit
    circuit : list of lists
            quantum circuit as returned by
            :py:meth:`operators.read_circuit_file`
    pdict : dict
            Dictionary with placeholders if any parameteric gates
            were unresolved
    max_depth : int, default None
            Maximal depth of the circuit which should be used
    omit_terminals : bool, default True
            If terminal nodes should be excluded from the final
            graph.

    Returns
    -------
    graph : networkx.MultiGraph
            Graph which corresponds to the circuit
    data_dict : dict
            Dictionary with all tensor data
    """
    import functools
    import qtree.operators as ops

    if max_depth is None:
        max_depth = len(circuit)

    data_dict = {}

    # Let's build the graph.
    # The circuit is built from left to right, as it operates
    # on the ket ( |0> ) from the left. We thus first place
    # the bra ( <x| ) and then put gates in the reverse order

    # Fill the variable `frame`
    layer_variables = list(range(qubit_count))
    current_var_idx = qubit_count

    # Initialize the graph
    graph = nx.MultiGraph()

    # Populate nodes and save variables of the bra
    bra_variables = []
    for var in layer_variables:
        graph.add_node(var, name=f'o_{var}', size=2)
        bra_variables.append(Var(var, name=f"o_{var}"))

    # Place safeguard measurement circuits before and after
    # the circuit
    measurement_circ = [[ops.M(qubit) for qubit in range(qubit_count)]]

    combined_circ = functools.reduce(
        lambda x, y: itertools.chain(x, y),
        [measurement_circ, reversed(circuit[:max_depth])])

    # Start building the graph in reverse order
    for layer in combined_circ:
        for op in layer:
            # build the indices of the gate. If gate
            # changes the basis of a qubit, a new variable
            # has to be introduced and current_var_idx is increased.
            # The order of indices
            # is always (a_new, a, b_new, b, ...), as
            # this is how gate tensors are chosen to be stored
            variables = []
            current_var_idx_copy = current_var_idx
            for qubit in op.qubits:
                if qubit in op.changed_qubits:
                    variables.extend(
                        [layer_variables[qubit], current_var_idx_copy])
                    graph.add_node(current_var_idx_copy,
                                   name='v_{}'.format(current_var_idx_copy),
                                   size=2)
                    current_var_idx_copy += 1
                else:
                    variables.extend([layer_variables[qubit]])

            # Form a tensor and add a clique to the graph
            # fill placeholders in gate's parameters if any
            for par, value in op.parameters.items():
                if isinstance(value, ops.placeholder):
                    op._parameters[par] = pdict[value]

            data_key = hash((op.name, tuple(op.parameters.items())))
            tensor = {
                'name': op.name,
                'indices': tuple(variables),
                'data_key': data_key
            }

            # Insert tensor data into data dict
            data_dict[data_key] = op.gen_tensor()

            if len(variables) > 1:
                edges = itertools.combinations(variables, 2)
            else:
                edges = [(variables[0], variables[0])]

            graph.add_edges_from(edges, tensor=tensor)

            # Update current variable frame
            for qubit in op.changed_qubits:
                layer_variables[qubit] = current_var_idx
                current_var_idx += 1

    # Finally go over the qubits, append measurement gates
    # and collect ket variables
    ket_variables = []

    op = ops.M(0)  # create a single measurement gate object

    for qubit in range(qubit_count):
        var = layer_variables[qubit]
        new_var = current_var_idx

        ket_variables.append(Var(new_var, name=f'i_{qubit}', size=2))
        # update graph and variable `frame`
        graph.add_node(new_var, name=f'i_{qubit}', size=2)
        data_key = hash((op.name, tuple(op.parameters.items())))
        tensor = {
            'name': op.name,
            'indices': (var, new_var),
            'data_key': data_key
        }

        graph.add_edge(var, new_var, tensor=tensor)
        layer_variables[qubit] = new_var
        current_var_idx += 1

    if omit_terminals:
        graph.remove_nodes_from(tuple(map(int, bra_variables + ket_variables)))

    v = graph.number_of_nodes()
    e = graph.number_of_edges()

    log.info(f"Generated graph with {v} nodes and {e} edges")
    # log.info(f"last index contains from {layer_variables}")

    return graph, data_dict, bra_variables, ket_variables
示例#10
0
def eval_circuit(n_qubits, circuit, final_state,
                 initial_state=0, measured_final=None,
                 measured_initial=None, pdict={}):
    """
    Evaluate a circuit with specified initial and final states.

    Parameters
    ----------
    n_qubits: int
              Number of qubits in the circuit
    circuit: list of lists
             List of lists of gates
    final_state: int
             Values of measured qubits at the end of the circuit (bra).
             Bitwise coded with the length of
             min(n_qubits, len(measured_final)
    initial_state: int
             Values of the measured qubits at the beginning of the
             circuit (ket). Bitwise coded with the length of
             min(n_qubits, len(measured_initial)
    measured_final: list, default None
             Iterable with the positions of qubits which are measured. If
             not all qubits are measured, then a subset of amplitudes will
             be evaluated
    measured_initial: list, default None
             Iterable with the positions of qubits which are measured
             initially. If not all are measured, then the resulting
             amplitudes will be evaluated for a subset of initial states.
    pdict: dict, default {}
    Returns
    -------
    amplitudes: numpy.array
    """
    # Check which qubits are measured
    all_qubits = set(range(n_qubits))
    if measured_final is None:
        measured_final = tuple(range(n_qubits))
    else:
        if not set(measured_final).issubset(all_qubits):
            raise ValueError(f'measured_final qubits outside allowed'
                             f' range: {measured_final}')

    if measured_initial is None:
        measured_initial = tuple(range(n_qubits))
    else:
        if not set(measured_initial).issubset(all_qubits):
            raise ValueError(f'measured_initial qubits outside allowed'
                             f' range: {measured_initial}')

    # Prepare graphical model
    buckets, data_dict, bra_vars, ket_vars = opt.circ2buckets(
        n_qubits, circuit, pdict=pdict)

    # Collect free qubit variables
    free_final = sorted(all_qubits - set(measured_final))
    free_bra_vars = [bra_vars[idx] for idx in free_final]
    bra_vars = [bra_vars[idx] for idx in measured_final]

    free_initial = sorted(all_qubits - set(measured_initial))
    free_ket_vars = [ket_vars[idx] for idx in free_initial]
    ket_vars = [ket_vars[idx] for idx in measured_initial]

    if len(free_bra_vars) > 0:
        log.info('Evaluate amplitudes over all final states of qubits:')
        log.info(f'{free_final}')

    if len(free_ket_vars) > 0:
        log.info('Evaluate amplitudes over all initial states of qubits:')
        log.info(f'{free_initial}')

    graph_initial = gm.buckets2graph(
        buckets,
        ignore_variables=bra_vars+ket_vars)

    graph = gm.make_clique_on(graph_initial, free_bra_vars+free_ket_vars)

    # Get PEO
    peo_initial, treewidth = gm.get_peo(graph)

    # transform peo so free_bra_vars and free_ket_vars are at the end
    # this fixes the layout of the tensor
    peo = gm.get_equivalent_peo(graph, peo_initial,
                                free_bra_vars+free_ket_vars)

    # place bra and ket variables to beginning, so these variables
    # will be contracted first
    perm_buckets, perm_dict = opt.reorder_buckets(
        buckets, bra_vars + ket_vars + peo)
    perm_graph, _ = gm.relabel_graph_nodes(
        graph, perm_dict)

    # extract bra and ket variables from variable list and sort according
    # to qubit order
    ket_vars = sorted([perm_dict[idx] for idx in ket_vars], key=str)
    bra_vars = sorted([perm_dict[idx] for idx in bra_vars], key=str)

    # make proper slice dictionaries. We choose ket = |0>,
    # bra = |0> on fixed entries
    slice_dict = utils.slice_from_bits(initial_state, ket_vars)
    slice_dict.update(utils.slice_from_bits(final_state, bra_vars))
    slice_dict.update({var: slice(None) for var in free_bra_vars})
    slice_dict.update({var: slice(None) for var in free_ket_vars})

    # Finally make numpy buckets and calculate
    sliced_buckets = npfr.get_sliced_np_buckets(
        perm_buckets, data_dict, slice_dict)
    result = opt.bucket_elimination(
        sliced_buckets, npfr.process_bucket_np,
        n_var_nosum=len(free_bra_vars+free_ket_vars))

    return result.data
示例#11
0
文件: splitters.py 项目: qbit-/qtree
def split_graph_by_metric(
        old_graph, n_var_parallel=0,
        metric_fn=get_node_by_degree,
        forbidden_nodes=()):
    """
    Parallel-splitted version of :py:meth:`get_peo` with nodes
    to split chosen according to the metric function. Metric
    function should take a graph and return a list of pairs
    (node : metric_value)

    Parameters
    ----------
    old_graph : networkx.Graph or networkx.MultiGraph
                graph to split by parallelizing over variables
                and to contract

                Parallel edges and self-loops in the graph are
                removed (if any) before the calculation of metric

    n_var_parallel : int
                number of variables to eliminate by parallelization
    metric_fn : function, optional
                function to evaluate node metric.
                Default get_node_by_degree
    forbidden_nodes : iterable, optional
                nodes in this list will not be considered
                for deletion. Default ().
    Returns
    -------
    idx_parallel : list
          variables removed by parallelization
    graph : networkx.Graph
          new graph without parallelized variables
    """
    # graph = get_simple_graph(old_graph)
    # import pdb
    # pdb.set_trace()
    graph = copy.deepcopy(old_graph)

    # convert everything to int
    forbidden_nodes = [int(var) for var in forbidden_nodes]

    # get nodes by metric in descending order
    nodes_by_metric = metric_fn(graph)
    nodes_by_metric.sort(key=lambda pair: int(pair[1]), reverse=True)

    nodes_by_metric_allowed = []
    for node, metric in nodes_by_metric:
        if node not in forbidden_nodes:
            nodes_by_metric_allowed.append((node, metric))

    idx_parallel = []
    for ii in range(n_var_parallel):
        node, metric = nodes_by_metric_allowed[ii]
        idx_parallel.append(node)

    # create var objects from nodes
    idx_parallel_var = [Var(var, size=graph.nodes[var]['size'])
                        for var in idx_parallel]

    for idx in idx_parallel:
        remove_node(graph, idx)

    log.info("Removed indices by parallelization:\n{}".format(idx_parallel))
    log.info("Removed {} variables".format(len(idx_parallel)))

    return idx_parallel_var, graph
示例#12
0
def read_circuit_file(filename, max_depth=None):
    """
    Read circuit file and return quantum circuit in the
    form of a list of lists

    Parameters
    ----------
    filename : str
             circuit file in the format of Sergio Boixo
    max_depth : int
             maximal depth of gates to read

    Returns
    -------
    qubit_count : int
            number of qubits in the circuit
    circuit : list of lists
            quantum circuit as a list of layers of gates
    """
    label_to_gate_dict = {
        'i': I,
        'h': H,
        't': T,
        'z': Z,
        'cz': cZ,
        'x': X,
        'y': Y,
        'x_1_2': X_1_2,
        'y_1_2': Y_1_2,
    }

    operation_search_patt = r'(?P<operation>' + r'|'.join(
        label_to_gate_dict.keys()) + r')(?P<qubits>( \d+)+)'

    log.info("reading file {}".format(filename))
    circuit = []
    circuit_layer = []

    with open(filename, "r") as fp:
        qubit_count = int(fp.readline())
        log.info("There are {:d} qubits in circuit".format(qubit_count))
        n_ignored_layers = 0
        current_layer = 0

        for idx, line in enumerate(fp):
            m = re.search(r'(?P<layer>[0-9]+) (?=[a-z])', line)
            if m is None:
                raise Exception("file format error at line {}".format(idx))
            # Read circuit layer by layer
            layer_num = int(m.group('layer'))

            if max_depth is not None and layer_num > max_depth:
                n_ignored_layers = layer_num - max_depth
                continue

            if layer_num > current_layer:
                circuit.append(circuit_layer)
                circuit_layer = []
                current_layer = layer_num

            op_str = line[m.end():]
            m = re.search(operation_search_patt, op_str)
            if m is None:
                raise Exception("file format error in {}".format(op_str))

            op_identif = m.group('operation')

            q_idx = tuple(int(qq) for qq in m.group('qubits').split())

            op = label_to_gate_dict[op_identif](*q_idx)
            circuit_layer.append(op)

        circuit.append(circuit_layer)  # last layer

        if n_ignored_layers > 0:
            log.info("Ignored {} layers".format(n_ignored_layers))

    return qubit_count, circuit