예제 #1
0
    def __init__(self, G, max_res, min_res, preprocess, REF, algorithm=None):
        # Check inputs
        check(G, max_res, min_res, REF_forward=REF, algorithm=__name__)
        # Preprocess graph
        self.G = preprocess_graph(G, max_res, min_res, preprocess, REF)

        self.max_res = max_res
        self.min_res = min_res
        # Update resource extension function if given
        if REF:
            self.REF = REF
        else:
            self.REF = add

        if negative_edge_cycle(G) or algorithm == "simple":
            self.algorithm = "simple"
        else:
            self.algorithm = "astar"

        # Attribute to hold source-sink path
        self.st_path = None

        # Attribrutes for exposure #
        # resource feasible source-sink path
        self.best_path = None
        # Final cost
        self.best_path_cost = None
        # Final resource consumption
        self.best_path_total_res = None
예제 #2
0
    def __init__(
            self,
            G: DiGraph,
            max_res: List[float],
            min_res: List[float],
            preprocess: Optional[bool] = False,
            direction: Optional[str] = "both",
            method: Optional[str] = "unprocessed",
            time_limit: Optional[Union[float, int]] = None,
            threshold: Optional[float] = None,
            elementary: Optional[bool] = False,
            bounds_pruning: Optional[bool] = False,
            find_critical_res: Optional[bool] = False,
            critical_res: Optional[int] = None,
            # seed: Union[int] = None,
            REF_callback: Optional[REFCallback] = None):
        # Check inputs
        check(G, max_res, min_res, direction, REF_callback, __name__)
        # check_seed(seed, __name__)
        # Preprocess and save graph
        self.G: DiGraph = preprocess_graph(G, max_res, min_res, preprocess,
                                           REF_callback)
        # Vertex id with source/sink
        self._source_id: int = None
        self._sink_id: int = None

        max_res_vector = _convert_list_to_double_vector(max_res)
        min_res_vector = _convert_list_to_double_vector(min_res)

        # Pass graph
        self._init_graph()
        self.bidirectional_cpp = BiDirectionalCpp(len(self.G.nodes()),
                                                  len(self.G.edges()),
                                                  self._source_id,
                                                  self._sink_id,
                                                  max_res_vector,
                                                  min_res_vector)
        self._load_graph()
        # pass solving attributes
        if direction != "both":
            self.bidirectional_cpp.setDirection(direction)
        if method in ["random", "generated", "processed"]:
            self.bidirectional_cpp.setMethod(method)
        if time_limit is not None and isinstance(time_limit, (int, float)):
            self.bidirectional_cpp.setTimeLimit(time_limit)
        if threshold is not None and isinstance(time_limit, (int, float)):
            self.bidirectional_cpp.setThreshold(threshold)
        if isinstance(elementary, bool) and elementary:
            self.bidirectional_cpp.setElementary(True)
        if isinstance(bounds_pruning, bool) and not bounds_pruning:
            self.bidirectional_cpp.setBoundsPruning(bounds_pruning)
        if isinstance(find_critical_res, bool) and critical_res:
            self.bidirectional_cpp.setFindCriticalRes(True)
        if isinstance(critical_res, int) and critical_res != 0:
            self.bidirectional_cpp.setCriticalRes(critical_res)
        if REF_callback is not None:
            # Add a Python callback (caller owns the callback, so we
            # disown it first by calling __disown__).
            # see: https://github.com/swig/swig/blob/b6c2438d7d7aac5711376a106a156200b7ff1056/Examples/python/callback/runme.py#L36
            self.bidirectional_cpp.setREFCallback(REF_callback.__disown__())
예제 #3
0
    def __init__(self,
                 G: DiGraph,
                 max_res: List[float],
                 min_res: List[float],
                 preprocess: Optional[bool] = False,
                 direction: Optional[str] = "both",
                 method: Optional[str] = "random",
                 time_limit: Optional[float] = None,
                 threshold: Optional[float] = None,
                 elementary: Optional[bool] = False,
                 dominance_frequency: Optional[int] = 1,
                 seed: Union[int, RandomState, None] = None,
                 REF_forward: Optional[Callable] = None,
                 REF_backward: Optional[Callable] = None,
                 REF_join: Optional[Callable] = None):
        # Check inputs
        check(G,
              max_res,
              min_res,
              direction,
              REF_forward=REF_forward,
              REF_backward=REF_backward,
              REF_join=REF_join,
              algorithm=__name__)
        # Preprocess graph
        self.G = preprocess_graph(G, max_res, min_res, preprocess, REF_forward)

        self.max_res = max_res.copy()
        self.min_res = min_res.copy()
        self.max_res_in, self.min_res_in = array(max_res.copy()), array(
            min_res.copy())
        self.direction = direction
        self.method = method
        self.time_limit = time_limit
        self.elementary = elementary
        self.threshold = threshold
        self.dominance_frequency = dominance_frequency
        self.random_state = check_seed(seed)
        # Set label class attributes
        Label.REF_forward = REF_forward if REF_forward else add
        Label.REF_backward = REF_backward if REF_backward else sub

        self.REF_join = REF_join

        # Algorithm specific attributes
        self.iteration = 0
        # Containers for labels
        self.current_label: Dict[str, Label] = None
        self.unprocessed_labels: Dict[str, List[Label]] = None
        self.best_labels: Dict[str, List[Label]] = None
        # Containers for counters
        self.unprocessed_counts: Dict[str, int] = 0
        self.processed_counts: Dict[str, int] = 0
        self.generated_counts: Dict[str, int] = 0
        # For exposure
        self.final_label: Label = None
        self.best_label: Label = None
        # Populate containers
        self._init_containers()
