示例#1
0
    def update(self, Q, from_default=True, **kwargs):
        """
        Updates the attributes of a given qnode
        :param qnode: Node to be updated
        :param kwargs: Keyword arguements for updating the qnode
        :return: None

        """
        if from_default is True:
            # Set attributes of node to default
            class_name = self.__class__
            dummy_node = class_name(Q, name=self.name)
            for arg in self.__dict__:
                setattr(self, arg, dummy_node.__dict__[arg])

        # For all keyword arguments, check if it exists in the Qnode attributes. If it does, update it.
        args_to_cull = []
        for arg in kwargs:
            if arg in self.__dict__:
                setattr(self, arg, kwargs[arg])
                args_to_cull.append(arg)

        # Remove the kwargs that were used
        for arg in args_to_cull:
            kwargs.pop(arg)

        # Update node.costs with remaining kwargs
        for item in kwargs:
            print(kwargs)
        cost_vector = QNET.make_cost_vector(Q, **kwargs)
        self.costs = cost_vector
        
        # Update node.memory with remaining kwargs
        memory_vector = QNET.make_memory_vector(Q, **kwargs)
        self.memory = memory_vector
示例#2
0
    def add_memory_qchan(self, edge=None, **kwargs):
        """
        Add a temporal Qchan to the graph to show time-connection of nodes with quantum memory.

        The only difference with add_qchan() is that if either of the node types are Satellites, the airCosts
        will be not be updated since the memory costs are inherent properties of the nodes themselves and
        hence, are time independent.

        :param list edge: Array-like object of two qnodes to be connected
        :param float kwargs: Other costs or qualifying attributes
        :return: None
        """

        # Assert edge is valid
        assert (edge !=
                None), "\'edge\' must be an array-like object of two qnodes"
        assert (len(edge) == 2
                ), "\'edge\' must be an array-like object of two qnodes"

        u = self.getNode(str(edge[0]))
        v = self.getNode(str(edge[1]))

        if u is None:
            u = QNET.Ground(self, name=str(edge[0]))
            self.add_node(u)
        if v is None:
            v = QNET.Ground(self, name=str(edge[1]))
            self.add_node(v)

        cost_vector = QNET.make_cost_vector(self, **kwargs)
        self.add_edge(u, v, **cost_vector)
示例#3
0
    def __init__(self, Q, name=None, coords=None, isMemory=False, **kwargs):
        """
        Qnode Initialization
        :param Q: The Qnet graph intended for the node.
        :param name: The name of the Qnode.
        :param coords: Cartesian coordinates. Usage: [x,y,z]
        :param isMemory: Boolean to describe if the node has quantum memory or not
        :param kwargs: Costs that are valid for the Qnet graph Q.
        """
        if name is None:
            name = "None"
        if coords is None:
            coords = [0]*3
        else:
            assert (len(coords) == 3), "Usage: [x, y, z]"

        # Initialize cost vector
        cost_vector = QNET.make_cost_vector(Q, **kwargs)
        
        # Initialize memory cost vector
        memory_vector = QNET.make_memory_vector(Q, **kwargs)

        self.name = name
        self.coords = coords
        self.costs = cost_vector
        self.memory = memory_vector
        self.isMemory = isMemory
def best_costs(P=None, head=None, tail=None):
    # Default cost_array
    if None in [P, head, tail]:
        return {'e': 0, 'f': 0}
    else:
        e = QNET.best_path_cost(P, head, tail, cost_type='e')
        f = QNET.best_path_cost(P, head, tail, cost_type='f')
    cost_vector = {'e': e, 'f': f}
    return cost_vector
