% Object: realized as the "primary key" (and first argument) of some predicates, % its identifier is a classname(id) term % TODO: add pseudo random noise to the simulation and sensors % TODO: conveyor should "drop off" transported objects when they get to the end %%% First, some generic object facets % fluents location(_Ob,_Point). % Point is point(X,Y) % Although some locations do not change, objects are assumed to be created over time, making their "timeless" % attributes - such as some locations - as effectively... fluents actions setLocation(_Ob,_Point). setLocation(Ob,L) updates Old to L in location(Ob,Old) if L=point(X,Y),number(X),number(Y). % some basic type checking fluents working(_Ob,_Yes). % whether an object is on (true/false) actions start(_Object1), stop(_Object2). start(Ob) updates Old to true in working(Ob,Old). stop(Ob) updates Old to (false) in working(Ob,Old). % LPS hacking: clone(Ob,New) from T1 to T2 if Ob=..[Class,_], objectName(X), New=..[Class,X], findall(NF,(holds(F,T1), F=..[FF,Ob|Args], NF=..[FF,New|Args]),NewFluents), initiateAll(NewFluents) from T1 to T2. initiateAll([F|Fluents]) from T1 to T2 if initiate F from T1, initiateAll(Fluents) from T1 to T2. initiateAll([]) from T1 to T2 if T2 =:= T1+1. % Make N clones of a prototype object in a single cycle clone(N,Proto,[New|Clones]) from T1 to T2 if N>0, clone(Proto,New) from T1 to T2, NewN is N-1, clone(NewN,Proto,Clones) from T1 to T2. clone(0,_,[]). %%% Conveyor: an object continuously changing some objects' locations, % in a straight line from the start point to the endpoint, at Speed (per LPS cycle) as measured on the line vector % the conveyor's location is the start point fluents transports(_ConveyorId,_ObjectId), conveyorEndpoint(_C,_Point), conveyorSpeed(_C,_Speed). % Speed may be negative actions setConveyorSpeed(_ObjectID,_Speed). setConveyorSpeed(Ob,S) updates Old to S in conveyorSpeed(Ob,Old). placeOnConveyor(Ob,ConveyorOb,DistanceFromStart) from T1 to T2 if not transports(ConveyorOb,Ob) at T1, location(ConveyorOb,Start), conveyorEndpoint(ConveyorOb,End), interpolate(Start,End,DistanceFromStart,Point), update Old to Point in location(Ob,Old) from T1 to T2, initiate transports(ConveyorOb,Ob) from T1 to T2. % Ob will be the new object ID; Name is optional (can be var), and somehow redundant with Ob; it's useful mostly % for debugging createConveyor(Name,Start,End,Ob) from T1 to T2 if Ob=conveyor(Name), objectName(Name), initiate location(Ob,Start) from T1 to T2, initiate conveyorEndpoint(Ob,End) from T1 to T2, initiate conveyorSpeed(Ob,0) from T1 to T2, initiate working(Ob,false) from T1 to T2. conveyor(C,Start,End,Working,Speed) at T if conveyorEndpoint(C,End) at T, location(C,Start) at T, working(C,Working) at T, conveyorSpeed(C,Speed) at T. d(conveyor(C,point(SX,SY_),point(EX,EY_),_Working,Speed),[ % type:line, strokeWidth: 2, strokeColor:black, from:[SX,SY], to:[EX, EY] ] ). type:Type, arrow:Speed, headLength:10, strokeWidth: 2, strokeColor:black, from:[RSX,RSY], to:[REX, REY]|Label ] ) :- SY is SY_-5, EY is EY_-5, % hack to draw our line a bit under the conveyed objects format(string(Sp),"~w px/cycle",[Speed]), (Speed=0 -> Type=line, RSX=SX, RSY=SY, REX=EX, REY=EY, label=[] ; Speed>0 -> Type=arrow, RSX=SX, RSY=SY, REX=EX, REY=EY, Label=[label:Sp] ; Type=arrow, RSX=EX, RSY=EY, REX=SX, REY=SY, Label=[label:Sp] ). % specific d/2 clauses for rendering % this alternative approach depends on asserted code which we're now avoiding; hence the richer, to-be-sampled fluent above %timeless_element([type:line, strokeWidth: 2, strokeColor:black, from:[SX,SY], to:[EX, EY]]) :- % conveyor class % conveyorEndpoint(C,point(EX,EY)), fixedLocation(C,point(SX,SY)). % The conveyor's active behavior: for all transported objects still far from the end, move them closer if conveyorSpeed(C,Speed) at T1, working(C,true) at T1, transports(C,Ob) at T1, conveyorEndpoint(C,End) at T1, location(Ob,Current) at T1, location(C,Start) at T1, notReachingConveyorEnds(Speed,Start,End,Current) then newPosition(Current,Start,End,Speed,NewPoint), update Current to NewPoint in location(Ob,Current) from T1. % notGoingOverConveyorEnds(Speed,Start,End,Current). notReachingConveyorEnds(0,_,_,_). notReachingConveyorEnds(Speed,_,End,Current):- Speed>0, distance(Current,End,Delta), Delta>=abs(Speed). notReachingConveyorEnds(Speed,Start,_End,Current):- Speed<0, distance(Start,Current,Delta), Delta>=abs(Speed). %%% Container: something containing a quantity and a location, and that's it...but it is known to pumps/valves fluents container(_ID,_Level). createContainer(Name,Level,Ob) from T1 to T2 if Ob=container(Name), objectName(Name), number(Level), initiate container(Ob,Level) from T1 to T2, initiate location(Ob,point(0,0)) from T1 to T2. container(C,Level,Where) at T if % visualisation helper container(C,Level) at T, location(C,Where) at T. d(container(C,Level,point(X,Y)),[from:[X,Y], to:[RightX,RightY], label:(Name:Level), type:rectangle, fontSize:13, fillColor:'#85bb65']) :- RightX is X+10, RightY is Y+Level, C=..[_,Name]. %%% Heater; transfers its temperature instantaneously to all heatable(Ob,_Temperature) objects inside its rectangle % when they leave, their temperature is instantaneously reset to what it was prior to entering % heatable objects should have a location(Ob,Point) fluents heater(_ID,_BottomLeft,_TopRight,_Temperature), heatable(_ID,_Temperature), initialTemperature(_Heater,_Ob,_Temp). createHeater(Name,BL,TR,InitialTemp,Ob) from T1 to T2 if Ob=heater(Name), objectName(Name), initiate heater(Ob,BL,TR,InitialTemp) from T1 to T2, initiate working(Ob,false) from T1 to T2. if heater(H,BL,TR,_Temp) at T, working(H,true) at T, heatable(Ob,InitialTemp) at T, location(Ob,L1) at T, \+ inside(L1,BL,TR), location(Ob,L2) at T+1, inside(L2,BL,TR) then initiate initialTemperature(H,Ob,InitialTemp) from T+1. if heater(H,BL,TR,Temp) at T, working(H,true) at T, heatable(Ob,_) at T, location(Ob,L) at T, inside(L,BL,TR) then update Old to Temp in heatable(Ob,Old) from T. if heater(H,BL,TR,_) at T, working(H,true) at T, heatable(Ob,Temp) at T, location(Ob,L1) at T, inside(L1,BL,TR), location(Ob,L2) at T+1, \+ inside(L2,BL,TR), initialTemperature(H,Ob,InitialTemp) then terminate initialTemperature(H,Ob,InitialTemp) from T+1, update Temp to InitialTemp in heatable(Ob,Temp) from T+1. d(heater(ID,point(BLX,BLY),point(TRX,TRY),Temp),[ type:rectangle,label:TS,from:[BLX,BLY],to:[TRX,TRY],strokeColor:red ]) :- format(string(TS),"~wo",[Temp]). %%% Cookable; an object with a location that gets cooked when exposed to heat % Doneness is a nonnegative number reflecting how well cooked the item is versus its initial state, % and depends on temperature (Celsius) % This model could be improved, e.g. https://opentextbc.ca/physicstestbook2/chapter/temperature-change-and-heat-capacity/ fluents cookable(_ID,_InitialTemperature,_Doneness). createCookable(Name,InitialTemp,Ob) from T1 to T2 if Ob=cookable(Name), objectName(Name), initiate location(Ob,point(0,0)) from T1 to T2, initiate heatable(Ob,InitialTemp) from T1 to T2, initiate cookable(Ob,InitialTemp,0) from T1 to T2. if cookable(Ob,Initial,D) at T, heatable(Ob,Current) at T, Current>Initial then NewD is D+(Current-Initial)*0.01, update D to NewD in cookable(Ob,Initial,D). cookable(Ob,InitialTemp,Doneness,Where) at T if % visualisation helper cookable(Ob,InitialTemp,Doneness) at T, location(Ob,Where) at T. d(cookable(C,_,D,point(X,Y)),[type:circle,center:[X,Y], radius:5, label:Ds, fontSize:13, fillColor:red]) :- format(string(Ds),"~2f",[D]). %%% Pump, a generic version of valve, with a flow and ways to impact it % It has one input and one output, which must be containers fluents pump(_ID,_Input,_Output), pumpFlow(_ID,_Flow). % flow is units per LPS cycle from Input to Output createPump(Name,Input,Output,Ob) from T1 to T2 if Input \= Output, Input=container(_), Output=container(_), Ob=pump(Name), objectName(Name), initiate pump(Ob,Input,Output) from T1 to T2, initiate pumpFlow(Ob,0) from T1 to T2, initiate working(Ob,false) from T1 to T2. actions setPumpFlow(_Pump,_Flow), switchPumpOutputTo(_Pump,_Output), switchPumpInputTo(_Pump,_Input), pumpIt(_Pump,_Delta). setPumpFlow(P,Flow) updates Old to Flow in pumpFlow(P,Old) if number(Flow). switchPumpOutputTo(P,New) updates Old to New in pump(P,_I,Old) if New=container(_). switchPumpInputTo(P,New) updates Old to New in pump(P,Old,_) if New=container(_). if pump(P,I,O) at T, working(P,true) at T, container(I,InputLevel) at T then pumpFlow(P,Flow) at T, InputLevel-Flow>=0, % assuming this pump to "explode" (nuking the simulation) if no input available pumpIt(I,-Flow) from T, pumpIt(O,Flow) from T. % This postcondition must be used, rather than a simple update "anonymous action", for serializability, % e.g. integrating output and input to/from a single container pumpIt(C,Delta) updates Old to New in container(C,Old) if New is Old+Delta. %%% Dropper, a maker of objects from a given prototype, that places them on a conveyor at position 0 % a fractional speed will space droppings over cycles; displayed above the conveyor's start point fluents dropper(_ID,_Speed,_PrototypeObject,_Conveyor,_LastDropAt). createDropper(Name,Speed,Prototype,Conveyor,Ob) from T1 to T2 if Ob=dropper(Name), objectName(Name), number(Speed), Conveyor=conveyor(_), location(Conveyor,point(CX,CY)) at T1, Y is CY+25, initiate working(Ob,false) from T1 to T2, initiate location(Ob,point(CX,Y)) from T1 to T2, initiate dropper(Ob,Speed,Prototype,Conveyor,T1) from T1 to T2. actions setDroppingSpeed(_Dropper,_Speed). setDroppingSpeed(D,S) updates Old to S in dropper(D,Old,_,_,_) if number(S). if dropper(D,Speed,Prototype,Conveyor,LastDrop) at T1, working(D,true) at T1, Speed*(T1-LastDrop)>=1 then update Old to T1 in dropper(D,Speed,Prototype,Conveyor,Old) from T1 to T1+1, N is round(Speed*(T1-LastDrop)), clone(N,Prototype,Clones) from T1 to T1+1, drop(Clones,Conveyor) from T1+1 to T1+2. drop([Ob|Objects],Conveyor) from T1 to T2 if placeOnConveyor(Ob,Conveyor,0) from T1 to T2, drop(Objects,Conveyor) from T1 to T2. drop([],_). dropper(D,Speed,Where) at T if % helper for displaying dropper(D,Speed,_Proto,_,_) at T, location(D,Where) at T. d(dropper(dropper(Name),Speed,point(X,Y)),[ [type:line,from:[X,Y],to:[TLX,TY],strokeColor:black], [type:line,from:[X,Y],to:[TRX,TY],strokeColor:black] ]) :- TLX is X-10, TY is Y+15, TRX is X+10. % "if true at T then ..." is a way to "observe" (e.g. request to perform) a composite event/action % Setup our test scene, thinking of a "Diamonds are forever" scene % test conveyor /* if true at 2 then initiate location(coffin,somewhere), createConveyor(_SomeName,point(50,50),point(300,100),Conveyor), start(Conveyor), setConveyorSpeed(Conveyor,25), placeOnConveyor(coffin,Conveyor,0). */ % test pump /*if true at 2 then createContainer(bob,100,Bob), createContainer(fariba,200,Fariba), createPump(payments,Bob,Fariba,P), setPumpFlow(Pump,20) to T, start(Pump) to T, stop(Pump) from T+3. % ... otherwise the program would fail, to protect the pump! */ % a sliding variation on the bank transfer example; all conveyed upwards until Fariba's funding ends /* if true at 2 then createContainer(bob,50,Bob), createContainer(fariba,100,Fariba), createContainer(miguel,10,Miguel) to TC, createPump(payments,Fariba,Bob,Pump), createCookable(shrimp,20,Shrimp), createHeater(_,point(0,0),point(300,200),90,Heater), start(Heater), createConveyor(_SomeName,point(10,10),point(250,100),Conveyor) to T0, createDropper(_,0.25,Shrimp,Conveyor,Dropper) from T0 to T1, % T0 is needed, otherwise Bob may backtrack prematurely! % Truth pursuing can be rather subtle, when in doubt make time explicit! placeOnConveyor(Shrimp,Conveyor,220) from T0 to T1, placeOnConveyor(Bob,Conveyor,0) from T0 to T1, placeOnConveyor(Fariba,Conveyor,100) from T0 to T1, placeOnConveyor(Miguel,Conveyor,200) from T0 to T1, start(Dropper) from T1 to T2, start(Conveyor) from T1 to T2, setConveyorSpeed(Conveyor,10) from T1 to T2, setPumpFlow(Pump,10) from T2, start(Pump) from T2, container(Fariba,Left) at T3, Left=<20, setPumpFlow(Pump,-10) from T3, switchPumpInputTo(Pump,Miguel) from T3, % stop(Pump) from T3, % ... otherwise the program would fail, to protect the pump! setConveyorSpeed(Conveyor,-10) from T3. */ /* TODO: Simple conveyor revisited: if true at 2 then createConveyor(simple,point(10,10),point(210,10),Conveyor) to T0, createContainer(bottle,10,Bottle) to T0, placeOnConveyor(Bottle,Conveyor,0) from T0 to T1, start(Conveyor) from T1 to T2, setConveyorSpeed(Conveyor,5) from T1 to T2. */ if true at 1 then createContainer(hotOil,50,Hot) from T1 to T2, createContainer(usedOil,5,Used) from T1 to T2, createContainer(newOil,30,New) from T1 to T2, setLocation(Hot,point(300,200)) from T2 to T3, setLocation(Used,point(200,0)) from T2 to T3, setLocation(New,point(200,80)) from T2 to T3, createPump(outward,Hot,Used,Outward) from T2 to T3, createPump(inward,Used,Hot,Inward) from T2 to T3, setPumpFlow(Outward,1) from T3, setPumpFlow(Inward,1) from T3, start(Outward) from T3, start(Inward) from T3 to T4, createConveyor(feeding,point(550,140),point(50,140),Feeding) from T4 to T5, setConveyorSpeed(Feeding,10) from T5 to T6, start(Feeding) from T5 to T6, createHeater(_,point(100,75),point(500,290),150,Heater) from T6 to T7, start(Heater) from T7, createCookable(shrimp,4,ProtoShrimp) from T7 to T8, setLocation(ProtoShrimp,point(-20,-20)) from T8, % hack to hide prototype createDropper(_,0.25,ProtoShrimp,Feeding,Dropper) from T8 to T9, start(Dropper) from T9. %%% KitchenSummary; a singleton object aggregating some useful statistics :- use_module(library(lists)). kitchenSummary(CookableCount,AvgDoneness ,Min,Max) at _ if findall(D,cookable(_,_,D),L), length(L,CookableCount), CookableCount>0, sum_list(L,Total), AvgDoneness is Total/CookableCount, min_list(L,Min), max_list(L,Max). d(kitchenSummary(Count,Avg,Min,Max),[ from:[400,300], to:[600,350], label:S, type:rectangle, fillColor:salmon]) :- format(string(S),"~w items, ~ndoneness ~2f (~2f-~2f)",[Count,Avg,Min,Max]). d(timeless, [[type:rectangle,from:[0,0],to:[600,350],strokeColor:green]]). % bounds for our display maxTime(100). % minCycleTime(0). %% geometry and other utilities %d(timeless,Props) :- findall(P,timeless_element(P),Props). % TODO: some other scene elements? % interpolate(StartPoint,EndPoint,DistanceFromStart,Point) interpolate(point(SX,Y),point(EX,Y),D,point(X,Y)) :- !, (EX>=SX -> X is SX+D ; X is SX-D). interpolate(point(X,SY),point(X,EY),D,point(X,Y)) :- !, (EY>=SY -> Y is SY+D ; Y is SY-D). interpolate(point(SX,SY),point(EX,EY),D,point(X,Y)) :- M is (EY-SY)/(EX-SX), DX is D/sqrt(1+M*M), DY is DX*M, X is round(SX+DX), Y is round(SY+DY). % newPosition(CurrentPoint,EndPoint,Delta,NewPoint) newPosition(Current,_Start,End,Delta,NewPoint) :- Delta>=0, interpolate(Current,End,Delta,NewPoint). newPosition(Current,Start,_End,Delta,NewPoint) :- Delta<0, interpolate(Current,Start,Delta,NewPoint). distance(point(X1,Y1),point(X2,Y2),D) :- D is sqrt((X2-X1)*(X2-X1)+(Y2-Y1)*(Y2-Y1)). % inside(Point,BottomLeft,TopRight) whether a point is inside a rectangle defined by two points inside(point(X,Y),point(BLX,BLY),point(TRX,TRY)) :- X>BLX,XBLY,Y true ; gensym(object,Name). /** ?- go(Timeline). */