Skip to content

Commit

Permalink
Added a function that displays grouping of shipments by allowed vehic…
Browse files Browse the repository at this point in the history
…le indices.
  • Loading branch information
ondrasej committed Oct 31, 2023
1 parent 5c17a39 commit c128ea4
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 0 deletions.
50 changes: 50 additions & 0 deletions python/cfr/analysis/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,14 @@ def skipped_shipments(self) -> Sequence[cfr_json.SkippedShipment]:
"""Returns the list of skipped shipments in the scenario."""
return self.solution.get("skippedShipments", ())

@functools.cached_property
def skipped_shipment_indices(self) -> Set[int]:
"""Returns the set of skipped shipment indices in the solution."""
return set(
skipped_shipment.get("index", 0)
for skipped_shipment in self.skipped_shipments
)

@functools.cached_property
def vehicle_for_shipment(self) -> Mapping[int, int]:
"""Returns a mapping from a shipment to the vehicle that serves it.
Expand Down Expand Up @@ -242,6 +250,48 @@ def get_parking_location_aggregate_data(
)


def get_vehicle_shipment_groups(
model: cfr_json.ShipmentModel,
) -> Sequence[tuple[Set[int], Set[int]]]:
"""Returns grouping of vehicles and shipments by vehicle-shipment constraints.
Uses `Shipment.allowedVehicleIndices` to group shipments by the vehicles that
can serve them. The output of the function is a collection of pairs
`(vehicles, shipments)` where `shipments` is a set of shipment indices, and
`vehicles` is a set of vehicle indices such that for each shipment in
`shipments`, its `allowedVehicleIndices` are exactly `vehicles`.
As a consequence of this computation:
- each shipment in the model appears in exactly one group.
- each vehicle appears can appear in zero or more groups.
Args:
model: The model for which the grouping is computed.
Returns:
A collection of vehicle group/shipment group pairs. See above for a detailed
description.
"""
shipments = cfr_json.get_shipments(model)
vehicles = cfr_json.get_vehicles(model)

all_vehicles = frozenset(range(len(vehicles)))

shipments_by_allowed_vehicles = collections.defaultdict(set)
for shipment_index, shipment in enumerate(shipments):
shipment_label = shipment.get("label", "")
if shipment_label.endswith(" arrival") or shipment_label.endswith(
" departure"
):
continue
allowed_vehicles = frozenset(
shipment.get("allowedVehicleIndices", all_vehicles)
)
shipments_by_allowed_vehicles[allowed_vehicles].add(shipment_index)

return tuple(shipments_by_allowed_vehicles.items())


def consume_suffix(text: str, suffix: str) -> str | None:
"""Consumes the suffix of a text.
Expand Down
51 changes: 51 additions & 0 deletions python/cfr/analysis/analysis_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import unittest

from google3.third_party.cfr.python.cfr.analysis import analysis
from google3.third_party.cfr.python.cfr.json import cfr_json


class VehicleShipmentGroupsTest(unittest.TestCase):

def test_without_allowed_vehicle_indices(self):
model: cfr_json.ShipmentModel = {
"shipments": [
{"label": "S001"},
{"label": "S002"},
{"label": "S003"},
{"label": "S004"},
{"label": "S005"},
],
"vehicles": [
{"label": "V001"},
{"label": "V002"},
],
}
self.assertSequenceEqual(
analysis.get_vehicle_shipment_groups(model),
(({0, 1}, {0, 1, 2, 3, 4}),),
)

def test_with_some_allowed_vehicle_indices(self):
model: cfr_json.ShipmentModel = {
"shipments": [
{"label": "S001", "allowedVehicleIndices": [0, 2]},
{"label": "S002", "allowedVehicleIndices": [0, 2]},
{"label": "S003", "allowedVehicleIndices": [1]},
{"label": "S004", "allowedVehicleIndices": [1, 2, 3]},
{"label": "S005"},
],
"vehicles": [
{"label": "V001"},
{"label": "V002"},
{"label": "V003"},
{"label": "V004"},
],
}
self.assertCountEqual(
analysis.get_vehicle_shipment_groups(model),
(({0, 2}, {0, 1}), ({1}, {2}), ({1, 2, 3}, {3}), ({0, 1, 2, 3}, {4})),
)


if __name__ == "__main__":
unittest.main()
56 changes: 56 additions & 0 deletions python/cfr/analysis/cfr-json-analysis.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -1005,6 +1005,62 @@
"show_table_from_all_scenarios(get_skipped_shipment_list)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "eaQbPrRHaCeL"
},
"source": [
"## Vehicle-shipment grouping"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cellView": "form",
"id": "Wkhvoon2aF3y"
},
"outputs": [],
"source": [
"# @title Shipments by allowed vehicles\n",
"#\n",
"# @markdown Groups shipments by the vehicles that can handle them as expressed\n",
"# @markdown in the `Shipment.allowedVehicleIndices` field. Each shipment is\n",
"# @markdown counted exactly once (appears in one row); each vehicle may appear\n",
"# @markdown in zero, one or more rows.\n",
"# @markdown\n",
"# @markdown - _# shipments_ is the total number of shipments that can be handled\n",
"# @markdown by exactly these vehicles.\n",
"# @markdown - _# skipped shipments_ is the number of skipped shipments in the\n",
"# @markdown solution that can be handled only by this group of vehicles.\n",
"\n",
"\n",
"def get_vehicle_shipment_groups(scenario):\n",
" groups = sorted(analysis.get_vehicle_shipment_groups(scenario.model))\n",
" data = []\n",
" for vehicle_indices, shipment_indices in groups:\n",
" skipped_shipments_in_group = (\n",
" scenario.skipped_shipment_indices \u0026 shipment_indices\n",
" )\n",
" vehicle_labels = sorted(\n",
" scenario.vehicle_label(vehicle_index)\n",
" for vehicle_index in vehicle_indices\n",
" )\n",
" data.append({\n",
" \"allowed vehicles\": \", \".join(vehicle_labels),\n",
" \"# shipments\": len(shipment_indices),\n",
" \"# skipped shipments\": len(skipped_shipments_in_group),\n",
" })\n",
"\n",
" # Sort the list by vehicle labels to make it easier to read.\n",
" data.sort(key=lambda x: x[\"allowed vehicles\"])\n",
" return data\n",
"\n",
"\n",
"show_table_from_all_scenarios(get_vehicle_shipment_groups)"
]
},
{
"cell_type": "markdown",
"metadata": {
Expand Down

0 comments on commit c128ea4

Please sign in to comment.