Skip to content

Commit

Permalink
using dbscan clustering algorithm, all tests pass except clustiner to…
Browse files Browse the repository at this point in the history
…o loosly on large grids
  • Loading branch information
RSid committed Nov 11, 2018
1 parent e147e02 commit 54c1397
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 54 deletions.
11 changes: 11 additions & 0 deletions cluster.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from operator import attrgetter

class Cluster:
def __init__(self, label, destinations):
self.destinations = destinations
self.destination_closest_to_car = min(destinations, key=attrgetter('distance_from_car'))
self.label = label
self.size = len(destinations)

def set_weight(self, normalized_size_weight, normalized_distance_weight):
self.weight = normalized_size_weight + normalized_distance_weight
17 changes: 3 additions & 14 deletions destination.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,8 @@
from utils import Utils

class Destination:
def __init__(self, location, distance_from_car):
def __init__(self, location, distance_from_car, cluster_label):
self.location = location
self.distance_from_car = distance_from_car
self.cluster_weight = 0


def build_cluster_weight(self, destinations, closest_location_distance):
distance_to_neighbors = [Utils.get_taxicab_distance(neighbor.location, self.location) for neighbor in destinations if neighbor.location != self.location]
#let's say locations are clustered 'close' if they're closer to each other
#than the otherwise nearest location is to the car
close_neighbors = [neighbor_distance for neighbor_distance in distance_to_neighbors if neighbor_distance <= closest_location_distance]
if close_neighbors:
self.cluster_weight = len(close_neighbors)
self.cluster_label = cluster_label

def __eq__(self, other):
return self.location == other.location and self.distance_from_car == other.distance_from_car and self.cluster_weight == other.cluster_weight
return self.location == other.location and self.distance_from_car == other.distance_from_car
46 changes: 31 additions & 15 deletions rideshare.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import itertools
from operator import itemgetter
from operator import attrgetter
from sklearn.cluster import DBSCAN
from sklearn import preprocessing
from utils import Utils
from car import Car
from passenger import Passenger
from destination import Destination
from cluster import Cluster

class CityState:
def __init__(self, x, y):
Expand All @@ -17,32 +21,44 @@ def get_locations_to_go(self):
destinations = dropoffs + pickups
return destinations

def build_destinations(self, locations_to_go):
destinations = [Destination(location, Utils.get_taxicab_distance(location, self.car.get_location())) for location in locations_to_go]
def build_clusters(self, locations_to_go):
#TODO: eps must be derived from whole grid size, not just rows
epsilon = int(round(.10 * self.max_rows))
clustering = DBSCAN(eps=epsilon, min_samples=2).fit(locations_to_go)
locations_with_cluster_labels = []
for index, location in enumerate(locations_to_go):
if index in clustering.core_sample_indices_:
labeled_location = (location, clustering.labels_[index])
else:
labeled_location = (location, None)
locations_with_cluster_labels.append(labeled_location)
groups = itertools.groupby(locations_with_cluster_labels, itemgetter(1))
clusters = [Cluster(key, self.build_destinations(list(value))) for key, value in groups]

normalized_cluster_sizes = preprocessing.scale([cluster.size for cluster in clusters])
normalized_cluster_distances = preprocessing.scale([cluster.destination_closest_to_car.distance_from_car for cluster in clusters])
for index, cluster in enumerate(clusters):
cluster.set_weight(normalized_cluster_sizes[index], normalized_cluster_distances[index])
return clusters

def build_destinations(self, clustered_locations):
destinations = [Destination(clustered_location[0], Utils.get_taxicab_distance(clustered_location[0], self.car.get_location()), clustered_location[1]) for clustered_location in clustered_locations]
return destinations

def get_next_destination(self):
locations_to_go = self.get_locations_to_go()
if locations_to_go:
destinations = self.build_destinations(locations_to_go)
closest_to_car = min(destinations, key=attrgetter('distance_from_car'))
[destination.build_cluster_weight(destinations, closest_to_car.distance_from_car) for destination in destinations]

if any(weighted_dest.cluster_weight > 0 for weighted_dest in destinations):
if (closest_to_car.distance_from_car < max(int(.5 * self.max_rows), int(.5 * self.max_columns))):
destinations = [destination for destination in destinations if destination.distance_from_car <= closest_to_car.distance_from_car]
densest_cluster = max(destinations, key=attrgetter('cluster_weight'))
return densest_cluster
else:
return closest_to_car
else:
return Destination(self.car.get_location(), 0)
clusters = self.build_clusters(locations_to_go)
selected_cluster = max(clusters, key=attrgetter('weight'))
return selected_cluster.destination_closest_to_car
return Destination(self.car.get_location(), 0, None)

def increment_time(self, requestJson):
if requestJson:
new_pickups = [Passenger(person) for person in requestJson]
self.car.pickup_requests.extend(new_pickups)
next_destination = self.get_next_destination()
print(next_destination.location)
self.car.move(next_destination.location)
self.car.do_pickups()
self.car.do_dropoffs()
Expand Down
61 changes: 36 additions & 25 deletions tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
class TestRideshareInitialization(unittest.TestCase):

def test_city_state(self):
#GIVEN a city that was instantiated as 2 x2
#GIVEN a city
city = CityState(2,2)

#WHEN I get its car location
Expand All @@ -30,7 +30,7 @@ def test_nothing_to_do(self):

