# Online Continual Learning and Event Handlers

The OCL model is experimenting with an event-based system to facilitate communication
between different objects. This allows objects to hook into stages of training, evaluation, and inference. A common use is to implement custom metrics or loggers.

This block shows how to use the event system in isolation:

```ipython3
from dataclasses import dataclass

from capymoa.ocl.events import Dispatcher, Event


@dataclass
class MyEvent(Event):
    value: int


dispatcher = Dispatcher()


def print_event_type(event):
    print(type(event))


dispatcher.subscribe(MyEvent, print_event_type)
dispatcher.notify(MyEvent(42))
```

```myst-ansi
<class '__main__.MyEvent'>
```

This block shows how to use the event system within the OCL training loop. You can
subscribe to the `None` event to get notified about every event emitted.

```ipython3
from capymoa.classifier import NoChange
from capymoa.ocl.datasets import TinySplitMNIST
from capymoa.ocl.evaluation import ocl_train_eval_loop

dispatcher = Dispatcher()
# Subscribe to all events by using None as the event type
dispatcher.subscribe(None, print_event_type)

scenario = TinySplitMNIST()
_ = ocl_train_eval_loop(
    NoChange(scenario.schema),
    train_streams=scenario.train_loaders(64)[:1],
    test_streams=scenario.test_loaders(64)[:1],
    dispatcher=dispatcher,
)
```

```myst-ansi
<class 'capymoa.ocl.evaluation.events.TrainBegin'>
<class 'capymoa.ocl.evaluation.events.TrainTaskBegin'>
<class 'capymoa.ocl.evaluation.events.TrainBatchPredict'>
<class 'capymoa.ocl.evaluation.events.TrainBatchPredict'>
<class 'capymoa.ocl.evaluation.events.TrainBatchPredict'>
<class 'capymoa.ocl.evaluation.events.TrainBatchPredict'>
<class 'capymoa.ocl.evaluation.events.TestBegin'>
<class 'capymoa.ocl.evaluation.events.TestTaskBegin'>
```

```myst-ansi
<class 'capymoa.ocl.evaluation.events.EvalBatchPredict'>
<class 'capymoa.ocl.evaluation.events.TestTaskEnd'>
<class 'capymoa.ocl.evaluation.events.TestEnd'>
<class 'capymoa.ocl.evaluation.events.TrainTaskEnd'>
<class 'capymoa.ocl.evaluation.events.TrainEnd'>
```

We can use the `TestTaskBegin` and `TrainTaskBegin` events to implement a
task-incremental learner. To standardize subscription to a dispatcher, our method
implements `attach_with` from the `Handler` class.

```ipython3
from torch import Tensor

from capymoa.base import BatchClassifier
from capymoa.classifier import Finetune
from capymoa.core.torch.ann import Perceptron
from capymoa.ocl.evaluation.events import TestTaskBegin, TrainTaskBegin
from capymoa.ocl.events import Dispatcher, Handler


class PerceptronTI(BatchClassifier, Handler):
    def __init__(self, schema, n_tasks: int) -> None:
        super().__init__(schema)
        self._classifiers = [Finetune(schema, Perceptron) for _ in range(n_tasks)]
        self._train_task = 0
        self._test_task = 0

    def batch_train(self, x: Tensor, y: Tensor) -> None:
        self._classifiers[self._train_task].batch_train(x, y)

    def batch_predict_proba(self, x: Tensor) -> Tensor:
        return self._classifiers[self._test_task].batch_predict_proba(x)

    def _on_train_task_begin(self, event: TrainTaskBegin) -> None:
        print(f"Train task {event.train_task} has begun.")
        self._train_task = event.train_task

    def _on_test_task_begin(self, event: TestTaskBegin) -> None:
        self._test_task = event.test_task

    def attach_with(self, dispatcher: Dispatcher) -> Handler:
        dispatcher.subscribe(TrainTaskBegin, self._on_train_task_begin)
        dispatcher.subscribe(TestTaskBegin, self._on_test_task_begin)
        return super().attach_with(dispatcher)


learner = PerceptronTI(scenario.schema, n_tasks=5)
results = ocl_train_eval_loop(
    learner,  # If the learner is a Handler, it will be automatically subscribed.
    train_streams=scenario.train_loaders(32),
    test_streams=scenario.test_loaders(32),
)
print(f"Accuracy {results.accuracy_seen_avg * 100:.2f}")
```

```myst-ansi
Train task 0 has begun.
Train task 1 has begun.
Train task 2 has begun.
Train task 3 has begun.
Train task 4 has begun.
Accuracy 63.57
```

If you use a custom train-test loop with a `Classifier` that is also a `Handler` you
will need to manually notify the classifier as required.
