Beispiel #1
0
def perform_experiment(original_graph: nx.Graph, ftrp: float, exp_name: str,
                       data_path: str):
    print(exp_name)

    # removing graph isolates
    # NoisyGraph is not intended to deal with isolated nodes
    original_graph.remove_nodes_from(list(nx.isolates(original_graph)))

    # constructing noisy graph
    noisy_graph = NoisyGraph(ftrp=ftrp)
    noisy_graph.construct_graph(original_graph)

    # algorithm compliance
    sigma_mean, sigma_variance = noisy_graph.get_sigmas_profile()

    # uncertainty
    uncertainty_mean, uncertainty_variance = noisy_graph.get_uncertainty_profile(
    )

    # centrality_metrics
    dc_distance, dc_correlation, dc_mean_change = noisy_graph.degree_centrality_profile(
        original_graph)
    bc_distance, bc_correlation, bc_mean_change = noisy_graph.betweenness_profile(
        original_graph)
    cc_distance, cc_correlation, cc_mean_change = noisy_graph.closeness_profile(
        original_graph)
    ec_distance, ec_correlation, ec_mean_change = noisy_graph.eigenvector_centrality_profile(
        original_graph)

    # create raw file with original edges and noisy edges
    f = open(f"raw_data/{exp_name[:2]}.txt", "a")
    f.write(f"Experimental conditions: {exp_name}\n")
    f.write(f"Real edges: {noisy_graph.edges_if(real=True)}\n")
    f.write(f"Fake edges: {noisy_graph.edges_if(real=False)}\n\n")
    f.close()

    # add result to csv
    result = f"{exp_name},{sigma_mean},{sigma_variance},{uncertainty_mean},{uncertainty_variance},"
    result += f"{dc_distance},{dc_correlation},{dc_mean_change},"
    result += f"{bc_distance},{bc_correlation},{bc_mean_change},"
    result += f"{cc_distance},{cc_correlation},{cc_mean_change},"
    result += f"{ec_distance},{ec_correlation},{ec_mean_change}"
    result += "\n"

    f = open(data_path, "a")
    f.write(result)
    f.close()
Beispiel #2
0
def propcess_graph(graph: nx.Graph) -> t.Tuple[pd.DataFrame, t.Dict[str, int]]:
    """
    Process information in graph, returning a table (table data + column names)
    as a result. Each row of the table represents a side-chain, and each column
    records one property of the side-chain, such as the number of heavy atoms it
    contains, whether it is a hydrogen bond donor or acceptor, ect.
    """
    scaffold_nodes = []
    for node_id, node in graph.nodes.items():
        if 'is_scaffold' in node:
            scaffold_nodes.append(node_id)
    # Remove scaffold
    graph.remove_nodes_from(scaffold_nodes)
    # Initialize data
    graph_info = []
    # Iterate through disconnected subgraphs
    for subgraph in connected_components(graph):
        attached_atom_id = None
        num_heavy_atoms, is_hbd, is_hba = 0, False, False
        for node_id in subgraph:
            node = graph.nodes[node_id]
            if attached_atom_id is None and 'anchor' in node:
                attached_atom_id = node['anchor']
            if not is_hba and 'is_hba' in node:
                is_hba = True
            if not is_hbd and 'is_hbd' in node:
                is_hbd = True
            num_heavy_atoms += 1
        if attached_atom_id is None:
            raise ValueError
        is_hbd_and_hba = is_hbd and is_hba
        graph_info.append([
            attached_atom_id, num_heavy_atoms,
            int(is_hbd),
            int(is_hba),
            int(is_hbd_and_hba)
        ])
    # Convert graph_info to dataframe
    if not graph_info:
        raise NoSubstitutionException()
    graph_info = np.array(graph_info, dtype=np.int32)
    col_names = [
        'attached_atom_id', 'num_heavy_atoms', 'is_hbd', 'is_hba',
        'is_hbd_and_hba'
    ]
    col_names = {key: val for val, key in enumerate(col_names)}
    return graph_info, col_names
Beispiel #3
0
def remove_isolates(graph: nx.Graph) -> int:
    """Removes all isolates nodes in the given graph.

    Parameters
    ----------
    graph : nx.Graph
        A graph on which to remove all isolates

    Returns
    -------
    int
        Number of removed isolates

    """
    isolates = list(nx.isolates(graph))
    graph.remove_nodes_from(isolates)
    return len(isolates)
