Skip to content

Commit 0ef3607

Browse files
committed
Implements multiple time windows per location, resolves #8
1 parent dd24030 commit 0ef3607

File tree

6 files changed

+75
-30
lines changed

6 files changed

+75
-30
lines changed

API.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ Initializes and caches user data internally for efficiency.
9393
- `numNodes` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** Number of locations in the problem ("nodes").
9494
- `costs` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** Cost array the solver minimizes in optimization. Can for example be duration, distance but does not have to be. Two-dimensional with `costs[from][to]` being a **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** representing the cost for traversing the arc from `from` to `to`.
9595
- `durations` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** Duration array the solver uses for time constraints. Two-dimensional with `durations[from][to]` being a **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** representing the duration for servicing node `from` plus the time for traversing the arc from `from` to `to`.
96-
- `timeWindows` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** Time window array the solver uses for time constraints. Two-dimensional with `timeWindows[at]` being an **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** of two **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** representing the start and end time point of the time window when servicing the node `at` is allowed. The solver starts from time point `0` (you can think of this as the start of the work day) and the time points need to be positive offsets to this time point.
96+
- `timeWindows` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** Time window array the solver uses for time constraints. Three-dimensional with `timeWindows[at]` being an **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** of potentially multiple time window **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** with two **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** representing the start and end time point of the time windows when servicing the node `at` is allowed. Multiple time windows per location must not overlap and must be sorted in increasing order. The solver starts from time point `0` (you can think of this as the start of the work day) and the time points need to be positive offsets to this time point.
9797
- `demands` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** Demands array the solver uses for vehicle capacity constraints. Two-dimensional with `demands[from][to]` being a **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** representing the demand at node `from`, for example number of packages to deliver to this location. The `to` node index is unused and reserved for future changes; set `demands[at]` to a constant array for now. The depot should have a demand of zero.
9898

9999

@@ -104,7 +104,7 @@ var vrpSolverOpts = {
104104
numNodes: 3,
105105
costs: [[0, 10, 10], [10, 0, 10], [10, 10, 0]],
106106
durations: [[0, 2, 2], [2, 0, 2], [2, 2, 0]],
107-
timeWindows: [[0, 9], [2, 3], [2, 3]],
107+
timeWindows: [[[0, 9]], [[2, 3]], [[2, 3]]],
108108
demands: [[0, 0, 0], [1, 1, 1], [1, 1, 1]]
109109
};
110110

