Esempio n. 1
0
    def compute_periodicity_cycle_basis(self):
        """

        Returns:

        """
        my_simple_graph = nx.Graph(self._connected_subgraph)
        cycles = nx.cycle_basis(my_simple_graph)
        all_deltas = []
        for cyc in cycles:
            mycyc = list(cyc)
            mycyc.append(cyc[0])
            this_cycle_deltas = [np.zeros(3, np.int)]
            for (node1, node2) in [(node1, mycyc[inode1 + 1])
                                   for inode1, node1 in enumerate(mycyc[:-1])]:
                this_cycle_deltas_new = []
                for key, edge_data in self._connected_subgraph[node1][
                        node2].items():
                    delta = get_delta(node1, node2, edge_data)
                    for current_delta in this_cycle_deltas:
                        this_cycle_deltas_new.append(current_delta + delta)
                this_cycle_deltas = this_cycle_deltas_new
            all_deltas.extend(this_cycle_deltas)
            all_deltas = get_linearly_independent_vectors(all_deltas)
            if len(all_deltas) == 3:
                self._periodicity_vectors = all_deltas
                return
        # One has to consider pairs of nodes with parallel edges (these are not considered in the simple graph cycles)
        edges = my_simple_graph.edges()
        for n1, n2 in edges:
            if n1 == n2:
                continue
            if len(self._connected_subgraph[n1][n2]) == 1:
                continue
            elif len(self._connected_subgraph[n1][n2]) > 1:
                for iedge1, iedge2 in itertools.combinations(
                        self._connected_subgraph[n1][n2], 2):
                    e1data = self._connected_subgraph[n1][n2][iedge1]
                    e2data = self._connected_subgraph[n1][n2][iedge2]
                    current_delta = get_delta(n1, n2, e1data)
                    delta = get_delta(n2, n1, e2data)
                    current_delta += delta
                    all_deltas.append(current_delta)
            else:
                raise ValueError('Should not be here ...')
            all_deltas = get_linearly_independent_vectors(all_deltas)
            if len(all_deltas) == 3:
                self._periodicity_vectors = all_deltas
                return
        self._periodicity_vectors = all_deltas
Esempio n. 2
0
    def description(self, full=False):
        """

        Args:
            full ():

        Returns:

        """
        out = ["Connected component with environment nodes :"]
        if not full:
            out.extend([str(en) for en in sorted(self.graph.nodes())])
            return "\n".join(out)
        for en in sorted(self.graph.nodes()):
            out.append("{}, connected to :".format(str(en)))
            en_neighbs = nx.neighbors(self.graph, en)
            for en_neighb in sorted(en_neighbs):
                out.append("  - {} with delta image cells".format(en_neighb))
                all_deltas = sorted(
                    [
                        get_delta(node1=en, node2=en_neighb, edge_data=edge_data).tolist()
                        for iedge, edge_data in self.graph[en][en_neighb].items()
                    ]
                )
                out.extend(["     ({:d} {:d} {:d})".format(delta[0], delta[1], delta[2]) for delta in all_deltas])
        return "\n".join(out)
Esempio n. 3
0
 def test_get_delta(self):
     n1 = FakeNode(3)
     n2 = FakeNode(7)
     edge_data = {"start": 3, "end": 7, "delta": [2, 6, 4]}
     self.assertTrue(np.allclose(get_delta(n1, n2, edge_data), [2, 6, 4]))
     edge_data = {"start": 7, "end": 3, "delta": [2, 6, 4]}
     self.assertTrue(np.allclose(get_delta(n1, n2, edge_data), [-2, -6, -4]))
     with self.assertRaisesRegex(
         ValueError,
         "Trying to find a delta between two nodes with an edge that seems not to link these nodes.",
     ):
         edge_data = {"start": 6, "end": 3, "delta": [2, 6, 4]}
         get_delta(n1, n2, edge_data)
     with self.assertRaisesRegex(
         ValueError,
         "Trying to find a delta between two nodes with an edge that seems not to link these nodes.",
     ):
         edge_data = {"start": 7, "end": 2, "delta": [2, 6, 4]}
         get_delta(n1, n2, edge_data)
