Exemplo n.º 1
0
    def test_edge_costs(self) -> None:
        graph = AngleGraph(self.example_inst, self.working_expl_corr)
        self.cfg.angle_weight = 0
        self.cfg.edge_weight = 0.5
        path = graph.single_sp(**vars(self.cfg))
        path, _, cost_sum = graph.transform_path(path)
        dest_ind = graph.pos2node[tuple(self.dest_inds)]
        dest_costs = np.min(graph.dists[dest_ind])
        a = []
        path = np.array(path)
        weighted_inst = self.example_inst
        for p in range(len(path) - 1):
            line = bresenham_line(path[p, 0], path[p, 1], path[p + 1, 0],
                                  path[p + 1, 1])[1:-1]
            line_costs = [weighted_inst[i, j] for (i, j) in line]
            line_costs = [li for li in line_costs if li < np.inf]
            bresenham_edge_dist = np.mean(line_costs) * self.cfg.edge_weight
            shift_costs = np.linalg.norm(path[p] - path[p + 1])
            # append edge cost
            a.append((0.5 * (weighted_inst[tuple(path[p])] +
                             weighted_inst[tuple(path[p + 1])]) +
                      shift_costs * bresenham_edge_dist))
        dest_costs_gt = np.sum(a)
        self.assertTrue(np.isclose(cost_sum, dest_costs_gt))
        self.assertTrue(np.isclose(dest_costs, dest_costs_gt))
        out_compute_cost_method = compute_geometric_costs(
            path, weighted_inst, edge_weight=self.cfg.edge_weight)
        self.assertEqual(np.sum(out_compute_cost_method), cost_sum)

        self.cfg.angle_weight = 0.25
        self.cfg.edge_weight = 0
Exemplo n.º 2
0
 def test_correct_shortest_path(self) -> None:
     """ Test the implicit line graph construction """
     graph = AngleGraph(self.example_inst, self.working_expl_corr)
     path = graph.single_sp(**vars(self.cfg))
     path, path_costs, cost_sum = graph.transform_path(path)
     self.assertTupleEqual(graph.instance.shape, self.expl_shape)
     self.assertEqual(graph.angle_weight + graph.resistance_weight, 1)
     self.assertNotEqual(len(graph.shifts), 0)
     # all initialized to infinity
     # self.assertTrue(not np.any([graph.dists[graph.dists < np.inf]]))
     # start point was set to normalized value
     start_ind = graph.pos2node[tuple(self.start_inds)]
     self.assertEqual(0, graph.dists[start_ind, 0])
     # all start dists have same value
     self.assertEqual(len(np.unique(graph.dists[start_ind])), 1)
     # not all values still inf
     self.assertLess(np.min(graph.dists[start_ind]), np.inf)
     # get actual best path
     # path, path_costs, cost_sum = graph.get_shortest_path(
     #     self.start_inds, self.dest_inds
     # )
     self.assertNotEqual(len(path), 0)
     self.assertEqual(len(path), len(path_costs))
     self.assertGreaterEqual(cost_sum, 5)
     for (i, j) in path:
         self.assertEqual(self.example_inst[i, j], 1)
Exemplo n.º 3
0
 def test_empty_path(self) -> None:
     self.cfg.max_angle = np.pi
     graph = AngleGraph(self.example_inst, self.no_connection_corr)
     _ = graph.single_sp(**vars(self.cfg))
     # assert that destination can NOT be reached
     dest_ind = graph.pos2node[tuple(self.dest_inds)]
     self.assertFalse(np.min(graph.dists[dest_ind]) < np.inf)
Exemplo n.º 4
0
    def test_discrete(self) -> None:
        """ DISCRETE """
        self.cfg["angle_cost_function"] = "discrete"
        self.cfg["max_angle"] = np.pi / 4
        self.cfg["angle_weight"] = .3
        graph = AngleGraph(self.instance, self.corridor)
        _ = graph.single_sp(**self.cfg)
        gt_angle_cost_arr = graph.angle_cost_array.copy()
        gt_dists = graph.dists.copy()
        graph.dists = np.zeros(
            (len(graph.stack_array), len(graph.shifts))
        ) + np.inf
        graph.dists[0, :] = 0
        graph.preds = np.zeros(graph.dists.shape) - 1
        graph.angle_cost_function = "some_non_existant"
        graph.build_source_sp_tree(**self.cfg)

        self.assertTrue(np.all(gt_angle_cost_arr == graph.angle_cost_array))
        self.assertTrue(np.all(np.isclose(gt_dists, graph.dists)))
Exemplo n.º 5
0
def _initialize_graph(instance, cfg):
    """
    Auxiliary method to preprocess the instance and initialize the graph
    @params:
        instance: 2D numpy array of resistances values (float or int)
                  If cells are forbidden, set them to NaN or a value x and
                  specify x in cfg["forbidden_val"].
        cfg: configuration as specified on top of this file
    @returns:
        graph: AngleGraph object initialized with instance
        cfg: updated configuration file
    """
    forbidden_val = cfg.get("forbidden_val", np.nan)
    logger.info(f"forbidden val: {forbidden_val}")

    # make forbidden region array
    project_region = np.ones(instance.shape)
    project_region[np.isnan(instance)] = 0
    project_region[instance == forbidden_val] = 0

    # instance must not contain zeros
    non_zero_add = 1e-8
    instance += non_zero_add

    # compute maximum values without forbidden weight
    normal_vals = instance[
        np.logical_and(instance != forbidden_val, ~np.isnan(instance))]
    assert np.all(
        normal_vals < np.inf
    ), "check forbidden_val parameter in cfg, it is\
         not inf but there are inf values in array"

    # fill values in instance
    instance[project_region == 0] = np.max(normal_vals)
    # normalize instance by maximum value (excluding forbidden areas)
    # normalization is necessary to balance angle- and resistance-costs
    instance = instance / np.max(normal_vals)
    assert np.min(instance) > 0 and np.isclose(
        np.max(instance), 1
    ), "Minimum must be greater than zero and maximum ~1 after normalizing"

    # initialize graph
    graph = AngleGraph(instance, project_region)

    return graph, cfg
