예제 #1
0
def test_aggregation_chain():
    """ test the aggregation chain with a single event attribute """

    class _EventInterface(ABC):
        @define_epoch_stats(sum)
        @define_episode_stats(sum)
        @define_step_stats(sum)
        def event1(self, attr1):
            pass

    agg_episode = LogStatsAggregator(LogStatsLevel.EPOCH)
    agg_step = LogStatsAggregator(LogStatsLevel.EPISODE, agg_episode)
    agg_event = LogStatsAggregator(LogStatsLevel.STEP, agg_step)

    no_steps = 5
    no_episodes = 7
    for episode in range(no_episodes):
        for step in range(no_steps):
            agg_event.add_event(EventRecord(_EventInterface, _EventInterface.event1, dict(attr1=2)))
            agg_event.add_event(EventRecord(_EventInterface, _EventInterface.event1, dict(attr1=3)))
            agg_event.reduce()

        episode_stats = agg_step.reduce()
        assert len(episode_stats) == 1
        value = episode_stats[(_EventInterface.event1, None, None)]
        assert value == no_steps * 5

    epoch_stats = agg_episode.reduce()
    assert len(epoch_stats) == 1
    value = epoch_stats[(_EventInterface.event1, None, None)]
    assert value == no_episodes * no_steps * 5
예제 #2
0
    def _record(*args, **kwargs) -> None:
        if len(args) > 1:
            raise TypeError(
                'event methods with more than one argument must be called with named arguments'
            )

        if len(args) == 1:
            if len(event_attribute_names) > 1:
                raise TypeError(
                    'event methods with more than one argument must be called with named arguments'
                )

            value = args[0]
            attribute_name = event_attribute_names[0]
            fn_notify_event(
                EventRecord(interface_class,
                            interface_method,
                            attributes={attribute_name: value}))
            return

        # call the original interface, to raise error on signature mismatch
        for key in kwargs.keys():
            if key not in event_attribute_names:
                raise TypeError(
                    "got an unexpected keyword argument {}".format(key))

        fn_notify_event(
            EventRecord(interface_class, interface_method, attributes=kwargs))
예제 #3
0
def test_multi_group_projection():
    """ test grouping by three attributes """

    class _EventInterface(ABC):
        @define_stats_grouping("group1", "group2", "group3")
        @define_step_stats(sum, group_by="group1", output_name="g1")
        @define_step_stats(sum, group_by="group2", output_name="g2")
        @define_step_stats(sum, group_by="group3", output_name="g3")
        def event1(self, group1, group2, group3, attr1):
            pass

    agg = LogStatsAggregator(LogStatsLevel.STEP)
    agg.add_event(EventRecord(_EventInterface, _EventInterface.event1, dict(group1=1, group2=0, group3=0, attr1=1)))
    agg.add_event(EventRecord(_EventInterface, _EventInterface.event1, dict(group1=0, group2=1, group3=0, attr1=2)))
    agg.add_event(EventRecord(_EventInterface, _EventInterface.event1, dict(group1=0, group2=0, group3=1, attr1=4)))

    stats = agg.reduce()
    assert len(stats) == 6

    assert stats[(_EventInterface.event1, "g1", (0, None, None))] == 6
    assert stats[(_EventInterface.event1, "g1", (1, None, None))] == 1
    assert stats[(_EventInterface.event1, "g2", (None, 0, None))] == 5
    assert stats[(_EventInterface.event1, "g2", (None, 1, None))] == 2
    assert stats[(_EventInterface.event1, "g3", (None, None, 0))] == 3
    assert stats[(_EventInterface.event1, "g3", (None, None, 1))] == 4