예제 #4
0
    def __init__(self,
                 G: DiGraph,
                 max_res: List[float],
                 min_res: List[float],
                 preprocess: Optional[bool] = False,
                 direction: Optional[str] = "both",
                 method: Optional[str] = "unprocessed",
                 time_limit: Optional[Union[float, int]] = None,
                 threshold: Optional[float] = None,
                 elementary: Optional[bool] = False,
                 bounds_pruning: Optional[bool] = False,
                 seed: Union[int] = None,
                 REF_callback: Optional[REFCallback] = None):
        # Check inputs
        check(G, max_res, min_res, direction, REF_callback, __name__)
        # check_seed(seed, __name__)
        # Preprocess graph
        G = preprocess_graph(G, max_res, min_res, preprocess, REF_callback)
        # To save original node type (for conversion at the end)
        self._original_node_type: str = None

        max_res_vector = _convert_list_to_double_vector(max_res)
        min_res_vector = _convert_list_to_double_vector(min_res)

        self.bidirectional_cpp = BiDirectionalCpp(len(G.nodes()),
                                                  len(G.edges()),
                                                  max_res_vector,
                                                  min_res_vector)
        # pass solving attributes
        if direction != "both":
            self.bidirectional_cpp.direction = direction
        if method in ["random", "generated", "processed"]:
            self.bidirectional_cpp.method = method
        if time_limit is not None and isinstance(time_limit, (int, float)):
            self.bidirectional_cpp.time_limit = time_limit
        if threshold is not None and isinstance(time_limit, (int, float)):
            self.bidirectional_cpp.threshold = threshold
        if isinstance(elementary, bool) and elementary:
            self.bidirectional_cpp.elementary = elementary
        if isinstance(bounds_pruning, bool) and not bounds_pruning:
            self.bidirectional_cpp.bounds_pruning = bounds_pruning
        if isinstance(seed, int) and seed is not None:
            self.bidirectional_cpp.setSeed(seed)
        if REF_callback is not None:
            # Add a Python callback (caller owns the callback, so we
            # disown it first by calling __disown__).
            # see: https://github.com/swig/swig/blob/b6c2438d7d7aac5711376a106a156200b7ff1056/Examples/python/callback/runme.py#L36
            self.bidirectional_cpp.setREFCallback(REF_callback.__disown__())

        # Pass graph
        self._init_graph(G)
예제 #5
0
    def testFormatting(self):
        """
        Check if wrong formatting ang graph attributes raise appropriate error
        messages.
        """
        with self.assertRaises(Exception) as context:
            check(self.H, self.max_res, self.min_res, 1, "foo", "alg")

        self.assertTrue(
            "REF functions must be callable" in str(context.exception))
        self.assertTrue(
            "Input must be a nx.Digraph()" in str(context.exception))

        # Turn MultiGraph into DiGraph
        self.H = DiGraph(self.H)
        with self.assertRaises(Exception) as context:
            check(self.H, self.max_res, [1, 2])
        self.assertTrue(
            "Input graph must have 'n_res' attribute" in str(context.exception))
        self.assertTrue("Input graph must have edges with 'res_cost' attribute"
                        in str(context.exception))
        self.assertTrue(
            "Input lists have to be equal length" in str(context.exception))
        with self.assertRaises(Exception) as context:
            check(self.H, self.max_res, [2], 1)
        self.assertTrue(
            "Elements of input lists must be numbers" in str(context.exception))
예제 #6
0
def preprocess_graph(
    G,
    max_res,
    min_res,
    preprocess,
    REF=None,
):
    """
    Applies preprocessing that removes nodes that cannot be reached due to
    resource limits.

    Parameters
    ----------
    G : object instance :class:`nx.Digraph()`
        must have ``n_res`` graph attribute and all edges must have
        ``res_cost`` attribute.

    max_res : list of floats, optional
        :math:`[L, M_1, M_2, ..., M_{n\_res}]`
        upper bound for resource usage.
        We must have ``len(max_res)`` :math:`\geq 2`

    min_res : list of floats, optional
        :math:`[U, L_1, L_2, ..., L_{nres}]` lower bounds for resource usage.
        We must have ``len(min_res)`` :math:`=` ``len(max_res)`` :math:`\geq 2`

    preprocess : bool
        enables preprocessing routine.

    :return: If ``preprocess`` is True, returns the preprocessed graph if no
        exceptions are raised.

    """
    if REF:
        # Cannot apply pruning with custom REFs
        return deepcopy(G.copy())
    if preprocess:
        G = prune_graph(G, max_res, min_res)
        check(G)
    return deepcopy(G.copy())