Exemplo n.º 6
0
 def test_angle_sp(self) -> None:
     graph = AngleGraph(self.example_inst, self.high_angle_corr)
     _ = graph.single_sp(**vars(self.cfg))
     # assert that destination can NOT be reached
     dest_ind = graph.pos2node[tuple(self.dest_inds)]
     self.assertFalse(np.min(graph.dists[dest_ind]) < np.inf)
     # Note: this test does not change the stack array because the path is
     # only empty because of an inf in angles_all
     # NEXT TRY: more angles allowed
     self.cfg.max_angle = np.pi
     graph = AngleGraph(self.example_inst, self.high_angle_corr)
     path = graph.single_sp(**vars(self.cfg))
     path, _, cost_sum = graph.transform_path(path)
     # assert that dest CAN be reached
     dest_ind = graph.pos2node[tuple(self.dest_inds)]
     self.assertTrue(np.min(graph.dists[dest_ind]) < np.inf)
Exemplo n.º 7
0
    def test_linear(self) -> None:
        """ LINEAR """
        self.cfg["angle_cost_function"] = "linear"
        self.cfg["max_angle"] = np.pi
        self.cfg["angle_weight"] = .3
        graph = AngleGraph(self.instance, self.corridor)
        _ = graph.single_sp(**self.cfg)
        gt_angle_cost_arr = graph.angle_cost_array.copy()
        gt_dists = graph.dists.copy()
        graph.dists = np.zeros(
            (len(graph.stack_array), len(graph.shifts))
        ) + np.inf
        graph.dists[0, :] = 0
        graph.preds = np.zeros(graph.dists.shape) - 1
        graph.angle_cost_function = "some_non_existant"
        graph.build_source_sp_tree(**self.cfg)

        self.assertTrue(
            np.all(np.isclose(gt_angle_cost_arr, graph.angle_cost_array))
        )
        # Errors can occur randomly, but seem to be only precision errors
        # errors only appear if angle_weight > 0, try 1 to reproduce
        self.assertTrue(np.all(np.isclose(gt_dists, graph.dists, atol=1e-04)))
Exemplo n.º 8
0
def optimal_point_spotting(
    instance, cfg, corridor=None, k=1, algorithm=KSP.ksp
):
    """
    Compute the (angle-) optimal point spotting
    @params:
        instance: 2D np array of resistances (see details top of file)
        cfg: config dict, must contain start and dest (see details top of file)
        corridor: relevant region --> either
            - a path, e.g. output of optimal_route, or
            - a 2D array with 0: do not consider, 1: consider
    @returns:
        a single optimal path of points (list of X Y coordinates)
        or empty list if no path exists
    """
    # initialize graph
    graph, cfg = _initialize_graph(instance, cfg)
    # initial project region
    original_inst = graph.edge_inst.copy()
    original_corr = (graph.instance < np.inf).astype(int)
    # initialize corridor
    if corridor is None:
        corridor = np.ones(original_corr.shape)

    # estimate how many edges the graph will have:
    graph.set_shift(cfg["start_inds"], cfg["dest_inds"], **cfg)
    orig_shifts = len(graph.shift_tuples)
    instance_vertices = np.sum(original_corr * corridor > 0)

    mem_limit = cfg.get("memory_limit", 5e7)
    # define pipeline
    pipeline = cfg.get(
        "pipeline",
        ut_general.get_pipeline(instance_vertices, orig_shifts, mem_limit)
    )
    assert all(
        pipeline[i] > pipeline[i + 1] for i in range(len(pipeline) - 1)
    ), "pipeline must consist of decreasing downsampling factors"
    assert pipeline[
        -1] == 1, "last factor in pipeline must be 1 (= no downsampling)"

    # execute pipeline
    logger.info(f"Pipeline set to: {pipeline}")

    # execute iterative shortest path computation
    for pipe_step, factor in enumerate(pipeline):
        assert isinstance(factor, int) or float(factor).is_integer(
        ), "downsampling factors in pipeline must be integers"
        logger.info(
            f"---------- Start {pipe_step+1}th step {factor} ---------------"
        )
        # rescale and set parameters accordingly
        corridor = (corridor * original_corr > 0).astype(int)
        current_instance, current_corridor, current_cfg = ut_general.rescale(
            original_inst, corridor, cfg, factor
        )
        # run shortest path computation
        graph = AngleGraph(current_instance, current_corridor)
        if k > 1:
            paths = _run_ksp(graph, current_cfg, k, algorithm=algorithm)
            paths = [np.array(path) * factor for path in paths]
        else:
            path = graph.single_sp(**current_cfg)
            paths = [np.asarray(path) * factor]
        logger.debug(f"got {len(paths)} paths in this step")

        # compute next corridor
        if pipe_step < len(pipeline) - 1 and len(paths[0]) > 0:
            corridor = ut_general.pipeline_corridor(
                paths, instance.shape, orig_shifts, mem_limit,
                pipeline[pipe_step + 1]
            )
    if k == 1:
        return path
    else:
        return paths