def test_random(self, _, seed): 'Test method = "random" for a range of seeds' alg = BiDirectional(self.G, self.max_res, self.min_res, seed=seed) # Run and test results alg.run() self.assertEqual(alg.path, self.result_path) self.assertEqual(alg.total_cost, self.total_cost) self.assertTrue(all(alg.consumed_resources == self.consumed_resources))
def test_backward(self): alg = BiDirectional(self.G, self.max_res, self.min_res, direction='backward') alg.run() self.assertEqual(alg.path, self.result_path) self.assertEqual(alg.total_cost, self.total_cost) self.assertTrue(alg.consumed_resources == self.consumed_resources)
def test_bidirectional(self): """ Test BiDirectional with randomly chosen sequence of directions for a range of seeds. """ alg = BiDirectional(self.G, self.max_res, self.min_res) alg.run() self.assertEqual(alg.path, self.result_path) self.assertEqual(alg.total_cost, self.total_cost) self.assertEqual(alg.consumed_resources, self.consumed_resources)
def test_unprocessed(self): 'Test method = "unprocessed"' alg = BiDirectional(self.G, self.max_res, self.min_res, method="unprocessed") alg.run() self.assertEqual(alg.path, self.result_path) self.assertEqual(alg.total_cost, self.total_cost) self.assertTrue(alg.consumed_resources == self.consumed_resources)
def test_bidirectional_forward(self): """Test BiDirectional with custom callback.""" alg = BiDirectional(self.G, self.max_res, self.min_res, direction="forward", REF_callback=self.my_callback) alg.run() self.assertEqual(alg.path, self.result_path) self.assertEqual(alg.total_cost, self.total_cost) self.assertEqual(alg.consumed_resources, self.consumed_resources)
def test_bidirectional(self): """Test BiDirectional with custom callback.""" alg = BiDirectional(self.G, self.max_res, self.min_res, method="unprocessed", REF_callback=self.my_callback) # Overwrite graph as original labelling won't match self.my_callback.G = alg.G alg.run() self.assertEqual(alg.path, self.result_path) self.assertEqual(alg.total_cost, self.total_cost) self.assertEqual(alg.consumed_resources, self.consumed_resources)
def test_unprocessed_time_limit(self): 'Test time_limit parameter' alg = BiDirectional(self.G, self.max_res, self.min_res, method="unprocessed", time_limit=0.001) start = time() alg.run() self.assertTrue(time() - start <= 0.001 + 1e-3) self.assertEqual(alg.path, self.result_path) self.assertEqual(alg.total_cost, self.total_cost) self.assertTrue(alg.consumed_resources == self.consumed_resources)
def solve(self, time_limit): """ Solves the subproblem with cspy. Time limit is reduced by 0.5 seconds as a safety window. Resolves at most twice: 1. using elementary = False, 2. using elementary = True, and threshold, if a route has already been found previously. """ if not self.run_subsolve: return self.routes, False self.formulate() logger.debug("resources = {}".format(self.resources)) logger.debug("min res = {}".format(self.min_res)) logger.debug("max res = {}".format(self.max_res)) more_routes = False my_callback = self.get_REF() direction = ("forward" if (self.time_windows or self.pickup_delivery or self.distribution_collection) else "both") # Run only twice: Once with `elementary=False` check if route already # exists. s = ([False, True] if (not self.distribution_collection and not self.elementary) else [True]) for elementary in s: if elementary: # Use threshold if non-elementary (safe-guard against large # instances) thr = self._avg_path_len * min(self.G.edges[i, j]["weight"] for (i, j) in self.G.edges()) else: thr = None logger.debug( f"Solving subproblem using elementary={elementary}, threshold={thr}, direction={direction}" ) alg = BiDirectional( self.sub_G, self.max_res, self.min_res, threshold=thr, direction=direction, time_limit=time_limit - 0.5 if time_limit else None, elementary=elementary, REF_callback=my_callback, # pickup_delivery_pairs=self.pickup_delivery_pairs, ) # Pass processed graph if my_callback is not None: my_callback._sub_G = alg.G my_callback._source_id = alg._source_id my_callback._sink_id = alg._sink_id alg.run() logger.debug("subproblem") logger.debug("cost = %s", alg.total_cost) logger.debug("resources = %s", alg.consumed_resources) if alg.total_cost is not None and alg.total_cost < -(1e-3): new_route = self.create_new_route(alg.path) logger.debug(alg.path) path_len = len(alg.path) if not any( list(new_route.edges()) == list(r.edges()) for r in self.routes): more_routes = True self.routes.append(new_route) self.total_cost = new_route.graph["cost"] logger.debug("reduced cost = %s", alg.total_cost) logger.debug("real cost = %s", self.total_cost) if path_len > 2: self._avg_path_len += ( path_len - self._avg_path_len) / self._iters self._iters += 1 break else: logger.info("Route already found, finding elementary one") else: break return self.routes, more_routes
def solve(self, time_limit): """ Solves the subproblem with cspy. Resolves until: 1. heuristic algorithm gives a new route (column with -ve reduced cost); 2. exact algorithm gives a new route; 3. neither heuristic nor exact give a new route. Note : time_limit has no effect for the moment """ if not self.run_subsolve: return self.routes, False self.formulate() logger.debug("resources = {}".format(self.resources)) logger.debug("min res = {}".format(self.min_res)) logger.debug("max res = {}".format(self.max_res)) more_routes = False while True: if self.exact: logger.debug("solving with bidirectional") self.alg = BiDirectional( self.sub_G, self.max_res, self.min_res, direction="both", method="generated", REF_forward=self.get_REF("forward"), REF_backward=self.get_REF("backward"), REF_join=self.get_REF("join"), ) else: logger.debug("solving with greedyelim") self.alg = GreedyElim( self.sub_G, self.max_res, self.min_res, REF=self.get_REF("forward"), max_depth=40, ) self.alg.run() logger.debug("subproblem") logger.debug("cost = %s" % self.alg.total_cost) logger.debug("resources = %s" % self.alg.consumed_resources) if self.alg.total_cost < -(10**-3): more_routes = True self.add_new_route() logger.debug("new route %s" % self.alg.path) logger.debug("reduced cost = %s" % self.alg.total_cost) logger.debug("real cost = %s" % self.total_cost) break # If not already solved exactly elif not self.exact: # Solve exactly from here on self.exact = True # Solved heuristically and exactly and no more routes else: break return self.routes, more_routes
def test_time_limit_raises(self): 'Time limit of 0 raises an exception' alg = BiDirectional(self.G, self.max_res, self.min_res, time_limit=0) alg.run() with self.assertRaises(Exception) as context: alg.path