예제 #1
0
    def connect_features(cls, catchment: Catchment, nexus: Nexus,
                         is_catchment_upstream: bool):
        """
        Make the connections on both sides between this catchment and nexus.

        Set the connections for this catchment and nexus pair.  How the upstream/downstream relationship exists is
        indicated by another parameter.  E.g., if ``is_catchment_upstream`` is ``True``, then the catchment's
        ::attribute:`Catchment.outflow` property is set to this nexus, and the catchment is added to the nexus's
        ::attribute:`Nexus.contributing_catchments` tuple (or the tuple is created with the catchment placed in it).

        Other properties, in particular those on the other "side" of each entity from where the two are connected, are
        not altered.

        Note that, for ::class:`Nexus` properties, duplication is avoided, but otherwise the catchment will be added to
        a collection property (potentially with the collection object being created).  However, for ::class`Catchment`
        properties, which are each an individual reference, the reference will be always be set.  No check is performed
        as to whether the property currently references ``None``.

        Parameters
        ----------
        catchment : Catchment
            The upstream/downstream catchment in the connected pair.
        nexus : Nexus
            The upstream/downstream (with this being opposite the state of ``catchment``) nexus in the connected pair.
        is_catchment_upstream : bool
            Whether ``catchment`` is connected upstream of ``nexus``.
        """
        if is_catchment_upstream:
            # Add catchment to nexus's collection of contributing, accounting for it being in there or the collection
            # needing to be created
            if nexus.contributing_catchments is None:
                nexus._contributing_catchments = (catchment, )
            elif catchment.id not in [
                    cat.id for cat in nexus.contributing_catchments
            ]:
                nexus._contributing_catchments = nexus.contributing_catchments + (
                    catchment, )
            # Add nexus as catchment's outflow
            catchment._outflow = nexus
        else:
            # Add catchment to nexus's collection of receiving, accounting for it being in there or the collection
            # needing to be created
            if nexus.receiving_catchments is None:
                nexus._receiving_catchments = (catchment, )
            elif catchment.id not in [
                    cat.id for cat in nexus.receiving_catchments
            ]:
                nexus._receiving_catchments = nexus.receiving_catchments + (
                    catchment, )
            # Add nexus as catchment's inflow
            catchment._inflow = nexus
예제 #2
0
def nexus():
    """
        Nexus object to test
    """
    nexus_id = 'nex-test'

    catchment_id_receiving0 = 'cat-hymod-receive0'
    catchment_id_receiving1 = 'cat-hymod-receive1'
    catchment_id_contributing0 = 'cat-hymod-contribute0'
    catchment_id_contributing1 = 'cat-hymod-contribute1'
    data_path = _current_dir.joinpath('data')
    data_file = data_path.joinpath('example_realization_config.json')
    with open(data_file) as fp:
        data = json.load(fp)
    params = {}

    receiving_catchments = (Catchment(catchment_id_receiving0, params),
                            Catchment(catchment_id_receiving1, params))  #tuple

    contributing_catchments = [
        Catchment(catchment_id_contributing0, params),
        Catchment(catchment_id_contributing1, params)
    ]  #list

    location = HydroLocation(nexus_id, (0, 0), HydroLocationType.UNDEFINED,
                             None)

    nexus = Nexus(nexus_id, location, receiving_catchments,
                  contributing_catchments)
    receiving_catchments[0]._inflow = nexus
    receiving_catchments[1]._inflow = nexus
    contributing_catchments[0]._outflow = nexus
    contributing_catchments[1]._outflow = nexus

    yield nexus