Esempio n. 4
0
 def test_get_delta(self):
     n1 = FakeNode(3)
     n2 = FakeNode(7)
     edge_data = {'start': 3, 'end': 7, 'delta': [2, 6, 4]}
     self.assertTrue(np.allclose(get_delta(n1, n2, edge_data), [2, 6, 4]))
     edge_data = {'start': 7, 'end': 3, 'delta': [2, 6, 4]}
     self.assertTrue(np.allclose(get_delta(n1, n2, edge_data),
                                 [-2, -6, -4]))
     with self.assertRaisesRegex(
             ValueError,
             'Trying to find a delta between two nodes with an edge '
             'that seems not to link these nodes.'):
         edge_data = {'start': 6, 'end': 3, 'delta': [2, 6, 4]}
         get_delta(n1, n2, edge_data)
     with self.assertRaisesRegex(
             ValueError,
             'Trying to find a delta between two nodes with an edge '
             'that seems not to link these nodes.'):
         edge_data = {'start': 7, 'end': 2, 'delta': [2, 6, 4]}
         get_delta(n1, n2, edge_data)
Esempio n. 5
0
    def compute_periodicity_all_simple_paths_algorithm(self):
        """

        Returns:

        """
        self_loop_nodes = list(nx.nodes_with_selfloops(self._connected_subgraph))
        all_nodes_independent_cell_image_vectors = []
        my_simple_graph = nx.Graph(self._connected_subgraph)
        for test_node in self._connected_subgraph.nodes():
            # TODO: do we need to go through all test nodes ?
            this_node_cell_img_vectors = []
            if test_node in self_loop_nodes:
                for key, edge_data in self._connected_subgraph[test_node][test_node].items():
                    if edge_data["delta"] == (0, 0, 0):
                        raise ValueError("There should not be self loops with delta image = (0, 0, 0).")
                    this_node_cell_img_vectors.append(edge_data["delta"])
            for d1, d2 in itertools.combinations(this_node_cell_img_vectors, 2):
                if d1 == d2 or d1 == tuple(-ii for ii in d2):
                    raise ValueError("There should not be self loops with the same (or opposite) delta image.")
            this_node_cell_img_vectors = get_linearly_independent_vectors(this_node_cell_img_vectors)
            # Here, we adopt a cutoff equal to the size of the graph, contrary to the default of networkX (size - 1),
            # because otherwise, the all_simple_paths algorithm fail when the source node is equal to the target node.
            paths = []
            # TODO: its probably possible to do just a dfs or bfs traversal instead of taking all simple paths!
            test_node_neighbors = my_simple_graph.neighbors(test_node)
            breaknodeloop = False
            for test_node_neighbor in test_node_neighbors:
                # Special case for two nodes
                if len(self._connected_subgraph[test_node][test_node_neighbor]) > 1:
                    this_path_deltas = []
                    node_node_neighbor_edges_data = list(
                        self._connected_subgraph[test_node][test_node_neighbor].values()
                    )
                    for edge1_data, edge2_data in itertools.combinations(node_node_neighbor_edges_data, 2):
                        delta1 = get_delta(test_node, test_node_neighbor, edge1_data)
                        delta2 = get_delta(test_node_neighbor, test_node, edge2_data)
                        this_path_deltas.append(delta1 + delta2)
                    this_node_cell_img_vectors.extend(this_path_deltas)
                    this_node_cell_img_vectors = get_linearly_independent_vectors(this_node_cell_img_vectors)
                    if len(this_node_cell_img_vectors) == 3:
                        break
                for path in nx.all_simple_paths(
                    my_simple_graph,
                    test_node,
                    test_node_neighbor,
                    cutoff=len(self._connected_subgraph),
                ):
                    path_indices = [nodepath.isite for nodepath in path]
                    if path_indices == [test_node.isite, test_node_neighbor.isite]:
                        continue
                    path_indices.append(test_node.isite)
                    path_indices = tuple(path_indices)
                    if path_indices not in paths:
                        paths.append(path_indices)
                    else:
                        continue
                    path.append(test_node)
                    # TODO: there are some paths that appears twice for cycles, and there are some paths that should
                    # probably not be considered
                    this_path_deltas = [np.zeros(3, np.int_)]
                    for (node1, node2) in [(node1, path[inode1 + 1]) for inode1, node1 in enumerate(path[:-1])]:
                        this_path_deltas_new = []
                        for key, edge_data in self._connected_subgraph[node1][node2].items():
                            delta = get_delta(node1, node2, edge_data)
                            for current_delta in this_path_deltas:
                                this_path_deltas_new.append(current_delta + delta)
                        this_path_deltas = this_path_deltas_new
                    this_node_cell_img_vectors.extend(this_path_deltas)
                    this_node_cell_img_vectors = get_linearly_independent_vectors(this_node_cell_img_vectors)
                    if len(this_node_cell_img_vectors) == 3:
                        breaknodeloop = True
                        break
                if breaknodeloop:
                    break
            this_node_cell_img_vectors = get_linearly_independent_vectors(this_node_cell_img_vectors)
            independent_cell_img_vectors = this_node_cell_img_vectors
            all_nodes_independent_cell_image_vectors.append(independent_cell_img_vectors)
            # If we have found that the sub structure network is 3D-connected, we can stop ...
            if len(independent_cell_img_vectors) == 3:
                break
        self._periodicity_vectors = []
        if len(all_nodes_independent_cell_image_vectors) != 0:
            for independent_cell_img_vectors in all_nodes_independent_cell_image_vectors:
                if len(independent_cell_img_vectors) > len(self._periodicity_vectors):
                    self._periodicity_vectors = independent_cell_img_vectors
                if len(self._periodicity_vectors) == 3:
                    break