example/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ MbxClient.getDistances(locations, {profile: profile}, function(err, results) {
8787
var timeWindows = new Array(results.durations.length);
8888

8989
for (var at = 0; at < results.durations.length; ++at)
90-
timeWindows[at] = [dayStarts, dayEnds];
90+
timeWindows[at] = [[dayStarts, dayEnds]];
9191

9292
// Dummy demands of one except at the depot
9393
var demands = new Array(results.durations.length);

src/types.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ struct Interval {
3434
std::int32_t stop;
3535
};
3636

37-
using TimeWindows = NewType<Vector<Interval>, struct TimeWindowsTag>::Type;
37+
using TimeWindows = NewType<Vector<Vector<Interval>>, struct TimeWindowsTag>::Type;
3838

3939
namespace ort = operations_research;
4040

src/vrp_params.h

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ struct VRPSearchParams {
3535
v8::Local<v8::Function> callback;
3636
};
3737

38-
// Caches user provided 2d Array of [Number, Number] into Vectors of Intervals
39-
inline auto makeTimeWindowsFrom2dArray(std::int32_t n, v8::Local<v8::Array> array) {
38+
// Caches user provided 3d Array of [[Number, Number], ..] into Vectors of Vectors of Intervals
39+
inline auto makeTimeWindowsFrom3dArray(std::int32_t n, v8::Local<v8::Array> array) {
4040
if (n < 0)
4141
throw std::runtime_error{"Negative size"};
4242

@@ -45,29 +45,41 @@ inline auto makeTimeWindowsFrom2dArray(std::int32_t n, v8::Local<v8::Array> arra
4545

4646
TimeWindows timeWindows(n);
4747

48-
for (std::int32_t atIdx = 0; atIdx < n; ++atIdx) {
49-
auto inner = Nan::Get(array, atIdx).ToLocalChecked();
48+
for (std::int32_t locationIdx = 0; locationIdx < n; ++locationIdx) {
49+
auto timeWindowsForLocation = Nan::Get(array, locationIdx).ToLocalChecked();
5050

51-
if (!inner->IsArray())
51+
if (!timeWindowsForLocation->IsArray())
5252
throw std::runtime_error{"Expected Array of Arrays"};
5353

54-
auto innerArray = inner.As<v8::Array>();
54+
auto timeWindowsForLocationArray = timeWindowsForLocation.As<v8::Array>();
55+
const auto numTimeWindowsForLocation = static_cast<std::int32_t>(timeWindowsForLocationArray->Length());
56+
57+
Vector<Interval> locationTimeWindows(numTimeWindowsForLocation);
5558

56-
if (static_cast<std::int32_t>(innerArray->Length()) != 2)
57-
throw std::runtime_error{"Expected interval Array of shape [start, stop]"};
59+
for (std::int32_t timeWindowIdx = 0; timeWindowIdx < numTimeWindowsForLocation; ++timeWindowIdx) {
60+
auto interval = Nan::Get(timeWindowsForLocationArray, timeWindowIdx).ToLocalChecked();
5861

59-
auto start = Nan::Get(innerArray, 0).ToLocalChecked();
60-
auto stop = Nan::Get(innerArray, 1).ToLocalChecked();
62+
if (!interval->IsArray())
63+
throw std::runtime_error{"Expected Array of time interval Arrays"};
6164

62-
if (!start->IsNumber() || !stop->IsNumber())
63-
throw std::runtime_error{"Expected interval start and stop of type Number"};
65+
auto intervalArray = interval.As<v8::Array>();
6466

65-
auto startValue = Nan::To<std::int32_t>(start).FromJust();
66-
auto stopValue = Nan::To<std::int32_t>(stop).FromJust();
67+
if (intervalArray->Length() != 2)
68+
throw std::runtime_error{"Expected interval Array of shape [start, stop]"};
6769

68-
Interval out{startValue, stopValue};
70+
auto start = Nan::Get(intervalArray, 0).ToLocalChecked();
71+
auto stop = Nan::Get(intervalArray, 1).ToLocalChecked();
72+
73+
if (!start->IsNumber() || !stop->IsNumber())
74+
throw std::runtime_error{"Expected interval start and stop of type Number"};
75+
76+
auto startValue = Nan::To<std::int32_t>(start).FromJust();
77+
auto stopValue = Nan::To<std::int32_t>(stop).FromJust();
78+
79+
locationTimeWindows.at(timeWindowIdx) = Interval{startValue, stopValue};
80+
}
6981

70-
timeWindows.at(atIdx) = std::move(out);
82+
timeWindows.at(locationIdx) = std::move(locationTimeWindows);
7183
}
7284

7385
return timeWindows;
@@ -147,7 +159,7 @@ VRPSolverParams::VRPSolverParams(const Nan::FunctionCallbackInfo<v8::Value>& inf
147159

148160
costs = makeMatrixFrom2dArray<CostMatrix>(numNodes, costMatrix);
149161
durations = makeMatrixFrom2dArray<DurationMatrix>(numNodes, durationMatrix);
150-
timeWindows = makeTimeWindowsFrom2dArray(numNodes, timeWindowsVector);
162+
timeWindows = makeTimeWindowsFrom3dArray(numNodes, timeWindowsVector);
151163
demands = makeMatrixFrom2dArray<DemandMatrix>(numNodes, demandMatrix);
152164
}
153165

src/vrp_worker.h

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,24 @@ struct VRPWorker final : Nan::AsyncWorker {
5858

5959
const auto costsOk = costs->dim() == numNodes;
6060
const auto durationsOk = durations->dim() == numNodes;
61+
6162
const auto timeWindowsOk = timeWindows->size() == numNodes;
63+
64+
for (std::int32_t i = 0; i < timeWindows->size(); ++i) {
65+
const auto& timeWindowsForLocation = timeWindows->at(i);
66+
const auto numTimeWindowsForLocation = timeWindowsForLocation.size();
67+
68+
for (std::int32_t j = 0; j < numTimeWindowsForLocation - 1; ++j) {
69+
const auto& lhs = timeWindowsForLocation.at(j + 0);
70+
const auto& rhs = timeWindowsForLocation.at(j + 1);
71+
72+
const auto ordered = lhs.stop <= rhs.start;
73+
74+
if (!ordered)
75+
throw std::runtime_error{"Expected multiple time windows per location to be sorted"};
76+
}
77+
}
78+
6279
const auto demandsOk = demands->dim() == numNodes;
6380

6481
if (!costsOk || !durationsOk || !timeWindowsOk || !demandsOk)
@@ -105,15 +122,31 @@ struct VRPWorker final : Nan::AsyncWorker {
105122
model.AddDimension(durationCallback, timeHorizon, timeHorizon, /*fix_start_cumul_to_zero=*/true, kDimensionTime);
106123
const auto& timeDimension = model.GetDimensionOrDie(kDimensionTime);
107124

125+
/*
108126
for (std::int32_t node = 0; node < numNodes; ++node) {
109-
const auto interval = timeWindows->at(node);
110-
timeDimension.CumulVar(node)->SetRange(interval.start, interval.stop);
111-
// At the moment we only support a single interval for time windows.
112-
// We can support multiple intervals if we sort intervals by start then stop.
113-
// Then Cumulval(n)->SetRange(minStart, maxStop), then walk over intervals
114-
// removing intervals between active intervals:
115-
// CumulVar(n)->RemoveInterval(stop, start).
127+
const auto timeWindowsForLocation = timeWindows->at(node);
128+
const auto numTimeWindowsForLocation = timeWindowsForLocation.size();
129+
130+
if (numTimeWindowsForLocation < 1)
131+
continue;
132+
133+
// We can support multiple intervals sorted by start then stop by
134+
// enabling the interval [minStart, maxStop] then walking over all
135+
// intervals and disabling ranges between adjacent time intervals.
136+
137+
const auto minStart = timeWindowsForLocation.at(0).start;
138+
const auto maxStop = timeWindowsForLocation.at(numTimeWindowsForLocation - 1).stop;
139+
140+
timeDimension.CumulVar(node)->SetRange(minStart, maxStop);
141+
142+
for (std::int32_t i = 0; i < numTimeWindowsForLocation - 1; ++i) {
143+
const auto& lhs = timeWindowsForLocation.at(i + 0);
144+
const auto& rhs = timeWindowsForLocation.at(i + 1);
145+
146+
timeDimension.CumulVar(node)->RemoveInterval(lhs.stop, rhs.start);
147+
}
116148
}
149+
*/
117150

118151
// Capacity Dimension
119152

test/vrp.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ var timeWindows = new Array(locations.length);
6969

7070
for (var at = 0; at < locations.length; ++at) {
7171
if (at === depot) {
72-
timeWindows[at] = [dayStarts, dayEnds];
72+
timeWindows[at] = [[dayStarts, dayEnds]];
7373
continue;
7474
}
7575

@@ -79,7 +79,7 @@ for (var at = 0; at < locations.length; ++at) {
7979
var start = rand() * (latest - earliest) + earliest;
8080
var stop = rand() * (latest - start) + start;
8181

82-
timeWindows[at] = [start, stop];
82+
timeWindows[at] = [[start, stop]];
8383
}
8484

8585

0 commit comments

Comments
 (0)