예제 #3
0
    def hydrofabric_graph(self) -> Dict[str, Union[Catchment, Nexus]]:
        """
        Lazily get the hydrofabric object graph as a dictionary of the elements by their ids.

        Using data in ::attribute:`catchment_geodataframe` and ::attribute:`nexus_geodataframe`, method generates (when
        necessary) a hydrofabric object graph of associated ::class:`Catchment` and ::class:`Nexus` objects, including
        all the inter-object relationships.  These are collected in dictionary data structure keyed by the ``id``
        property of each object.  Once that is created, the backing attribute is set to this dictionary for subsequent
        reuse, and the dictionary is returned.

        Returns
        -------
        Dict[str, Union[Catchment, Nexus]]
            A dictionary of the nodes of the graph keyed by each's string id value.
        """
        if self._hydrofabric_graph is None:
            # Keys of nexus id to lists of catchment ids for the catchments receiving water from this nexus
            nexus_receiving_cats = dict()
            # Keys of nexus id to lists of catchment ids for the catchments contributing water to this nexus
            nexus_contrib_cats = dict()
            known_catchment_ids = set()
            known_nexus_ids = set()
            cat_to = dict()
            cat_from = dict()

            for cat_id in self.catchment_geodataframe.index:
                known_catchment_ids.add(cat_id)
                # TODO: do we need to account for more than one downstream?
                to_nex_id = self.catchment_geodataframe.loc[cat_id][
                    'toid'].strip()
                known_nexus_ids.add(to_nex_id)
                cat_to[cat_id] = to_nex_id
                if to_nex_id in nexus_contrib_cats:
                    nexus_contrib_cats[to_nex_id].add(cat_id)
                else:
                    nexus_contrib_cats[to_nex_id] = {cat_id}
                # TODO: do we need to account for contained/containing/conjoined?
            for nex_id in self.nexus_geodataframe.index:
                known_nexus_ids.add(nex_id)
                to_cats = self.nexus_geodataframe.loc[nex_id]['toid']
                # Handle the first one with conditional check separate, to optimize later ones
                first_cat_id = to_cats.split(',')[0].strip()
                if nex_id in nexus_receiving_cats and to_cats:
                    nexus_receiving_cats[nex_id].add(first_cat_id)
                else:
                    nexus_receiving_cats[nex_id] = {first_cat_id}
                known_catchment_ids.add(first_cat_id)
                cat_from[first_cat_id] = nex_id
                # Now add any remaining
                for cat_id in to_cats[1:]:
                    clean_cat_id = cat_id.strip()
                    nexus_receiving_cats[nex_id].add(clean_cat_id)
                    known_catchment_ids.add(clean_cat_id)
                    cat_from[cat_id] = nex_id

            hf = dict()
            # Create the catchments first, just without any upstream/downstream connections
            for cat_id in known_catchment_ids:
                # TODO: do params need to be something different?
                hf[cat_id] = Catchment(catchment_id=cat_id, params=dict())
            # Create the nexuses next, applying the right collections of catchments for contrib and receiv
            for nex_id in known_nexus_ids:
                contributing = set()
                for cid in nexus_contrib_cats[nex_id]:
                    contributing.add(hf[cid])
                receiving = set()
                for cid in nexus_receiving_cats[nex_id]:
                    receiving.add(hf[cid])
                hf[nex_id] = Nexus(
                    nexus_id=nex_id,
                    hydro_location=HydroLocation(realized_nexus=nex_id),
                    receiving_catchments=list(receiving),
                    contributing_catchments=list(contributing))
            # Now go back and apply the right to/from relationships for catchments
            for cat_id, nex_id in cat_to.items():
                hf[cat_id]._outflow = hf[nex_id]
            for cat_id, nex_id in cat_from.items():
                hf[cat_id]._inflow = hf[nex_id]
            # TODO: again, do we need to worry about contained/containing/conjoined?
            # Finally ...
            self._hydrofabric_graph = hf
        return self._hydrofabric_graph