def visualize_graph_layer(graph: Graph, layer: int):
    graph = graph.copy()

    __pull__overlapping_vertices_apart(graph, 0.05)

    to_remove = []
    for node, data in graph.nodes(data=True):
        if data['layer'] != layer:
            to_remove.append(node)

    graph.remove_nodes_from(to_remove)

    colors = [__get_color(d) for n, d in graph.nodes(data=True)]
    networkx.draw(graph,
                  networkx.get_node_attributes(graph, 'position'),
                  labels=networkx.get_node_attributes(graph, 'label'),
                  node_color=colors,
                  with_labels=True)
def draw_graph(g: nx.Graph, seed=6, pos=None, idx2node=None, limit_node=50):
    g = g.copy()
    if limit_node is not None:
        g.remove_nodes_from(nodes=list(g.nodes)[limit_node:])

    if pos is None:
        pos = nx.spring_layout(g, seed=seed)

    nx.draw(g, pos=pos)
    if idx2node is not None:
        labels = {}
        for u in g.nodes:
            labels[u] = str(idx2node[u])
        nx.draw_networkx_labels(g, pos, labels=labels)
    else:
        nx.draw_networkx_labels(g, pos=pos)

    plt.show()
def filter_graph(
    G: nx.Graph,
    min_component_size: int = FamilyNetworkConfig.default_min_component_size,
) -> nx.Graph:
    # create copy of input graph
    G = G.copy()

    # remove discrete components with less than 3 nodes.
    small_components = [
        c for c in nx.connected_components(G) if len(c) < min_component_size
    ]
    for component in small_components:
        G.remove_nodes_from(component)
    logger.debug(
        f"Removed discrete components with less than {min_component_size} nodes from graph."
    )

    return G
def top_k_prediction_edges(G: nx.Graph,
                           y_pred,
                           possible_edges_df,
                           top_k,
                           show_acc_on_edge=False,
                           plot_link_pred=False,
                           limit_node=100,
                           idx2node=None):
    # get top K link prediction
    # sorted_y_pred, sorted_possible_edges = zip(*sorted(zip(y_pred, possible_egdes)))
    node_1 = possible_edges_df['node_1'].to_list()
    node_2 = possible_edges_df['node_2'].to_list()

    # unconnected_edges = [possible_egdes_df['node_1'].to_list(), possible_egdes_df['node_2'].to_list()]
    unconnected_edges = [(node_1[i], node_2[i]) for i in range(len(node_1))]
    sorted_y_pred, sorted_possible_edges = (list(t) for t in zip(
        *sorted(zip(y_pred, unconnected_edges), reverse=True)))

    if plot_link_pred:
        if len(G.nodes) > limit_node:
            G.remove_nodes_from(nodes=list(G.nodes)[limit_node:])
        if show_acc_on_edge:
            plot_link_prediction_graph(
                G=G,
                pred_edges=sorted_possible_edges[:top_k],
                pred_acc=sorted_y_pred[:top_k],
                idx2node=idx2node)
        else:
            plot_link_prediction_graph(
                G=G,
                pred_edges=sorted_possible_edges[:top_k],
                idx2node=idx2node)

    if idx2node is None:
        original_pred_edges = sorted_possible_edges[:top_k]
    else:
        original_pred_edges = [(idx2node[u], idx2node[v])
                               for u, v in sorted_possible_edges[:top_k]]

    print(f"Top {top_k} predicted edges: edge|accuracy")
    for i in range(top_k):
        print(f"{original_pred_edges[i]} : {round(sorted_y_pred[i], 2)}")

    return original_pred_edges, sorted_y_pred[:top_k]
def plot_network(g: nx.Graph, *, height='1000px', smooth_edges=None,
                 max_node_size=100, min_node_size=5, largest_component=True,
                 interactive=True, controls=False, scale=1, iterations=1000,
                 gravity=1):
    """Plot a `networkx.Graph` generated by one of the `build_X_graph`
    functions in this module. Plotting is done using the `pyvis` library.


    :param height: Height of the plot.
    :param smooth_edges: Enables curved ('smooth') edges. Looks nice but is
                         heavy on performance.
    :param min_node_size: The radius of the smallest node.
    :param max_node_size: The radius of the largest node.
    :param largest_component: Only plot the largest connected component of
                              the graph.
    """
    if isolates := list(nx.isolates(g)):
        g = g.copy()
        g.remove_nodes_from(isolates)
