Named Entities#

Named entities are an EnvironmentContext and NamedUpstageEntity enabled feature where you can store instances in particular “entity groups” to gather them from later. UPSTAGE’s Actor inherits from NamedUpstageEntity, giving all Actors the feature. Similarly, the SelfMonitoring<> resources do the same to enable quick access to recorded simulation data.

All Actors are retrievable with the get_actors() method if they inherit from Actor.

Entities are retrievable with get_all_entity_groups() and get_entity_group().

Defining a named entity is done in the class definition:

class Car(UP.Actor, entity_groups=["vehicle"]):
    ...

class Talker(UP.Actor, entity_groups=["has-radio"]):
    ...

class Plane(UP.Actor, entity_groups=["vehicle", "air"]):
    ...

class FastTalkingPlane(Plane, Talker):
    ...

class NoSpecificGroup(UP.Actor):
    ...

class Different(UP.NamedUpstageEntity, entity_groups=["separate"]):
    ...

Once you are in an environment context you can get the actual instances.

with UP.EnvironmentContext():
    manage = UP.UpstageBase()

    c1 = Car(name="car1")
    c2 = Car(name="car2")
    p = Plane(name="plane")
    fp = FastTalkingPlane(name="fast plane")
    other = NoSpecificGroup(name="all alone")
    talk = Talker(name="Basic Talker")
    d = Different()

    actor_entities = manage.get_actors()
    print(actor_entities)
    >>> [Car: car1, Car: car2, Plane: plane, FastTalkingPlane: fast plane, NoSpecificGroup: all alone, Talker: Basic Talker]

    vehicles = manage.get_entity_group("vehicle")
    print(vehicles)
    >>> [Car: car1, Car: car2, Plane: plane, FastTalkingPlane: fast plane]

    air = manage.get_entity_group("air")
    print(air)
    >>> [Plane: plane, FastTalkingPlane: fast plane]

    radio = manage.get_entity_group("has-radio")
    print(radio)
    >>> [FastTalkingPlane: fast plane, Talker: Basic Talker]

    different = manage.get_entity_group("separate")
    print(different)
    >>> [<__main__.Different object at ...>]

Note that entity groups are inheritable and that you can inherit from NamedUpstageEntity and retrieve the instance without needing an Actor. You may also create an instance of UpstageBase to get access to the required methods. Actors and Tasks can access that method already.

If you are going to create a non-Actor version of a named entity, ensure your init calls super(). The following example shows a use case where a simulation may want to look up entities in the simulation universe that don’t need to be actors.

class PowerGenerator(UP.NamedUpstageEntity):
    def __init__(self, name: str, kwh: float) -> None:
        super().__init__()
        self.name = name
        self.kwh = kwh

    def __repr__(self) -> str:
        return f"{self.name} - {self.kwh}KwH"


class Nuclear(PowerGenerator, entity_groups="Nuclear"):
    def __init__(self, name: str, kwh: float, num_towers: int) -> None:
        super().__init__(name, kwh)
        self.num_towers = num_towers


class Planner(UP.DecisionTask):
    def make_decision(self, *, actor: UP.Actor) -> None:
        # Decide on which power plant to upgrade next
        plants = self.get_entity_group("PowerGenerator")
        nuclear = self.get_entity_group("Nuclear")
        print(plants)
        print(nuclear)


with UP.EnvironmentContext(random_seed=321456) as env:
    rng = UP.get_stage().random
    pgs = [
        PowerGenerator(f"{i}", rng.randint(10, 100))
        for i in range(5)
    ]
    pgs += [
        Nuclear(f"Nuc_{i}", rng.randint(10, 100), rng.randint(1,4))
        for i in range(5)
    ]
    # You wouldn't actually make an actor just to run the task,
    # but it serves as a reminder of getting entities inside
    # an UpstageBase subclass
    t = Planner()
    t.make_decision(actor=UP.Actor(name="example"))
    >>>[0 - 61KwH, 1 - 71KwH, 2 - 58KwH, 3 - 43KwH, 4 - 37KwH, Nuc_0 - 66KwH, Nuc_1 - 60KwH, Nuc_2 - 37KwH, Nuc_3 - 53KwH, Nuc_4 - 15KwH]
    >>>[Nuc_0 - 66KwH, Nuc_1 - 60KwH, Nuc_2 - 37KwH, Nuc_3 - 53KwH, Nuc_4 - 15KwH]