0

The problem

An elevator in a tower of seven floors numbered from 0 to 6. The hardware and software components are:

  • A cabin, with doors and a command panel to choose a destination
  • An Elevator Shaft, which moves the Cabin and present at each floor a button to call the Cabin
  • A Command And Control (C2) software receives information and send messages to : opens and closes the doors and moves or stops the Cabin

Elevator Shaft send to C2 the location of the Cabin as a percentage: 0 is the bottom of the tower, 1.0 is the top.

A typical use case is:

  • The cabin is at floor 0
  • At time = 80, user "U1" at floor 6 press the "call" button, the cabin moves from 0 to 6
  • At time = 100, user "U2" call the cabin from floor 2, when the cabin is at floor 2, C2 stops it, opens the doors, close the doors and get going to 6
  • At time = 150, user "U2", from the Cabin press "floor 0" button, its request in memorized, and it will be executed after the arrival at 6
  • The cabin finally arrive at floor 6, C2 stops, opens the doors, closes the doors and send a message to Elevator Shaft to go to floor 0

The context

This problem has been well solved in Java and C++, with 90/100 lines of code.

It has also been specified with a tool named Stimulus, in a declarative form which can be "executed" (it works!). I'm trying to code the declarative requirements in Prolog to measure the distance between a formal, declarative, executable specification and the Prolog code.

My question

The whole problem require a lot of clauses, I try to solve one by one.

First, I need the predicate: "is the cabin has to stop at location #N?"