Beispiel #9
0
def remove_dead_ends(graph: nx.Graph, node_filter=no_filter, only_strict=False):
    """Remove dead ends from a given graph. A dead end is defined as a node having only one neighbor. For
    directed graphs, a strict dead end is a node having a unique predecessor and no successors. A weak dead end is a
    node having a unique predecessor that is also its unique successor.

    Parameters
    ----------
    graph : nx.Graph
        Graph to simplify
    node_filter :
        Evaluates to true if a node can be removed, false otherwise. (Default value = no_filter)
    only_strict :
        If true, remove only strict dead ends. Used only for directed graphs. (Default value = False)

    """
    nodes_to_remove = get_dead_ends(graph, node_filter, only_strict)
    while nodes_to_remove:
        graph.remove_nodes_from(nodes_to_remove)
        nodes_to_remove = get_dead_ends(graph, node_filter, only_strict)
Beispiel #10
0
def graph_to_node_link(g: nx.Graph,
                       user_name_dict: Dict[int, str] = None,
                       min_degree: int = 0) -> dict:
    # logger.debug("Graph nodes: %s", g.nodes())
    # logger.debug("Graph edges: %s", g.edges())
    # add usernames before taking stuff out
    """
    transforms the graph to node link for d3js and optionally removes nodes having fewer than a certain number of edges
    :param g: an instance of networkx.Graph
    :param user_name_dict: a mapping of user ids to user names
    :param min_degree: the minimum number of edges for a node to be included
    :return: a dict/json sufficient for use with d3js force layouts etc
    """
    if user_name_dict:
        nx.relabel_nodes(g, user_name_dict, copy=False)
    if min_degree > 0:
        outdeg = g.degree()
        to_remove = [n for n in outdeg if outdeg[n] < min_degree]
        g.remove_nodes_from(to_remove)
    data = nxjs.node_link_data(g)
    return data
Beispiel #11
0
def clique_exact_find_greedy_eliminate(graph: nx.Graph) -> list:
    """Perform minimum clique cover by exactly find maximum clique and iteratively eliminate it.

    Find the maximum clique by enumerating all the cliques and iteratively eliminate it.

    Args:
        graph (nx.Graph): graph to solve
    Returns:
        list: list of node names for each clique
    """
    graph = graph.copy()
    clique_list = []
    while len(graph.nodes())>0:
        max_size = 0
        max_clique = []
        for clique in nx.find_cliques(graph):
            size = len(clique)
            if size > max_size:
                max_size = size
                max_clique = clique
        graph.remove_nodes_from(max_clique)
        clique_list.append(max_clique)
    return clique_list
def remove_identical_clusters(graph: nx.Graph):
    """
    Removes all nodes from the graph that are only connected to a single
    other node (ie. identical clusters)
    :param graph: The graph to manipulate
    :return: The number of removed nodes
    """
    identical_clusters = list()
    for node in graph.nodes:
        neighbors = list(graph.neighbors(node))

        if len(neighbors) != 1:
            continue

        # make sure the neighbor also only has 1 neighbor
        neighbors_neighbors = list(graph.neighbors(neighbors[0]))

        if len(neighbors_neighbors) == 1:
            identical_clusters.append(node)

    graph.remove_nodes_from(identical_clusters)

    return len(identical_clusters)
Beispiel #13
0
def preprocess(graph: nx.Graph): # Doing it inplace to reduce memory usage
    selfloop_edges = nx.selfloop_edges(graph)
    graph.remove_edges_from(selfloop_edges)  # Remove self-loops
    isolated_nodes = nx.isolates(graph)
    graph.remove_nodes_from(isolated_nodes)  # Remove orphan nodes
    return isolated_nodes, selfloop_edges
def filter_isolated_nodes(graph: nx.Graph):
    rejected_nodes = []
    for node in graph.nodes:
        if graph.degree(node) == 0:
            rejected_nodes.append(node)
    graph.remove_nodes_from(rejected_nodes)
