mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
[yul-phaser] Add options for selecting crossover operator used by the algorithms
This commit is contained in:
parent
ad89b477c8
commit
d9e2735361
@ -51,6 +51,8 @@ protected:
|
|||||||
/* mutationChance = */ 0.0,
|
/* mutationChance = */ 0.0,
|
||||||
/* deletionChance = */ 0.0,
|
/* deletionChance = */ 0.0,
|
||||||
/* additionChance = */ 0.0,
|
/* additionChance = */ 0.0,
|
||||||
|
/* CrossoverChoice = */ CrossoverChoice::SinglePoint,
|
||||||
|
/* uniformCrossoverSwapChance= */ 0.5,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -113,6 +115,8 @@ BOOST_FIXTURE_TEST_CASE(runNextRound_should_preserve_elite_and_regenerate_rest_o
|
|||||||
/* deletionVsAdditionChance = */ 1.0,
|
/* deletionVsAdditionChance = */ 1.0,
|
||||||
/* percentGenesToRandomise = */ 0.0,
|
/* percentGenesToRandomise = */ 0.0,
|
||||||
/* percentGenesToAddOrDelete = */ 1.0,
|
/* percentGenesToAddOrDelete = */ 1.0,
|
||||||
|
/* CrossoverChoice = */ CrossoverChoice::SinglePoint,
|
||||||
|
/* uniformCrossoverSwapChance= */ 0.5,
|
||||||
};
|
};
|
||||||
GenerationalElitistWithExclusivePools algorithm(options);
|
GenerationalElitistWithExclusivePools algorithm(options);
|
||||||
|
|
||||||
@ -133,6 +137,8 @@ BOOST_FIXTURE_TEST_CASE(runNextRound_should_not_replace_elite_with_worse_individ
|
|||||||
/* deletionVsAdditionChance = */ 0.0,
|
/* deletionVsAdditionChance = */ 0.0,
|
||||||
/* percentGenesToRandomise = */ 0.0,
|
/* percentGenesToRandomise = */ 0.0,
|
||||||
/* percentGenesToAddOrDelete = */ 1.0,
|
/* percentGenesToAddOrDelete = */ 1.0,
|
||||||
|
/* CrossoverChoice = */ CrossoverChoice::SinglePoint,
|
||||||
|
/* uniformCrossoverSwapChance= */ 0.5,
|
||||||
};
|
};
|
||||||
GenerationalElitistWithExclusivePools algorithm(options);
|
GenerationalElitistWithExclusivePools algorithm(options);
|
||||||
|
|
||||||
@ -152,6 +158,8 @@ BOOST_FIXTURE_TEST_CASE(runNextRound_should_generate_individuals_in_the_crossove
|
|||||||
/* deletionVsAdditionChance = */ 0.5,
|
/* deletionVsAdditionChance = */ 0.5,
|
||||||
/* percentGenesToRandomise = */ 1.0,
|
/* percentGenesToRandomise = */ 1.0,
|
||||||
/* percentGenesToAddOrDelete = */ 1.0,
|
/* percentGenesToAddOrDelete = */ 1.0,
|
||||||
|
/* CrossoverChoice = */ CrossoverChoice::SinglePoint,
|
||||||
|
/* uniformCrossoverSwapChance= */ 0.5,
|
||||||
};
|
};
|
||||||
GenerationalElitistWithExclusivePools algorithm(options);
|
GenerationalElitistWithExclusivePools algorithm(options);
|
||||||
|
|
||||||
@ -179,6 +187,8 @@ BOOST_FIXTURE_TEST_CASE(runNextRound_should_generate_individuals_in_the_crossove
|
|||||||
/* deletionVsAdditionChance = */ 0.0,
|
/* deletionVsAdditionChance = */ 0.0,
|
||||||
/* percentGenesToRandomise = */ 0.0,
|
/* percentGenesToRandomise = */ 0.0,
|
||||||
/* percentGenesToAddOrDelete = */ 0.0,
|
/* percentGenesToAddOrDelete = */ 0.0,
|
||||||
|
/* CrossoverChoice = */ CrossoverChoice::SinglePoint,
|
||||||
|
/* uniformCrossoverSwapChance= */ 0.5,
|
||||||
};
|
};
|
||||||
GenerationalElitistWithExclusivePools algorithm(options);
|
GenerationalElitistWithExclusivePools algorithm(options);
|
||||||
|
|
||||||
|
@ -45,6 +45,8 @@ protected:
|
|||||||
/* algorithm = */ Algorithm::Random,
|
/* algorithm = */ Algorithm::Random,
|
||||||
/* minChromosomeLength = */ 50,
|
/* minChromosomeLength = */ 50,
|
||||||
/* maxChromosomeLength = */ 100,
|
/* maxChromosomeLength = */ 100,
|
||||||
|
/* CrossoverChoice = */ CrossoverChoice::Uniform,
|
||||||
|
/* uniformCrossoverSwapChance = */ 0.5,
|
||||||
/* randomElitePoolSize = */ 0.5,
|
/* randomElitePoolSize = */ 0.5,
|
||||||
/* gewepMutationPoolSize = */ 0.1,
|
/* gewepMutationPoolSize = */ 0.1,
|
||||||
/* gewepCrossoverPoolSize = */ 0.1,
|
/* gewepCrossoverPoolSize = */ 0.1,
|
||||||
@ -121,6 +123,9 @@ BOOST_FIXTURE_TEST_CASE(build_should_select_the_right_algorithm_and_pass_the_opt
|
|||||||
|
|
||||||
auto gewepAlgorithm = dynamic_cast<GenerationalElitistWithExclusivePools*>(algorithm2.get());
|
auto gewepAlgorithm = dynamic_cast<GenerationalElitistWithExclusivePools*>(algorithm2.get());
|
||||||
BOOST_REQUIRE(gewepAlgorithm != nullptr);
|
BOOST_REQUIRE(gewepAlgorithm != nullptr);
|
||||||
|
BOOST_TEST(gewepAlgorithm->options().crossover == m_options.crossover);
|
||||||
|
BOOST_TEST(gewepAlgorithm->options().uniformCrossoverSwapChance.has_value());
|
||||||
|
BOOST_TEST(gewepAlgorithm->options().uniformCrossoverSwapChance.value() == m_options.uniformCrossoverSwapChance);
|
||||||
BOOST_TEST(gewepAlgorithm->options().mutationPoolSize == m_options.gewepMutationPoolSize);
|
BOOST_TEST(gewepAlgorithm->options().mutationPoolSize == m_options.gewepMutationPoolSize);
|
||||||
BOOST_TEST(gewepAlgorithm->options().crossoverPoolSize == m_options.gewepCrossoverPoolSize);
|
BOOST_TEST(gewepAlgorithm->options().crossoverPoolSize == m_options.gewepCrossoverPoolSize);
|
||||||
BOOST_TEST(gewepAlgorithm->options().randomisationChance == m_options.gewepRandomisationChance);
|
BOOST_TEST(gewepAlgorithm->options().randomisationChance == m_options.gewepRandomisationChance);
|
||||||
@ -134,6 +139,8 @@ BOOST_FIXTURE_TEST_CASE(build_should_select_the_right_algorithm_and_pass_the_opt
|
|||||||
|
|
||||||
auto classicAlgorithm = dynamic_cast<ClassicGeneticAlgorithm*>(algorithm3.get());
|
auto classicAlgorithm = dynamic_cast<ClassicGeneticAlgorithm*>(algorithm3.get());
|
||||||
BOOST_REQUIRE(classicAlgorithm != nullptr);
|
BOOST_REQUIRE(classicAlgorithm != nullptr);
|
||||||
|
BOOST_TEST(classicAlgorithm->options().uniformCrossoverSwapChance.has_value());
|
||||||
|
BOOST_TEST(classicAlgorithm->options().uniformCrossoverSwapChance.value() == m_options.uniformCrossoverSwapChance);
|
||||||
BOOST_TEST(classicAlgorithm->options().elitePoolSize == m_options.classicElitePoolSize);
|
BOOST_TEST(classicAlgorithm->options().elitePoolSize == m_options.classicElitePoolSize);
|
||||||
BOOST_TEST(classicAlgorithm->options().crossoverChance == m_options.classicCrossoverChance);
|
BOOST_TEST(classicAlgorithm->options().crossoverChance == m_options.classicCrossoverChance);
|
||||||
BOOST_TEST(classicAlgorithm->options().mutationChance == m_options.classicMutationChance);
|
BOOST_TEST(classicAlgorithm->options().mutationChance == m_options.classicMutationChance);
|
||||||
|
@ -96,7 +96,10 @@ Population GenerationalElitistWithExclusivePools::runNextRound(Population _popul
|
|||||||
geneAddition(m_options.percentGenesToAddOrDelete)
|
geneAddition(m_options.percentGenesToAddOrDelete)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
std::function<Crossover> crossoverOperator = randomPointCrossover();
|
std::function<Crossover> crossoverOperator = buildCrossoverOperator(
|
||||||
|
m_options.crossover,
|
||||||
|
m_options.uniformCrossoverSwapChance
|
||||||
|
);
|
||||||
|
|
||||||
return
|
return
|
||||||
_population.select(elitePool) +
|
_population.select(elitePool) +
|
||||||
@ -111,10 +114,15 @@ Population ClassicGeneticAlgorithm::runNextRound(Population _population)
|
|||||||
|
|
||||||
Population selectedPopulation = select(_population, rest.individuals().size());
|
Population selectedPopulation = select(_population, rest.individuals().size());
|
||||||
|
|
||||||
|
std::function<SymmetricCrossover> crossoverOperator = buildSymmetricCrossoverOperator(
|
||||||
|
m_options.crossover,
|
||||||
|
m_options.uniformCrossoverSwapChance
|
||||||
|
);
|
||||||
|
|
||||||
Population crossedPopulation = Population::combine(
|
Population crossedPopulation = Population::combine(
|
||||||
selectedPopulation.symmetricCrossoverWithRemainder(
|
selectedPopulation.symmetricCrossoverWithRemainder(
|
||||||
PairsFromRandomSubset(m_options.crossoverChance),
|
PairsFromRandomSubset(m_options.crossoverChance),
|
||||||
symmetricRandomPointCrossover()
|
crossoverOperator
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -130,6 +130,8 @@ public:
|
|||||||
double deletionVsAdditionChance; ///< The chance of choosing @a geneDeletion as the mutation if randomisation was not chosen.
|
double deletionVsAdditionChance; ///< The chance of choosing @a geneDeletion as the mutation if randomisation was not chosen.
|
||||||
double percentGenesToRandomise; ///< The chance of any given gene being mutated in gene randomisation.
|
double percentGenesToRandomise; ///< The chance of any given gene being mutated in gene randomisation.
|
||||||
double percentGenesToAddOrDelete; ///< The chance of a gene being added (or deleted) in gene addition (or deletion).
|
double percentGenesToAddOrDelete; ///< The chance of a gene being added (or deleted) in gene addition (or deletion).
|
||||||
|
CrossoverChoice crossover; ///< The crossover operator to use.
|
||||||
|
std::optional<double> uniformCrossoverSwapChance; ///< Chance of a pair of genes being swapped in uniform crossover.
|
||||||
|
|
||||||
bool isValid() const
|
bool isValid() const
|
||||||
{
|
{
|
||||||
@ -140,6 +142,7 @@ public:
|
|||||||
0 <= deletionVsAdditionChance && deletionVsAdditionChance <= 1.0 &&
|
0 <= deletionVsAdditionChance && deletionVsAdditionChance <= 1.0 &&
|
||||||
0 <= percentGenesToRandomise && percentGenesToRandomise <= 1.0 &&
|
0 <= percentGenesToRandomise && percentGenesToRandomise <= 1.0 &&
|
||||||
0 <= percentGenesToAddOrDelete && percentGenesToAddOrDelete <= 1.0 &&
|
0 <= percentGenesToAddOrDelete && percentGenesToAddOrDelete <= 1.0 &&
|
||||||
|
0 <= uniformCrossoverSwapChance && uniformCrossoverSwapChance <= 1.0 &&
|
||||||
mutationPoolSize + crossoverPoolSize <= 1.0
|
mutationPoolSize + crossoverPoolSize <= 1.0
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -185,6 +188,8 @@ public:
|
|||||||
double mutationChance; ///< The chance of a particular gene being randomised in @a geneRandomisation mutation.
|
double mutationChance; ///< The chance of a particular gene being randomised in @a geneRandomisation mutation.
|
||||||
double deletionChance; ///< The chance of a particular gene being deleted in @a geneDeletion mutation.
|
double deletionChance; ///< The chance of a particular gene being deleted in @a geneDeletion mutation.
|
||||||
double additionChance; ///< The chance of a particular gene being added in @a geneAddition mutation.
|
double additionChance; ///< The chance of a particular gene being added in @a geneAddition mutation.
|
||||||
|
CrossoverChoice crossover; ///< The crossover operator to use
|
||||||
|
std::optional<double> uniformCrossoverSwapChance; ///< Chance of a pair of genes being swapped in uniform crossover.
|
||||||
|
|
||||||
bool isValid() const
|
bool isValid() const
|
||||||
{
|
{
|
||||||
@ -193,7 +198,8 @@ public:
|
|||||||
0 <= crossoverChance && crossoverChance <= 1.0 &&
|
0 <= crossoverChance && crossoverChance <= 1.0 &&
|
||||||
0 <= mutationChance && mutationChance <= 1.0 &&
|
0 <= mutationChance && mutationChance <= 1.0 &&
|
||||||
0 <= deletionChance && deletionChance <= 1.0 &&
|
0 <= deletionChance && deletionChance <= 1.0 &&
|
||||||
0 <= additionChance && additionChance <= 1.0
|
0 <= additionChance && additionChance <= 1.0 &&
|
||||||
|
0 <= uniformCrossoverSwapChance && uniformCrossoverSwapChance <= 1.0
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -78,6 +78,14 @@ map<MetricAggregatorChoice, string> const MetricAggregatorChoiceToStringMap =
|
|||||||
};
|
};
|
||||||
map<string, MetricAggregatorChoice> const StringToMetricAggregatorChoiceMap = invertMap(MetricAggregatorChoiceToStringMap);
|
map<string, MetricAggregatorChoice> const StringToMetricAggregatorChoiceMap = invertMap(MetricAggregatorChoiceToStringMap);
|
||||||
|
|
||||||
|
map<CrossoverChoice, string> const CrossoverChoiceToStringMap =
|
||||||
|
{
|
||||||
|
{CrossoverChoice::SinglePoint, "single-point"},
|
||||||
|
{CrossoverChoice::TwoPoint, "two-point"},
|
||||||
|
{CrossoverChoice::Uniform, "uniform"},
|
||||||
|
};
|
||||||
|
map<string, CrossoverChoice> const StringToCrossoverChoiceMap = invertMap(CrossoverChoiceToStringMap);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
istream& phaser::operator>>(istream& _inputStream, PhaserMode& _phaserMode) { return deserializeChoice(_inputStream, _phaserMode, StringToPhaserModeMap); }
|
istream& phaser::operator>>(istream& _inputStream, PhaserMode& _phaserMode) { return deserializeChoice(_inputStream, _phaserMode, StringToPhaserModeMap); }
|
||||||
@ -88,6 +96,8 @@ istream& phaser::operator>>(istream& _inputStream, MetricChoice& _metric) { retu
|
|||||||
ostream& phaser::operator<<(ostream& _outputStream, MetricChoice _metric) { return serializeChoice(_outputStream, _metric, MetricChoiceToStringMap); }
|
ostream& phaser::operator<<(ostream& _outputStream, MetricChoice _metric) { return serializeChoice(_outputStream, _metric, MetricChoiceToStringMap); }
|
||||||
istream& phaser::operator>>(istream& _inputStream, MetricAggregatorChoice& _aggregator) { return deserializeChoice(_inputStream, _aggregator, StringToMetricAggregatorChoiceMap); }
|
istream& phaser::operator>>(istream& _inputStream, MetricAggregatorChoice& _aggregator) { return deserializeChoice(_inputStream, _aggregator, StringToMetricAggregatorChoiceMap); }
|
||||||
ostream& phaser::operator<<(ostream& _outputStream, MetricAggregatorChoice _aggregator) { return serializeChoice(_outputStream, _aggregator, MetricAggregatorChoiceToStringMap); }
|
ostream& phaser::operator<<(ostream& _outputStream, MetricAggregatorChoice _aggregator) { return serializeChoice(_outputStream, _aggregator, MetricAggregatorChoiceToStringMap); }
|
||||||
|
istream& phaser::operator>>(istream& _inputStream, CrossoverChoice& _crossover) { return deserializeChoice(_inputStream, _crossover, StringToCrossoverChoiceMap); }
|
||||||
|
ostream& phaser::operator<<(ostream& _outputStream, CrossoverChoice _crossover) { return serializeChoice(_outputStream, _crossover, CrossoverChoiceToStringMap); }
|
||||||
|
|
||||||
GeneticAlgorithmFactory::Options GeneticAlgorithmFactory::Options::fromCommandLine(po::variables_map const& _arguments)
|
GeneticAlgorithmFactory::Options GeneticAlgorithmFactory::Options::fromCommandLine(po::variables_map const& _arguments)
|
||||||
{
|
{
|
||||||
@ -95,6 +105,8 @@ GeneticAlgorithmFactory::Options GeneticAlgorithmFactory::Options::fromCommandLi
|
|||||||
_arguments["algorithm"].as<Algorithm>(),
|
_arguments["algorithm"].as<Algorithm>(),
|
||||||
_arguments["min-chromosome-length"].as<size_t>(),
|
_arguments["min-chromosome-length"].as<size_t>(),
|
||||||
_arguments["max-chromosome-length"].as<size_t>(),
|
_arguments["max-chromosome-length"].as<size_t>(),
|
||||||
|
_arguments["crossover"].as<CrossoverChoice>(),
|
||||||
|
_arguments["uniform-crossover-swap-chance"].as<double>(),
|
||||||
_arguments.count("random-elite-pool-size") > 0 ?
|
_arguments.count("random-elite-pool-size") > 0 ?
|
||||||
_arguments["random-elite-pool-size"].as<double>() :
|
_arguments["random-elite-pool-size"].as<double>() :
|
||||||
optional<double>{},
|
optional<double>{},
|
||||||
@ -155,6 +167,8 @@ unique_ptr<GeneticAlgorithm> GeneticAlgorithmFactory::build(
|
|||||||
/* deletionVsAdditionChance = */ _options.gewepDeletionVsAdditionChance,
|
/* deletionVsAdditionChance = */ _options.gewepDeletionVsAdditionChance,
|
||||||
/* percentGenesToRandomise = */ percentGenesToRandomise,
|
/* percentGenesToRandomise = */ percentGenesToRandomise,
|
||||||
/* percentGenesToAddOrDelete = */ percentGenesToAddOrDelete,
|
/* percentGenesToAddOrDelete = */ percentGenesToAddOrDelete,
|
||||||
|
/* crossover = */ _options.crossover,
|
||||||
|
/* uniformCrossoverSwapChance = */ _options.uniformCrossoverSwapChance,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
case Algorithm::Classic:
|
case Algorithm::Classic:
|
||||||
@ -165,6 +179,8 @@ unique_ptr<GeneticAlgorithm> GeneticAlgorithmFactory::build(
|
|||||||
/* mutationChance = */ _options.classicMutationChance,
|
/* mutationChance = */ _options.classicMutationChance,
|
||||||
/* deletionChance = */ _options.classicDeletionChance,
|
/* deletionChance = */ _options.classicDeletionChance,
|
||||||
/* additionChance = */ _options.classicAdditionChance,
|
/* additionChance = */ _options.classicAdditionChance,
|
||||||
|
/* crossover = */ _options.crossover,
|
||||||
|
/* uniformCrossoverSwapChance = */ _options.uniformCrossoverSwapChance,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@ -451,6 +467,16 @@ Phaser::CommandLineDescription Phaser::buildCommandLineDescription()
|
|||||||
po::value<size_t>()->value_name("<NUM>")->default_value(30),
|
po::value<size_t>()->value_name("<NUM>")->default_value(30),
|
||||||
"Maximum length of randomly generated chromosomes."
|
"Maximum length of randomly generated chromosomes."
|
||||||
)
|
)
|
||||||
|
(
|
||||||
|
"crossover",
|
||||||
|
po::value<CrossoverChoice>()->value_name("<NAME>")->default_value(CrossoverChoice::SinglePoint),
|
||||||
|
"Type of the crossover operator to use."
|
||||||
|
)
|
||||||
|
(
|
||||||
|
"uniform-crossover-swap-chance",
|
||||||
|
po::value<double>()->value_name("<PROBABILITY>")->default_value(0.5),
|
||||||
|
"Chance of two genes being swapped between chromosomes in uniform crossover."
|
||||||
|
)
|
||||||
;
|
;
|
||||||
keywordDescription.add(algorithmDescription);
|
keywordDescription.add(algorithmDescription);
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <tools/yulPhaser/AlgorithmRunner.h>
|
#include <tools/yulPhaser/AlgorithmRunner.h>
|
||||||
|
#include <tools/yulPhaser/GeneticAlgorithms.h>
|
||||||
|
|
||||||
#include <boost/program_options.hpp>
|
#include <boost/program_options.hpp>
|
||||||
|
|
||||||
@ -83,6 +84,8 @@ std::istream& operator>>(std::istream& _inputStream, solidity::phaser::MetricCho
|
|||||||
std::ostream& operator<<(std::ostream& _outputStream, solidity::phaser::MetricChoice _metric);
|
std::ostream& operator<<(std::ostream& _outputStream, solidity::phaser::MetricChoice _metric);
|
||||||
std::istream& operator>>(std::istream& _inputStream, solidity::phaser::MetricAggregatorChoice& _aggregator);
|
std::istream& operator>>(std::istream& _inputStream, solidity::phaser::MetricAggregatorChoice& _aggregator);
|
||||||
std::ostream& operator<<(std::ostream& _outputStream, solidity::phaser::MetricAggregatorChoice _aggregator);
|
std::ostream& operator<<(std::ostream& _outputStream, solidity::phaser::MetricAggregatorChoice _aggregator);
|
||||||
|
std::istream& operator>>(std::istream& _inputStream, solidity::phaser::CrossoverChoice& _crossover);
|
||||||
|
std::ostream& operator<<(std::ostream& _outputStream, solidity::phaser::CrossoverChoice _crossover);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds and validates instances of @a GeneticAlgorithm and its derived classes.
|
* Builds and validates instances of @a GeneticAlgorithm and its derived classes.
|
||||||
@ -95,13 +98,18 @@ public:
|
|||||||
Algorithm algorithm;
|
Algorithm algorithm;
|
||||||
size_t minChromosomeLength;
|
size_t minChromosomeLength;
|
||||||
size_t maxChromosomeLength;
|
size_t maxChromosomeLength;
|
||||||
|
CrossoverChoice crossover;
|
||||||
|
double uniformCrossoverSwapChance;
|
||||||
|
|
||||||
std::optional<double> randomElitePoolSize;
|
std::optional<double> randomElitePoolSize;
|
||||||
|
|
||||||
double gewepMutationPoolSize;
|
double gewepMutationPoolSize;
|
||||||
double gewepCrossoverPoolSize;
|
double gewepCrossoverPoolSize;
|
||||||
double gewepRandomisationChance;
|
double gewepRandomisationChance;
|
||||||
double gewepDeletionVsAdditionChance;
|
double gewepDeletionVsAdditionChance;
|
||||||
std::optional<double> gewepGenesToRandomise;
|
std::optional<double> gewepGenesToRandomise;
|
||||||
std::optional<double> gewepGenesToAddOrDelete;
|
std::optional<double> gewepGenesToAddOrDelete;
|
||||||
|
|
||||||
double classicElitePoolSize;
|
double classicElitePoolSize;
|
||||||
double classicCrossoverChance;
|
double classicCrossoverChance;
|
||||||
double classicMutationChance;
|
double classicMutationChance;
|
||||||
|
Loading…
Reference in New Issue
Block a user