Please Note: This was inspired by David R. Miller's simulator, and is just a quick project i wanted to write; It does not have very optimised / clean code.
When you clone this repository, you must use the argument "--recursive" to make sure it pulls all the submodules.
main
- This is the normal branch, just the base program.gpu-acceleration-wip
- This is where i am working on GPU Acceleration using compute shaders. I do not know if this will work well to speed up simulation.
- Generational Simulator
- Neural Map Viewer / Visualiser
- Shape Parsing Language
- GPU Acceleration
- Simulation of custom Neural Maps
Note: These are all included in the repo, and all (apart from FFMPEG) are statically linked into the program
- Premake - VS Project Generation
- SFML - Window/Image Rendering
- FFMPEG - Video Rendering
- mINI - INI Config Reading
- nodesoup - Fruchterman-Reingold graph positioning
- spdlog - Output logging
- Edit
config/config.ini
in the program directory to change the simulation parameters - Setup
config/survival.shape
in the program directory to configure the Survival Conditions - Run
NeuralSim.exe
, optionally with the argument-c [.INI FILE]
- Run
NeuralSim.exe
with the argument-v [.NM FILE]
Argument | Description |
---|---|
-h |
Outputs program usage |
-s |
Simulate mode [DEFAULT] |
-v [*.nm] |
Visualise mode |
-c [*.ini] |
Specify configuration file [DEFAULT: config/config.ini ] |
-sc [*.shape] |
Specify the survival shape file [DEFAULT: config/survival.shape ] |
-k [int] |
Set the Fruchterman-Reingold constant [DEFAULT: 400] |
Please Note: The square brackets in this documentation only show there is a token or parameter to change, the program will not understand your arguments if you include square brackets when executing the program. Also, the program does not support wildcards (*), this is only in the documentation to show that a specific file of that filetype is required.
- Run
GenerateScripts.bat
to use premake to generate the Visual Studio 2022 project files - Open NeuralSim.sln
Receptors are defined in a Receptor
enum in src/Classes/NeuralMap/Types.h
Receptor behaviour is defined in Cell::GetReceptorVal(NeuralNet::Receptor receptor)
in src/Classes/NeuralMap/Receptors.cpp
Receptor name strings are defined in receptorName(Receptor receptor)
in src/Classes/NeuralMap/DebugTypes.cpp
Effectors are defined in an Effector
enum in src/Classes/NeuralMap/Types.h
Effector behaviour is defined in ProcessEffectorQueue(int owner)
in src/Classes/NeuralMap/Effectors.cpp
Effector name strings are defined in effectorName(Effector effector)
in src/Classes/NeuralMap/DebugTypes.cpp
The Visualiser long name strings are defined in shrtNodeToLong(std::string shrt)
in src/Visualise/Visualise.cpp
This section will be expanded along with comments in the source
This documentation will be expanded in the future, and I may make a youtube video explaining how the simulator and visualiser works (as in more technical / code orientated detail than David Miller's video)
The .shape
file is a custom language that allows the program to parse shapes for the simulation. It is extremely basic right now, and is only used for survival conditions. In the future, they will be used to define walls inside the simulation. The language needs specific usage of spaces and newlines to parse correctly.
Shapes
Shape | Arguments | Example |
---|---|---|
CIRCLE |
[LOC X] [LOC Y] [RADIUS] |
CIRCLE 64 64 25 |
RECT |
[LEFT X] [RIGHT X] [TOP Y] [BOTTOM Y] |
RECT 64 128 0 128 |
Right now, shapes are used to define areas where cells will survive at the end of a generation. In the future, kill shapes will be added, where cells are killed off if they are inside the shape for a certain amount of time. Wall shapes will also be added so that dynamic maps for the cells to survive in can be created.
Conditions
Keyword | Arguments | Description | Example |
---|---|---|---|
IF |
[LEFT VAL] [OPERATOR] [RIGHT VAL] |
Sets the current condition to parse with | IF {GEN} > 200 |
ELSE |
Inverts the current condition | ELSE |
|
ENDIF |
Clears the current condition | ENDIF |
Please Note: Conditions do not support expression inputs yet. The left val and right val can only be a Token, or an integer. Expression parsing will come in the future. (This means that
{STEPSPERGEN}/2
would be invalid)
Conditions allow shapes to change during a simulation. They are extremely simple right now and so do not support math operations as inputs. However, they do support all conditional operators (==
, !=
, >
, >=
, <
, <=
).
Tokens
Token | Description |
---|---|
STEP |
The current simulation step |
STEPSPERGEN |
The total amount of simulation steps in a generation |
GEN |
The current generation |
Please Note: Currently, Tokens can only be used in conditions.
Tokens are used for specifying simulation parameters in a shape file. They are used by enclosing the variable name in {...}
; for instance: {GEN}
, which returns the simulation's current generation.
Shape condition tokens are not very useful for survival configuration files as changing them during a generation wont do anything; It only matters what shape is active at the end of the generation, meaning only {GEN}
is useful. I might add random tokens, and I definitely will add shape files for map configuration, so moving walls, etc, can be programmed.
An example of a .shape
file is:
~ Will switch to rectangle after generation 500
IF {GEN} >= 500
RECT 64 128 0 128 ~ Right side box
ELSE
CIRCLE 64 64 25 ~ Center circle
ENDIF
~ Circle is always present in left corner (ENDIF clears the current condition)
CIRCLE 0 128 25
~ To use this run `NeuralSim.exe -sc config/circle2rect.shape`
~
can be used to comment either on a full line or after syntax
Receptors are inputs for neural maps that represent a cell's senses, and other inputs such as an oscillator as an internal clock. Right now there are 5 receptors (LOC_X
, LOC_Y
, RANDOM
, AGE
, and OSCILLATOR
).
Effectors are outputs for neural maps that affect the cell's position. Right now there are 9 movement effectors, and 0 misc effectors. Misc effectors could be used to add pheremones or audio / visual communication between cells.
Internal nodes are intermediate nodes in neural maps that act as multipliers or variables for it. They allow inputs to be summed up and output into another node. They can also connect to themselves, which act as a sort of data delay, as a loop back connection uses the node's value from the last step.
Steps are an 'update' in the simulation. Each step a cell can move 1 position in any direction as a result of their neural map. Neural maps are simulated every step.
A Generation is a full lifetime of a cell. A generation has a certain number of steps and once a generation has passed, every cell that does not meet the survival conditions is killed off. Then, a new set of cells are spawned, with their Genome being a mutated mix of a random pair of cells that survived the previous generation.
A Gene is a connection between two Nodes in a Neural Map. A source is the node it comes from and a sink is the node it connects to. It has a weight and it multiplies it's source's output by its weight, then passes that to it's sink's input.
A Genome is a list of genes that defines a full neural map. Genomes can be mutilated and combined to form child genomes.
A Neural Map is a set of nodes that are connected using the connections described in a Genome. Each cell has a Neural Map, and for every step of the generation, every cell's neural map is simulated to determine the cell's behaviour that step.
A simple "microorganism" that contains a Neural Map, and a location on the grid.
The main simulator and visualiser is finished. What's mostly needed is optimisation, and a couple other features that make the program more fun to play around with.
The most help is needed with optimisation, as I only have ~1 years experience with C++, and there will be many slow operations and calls that I wrote without realising how slow they are. I also dont have much time to work on the program, so any expansion with new receptors, effectors, etc is greatly appreciated :D