示例#5
0
    def add_qchan(self, edge=None, key=None, **kwargs):
        """
        Add a Qchan to the graph.

        If either of the node types are Satellites, the costs of the edge will be automatically calculated with
        the Node class method "airCost"

        Parameters
        ----------
        edge: (qnode, qnode)
            A pair of nodes to be connected. Order does not matter.

        key: int
            Optional
            A key specifying a specific edge

        kwargs:
            Key word arguments for the edge cost

        Returns
        -------
        None

        Warnings
        --------
        Additional costs specified in **kwargs that are not in self.cost_vector will not be added. This is because
        such costs do not have an associated range or additive conversion method.
        """
        # Assert edge is valid
        assert (edge !=
                None), "\'edge\' must be an array-like object of two qnodes"
        assert (len(edge) == 2
                ), "\'edge\' must be an array-like object of two qnodes"

        u = self.getNode(str(edge[0]))
        v = self.getNode(str(edge[1]))

        if u is None:
            u = QNET.Ground(self, name=str(edge[0]))
            self.add_node(u)
        if v is None:
            v = QNET.Ground(self, name=str(edge[1]))
            self.add_node(v)

        # If either of the nodes are satellites, get air costs
        if isinstance(u, QNET.Satellite):
            e, f = u.airCost(v)
            kwargs.update({'e': e, 'f': f})
        elif isinstance(v, QNET.Satellite):
            e, f = v.airCost(u)
            kwargs.update({'e': e, 'f': f})

        # Pass it through make_cost_vector to ensure that
        cost_vector = QNET.make_cost_vector(self, **kwargs)
        self.add_edge(u, v, key=key, **cost_vector)
    def picky_path(Q, node_list, cost_type):
        """
        Given a list of nodes for the shortest path in cost_type, this function returns a QNET.Path object specifying
        the shortest path.

        Since Qnet is a multi-graph, a list of nodes is insufficient to specify the true shortest path. What picky_path
        does is find the edges in the list of nodes that minimizes cost_type and then create a QNET.Path object with
        this information contained.

        Parameters
        ----------
        Q: Qnet()
        node_list: [Qnode()]
            list of nodes in the shortest path
        cost_type: str
            Any valid cost from the cost vector

        Returns
        -------
        Path()
        """
        edge_keys = []
        path_len = len(node_list)
        i = 0
        while i < path_len - 1:
            cur = node_list[i]
            nxt = node_list[i + 1]
            edge_num = Q.number_of_edges(cur, nxt)
            edge_list = [Q.edges[cur, nxt, j] for j in range(edge_num)]
            seq = [e[cost_type] for e in edge_list]
            key = seq.index(max(seq))
            edge_keys.append(key)
            i += 1
        return QNET.Path(Q, node_list, edge_keys)
def plot_paths(Q, tMax, dt):
    """
    Plot the costs of all simple paths over time along with the cost from simple_purify
    :param Q: Qnet Graph
    :param tMax: Maximum time
    :param dt: Size of time increment
    :return: None
    """
    # Get Time Array
    time_arr = QNET.getTimeArr(tMax, dt)

    # Plot the costs of every simple path over time
    path_dict = sim_all_simple(Q, 'A', 'B', tMax, dt)
    for path in path_dict:
        for cost in Q.cost_vector.keys():
            a = []
            for d in path_dict[path]:
                a.append(d[cost])
            plt.plot(time_arr, a, label=f"{str(path)} ({cost})")

    # Purified costs over time:
    pur_arr = sim_protocol(Q, "A", "B", QNET.simple_purify, tMax, dt)
    plot_cv(time_arr, pur_arr, label="Path Purification")

    plt.xlabel('Time')
    plt.ylabel("Path Costs")
    plt.title("Network Path Costs Over Time Between Nodes \"A\" and \"B\"")

    plt.legend()
    plt.show()
def sim_all_simple(G, source, target, tMax, dt, cost_type=None):
    """
    Get the cost arrays for all simple paths over time
    :param G: Qnet Graph
    :type G: Qnet()
    :param source: Qnode
    :param target: Qnode
    :param tMax: Timespan
    :type tMax: float
    :param dt: Time interval
    :type dt: float
    :param cost_type: string, optional
    :return: Dictionary of paths to a list of cost arrays over time
    """
    C = copy.deepcopy(G)

    # get source and target from names
    source = C.getNode(source)
    target = C.getNode(target)

    # Create a generator of all simple paths
    simplePathGen = nx.algorithms.simple_paths.all_simple_paths(
        C, source, target)

    # Unpack paths from generator into array as QNET paths
    path_arr = []
    for path in simplePathGen:
        path_arr.append(QNET.Path(C, path))

    # Assign each path to an empty cost array
    path_dict = {path: [] for path in path_arr}

    # Initialize array size
    size_arr = len(np.arange(0, tMax, dt))
    i = 0
    while i < size_arr:
        j = 0
        while j < len(path_arr):
            # Get the cost of each path and append it to respective array
            if cost_type is None:
                # Fetch all costs in cost vector
                cost = path_arr[j].cost_vector
            else:
                # Fetch specified cost
                cost = path_arr[j].cost_vector[cost_type]
            path_dict[path_arr[j]].append(cost)
            j += 1

        C.update(dt)
        i += 1

    for path in path_arr:
        for node in path.node_array:
            if isinstance(node, QNET.Satellite):
                if node.cartesian is False:
                    node.setTime()

    return path_dict