I write the clause stopIsRequired which is verified when a user want to stop at the nth floor (see scenario, at #2 and #6 here) AND the current location of the elevator cabin is at the requested floor (0.33 and 1.0 here).

Here is my first try:

#!/usr/bin/swipl

epsilon( 0.01 ).
floor_locations([ 0.0, 0.17, 0.33, 0.5, 0.67, 0.83, 1.0 ]).
scenario([ false, false, true, false, false, false, true ]).

stopIsRequired( [true|_], CabinLocation, [Floor_location|_] ) :-
    Distance is abs( CabinLocation - Floor_location ),
    epsilon( Epsilon ),
    Distance =< Epsilon,
    !.
stopIsRequired( [false|Floor_has_to_be_served_list_tail], CabinLocation, [_|Floor_locations_tail] ) :-
    stopIsRequired( Floor_has_to_be_served_list_tail, CabinLocation, Floor_locations_tail ).
stopIsRequired( [], _, [] ).

/*--------------------------------- TESTS ---------------------------------*/

scenario([ false, false, true, false, false, false, true ]).

stopIsRequired_tests( [Location|Locations] ) :-
    scenario( Scenario ),
    floor_locations( Floor_locations ),
    CabinLocation is Location + 0.005,
    stopIsRequired( Scenario, CabinLocation, Floor_locations ),
    format( "Cabin must stop when location equals ~w~n", [CabinLocation]),
    stopIsRequired_tests( Locations ).
stopIsRequired_tests( [_|Locations] ) :-
    stopIsRequired_tests( Locations ).
stopIsRequired_tests( [] ).

stopIsRequired_tests :-
    floor_locations( Locations ),
    stopIsRequired_tests( Locations ).

I expect that stopIsRequired_tests prints "Cabin must stop when location equals 0.335" and "Cabin must stop when location equals 1.005" but only "Cabin must stop when location equals 0.335" is printed.

Aubin
  • 14,617
  • 9
  • 61
  • 84
  • Did you try to debug/trace your code? – TA_intern Nov 12 '20 at 14:08
  • I tried to understand your code and gave up. Is the duplication of values between the list in `floor_locations` and the six tests significant? – TA_intern Nov 12 '20 at 14:23
  • I don't known how to debug Prolog, I'm a very beginner. I have added some explanation of this use case : a naive elevator problem I defined to help me learn Prolog (as a hobby). – Aubin Nov 12 '20 at 17:05
  • You can _always_ debug by printing out things. Like, `writeln(X)` or maybe `format(user_error, "~w~n", [X])` – TA_intern Nov 13 '20 at 06:51
  • 2
    But there is more: see this intro to the command-line debugger: https://blog.inductorsoftware.com/blog/SWIPrologTrace and this for the graphical debugger: https://blog.inductorsoftware.com/blog/SWIPrologGraphicalDebugger it seems both are brand new – TA_intern Nov 13 '20 at 06:53
  • Added some questions in my response. Maybe you can add some notes to your question to clarify the problem. Thanks! – David Tonhofer Nov 13 '20 at 10:30
  • This amended question is much better. But you want an "elevator simulator" in Prolog, in a sense. First, you need _time_. Then you have a list of `request-time` pairs: `[reqforfloor1-0, reqforfloor2-12, reqforfloor1-13, ... ]`, possibly infinite. You have elevator state (not moving/moving up/down and at location in [0,1]) and you have the control software which drives the elevator (a predicate called regularly), resulting in a sequence of elevator moves at certain times `[go(floor0)-1, opendoor-2, closedoor-3, go(floor1)-4, ...]` which is then output for inspection. – David Tonhofer Nov 14 '20 at 12:54
  • But you seem to want to have assurance that certain requirement (in particular, safety) are not violated during such a run (e.g. the elevator opening the door between two floors) Right? So, the elevator simulator is doable in Prolog. But, you may be looking for the [Event Calculus](https://en.wikipedia.org/wiki/Event_calculus). – David Tonhofer Nov 14 '20 at 12:57
  • Unless you are _really_ looking for a theorem prover that proves that the specification fulfills all requirements (as in [this paper](https://www.researchgate.net/publication/261768463_Reasoning_and_Verification_State_of_the_Art_and_Current_Trends) which is not really Prolog's domain.(Also, this may well be a problem for languages like [Esterel](https://en.wikipedia.org/wiki/Esterel) ... Gérard Berry FTW!) – David Tonhofer Nov 14 '20 at 12:57

2 Answers2

3

According to first version of question

Here is a partial fix with comments highlighting problems, using plunit (I'm a fan!)

Run with run_tests. in SWI-Prolog.

epsilon( 0.01 ).
floor_locations([ 0.0, 0.17, 0.33, 0.5, 0.67, 0.83, 1.0 ]).

scenario([ false, false, true, false, false, false, true ]).

:- begin_tests(elevator).

stopIsRequired( true, Distance ) :-
    epsilon( Epsilon ),
    Distance =< Epsilon.

stopIsRequired( false, Distance ) :-      % <---- If you reify the success/failure, you must add a clause for the complement so that the predicate always succeeds
    epsilon( Epsilon ),
    Distance > Epsilon.

stopIsRequired( [Stop|Stops], CabinLoc, [Loc|Locs] ) :-
    Delta is abs( CabinLoc - Loc ),          % <---- You cannot "eval a function inline" in Prolog
    stopIsRequired( StopComputed, Delta ),
    assertion(Stop == StopComputed),         % <---- Not sure what is supposed to happen; Stop is a given...
    stopIsRequired( Stops, CabinLoc, Locs ).

stopIsRequired( [], _, [] ).                 % <----- You must terminate the recursion successfully

test("Floor 0") :-
    scenario( Scenario ),
    floor_locations( Locations ),
    stopIsRequired( Scenario, 0.0, Locations ).

test("Floor 1") :-
    scenario( Scenario ),
    floor_locations( Locations ),
    stopIsRequired( Scenario, 0.17, Locations ).

test("Floor 2") :-
    scenario( Scenario ),
    floor_locations( Locations ),
    stopIsRequired( Scenario, 0.33, Locations ).

test("Floor 3") :-
    scenario( Scenario ),
    floor_locations( Locations ),
    stopIsRequired( Scenario, 0.5, Locations ).

test("Floor 4") :-
    scenario( Scenario ),
    floor_locations( Locations ),
    stopIsRequired( Scenario, 0.67, Locations ).

test("Floor 5") :-
    scenario( Scenario ),
    floor_locations( Locations ),
    stopIsRequired( Scenario, 0.83, Locations ).

test("Floor 6") :-
    scenario( Scenario ),
    floor_locations( Locations ),
    stopIsRequired( Scenario, 1.0, Locations ).

:- end_tests(elevator).    

Trying again

After all this, I still don't really understand the problem we are trying to solve. What are the givens, and what do we want to test?

Explainer

  • We have the floor positions
  • We have the user requests to stop
  • We DO NOT have the error-afflicted cabin positions though. They seem to be conceptually mixed up with the floor positions.
  • Neither do we have the expected outcomes from a test. They seem to be conceptually mixed up with the user requests to stop.

Can you clarify this please?

Some code that may be useful if the question becomes clearer

epsilon( 0.01 ).
floor_locations([ 0.0, 0.17, 0.33, 0.5, 0.67, 0.83, 1.0 ]).

% stopIsRequired(+ScenarioValues, +CabinLocationFloat, +FloorLocationFloats)

stopIsRequired( [ScenVal|ScenVals], CabinLoc, [FloorLoc|FloorLocs] ) :-
    epsilon( Epsilon ),
    Distance is abs( CabinLoc - FloorLoc ),
    % if the next call (containing OR) succeeds, we are good
    ((Distance =< Epsilon, ScenVal = true)
      ;
     (Distance > Epsilon, ScenVal = false)),
    !, % cut here because there is no need to try the code above a second time 
    format("On floor ~g with CabinLoc = ~g: ~q\n", [FloorLoc,CabinLoc,ScenVal]),
    % and so we can continue on the recursion
    stopIsRequired( ScenVals, CabinLoc, FloorLocs ).

% God's in his heaven, all is right at the recursion's end

stopIsRequired( [], _, [] ).

/*----------------------------- TESTS ------ */

scenario([ X1, X2, X3, X4, X5, X6, X7 ]).

stopIsRequiredAtLocation( CabinLoc ) :-
    scenario( ScenVals ),
    floor_locations( FloorLocs ),
    stopIsRequired( ScenVals, CabinLoc, FloorLocs ).

Test call:

maplist(stopIsRequiredAtLocation, [0.000, 0.167, 0.333, 0.5, 0.667, 0.833, 1.000]).  

But that's still not it, the code makes still no sense after all.

David Tonhofer
  • 14,559
  • 5
  • 55
  • 51
  • Thanks! Your advices have helped but I have edited my question, the problem remains for floor 6. – Aubin Nov 12 '20 at 17:03
  • @Aubin Well, if I load the new code, I already get a few warnings. But more importantly, what is the program supposed to do? It seems to too much recursion. Can you do a Pascal-like pseudocode description? – David Tonhofer Nov 12 '20 at 21:23
  • The elevator has to stop where people are waiting (scenario) when it's located (Location) at the correct floor. I've posted a solution. – Aubin Nov 12 '20 at 22:00
  • The fixed updated code doesn't work, the answer is always 'false' – Aubin Nov 12 '20 at 22:37
  • @Aubin yeah.. But what happens with the `[0.000, 0.167, 0.333, 0.5, 0.667, 0.833, 1.000]` what's up with those? They represent cabin location as you compare them to the floor, but what's the exact relationship to the scenario? – David Tonhofer Nov 12 '20 at 22:41
  • The elevator serves 7 floors. When it's called from floor 2 and 6, the list `UserRequests` contains [false, false, true, false, false, false, true] here is the scenario. The position of the cabin is between 0.0 (bottom) and 1.0 (last floor). – Aubin Nov 12 '20 at 23:01
  • I have detailed the context in my question. – Aubin Nov 13 '20 at 12:56
1

Finally, with your advices (thanks!), I've made a short version which do what I want:

#!/usr/bin/swipl

epsilon( 0.01 ).
floorLocations([ 0.0, 0.17, 0.33, 0.5, 0.67, 0.83, 1.0 ]).

stopIsRequired( [true|_], CabinLocation, [FloorLocation|_], true ) :-
    epsilon( Epsilon ),
    Distance is abs( CabinLocation - FloorLocation ),
    Distance =< Epsilon,
    !.
stopIsRequired( [_|UserRequestsTail], CabinLocation, [_|FloorLocationsTail], MustStop ) :-
    stopIsRequired( UserRequestsTail, CabinLocation, FloorLocationsTail, MustStop ).
stopIsRequired( [], _, [], false ).

/*--------------------------------- TESTS ---------------------------------*/

scenario([ false, false, true, false, false, false, true ]).

stopIsRequiredAtLocation( CabinLocation ) :-
    scenario( Scenario ),
    floorLocations( FloorLocations ),
    stopIsRequired( Scenario, CabinLocation, FloorLocations, true ).

Here is the execution, the parameter is the location of the elevator cabin and the scenario is the floor where people are waiting:

?- stopIsRequiredAtLocation( 0.000 ).
false.

?- stopIsRequiredAtLocation( 0.167 ).
false.

?- stopIsRequiredAtLocation( 0.333 ).
true.

?- stopIsRequiredAtLocation( 0.5 ).
false.

?- stopIsRequiredAtLocation( 0.667 ).
false.

?- stopIsRequiredAtLocation( 0.833 ).
false.

?- stopIsRequiredAtLocation( 1.000 ).
true.

?- 

I'm not satisfied with the cut. A proposal to remove it is welcome.

Aubin
  • 14,617
  • 9
  • 61
  • 84
  • 1
    Hint: You can use [`maplist`](https://eu.swi-prolog.org/pldoc/doc_for?object=maplist/2) to apply a predicate iteratively to a sequence of arguments in one go: `maplist(stopIsRequiredAtLocation,[0.000, 0.167, 0.333, 0.5, 0.667, 0.833, 1.000]).` – David Tonhofer Nov 12 '20 at 22:02
  • Reading through the code (I will just go through the points) ... – David Tonhofer Nov 12 '20 at 22:06
  • 1) Let's get rid of the reification of truth values, as in ` stopIsRequired( Scenario, Location, Floor_locations, true ).` Just let the predicate call fail or succeed: `stopIsRequired( Scenario, Location, Floor_locations)` either succeeds or not. – David Tonhofer Nov 12 '20 at 22:06
  • 1
    2) Stylistically, write variables in CamelCase instead of Snake_case, and keep them short for easy eyeball scanning (there are usually only a few variables in a clause of 1-5 characters or so) – David Tonhofer Nov 12 '20 at 22:08
  • 1
    3) There is no need to break the `stopIsRequired/3` (without reified outcome) into two clauses, one for the "check at the head of the list" and one for "recursion". Do both in one! If the check fails (i.e we see a `true` at the head of the `Scenario` list instead of the expected `false`, or the converse) then _fail_ the predicate (and do no recursion), otherwise, do the recursion (and make the predicate's success or failure depend on the success or failure of the recursion). This also obviates the need for the cut. – David Tonhofer Nov 12 '20 at 22:13
  • 1
    @DavidTonhofer (sidenote) reification *IS* indirection! (that which by adding an additional step of, any computational problem can allegedly be solved). ---- (incidentally, [monad](https://stackoverflow.com/a/64377797/849891) *is* reified computations, i.e. Interpreter Pattern.) – Will Ness Nov 13 '20 at 10:31
  • @WillNess Yes it is. One can consider it "taking one step upwards" - observing the computation from another computation. Although I have not considered the monad as an Interpreter Pattern ... isn't it all about mapping the value domain into the monad value domain to be able to chain computations without caring about incidental complexity (null values, incontinent side effects, exceptions etc) giving a nice **one-liner** (category theory meets ... implementation pragmatics? when worlds collide!), then later map back to the original value domain? – David Tonhofer Nov 13 '20 at 10:40
  • 1
    @DavidTonhofer you have described interpretable reified computations (computations as first-class things in our own programming language). :) common lisp does the same thing but from the other end, with its macros and "compiler is a part of the language". assembler / machine code was always like that, too. interpretation / source code is primary, compilation is secondary, an *efficiency* optimization. except that because of property concerns the world cut the cord and turned to distributing the executables *without* the source code. a *trillion* dollars mistake, perhaps. :) – Will Ness Nov 13 '20 at 13:06