Current section

Files

Jump to
macula_neuroevolution src neuroevolution_lineage_events.erl
Raw

src/neuroevolution_lineage_events.erl

%%%-------------------------------------------------------------------
%%% @doc
%%% Behaviour definition for neuroevolution lineage event persistence.
%%%
%%% This behaviour defines the complete API for lineage tracking:
%%% - Required callbacks: Event store operations (persist, read, subscribe)
%%% - Optional callbacks: Query operations (trees, trajectories, histories)
%%%
%%% == Architecture ==
%%%
%%% The behaviour separates concerns following CQRS principles:
%%%
%%% ```
%%% REQUIRED (Event Store) OPTIONAL (Queries)
%%% ---------------------- ------------------
%%% persist_event/2 get_breeding_tree/2
%%% persist_batch/2 get_fitness_trajectory/2
%%% read_stream/3 get_mutation_history/2
%%% subscribe/3 get_knowledge_transfers/2
%%% unsubscribe/3 get_by_causation/2
%%% '''
%%%
%%% Implementations should:
%%% - Use an event store for required callbacks
%%% - Use projections (read models) for optional query callbacks
%%%
%%% == Stream Design ==
%%%
%%% Events are organized into streams based on entity type:
%%% - `individual-{id}' - Birth, death, fitness, mutations
%%% - `species-{id}' - Speciation, lineage events
%%% - `population-{id}' - Generation, capacity events
%%% - `coalition-{id}' - Coalition lifecycle
%%%
%%% @end
%%%-------------------------------------------------------------------
-module(neuroevolution_lineage_events).
%%% ============================================================================
%%% Type Definitions
%%% ============================================================================
-type event() :: map().
-type stream_id() :: binary().
-type position() :: non_neg_integer().
-type direction() :: forward | backward.
-type individual_id() :: binary().
-type causation_id() :: binary().
-type read_opts() :: #{
from => position(),
limit => pos_integer(),
direction => direction()
}.
-type tree_node() :: #{
individual_id := individual_id(),
parents => [tree_node()],
birth_event => event()
}.
-type trajectory_point() :: {Timestamp :: integer(), Fitness :: float()}.
-export_type([
event/0, stream_id/0, position/0, direction/0, read_opts/0,
individual_id/0, causation_id/0, tree_node/0, trajectory_point/0
]).
%%% ============================================================================
%%% Required Callbacks - Event Store Operations
%%% ============================================================================
%% @doc Initialize the event store backend.
-callback init(Config :: map()) ->
{ok, State :: term()} | {error, Reason :: term()}.
%% @doc Persist a single event to the appropriate stream.
%% The implementation should route the event based on event_type.
-callback persist_event(Event :: event(), State :: term()) ->
ok | {error, Reason :: term()}.
%% @doc Persist a batch of events (for efficiency).
%% Events may be routed to different streams based on their type.
-callback persist_batch(Events :: [event()], State :: term()) ->
ok | {error, Reason :: term()}.
%% @doc Read raw events from a stream.
%% Returns events in order, optionally filtered by position and limit.
-callback read_stream(StreamId :: stream_id(), Opts :: read_opts(), State :: term()) ->
{ok, Events :: [event()]} | {error, Reason :: term()}.
%% @doc Subscribe to new events on a stream (for projections).
%% The subscriber receives {lineage_event, StreamId, Event} messages.
-callback subscribe(StreamId :: stream_id(), Pid :: pid(), State :: term()) ->
ok | {error, Reason :: term()}.
%% @doc Unsubscribe from a stream.
-callback unsubscribe(StreamId :: stream_id(), Pid :: pid(), State :: term()) ->
ok | {error, Reason :: term()}.
%%% ============================================================================
%%% Optional Callbacks - Query Operations (via Projections)
%%% ============================================================================
%%%
%%% These queries return derived data built from events. Implementations
%%% should use internal projections (read models) to satisfy these queries,
%%% NOT scan raw events on every call.
%%%
%%% Simple backends (e.g., mock for testing) may return {error, not_implemented}.
-optional_callbacks([
get_breeding_tree/3,
get_fitness_trajectory/2,
get_mutation_history/2,
get_knowledge_transfers/2,
get_by_causation/2
]).
%% @doc Get the breeding tree (ancestry) for an individual.
%%
%% Returns a tree structure showing parents, grandparents, etc.
%% The Depth parameter controls how many generations to include.
%%
%% Implementations should query a lineage tree projection.
-callback get_breeding_tree(IndividualId :: individual_id(),
Depth :: pos_integer(),
State :: term()) ->
{ok, Tree :: tree_node()} | {error, Reason :: term()}.
%% @doc Get the fitness trajectory for an individual.
%%
%% Returns a time-ordered list of (timestamp, fitness) pairs showing
%% how the individual's fitness changed over evaluations.
%%
%% Implementations should query a fitness trajectory projection.
-callback get_fitness_trajectory(IndividualId :: individual_id(),
State :: term()) ->
{ok, Trajectory :: [trajectory_point()]} | {error, Reason :: term()}.
%% @doc Get the mutation history for an individual.
%%
%% Returns all mutation events (neuron_added, connection_removed,
%% weight_perturbed, etc.) for the individual in chronological order.
%%
%% Implementations should query a mutation history projection.
-callback get_mutation_history(IndividualId :: individual_id(),
State :: term()) ->
{ok, Mutations :: [event()]} | {error, Reason :: term()}.
%% @doc Get knowledge transfer events involving an individual.
%%
%% Returns events where the individual was either mentor or student
%% in knowledge transfer, skill imitation, or weight grafting.
%%
%% Implementations should query a knowledge transfer projection.
-callback get_knowledge_transfers(IndividualId :: individual_id(),
State :: term()) ->
{ok, Transfers :: [event()]} | {error, Reason :: term()}.
%% @doc Get all events with a specific causation ID.
%%
%% Useful for tracing related events (e.g., all events from one
%% evaluation batch, or all mutations from one breeding cycle).
%%
%% Implementations may scan events or use a causation index.
-callback get_by_causation(CausationId :: causation_id(),
State :: term()) ->
{ok, Events :: [event()]} | {error, Reason :: term()}.