def purify(Q, source, target):
    """

    This function performs a multi-path entanglement purification between a source and target node using all
    available paths.

    :param Q: Qnet Graph
    :param source: Name of source node
    :param target: Name of target node
    If none, will purify all possible paths
    :param string, optional, method: The method used to do the purification.
    Supported options: "edge_disjoint", "node_disjoint", "total_disjoint", "greedy".
        edge_disjoint: No intersecting edges
        node_disjoint: No intersecting nodes
        total_disjoint: No intersecting edges or nodes
        Other inputs produce a ValueError
    :return: float
    """

    # TODO: Implement a threshold attribute so user doesn't have to iterate through all paths

    def fidTransform(F1, F2):
        return (F1 * F2) / (F1 * F2 + (1 - F1) * (1 - F2))

    # Get paths for Graph
    u = Q.getNode(source)
    v = Q.getNode(target)

    # TODO: Find a better way of producing disjoint paths
    generator = nx.node_disjoint_paths(Q, u, v)

    # Get p values for each path
    f_arr = []
    for path in generator:
        new_path = QNET.Path(Q, path)
        # check if path is valid
        if new_path.is_valid() == True:
            f = new_path.cost_vector['f']
            f_arr.append(f)
        else:
            pass

    assert (len(f_arr) != 0), f"No path exists from {source} to {target}"

    # Initialize purified fidelity as the max fidelity value
    pure_cost = max(f_arr)
    f_arr.remove(pure_cost)

    # Purify fidelities together
    # TODO: Depreciate this code
    while len(f_arr) != 0:
        pmax = max(f_arr)
        pure_cost = fidTransform(pure_cost, pmax)
        f_arr.remove(pmax)

    return pure_cost
示例#10
0
def multidim_lattice(dim, size, e, f, periodic=False):
    dim = [size] * dim
    G = nx.grid_graph(dim, periodic)

    Q = QNET.Qnet()
    for edge in G.edges():
        u = edge[0]
        v = edge[1]
        Q.add_qchan(edge=(u, v), e=e, f=f)
    return Q
示例#11
0
def square_lattice(m, n, efficiency, fidelity):
    G = nx.grid_2d_graph(m, n, periodic=False, create_using=None)
    Q = QNET.Qnet()

    for node in G.nodes():
        Q.add_qnode(name=str(node),
                    qnode_type="Ground",
                    coords=(node[0], node[1], 0))

    for edge in G.edges():
        u = edge[0]
        v = edge[1]
        Q.add_qchan(edge=(u, v), e=efficiency, f=fidelity)
    return Q
示例#12
0
    def add_qnode(self, name=None, qnode_type=None, coords=None, **kwargs):
        """
        Initialize a qnode of some type and add it to the graph

        In the event where a node of the same name is initialised, update the old node with new parameters

        Parameters
        ----------
        name: str
            Node name

        qnode_type: str {'Ground', "Satellite', 'Swapper'}, optional
            Qnode subclass

            (The default is None, which initializes a Qnode of the default type)

        kwargs
            Keyword arguments for qnode initialization

            For details, consult the documentation for the Node class

        Returns
        -------
        None

        Raises
        ------
        AssertionError
            If the qnode_type is invalid
        """
        # Check if node already exists in graph. If yes, update old node
        old_node = self.getNode(name)
        if old_node is not None:
            old_node.update(self, name=name, coords=coords, **kwargs)
        # Else, add new node
        else:
            # If qnode_type is none, initialize a node of the default type
            if qnode_type is None:
                new_node = QNET.Qnode(self, name=name, coords=coords, **kwargs)
            # Else initialize a node of the specified type
            else:
                # Check if type is valid
                assert (
                    qnode_type
                    in typeDict), f"Unsupported qnode type: \'{qnode_type}\'"
                new_node = typeDict[qnode_type](self,
                                                name=name,
                                                coords=coords,
                                                **kwargs)
            self.add_node(new_node)
def data_method(Q, pairs):
    """
    An arbitrary method for measuring the effectiveness of a graph

    For all given communication pairs, find the best path between them in terms of fidelity.
    Average the cost vectors, and return the result.

    Parameters
    ----------
    Q: Qnet
    pairs: list

    Returns
    -------
    dict
        Cost vector
    """
    # If no path exists, return minimum values for efficiency and fidelity
    if Q is None or pairs is None:
        return {"e":0, "f":0.5}

    df = pd.DataFrame()
    for index, pair in enumerate(pairs):
        # Get shortest path between pairs in fidelity
        u, v = pair
        best_path = QNET.best_path(Q, u, v, 'f')

        # Get cost vector, and forget the additive costs
        cost_vector = best_path.cost_vector
        cost_vector = QNET.cv_strip_add(cost_vector)

        # Add the costs to the DataFrame
        df = df.append(pd.Series(cost_vector), ignore_index=True)
    # Convert data frame into single cost vector
    meanie = df.mean()
    return meanie.to_dict()
def sim_optimal_cost(G, source_name, target_name, cost_type, tMax, dt):
    """
    Calculate the costs of the lowest cost path from "source" to "target" over time.
    :param G: Qnet Graph
    :param string source_name: Name of source node
    :param string target_name: Name of target node
    :param string cost_type: The type of cost to optimise over. Choose from {'loss', 'fid'}
    :param float tMax: Time period
    :param float dt: Time increment
    :return: Optimal loss array
    """
    C = copy.deepcopy(G)

    u = C.getNode(source_name)
    v = C.getNode(target_name)

    # Initialize arrays
    cost_arr = []
    size = len(np.arange(0, tMax, dt))

    # Get optimal path cost and append it to costArr
    i = 0
    while i < size:
        cost = QNET.best_path_cost(C, source_name, target_name, cost_type)
        cost_arr.append(cost)
        # Update network
        C.update(dt)
        i += 1
    """ 
    simplePathGen = nx.algorithms.simple_paths.all_simple_paths(C, u, v) 
    pathArr = []
    for path in simplePathGen:
        pathArr.append(QNET.Path(C, path))
    for path in pathArr:
        for node in path.node_array:
            if isinstance(node, QNET.Satellite):
                if node.cartesian is False:
                    node.setTime()
    """

    return cost_arr