예제 #4
0
def test_aggregation_chain_fork():
    """ test the aggregation chain with two event attributes and different aggregation operations """

    class _EventInterface(ABC):
        @define_epoch_stats(sum, input_name="attr1_sum")
        @define_epoch_stats(np.mean, input_name="attr2_mean")
        @define_episode_stats(sum, input_name="attr1_sum")
        @define_episode_stats(np.mean, input_name="attr2_mean")
        @define_step_stats(sum, input_name="attr1", output_name="attr1_sum")
        @define_step_stats(np.mean, input_name="attr1", output_name="attr1_mean")
        @define_step_stats(sum, input_name="attr2", output_name="attr2_sum")
        @define_step_stats(np.mean, input_name="attr2", output_name="attr2_mean")
        def event1(self, attr1, attr2):
            pass

    agg_episode = LogStatsAggregator(LogStatsLevel.EPOCH)
    agg_step = LogStatsAggregator(LogStatsLevel.EPISODE, agg_episode)
    agg_event = LogStatsAggregator(LogStatsLevel.STEP, agg_step)

    no_steps = 5
    no_episodes = 7
    for episode in range(no_episodes):
        for step in range(no_steps):
            agg_event.add_event(EventRecord(_EventInterface, _EventInterface.event1, dict(attr1=2.0, attr2=-2.0)))
            agg_event.add_event(EventRecord(_EventInterface, _EventInterface.event1, dict(attr1=3.0, attr2=-3.0)))

            step_stats = agg_event.reduce()
            assert len(step_stats) == 4
            value1_sum = step_stats[(_EventInterface.event1, "attr1_sum", None)]
            value1_mean = step_stats[(_EventInterface.event1, "attr1_mean", None)]
            value2_sum = step_stats[(_EventInterface.event1, "attr2_sum", None)]
            value2_mean = step_stats[(_EventInterface.event1, "attr2_mean", None)]
            assert value1_sum == 5.0
            assert value1_mean == 2.5
            assert value2_sum == -5.0
            assert value2_mean == -2.5

        episode_stats = agg_step.reduce()
        assert len(episode_stats) == 2
        value1 = episode_stats[(_EventInterface.event1, "attr1_sum", None)]
        value2 = episode_stats[(_EventInterface.event1, "attr2_mean", None)]
        assert value1 == no_steps * 5.0
        assert value2 == -2.5

    epoch_stats = agg_episode.reduce()
    assert len(epoch_stats) == 2
    value1 = epoch_stats[(_EventInterface.event1, "attr1_sum", None)]
    value2 = epoch_stats[(_EventInterface.event1, "attr2_mean", None)]
    assert value1 == no_episodes * no_steps * 5.0
    assert value2 == -2.5
예제 #5
0
def test_event_single_attribute():
    """ test if the aggregation function receives scalars if there is only a single event attribute """

    class _EventInterface(ABC):
        @define_step_stats(sum)
        def event1(self, attr1):
            pass

    agg = LogStatsAggregator(LogStatsLevel.STEP)
    agg.add_event(EventRecord(_EventInterface, _EventInterface.event1, dict(attr1=1)))
    agg.add_event(EventRecord(_EventInterface, _EventInterface.event1, dict(attr1=2)))

    stats = agg.reduce()
    assert len(stats) == 1

    key, value = next(iter(stats.items()))
    assert value == 3
    # tuple (event, output name)
    assert key == (_EventInterface.event1, None, None)
예제 #6
0
def test_event_counting():
    """ test counting as a simple aggregation that operates on the attributes dict """

    class _EventInterface(ABC):
        @define_step_stats(len)
        def event1(self, attr1, attr2):
            pass

    agg = LogStatsAggregator(LogStatsLevel.STEP)
    agg.add_event(EventRecord(_EventInterface, _EventInterface.event1, dict(attr1=1, attr2=2)))
    agg.add_event(EventRecord(_EventInterface, _EventInterface.event1, dict(attr1=1, attr2=2)))

    stats = agg.reduce()
    assert len(stats) == 1

    key, value = list(stats.items())[0]
    assert value == 2
    # tuple (event, output name)
    assert key == (_EventInterface.event1, None, None)
예제 #7
0
def test_subscriber_query_events():
    subscriber = DummySubscriber()

    ev1 = EventRecord(interface_class=DummyInterface, interface_method=DummyInterface.event1, attributes=dict())
    subscriber.notify_event(ev1)

    assert [ev1] == subscriber.query_events(event_spec=DummyInterface.event1)

    subscriber.reset()

    assert subscriber.query_events(event_spec=DummyInterface.event1) == []