Beispiel #15
0
            print('{0}/{1} nodes processed'.format(i, n), end='\r')
        print('{0}/{1} nodes processed'.format(i, n))

    print('Search for orphaned nodes')
    orphaned = set()
    n = graph.number_of_nodes()
    i = 0
    for node in graph.nodes_iter():
        if graph.degree(node) == 0:
            orphaned.add(node)
        i += 1
        print('{0}/{1} nodes processed'.format(i, n), end='\r')
    
    print('{0}/{1} nodes processed'.format(i, n))
    print('Delete {0} orphaned nodes'.format(len(orphaned)))
    graph.remove_nodes_from(orphaned)

    print('Calculate offset')
    points = [node[1]['pos'] for node in graph.nodes(data=True)]
    min_x = min(points, key=lambda p: p[0])[0]
    min_y = min(points, key=lambda p: p[1])[1]
    for node in graph.nodes_iter():
        pos = (graph.node[node]['pos'][0] - min_x, graph.node[node]['pos'][1] - min_y)
        graph.node[node]['pos'] = pos
    print('Translated data by ({0}, {1})'.format(-min_x, -min_y))

    print('Calculate edge weights')
    n = graph.number_of_edges()
    i = 0
    for edge in graph.edges():
        lat1 = math.radians(graph.node[edge[0]]['lat'])