def posPlot(Q, u, v, tMax, dt):
    """
    Plot the distance between two nodes over time
    :param Q: QNet Graph
    :param u: Name of Qnode
    :param v: Name of Qnode
    :param tMax: Maximum time
    :param dt: Size of timestep
    :return: None
    """
    C = copy.deepcopy(Q)

    u = C.getNode(u)
    v = C.getNode(v)

    posArr = []
    sizeArr = len(np.arange(0, tMax, dt))

    i = 0
    while i < sizeArr:
        if isinstance(u, QNET.Satellite):
            dist = u.distance(v)
        elif isinstance(v, QNET.Satellite):
            dist = v.distance(u)
        else:
            assert (False)
        posArr.append(dist)
        C.update(dt)
        i += 1
    if isinstance(u, QNET.Satellite):
        u.setTime()
    if isinstance(v, QNET.Satellite):
        v.setTime()

    timeArr = QNET.getTimeArr(tMax, dt)
    plt.plot(timeArr, posArr)
    plt.xlabel("Time (in s)")
    plt.ylabel("Distance (in 10^3 km)")
    plt.title(f"Distance between {u.name} and {v.name}")
    plt.show()
示例#16
0
def hexagonal_lattice(m,
                      n,
                      efficiency,
                      fidelity,
                      periodic=False,
                      with_positions=True):
    """
    Returns the m by n triangular lattice Qnet

    Parameters
    ----------
    m
    n
    efficiency
    fidelity
    periodic
    with_positions
    create_using

    Returns
    -------
    Qnet
    """
    G = nx.generators.hexagonal_lattice_graph(m,
                                              n,
                                              periodic=periodic,
                                              with_positions=with_positions)

    Q = QNET.Qnet()
    for edge in G.edges():
        u = edge[0]
        v = edge[1]
        Q.add_qchan(edge=(u, v), e=efficiency, f=fidelity)

    if with_positions is True:
        for node_name, position in list(G.nodes(data="pos")):
            # Update Qnet nodes with positions
            Q.add_qnode(name=str(node_name),
                        coords=[position[0], position[1], 0])
    return Q
示例#17
0
    def get_cost_vector(self):
        # List of cost vectors for all elements in the path:
        element_cvs = []

        # Get cost vectors of edges
        path_len = len(self.node_array)
        i = 0
        while i < path_len - 1:
            cur = self.node_array[i]
            nxt = self.node_array[i + 1]
            # TODO: Add key key here to make it more clear
            edge_data = self.G.get_edge_data(cur, nxt, self.edge_keys[i])
            element_cvs.append(edge_data)
            i += 1

        # Get cost vectors of nodes
        for node in self.node_array:
            element_cvs.append(node.costs)

        # Cost vectors have identical keys
        # Sum all key elements together, convert additive costs to correct value, update dictionary
        def sum_cost_vectors(cv_list):
            counter = collections.Counter()
            for d in cv_list:
                counter.update(d)
            return dict(counter)

        new_cv = sum_cost_vectors(element_cvs)
        # Convert additive costs into regular costs:
        for cost_type in new_cv:
            if cost_type.startswith("add_"):
                # Get additive cost
                cost = new_cv[cost_type]
                # Remove prefix of cost_type
                cost_type = QNET.remove_prefix(cost_type, "add_")
                # Convert additive cost to normal, add it to new_cv
                new_cv[cost_type] = self.G.conversions[cost_type][1](cost)

        return new_cv
def sim_path(G, path, tMax, dt, cost_type=None):
    """
    Return an array of path costs over time

    Parameters
    ----------
    G: Qnet Graph
    path: An array of node names
    tMax: float
        Timespan of the simulation
    dt: Time increment
    cost_type: string, optional
        (The default value is None, which returns a list of cost vectors for the path over time)

    Returns
    -------
    List of floats or list of dicts
    """
    if cost_type is not None:
        assert cost_type in G.cost_vector

    C = copy.deepcopy(G)
    path = QNET.Path(C, path)

    cost_array = []
    i = 0
    size_arr = len(np.arange(0, tMax, dt))
    while i < size_arr:
        if cost_type is not None:
            cost_array.append(path.cost_vector[cost_type])
        else:
            cost_array.append(path.cost_vector)
        C.update(dt)
        path.update()
        i += 1
    return cost_array
