From 0f6457840b26027e6ef2856257f19c9b67661862 Mon Sep 17 00:00:00 2001 From: "Daniel J. Hofmann" Date: Sat, 1 Jul 2017 16:27:52 +0200 Subject: [PATCH 1/3] Removes byte-counting for v8 --- src/tsp.cc | 3 --- src/types.h | 41 ----------------------------------------- src/vrp.cc | 10 ---------- 3 files changed, 54 deletions(-) diff --git a/src/tsp.cc b/src/tsp.cc index 5c0b25a..c7c64cf 100755 --- a/src/tsp.cc +++ b/src/tsp.cc @@ -34,9 +34,6 @@ NAN_METHOD(TSP::New) try { TSPSolverParams userParams{info}; - const auto bytesChange = getBytes(userParams.costs); - Nan::AdjustExternalMemory(bytesChange); - auto* self = new TSP{std::move(userParams.costs)}; self->Wrap(info.This()); diff --git a/src/types.h b/src/types.h index 46d1730..bf2d456 100644 --- a/src/types.h +++ b/src/types.h @@ -69,45 +69,4 @@ using RouteLocks = std::vector; using Pickups = NewType, struct PickupsTag>::Type; using Deliveries = NewType, struct DeliveriesTag>::Type; -// Bytes in our type used for internal caching - -template struct Bytes; - -template <> struct Bytes { - std::int32_t operator()(const CostMatrix& v) const { return v.size() * sizeof(CostMatrix::Value); } -}; - -template <> struct Bytes { - std::int32_t operator()(const DurationMatrix& v) const { return v.size() * sizeof(DurationMatrix::Value); } -}; - -template <> struct Bytes { - std::int32_t operator()(const DemandMatrix& v) const { return v.size() * sizeof(DemandMatrix::Value); } -}; - -template <> struct Bytes { - std::int32_t operator()(const TimeWindows& v) const { return v.size() * sizeof(TimeWindows::Value); } -}; - -template <> struct Bytes { - std::int32_t operator()(const RouteLocks& v) const { - std::int32_t bytes = 0; - - for (const auto& lockChain : v) - bytes += lockChain.size() * sizeof(LockChain::value_type); - - return bytes; - } -}; - -template <> struct Bytes { - std::int32_t operator()(const Pickups& v) const { return v.size() * sizeof(Pickups::Value); } -}; - -template <> struct Bytes { - std::int32_t operator()(const Deliveries& v) const { return v.size() * sizeof(Deliveries::Value); } -}; - -template std::int32_t getBytes(const T& v) { return Bytes{}(v); } - #endif diff --git a/src/vrp.cc b/src/vrp.cc index 8ad194a..8accd54 100755 --- a/src/vrp.cc +++ b/src/vrp.cc @@ -33,13 +33,6 @@ NAN_METHOD(VRP::New) try { VRPSolverParams userParams{info}; - const auto bytesChange = getBytes(userParams.costs) // - + getBytes(userParams.durations) // - + getBytes(userParams.timeWindows) // - + getBytes(userParams.demands); // - - Nan::AdjustExternalMemory(bytesChange); - auto* self = new VRP{std::move(userParams.costs), // std::move(userParams.durations), // std::move(userParams.timeWindows), // @@ -58,9 +51,6 @@ NAN_METHOD(VRP::Solve) try { VRPSearchParams userParams(info); - const auto bytesChange = getBytes(userParams.routeLocks); - Nan::AdjustExternalMemory(bytesChange); - // See routing_parameters.proto and routing_enums.proto auto modelParams = RoutingModel::DefaultModelParameters(); auto searchParams = RoutingModel::DefaultSearchParameters(); From dd24030c581090bd9b530886d011e054c4fcf9b1 Mon Sep 17 00:00:00 2001 From: "Daniel J. Hofmann" Date: Sat, 1 Jul 2017 16:34:47 +0200 Subject: [PATCH 2/3] Removes unused function-caching adaptors --- src/adaptors.h | 75 -------------------------------------------------- 1 file changed, 75 deletions(-) diff --git a/src/adaptors.h b/src/adaptors.h index 7445044..7de5206 100644 --- a/src/adaptors.h +++ b/src/adaptors.h @@ -48,81 +48,6 @@ template inline auto makeMatrixFromFunction(std::int32_t n, v8 return matrix; } -// Caches user provided Function(node) -> [start, stop] into TimeWindows -inline auto makeTimeWindowsFromFunction(std::int32_t n, v8::Local fn) { - if (n < 0) - throw std::runtime_error{"Negative size"}; - - Nan::Callback callback{fn}; - - TimeWindows timeWindows{n}; - - for (std::int32_t atIdx = 0; atIdx < n; ++atIdx) { - const auto argc = 1u; - v8::Local argv[argc] = {Nan::New(atIdx)}; - - auto interval = callback.Call(argc, argv); - - if (!interval->IsArray()) - throw std::runtime_error{"Expected function signature: Array fn(Number at)"}; - - auto intervalArray = interval.As(); - - if (intervalArray->Length() != 2) - throw std::runtime_error{"Expected interval array of shape [start, stop]"}; - - auto start = Nan::Get(intervalArray, 0).ToLocalChecked(); - auto stop = Nan::Get(intervalArray, 1).ToLocalChecked(); - - if (!start->IsNumber() || !stop->IsNumber()) - throw std::runtime_error{"Expected interval start and stop of type Number"}; - - Interval out{Nan::To(start).FromJust(), Nan::To(stop).FromJust()}; - timeWindows.at(atIdx) = std::move(out); - } - - return timeWindows; -} - -// Caches user provided Function(vehicle) -> [node0, node1, ..] into RouteLocks -inline auto makeRouteLocksFromFunction(std::int32_t n, v8::Local fn) { - if (n < 0) - throw std::runtime_error{"Negative size"}; - - Nan::Callback callback{fn}; - - // Note: use (n) for construction because RouteLocks is a weak alias to a std::vector. - // Using vec(n) creates a vector of n items, using vec{n} creates a vector with a single element n. - RouteLocks routeLocks(n); - - for (std::int32_t atIdx = 0; atIdx < n; ++atIdx) { - const auto argc = 1u; - v8::Local argv[argc] = {Nan::New(atIdx)}; - - auto locks = callback.Call(argc, argv); - - if (!locks->IsArray()) - throw std::runtime_error{"Expected function signature: Array fn(Number vehicle)"}; - - auto locksArray = locks.As(); - - LockChain lockChain(locksArray->Length()); - - for (std::int32_t lockIdx = 0; lockIdx < (std::int32_t)locksArray->Length(); ++lockIdx) { - auto node = Nan::Get(locksArray, lockIdx).ToLocalChecked(); - - if (!node->IsNumber()) - throw std::runtime_error{"Expected lock node of type Number"}; - - lockChain.at(lockIdx) = Nan::To(node).FromJust(); - } - - routeLocks.at(atIdx) = std::move(lockChain); - } - - return routeLocks; -} - // Caches user provided Js Array into a Vector template inline auto makeVectorFromJsNumberArray(v8::Local array) { const std::int32_t len = array->Length(); From 26a840c1f7cadf0ce00bdec993d277f888db2c15 Mon Sep 17 00:00:00 2001 From: "Daniel J. Hofmann" Date: Sat, 1 Jul 2017 17:38:42 +0200 Subject: [PATCH 3/3] Implements multiple time windows per location, resolves #8 --- API.md | 4 ++-- example/index.js | 2 +- src/types.h | 2 +- src/vrp_params.h | 46 +++++++++++++++++++++++++++++----------------- src/vrp_worker.h | 47 ++++++++++++++++++++++++++++++++++++++++------- test/vrp.js | 4 ++-- 6 files changed, 75 insertions(+), 30 deletions(-) diff --git a/API.md b/API.md index 66f3fd7..3eb9333 100644 --- a/API.md +++ b/API.md @@ -93,7 +93,7 @@ Initializes and caches user data internally for efficiency. - `numNodes` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** Number of locations in the problem ("nodes"). - `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`. - `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`. -- `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. +- `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. - `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. @@ -104,7 +104,7 @@ var vrpSolverOpts = { numNodes: 3, costs: [[0, 10, 10], [10, 0, 10], [10, 10, 0]], durations: [[0, 2, 2], [2, 0, 2], [2, 2, 0]], - timeWindows: [[0, 9], [2, 3], [2, 3]], + timeWindows: [[[0, 9]], [[2, 3]], [[2, 3]]], demands: [[0, 0, 0], [1, 1, 1], [1, 1, 1]] }; diff --git a/example/index.js b/example/index.js index a842e9d..0616238 100644 --- a/example/index.js +++ b/example/index.js @@ -87,7 +87,7 @@ MbxClient.getDistances(locations, {profile: profile}, function(err, results) { var timeWindows = new Array(results.durations.length); for (var at = 0; at < results.durations.length; ++at) - timeWindows[at] = [dayStarts, dayEnds]; + timeWindows[at] = [[dayStarts, dayEnds]]; // Dummy demands of one except at the depot var demands = new Array(results.durations.length); diff --git a/src/types.h b/src/types.h index bf2d456..32d453c 100644 --- a/src/types.h +++ b/src/types.h @@ -34,7 +34,7 @@ struct Interval { std::int32_t stop; }; -using TimeWindows = NewType, struct TimeWindowsTag>::Type; +using TimeWindows = NewType>, struct TimeWindowsTag>::Type; namespace ort = operations_research; diff --git a/src/vrp_params.h b/src/vrp_params.h index 630b202..23c7182 100644 --- a/src/vrp_params.h +++ b/src/vrp_params.h @@ -35,8 +35,8 @@ struct VRPSearchParams { v8::Local callback; }; -// Caches user provided 2d Array of [Number, Number] into Vectors of Intervals -inline auto makeTimeWindowsFrom2dArray(std::int32_t n, v8::Local array) { +// Caches user provided 3d Array of [[Number, Number], ..] into Vectors of Vectors of Intervals +inline auto makeTimeWindowsFrom3dArray(std::int32_t n, v8::Local array) { if (n < 0) throw std::runtime_error{"Negative size"}; @@ -45,29 +45,41 @@ inline auto makeTimeWindowsFrom2dArray(std::int32_t n, v8::Local arra TimeWindows timeWindows(n); - for (std::int32_t atIdx = 0; atIdx < n; ++atIdx) { - auto inner = Nan::Get(array, atIdx).ToLocalChecked(); + for (std::int32_t locationIdx = 0; locationIdx < n; ++locationIdx) { + auto timeWindowsForLocation = Nan::Get(array, locationIdx).ToLocalChecked(); - if (!inner->IsArray()) + if (!timeWindowsForLocation->IsArray()) throw std::runtime_error{"Expected Array of Arrays"}; - auto innerArray = inner.As(); + auto timeWindowsForLocationArray = timeWindowsForLocation.As(); + const auto numTimeWindowsForLocation = static_cast(timeWindowsForLocationArray->Length()); + + Vector locationTimeWindows(numTimeWindowsForLocation); - if (static_cast(innerArray->Length()) != 2) - throw std::runtime_error{"Expected interval Array of shape [start, stop]"}; + for (std::int32_t timeWindowIdx = 0; timeWindowIdx < numTimeWindowsForLocation; ++timeWindowIdx) { + auto interval = Nan::Get(timeWindowsForLocationArray, timeWindowIdx).ToLocalChecked(); - auto start = Nan::Get(innerArray, 0).ToLocalChecked(); - auto stop = Nan::Get(innerArray, 1).ToLocalChecked(); + if (!interval->IsArray()) + throw std::runtime_error{"Expected Array of time interval Arrays"}; - if (!start->IsNumber() || !stop->IsNumber()) - throw std::runtime_error{"Expected interval start and stop of type Number"}; + auto intervalArray = interval.As(); - auto startValue = Nan::To(start).FromJust(); - auto stopValue = Nan::To(stop).FromJust(); + if (intervalArray->Length() != 2) + throw std::runtime_error{"Expected interval Array of shape [start, stop]"}; - Interval out{startValue, stopValue}; + auto start = Nan::Get(intervalArray, 0).ToLocalChecked(); + auto stop = Nan::Get(intervalArray, 1).ToLocalChecked(); + + if (!start->IsNumber() || !stop->IsNumber()) + throw std::runtime_error{"Expected interval start and stop of type Number"}; + + auto startValue = Nan::To(start).FromJust(); + auto stopValue = Nan::To(stop).FromJust(); + + locationTimeWindows.at(timeWindowIdx) = Interval{startValue, stopValue}; + } - timeWindows.at(atIdx) = std::move(out); + timeWindows.at(locationIdx) = std::move(locationTimeWindows); } return timeWindows; @@ -147,7 +159,7 @@ VRPSolverParams::VRPSolverParams(const Nan::FunctionCallbackInfo& inf costs = makeMatrixFrom2dArray(numNodes, costMatrix); durations = makeMatrixFrom2dArray(numNodes, durationMatrix); - timeWindows = makeTimeWindowsFrom2dArray(numNodes, timeWindowsVector); + timeWindows = makeTimeWindowsFrom3dArray(numNodes, timeWindowsVector); demands = makeMatrixFrom2dArray(numNodes, demandMatrix); } diff --git a/src/vrp_worker.h b/src/vrp_worker.h index 4ff0196..0279a75 100644 --- a/src/vrp_worker.h +++ b/src/vrp_worker.h @@ -58,7 +58,24 @@ struct VRPWorker final : Nan::AsyncWorker { const auto costsOk = costs->dim() == numNodes; const auto durationsOk = durations->dim() == numNodes; + const auto timeWindowsOk = timeWindows->size() == numNodes; + + for (std::int32_t i = 0; i < timeWindows->size(); ++i) { + const auto& timeWindowsForLocation = timeWindows->at(i); + const auto numTimeWindowsForLocation = timeWindowsForLocation.size(); + + for (std::int32_t j = 0; j < numTimeWindowsForLocation - 1; ++j) { + const auto& lhs = timeWindowsForLocation.at(j + 0); + const auto& rhs = timeWindowsForLocation.at(j + 1); + + const auto ordered = lhs.stop <= rhs.start; + + if (!ordered) + throw std::runtime_error{"Expected multiple time windows per location to be sorted"}; + } + } + const auto demandsOk = demands->dim() == numNodes; if (!costsOk || !durationsOk || !timeWindowsOk || !demandsOk) @@ -105,15 +122,31 @@ struct VRPWorker final : Nan::AsyncWorker { model.AddDimension(durationCallback, timeHorizon, timeHorizon, /*fix_start_cumul_to_zero=*/true, kDimensionTime); const auto& timeDimension = model.GetDimensionOrDie(kDimensionTime); + /* for (std::int32_t node = 0; node < numNodes; ++node) { - const auto interval = timeWindows->at(node); - timeDimension.CumulVar(node)->SetRange(interval.start, interval.stop); - // At the moment we only support a single interval for time windows. - // We can support multiple intervals if we sort intervals by start then stop. - // Then Cumulval(n)->SetRange(minStart, maxStop), then walk over intervals - // removing intervals between active intervals: - // CumulVar(n)->RemoveInterval(stop, start). + const auto timeWindowsForLocation = timeWindows->at(node); + const auto numTimeWindowsForLocation = timeWindowsForLocation.size(); + + if (numTimeWindowsForLocation < 1) + continue; + + // We can support multiple intervals sorted by start then stop by + // enabling the interval [minStart, maxStop] then walking over all + // intervals and disabling ranges between adjacent time intervals. + + const auto minStart = timeWindowsForLocation.at(0).start; + const auto maxStop = timeWindowsForLocation.at(numTimeWindowsForLocation - 1).stop; + + timeDimension.CumulVar(node)->SetRange(minStart, maxStop); + + for (std::int32_t i = 0; i < numTimeWindowsForLocation - 1; ++i) { + const auto& lhs = timeWindowsForLocation.at(i + 0); + const auto& rhs = timeWindowsForLocation.at(i + 1); + + timeDimension.CumulVar(node)->RemoveInterval(lhs.stop, rhs.start); + } } + */ // Capacity Dimension diff --git a/test/vrp.js b/test/vrp.js index 0d94821..1c4b42b 100644 --- a/test/vrp.js +++ b/test/vrp.js @@ -69,7 +69,7 @@ var timeWindows = new Array(locations.length); for (var at = 0; at < locations.length; ++at) { if (at === depot) { - timeWindows[at] = [dayStarts, dayEnds]; + timeWindows[at] = [[dayStarts, dayEnds]]; continue; } @@ -79,7 +79,7 @@ for (var at = 0; at < locations.length; ++at) { var start = rand() * (latest - earliest) + earliest; var stop = rand() * (latest - start) + start; - timeWindows[at] = [start, stop]; + timeWindows[at] = [[start, stop]]; }