Alrium/README.md
2015-10-02 13:21:29 +02:00

13 KiB

NewAIrium

AIrium is an effort to create a working, complex artificial life simulation environment.

This is a design paper for the project and its implementation.

Description

This new iteration of the project focuses on solving the problems of the first:

  • variety in creature body and mind structure
  • removing evolution limits (such as brain size)
  • allowing different species to mate and produce possibly stable children
  • speed: trying to get the simulation to be as fast as possible on consumer grade hardware
  • documentation: having an actual model to follow
  • concurrency: try to enable the maximum concurrency when simulating the world, to the point that multiple networked computers may collaborate and work together to simulate a world faster or help each other into achieving meaningful results faster.
  • java being used to write the implementation

Possible Applications

  • studying evolution dynamics
  • discovering new approaches at common problems by looking at how the simulated machines solved them
  • learning how to create the environment necessary for a specific kind of evolution to take place
  • having fun designing such a world and implementing a simulator for it
  • having fun manually designing and creating structures
  • having fun looking at machines interacting with the world and each other
  • bragging about having designed and implemented such a simulation
  • figuring out wether it would actually work
  • distributing a creative alternative to a fish tank

World

The world is a matrix where each cell is either empty or occupied by a structure. Every structure occupies only one cell.

Structures and Creatures

  • a structure is a matrix where each cell is either empty or occupied by a Block.
  • each adiacent Block can be connected.
  • the blocks make up both the body and, if the blocks are connected, the mind.
  • structures can move in the world, but can't occupy the same space of another structure.

A structure with an operational mind governing the body is called a creature or machine.

There are four kinds of blocks:

  • neuron blocks make the calculations
  • action blocks let a structure do something
  • sensor blocks let a structure know something about itself or anything else
  • resource blocks can be interacted with, causing some effects

Signals

  • a signal is a floating point value. Each block stores a signal.
  • a signal is always between -1 and 1 included.

Connection

  • Each block can be connected to every other adiacent block (diagonals too), so each block has 9 input connection weights: one for each neighbor and one for itself.
  • A connection is a weight, which is a floating point number. All connections are inputs.
  • A neuron block's output is its adiacent blocks's current stored signals multiplied by the appropriate weight.
  • the weight (or connection) is a floating point number between -1 and 1 included.
  • If the connection is 0, then there's no connection
  • if the connection is not 0 then there is a connection
  • if the connection is 1 then there is a full positive connection, meaning the source value passes intact 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

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 lower than -1 will be considered to be -1

Neuron (Processing) Blocks

  • new neuron blocks contain the 0 signal (null signal).
  • neuron blocks store a signal until it is changed. The signal is calculated again at every iteration of the simulation
  • a neuron block's signal is the sum of all the incoming values, divided then by the sum of all the weights.
    • an incoming value is the signal coming from a connection multiplied by the connection weight.

Action (Output) blocks

They take an input signal and act by doing something, for example:

  • they move the body in space
  • they eject something
  • they change color
  • they interact with other structures
  • they "eat" a block
  • nothing (can be useful to stop signals from passing through)

Sensor (Input) blocks

They give adiacent blocks a signal.

Some examples:

  • they communicate how far is the first structure in a straight line
  • they communicate the amount of structures in relation to blank spaces in an area (popularity in an area)
  • they communicate the color/type/activity of the first block in a straight line
  • they communicate information about the presence of a particular kind of structure in a direction
  • they communicate information about the structure itself, like integrity of adiacent blocks

Generated signals are floating point numbers ranging from -1 to 1 included.

Combined (Input+Output) blocks

They act as Sensor blocks and Action blocks at the same time.

Examples:

  • a "mouth" block that can communicate wether food is available to eat and can receive eat commands at the same time

Reproduction and evolution

This requires two action blocks:

  • the egg block
  • the fertilizer block

machines can of course have both blocks, or even multiples of them.

  • the egg block leaves a structure behind, an egg. This egg will either be fertilized or deplete energy and die.
  • the fertilizer block can be used on egg structure to fertilize them

Hatching

When the egg hatches, a structure is born. The structure is built using a combination of the father's and the mother's blocks, then a mutation is applied to the result.

Energy