def simple_purify(Q=None, head=None, tail=None, threshold=None):
    """
    A simple purification algorithm that works as follows:
    1. Find the best path between source and target and save the cost
    2. Remove the edges of this path from the graph
    3. Find the next best path
    4. Purify the paths together
    5. Repeat steps 3 and 4 until either we hit the threshold number or there are no more paths between source and
    target

    :param Q:
    :param source: Source Node
    :param target: Target Node
    :param: threshold: Maximum number of paths to purify before returning cost vector
    :return: cost vector
    :param: dict
    """
    if threshold is not None:
        assert isinstance(threshold, int)
        assert threshold > 0

    # Default cost_array
    if None in [Q, head, tail]:
        return {'e': 0, 'f': 0}

    # Make copy of graph
    C = copy.deepcopy(Q)

    # Get source and target
    head = C.getNode(head)
    tail = C.getNode(tail)

    # Find the best path in terms of fidelity,
    path = QNET.best_path(C, head, tail, 'f')
    # pur_f = path.cost('f')
    pur_f = path.cost_vector['f']
    # pur_e = path.cost('e')
    pur_e = path.cost_vector['e']
    path.remove_edges()
    path_counter = 1

    # Purify paths against eachother until either no path exists or threshold is reached
    while nx.has_path(C, head, tail) is True:
        if threshold is not None:
            if path_counter > threshold:
                break
        path = QNET.best_path(C, head, tail, 'f')
        # new_f = path.cost('f')
        new_f = path.cost_vector['f']
        # new_e = path.cost('e')
        new_e = path.cost_vector['e']

        # Efficiency is weakest-link. Update pur_e to whatever the lowest path efficiency is.
        if new_e < pur_e:
            pur_e = new_e

        pur_f = QNET.fidTransform(pur_f, new_f)
        path.remove_edges()
        path_counter += 1

    # Each path purification requires 2*(n-1) bell projections, where n is the number of bell pairs
    # Assume that projections are done with a PBS with e = 1/2
    pur_e = pur_e * 0.5**(2 * path_counter - 1)

    return {'e': pur_e, 'f': pur_f}
示例#20
0
def altLinSatGen(n, spacing, e=1, f=1, startTime=0, Line1='', Line2='', *args):
    '''
    Constructs a linear chain with end Qnodes A and B, and alternating Swapper and Ground nodes in the middle.

    A satellite is connected to end nodes A and B, and all swapper nodes.

    Parameters
    ----------
    n : int
        Total number of nodes in the chain, including end nodes A and B.
    spacing : Array of float
        Distance between two consecutive nodes in [x,y,z] format.
    eVal : float
        Efficiency of a single channel in the regular lattice. The default is 1.
    pxVal : float
        Probability of state not undergoing X-flip in the regular lattice. The default is 0.
    pyVal : float
        Probability of state not undergoing Y-flip in the regular lattice. The default is 0.
    pzVal : float
        Probability of state not undergoing Z-flip in the regular lattice. The default is 0.
    startTime : int
        Time in seconds from now (current time) starting which the satellite should be tracked. The default is 0.
    Line1 : String
        Line 1 of satellite TLE. The deault is ''.
    Line2 : String
        Line 2 of satellite TLE. The deault is ''.

    Returns
    -------
    Q : QNET Graph
        A linear chain of length n consisting of alternating Bell pair generator and swappers.
        End nodes A and B, and all swapper nodes are connected to a satellite.

    '''

    Q = QNET.Qnet()

    if n > 0:
        firstNode = QNET.Qnode(Q, name='A', coords=spacing)
        Q.add_node(firstNode)

    previousNode = QNET.Qnode(Q)
    currentNode = QNET.Qnode(Q)

    ChannelList = []

    if n > 2:
        for i in range(n - 2):
            GroundNode = QNET.Ground(Q,
                                     name=('G' + str(i + 1)),
                                     coords=np.multiply(i + 2, spacing))
            SwapNode = QNET.Swapper(Q,
                                    name=('T' + str(i + 1)),
                                    coords=np.multiply(i + 2, spacing))

            if n > 3:
                if (i) % 2 == 0:
                    currentNode = SwapNode
                else:
                    currentNode = GroundNode
            else:
                currentNode = GroundNode

            Q.add_node(currentNode)

            if i > 0:
                ChannelList.append({
                    'edge': (previousNode.name, currentNode.name),
                    'e':
                    e,
                    'f':
                    f
                })

            previousNode = currentNode

    if n > 1:
        lastNode = QNET.Qnode(Q, name='B', coords=np.multiply(n, spacing))
        Q.add_node(lastNode)

        ChannelList.append({
            'edge': (firstNode.name, list(Q.nodes)[1].name),
            'e': e,
            'f': f
        })
        ChannelList.append({
            'edge': (list(Q.nodes)[n - 2].name, lastNode.name),
            'e': e,
            'f': f
        })

    S = QNET.Satellite(Q,
                       name='S',
                       t=startTime,
                       line1=Line1,
                       line2=Line2,
                       cartesian=False)
    Q.add_node(S)

    nodeList = Q.nodes()
    for node in nodeList:
        if type(node) != QNET.Ground and type(node) != QNET.Satellite:
            ChannelList.append({'edge': (node.name, S.name)})

    Q.add_qchans_from(ChannelList)

    return Q
