Skip to content

Commit

Permalink
Add black formatter (#15)
Browse files Browse the repository at this point in the history
* Add black formatter to dependencies

* Document contributing changes

Add formatter check in CI tests

* Run black formatter

* Limit line length to 79 characters

* Add flake8 to docs
  • Loading branch information
Fillipe Goulart authored Apr 26, 2021
1 parent 62b0f96 commit e39b6ad
Show file tree
Hide file tree
Showing 18 changed files with 329 additions and 55 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ jobs:
#- name: Check type hints with mypy
#run: |
#poetry run mypy --ignore-missing-imports .
- name: Check whether black formatter was run
run: |
poetry run black --check .
- name: Run tests and check coverage
run: |
poetry run pytest tests/
40 changes: 40 additions & 0 deletions docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,43 @@ poetry run python -m loggibud.v1.eval.task1 \
--instance tests/results/cvrp-instances/train/rj-0-cvrp-0.json \
--solution results/rj-0-cvrp-0.json
```


## Contributing

First of all, thanks for the interest in the project. If you found a bug or have any question, feel free to open an issue.

If you prefer to contribute with code or documentation, first fork the repository, make the appropriate changes and open a pull request to our `master` branch.

Notice we use Python Poetry to manage dependencies. So if you need to add, remove or update any dependency make sure to use the proper [`poetry`](https://python-poetry.org/docs/) commands to write the changes in the `pyproject.toml` and `poetry.lock` files.

Moreover, before opening a pull request, make sure the following were taken care:

- The `black` formatter was run:
```bash
poetry run black .
```

- The code is conformant with `flake8`:
```bash
poetry run flake8 .
```

- The tests are still passing:
```bash
poetry run pytest tests/
```

### Note to Windows users

In some cases, Windows uses CRLF as end of line instead of LF, which is the norm in Unix-based systems. This erroneously makes git thinks that a whole file was changed when saved in different operating systems.

To alleviate this issue, we recommend Windows users to do one of the following:

- When installing Git for Windows, choose the option "Checkout Windows-style, commit Unix-style line endings" [(see this StackOverflow answer)](https://stackoverflow.com/questions/1889559/git-diff-to-ignore-m)

- If Git is already installed, write the following in the LoggiBUD repository before making any commit:

```bash
git config core.whitespace cr-at-eol
```
4 changes: 3 additions & 1 deletion loggibud/v1/baselines/run_task1.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
# Load method from path.
module = importlib.import_module(args.module)
method = getattr(module, args.method)
params_class = getattr(module, args.params_class) if args.params_class else None
params_class = (
getattr(module, args.params_class) if args.params_class else None
)

# Load instance and heuristic params.
path = Path(args.instances)
Expand Down
16 changes: 12 additions & 4 deletions loggibud/v1/baselines/shared/ortools.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,17 @@ def solve(
model = pywrapcp.RoutingModel(manager)

# Unwrap the size index for every point.
sizes = np.array([0] + [d.size for d in instance.deliveries], dtype=np.int32)
sizes = np.array(
[0] + [d.size for d in instance.deliveries], dtype=np.int32
)

def capacity_callback(src):
src = manager.IndexToNode(src)
return sizes[src]

capacity_callback_index = model.RegisterUnaryTransitCallback(capacity_callback)
capacity_callback_index = model.RegisterUnaryTransitCallback(
capacity_callback
)
model.AddDimension(
capacity_callback_index, 0, instance.vehicle_capacity, True, "Capacity"
)
Expand All @@ -84,7 +88,9 @@ def capacity_callback(src):

# Compute the distance matrix between points.
logger.info("Computing distance matrix.")
distance_matrix = (calculate_distance_matrix_m(locations) * 10).astype(np.int32)
distance_matrix = (calculate_distance_matrix_m(locations) * 10).astype(
np.int32
)

def distance_callback(src, dst):
x = manager.IndexToNode(src)
Expand All @@ -97,7 +103,9 @@ def distance_callback(src, dst):
search_parameters = pywrapcp.DefaultRoutingSearchParameters()
search_parameters.first_solution_strategy = params.first_solution_strategy

search_parameters.local_search_metaheuristic = params.local_search_metaheuristic
search_parameters.local_search_metaheuristic = (
params.local_search_metaheuristic
)

if params.solution_limit:
search_parameters.solution_limit = params.solution_limit
Expand Down
17 changes: 14 additions & 3 deletions loggibud/v1/baselines/task1/kmeans_aggregation_ortools.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@
import numpy as np
from sklearn.cluster import MiniBatchKMeans

from loggibud.v1.types import CVRPInstance, CVRPSolution, CVRPSolutionVehicle, Delivery
from loggibud.v1.types import (
CVRPInstance,
CVRPSolution,
CVRPSolutionVehicle,
Delivery,
)
from ..shared.ortools import solve_cvrp as ortools_solve, ORToolsParams


Expand Down Expand Up @@ -64,7 +69,9 @@ def solve(
logger.info(f"Clustering instance into {num_clusters} subinstances")
clustering = MiniBatchKMeans(num_clusters, random_state=params.seed)

points = np.array([[d.point.lng, d.point.lat] for d in instance.deliveries])
points = np.array(
[[d.point.lng, d.point.lat] for d in instance.deliveries]
)
clusters = clustering.fit_predict(points)

delivery_array = np.array(instance.deliveries)
Expand Down Expand Up @@ -123,7 +130,11 @@ def aggregate_deliveries(idx, deliveries):
deliveries=[
d
for v in solve_cluster(
[d for groups in v.deliveries for d in subsolutions[int(groups.id)]]
[
d
for groups in v.deliveries
for d in subsolutions[int(groups.id)]
]
)
for d in v
],
Expand Down
8 changes: 6 additions & 2 deletions loggibud/v1/baselines/task1/kmeans_partition_ortools.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,17 @@ def solve(
num_deliveries = len(instance.deliveries)
num_clusters = int(
params.fixed_num_clusters
or np.ceil(num_deliveries / (params.variable_num_clusters or num_deliveries))
or np.ceil(
num_deliveries / (params.variable_num_clusters or num_deliveries)
)
)

logger.info(f"Clustering instance into {num_clusters} subinstances")
clustering = KMeans(num_clusters, random_state=params.seed)

points = np.array([[d.point.lng, d.point.lat] for d in instance.deliveries])
points = np.array(
[[d.point.lng, d.point.lat] for d in instance.deliveries]
)
clusters = clustering.fit_predict(points)

delivery_array = np.array(instance.deliveries)
Expand Down
5 changes: 4 additions & 1 deletion loggibud/v1/baselines/task1/lkh_3.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
import numpy as np

from loggibud.v1.types import (
CVRPInstance, CVRPSolution, CVRPSolutionVehicle, JSONDataclassMixin
CVRPInstance,
CVRPSolution,
CVRPSolutionVehicle,
JSONDataclassMixin,
)
from loggibud.v1.data_conversion import to_tsplib

Expand Down
44 changes: 34 additions & 10 deletions loggibud/v1/baselines/task2/kmeans_greedy.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@
from sklearn.cluster import KMeans
from tqdm import tqdm

from loggibud.v1.types import Delivery, CVRPInstance, CVRPSolution, CVRPSolutionVehicle
from loggibud.v1.types import (
Delivery,
CVRPInstance,
CVRPSolution,
CVRPSolutionVehicle,
)
from loggibud.v1.baselines.shared.ortools import (
solve as ortools_solve,
ORToolsParams,
Expand Down Expand Up @@ -67,7 +72,9 @@ def pretrain(
num_deliveries = len(points)
num_clusters = int(
params.fixed_num_clusters
or np.ceil(num_deliveries / (params.variable_num_clusters or num_deliveries))
or np.ceil(
num_deliveries / (params.variable_num_clusters or num_deliveries)
)
)

logger.info(f"Clustering instance into {num_clusters} subinstances")
Expand All @@ -80,13 +87,17 @@ def pretrain(
)


def finetune(model: KMeansGreedyModel, instance: CVRPInstance) -> KMeansGreedyModel:
def finetune(
model: KMeansGreedyModel, instance: CVRPInstance
) -> KMeansGreedyModel:
"""Prepare the model for one particular instance."""

return KMeansGreedyModel(
params=model.params,
clustering=model.clustering,
cluster_subsolutions={i: [] for i in range(model.clustering.n_clusters)},
cluster_subsolutions={
i: [] for i in range(model.clustering.n_clusters)
},
# Just fill some random instance.
subinstance=instance,
)
Expand All @@ -95,12 +106,17 @@ def finetune(model: KMeansGreedyModel, instance: CVRPInstance) -> KMeansGreedyMo
def route(model: KMeansGreedyModel, delivery: Delivery) -> KMeansGreedyModel:
"""Route a single delivery using the model instance."""

cluster = model.clustering.predict([[delivery.point.lng, delivery.point.lat]])[0]
cluster = model.clustering.predict(
[[delivery.point.lng, delivery.point.lat]]
)[0]

subsolution = model.cluster_subsolutions[cluster]

def is_feasible(route):
return route.occupation + delivery.size < model.subinstance.vehicle_capacity
return (
route.occupation + delivery.size
< model.subinstance.vehicle_capacity
)

# TODO: We could make this method faster by using a route size table, but seems a bit
# overkill since it's not a bottleneck.
Expand All @@ -114,7 +130,9 @@ def is_feasible(route):
route_idx, route = max(feasible_routes, key=lambda v: v[1].occupation)

else:
route = CVRPSolutionVehicle(origin=model.subinstance.origin, deliveries=[])
route = CVRPSolutionVehicle(
origin=model.subinstance.origin, deliveries=[]
)
subsolution.append(route)
route_idx = len(subsolution) - 1

Expand Down Expand Up @@ -146,7 +164,9 @@ def finish(instance: CVRPInstance, model: KMeansGreedyModel) -> CVRPSolution:

return CVRPSolution(
name=instance.name,
vehicles=[v for subsolution in subsolutions for v in subsolution.vehicles],
vehicles=[
v for subsolution in subsolutions for v in subsolution.vehicles
],
)


Expand Down Expand Up @@ -179,11 +199,15 @@ def solve_instance(
# Load instance and heuristic params.
eval_path = Path(args.eval_instances)
eval_path_dir = eval_path if eval_path.is_dir() else eval_path.parent
eval_files = [eval_path] if eval_path.is_file() else list(eval_path.iterdir())
eval_files = (
[eval_path] if eval_path.is_file() else list(eval_path.iterdir())
)

train_path = Path(args.train_instances)
train_path_dir = train_path if train_path.is_dir() else train_path.parent
train_files = [train_path] if train_path.is_file() else list(train_path.iterdir())
train_files = (
[train_path] if train_path.is_file() else list(train_path.iterdir())
)

# params = params_class.from_file(args.params) if args.params else None

Expand Down
8 changes: 5 additions & 3 deletions loggibud/v1/baselines/task2/qrp_sweep.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@
from tqdm import tqdm

from loggibud.v1.types import (
Delivery, CVRPInstance, CVRPSolution, CVRPSolutionVehicle
Delivery,
CVRPInstance,
CVRPSolution,
CVRPSolutionVehicle,
)
from loggibud.v1.baselines.shared.ortools import (
solve as ortools_solve,
Expand Down Expand Up @@ -99,8 +102,7 @@ def predict(self, delivery: Delivery) -> int:
"""

point_translated = (
np.array([delivery.point.lng, delivery.point.lat])
- self.center
np.array([delivery.point.lng, delivery.point.lat]) - self.center
)
angle = np.arctan2(point_translated[1], point_translated[0])

Expand Down
18 changes: 6 additions & 12 deletions loggibud/v1/data_conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,36 +53,30 @@ def to_tsplib(
tspfile += "\n"

# Demand section
tspfile += (
"DEMAND_SECTION\n"
"1 0\n"
)
tspfile += "DEMAND_SECTION\n" "1 0\n"
tspfile += "\n".join(
f"{i} {delivery.size}"
for i, delivery in enumerate(instance.deliveries, start=2)
)
tspfile += "\n"

# Depot section: ensure node 1 is the depot (-1 to terminate the list)
tspfile += (
"DEPOT_SECTION\n"
"1\n"
"-1\n"
)
tspfile += "DEPOT_SECTION\n" "1\n" "-1\n"

# Edge section:
# Compute distance matrix
locations = [instance.origin] + [
delivery.point for delivery in instance.deliveries
]
distance_matrix = (
calculate_distance_matrix_m(locations) * 10
).astype(np.int32)
distance_matrix = (calculate_distance_matrix_m(locations) * 10).astype(
np.int32
)

tspfile += "EDGE_WEIGHT_SECTION\n"

def print_row(row):
return " ".join(str(el) for el in row)

tspfile += "\n".join(print_row(row) for row in distance_matrix)

if not file_name:
Expand Down
17 changes: 11 additions & 6 deletions loggibud/v1/distances.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ def calculate_distance_matrix_m(
if len(points) < 2:
return 0

coords_uri = ";".join(["{},{}".format(point.lng, point.lat) for point in points])
coords_uri = ";".join(
["{},{}".format(point.lng, point.lat) for point in points]
)

response = requests.get(
f"{config.host}/table/v1/driving/{coords_uri}?annotations=distance",
Expand All @@ -44,7 +46,9 @@ def calculate_route_distance_m(
if len(points) < 2:
return 0

coords_uri = ";".join("{},{}".format(point.lng, point.lat) for point in points)
coords_uri = ";".join(
"{},{}".format(point.lng, point.lat) for point in points
)

response = requests.get(
f"{config.host}/route/v1/driving/{coords_uri}?annotations=distance&continue_straight=false",
Expand All @@ -57,7 +61,7 @@ def calculate_route_distance_m(


def calculate_distance_matrix_great_circle_m(
points: Iterable[Point]
points: Iterable[Point],
) -> np.ndarray:
"""Distance matrix using the Great Circle distance
This is an Euclidean-like distance but on spheres [1]. In this case it is
Expand Down Expand Up @@ -88,16 +92,17 @@ def calculate_distance_matrix_great_circle_m(

delta_sigma = np.arctan2(
np.sqrt(
(np.cos(phi2) * np.sin(delta_lambda))**2
(np.cos(phi2) * np.sin(delta_lambda)) ** 2
+ (
np.cos(phi1) * np.sin(phi2)
- np.sin(phi1) * np.cos(phi2) * np.cos(delta_lambda)
)**2
)
** 2
),
(
np.sin(phi1) * np.sin(phi2)
+ np.cos(phi1) * np.cos(phi2) * np.cos(delta_lambda)
)
),
)

return EARTH_RADIUS_METERS * delta_sigma
Expand Down
Loading

0 comments on commit e39b6ad

Please sign in to comment.