Beispiel #16
0
class Gratoms(Atoms):
    """Graph based atoms object.

    An Integrated class for an ASE atoms object with a corresponding
    Networkx Graph.
    """
    def __init__(self,
                 symbols=None,
                 positions=None,
                 numbers=None,
                 tags=None,
                 momenta=None,
                 masses=None,
                 magmoms=None,
                 charges=None,
                 scaled_positions=None,
                 cell=None,
                 pbc=None,
                 celldisp=None,
                 constraint=None,
                 calculator=None,
                 info=None,
                 edges=None):
        super().__init__(symbols, positions, numbers, tags, momenta, masses,
                         magmoms, charges, scaled_positions, cell, pbc,
                         celldisp, constraint, calculator, info)

        if isinstance(edges, np.ndarray):
            if self.pbc.any():
                self._graph = MultiGraph(edges)
            else:
                self._graph = Graph(edges)
        else:
            if self.pbc.any():
                self._graph = MultiGraph()
            else:
                self._graph = Graph()

        nodes = [[i, {
            'number': n
        }] for i, n in enumerate(self.arrays['numbers'])]
        self._graph.add_nodes_from(nodes)

        if isinstance(edges, list):
            self._graph.add_edges_from(edges)

    @property
    def graph(self):
        return self._graph

    @property
    def nodes(self):
        return self._graph.nodes

    @property
    def edges(self):
        return self._graph.edges

    @property
    def adj(self):
        return self._graph.adj

    @property
    def degree(self):
        degree = self._graph.degree
        return np.array([_[1] for _ in degree])

    @property
    def connectivity(self):
        connectivity = nx.to_numpy_matrix(self._graph).astype(int)
        return connectivity

    def get_surface_atoms(self):
        """Return surface atoms."""
        surf_atoms = np.where(self.get_array('surface_atoms') > 0)[0]
        return surf_atoms

    def set_surface_atoms(self, top, bottom=None):
        """Assign surface atoms."""
        n = np.zeros(len(self))
        if bottom is not None:
            n[bottom] = -1
        # Overwrites bottom indexing
        n[top] = 1
        self.set_array('surface_atoms', n)

    def get_neighbor_symbols(self, u):
        """Get chemical symbols for neighboring atoms of u."""
        neighbors = list(self._graph[u])

        return sym[self.arrays['numbers'][neighbors]]

    def is_isomorph(self, other):
        """Check if isomorphic by bond count and atomic number."""
        isomorphic = nx.is_isomorphic(self._graph,
                                      other._graph,
                                      edge_match=em,
                                      node_match=nm)

        return isomorphic

    def get_chemical_tags(self, rank=2):
        """Generate a hash descriptive of the chemical formula (rank 0)
        or include bonding (rank 1).
        """
        cnt = np.bincount(self.arrays['numbers'])
        composition = ','.join(cnt.astype(str))

        if rank == 1:
            return composition[2:]

        for adj in self.adj.items():

            num = self.arrays['numbers'][list(adj[1].keys())]
            cnt += np.bincount(num, minlength=len(cnt))

        bonding = ','.join(cnt.astype(str))

        return composition[2:], bonding[2:]

    def get_unsaturated_nodes(self, screen=None):

        unsaturated = []
        for node, data in self.nodes(data=True):
            radicals = data['valence']

            if screen in data:
                continue

            if radicals > 0:
                unsaturated += [node]

        return np.array(unsaturated)

    def copy(self):
        """Return a copy."""
        atoms = self.__class__(cell=self._cell, pbc=self._pbc, info=self.info)

        atoms.arrays = {}
        for name, a in self.arrays.items():
            atoms.arrays[name] = a.copy()
        atoms.constraints = copy.deepcopy(self.constraints)
        if hasattr(self, '_graph'):
            atoms._graph = self._graph.copy()

        return atoms

    def __getitem__(self, i):
        """Return a subset of the atoms.

        i -- scalar integer, list of integers, or slice object
        describing which atoms to return.

        If i is a scalar, return an Atom object. If i is a list or a
        slice, return an Atoms object with the same cell, pbc, and
        other associated info as the original Atoms object. The
        indices of the constraints will be shuffled so that they match
        the indexing in the subset returned.
        """

        if isinstance(i, (int, np.int64)):
            natoms = len(self)
            if i < -natoms or i >= natoms:
                raise IndexError('Index out of range.')

            return Atom(atoms=self, index=i)
        elif isinstance(i, list) and len(i) > 0:
            # Make sure a list of booleans will work correctly and not be
            # interpreted at 0 and 1 indices.
            i = np.array(i)

        conadd = []
        # Constraints need to be deepcopied, but only the relevant ones.
        for con in copy.deepcopy(self.constraints):
            if isinstance(con, (FixConstraint, FixBondLengths)):
                try:
                    con.index_shuffle(self, i)
                    conadd.append(con)
                except IndexError:
                    pass

        atoms = self.__class__(cell=self._cell,
                               pbc=self._pbc,
                               info=self.info,
                               celldisp=self._celldisp)

        atoms.arrays = {}
        for name, a in self.arrays.items():
            atoms.arrays[name] = a[i].copy()

        # Copy the graph, conserving correct indexing
        if self.nodes:
            nodes = [[_, {
                'number': n
            }] for _, n in enumerate(self.arrays['numbers'])]
            atoms.graph.add_nodes_from(nodes)

            j = i.tolist()
            for u, v in self.graph.edges():
                if u not in i or v not in i:
                    continue
                atoms.graph.add_edge(j.index(u), j.index(v))

        atoms.constraints = conadd
        return atoms

    def __iadd__(self, other):
        """Extend atoms object by appending atoms from *other*."""
        if isinstance(other, Atom):
            other = self.__class__([other])

        n1 = len(self)
        n2 = len(other)

        for name, a1 in self.arrays.items():
            a = np.zeros((n1 + n2, ) + a1.shape[1:], a1.dtype)
            a[:n1] = a1
            if name == 'masses':
                a2 = other.get_masses()
            else:
                a2 = other.arrays.get(name)
            if a2 is not None:
                a[n1:] = a2
            self.arrays[name] = a

        for name, a2 in other.arrays.items():
            if name in self.arrays:
                continue
            a = np.empty((n1 + n2, ) + a2.shape[1:], a2.dtype)
            a[n1:] = a2
            if name == 'masses':
                a[:n1] = self.get_masses()[:n1]
            else:
                a[:n1] = 0

            self.set_array(name, a)

        if isinstance(other, Gratoms):
            if isinstance(self._graph, nx.MultiGraph) & \
               isinstance(other._graph, nx.Graph):
                other._graph = nx.MultiGraph(other._graph)

            self._graph = nx.disjoint_union(self._graph, other._graph)

        return self

    def __delitem__(self, i):
        from ase.constraints import FixAtoms
        for c in self._constraints:
            if not isinstance(c, FixAtoms):
                raise RuntimeError('Remove constraint using set_constraint() '
                                   'before deleting atoms.')

        if isinstance(i, (list, int)):
            # Make sure a list of booleans will work correctly and not be
            # interpreted at 0 and 1 indices.
            i = np.atleast_1d(i)

        n = len(self)
        i = np.arange(n)[i]

        if len(self._constraints) > 0:
            if isinstance(i, int):
                i = [i]
            constraints = []
            for c in self._constraints:
                c = c.delete_atoms(i, n)
                if c is not None:
                    constraints.append(c)
            self.constraints = constraints

        mask = np.ones(len(self), bool)
        mask[i] = False

        for name, a in self.arrays.items():
            self.arrays[name] = a[mask]

        if self.nodes:
            self._graph.remove_nodes_from(i)
            mapping = dict(zip(np.where(mask)[0], np.arange(len(self))))
            nx.relabel_nodes(self._graph, mapping, copy=False)

    def __imul__(self, m):
        """In-place repeat of atoms."""
        if isinstance(m, int):
            m = (m, m, m)

        for x, vec in zip(m, self._cell):
            if x != 1 and not vec.any():
                raise ValueError(
                    'Cannot repeat along undefined lattice vector')

        if self.pbc.any() and len(self.edges()) > 0:
            warnings.warn(("Edge conservation not currently supported with "
                           "pbc. Remove pbc or edges first."))

        M = np.product(m)
        n = len(self)

        for name, a in self.arrays.items():
            self.arrays[name] = np.tile(a, (M, ) + (1, ) * (len(a.shape) - 1))
            cgraph = self._graph.copy()

        positions = self.arrays['positions']
        i0 = 0

        for m0 in range(m[0]):
            for m1 in range(m[1]):
                for m2 in range(m[2]):
                    i1 = i0 + n
                    positions[i0:i1] += np.dot((m0, m1, m2), self._cell)
                    i0 = i1
                    if m0 + m1 + m2 != 0:
                        self._graph = nx.disjoint_union(self._graph, cgraph)

        if self.constraints is not None:
            self.constraints = [c.repeat(m, n) for c in self.constraints]

        self._cell = np.array([m[c] * self._cell[c] for c in range(3)])

        return self
