Clocks and time are a very important instrument and required, if your agents want to delay the execution of a task for some time, schedule a task at a certain time or just need to define a timeout.
Usually, the real (or wall-clock) time is used for this. In some contexts, however, you need a different notion of time – for example if you want to couple a multi-agent system with external simulators that usually run faster than real-time.
An alternative clock is the
ExternalClock. The time of this clock can
be set by external processes so that the time within your agent system passes
as fast (or slow) as in that external process.
>>> import aiomas >>> >>> >>> CLOCK = aiomas.AsyncioClock() >>> # CLOCK = aiomas.ExternalClock('2016-01-01T00:00:00') >>> >>> class Sleeper(aiomas.Agent): ... async def run(self): ... # await asyncio.sleep(.5) # <-- Don't use this! ... # Depending on the clock used, this sleeps for a "real" half ... # second or whatever the ExternalClock tells you: ... await self.container.clock.sleep(.5) >>> >>> container = aiomas.Container.create(('127.0.0.1', 5555), clock=CLOCK) >>> agent = Sleeper(container) >>> aiomas.run(agent.run()) >>> container.shutdown()
(If you uncomment the ExternalClock in the example above, your program won’t terminate because there’s no process that sets its time.)
All clocks represent time as a monotonically increasing number (not necessarily with a defined initial value) and as date/time object (for which the arrow package is used).
You should work with UTC dates as much as possible. Input dates with a local timezone should be converted to UTC as early as possible. If you output dates, convert them as late as possible back to local time.
Doing date and time calclulations in UTC saves you from a lot of bugs, i.e., when dealing with daylight-saving times.
The container clock provides tasks that let your agent sleep for a given amount of time or until a given time is reached.
In order to sleep for a given time, you have to use the method
sleep() with the number of seconds (as float) that you want
Both methods return a future which you have to
await in order to actually
Comparably to sleeping, you can schedule the future execution of a task in a given period of time or at a given time.
call_in() will run the specified task after
a delay dt in seconds;
BaseClock.call_at() will run the task at the
specified date (either in seconds or as
Arrow date). You
can only pass positional arguments to these methods, because that’s what the
underlying asyncio functions allow.
Both methods are normal functions that return a handle to the scheduled call.
You can use this handle to
cancel() the scheduled
execution of the task.
How to use the ExternalClock¶
Remember the first example which did not actually work if you used the
ExternalClock? Here is a fully working version of it:
>>> import asyncio >>> import time >>> >>> import aiomas >>> >>> >>> CLOCK = aiomas.ExternalClock('2016-01-01T00:00:00') >>> >>> class Sleeper(aiomas.Agent): ... async def run(self): ... print('Gonna sleep for 1s ...') ... await self.container.clock.sleep(1) >>> >>> >>> async def clock_setter(factor=0.5): ... """Let the time pass *factor* as fast as real-time.""" ... while True: ... await asyncio.sleep(factor) ... CLOCK.set_time(CLOCK.time() + 1) >>> >>> container = aiomas.Container.create(('127.0.0.1', 5555), clock=CLOCK) >>> >>> # Start the process that sets the clock: >>> t_clock_setter = asyncio.async(clock_setter()) >>> >>> # Start the agent an measure how long he runs in real-time: >>> agent = Sleeper(container) >>> start = time.monotonic() >>> aiomas.run(agent.run()) Gonna sleep for 1s ... >>> print('Agent process finished after %.1fs' % (time.monotonic() - start)) Agent process finished after 0.5s >>> >>> _ = t_clock_setter.cancel() >>> container.shutdown()
Now that we have a background process that steps the time forward, the example actually terminates.
In scenarios where you want to couple you agent system with the clock of
another system, the
clock_setter() process would not sleep but receive
clock updates from that other process and use these updates to set the agent’s
clock to a new time.
If you distribute your agent system over multiple processes, make sure that you
spread the clock updates to all agent containers. Therefore, the
Manager agent in the
set_time() method that an agent in your
master process can call.