示例#21
0
def regularLatticeGen(x, y, xspacing, yspacing=[0, 0, 0], e=1, f=1):
    '''
    Constructs a 2D Regular Lattice with end Qnodes A and B, and alternating Swapper and Ground nodes in the middle.


    Parameters
    ----------
    x : int
        Number of columns in the regular lattice.
    y : int
        Number of rows in the regular lattice.
    xspacing : 3x1 Array of float
        Distance between two consecutive columns in 2D regular lattice in [x,0,0] format.
    yspacing : 3x1 Array of float
        Distance between two consecutive rows in 2D regular lattice in [0,y,0] format. The default is [0,0,0].
    eVal : float
        Efficiency of a single channel in the regular lattice. The default is 1.
    pxVal : float
        Probability of state not undergoing X-flip in the regular lattice. The default is 0.
    pyVal : float
        Probability of state not undergoing Y-flip in the regular lattice. The default is 0.
    pzVal : float
        Probability of state not undergoing Z-flip in the regular lattice. The default is 0.

    Returns
    -------
    Q : QNET Graph
        A y*x 2D regular lattice.

    '''

    assert (x > 0)
    assert (y > 0)

    if yspacing == [0, 0, 0]:
        yspacing[1] = xspacing[0]

    Q = QNET.Qnet()

    firstNode = QNET.Qnode(Q, name='A', coords=[0, 0, 0])
    lastNode = QNET.Qnode(Q,
                          name='B',
                          coords=np.add(np.multiply(x - 1, xspacing),
                                        np.multiply(y - 1, yspacing)))

    previousNode = QNET.Qnode(Q)
    currentNode = QNET.Qnode(Q)

    ChannelList = []

    # Constructing the nodes in the Regular Lattice
    for i in range(x):
        for j in range(y):

            GroundNode = QNET.Ground(Q,
                                     name=('G(' + str(i + 1) + ',' +
                                           str(j + 1) + ')'),
                                     coords=np.add(np.multiply(i, xspacing),
                                                   np.multiply(j, yspacing)))
            SwapNode = QNET.Swapper(Q,
                                    name=('T(' + str(i + 1) + ',' +
                                          str(j + 1) + ')'),
                                    coords=np.add(np.multiply(i, xspacing),
                                                  np.multiply(j, yspacing)))

            # Swap nodes at odd positions and Ground nodes at even positions in a Regular Lattice
            if x >= 1 and y >= 1:
                if (i + j) % 2 == 0:
                    currentNode = GroundNode
                else:
                    currentNode = SwapNode

            # Linear chain of length 3 exception
            if (x == 3 and y == 1) or (x == 1 and y == 3):
                currentNode = GroundNode

            # 2*2 2D lattice exception
            if x == 2 and y == 2:
                currentNode = GroundNode

            # Initialising the first node as A
            if i == 0 and j == 0:
                currentNode = firstNode

            # Initialising the last node as B
            if i == (x - 1) and j == (y - 1):
                currentNode = lastNode

            Q.add_node(currentNode)

            previousNode = currentNode

    # Constructing the vertical edges in the Regular Lattice
    for i in range(x):
        previousNode = list(Q.nodes)[i * y]
        for j in range(y - 1):
            currentNode = list(Q.nodes)[j + 1 + (i * y)]
            ChannelList.append({
                'edge': (previousNode.name, currentNode.name),
                'e': e,
                'f': f
            })
            previousNode = currentNode

    # Constructing the horizontal edges in the Regular Lattice
    for j in range(y):
        previousNode = list(Q.nodes)[j]
        for i in range(x - 1):
            currentNode = list(Q.nodes)[j + (i + 1) * y]
            ChannelList.append({
                'edge': (previousNode.name, currentNode.name),
                'e': e,
                'f': f
            })
            previousNode = currentNode

    Q.add_qchans_from(ChannelList)
    # Q.print_qchans()

    return Q