def remove_isolates(G: nx.Graph):
    G.remove_nodes_from(list(nx.isolates(G)))
    return G
Beispiel #18
0
class Gratoms(Atoms):
    """Graph based atoms object.

    An Integrated class for an ASE atoms object with a corresponding
    Networkx Graph.
    """

    def __init__(self,
                 symbols=None,
                 positions=None,
                 numbers=None,
                 tags=None,
                 momenta=None,
                 masses=None,
                 magmoms=None,
                 charges=None,
                 scaled_positions=None,
                 cell=None,
                 pbc=None,
                 celldisp=None,
                 constraint=None,
                 calculator=None,
                 info=None,
                 edges=None):
        super().__init__(symbols, positions, numbers, tags, momenta, masses,
                         magmoms, charges, scaled_positions, cell, pbc,
                         celldisp, constraint, calculator, info)

        if self.pbc.any():
            self._graph = MultiGraph()
        else:
            self._graph = Graph()

        nodes = [[i, {
            'number': n
        }] for i, n in enumerate(self.arrays['numbers'])]
        self._graph.add_nodes_from(nodes)

        if edges:
            self._graph.add_edges_from(edges, bonds=1)

        self._surface_atoms = None

    @property
    def graph(self):
        return self._graph

    @property
    def nodes(self):
        return self._graph.nodes

    @property
    def edges(self):
        return self._graph.edges

    @property
    def adj(self):
        return self._graph.adj

    def get_surface_atoms(self):
        """Return surface atoms."""
        return self._surface_atoms

    def set_surface_atoms(self, surface_atoms):
        """Assign surface atoms."""
        self._surface_atoms = surface_atoms

    def get_neighbor_symbols(self, u):
        """Get chemical symbols for neighboring atoms of u."""
        neighbors = list(self._graph[u])

        return sym[self.arrays['numbers'][neighbors]]

    def is_isomorph(self, other):
        """Check if isomorphic by bond count and atomic number."""
        isomorphic = nx.is_isomorphic(
            self._graph, other._graph, edge_match=em, node_match=nm)

        return isomorphic

    def get_chemical_tags(self, rank=2):
        """Generate a hash descriptive of the chemical formula (rank 0)
        or include bonding (rank 1).
        """
        cnt = np.bincount(self.arrays['numbers'])
        composition = ','.join(cnt.astype(str))

        if rank == 1:
            return composition[2:]

        for adj in self.adj.items():

            num = self.arrays['numbers'][list(adj[1].keys())]
            cnt += np.bincount(num, minlength=len(cnt))

        bonding = ','.join(cnt.astype(str))

        return composition[2:], bonding[2:]

    def get_unsaturated_nodes(self, screen=None):

        unsaturated = []
        for node, data in self.nodes(data=True):
            radicals = data['valence']

            if screen in data:
                continue

            if radicals > 0:
                unsaturated += [node]

        return np.array(unsaturated)

    def copy(self):
        """Return a copy."""
        atoms = self.__class__(cell=self._cell, pbc=self._pbc, info=self.info)

        atoms.arrays = {}
        for name, a in self.arrays.items():
            atoms.arrays[name] = a.copy()
        atoms.constraints = copy.deepcopy(self.constraints)
        atoms._graph = self.graph.copy()

        return atoms

    def __iadd__(self, other):
        """Extend atoms object by appending atoms from *other*."""
        if isinstance(other, Atom):
            other = self.__class__([other])

        n1 = len(self)
        n2 = len(other)

        for name, a1 in self.arrays.items():
            a = np.zeros((n1 + n2, ) + a1.shape[1:], a1.dtype)
            a[:n1] = a1
            if name == 'masses':
                a2 = other.get_masses()
            else:
                a2 = other.arrays.get(name)
            if a2 is not None:
                a[n1:] = a2
            self.arrays[name] = a

        for name, a2 in other.arrays.items():
            if name in self.arrays:
                continue
            a = np.empty((n1 + n2, ) + a2.shape[1:], a2.dtype)
            a[n1:] = a2
            if name == 'masses':
                a[:n1] = self.get_masses()[:n1]
            else:
                a[:n1] = 0

            self.set_array(name, a)

        if isinstance(other, Gratoms):
            if isinstance(self._graph, nx.MultiGraph) & \
               isinstance(other._graph, nx.Graph):
                other._graph = nx.MultiGraph(other._graph)

            self._graph = nx.disjoint_union(self._graph, other._graph)

        return self

    def __delitem__(self, i):
        from ase.constraints import FixAtoms
        for c in self._constraints:
            if not isinstance(c, FixAtoms):
                raise RuntimeError('Remove constraint using set_constraint() '
                                   'before deleting atoms.')

        if isinstance(i, (list, int)):
            # Make sure a list of booleans will work correctly and not be
            # interpreted at 0 and 1 indices.
            i = np.atleast_1d(i)

        if len(self._constraints) > 0:
            n = len(self)
            i = np.arange(n)[i]
            if isinstance(i, int):
                i = [i]
            constraints = []
            for c in self._constraints:
                c = c.delete_atoms(i, n)
                if c is not None:
                    constraints.append(c)
            self.constraints = constraints

        mask = np.ones(len(self), bool)
        mask[i] = False

        for name, a in self.arrays.items():
            self.arrays[name] = a[mask]

        if isinstance(i, slice):
            i = np.arange(n)[i]

        self._graph.remove_nodes_from(i)
        mapping = dict(zip(np.where(mask)[0], np.arange(len(self))))
        nx.relabel_nodes(self._graph, mapping, copy=False)

    def __imul__(self, m):
        """In-place repeat of atoms."""
        if isinstance(m, int):
            m = (m, m, m)

        for x, vec in zip(m, self._cell):
            if x != 1 and not vec.any():
                raise ValueError(
                    'Cannot repeat along undefined lattice vector')

        if self.pbc.any() and len(self.edges()) > 0:
            raise ValueError("Edge conservation not currently supported with "
                             "pbc. Remove pbc or edges first.")

        M = np.product(m)
        n = len(self)

        for name, a in self.arrays.items():
            self.arrays[name] = np.tile(a, (M, ) + (1, ) * (len(a.shape) - 1))
            cgraph = self._graph.copy()

        positions = self.arrays['positions']
        i0 = 0

        for m0 in range(m[0]):
            for m1 in range(m[1]):
                for m2 in range(m[2]):
                    i1 = i0 + n
                    positions[i0:i1] += np.dot((m0, m1, m2), self._cell)
                    i0 = i1
                    if m0 + m1 + m2 != 0:
                        self._graph = nx.disjoint_union(self._graph, cgraph)

        if self.constraints is not None:
            self.constraints = [c.repeat(m, n) for c in self.constraints]

        self._cell = np.array([m[c] * self._cell[c] for c in range(3)])

        return self
