Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple Time Windows per Location #26

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.


Expand All @@ -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]]
};

Expand Down
2 changes: 1 addition & 1 deletion example/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
75 changes: 0 additions & 75 deletions src/adaptors.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,81 +48,6 @@ template <typename Matrix> 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<v8::Function> 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<v8::Value> 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<v8::Array>();

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<std::int32_t>(start).FromJust(), Nan::To<std::int32_t>(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<v8::Function> 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<v8::Value> 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<v8::Array>();

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<std::int32_t>(node).FromJust();
}

routeLocks.at(atIdx) = std::move(lockChain);
}

return routeLocks;
}

// Caches user provided Js Array into a Vector
template <typename Vector> inline auto makeVectorFromJsNumberArray(v8::Local<v8::Array> array) {
const std::int32_t len = array->Length();
Expand Down
3 changes: 0 additions & 3 deletions src/tsp.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
43 changes: 1 addition & 42 deletions src/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ struct Interval {
std::int32_t stop;
};

using TimeWindows = NewType<Vector<Interval>, struct TimeWindowsTag>::Type;
using TimeWindows = NewType<Vector<Vector<Interval>>, struct TimeWindowsTag>::Type;

namespace ort = operations_research;

Expand Down Expand Up @@ -69,45 +69,4 @@ using RouteLocks = std::vector<LockChain>;
using Pickups = NewType<Vector<NodeIndex>, struct PickupsTag>::Type;
using Deliveries = NewType<Vector<NodeIndex>, struct DeliveriesTag>::Type;

// Bytes in our type used for internal caching

template <typename T> struct Bytes;

template <> struct Bytes<CostMatrix> {
std::int32_t operator()(const CostMatrix& v) const { return v.size() * sizeof(CostMatrix::Value); }
};

template <> struct Bytes<DurationMatrix> {
std::int32_t operator()(const DurationMatrix& v) const { return v.size() * sizeof(DurationMatrix::Value); }
};

template <> struct Bytes<DemandMatrix> {
std::int32_t operator()(const DemandMatrix& v) const { return v.size() * sizeof(DemandMatrix::Value); }
};

template <> struct Bytes<TimeWindows> {
std::int32_t operator()(const TimeWindows& v) const { return v.size() * sizeof(TimeWindows::Value); }
};

template <> struct Bytes<RouteLocks> {
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<Pickups> {
std::int32_t operator()(const Pickups& v) const { return v.size() * sizeof(Pickups::Value); }
};

template <> struct Bytes<Deliveries> {
std::int32_t operator()(const Deliveries& v) const { return v.size() * sizeof(Deliveries::Value); }
};

template <typename T> std::int32_t getBytes(const T& v) { return Bytes<T>{}(v); }

#endif
10 changes: 0 additions & 10 deletions src/vrp.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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), //
Expand All @@ -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();
Expand Down
46 changes: 29 additions & 17 deletions src/vrp_params.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ struct VRPSearchParams {
v8::Local<v8::Function> callback;
};

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

Expand All @@ -45,29 +45,41 @@ inline auto makeTimeWindowsFrom2dArray(std::int32_t n, v8::Local<v8::Array> 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<v8::Array>();
auto timeWindowsForLocationArray = timeWindowsForLocation.As<v8::Array>();
const auto numTimeWindowsForLocation = static_cast<std::int32_t>(timeWindowsForLocationArray->Length());

Vector<Interval> locationTimeWindows(numTimeWindowsForLocation);

if (static_cast<std::int32_t>(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<v8::Array>();

auto startValue = Nan::To<std::int32_t>(start).FromJust();
auto stopValue = Nan::To<std::int32_t>(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<std::int32_t>(start).FromJust();
auto stopValue = Nan::To<std::int32_t>(stop).FromJust();

locationTimeWindows.at(timeWindowIdx) = Interval{startValue, stopValue};
}

timeWindows.at(atIdx) = std::move(out);
timeWindows.at(locationIdx) = std::move(locationTimeWindows);
}

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

costs = makeMatrixFrom2dArray<CostMatrix>(numNodes, costMatrix);
durations = makeMatrixFrom2dArray<DurationMatrix>(numNodes, durationMatrix);
timeWindows = makeTimeWindowsFrom2dArray(numNodes, timeWindowsVector);
timeWindows = makeTimeWindowsFrom3dArray(numNodes, timeWindowsVector);
demands = makeMatrixFrom2dArray<DemandMatrix>(numNodes, demandMatrix);
}

Expand Down
47 changes: 40 additions & 7 deletions src/vrp_worker.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions test/vrp.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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]];
}


Expand Down