def simple_swap(Q, source, dest):
    """
    Given a linear graph or subgraph, this function calculates the efficiency from head to tail if all valid swapper
    nodes are used. Currently, both this quantity and the normal efficiency of the path are returned

    In the future, if it's more efficient not to use any swappers, the normal efficiency of the path
    will be returned.

    :param Q: Linear Qnet Graph
    :return float: no_swap and swap efficiencies of the graph

    Example:

    A - G1 - T - G2 - T - G3 - B
    net_eff = min ( e[A-G1-T], e[T-G2], e[T-G3-B] ) * e[T]**2

    Where e[A-G-T] is taken to mean the efficiency of the path [A-G-T]
    and e[T] is taken to mean the efficiency cost of performing a swap with the node T.

    """
    # Check that path exists from A to B
    assert (nx.has_path(Q, source, dest))

    # Create generator of paths from A to B. If no generator exists raise exception
    path_gen = nx.all_simple_paths(Q, source, dest)
    assert (path_gen != None
            ), f"No valid path exists between {source.name} and {dest.name}."

    #  Unpack generator. If more than one path exists, raise exception
    path_count = 0
    for obj in path_gen:
        path = QNET.Path(Q, obj)
        path_count += 1

    assert (path_count == 1
            ), "More than one path exists, linear reduction cannot be done."

    # Does a Swapper with swapping potential exist?
    swap_candidate = None
    # Does ground exist before a swapper?
    front_ground = False
    # Local efficiency of a Ground - Swapper - Ground configuration (or equivalent)
    local_eff = 1
    # list of swapper nodes used in the swapping process
    swap_list = []
    # Total efficiency of path
    net_eff = 1

    # Current node
    cur = source
    # Counter
    i = 0

    while cur != dest:
        # If current node is a Ground and we haven't seen a Swapper yet, set front_ground to True
        if isinstance(cur, QNET.Ground) and swap_candidate is None:
            # Add node eff to local
            local_eff *= cur.costs['e']
            front_ground = True

        # If Current node is a Swapper and we've seen a ground node previously, we now have a candidate for a swapper
        # that could be used.
        elif isinstance(cur, QNET.Swapper) and front_ground is True:
            swap_switch = True
            if swap_candidate == None:
                swap_candidate = cur
            # If we had another swap candidate previously, update swap_candidate to be better efficiency option
            elif swap_candidate.swap_prob < cur.swap_prob:
                swap_candidate = cur
            local_eff *= cur.costs['e']

        # If current node is a Ground and we've seen a Swapper, we perform a swap with the local resources
        # (Front-Ground, Swapper, Back-Ground) and update net_eff.
        elif isinstance(cur, QNET.Ground) and swap_candidate is not None:
            # Add node eff to local
            local_eff *= cur.costs['e']

            # Update net_eff to local_eff if net_eff is smaller
            if local_eff < net_eff:
                net_eff = local_eff

            # Add the swapper we were considering to the list of swappers used
            swap_list.append(swap_candidate)

            # Reset parameters to prepare for the next swap
            local_eff = 1
            swap_candidate = None
            front_ground = False

        # Otherwise include the node cost in the local efficiency
        else:
            local_eff *= cur.costs['e']

        # Increment counter and get the edge efficiency
        i += 1
        edge_costs = Q.get_edge_data(cur, path.node_array[i])

        # Update local_eff with edge efficiency
        local_eff *= edge_costs['e']
        # Move to next node
        cur = path.node_array[i]

        # If the new node is the destination
        if cur == dest:
            # Append node costs from dest
            local_eff *= cur.costs['e']
            # Update net_eff to local_eff if net_eff is smaller
            # This also handles the case where no valid swapper was encountered
            if local_eff < net_eff:
                net_eff = local_eff

    # Multiply net_eff with the costs of all swappers used.
    for swapper in swap_list:
        net_eff *= swapper.costs['e']
    """Calculate cost of path without any swapping"""
    # Cost of path without swapping
    no_swap = 1
    # Length of path
    path_len = len(path.node_array)
    i = 0

    # Sum all edge efficiencies
    while (i < path_len - 1):
        cur = path.node_array[i]
        nxt = path.node_array[i + 1]
        edgeData = path.G.get_edge_data(cur, nxt)
        no_swap *= edgeData['e']
        i += 1

    # Sum all node efficiencies except for swappers
    for node in path.node_array:
        no_swap *= node.costs['e']

    # DEBUG: For now, return both swap and no swap:
    return (no_swap, net_eff)
    """
示例#23
0
def temporalGen(Q, dt, n, startLayer=0, endLayer=None):
    """
    Creates a temporal extension of given graph.

    Creates a temporal extension of given graph to simulate the effect of quantum memory. Each layer(slice)
    of this temporal graph is an updated/evolved version of the previous layer by dt time. n such layers of
    these time updated graphs are created and only those nodes with quantum memory (node.isMemory == True)
    are connected in the temporal dimension.

    However, out of all these n time-updated layers of graph Q, we might only want to connect a few in the
    temporal dimension. The arguments startLayer and endLayer correspond to the number of layer between which
    all the graphs are connected.

    Example: If startLayer=1 and endLayer=3, only layers from 1 to 3 are connected in temporal dimension.

        t=0   -------

        t=1   ------- startLayer=1
              | | | |
        t=2   -------
              | | | |
        t=3   ------- endLayer=3
        :
        t=n-1 -------

    Parameters
    ----------
    Q : QNET Graph
        The spatial graph which is to be utilised to make a spatio-temporal graph.
    dt : float
        Time steps by which each layer/slice/graph is to be updated.
    n : int
        Number of such layers/slices/graphs to be made.
    startLayer : int
        The number corresponding to first layer starting which the layers are connected in temporal dimension.
        Default value is 0 i.e. the first layer itself.
    endLayer : int
        The number corresponding to last layer at which the layers' connection in temporal dimension ends.
        Default value is n-1 i.e. the last layer itself.

    Returns
    -------
    finalGraph : QNET Graph
        The temporal extension (including both connected and unconnected layers) of given graph Q.

    """

    if endLayer == None:
        endLayer = n - 1

    ## Create a list of time-updated graph layers ##
    G = []
    new_graph = copy.deepcopy(Q)
    layer_num = 0
    for i in range(0, n):
        C = copy.deepcopy(new_graph)

        dummy_graph = copy.deepcopy(new_graph)
        dummy_graph.updateName(layer_num)
        G.append(dummy_graph)

        C.update(dt)
        layer_num = layer_num + 1
        new_graph = copy.deepcopy(C)

    ## Join these time-updated graph layers ##
    assert (0 <= startLayer <=
            n - 1), f"Out of range -- 0 <= startLayer <= n-1 "
    assert (0 <= endLayer <= n - 1), f"Out of range -- 0 <= endLayer <= n-1 "
    assert (startLayer <= endLayer), f"startLayer <= endLayer <= n-1 "

    finalGraph = QNET.Qnet()

    for layer_num in range(0, len(G)):
        finalGraph = nx.compose(finalGraph, G[layer_num])
        #print(finalGraph)

    for node in Q.nodes():
        for layer_num in range(startLayer + 1, endLayer + 1):
            if node.isMemory and layer_num > 0:
                u = finalGraph.getNode(str(layer_num - 1) + node.name)
                v = finalGraph.getNode(str(layer_num) + node.name)
                finalGraph.add_memory_qchan(edge=[u, v],
                                            e=u.memory['mem_e'],
                                            f=u.memory['mem_f'])

    return finalGraph
示例#24
0
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Mon Apr  6 13:44:28 2020

Small static tripartite graph without node costs

@author: hudson
"""

