Skip to content

Commit

Permalink
#29 - Feature allowed, documents added.
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesArruda committed Feb 11, 2025
1 parent 0b1d861 commit dee1407
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 4 deletions.
66 changes: 66 additions & 0 deletions docs/source/user_guide/how_tos/task.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
=====
Tasks
=====

The :py:class:`~upstage_des.task.Task` class is one of the fundamental blocks of an UPSTAGE
simulation. It controls the changes to ``Actor`` states and coordinates with the underlying
SimPy event queue.

Tasks are defined by subclassing from ``Task`` and creating the ``task`` method. In the
example below, the task is properly typed. UPSTAGE provides a type hint for
the generator object that ``task()`` is. Not also that ``actor`` is a required named
argument.

.. code-block:: python
import upstage_des.api as UP
from upstage_des.type_help import TASK_GEN
class Lathe(UP.Actor):
outgoing_bin = UP.ResourceState[UP.SelfMonitoringStore]()
class UseLathe(UP.Task):
def task(self, *, actor: Lathe) -> TASK_GEN:
"""Run the lathe task."""
piece = self.get_actor_knowledge("work_piece", must_exist=True)
time = actor.estimate_work_time(piece)
yield UP.Wait(time)
piece.status = "Done"
actor.number_worked += 1
yield UP.Put(actor.outgoing_bin, piece)
In that example, the task yields out UPSTAGE :doc:`Events </user_guide/how_tos/events>` only, which is the
typical usage. UPSTAGE will also let you yield a simpy process, but this will raise a warning and is
discouraged as interrupt handling and other services won't work. If a process is yielded on, and your
task is interrupted, the yielded process will receive an interrupt as well.

Tasks only allow one actor, so use :doc:`Knowledge </user_guide/how_tos/knowledge>` to help
manage interactions or other information. For more complex interactions, see :doc:`State Sharing </user_guide/how_tos/state_sharing>`.

Interrupts
----------

Task interruption, by default, will raise the usual SimPy exception. The user can add the ``on_interrupt`` method
to their task subclass to handle interruption. That method must accept an actor, a cause object, and must return
a enumerator that tells UPSTAGE how to handle the interruption.

See :doc:`Interrupts </user_guide/tutorials/interrupts>` for more.


Decision Tasks
--------------

Decision tasks are a special form of Task that does not touch the simulation queue, except for a zero time wait.
The zero-time wait exists so the user knows that other decision tasks may run before the Actor using the task
proceeds on to the next yield statement.

Subclass and implement ``make_decision`` to use the class. The ``rehearse_decision`` method can also be implemented
to provide rehearsal decision making. That is useful for separating planning and action code when the simpy clock
will not be advancing.

Rehearsal
---------

Rehearsal is a feature for estimating the results of a Task on a "cloned" Actor to examine actor state
for planning purposes. See :doc:`Rehearsal </user_guide/tutorials/rehearsal>` for more.
1 change: 1 addition & 0 deletions docs/source/user_guide/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ These pages detail the specific activities that can be accomplished using UPSTAG
how_tos/environment.rst
how_tos/states.rst
how_tos/knowledge.rst
how_tos/task.rst
how_tos/resources.rst
how_tos/resource_states.rst
how_tos/active_states.rst
Expand Down
9 changes: 5 additions & 4 deletions src/upstage_des/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,9 +406,10 @@ def rehearse(
next_event = generator.send(returned_item)
returned_item = None
if not issubclass(next_event.__class__, BaseEvent):
raise SimulationError(
f"Task {self} event {next_event} must be a subclass of BaseEvent!"
)
msg = f"Task {self} event {next_event}"
if isinstance(next_event, Process):
raise SimulationError(msg + " cannot be a process during rehearsal.")
raise SimulationError(msg + " must be a subclass of BaseEvent!")
time_advance, returned_item = next_event.rehearse()
mocked_env.now += time_advance

Expand Down Expand Up @@ -453,7 +454,7 @@ def _handle_interruption(
if isinstance(next_event, BaseEvent):
next_event.cancel()
elif isinstance(next_event, Process):
next_event.interrupt(cause="Interrupt from task")
next_event.interrupt(cause=interrupt.cause)
else:
raise SimulationError(f"Bad event passed: {next_event}")
stop_run = True
Expand Down

0 comments on commit dee1407

Please sign in to comment.