Esempio n. 6
0
    def coordination_sequence(self, source_node, path_size=5, coordination="number", include_source=False):
        """Get the coordination sequence for a given node.

        Args:
            source_node: Node for which the coordination sequence is computed.
            path_size: Maximum length of the path for the coordination sequence.
            coordination: Type of coordination sequence. The default ("number") corresponds to the number
                of environment nodes that are reachable by following paths of sizes between 1 and path_size.
                For coordination "env:number", this resulting coordination sequence is a sequence of dictionaries
                mapping the type of environment to the number of such environment reachable by following paths of
                sizes between 1 and path_size.
            include_source: Whether to include the source_node in the coordination sequence.

        Returns:
            dict: Mapping between the nth "layer" of the connected component with the corresponding coordination.

        Examples:
            The corner-sharing octahedral framework (as in perovskites) have the following coordination sequence (up to
            a path of size 6) :
            {1: 6, 2: 18, 3: 38, 4: 66, 5: 102, 6: 146}
            Considering both the octahedrons and the cuboctahedrons of the typical BaTiO3 perovskite, the "env:number"
            coordination sequence (up to a path of size 6) starting on the Ti octahedron and Ba cuboctahedron
            are the following :
            Starting on the Ti octahedron : {1: {'O:6': 6, 'C:12': 8}, 2: {'O:6': 26, 'C:12': 48},
                                             3: {'O:6': 90, 'C:12': 128}, 4: {'O:6': 194, 'C:12': 248},
                                             5: {'O:6': 338, 'C:12': 408}, 6: {'O:6': 522, 'C:12': 608}}
            Starting on the Ba cuboctahedron : {1: {'O:6': 8, 'C:12': 18}, 2: {'O:6': 48, 'C:12': 74},
                                                3: {'O:6': 128, 'C:12': 170}, 4: {'O:6': 248, 'C:12': 306},
                                                5: {'O:6': 408, 'C:12': 482}, 6: {'O:6': 608, 'C:12': 698}}
            If include_source is set to True, the source node is included in the sequence, e.g. for the corner-sharing
            octahedral framework : {0: 1, 1: 6, 2: 18, 3: 38, 4: 66, 5: 102, 6: 146}. For the "env:number" coordination
            starting on a Ba cuboctahedron (as shown above), the coordination sequence is then :
            {0: {'C:12': 1}, 1: {'O:6': 8, 'C:12': 18}, 2: {'O:6': 48, 'C:12': 74}, 3: {'O:6': 128, 'C:12': 170},
             4: {'O:6': 248, 'C:12': 306}, 5: {'O:6': 408, 'C:12': 482}, 6: {'O:6': 608, 'C:12': 698}}
        """
        if source_node not in self._connected_subgraph:
            raise ValueError("Node not in Connected Component. Cannot find coordination sequence.")
        # Example of an infinite periodic net in two dimensions consisting of a stacking of
        # A and B lines :
        #
        #     *     *     *     *     *
        #     *     *     *     *     *
        # * * A * * B * * A * * B * * A * *
        #     *     *     *     *     *
        #     *     *     *     *     *
        # * * A * * B * * A * * B * * A * *
        #     *     *     *     *     *
        #     *     *     *     *     *
        # * * A * * B * * A * * B * * A * *
        #     *     *     *     *     *
        #     *     *     *     *     *
        # * * A * * B * * A * * B * * A * *
        #     *     *     *     *     *
        #     *     *     *     *     *
        # * * A * * B * * A * * B * * A * *
        #     *     *     *     *     *
        #     *     *     *     *     *
        #
        # One possible quotient graph of this periodic net :
        #          __           __
        # (0,1,0) /  \         /  \ (0,1,0)
        #         `<--A--->---B--<´
        #            / (0,0,0) \
        #            \         /
        #             `--->---´
        #              (1,0,0)
        #
        # The "number" coordination sequence starting from any environment is : 4-8-12-16-...
        # The "env:number" coordination sequence starting from any environment is :
        # {A:2, B:2}-{A:4, B:4}-{A:6, B:6}-...
        current_delta = (0, 0, 0)
        current_ends = [(source_node, current_delta)]
        visited = {(source_node.isite, *current_delta)}
        path_len = 0
        cseq = {}
        if include_source:
            if coordination == "number":
                cseq[0] = 1
            elif coordination == "env:number":
                cseq[0] = {source_node.coordination_environment: 1}
            else:
                raise ValueError('Coordination type "{}" is not valid for coordination_sequence.'.format(coordination))
        while path_len < path_size:
            new_ends = []
            for current_node_end, current_delta_end in current_ends:
                for nb in self._connected_subgraph.neighbors(current_node_end):
                    for iedge, edata in self._connected_subgraph[current_node_end][nb].items():
                        new_delta = current_delta_end + get_delta(current_node_end, nb, edata)
                        if (nb.isite, *new_delta) not in visited:
                            new_ends.append((nb, new_delta))
                            visited.add((nb.isite, *new_delta))
                        if nb.isite == current_node_end.isite:  # Handle self loops
                            new_delta = current_delta_end - get_delta(current_node_end, nb, edata)
                            if (nb.isite, *new_delta) not in visited:
                                new_ends.append((nb, new_delta))
                                visited.add((nb.isite, *new_delta))
            current_ends = new_ends
            path_len += 1
            if coordination == "number":
                cseq[path_len] = len(current_ends)
            elif coordination == "env:number":
                myenvs = [myend.coordination_environment for myend, _ in current_ends]
                cseq[path_len] = {myenv: myenvs.count(myenv) for myenv in set(myenvs)}
            else:
                raise ValueError('Coordination type "{}" is not valid for coordination_sequence.'.format(coordination))
        return cseq
