BrainSystem for Neuroevolution of Agents

The following question is going to be very specific. But I've been struggeling with this problem for quite some time now. I would really appreciate some help.

Firstly some context: I'm trying to create a simulation, where "intelligent" agents should learn to survive using neuroevolution (genetic algorithm + neural network).
For this I want to implement a brain system which makes it very easy for me to add more brain inputs and outputs. The brain inputs are the perception of the agent (e.g. their vision) and the brain outputs are the deciscions the agents make (e.g. how to move).

The neural network implementation is quite trivial and was already done using the nalgebra crate. So my mission now is to convert various types into a input: nalgebra::DVector<f32> to feed the neural network and then convert the output: nalgebra::DVector<f32> back into types which are meaningful decisions.

So what I now want to implement is some way to have a struct containing all perceptions

#[derive(BrainInput)]
struct Perception {
    own_energy: InputF32,
    own_mass: InputF32,
    own_velocity: InputVector2,
    tile_color: InputColor,
}

Which is then automatically converted into a DVector<f32> by the derive macro of BrainInput.

And then the same thing the other way around:

#[derive(BrainOutput)]
struct Decisions
{
    force_to_apply: OutputVector2,
    own_color: OutputColor,
}

The macro part isn't the problem. I think I will be able to figure this part out myself.

I'm more concerned about the traits BrainInput and BrainOutput or maybe a combination of them BrainIO. They should be implemented manually on all the input types (e.g. IoVector2) and all the output types (e.g. IoColor) respectively.

These traits should just provide some way of converting between the perception types resp. decisions types and the DVector<f32>. Also they should provide the length the corresponding DVector2<f32> so the shape of the neural network is already known.

Now I want to figure out how to define these traits.

I'd imagine something like this:

trait BrainIO {
    fn len() -> usize;
    fn to_input(self) -> Vec<f32>;
    fn from_output(output: Vec<f32>) -> Self;
}

I'm using Vec<f32> as an intermediate type because DVector<f32> are hard to slice and concatenate.
There is probably a better fitting data structure for this job than Vec<f32>. If somebody has a better suggestion, this would help a lot.

This is an examle implementation of IoBool I came up with.

struct IoBool(bool);
impl BrainIo for InputBool {
    fn len() -> usize {
        1
    }
    fn to_input(self) -> Vec<f32> {
        vec![if self.0 { 1.0 } else { 0.0 }]
    }
    fn to_output(output: Vec<f32>) -> Self {
       Self(output[0] >= 0.5)
    }
}

This would just be manually implemented.
The macro one the other hand would just Vec::append(&mut self, other: &mut Self) all of these togehter.

But I don't really like this implementation because this involves a lot of cloning and heap allocations. Isn't there a better way to do this?

I also experimented with ExactSizeIterator and other approaches but I always have some problems. I'm just not quite sure what the best way would be to approach this.

Thanks in advance for some help!

Hi i think the simple way to map input to outputs is with functions(or closures) maybe you could store all the functions(closures) in a HashMap:

use std::collections::HashMap;

struct Input {
    name: String,
}

struct Output {
    a: bool
}

type Callback = Box<dyn Fn(&Input) -> Output>;

struct BrainSystem {
    functions: HashMap<String, Callback>
}

impl BrainSystem {
    fn new() -> Self {
        BrainSystem{functions: HashMap::new()}
    }

    fn add_function(&mut self, input_name: &str, callback: impl Fn(&Input) -> Output + 'static) {
        self.functions.insert(input_name.to_string(), Box::new(callback));
    }
}

impl BrainSystem {
    fn handle_function(&self, input: &Input) -> Output {
        match self.functions.get(&input.name) {
            None => panic!("errooor"),
            Some(function) => function(input)
        }
    }
}

sorry if I have not understood the problem well. Regards

Thanks for the reply.

That's not quite the problem I'm trying to solve.
I looking for an API which interfaces between the Inputs/Outputs of the neural network and the Perception/Decisions of the agents. I guess you could call it BrainIO.

I'm looking for the traits and data structures which fit this API the best.

You've described a system which enables me to have different mapping functions. But the mapping function is always the same: It's the hypothesis function of the neural network. It's the forward propagation.

I have updated the first post, to better reflect what I'm searching for.