예제 #8
0
def test_event_stats_histogram_2():
    """ test histogram loggin on an event level """

    class _EventInterface(ABC):
        @define_step_stats(histogram, input_name='attr1')
        @define_step_stats(histogram, input_name='attr2')
        def event1(self, attr1, attr2):
            pass

    agg = LogStatsAggregator(LogStatsLevel.STEP)
    agg.add_event(EventRecord(_EventInterface, _EventInterface.event1, dict(attr1=1, attr2=2)))
    agg.add_event(EventRecord(_EventInterface, _EventInterface.event1, dict(attr1=1, attr2=2)))

    stats = agg.reduce()
    assert len(stats) == 2

    value1 = stats[(_EventInterface.event1, "attr1", None)]
    value2 = stats[(_EventInterface.event1, "attr2", None)]

    assert value1 == [1, 1]
    assert value2 == [2, 2]
예제 #9
0
def test_event_attributes():
    """ test the aggregation of individual event attributes """

    class _EventInterface(ABC):
        @define_step_stats(sum, input_name='attr1')
        @define_step_stats(sum, input_name='attr2')
        def event1(self, attr1, attr2):
            pass

    agg = LogStatsAggregator(LogStatsLevel.STEP)
    agg.add_event(EventRecord(_EventInterface, _EventInterface.event1, dict(attr1=1, attr2=3)))
    agg.add_event(EventRecord(_EventInterface, _EventInterface.event1, dict(attr1=1, attr2=3)))

    stats = agg.reduce()
    assert len(stats) == 2

    value1 = stats[(_EventInterface.event1, "attr1", None)]
    value2 = stats[(_EventInterface.event1, "attr2", None)]

    assert value1 == 2
    assert value2 == 6
예제 #10
0
def test_grouping():
    """ test the aggregation of individual event attributes """

    class _EventInterface(ABC):
        @define_stats_grouping("group")
        @define_step_stats(sum)
        def event1(self, group, attr1):
            pass

    agg = LogStatsAggregator(LogStatsLevel.STEP)
    for v in [1, 3]:
        agg.add_event(EventRecord(_EventInterface, _EventInterface.event1, dict(group=0, attr1=v)))
        agg.add_event(EventRecord(_EventInterface, _EventInterface.event1, dict(group=1, attr1=v * 2)))

    stats = agg.reduce()
    assert len(stats) == 2

    value1 = stats[(_EventInterface.event1, None, (0,))]
    value2 = stats[(_EventInterface.event1, None, (1,))]

    assert value1 == 4
    assert value2 == 8
예제 #11
0
def test_aggregation_chain_multi_attribute():
    """ test the aggregation chain with two event attributes """

    class _EventInterface(ABC):
        @define_epoch_stats(sum, input_name="attr1")
        @define_epoch_stats(sum, input_name="attr2")
        @define_episode_stats(sum, input_name="attr1")
        @define_episode_stats(sum, input_name="attr2")
        @define_step_stats(sum, input_name="attr1")
        @define_step_stats(sum, input_name="attr2")
        def event1(self, attr1, attr2):
            pass

    agg_episode = LogStatsAggregator(LogStatsLevel.EPOCH)
    agg_step = LogStatsAggregator(LogStatsLevel.EPISODE, agg_episode)
    agg_event = LogStatsAggregator(LogStatsLevel.STEP, agg_step)

    no_steps = 5
    no_episodes = 7
    for episode in range(no_episodes):
        for step in range(no_steps):
            agg_event.add_event(EventRecord(_EventInterface, _EventInterface.event1, dict(attr1=2, attr2=-2)))
            agg_event.add_event(EventRecord(_EventInterface, _EventInterface.event1, dict(attr1=3, attr2=-3)))
            agg_event.reduce()

        episode_stats = agg_step.reduce()
        assert len(episode_stats) == 2
        value1 = episode_stats[(_EventInterface.event1, "attr1", None)]
        value2 = episode_stats[(_EventInterface.event1, "attr2", None)]
        assert value1 == no_steps * 5
        assert value2 == -no_steps * 5

    epoch_stats = agg_episode.reduce()
    assert len(epoch_stats) == 2
    value1 = epoch_stats[(_EventInterface.event1, "attr1", None)]
    value2 = epoch_stats[(_EventInterface.event1, "attr2", None)]
    assert value1 == no_episodes * no_steps * 5
    assert value2 == -no_episodes * no_steps * 5
