Skip to content

Commit

Permalink
Add hall request assigner
Browse files Browse the repository at this point in the history
  • Loading branch information
klasbo committed Feb 19, 2018
1 parent bbff917 commit 13f0e56
Show file tree
Hide file tree
Showing 10 changed files with 754 additions and 4 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -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
40 changes: 36 additions & 4 deletions cost_fns/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
-------------

Expand All @@ -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;
Expand Down Expand Up @@ -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.
Expand Down
91 changes: 91 additions & 0 deletions cost_fns/hall_request_assigner/README.md
Original file line number Diff line number Diff line change
@@ -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.
1 change: 1 addition & 0 deletions cost_fns/hall_request_assigner/build.sh
Original file line number Diff line number Diff line change
@@ -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;
10 changes: 10 additions & 0 deletions cost_fns/hall_request_assigner/config.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

int doorOpenDuration = 3000;
int travelDuration = 2500;

enum ClearRequestType {
all,
inDirn,
}

ClearRequestType clearRequestType = ClearRequestType.all;
1 change: 1 addition & 0 deletions cost_fns/hall_request_assigner/d-json
Submodule d-json added at 2aeef1
110 changes: 110 additions & 0 deletions cost_fns/hall_request_assigner/elevator_algorithm.d
Original file line number Diff line number Diff line change
@@ -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;
}
72 changes: 72 additions & 0 deletions cost_fns/hall_request_assigner/elevator_state.d
Original file line number Diff line number Diff line change
@@ -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][]);
}

Loading

0 comments on commit 13f0e56

Please sign in to comment.