#THEN the state does not change
self.assertEqual(city_state.car.get_location(), (0,0))
self.assertEqual(city_state.get_next_destination(), Destination((0,0), 0))
self.assertEqual(city_state.get_next_destination(), Destination((0,0), 0, None))
print('END SCENARIO')

def test_picks_up_passenger(self):
Expand Down Expand Up @@ -128,7 +128,6 @@ def test_provided_example(self):
self.assertEqual(time_increments_required, 16)
print('END SCENARIO')

class TestComplexRideshareScenarios(unittest.TestCase):
def test_multiple_requests(self):
#GIVEN an initial set of requests
city_state = CityState(8,8)
Expand Down Expand Up @@ -166,54 +165,66 @@ def test_large_lopsided_city(self):
self.assertEqual(time_increments_required, 2954)
print('END SCENARIO')

def test_prefer_dense_clusters_to_closer_locales(self):
class TestComplexRideshareScenarios(unittest.TestCase):
def test_prefer_clusters_to_individuals(self):
#GIVEN a set of requests, 1 of which is close and 2 of which are farther, but near each other
city_state = CityState(50,50)
close_person = {'name' : 'McCavity', 'start' : (10,10), 'end' : (10,10)}
close_person = {'name' : 'McCavity', 'start' : (10,10), 'end' : (10,11)}

request = [close_person,
{'name' : 'Ishmael', 'start' : (15,4), 'end' : (4,3)},
{'name' : 'Mieville', 'start' : (15,5), 'end' : (8,7)}]
{'name' : 'Ishmael', 'start' : (25,4), 'end' : (4,3)},
{'name' : 'Mieville', 'start' : (25,5), 'end' : (8,7)}]

#WHEN I decide which destination to choose
city_state.increment_time(request)

#THEN it is in the dense cluster even though it's further away
#THEN it is in the cluster even though it's further away
next_destination = city_state.get_next_destination()
self.assertEqual(next_destination.location, (15,4))
self.assertEqual(next_destination.location, (25,4))

def test_prefer_denser_clusters_to_less_dense(self):
#GIVEN a set of clustered requests, 1 of which is closer and 1 of which are farther, but denser
def test_prefer_larger_clusters_to_smaller(self):
#GIVEN a set of clustered requests, 1 of which is closer and 1 of which is farther, but larger
city_state = CityState(50,50)
closest_person = [{'name' : 'Queequeg', 'start' : (25,10), 'end' : (40,10)}]
individual = [{'name' : 'Queequeg', 'start' : (26,10), 'end' : (40,10)}]

closer_cluster = [{'name' : 'McCavity', 'start' : (28,10), 'end' : (40,10)},
{'name' : 'Mistoffoles', 'start' : (28,11), 'end' : (20,10)}]

farther_cluster = [{'name' : 'Ishmael', 'start' : (15,4), 'end' : (4,3)},
farther_larger_cluster = [{'name' : 'Ishmael', 'start' : (35,4), 'end' : (4,3)},
{'name' : 'Mieville', 'start' : (35,5), 'end' : (8,17)},
{'name' : 'Starbuck', 'start' : (35,6), 'end' : (30,7)}]
request = closest_person + closer_cluster + farther_cluster

request = individual + closer_cluster + farther_larger_cluster

#WHEN I decide which destination to choose
city_state.increment_time(request)

#THEN it is in the dense cluster even though it's further away
#THEN it is in the larger cluster even though it's further away
next_destination = city_state.get_next_destination()
self.assertEqual(next_destination.location, (15,4))
self.assertEqual(next_destination.location, (35,4))

def test_prefer_closer_locales_if_densest_cluster_is_far(self):
#GIVEN a set of requests, 1 of which is close and 2 of which are very far, but near each other
city_state = CityState(100,100)
close_person = {'name' : 'McCavity', 'start' : (1,2), 'end' : (10,10)}
request = [close_person,
{'name' : 'Ishmael', 'start' : (100,5), 'end' : (4,3)},
{'name' : 'Mieville', 'start' : (100,6), 'end' : (8,7)}]
def test_prefer_close_small_cluster_if_larger_cluster_is_very_far(self):
#GIVEN a set of clustered requests, 1 of which is close and 1 of which is very far
city_state = CityState(500,500)
individual = [{'name' : 'Queequeg', 'start' : (16,10), 'end' : (40,10)}]

closer_cluster = [{'name' : 'McCavity', 'start' : (28,10), 'end' : (40,10)},
{'name' : 'Mistoffoles', 'start' : (28,11), 'end' : (20,10)},
{'name' : 'Jennyanydots', 'start' : (27,11), 'end' : (20,10)}]

distant_large_cluster = [{'name' : 'Ishmael', 'start' : (461,4), 'end' : (4,3)},
{'name' : 'Mieville', 'start' : (460,5), 'end' : (8,17)},
{'name' : 'Starbuck', 'start' : (462,6), 'end' : (30,7)},
{'name' : 'Flask', 'start' : (463,6), 'end' : (30,7)}]

request = individual + closer_cluster + distant_large_cluster

#WHEN I decide which destination to choose
city_state.increment_time(request)

#THEN it is in the dense cluster even though it's further away
#THEN it is in the smaller cluster, even though a larger one exists
next_destination = city_state.get_next_destination()
self.assertEqual(next_destination.location, (1,2))
self.assertEqual(next_destination.location, (28,10))
print('END SCENARIO')

def test_cannot_run_too_slow(self):
Expand Down

0 comments on commit 54c1397

Please sign in to comment.