예제 #12
0
def test_event_record_is_pickleable():
    original = EventRecord(BaseEnvEvents, BaseEnvEvents.reward, dict(value=5))
    fname = "test"

    with open(fname, "wb") as out_f:
        pickle.dump(original, out_f)

    with open(fname, "rb") as in_f:
        pickled = pickle.load(in_f)

    assert pickled.interface_class == original.interface_class
    assert pickled.interface_method == original.interface_method
    assert pickled.attributes == original.attributes

    os.remove(fname)
예제 #13
0
def test_event_skip_aggregation():
    """ test the once-per-step logging """

    class _EventInterface(ABC):
        @define_step_stats(None)
        def event1(self, attr1):
            pass

    agg = LogStatsAggregator(LogStatsLevel.STEP)
    agg.add_event(EventRecord(_EventInterface, _EventInterface.event1, dict(attr1=3)))

    stats = agg.reduce()
    assert len(stats) == 1

    key, value = next(iter(stats.items()))
    assert value == 3
    # tuple (event, output name)
    assert key == (_EventInterface.event1, None, None)

    # check if multiple calls per step are correctly detected
    with pytest.raises(AssertionError):
        agg.add_event(EventRecord(_EventInterface, _EventInterface.event1, dict(attr1=3)))
        agg.add_event(EventRecord(_EventInterface, _EventInterface.event1, dict(attr1=3)))
        agg.reduce()
예제 #14
0
def test_multi_grouping():
    """ test grouping by three attributes """

    class _EventInterface(ABC):
        @define_stats_grouping("group1", "group2", "group3")
        @define_step_stats(sum)
        def event1(self, group1, group2, group3, attr1):
            pass

    agg = LogStatsAggregator(LogStatsLevel.STEP)
    for i in [1, 8]:
        agg.add_event(EventRecord(_EventInterface, _EventInterface.event1,
                                  dict(group1=1, group2=0, group3=0, attr1=1 * i)))
        agg.add_event(EventRecord(_EventInterface, _EventInterface.event1,
                                  dict(group1=0, group2=1, group3=0, attr1=2 * i)))
        agg.add_event(EventRecord(_EventInterface, _EventInterface.event1,
                                  dict(group1=0, group2=0, group3=1, attr1=4 * i)))

    stats = agg.reduce()
    assert len(stats) == 3

    assert stats[(_EventInterface.event1, None, (1, 0, 0))] == 9
    assert stats[(_EventInterface.event1, None, (0, 1, 0))] == 18
    assert stats[(_EventInterface.event1, None, (0, 0, 1))] == 36
예제 #15
0
    def step(self, action: Any) -> Tuple[Any, Any, bool, Dict[Any, Any]]:
        """Collect the rewards for the logging statistics
        """

        # get identifier of current substep
        substep_id, _ = self.env.actor_id() if isinstance(self.env, StructuredEnv) else (None, None)

        # take core env step
        obs, rew, done, info = self.env.step(action)

        # record the reward
        self.reward_events.append(EventRecord(BaseEnvEvents, BaseEnvEvents.reward, dict(value=rew)))

        self._record_stats_if_ready()

        return obs, rew, done, info
예제 #16
0
    def _calculate_kpis(self):
        """Calculate KPIs and append them to both aggregated and logged events."""
        if not isinstance(self.env, EventEnvMixin) or not self.episode_event_log:
            return

        kpi_calculator = self.env.get_kpi_calculator()
        if kpi_calculator is None:
            return

        last_maze_state = self.env.get_maze_state() if isinstance(self.env, RecordableEnvMixin) else None

        kpis_dict = kpi_calculator.calculate_kpis(self.episode_event_log, last_maze_state)
        kpi_events = []
        for name, value in kpis_dict.items():
            kpi_events.append(EventRecord(BaseEnvEvents, BaseEnvEvents.kpi, dict(name=name, value=value)))

        for event_record in kpi_events:
            self.episode_stats.add_event(event_record)  # Add the events to episode aggregator
            self.episode_event_log.step_event_logs[-1].events.append(event_record)  # Log the events