Esempio n. 7
0
def draw_network(env_graph, pos, ax, sg=None, periodicity_vectors=None):
    """Draw network of environments in a matplotlib figure axes.

    Args:
        env_graph: Graph of environments.
        pos: Positions of the nodes of the environments in the 2D figure.
        ax: Axes object in which the network should be drawn.
        sg: Not used currently (drawing of supergraphs).
        periodicity_vectors: List of periodicity vectors that should be drawn.

    Returns: None

    """
    for n in env_graph:
        c = Circle(pos[n], radius=0.02, alpha=0.5)
        ax.add_patch(c)
        env_graph.node[n]["patch"] = c
        x, y = pos[n]
        ax.annotate(str(n), pos[n], ha="center", va="center", xycoords="data")
    seen = {}
    e = None
    for (u, v, d) in env_graph.edges(data=True):
        n1 = env_graph.node[u]["patch"]
        n2 = env_graph.node[v]["patch"]
        rad = 0.1
        if (u, v) in seen:
            rad = seen.get((u, v))
            rad = (rad + np.sign(rad) * 0.1) * -1
        alpha = 0.5
        color = "k"
        periodic_color = "r"

        delta = get_delta(u, v, d)

        # center = get_center_of_arc(n1.center, n2.center, rad)
        n1center = np.array(n1.center)
        n2center = np.array(n2.center)
        midpoint = (n1center + n2center) / 2
        dist = np.sqrt(np.power(n2.center[0] - n1.center[0], 2) + np.power(n2.center[1] - n1.center[1], 2))
        n1c_to_n2c = n2center - n1center
        vv = np.cross(
            np.array([n1c_to_n2c[0], n1c_to_n2c[1], 0], np.float_),
            np.array([0, 0, 1], np.float_),
        )
        vv /= np.linalg.norm(vv)
        midarc = midpoint + rad * dist * np.array([vv[0], vv[1]], np.float_)
        xytext_offset = 0.1 * dist * np.array([vv[0], vv[1]], np.float_)

        if periodicity_vectors is not None and len(periodicity_vectors) == 1:
            if np.all(np.array(delta) == np.array(periodicity_vectors[0])) or np.all(
                np.array(delta) == -np.array(periodicity_vectors[0])
            ):
                e = FancyArrowPatch(
                    n1center,
                    n2center,
                    patchA=n1,
                    patchB=n2,
                    arrowstyle="-|>",
                    connectionstyle="arc3,rad=%s" % rad,
                    mutation_scale=15.0,
                    lw=2,
                    alpha=alpha,
                    color="r",
                    linestyle="dashed",
                )
            else:
                e = FancyArrowPatch(
                    n1center,
                    n2center,
                    patchA=n1,
                    patchB=n2,
                    arrowstyle="-|>",
                    connectionstyle="arc3,rad=%s" % rad,
                    mutation_scale=10.0,
                    lw=2,
                    alpha=alpha,
                    color=color,
                )
        else:
            ecolor = color if np.allclose(np.array(delta), np.zeros(3)) else periodic_color
            e = FancyArrowPatch(
                n1center,
                n2center,
                patchA=n1,
                patchB=n2,
                arrowstyle="-|>",
                connectionstyle="arc3,rad=%s" % rad,
                mutation_scale=10.0,
                lw=2,
                alpha=alpha,
                color=ecolor,
            )
        ax.annotate(
            delta,
            midarc,
            ha="center",
            va="center",
            xycoords="data",
            xytext=xytext_offset,
            textcoords="offset points",
        )
        seen[(u, v)] = rad
        ax.add_patch(e)
