From 13f0e56f61fba093f5632222b97697c23df567ce Mon Sep 17 00:00:00 2001 From: Anders Petersen Date: Mon, 19 Feb 2018 21:06:03 +0100 Subject: [PATCH] Add hall request assigner --- .gitmodules | 3 + cost_fns/README.md | 40 +- cost_fns/hall_request_assigner/README.md | 91 +++++ cost_fns/hall_request_assigner/build.sh | 1 + cost_fns/hall_request_assigner/config.d | 10 + cost_fns/hall_request_assigner/d-json | 1 + .../elevator_algorithm.d | 110 ++++++ .../hall_request_assigner/elevator_state.d | 72 ++++ cost_fns/hall_request_assigner/main.d | 76 ++++ .../optimal_hall_requests.d | 354 ++++++++++++++++++ 10 files changed, 754 insertions(+), 4 deletions(-) create mode 100644 cost_fns/hall_request_assigner/README.md create mode 100644 cost_fns/hall_request_assigner/build.sh create mode 100644 cost_fns/hall_request_assigner/config.d create mode 160000 cost_fns/hall_request_assigner/d-json create mode 100644 cost_fns/hall_request_assigner/elevator_algorithm.d create mode 100644 cost_fns/hall_request_assigner/elevator_state.d create mode 100644 cost_fns/hall_request_assigner/main.d create mode 100644 cost_fns/hall_request_assigner/optimal_hall_requests.d diff --git a/.gitmodules b/.gitmodules index 3e890a9..b578dee 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "elev_algo/driver"] path = elev_algo/driver url = https://github.com/TTK4145/driver-c +[submodule "cost_fns/hall_request_assigner/d-json"] + path = cost_fns/hall_request_assigner/d-json + url = https://github.com/klasbo/d-json diff --git a/cost_fns/README.md b/cost_fns/README.md index 47e0822..bbcfe7b 100644 --- a/cost_fns/README.md +++ b/cost_fns/README.md @@ -9,6 +9,11 @@ However, *maturity in programming* is still part of the learning goals, so any t Request distribution algorithms =============================== + - [Alternative 1](#alternative-1-assigning-only-the-new-request): Assigning only the new request + - [Alternative 1.1](#alternative-11-time-until-completionidle): Time until completion/idle + - [Alternative 1.2](#alternative-12-time-until-unassigned-request-served): Time until unassigned request served + - [Alternative 2](#alternative-2-reassigning-all-requests): Reassigning all requests + Required data ------------- @@ -29,7 +34,7 @@ From this we can calculate the cost of the new unassigned hall request by adding #### Alternative 1.1: Time until completion/idle -As a reminder, this is the data a single elevator contains (see `[elevator.h](/../elev_algo/elevator.h)` from the single-elevator example): +As a reminder, this is the data a single elevator contains (see `[elevator.h](../elev_algo/elevator.h)` from the single-elevator example): ```C typedef struct { int floor; @@ -172,11 +177,38 @@ int timeToServeRequest(Elevator e_old, Button b, floor f){ ### Alternative 2: Reassigning all requests -TODO: Migrate [the hall request assigner](https://github.com/klasbo/hall_request_assigner) to this repo -TODO: Explain how it works. Short version: Simulate the elevators one step (travel or door open) at a time, always moving the elevator with the shortest duration, and picking up/assigning hall requests as they are reached. -TODO: Provide executable +For this alternative, all hall requests are reassigned whenever new data enters the system. This new data could be a new request, an updated state from some elevator, or an update on who is alive on the network. This redistribution means that a request is not necessarily assigned to the same elevator for the duration of its lifetime, but can instead be re-assigned to a new elevator, for example if a new idle elevator connects to the network, or the previously assigned elevator gets a lot of cab reqeusts. + +In order for this approach to work, it is necessary that either a) this distribution is uniquely calculated by some single elevator (some kind of master elevator), or b) all elevators that calculate the redistribution eventually come to the same conclusion (if the input data is not (eventually) consistent across the elevators, we can end up in a situation where a request is never served because all the elevators come to different conclusions that say "it is optimal that some other elevator is serving this request") + +Unlike with Alternative 1, it is not recommended that you try to implement this code yourself - at least not without being inspired by (aka copying) existing code. This code [is found here](/hall_request_assigner), and has already been compiled as a standalone executable which can be found in [the releases tab](https://github.com/TTK4145/Project-resources/releases/latest). + +---- + +Again, we reuse the functions that we already have from the single elevator algorithm: Choose Direction, Should Stop, and Clear Requests At Current Floor - with the modification for clearing requests such that there are no side-effects when they are being cleared. + +The algorithm is very similar to that the Time To Idle function, but instead of simulating several single elevators to completion in turn, we simulate a single "step" for each elevator in turn. A "single step" here means something that takes time, which means either moving between floors or holding the door open. The main loop of the Time To Idle function must therefore be split into two phases, one for arriving at a floor and one for departing. And similarly, all elevators must be moved some initial step to put them into a state where they are either about to arrive or about to depart. + +Since a single step can have different durations associated with them (holding the door open might take longer than moving between floors), we make sure to always select the elevator that has the shortest total duration when choosing which elevator to move. The table of hall requests contains both the information of which requests are active (a boolean), and also who has been assigned each request (if it is active). The main loop then terminates once all active hall requests have been assigned. + +A single step looks something like this: + + - Create a temporary copy of this elevator, and assign it all active but unassigned requests. + - If arriving: + - Check if we should stop (given these temporary requests), and if we are stopping: + - Add the door open time + - Clear the request(s) at this floor, where the side-effect is *assigning it to ourselves* in the main hall request table + - Otherwise, keep moving to the next floor and add the travel time + - If departing: + - Choose a direction, and if we are idle: + - Remain idle + - Otherwise, depart in that direction and add the travel time + +---- +There is one major quirky issue though, involving the direction of the requests. Say we have one elevator at floor 0, one at floor 3, and two hall requests Down-1 and Up-2. The elevator at the bottom moves up to floor 1, the elevator at the top moves down to floor 2. In turn, they both see that there are requests further along in the direction of travel (as per the Should Stop function), and neither take the requests, but instead keep moving. Thus, the elevator at the top moved down to floor 1, and the elevator at the bottom moved up to floor 2, and have moved right past each other! +Which means we need a special case this situation, that can be expressed as "all the unassigned hall requests are at floors where there already is an elevator, and none of these elevators have any remaining cab requests". If this situation is true, we can take a shortcut in the main loop, and immediately assign all the remaining hall requests. diff --git a/cost_fns/hall_request_assigner/README.md b/cost_fns/hall_request_assigner/README.md new file mode 100644 index 0000000..ef65a4d --- /dev/null +++ b/cost_fns/hall_request_assigner/README.md @@ -0,0 +1,91 @@ +Hall request assigner +===================== + +Made for "on-the-fly" / "dynamic" hall request assignment, ie. where all hall requests are completely re-assigned every time a new request (or other event) arrives. + +### JSON format: + +Input: +``` +{ + "hallRequests" : + [[Boolean, Boolean], ...], + "states" : + { + "id_1" : { + "behaviour" : < "idle" | "moving" | "doorOpen" > + "floor" : NonNegativeInteger + "direction" : < "up" | "down" | "stop" > + "cabRequests" : [Boolean, ...] + }, + "id_2" : {...} + } +} +``` + +Output: +``` +{ + "id_1" : [[Boolean, Boolean], ...], + "id_2" : ... +} +``` + +The pairs of boolean hall requests are ordered `[[up-0, down-0], [up-1, down-1], ...]`, which is the standard in all official driver and example code. + + +### Example JSON: + +Input: +``` +{ + "hallRequests" : + [[false,false],[true,false],[false,false],[false,true]], + "states" : { + "one" : { + "behaviour":"moving", + "floor":2, + "direction":"up", + "cabRequests":[false,false,true,true] + }, + "two" : { + "behaviour":"idle", + "floor":0, + "direction":"stop", + "cabRequests":[false,false,false,false] + } + } +} +``` + +Output: +``` +{ + "one" : [[false,false],[false,false],[false,false],[false,true]], + "two" : [[false,false],[true,false],[false,false],[false,false]] +} +``` + + +Usage +----- + +### Downloading: + +See [the releases tab](https://github.com/TTK4145/Project-resources/releases/latest) to find executables. + +To download the code in order to compile yourself, use `git clone --recursive https://github.com/TTK4145/Project-resources` + +#### Building: + +Run `build.sh`, or copy its one line of content and run that. + +### Command line arguments: + + - `-i` | `--input` : JSON input. + - Example: `./hall_request_assigner --input '{"hallRequests":....}'` + - `--travelDuration` : Travel time between two floors in milliseconds (default 2500) + - `--doorOpenDuration` : Door open time in milliseconds (default 3000) + - `--clearRequestType` : When stopping at a floor, clear either `all` requests or only those `inDirn` (default) + +If JSON input is not passed on the command line, the program will read the first line from stdin instead. JSON output is written to stdout. diff --git a/cost_fns/hall_request_assigner/build.sh b/cost_fns/hall_request_assigner/build.sh new file mode 100644 index 0000000..b77ff57 --- /dev/null +++ b/cost_fns/hall_request_assigner/build.sh @@ -0,0 +1 @@ +dmd main.d config.d elevator_algorithm.d elevator_state.d optimal_hall_requests.d d-json/jsonx.d -w -g -ofhall_request_assigner; \ No newline at end of file diff --git a/cost_fns/hall_request_assigner/config.d b/cost_fns/hall_request_assigner/config.d new file mode 100644 index 0000000..e19a757 --- /dev/null +++ b/cost_fns/hall_request_assigner/config.d @@ -0,0 +1,10 @@ + +int doorOpenDuration = 3000; +int travelDuration = 2500; + +enum ClearRequestType { + all, + inDirn, +} + +ClearRequestType clearRequestType = ClearRequestType.all; \ No newline at end of file diff --git a/cost_fns/hall_request_assigner/d-json b/cost_fns/hall_request_assigner/d-json new file mode 160000 index 0000000..2aeef1f --- /dev/null +++ b/cost_fns/hall_request_assigner/d-json @@ -0,0 +1 @@ +Subproject commit 2aeef1f27b9e6590ff5d0fa757bc9135bc6fa0b5 diff --git a/cost_fns/hall_request_assigner/elevator_algorithm.d b/cost_fns/hall_request_assigner/elevator_algorithm.d new file mode 100644 index 0000000..8701316 --- /dev/null +++ b/cost_fns/hall_request_assigner/elevator_algorithm.d @@ -0,0 +1,110 @@ +import std.algorithm; +import std.range; +import std.stdio; + +import elevator_state; +import config; + + + +private bool requestsAbove(ElevatorState e){ + return e.requests[e.floor+1..$].map!(a => a.array.any).any; +} + +private bool requestsBelow(ElevatorState e){ + return e.requests[0..e.floor].map!(a => a.array.any).any; +} + +bool anyRequests(ElevatorState e){ + return e.requests.map!(a => a.array.any).any; +} + +bool anyRequestsAtFloor(ElevatorState e){ + return e.requests[e.floor].array.any; +} + + +bool shouldStop(ElevatorState e){ + final switch(e.direction) with(Dirn){ + case up: + return + e.requests[e.floor][CallType.hallUp] || + e.requests[e.floor][CallType.cab] || + !e.requestsAbove || + e.floor == 0 || + e.floor == e.requests.length-1; + case down: + return + e.requests[e.floor][CallType.hallDown] || + e.requests[e.floor][CallType.cab] || + !e.requestsBelow || + e.floor == 0 || + e.floor == e.requests.length-1; + case stop: + return true; + } +} + +Dirn chooseDirection(ElevatorState e){ + final switch(e.direction) with(Dirn){ + case up: + return + e.requestsAbove ? up : + e.requestsBelow ? down : + stop; + case down, stop: + return + e.requestsBelow ? down : + e.requestsAbove ? up : + stop; + } +} + +ElevatorState clearReqsAtFloor(ElevatorState e, void delegate(CallType c) onClearedRequest = null){ + + auto e2 = e; + + + void clear(CallType c){ + if(e2.requests[e2.floor][c]){ + if(&onClearedRequest){ + onClearedRequest(c); + } + e2.requests[e2.floor][c] = false; + } + } + + + final switch(clearRequestType) with(ClearRequestType){ + case all: + for(auto c = CallType.min; c < e2.requests[0].length; c++){ + clear(c); + } + break; + + case inDirn: + clear(CallType.cab); + + final switch(e.direction) with(Dirn){ + case up: + clear(CallType.hallUp); + if(!e2.requestsAbove){ + clear(CallType.hallDown); + } + break; + case down: + clear(CallType.hallDown); + if(!e2.requestsBelow){ + clear(CallType.hallUp); + } + break; + case stop: + clear(CallType.hallUp); + clear(CallType.hallDown); + break; + } + break; + } + + return e2; +} \ No newline at end of file diff --git a/cost_fns/hall_request_assigner/elevator_state.d b/cost_fns/hall_request_assigner/elevator_state.d new file mode 100644 index 0000000..4d6f145 --- /dev/null +++ b/cost_fns/hall_request_assigner/elevator_state.d @@ -0,0 +1,72 @@ + +import std.algorithm; +import std.array; +import std.conv; +import std.range; + +enum CallType : int { + hallUp, + hallDown, + cab +} + +enum HallCallType : int { + up, + down +} + +enum Dirn : int { + down = -1, + stop = 0, + up = 1 +} + +enum ElevatorBehaviour { + idle, + moving, + doorOpen, +} + + +struct LocalElevatorState { + ElevatorBehaviour behaviour; + int floor; + Dirn direction; + bool[] cabRequests; + this(this){ + cabRequests = cabRequests.dup; + } +} + + +struct ElevatorState { + ElevatorBehaviour behaviour; + int floor; + Dirn direction; + bool[3][] requests; + this(this){ + requests = requests.dup; + } +} + +LocalElevatorState local(ElevatorState e){ + return LocalElevatorState( + e.behaviour, + e.floor, + e.direction + ); +} + +ElevatorState withRequests(LocalElevatorState e, bool[2][] hallReqs){ + return ElevatorState( + e.behaviour, + e.floor, + e.direction, + zip(hallReqs, e.cabRequests).map!(a => a[0] ~ a[1]).array.to!(bool[3][]), + ); +} + +bool[2][] hallRequests(ElevatorState e){ + return e.requests.to!(bool[][]).map!(a => a[0..2]).array.to!(bool[2][]); +} + diff --git a/cost_fns/hall_request_assigner/main.d b/cost_fns/hall_request_assigner/main.d new file mode 100644 index 0000000..de77753 --- /dev/null +++ b/cost_fns/hall_request_assigner/main.d @@ -0,0 +1,76 @@ + +import std.conv; +import std.getopt; +import std.stdio; + +import optimal_hall_requests; +import elevator_state; +import config; +import jsonx; + +struct Input { + bool[][] hallRequests; + LocalElevatorState[string] states; +} + +void main(string[] args){ + + string input_str; + + args.getopt( + std.getopt.config.passThrough, + "doorOpenDuration", &doorOpenDuration, + "travelDuration", &travelDuration, + "clearRequestType", &clearRequestType, + "i|input", &input_str, + ); + + if(input_str == string.init){ + input_str = readln; + input_str = input_str[0..$-1]; // remove trailing newline + } + + Input i = input_str.jsonDecode!Input; + + optimalHallRequests(i.hallRequests.to!(bool[2][]), i.states) + .jsonEncode + .writeln; +} + + + +unittest { + import std.regex; + + Input i = jsonDecode!Input(q{ + { + "hallRequests" : + [[false,false],[true,false],[false,false],[false,true]], + "states" : { + "one" : { + "behaviour":"moving", + "floor":2, + "direction":"up", + "cabRequests":[false,false,true,true] + }, + "two" : { + "behaviour":"idle", + "floor":0, + "direction":"stop", + "cabRequests":[false,false,false,false] + } + } + } + }.replaceAll(regex(r"\s"), "")); + + string correctOutput = q{ + { + "one" : [[false,false],[false,false],[false,false],[false,true]], + "two" : [[false,false],[true,false],[false,false],[false,false]] + } + }.replaceAll(regex(r"\s"), ""); + + + string output = optimalHallRequests(i.hallRequests.to!(bool[2][]), i.states).jsonEncode; + assert(output == correctOutput); +} diff --git a/cost_fns/hall_request_assigner/optimal_hall_requests.d b/cost_fns/hall_request_assigner/optimal_hall_requests.d new file mode 100644 index 0000000..c0ed973 --- /dev/null +++ b/cost_fns/hall_request_assigner/optimal_hall_requests.d @@ -0,0 +1,354 @@ +//debug = optimal_hall_requests; + +import elevator_state; +import elevator_algorithm; +import config; + +import std.algorithm; +import std.conv; +import std.datetime; +import std.range; +import std.stdio; + + + + +bool[2][][string] optimalHallRequests( + bool[2][] hallReqs, + LocalElevatorState[string] elevatorStates, +){ + auto reqs = hallReqs.toReq; + auto states = initialStates(elevatorStates); + + debug(optimal_hall_requests) writefln("\n --- START --- "); + debug(optimal_hall_requests) writefln("states:\n %(%s,\n %)", states); + debug(optimal_hall_requests) writefln("reqs:\n%( %(%s, %)\n%)", reqs); + + foreach(ref s; states){ + performInitialMove(s, reqs); + } + + while(true){ + debug(optimal_hall_requests) writeln; + debug(optimal_hall_requests) writefln("states:\n %(%s,\n %)", states); + debug(optimal_hall_requests) writefln("reqs:\n%( %(%s, %)\n%)", reqs); + + bool done = true; + if(reqs.anyUnassigned){ + done = false; + } + if(unvisitedAreImmediatelyAssignable(reqs, states)){ + debug(optimal_hall_requests) writefln("unvisited immediately assignable"); + assignImmediate(reqs, states); + done = true; + } + + if(done){ + break; + } + + + states.sort!("a.time < b.time")(); + performSingleMove(states[0], reqs); + } + + + bool[2][][string] result; + foreach(id, _; elevatorStates){ + result[id] = new bool[2][](hallReqs.length); + } + for(int f = 0; f < hallReqs.length; f++){ + for(int c = 0; c < 2; c++){ + if(reqs[f][c].active){ + result[reqs[f][c].assignedTo][f][c] = true; + } + } + } + + + debug(optimal_hall_requests) writefln("\nfinal:"); + debug(optimal_hall_requests) writefln("states:\n %(%s,\n %)", states); + debug(optimal_hall_requests) writefln("reqs:\n%( %(%s, %)\n%)", reqs); + debug(optimal_hall_requests) writefln("result:\n%( %s : %([%(%d, %)]%|, %)\n%)", result); + debug(optimal_hall_requests) writefln(" --- END --- \n"); + + return result; +} + +private : + +struct Req { + bool active; + string assignedTo; +} + +struct State { + string id; + LocalElevatorState state; + Duration time; +} + + + + +bool[2][] filterReq(alias fn)(Req[2][] reqs){ + return reqs.map!(a => a.to!(Req[]).map!(fn).array).array.to!(bool[2][]); +} + +Req[2][] toReq(bool[2][] hallReqs){ + return hallReqs.map!(a => a.to!(bool[]).map!(b => Req(b, string.init)).array).array.to!(Req[2][]); +} + +ElevatorState withReqs(alias fn)(State s, Req[2][] reqs){ + return s.state.withRequests(reqs.filterReq!(fn)); +} + +bool anyUnassigned(Req[2][] reqs){ + return reqs + .filterReq!(a => a.active && a.assignedTo == string.init) + .map!(a => a.to!(bool[]).any).any; +} + +State[] initialStates(LocalElevatorState[string] states){ + return zip(states.keys, states.values) + .sort!(q{a[0] < b[0]}) + .zip(iota(states.length)) + .map!(a => + State(a[0][0], a[0][1], a[1].usecs) + ) + .array; +} + + + +void performInitialMove(ref State s, ref Req[2][] reqs){ + debug(optimal_hall_requests) writefln("initial move: %s", s); + final switch(s.state.behaviour) with(ElevatorBehaviour){ + case doorOpen: + debug(optimal_hall_requests) writefln(" closing door"); + s.time += doorOpenDuration.msecs/2; + goto case idle; + case idle: + foreach(c; 0..2){ + if(reqs[s.state.floor][c].active){ + debug(optimal_hall_requests) writefln(" taking req %s at current floor", c); + reqs[s.state.floor][c].assignedTo = s.id; + s.time += doorOpenDuration.msecs; + } + } + break; + case moving: + debug(optimal_hall_requests) writefln(" arriving"); + s.state.floor += s.state.direction; + s.time += travelDuration.msecs/2; + break; + } +} + +void performSingleMove(ref State s, ref Req[2][] reqs){ + + debug(optimal_hall_requests) writefln("single move: %s", s); + + auto e = s.withReqs!(a => a.active && (a.assignedTo == string.init || a.assignedTo == s.id))(reqs); + + debug(optimal_hall_requests) writefln("%s", e); + + final switch(s.state.behaviour) with(ElevatorBehaviour){ + case moving: + if(e.shouldStop){ + debug(optimal_hall_requests) writefln(" stopping"); + s.state.behaviour = doorOpen; + s.time += doorOpenDuration.msecs; + e.clearReqsAtFloor((CallType c){ + final switch(c) with(CallType){ + case hallUp, hallDown: + reqs[s.state.floor][c].assignedTo = s.id; + break; + case cab: + s.state.cabRequests[s.state.floor] = false; + } + }); + } else { + debug(optimal_hall_requests) writefln(" continuing"); + s.state.floor += s.state.direction; + s.time += travelDuration.msecs; + } + break; + case idle, doorOpen: + s.state.direction = e.chooseDirection; + if(s.state.direction == Dirn.stop){ + s.state.behaviour = idle; + debug(optimal_hall_requests) writefln(" idling"); + } else { + s.state.behaviour = moving; + debug(optimal_hall_requests) writefln(" departing"); + s.state.floor += s.state.direction; + s.time += travelDuration.msecs; + } + break; + } +} + +// no remaining cab requests and all unvisited hall requests are at floors with elevators +bool unvisitedAreImmediatelyAssignable(Req[2][] reqs, State[] states){ + if(states.map!(a => a.state.cabRequests.any).any){ + return false; + } + foreach(f, reqsAtFloor; reqs){ + foreach(c, req; reqsAtFloor){ + if(req.active && req.assignedTo == string.init){ + if(states.filter!(a => a.state.floor == f && !a.state.cabRequests.any).empty){ + return false; + } + } + } + } + return true; +} + +void assignImmediate(ref Req[2][] reqs, ref State[] states){ + foreach(f, ref reqsAtFloor; reqs){ + foreach(c, ref req; reqsAtFloor){ + if(req.active && req.assignedTo == string.init){ + foreach(ref s; states){ + if(s.state.floor == f && !s.state.cabRequests.any){ + req.assignedTo = s.id; + s.time += doorOpenDuration.msecs; + } + } + } + } + } +} + + + + + + + + + +unittest { + // Elevator 1 is idle one floor away, other elevators have several cab orders + // Should give the order to the idle elevator + LocalElevatorState[string] states = [ + "1" : LocalElevatorState(ElevatorBehaviour.idle, 0, Dirn.stop, [0, 0, 0, 0].to!(bool[])), + "2" : LocalElevatorState(ElevatorBehaviour.doorOpen, 3, Dirn.down, [1, 0, 0, 0].to!(bool[])), + "3" : LocalElevatorState(ElevatorBehaviour.moving, 2, Dirn.up, [1, 0, 0, 1].to!(bool[])), + ]; + + bool[2][] hallreqs = [ + [false, false], + [true, false], + [false, false], + [false, false], + ]; + + + auto optimal = optimalHallRequests(hallreqs, states); + assert(optimal == [ + "1" : [[0,0],[1,0],[0,0],[0,0]].to!(bool[2][]), + "2" : [[0,0],[0,0],[0,0],[0,0]].to!(bool[2][]), + "3" : [[0,0],[0,0],[0,0],[0,0]].to!(bool[2][]), + ]); +} + +unittest { + // Two elevators moving from each "end" toward the middle floors + // Elevators should stop at the closest order, even if it is in the "wrong" direction + LocalElevatorState[string] states = [ + "1" : LocalElevatorState(ElevatorBehaviour.idle, 0, Dirn.stop, [0, 0, 0, 0].to!(bool[])), + "2" : LocalElevatorState(ElevatorBehaviour.idle, 3, Dirn.stop, [0, 0, 0, 0].to!(bool[])), + ]; + + bool[2][] hallreqs = [ + [false, false], + [false, true], + [true, false], + [false, false], + ]; + + + auto optimal = optimalHallRequests(hallreqs, states); + assert(optimal == [ + "1" : [[0,0],[0,1],[0,0],[0,0]].to!(bool[2][]), + "2" : [[0,0],[0,0],[1,0],[0,0]].to!(bool[2][]), + ]); + + + // Change E1 idle->moving, stop->up. E1 is closer, but otherwise same scenario + states = [ + "1" : LocalElevatorState(ElevatorBehaviour.moving, 0, Dirn.up, [0, 0, 0, 0].to!(bool[])), + "2" : LocalElevatorState(ElevatorBehaviour.idle, 3, Dirn.stop, [0, 0, 0, 0].to!(bool[])), + ]; + + optimal = optimalHallRequests(hallreqs, states); + assert(optimal == [ + "1" : [[0,0],[0,1],[0,0],[0,0]].to!(bool[2][]), + "2" : [[0,0],[0,0],[1,0],[0,0]].to!(bool[2][]), + ]); + + + // Add cab order to E1, so that it has to continue upward anyway + // Changes scenario, now E1 skips order in "wrong" direction because there is work ahead in that dirn + states = [ + "1" : LocalElevatorState(ElevatorBehaviour.idle, 0, Dirn.stop, [0, 0, 1, 0].to!(bool[])), + "2" : LocalElevatorState(ElevatorBehaviour.idle, 3, Dirn.stop, [0, 0, 0, 0].to!(bool[])), + ]; + + optimal = optimalHallRequests(hallreqs, states); + assert(optimal == [ + "1" : [[0,0],[0,0],[1,0],[0,0]].to!(bool[2][]), + "2" : [[0,0],[0,1],[0,0],[0,0]].to!(bool[2][]), + ]); +} + +unittest { + // Two elevators are the same number of floors away from an order, but one is moving toward it + // Should give the order to the moving elevator + LocalElevatorState[string] states = [ + "27" : LocalElevatorState(ElevatorBehaviour.moving, 1, Dirn.down, [0, 0, 0, 0].to!(bool[])), + "20" : LocalElevatorState(ElevatorBehaviour.doorOpen, 1, Dirn.down, [0, 0, 0, 0].to!(bool[])), + ]; + + bool[2][] hallreqs = [ + [true, false], + [false, false], + [false, false], + [false, false], + ]; + + auto optimal = optimalHallRequests(hallreqs, states); + assert(optimal == [ + "27" : [[1,0],[0,0],[0,0],[0,0]].to!(bool[2][]), + "20" : [[0,0],[0,0],[0,0],[0,0]].to!(bool[2][]), + ]); +} + +unittest { + // Two hall requests at the same floor, but the closest elevator also has a cab call further in the same direction + // Should give the two hall orders to different elevators, the one in the "opposite" direction to the one without a cab order + LocalElevatorState[string] states = [ + "1" : LocalElevatorState(ElevatorBehaviour.moving, 3, Dirn.down, [1, 0, 0, 0].to!(bool[])), + "2" : LocalElevatorState(ElevatorBehaviour.idle, 3, Dirn.down, [0, 0, 0, 0].to!(bool[])), + ]; + + bool[2][] hallreqs = [ + [false, false], + [true, true], + [false, false], + [false, false], + ]; + + auto crt = clearRequestType; + clearRequestType = ClearRequestType.inDirn; + scope(exit) clearRequestType = crt; + + auto optimal = optimalHallRequests(hallreqs, states); + assert(optimal == [ + "1" : [[0,0],[0,1],[0,0],[0,0]].to!(bool[2][]), + "2" : [[0,0],[1,0],[0,0],[0,0]].to!(bool[2][]), + ]); +} +