Beispiel #19
0
def _dissolve_adjacent(_target_graph: nx.Graph,
                       _parent_node_name: str,
                       _node_group: Union[set, list, tuple],
                       highest_degree=False) -> nx.Graph:
    # set the new centroid from the centroid of the node group's Multipoint:
    node_geoms = []
    if not highest_degree:
        for n_uid in _node_group:
            x = _target_graph.nodes[n_uid]['x']
            y = _target_graph.nodes[n_uid]['y']
            node_geoms.append(geometry.Point(x, y))
    # if by highest_degree, then find the centroid of the highest degree nodes
    else:
        highest_degree = 0
        for n_uid in _node_group:
            if n_uid in _target_graph:
                if nx.degree(_target_graph, n_uid) > highest_degree:
                    highest_degree = nx.degree(_target_graph, n_uid)

        # aggregate the highest degree nodes
        node_geoms = []
        for n_uid in _node_group:
            if n_uid not in _target_graph:
                continue
            if nx.degree(_target_graph, n_uid) != highest_degree:
                continue
            x = _target_graph.nodes[n_uid]['x']
            y = _target_graph.nodes[n_uid]['y']
            # append geom
            node_geoms.append(geometry.Point(x, y))

    # find the new centroid
    c = geometry.MultiPoint(node_geoms).centroid
    _target_graph.add_node(_parent_node_name, x=c.x, y=c.y)

    # remove old nodes and reassign to new parent node
    # first determine new edges
    new_edges = []
    for uid in _node_group:
        for nb_uid in nx.neighbors(_target_graph, uid):
            # drop geoms between merged nodes
            # watch for self-loop edge cases
            if uid in _node_group and nb_uid in _node_group and uid != nb_uid:
                continue
            else:
                if 'geom' not in _target_graph[uid][nb_uid]:
                    raise KeyError(
                        f'Missing "geom" attribute for edge {uid}-{nb_uid}')
                line_geom = _target_graph[uid][nb_uid]['geom']
                if line_geom.type != 'LineString':
                    raise TypeError(
                        f'Expecting LineString geometry but found {line_geom.type} geometry for edge {uid}-{nb_uid}.'
                    )
                # first orient geom in correct direction
                s_x = _target_graph.nodes[uid]['x']
                s_y = _target_graph.nodes[uid]['y']
                # check geom coordinates directionality - flip if facing backwards direction
                if not np.allclose(
                    (s_x, s_y), line_geom.coords[0][:2], atol=0.001, rtol=0):
                    line_geom = geometry.LineString(line_geom.coords[::-1])
                # double check that coordinates now face the forwards direction
                if not np.allclose(
                    (s_x, s_y), line_geom.coords[0][:2], atol=0.001, rtol=0):
                    raise ValueError(
                        f'Edge geometry endpoint coordinate mismatch for edge {uid}-{nb_uid}'
                    )
                # update geom starting point to new parent node's coordinates
                coords = list(line_geom.coords)
                coords[0] = (c.x, c.y)
                # if self-loop, then the end also needs updating
                if uid == nb_uid:
                    coords[-1] = (c.x, c.y)
                    target_uid = _parent_node_name
                else:
                    target_uid = nb_uid
                new_line_geom = geometry.LineString(coords)
                new_edges.append(
                    (_parent_node_name, target_uid, new_line_geom))
    # remove the nodes from the target graph, this will also implicitly drop related edges
    _target_graph.remove_nodes_from(_node_group)
    # add the edges
    for s, e, geom in new_edges:
        # when dealing with a collapsed linestring, this should be a rare occurance
        if geom.length == 0:
            logger.warning(
                f'Encountered a geom of length 0m: check edge {s}-{e}.')
            continue
        # don't add edge duplicates from respectively merged nodes
        if (s, e) not in _target_graph.edges():
            _target_graph.add_edge(s, e, geom=geom)
        # however, do add if substantially different geom...
        else:
            diff = _target_graph[s][e]['geom'].length / geom.length
            if abs(diff) > 1.25:
                _target_graph.add_edge(s, e, geom=geom)

    return _target_graph