<commit_before>from operator import itemgetter

import networkx as nx
from networkx import DiGraph
import pandas as pnd
from shapely.geometry import Point, MultiLineString

from surficial.ops.shape import measure

class Alignment(DiGraph):
    """A directed network graph of LineStrings.

    Alignment is a subclass of networkx.DiGraph and adds methods for addressing points within
    the network. It represents the set of geometries onto which points of interest are
    projected.

    """
    def __init__(self, lines):
        """Construct a directed graph from a set of LineStrings.

        Parameters:
            lines (list of LineString): geometries in the network

        Returns:
            graph (DiGraph): directed network graph

        """
        super().__init__()

        # add the nodes
        endpoints = []
        for line in lines:
            endpoints.append(line.coords[0])
            endpoints.append(line.coords[-1])
        # set makes an exact comparision, whereas below, almost_equals does not
        # so i wind up with an extra node where two lines do not quite meet
        for i, p in enumerate(set(endpoints)):
            self.add_node(i, geom=Point(p))

        # add the edges
        for line in lines:
            node_from = None
            node_to = None
            for n, data in self.nodes(data=True):
                p = data['geom']
                if p.equals(Point(line.coords[0])):
                    node_from = n
                elif p.equals(Point(line.coords[-1])):
                    node_to = n
            self.add_edge(node_from, node_to, geom=line, len=line.length, meas=measure(line))

    def outlet(self):
        """Return the root node in a directed graph.

        In a stream network this represents the drainage outlet.

        Returns:
            n (int): outlet node ID

        """
        for node in self.nodes():
            if self.out_degree(node) == 0:
                return node

    def edge_addresses(self, outlet, weight='len'):
        """Calculate cost path distances from a given node to each graph edge end node. 

        The DataFrame columns are:

            :edge (tuple): tuple of node identifiers identifying an edge
            :address_v (float): the cost path distance between outlet node and edge end node

        Parameters:
            outlet (int): network outlet node ID
<commit_msg>Check Alignment for isolated nodes and multiple subgraphs and warn<commit_after>import warnings
from operator import itemgetter

import networkx as nx
from networkx import DiGraph
import pandas as pnd
from shapely.geometry import Point, MultiLineString

from surficial.ops.shape import measure

class Alignment(DiGraph):
    """A directed network graph of LineStrings.

    Alignment is a subclass of networkx.DiGraph and adds methods for addressing points within
    the network. It represents the set of geometries onto which points of interest are
    projected.

    """
    def __init__(self, lines):
        """Construct a directed graph from a set of LineStrings.

        Parameters:
            lines (list of LineString): geometries in the network

        Returns:
            graph (DiGraph): directed network graph

        """
        super().__init__()

        # add the nodes
        endpoints = []
        for line in lines:
            endpoints.append(line.coords[0])
            endpoints.append(line.coords[-1])
        # set makes an exact comparision, whereas below, almost_equals does not
        # so i wind up with an extra node where two lines do not quite meet
        for i, p in enumerate(set(endpoints)):
            self.add_node(i, geom=Point(p))

        # add the edges
        for line in lines:
            node_from = None
            node_to = None
            for n, data in self.nodes(data=True):
                p = data['geom']
                if p.equals(Point(line.coords[0])):
                    node_from = n
                elif p.equals(Point(line.coords[-1])):
                    node_to = n
            self.add_edge(node_from, node_to, geom=line, len=line.length, meas=measure(line))

        if nx.isolates(self):
            warnings.warn("Found isolated nodes, check input geometries using the repair subcommand. Exiting now.")
        if nx.connected_component_subgraphs(self.to_undirected()):
            warnings.warn("Found multiple subgraphs, check input geometries using the repair subcommand. Exiting now.")

    def outlet(self):
        """Return the root node in a directed graph.

        In a stream network this represents the drainage outlet.

        Returns:
            n (int): outlet node ID

        """
        for node in self.nodes():
            if self.out_degree(node) == 0:
                return node

    def edge_addresses(self, outlet, weight='len'):
        """Calculate cost path distances from a given node to each graph edge end node. 

        The DataFrame columns are:

            :edge (tuple): tuple of node identifiers identifying an edge
            :address_v (float): the cost path distance between outlet node and edge end node

        Parameters:
            outlet (int): network outlet node ID

{'fraction_non_alphanumeric': 0.07363968467602384, 'fraction_numerical': 0.0019227071716977505, 'mean_word_length': 2.148910411622276, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2945947', 'n_tokens_mistral': 1384, 'n_tokens_neox': 1293, 'n_words': 533}