Esempio n. 8
0
def draw_network(env_graph, pos, ax, sg=None, periodicity_vectors=None):
    """

    Args:
        env_graph ():
        pos ():
        ax ():
        sg ():
        periodicity_vectors ():

    Returns:

    """
    for n in env_graph:
        c = Circle(pos[n], radius=0.02, alpha=0.5)
        ax.add_patch(c)
        env_graph.node[n]['patch'] = c
        x, y = pos[n]
        ax.annotate(str(n), pos[n], ha='center', va='center', xycoords='data')
    seen = {}
    e = None
    for (u, v, d) in env_graph.edges(data=True):
        n1 = env_graph.node[u]['patch']
        n2 = env_graph.node[v]['patch']
        rad = 0.1
        if (u, v) in seen:
            rad = seen.get((u, v))
            rad = (rad + np.sign(rad) * 0.1) * -1
        alpha = 0.5
        color = 'k'
        periodic_color = 'r'

        delta = get_delta(u, v, d)

        # center = get_center_of_arc(n1.center, n2.center, rad)
        n1center = np.array(n1.center)
        n2center = np.array(n2.center)
        midpoint = (n1center + n2center) / 2
        dist = np.sqrt(
            np.power(n2.center[0] - n1.center[0], 2) +
            np.power(n2.center[1] - n1.center[1], 2))
        n1c_to_n2c = n2center - n1center
        vv = np.cross(np.array([n1c_to_n2c[0], n1c_to_n2c[1], 0], np.float),
                      np.array([0, 0, 1], np.float))
        vv /= np.linalg.norm(vv)
        midarc = midpoint + rad * dist * np.array([vv[0], vv[1]], np.float)
        xytext_offset = 0.1 * dist * np.array([vv[0], vv[1]], np.float)

        if periodicity_vectors is not None and len(periodicity_vectors) == 1:
            if np.all(np.array(delta) == np.array(
                    periodicity_vectors[0])) or np.all(
                        np.array(delta) == -np.array(periodicity_vectors[0])):
                e = FancyArrowPatch(n1center,
                                    n2center,
                                    patchA=n1,
                                    patchB=n2,
                                    arrowstyle='-|>',
                                    connectionstyle='arc3,rad=%s' % rad,
                                    mutation_scale=15.0,
                                    lw=2,
                                    alpha=alpha,
                                    color='r',
                                    linestyle='dashed')
            else:
                e = FancyArrowPatch(n1center,
                                    n2center,
                                    patchA=n1,
                                    patchB=n2,
                                    arrowstyle='-|>',
                                    connectionstyle='arc3,rad=%s' % rad,
                                    mutation_scale=10.0,
                                    lw=2,
                                    alpha=alpha,
                                    color=color)
        else:
            ecolor = color if np.allclose(np.array(delta),
                                          np.zeros(3)) else periodic_color
            e = FancyArrowPatch(n1center,
                                n2center,
                                patchA=n1,
                                patchB=n2,
                                arrowstyle='-|>',
                                connectionstyle='arc3,rad=%s' % rad,
                                mutation_scale=10.0,
                                lw=2,
                                alpha=alpha,
                                color=ecolor)
        ax.annotate(delta,
                    midarc,
                    ha='center',
                    va='center',
                    xycoords='data',
                    xytext=xytext_offset,
                    textcoords='offset points')
        seen[(u, v)] = rad
        ax.add_patch(e)

    return e