예제 #7
0
    def __init__(self, G, max_res, min_res, REF=None, preprocess=False):
        # Check inputs
        check(G, max_res, min_res, REF, algorithm=__name__)
        # Preprocess graph
        self.G = preprocess_graph(G, max_res, min_res, preprocess, REF)

        self.max_res = max_res
        self.min_res = min_res
        # Update resource extension function if given
        if REF:
            self.REF = REF
        else:
            self.REF = add

        # Attribute to hold source-sink path
        self.st_path = None

        # Attribrutes for exposure #
        # resource feasible source-sink path
        self.best_path = None
        # Final cost
        self.best_path_cost = None
        # Final resource consumption
        self.best_path_total_res = None
예제 #8
0
    def __init__(self,
                 G,
                 max_res,
                 min_res,
                 preprocess=False,
                 direction="both",
                 method="random",
                 seed=None,
                 REF_forward=None,
                 REF_backward=None,
                 REF_join=None):

        # Check inputs
        check(G,
              max_res,
              min_res,
              REF_forward=REF_forward,
              REF_backward=REF_backward,
              REF_join=REF_join,
              direction=direction,
              algorithm=__name__)
        # Preprocess graph
        self.G = preprocess_graph(G, max_res, min_res, preprocess, REF_forward)
        self.REF_join = REF_join
        self.direc_in = direction
        self.max_res, self.min_res = max_res.copy(), min_res.copy()
        self.max_res_in, self.min_res_in = array(max_res.copy()), array(
            min_res.copy())
        self.method = method
        # To expose results
        self.best_label = None

        # Algorithm specific parameters #
        # set bounds for bacward search
        bwd_start = deepcopy(min_res)
        bwd_start[0] = max_res[0]
        # Current forward and backward labels
        self.current_label = OrderedDict({
            "forward":
            Label(0, "Source", min_res, ["Source"]),
            "backward":
            Label(0, "Sink", bwd_start, ["Sink"])
        })
        # Unprocessed labels dict (both directions)
        self.unprocessed_labels = OrderedDict({
            "forward": deque(),
            "backward": deque()
        })
        # All generated label
        self.generated_labels = OrderedDict({"forward": 0, "backward": 0})
        # Best labels
        # (with initial labels for small cases, see:
        # https://github.com/torressa/cspy/issues/38 )
        self.best_labels = OrderedDict({
            "forward":
            deque([self.current_label["forward"]]),
            "backward":
            deque([self.current_label["backward"]])
        })
        # Final labels dicts for unidirectional search
        self.final_label = None

        # If given, set REFs for dominance relations and feasibility checks
        if REF_forward:
            Label._REF_forward = REF_forward
        else:
            Label._REF_forward = add
        if REF_backward:
            Label._REF_backward = REF_backward
        else:
            Label._REF_backward = sub
        # Init with seed if given
        if seed is None:
            self.random_state = RandomState()
        elif isinstance(seed, int):
            self.random_state = RandomState(seed)
        elif isinstance(seed, RandomState):
            self.random_state = seed
        else:
            raise Exception("{} cannot be used to seed".format(seed))
예제 #9
0
    def __init__(self,
                 G,
                 max_res,
                 min_res,
                 REF=None,
                 preprocess=False,
                 direction="both",
                 method="random",
                 seed=None):
        # Check inputs and preprocess G unless option disabled
        check(G, max_res, min_res, REF, direction, __name__)
        # Preprocess graph
        self.G = preprocess_graph(G, max_res, min_res, preprocess, REF)
        self.direc_in = direction
        self.max_res, self.min_res = max_res.copy(), min_res.copy()
        self.max_res_in, self.min_res_in = array(max_res.copy()), array(
            min_res.copy())
        self.method = method
        # To expose results
        self.best_label = None

        # Algorithm specific parameters #
        # set bounds for bacward search
        bwd_start = deepcopy(min_res)
        bwd_start[0] = max_res[0]
        # Current forward and backward labels
        self.current_label = OrderedDict({
            "forward":
            Label(0, "Source", min_res, ["Source"]),
            "backward":
            Label(0, "Sink", bwd_start, ["Sink"])
        })
        # Unprocessed labels dict (both directions)
        self.unprocessed_labels = OrderedDict({
            "forward": deque(),
            "backward": deque()
        })
        # All generated label
        self.generated_labels = OrderedDict({"forward": 0, "backward": 0})
        # To save all best labels
        self.best_labels = OrderedDict({
            "forward": deque(),
            "backward": deque()
        })
        # Final labels dicts for unidirectional search
        self.final_label = None

        # If given, set REFs for dominance relations and feasibility checks
        if REF:
            Label._REF = REF
        else:
            Label._REF = add
        # Init with seed if given
        if seed is None:
            self.random_state = RandomState()
        elif isinstance(seed, int):
            self.random_state = RandomState(seed)
        elif isinstance(seed, RandomState):
            self.random_state = seed
        else:
            raise Exception("{} cannot be used to seed".format(seed))