This requires some new blocks:

  • the battery block (first mentioned resource block)
  • the energy drain block
  • the generator block
  • the stomach block
  • the energy drain block sucks energy from a structure's batteries.
  • the battery block stores energy and emits the current level to its connections
  • the generator block uses up a lot of energy, then gives back slightly more
  • the stomach block can be used to eat blocks from another structure. It then uses some energy to digest the blocks, providing it back with an extra amount after it's done.

Consumption

  • each action block uses up a moderate amount of energy when activated
  • each sensor block uses up a small amount of energy when activated

Fitness of a structure

The fitness is calculated by dividing the creature's total harvested energy by the creature's total consumed energy.

A creature with a fitness lower than or equal to 0 has all its batteries depleted. Then, it is either revived by being charged somehow, or it can't do anything and no mind activity can take place.

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, thus decreasing fitness
  • What is a creature couldn't harvest energy because the circumstances weren't favorable?

The conclusion is that this system is far too complex to consider fitness evaluation.

Complexity of a structure

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: 0

Complexity of a block starts at 0, then:

  • 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

The world should be composed of a variety of huge, small and anything in between energy farming or storage structures.

Even better, there should be structures generating energy and shipping it off in space.

Primordial creature

Since we can't expect that interesting machines pop up from nothing in our simulation, we need to design one. On Earth, it took billions of years for something to show up, but we want to speed things up a little bit.

This machine needs:

  • to search for energy sources
  • to find energy sources and get there
  • to use energy sources to fill up batteries
  • to successfully create fertilized eggs

From there, maybe something smarter can show up. It is essential that the user can save a machine at any time, even early in an implementation. We don't want to lose good machines!

Interface

It is very important to give the user the ability to see the world in motion.

The interface should be structured akin to the following guidelines:

  • a world navigation screen
    • lets the user create an empty structure (needs at least 1 block)
  • structure navigation screen
    • ability to save the structure to file
    • has a small selected block sidebar with block informaton
    • lets the user clear cells or set a block to them
  • block navigation screen
    • provides all info about the block and signal activity information
    • lets the user view all connections and edit them
    • lets the user edit the block type, preserving the connections

A sidebar has controls to handle simulation speed and pausing and other information such as:

  • current CPU load.
  • available energy in the world and amount being generated
  • population information

Implementation

The rust programming language is the best suited for a variety of reasons, namely:

  • it is a fast language, well suited for performance intensive tasks
  • it has a package manager that allows a programmer to focus on writing the code, not desiging infrastructure, build systems, or coding functions already available in popular libraries
  • it produces native, multiplatform binaries
  • it obviously supports all the required features for the implementation of the simulation

But most importantly:

  • it is built with a focus on type safety, concurrency and thread safety
  • it's impossible to compile a thread unsafe concurrent program with the rust reference compiler

Simulation iteration evaluation algorithm

  • MT: manager/main thread, controls the flow, HIGHEST PRIORITY
  • WT: worker thread, evaluates a simulation iteration for one or more structures
  • AT: applier threads, applies actions and writes the new stored signal into the current stored signal for all blocks

One tick is evaluated using the following algorithm:

  1. MT
    • rebalances the WTs work load, evaluating it based on structure complexity
    • assignes newborn structures to the workers
    • starts all the workers
  2. WTs work, MT waits until they are done
  3. MT assigns actions returned by all the WTs to a number of ATs
    • tries to balance the ATs' work
    • groups events from creatures that interact with each other together, keeping it thread safe but as distributed across threads as possible
  4. ATs work, MT waits untile they are done

It's probably not as fast as it gets, but it should be decent

Structure iteration evaluation algorithm

  1. the new stored signal of each block is calculated
    • if the block is a neuron block, calculate its new stored signal using immutable references to the other blocks and the connection values.
    • if the block is an input block, run its logic to figure out what its output should be
    • if the block is an output block and it should perform an action, add it to the action queue
      • an action is a function or method and a list of parameters to pass to it (if it takes parameters)
    • This new stored signal is stored in a special variable without overwriting the current stored signal so that the process can be easily accomplished in multithreading execution environments.

Note: the bigger a machine, the slower it reacts to inputs because signals need more time to travel through the body

Structs, traits and methods

  • world (class), keeps all information about a world

  • structure (trait)

    • 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.

  • 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 to be executed by the manager thread.