more design, choosing java
This commit is contained in:
parent
353bf3820d
commit
1bd5c388be
84
README.md
84
README.md
@ -50,7 +50,7 @@ Every structure occupies only one cell.
|
|||||||
A structure with an operational mind governing the body is called a creature or machine.
|
A structure with an operational mind governing the body is called a creature or machine.
|
||||||
|
|
||||||
There are four kinds of blocks:
|
There are four kinds of blocks:
|
||||||
- __neuron blocks__ make the calculations
|
- __neuron blocks__ make calculations
|
||||||
- __action blocks__ let a structure do something
|
- __action blocks__ let a structure do something
|
||||||
- __sensor blocks__ let a structure know something about itself or anything else
|
- __sensor blocks__ let a structure know something about itself or anything else
|
||||||
- __resource blocks__ can be interacted with, causing some effects
|
- __resource blocks__ can be interacted with, causing some effects
|
||||||
@ -74,6 +74,19 @@ There are four kinds of blocks:
|
|||||||
- if the connection is `-1` then the exact opposite of the input value passes to the destination
|
- if the connection is `-1` then the exact opposite of the input value passes to the destination
|
||||||
- the highest `abs(val) where val is the connection/weight` is, the stronger the connection
|
- the highest `abs(val) where val is the connection/weight` is, the stronger the connection
|
||||||
|
|
||||||
|
|
||||||
|
\ | /
|
||||||
|
v v v
|
||||||
|
#########
|
||||||
|
-> # block # <-
|
||||||
|
#########
|
||||||
|
^ ^ ^
|
||||||
|
/ | \
|
||||||
|
|
||||||
|
|
||||||
|
- assuming the block in the drawing is at (2,2), it will be connected with: (1,1),(1,2),(1,3),(2,1),(2,2),(2,3),(3,1),(3,2),(3,3)
|
||||||
|
- this drawing doesn't include the recursive connection the block has with itself
|
||||||
|
|
||||||
A signal can be any floating point value between -1 and 1.
|
A signal can be any floating point value between -1 and 1.
|
||||||
|
|
||||||
- any signal higher than 1 will be considered to be 1
|
- any signal higher than 1 will be considered to be 1
|
||||||
@ -167,24 +180,22 @@ This method to calculate fitness has many problems:
|
|||||||
|
|
||||||
- What if a creature donates energy to others of the same species? It gets counted as energy consumption,
|
- What if a creature donates energy to others of the same species? It gets counted as energy consumption,
|
||||||
thus decreasing fitness
|
thus decreasing fitness
|
||||||
- What is a creature couldn't harvest energy because the circumstances weren't favorable?
|
- What if a creature couldn't harvest energy because the environment wasn't ideal?
|
||||||
|
- What if a creatures scored hugely because the environment was just perfect for huge scores?
|
||||||
|
|
||||||
The conclusion is that this system is far too complex to consider fitness evaluation.
|
The conclusion is that this system is far too complex to consider fitness evaluation.
|
||||||
|
|
||||||
### Complexity of a structure
|
### Complexity of a structure
|
||||||
|
|
||||||
|
The point of structure complexity is to try and approximately figure out how computationally expensive it is
|
||||||
|
to compute an itearation of the simulation for the structure.
|
||||||
|
|
||||||
The complexity of a structure is:
|
The complexity of a structure is:
|
||||||
|
|
||||||
- if the fitness is `> 0`: the sum of the complexity of all its blocks.
|
- if the fitness is `> 0`: the sum of the complexity of all its blocks.
|
||||||
- if the fitness is `<= 0`: 0
|
- if the fitness is `<= 0`: 0, because there is nothing to compute if the structure has no energy.
|
||||||
|
|
||||||
Complexity of a block starts at 0, then:
|
Block complexity evaluation is covered in the Parallelism section.
|
||||||
|
|
||||||
- increases by 1 for every not null connection of the block, if the block is an input block
|
|
||||||
- increases by x for every output block, depending on the specific block
|
|
||||||
|
|
||||||
The point of structure complexity is to try and approximately figure out how computationally expensive it is
|
|
||||||
to compute an itearation of the simulation for the structure
|
|
||||||
|
|
||||||
## Primordial world
|
## Primordial world
|
||||||
|
|
||||||
@ -252,8 +263,10 @@ However, it has also some issues:
|
|||||||
- I don't know the rust programming language very well (not a big problem though)
|
- I don't know the rust programming language very well (not a big problem though)
|
||||||
- its lower level nature (compared to java) would make development slower
|
- its lower level nature (compared to java) would make development slower
|
||||||
- it's very hard to write a multithreaded program in rust due to the compiler being extremely limiting by refusing to compile programs with a chance to cause segfaults or race conditions
|
- it's very hard to write a multithreaded program in rust due to the compiler being extremely limiting by refusing to compile programs with a chance to cause segfaults or race conditions
|
||||||
|
- no structure/object/class inheritance
|
||||||
|
- I honestly don't think I can implement this simulation in a multithreaded and/or parallel computing friendly way using rust.
|
||||||
|
|
||||||
Java would solve those issues, also maintaining most of rust's advantages. I'll eventually decide what to do.
|
That's why I ultimately chose to use Java. It's a language I know very well, that has most of rust's features necessary for this project without the problems.
|
||||||
|
|
||||||
### Simulation iteration evaluation algorithm
|
### Simulation iteration evaluation algorithm
|
||||||
|
|
||||||
@ -290,35 +303,28 @@ It's probably not as fast as it gets, but it should be decent
|
|||||||
|
|
||||||
__Note:__ the bigger a machine, the slower it reacts to inputs because signals need more time to travel through the body
|
__Note:__ the bigger a machine, the slower it reacts to inputs because signals need more time to travel through the body
|
||||||
|
|
||||||
## Rust implementation
|
## Multithreading and Parallelism
|
||||||
|
|
||||||
Quick and dirty scheme to keep track of things.
|
The first AIrium wasn't built with concurrency in mind, it was only added later with average results.
|
||||||
|
|
||||||
- world (class), keeps all information about a world
|
This time, concurrency was baked into the project since its first design: when evaluating an iteration of the simulation for a structure
|
||||||
- structure (trait)
|
its _output_ is just a list of _actions_ that the structure wants to perform on itself or the environment.
|
||||||
- compute(), computes an iteration of the simulation for this structure
|
|
||||||
- get_blocks(), returns an immutable reference to the blocks
|
|
||||||
- clean(), used to clear event queue, also calls clean() on blocks
|
|
||||||
- runnable (trait)
|
|
||||||
- run(), does stuff, mostly in another thread
|
|
||||||
- manager (impls runnable), manages a world
|
|
||||||
- worker (impls runnable), operates a list of creatures in a world
|
|
||||||
- applier (impls runnable), operates on a list of creatures in a world, also calls clean() on structures
|
|
||||||
- block (trait)
|
|
||||||
- get_connections(), returns an immutable reference to the connection vector
|
|
||||||
- get_value(), returns an immuatable reference to the current stored signal
|
|
||||||
- get_name(), returns an immutable reference to the block's name
|
|
||||||
- compute(), computes an iteration of the simulation for this block, then stores it in the new stored signal.
|
|
||||||
also returns a list of actions that the block wants to perform
|
|
||||||
- clean(), clears event queue and writes new stored signal into the current stored signal
|
|
||||||
- action (trait)
|
|
||||||
- get_source(), get the source structure (immutable ref)
|
|
||||||
- get_target(), get the target (immutable ref)
|
|
||||||
- apply(), applies the action
|
|
||||||
|
|
||||||
- PROBLEM: apply() requires a mutable reference to the target, but it's not possible to pass it in any way. Only an immutable ref can be passed.
|
The world simulation can be parallelized down to the single _block_ of a structure, then their actions can be applied to the world sequentally.
|
||||||
- SOLUTION: use an immutable reference and call as_mut on it?
|
|
||||||
- thread comms: use channels?
|
|
||||||
|
|
||||||
TODO: figure out where to put UI logic. We probably can't do it in parallel with the simulation, so it needs
|
__Note:__ Even the action application could be evaluated in parallel by grouping structures together, putting each action into a group.
|
||||||
to be executed by the manager thread.
|
if an action concerns both Structure A and Structure B, then they are related and actions concerning one of A or B should go in the same group.
|
||||||
|
The problem is that this kind of classification is already a big problem of its own, expecially considering that a _fast_ grouping algorithm is
|
||||||
|
essential to fullfull the high performance needed in this implementation. Thus, I think it's better to avoid trying to parallelize action
|
||||||
|
evaluation for now.
|
||||||
|
|
||||||
|
__Another problem__ the first version of AIrium had was when new creatures where created by the artificial selector. That was because computing a child
|
||||||
|
creature requires a lot of iteration, memory accessing and random number generation. This is slow and a big problem that will need to be addressed
|
||||||
|
expecially in environments with structures producing a high amount of eggs. The solution I'm thinking of using is to not try to compute the child
|
||||||
|
structure right away, but save a copy of the parent structures' inside the egg, then slowly build a child when evaluating the egg blocks.
|
||||||
|
|
||||||
|
There's also __the Block complexity (evaluation speed) variety__. Ideally, to be able to compute how complex a structure is,
|
||||||
|
we have to know how complex blocks are. But, the higher the complexity, the higher the computational time.
|
||||||
|
So ideally, we need to know, approximately, how much time does a block need to be evaluated.
|
||||||
|
|
||||||
|
A possible improvement is __Smart block evaluation__. It consists in not evaluating a circuit if no _action blocks_ are affected by it.
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
use util::{Point,Directions};
|
|
||||||
use structure::Structure;
|
|
||||||
|
|
||||||
pub enum Actions {
|
|
||||||
Movement { dir: Directions }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Action {
|
|
||||||
pub source: Structure,
|
|
||||||
pub action_type: Actions
|
|
||||||
}
|
|
45
src/block.rs
45
src/block.rs
@ -1,45 +0,0 @@
|
|||||||
use action::Action;
|
|
||||||
use structure::Structure;
|
|
||||||
|
|
||||||
pub enum Blocks {
|
|
||||||
Neuron,
|
|
||||||
Leg { dir: i32 }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Block {
|
|
||||||
structure: Structure,
|
|
||||||
connections: Vec<f64>,
|
|
||||||
current_value: f64,
|
|
||||||
new_value: f64,
|
|
||||||
block_type: Blocks
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Block {
|
|
||||||
pub fn new(structure: Structure, btype: Blocks) -> Block {
|
|
||||||
Block {
|
|
||||||
structure: structure,
|
|
||||||
connections: vec![0 as f64;9],
|
|
||||||
current_value: 0 as f64,
|
|
||||||
new_value: 0 as f64,
|
|
||||||
block_type: btype
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn get_connections(&self) -> &Vec<f64> {
|
|
||||||
&self.connections
|
|
||||||
}
|
|
||||||
pub fn get_value(&self) -> &f64 {
|
|
||||||
&self.current_value
|
|
||||||
}
|
|
||||||
pub fn get_name(&self) -> &'static str {
|
|
||||||
match self.block_type {
|
|
||||||
Blocks::Neuron => "Neuron",
|
|
||||||
Blocks::Leg { dir } => "Leg"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn clean(&mut self) {
|
|
||||||
self.current_value = self.new_value;
|
|
||||||
}
|
|
||||||
pub fn compute(&mut self) -> Option<Vec<Action>> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
14
src/main.rs
14
src/main.rs
@ -1,14 +0,0 @@
|
|||||||
mod util;
|
|
||||||
mod action;
|
|
||||||
mod block;
|
|
||||||
mod structure;
|
|
||||||
mod world;
|
|
||||||
|
|
||||||
use world::World;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let mut world = World::new(10);
|
|
||||||
loop {
|
|
||||||
world.compute()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
use util::{Point,Directions};
|
|
||||||
use action::Action;
|
|
||||||
use block::Block;
|
|
||||||
|
|
||||||
pub struct Structure {
|
|
||||||
pub blocks: Vec<Block>,
|
|
||||||
pub position: Point
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Structure {
|
|
||||||
pub fn compute(&mut self) -> Vec<Action> {
|
|
||||||
let mut v = Vec::new() as Vec<Action>;
|
|
||||||
// Iterate blocks
|
|
||||||
for b in &mut self.blocks {
|
|
||||||
// Compute and save result
|
|
||||||
let ret = match b.compute() {
|
|
||||||
Some(x) => x,
|
|
||||||
None => Vec::new() as Vec<Action>
|
|
||||||
};
|
|
||||||
// push actions to the structure's array
|
|
||||||
for i in ret {
|
|
||||||
v.push(i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
v
|
|
||||||
}
|
|
||||||
pub fn move_to(&mut self, dest : Point){
|
|
||||||
self.position = dest;
|
|
||||||
}
|
|
||||||
pub fn move_dir(&mut self, dir : Directions){
|
|
||||||
let mut deltaX = 0; let mut deltaY = 0;
|
|
||||||
match dir {
|
|
||||||
Directions::Left => deltaX = -1,
|
|
||||||
Directions::Right => deltaX = 1,
|
|
||||||
Directions::Up => deltaY = -1,
|
|
||||||
Directions::Down => deltaY = 1
|
|
||||||
}
|
|
||||||
self.position = Point { x: self.position.x + deltaX, y: self.position.y + deltaY }
|
|
||||||
}
|
|
||||||
}
|
|
32
src/util.rs
32
src/util.rs
@ -1,32 +0,0 @@
|
|||||||
use std::ops::Add;
|
|
||||||
|
|
||||||
pub struct Point {
|
|
||||||
pub x: i32,
|
|
||||||
pub y: i32
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Add for Point {
|
|
||||||
type Output = Point;
|
|
||||||
|
|
||||||
fn add(self, other: Point) -> Point {
|
|
||||||
Point { x: self.x + other.x, y: self.y + other.y }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum Directions {
|
|
||||||
Right,
|
|
||||||
Left,
|
|
||||||
Up,
|
|
||||||
Down
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dir_to_point(dir : Directions) -> Point {
|
|
||||||
let mut p = Point { x: 0, y: 0 };
|
|
||||||
match dir {
|
|
||||||
Directions::Left => p.x = -1,
|
|
||||||
Directions::Right => p.x = 1,
|
|
||||||
Directions::Up => p.y = -1,
|
|
||||||
Directions::Down => p.y = 1
|
|
||||||
}
|
|
||||||
p
|
|
||||||
}
|
|
60
src/world.rs
60
src/world.rs
@ -1,60 +0,0 @@
|
|||||||
use structure::Structure;
|
|
||||||
use action::{Action,Actions};
|
|
||||||
use util::{Point,Directions,dir_to_point};
|
|
||||||
use std::sync::mpsc::{Sender,Receiver,channel};
|
|
||||||
|
|
||||||
pub struct World {
|
|
||||||
size: i32, // width and height of the grid
|
|
||||||
grid: Vec<Option<Structure>>, // access a structure using coordinates
|
|
||||||
structures: Vec<Structure> // list of all structures
|
|
||||||
}
|
|
||||||
|
|
||||||
impl World {
|
|
||||||
pub fn new(size: i32) -> World {
|
|
||||||
let mut v = Vec::new() as Vec<Option<Structure>>;
|
|
||||||
v.reserve((size*size) as usize);
|
|
||||||
World {
|
|
||||||
size: size,
|
|
||||||
grid: v,
|
|
||||||
structures: Vec::new() as Vec<Structure>,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn compute(&mut self) {
|
|
||||||
let mut actions = Vec::new() as Vec<Action>;
|
|
||||||
for s in &mut self.structures {
|
|
||||||
for a in s.compute() {
|
|
||||||
actions.push(a);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for action in actions {
|
|
||||||
self.apply_action(action)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn apply_action(&mut self, action : Action){
|
|
||||||
let src = &mut action.source;
|
|
||||||
match action.action_type {
|
|
||||||
Actions::Movement { dir } => {
|
|
||||||
let p = src.position + dir_to_point(dir);
|
|
||||||
match self.get(p) {
|
|
||||||
&mut Some(x) => {},
|
|
||||||
&mut None => src.move_to(p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn get(&mut self, p: Point) -> &mut Option<Structure> {
|
|
||||||
&mut self.grid[(p.y+p.x) as usize]
|
|
||||||
}
|
|
||||||
pub fn fix_coords(&self, p: Point) -> Point {
|
|
||||||
fn fix_coord(c: i32, limit: i32) -> i32 {
|
|
||||||
match c < 0 {
|
|
||||||
true => (c * -1) % limit,
|
|
||||||
false => match c >= limit {
|
|
||||||
true => c % limit,
|
|
||||||
false => c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Point { x: fix_coord(p.x,self.size), y: fix_coord(p.y,self.size) }
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user