Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
obj/
tools/*.svg
build/
build/
.DS_Store
.vscode/
.gitignore
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.0.0)
project(BioSim4 VERSION 0.2.0)
project(BioSim4 VERSION 1.0.0)

include(CTest)
enable_testing()
Expand Down
4 changes: 2 additions & 2 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
MIT License

Copyright (c) 2021-2022 David R. Miller and other contributors. For the
exact contribution history, see the revision logs at the project page
Copyright (c) 2025 Alexander Gates and other contributors. For the
original contribution history, see the revision logs at the original project page
https://github.com/davidrmiller/biosim4.

Permission is hereby granted, free of charge, to any person obtaining a copy
Expand Down
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
# biosim4

Try out a Rust refactor here - https://github.com/AlexanderGatesDev/biosimRust

## Status

This project is transitioning to maintenance-only. Thanks to all who contributed
improvements to this project. We will continue to welcome bug fixes that enable this
program to compile and execute, and we welcome discussions about this program
and related topics in the Issues section.
This project is a fork with enhancements focused on improving variable-length genome stability and preventing artificial genome growth. Key improvements include:

- **Connection deduplication**: Automatic merging of duplicate neural network connections during genome-to-network conversion, preventing genomes from growing indefinitely by adding redundant connections. Duplicate connections (same source-sink pairs) are merged by summing their weights and clamping to i16 range.

- **Enhanced fitness normalization**: Increased default `fitnesslengthnormalization` from `0.01` to `0.03` to better prevent selection pressure favoring longer genomes. The normalization formula `normalized_score = score / (1 + beta * genome_length)` now applies a stronger penalty for longer genomes.

- **Strengthened length penalty weights**: Updated genome similarity calculations to use a triple penalty system (30% similarity, 35% relative length ratio, 35% absolute length bonus) instead of the previous 40/30/30 split. This creates stronger selection pressure to maintain genome lengths near the initial value.

- **Length-aware mutation scaling**: Enhanced insertion/deletion mutation rates to dynamically adjust based on genome length. As genomes grow beyond their initial length, insertion probability automatically decreases while deletion probability increases, creating natural selection pressure that favors genome lengths closer to the starting value.


## What is this?

Expand Down
219 changes: 117 additions & 102 deletions biosim4.ini
Original file line number Diff line number Diff line change
@@ -1,178 +1,193 @@
# biosim4.ini
# biosim4.ini is the default config file for the simulator.
# This is the default config file for Biosim4 (the C++ version of the simulator).
# The config filename is determined in simulator() in simulator.cpp.
# The config file is parsed by class ParamManager, see params.cpp and params.h.
# The config file is parsed by ParamManager, see params.cpp.
# Although not foolproof, the config file can be modified during a simulation
# run and the param manager will make any new params available to the simulator
# after the end of the current simulator step or after the end of the current
# generation.
#
# Parameter values can also be changed automatically based on the current generation
# number of the simulation using the syntax (see barrierType param for example):
# number of the simulation using the syntax (see barriertype param for example):
# <parameterName>@<generationNumber> = <newValue>
# Instances of the same parameter have to be ascending by generation number to work
# correctly, e.g. barrierType@100 must be later in the file than barrierType@50,
# which in turn must come after barrierType. Not all parameters can be safely
# correctly, e.g. barriertype@100 must be later in the file than barriertype@50,
# which in turn must come after barriertype. Not all parameters can be safely
# changed during a simulation. Some restrictions are noted below.

# numThreads must be 1 or greater. Best value is less than or equal to
# numthreads must be 1 or greater. Best value is less than or equal to
# the number of CPU cores. Cannot be changed after a simulation starts.
numThreads = 4
numthreads = 8

# sizeX, sizeY define the size of the 2D world. Minimum size is 16,16.
# sizex, sizey define the size of the 2D world. Minimum size is 16,16.
# Maximum size is 32767, 32767. Cannot be changed after a simulation starts.
sizeX = 128
sizeY = 128
sizex = 128
sizey = 128

# Population at the start of each generation. Maximum value = 32766.
# population at the start of each generation. Maximum value = 32766.
# Cannot be changed after a simulation starts.
population = 3000

# Number of simulation steps per generation. Range 1..INT_MAX.
stepsPerGeneration = 300
stepspergeneration = 300

# The simulator will stop when the generation number == maxGenerations.
# The simulator will stop when the generation number == maxgenerations.
# Range 1..INT_MAX
maxGenerations = 200000
maxgenerations = 10000

# genomeInitialLengthMin and genomeInitialLengthMax should be set to
# genomeinitiallengthmin and genomeinitiallengthmax should be set to
# the same value. (For future use, the max length might be larger to
# allow mutations that lengthen the genome.) Range 1..INT_MAX and
# must be no larger than genomeMaxLength. The range of genomeMaxLength
# is genomeInitialLengthMax..INT_MAX. Cannot be changed after a
# must be no larger than genomemaxlength. The range of genomemaxlength
# is genomeinitiallengthmax..INT_MAX. Cannot be changed after a
# simulation starts.
genomeInitialLengthMin = 24
genomeInitialLengthMax = 24
genomeMaxLength = 300
genomeinitiallengthmin = 24
genomeinitiallengthmax = 24
genomemaxlength = 300

# maxNumberNeurons is the maximum number of internal neurons that may
# maxnumberneurons is the maximum number of internal neurons that may
# be addressed by genes in the genome. Range 1..INT_MAX. Cannot be
# changed after a simulation starts. Cannot be changed after a
# simulation starts.
maxNumberNeurons = 5
# changed after a simulation starts.
maxnumberneurons = 28

# If killEnable is true and the "kill" action neuron is enabled in
# If killenable is true and the "kill" action neuron is enabled in
# sensors-actions.h and compiled in, then agents are permitted to
# kill their neighbor in the adjacent location in the direction of
# forward movement. If false, the neighbors are safe.
killEnable = false
killenable = false

# If sexualReproduction is false, newborns inherit the genes from a
# If sexualreproduction is false, newborns inherit the genes from a
# single parent. If true, newborns inherit a mixture of genes from
# two parents.
sexualReproduction = true
sexualreproduction = true

# If chooseParentByFitness is false, then every agent that survives the
# If chooseparentsbyfitness is false, then every agent that survives the
# selection criterion has equal chance of reproducing. If true, then
# preference is given to those parents who passed the selection criterion
# with a greater score. Fitness scores are determined in survival-criteria.cpp.
chooseParentsByFitness = true
chooseparentsbyfitness = true

# pointMutationRate is the probability per gene of having a single-bit
# pointmutationrate is the probability per gene of having a single-bit
# mutation during spawning. Range 0.0 .. 1.0. A reasonable range is
# 0.0001 to 0.01.
pointMutationRate = 0.001

# geneInsertionDeletionRate and deletionRatio are for future use to
# allow mutations that lengthen or shorten the genome. Ignored for now.
geneInsertionDeletionRate = 0.0
deletionRatio = 0.5

# responsivenessCurveKFactor is a small positive integer that determines
pointmutationrate = 0.01

# geneinsertiondeletionrate is the probability per genome of having a gene
# inserted or deleted during reproduction. Range 0.0 .. 1.0.
# A reasonable range is 0.0001 to 0.01. Set to 0.0 to disable.
# For variable-length genome testing, 0.01 provides controlled, gradual changes.
# deletionratio is the probability that a mutation will be a deletion vs insertion.
# Range 0.0 .. 1.0. 0.5 means equal chance of insertion or deletion.
# Note: Insertion probability automatically scales down as genome length increases.
geneinsertiondeletionrate = 0.01
deletionratio = 0.5

# fitnesslengthnormalization normalizes fitness scores by genome length to prevent
# selection pressure favoring longer genomes. Formula: normalized_score = score / (1 + beta * length)
# Range 0.0 .. 1.0. Higher values = stronger penalty for longer genomes.
# Recommended: 0.01 to 0.05. Default: 0.01
fitnesslengthnormalization = 0.03

# responsivenesscurvekfactor is a small positive integer that determines
# the shape of the curve that determines how reactive an agent is to its
# sensory inputs. Typical values are # 1, 2, 3, or 4, but greater values
# sensory inputs. Typical values are 1, 2, 3, or 4, but greater values
# are allowed experimentally.
responsivenessCurveKFactor = 2
responsivenesscurvekfactor = 2

# populationSensorRadius is the radius in which the population sensor
# populationsensorradius is the radius in which the population sensor
# looks for neighbors. Floating point value. A value of 1.5 includes
# all the immediate eight-neighborhood. Larger values incur exponentially
# increasing processor overhead. Range 0.5 up to (float)max(sizeX, sizeY).
populationSensorRadius = 2.5
# increasing processor overhead. Range 0.5 up to (float)max(sizex, sizey).
populationsensorradius = 2.5

# longProbeDistance is the default distance that the long-probe sensors
# longprobedistance is the default distance that the long-probe sensors
# are able to see. Applies to long-probe population sensor and long-probe
# signal (pheromone) sensor. Range 1..INT_MAX.
longProbeDistance = 16
longprobedistance = 16

# shortProbeBarrierDistance is the distance that the short-probe sensor
# shortprobebarrierdistance is the distance that the short-probe sensor
# can see. Range 1..INT_MAX.
shortProbeBarrierDistance = 4
shortprobebarrierdistance = 4

# signalSensorRadius is the radius in which the signal (pheromone) sensor
# signalsensorradius is the radius in which the signal (pheromone) sensor
# looks for pheromones. Floating point value. A value of 1.5 includes
# all the immediate eight-neighborhood. Larger values incur exponentially
# increasing processor overhead. Range 0.5 up to (float)max(sizeX, sizeY).
signalSensorRadius = 2.0
# increasing processor overhead. Range 0.5 up to (float)max(sizex, sizey).
signalsensorradius = 2.0

# signalLayers defines the number of pheromone layers. Must be 1 for now.
# signallayers defines the number of pheromone layers. Must be 1 for now.
# Values > 1 are for future use. Cannot be changed after a simulation starts.
signalLayers = 1
signallayers = 1

# imageDir is the relative or absolute directory path where generation
# imagedir is the relative or absolute directory path where generation
# movies are created.
imageDir = images
imagedir = images

# logDir is the relative or absolute directory path where text log files
# logdir is the relative or absolute directory path where text log files
# are created.
logDir = logs
logdir = logs

# displayScale scales the generation movie. Typical values are
# displayscale scales the generation movie. Typical values are
# 1 for actual size, or 2, 4, 8, 16, or 32 to scale up the movie.
displayScale = 8
displayscale = 8

# agentsize controls the size of the dot used to represent an agent
# in the generation movie. Typical value is displayscale / 2.
agentsize = 4

# agentSize controls the size of the dot used to represent an agent
# in the generation movie. Typical value is displayScale / 2.
agentSize = 4
# If savevideo is true, the simulator program will create generation
# movies in the directory named by imagedir at the intervals set by
# videosavefirstframes and videostride.
savevideo = false

# If videoSaveFirstFrames is 0, then only the parameter videoStride controls
# how often generation movies are made. If videoSaveFirstFrames is nonzero,
# If savepngframes is true, individual PNG frame files will be saved
# for each frame. If false, only video files are created (no PNG frames).
# Set to false to avoid PNG save errors and reduce disk I/O.
savepngframes = false

# videostride determines how often generation movies will be created.
# Also see savevideo and videosavefirstframes. Range 1..INT_MAX.
videostride = 25

# If videosavefirstframes is 0, then only the parameter videostride controls
# how often generation movies are made. If videosavefirstframes is nonzero,
# then generation movies will also be generated for every generation from 0
# through videoSaveFirstFrames (because the first few generations are often
# through videosavefirstframes (because the first few generations are often
# the most interesting). Range 1..INT_MAX.
videoSaveFirstFrames = 2
videosavefirstframes = 2

# updateGraphLog can be set to true to cause the simulator program to
# updategraphlog can be set to true to cause the simulator program to
# invoke graphlog.gp to update the simulation progress graph. If true,
# then updateGraphLogStride controls how often it is invoked. If
# updateGraphLog is false, then the simulator program will not invoke
# then updategraphlogstride controls how often it is invoked. If
# updategraphlog is false, then the simulator program will not invoke
# graphlog.gp.
updateGraphLog = true

# If saveVideo is true, the simulator program will create generation
# movies in the directory named by imageDir at the intervals set by
# videoSaveFirstFrames and videoStride.
saveVideo = true

# videoStride determines how often generation movies will be created.
# Also see saveVideo and videoSaveFirstFrames. Range 1..INT_MAX.
videoStride = 25
updategraphlog = true

# updateGraphLogStride determines how often the simulation progress graph
# is updated by direct invocation of graphlog.gp. Ignored if updateGraphLog
# is false. updateGraphLogStride may be a positive integer from 1 to INT_MAX,
# or may be set to the string videoStride to use the value of videoStride.
updateGraphLogStride = videoStride
# updategraphlogstride determines how often the simulation progress graph
# is updated by direct invocation of graphlog.gp. Ignored if updategraphlog
# is false. updategraphlogstride may be a positive integer from 1 to INT_MAX,
# or may be set to the string videoStride to use the value of videostride.
updategraphlogstride = videoStride

# genomeAnalysisStride determines how often the simulator will print genomic
# genomeanalysisstride determines how often the simulator will print genomic
# statistics. The stats are printed to stdout when the generation number
# modulo genomeAnalysisStride == 0. The value may be a positive integer from
# modulo genomeanalysisstride == 0. The value may be a positive integer from
# 1 to INT_MAX, or may be set to the string videoStride to use the value of
# videoStride.
genomeAnalysisStride = videoStride
# videostride.
genomeanalysisstride = videoStride

# When the genomic statistics are printed (see genomeAnalysisStride), the
# When the genomic statistics are printed (see genomeanalysisstride), the
# method used to measure genome diversity in the population is determined
# by genomeComparisonMethod. May be set to 0 for Jaro-Winkler method (useful
# for future use if genomes are allowed to grow or shrink in size); or 1
# by genomecomparisonmethod. May be set to 0 for Jaro-Winkler method (useful
# for variable-length genomes that can grow or shrink in size); or 1
# for a Hamming measure bit-by-bit, or 2 for a Hamming measure byte-by-byte.
# Typically set to 1.
genomeComparisonMethod = 1
# Set to 0 for variable-length genome testing (required when geneinsertiondeletionrate > 0.0).
genomecomparisonmethod = 0

# When genomic statistics are printed (see genomeAnalysisStride), the number
# When genomic statistics are printed (see genomeanalysisstride), the number
# of genomes sampled from the population and printed to stdout is determined
# by displaySampleGenomes. Range 0 to population size.
displaySampleGenomes = 5
# by displaysamplegenomes. Range 0 to population size.
displaysamplegenomes = 5

# challenge determines the selection criterion for reproduction. This is
# typically always under active development. See survival-criteria.cpp for
Expand All @@ -188,7 +203,7 @@ displaySampleGenomes = 5
# 7 = migrate distance
# 8 = center sparse
# 9 = left eighth
# 10 = radioactive walls
# 10 = radioactive walls (Left side then Right side)
# 11 = against any wall
# 12 = touch any wall any time
# 13 = east-west eighths
Expand All @@ -200,7 +215,7 @@ challenge = 6

# The simulator supports a feature called "barriers." Barriers are locations
# in the simulated 2D world where agents may not occupy. The value of
# barrierType is typically under active development. See createBarrier.cpp
# barriertype is typically under active development. See createBarrier.cpp
# for more information.
# 0 = none
# 1 = vertical bar constant location
Expand All @@ -209,21 +224,21 @@ challenge = 6
# 4 = horiz bar constant location north center
# 5 = floating islands
# 6 = sequence of spots
barrierType = 0
barriertype = 0

# This is an example of an automatic parameter change based on the generation.
# If uncommented, the barrier type will automatically change to the new value
# when the simulation reaches the generation specified after the @ delimiter.
# barrierType@500 = 5
# barriertype@500 = 5

# If true, then the random number generator (RNG) will be seeded by the value
# in RNGSeed, causing each thread to receive a deterministic sequence from
# in rngseed, causing each thread to receive a deterministic sequence from
# the RNG. If false, the RNG will be randomly seeded and program output will
# be non-deterministic. Cannot be changed after a simulation starts.
deterministic = false

# If deterministic is true, the random number generator will be seeded with
# this value. If deterministic is false, this value is ignored. Legal values
# are integers 0 to 4294967295. Cannot be changed after a simulation starts.
RNGSeed = 12345678
rngseed = 12345678

Loading