import networkx as nx
import QNET

X = QNET.Qnet()

nbunch = [{
    'name': 'A',
    'coords': [0, 50, 0]
}, {
    'name': 'B',
    'coords': [200, 50, 0]
}, {
    'name': 'G1',
    'qnode_type': 'Ground',
    'coords': [100, 20, 0]
}, {
    'name': 'G2',
    'qnode_type': 'Ground',
    'coords': [100, 40, 0]
}, {
    'name': 'G3',
示例#25
0
def altLinGen(n, spacing, e=1, f=1):
    '''
    Constructs a linear chain with end Qnodes A and B, and alternating Swapper and Ground nodes in the middle.

    Parameters
    ----------
    n : int
        Total number of nodes in the chain, including end nodes A and B.
    spacing : Array of float
        Distance between two consecutive nodes in [x,y,z] format.
    e : float
        Efficiency of a single channel in the regular lattice. The default is 1.

    Returns
    -------
    Q : QNET Graph
        A linear chain of length n consisting of alternating Bell pair generator and swappers.

    '''

    Q = QNET.Qnet()

    if n > 0:
        firstNode = QNET.Qnode(Q, name='A', coords=spacing)
        Q.add_node(firstNode)

    previousNode = QNET.Qnode(Q)
    currentNode = QNET.Qnode(Q)

    ChannelList = []

    if n > 2:
        for i in range(n - 2):
            GroundNode = QNET.Ground(Q,
                                     name=('G' + str(i + 1)),
                                     coords=np.multiply(i + 2, spacing))
            SwapNode = QNET.Swapper(Q,
                                    name=('T' + str(i + 1)),
                                    coords=np.multiply(i + 2, spacing))

            if n > 3:
                if (i) % 2 == 0:
                    currentNode = SwapNode
                else:
                    currentNode = GroundNode
            else:
                currentNode = GroundNode

            Q.add_node(currentNode)

            if i > 0:
                ChannelList.append({
                    'edge': (previousNode.name, currentNode.name),
                    'e':
                    e,
                    'f':
                    f
                })

            previousNode = currentNode

    if n > 1:
        lastNode = QNET.Qnode(Q, name='B', coords=np.multiply(n, spacing))
        Q.add_node(lastNode)

        ChannelList.append({
            'edge': (firstNode.name, list(Q.nodes)[1].name),
            'e': e,
            'f': f
        })
        ChannelList.append({
            'edge': (list(Q.nodes)[n - 2].name, lastNode.name),
            'e': e,
            'f': f
        })

    Q.add_qchans_from(ChannelList)

    return Q