예제 #4
0
    def get_subset_hydrofabric(
            self, subset: SubsetDefinition) -> 'MappedGraphHydrofabric':
        """
        Derive a hydrofabric object from this one with only entities included in a given subset.

        Parameters
        ----------
        subset : SubsetDefinition
            Subset describing which catchments/nexuses from this instance may be included in the produced hydrofabric.

        Returns
        -------
        GeoJsonHydrofabric
            A hydrofabric object that is a subset of this instance as defined by the given param.
        """
        new_graph: Dict[str, Union[Catchment, Nexus]] = dict()
        new_graph_roots = set()
        # TODO: consider changing to implement via Pickle and copy module later
        graph_features_stack = list(self.roots)
        already_seen = set()
        # keep track of new graph entities where we can't immediately link to one (or more) of their parents
        unlinked_to_parent = defaultdict(set)
        # Keep track of catchments with related nested catchments to handle at the end
        have_nested_catchments = set()

        while len(graph_features_stack) > 0:
            feature_id = graph_features_stack.pop()
            if feature_id in already_seen:
                continue
            else:
                already_seen.add(feature_id)
            old_cat = self._hydrofabric_graph[feature_id]
            # add ids for all downstream connected features to graph_features_stack for later processing
            graph_features_stack.extend(
                self.get_ids_of_connected(old_cat,
                                          upstream=False,
                                          downstream=True))

            subset_copy_of_feature = None
            # Assume False means feature is a Nexus
            is_catchment = False
            # If feature is in subset, make the start of a deep copy.
            #   Note that the deep copy's upstream refs are handled in next step, and downstream refs are handled during
            #   creation of the subset copy of the downstream object
            if feature_id in subset.catchment_ids:
                is_catchment = True
                subset_copy_of_feature = Catchment(
                    feature_id, params=dict(), realization=old_cat.realization)
                # Must track and handle (later) contained_catchments, containing_catchment, and conjoined_catchments
                if old_cat.containing_catchment is not None:
                    if old_cat.containing_catchment.id in subset.catchment_ids:
                        have_nested_catchments.add(feature_id)
                        graph_features_stack.append(
                            old_cat.containing_catchment.id)
                if old_cat.contained_catchments:
                    contained_ids = [
                        c.id for c in old_cat.contained_catchments
                        if c.id in subset.catchment_ids
                    ]
                    have_nested_catchments.update(contained_ids)
                    graph_features_stack.extend(contained_ids)
                if old_cat.conjoined_catchments:
                    conjoined_ids = [
                        c.id for c in old_cat.conjoined_catchments
                        if c.id in subset.catchment_ids
                    ]
                    have_nested_catchments.update(conjoined_ids)
                    graph_features_stack.extend(conjoined_ids)

            elif feature_id in subset.nexus_ids:
                subset_copy_of_feature = Nexus(
                    feature_id, hydro_location=old_cat._hydro_location)

            # Will be None when not in subset, so ...
            if subset_copy_of_feature is not None:
                # add to new_graph
                new_graph[feature_id] = subset_copy_of_feature
                # Get the ids of parents in the subset
                parent_ids = self.get_ids_of_connected(old_cat,
                                                       upstream=True,
                                                       downstream=False)
                pids_in_subset = [
                    pid for pid in parent_ids
                    if (pid in subset.catchment_ids or pid in subset.nexus_ids)
                ]
                # If old feature does not have any parents that will be in the subset, this is a new root
                if len(pids_in_subset) == 0:
                    new_graph_roots.add(feature_id)
                # For each parent in the subset, set up ref to new copy of parent and its ref down to feature's new copy
                for pid in pids_in_subset:
                    # if parent copy in new graph (i.e., the copy exists)
                    if pid in new_graph:
                        # set upstream connections to parent copy and downstream connection of parent copy to this
                        if is_catchment:
                            self.connect_features(
                                catchment=subset_copy_of_feature,
                                nexus=new_graph[pid],
                                is_catchment_upstream=False)
                        else:
                            self.connect_features(catchment=new_graph[pid],
                                                  nexus=subset_copy_of_feature,
                                                  is_catchment_upstream=True)
                    # If parent copy not in new graph yet (i.e., does not exist), add to collection to deal with later
                    else:
                        unlinked_to_parent[feature_id].add(pid)

        # Now deal with any previously unlinked parents
        for feature_id in unlinked_to_parent:
            new_cat = new_graph[feature_id]
            if isinstance(new_cat, Catchment):
                for pid in unlinked_to_parent[feature_id]:
                    self.connect_features(catchment=new_cat,
                                          nexus=new_graph[pid],
                                          is_catchment_upstream=False)
            else:
                for pid in unlinked_to_parent[feature_id]:
                    self.connect_features(catchment=new_graph[pid],
                                          nexus=new_cat,
                                          is_catchment_upstream=True)

        # Also deal with any catchment conjoined, containing, or contained collections
        for cid in subset.catchment_ids:
            old_cat = self._hydrofabric_graph[cid]
            new_cat = new_graph[cid]
            if old_cat.containing_catchment is not None and old_cat.containing_catchment.id in subset.catchment_ids:
                new_cat._containing_catchment = new_graph[
                    old_cat.containing_catchment.id]
            if old_cat.contained_catchments:
                for contained_id in [
                        c.id for c in old_cat.contained_catchments
                        if c.id in subset.catchment_ids
                ]:
                    if contained_id not in [
                            c.id for c in new_cat.contained_catchments
                    ]:
                        new_cat.contained_catchments = new_cat.contained_catchments + (
                            new_graph[contained_id], )
            if old_cat.conjoined_catchments:
                for conjoined_id in [
                        c.id for c in old_cat.conjoined_catchments
                        if c.id in subset.catchment_ids
                ]:
                    if conjoined_id not in [
                            c.id for c in new_cat.conjoined_catchments
                    ]:
                        new_cat.conjoined_catchments = new_cat.conjoined_catchments + (
                            new_graph[conjoined_id], )

        return MappedGraphHydrofabric(hydrofabric_object_graph=new_graph,
                                      roots=frozenset(new_graph_roots),
                                      graph_creator=self)