All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog and this project adheres to Semantic Versioning.
- Cooperative abort flag to cancel a running strategy early.
with_abort_flag(Arc<AtomicBool>)onEvolve,HillClimb,Permutateand the supersetStrategyBuilder; the run checks it cooperatively and returns the best chromosome so far, also short-circuiting thecall_repeatedly/call_speciatedmulti-run calls - Add
with_target_fitness_scoreending condition toPermutate, stopping before the full permutation space is exhausted. Added mainly for internal "finished = conclusive or exhausted" structure
- Update lru dependency from v0.12.4 to v0.16.3
- Fix bug in UniqueGenotype:
neighbouring_population_sizepanics whengenes_size < 2 - Manually review
AGENTS.mdas AI generation of the document made too many assumptions
- Compile-time crossover safety. Split
EvolveGenotypeinto an empty marker trait + separateSupportsGeneCrossoverandSupportsPointCrossovertraits. Removed runtimehas_crossover_indexes/has_crossover_pointsmethods - Framework-owned age lifecycle. Moved
state.population.increment_age()from crossover into the evolve loop (just before crossover call). - Sensible defaults:
target_population_sizefrom 0 to 100 forEvolve,replace_on_equal_fitnessto true for all - Auto-disable
genes_hashinginHillClimbas it is never sensible
- Added
fitness_value(float, precision)helper method to convert float to isize (FitnessValue) - Better validations and error messages
- Documentation additions for AI agent (and human) consumption:
- Added AGENTS.md as a comprehensive agent guide (decision matrices, API reference, copy-paste templates, gotchas)
- Added docstrings to all previously undocumented public constructors and key methods
- Fixed several inaccurate docstrings (extensions, crossover, mutate, select)
- Improved error messages with actionable fix suggestions
- Add example headers
- Add a new Heterogeneous Genotype example (
examples/evolve_heterogeneous)
Caution
Custom Crossover implementations need to be updated to increment the age of
chromosomes in their implementations of call() (e.g.
state.population.increment_age())
- Add multiple extension points to the
Evolveflow in theExtensiontypeafter_selection_complete()after_crossover_complete()after_mutation_complete()after_generation_complete()
- Add symmetrical reporter hooks, called before the extension hooks
on_selection_complete()(existing)on_crossover_complete()on_mutation_complete()on_generation_complete()(existing)
- Add
before()andafter()hooks toSelect,Crossover, andMutatetraits- Allows full customization of population lifecycle operations in Evolve
- Default implementations handle age filtering and cardinality updates
- Extension trait methods now receive
&mut Self::Genotypeinstead of&Self::Genotype- Allows extensions to modify genotype parameters during evolution
- Extension trait
call()method is nowafter_selection_complete()call()still exists and delegates toafter_selection_complete()for backward compatibility
- Move population lifecycle operations from inline
EvolveStatemethods to trait hooks- Age filtering moved to
Select::before() - Population cardinality update moved to
Select::after() - Age increment handled internally by crossover implementations
- Age filtering moved to
- Fix github build dependencies, as
v0.25.0was marked as failed to build due to missinglibfontconfig1dependency for plotter. No changes to code.
- (naming only) Refactor
MutationType::ScaledSteps(Vec<T>)toMutationType::StepScaled(Vec<T>)for naming consistency - (naming only) Refactor
MutationType::RelativeRange(T)toMutationType::Range(T), asMutationType::Step(T)is also relative. - Refactor
MutationType::Transition(usize, usize, T)toMutationType::RangeScaled(Vec<T>):- Drop the generation based transition configuration params (as it couldn't handle staleness)
- Replace params with bandwidth per scale (just like step-size per scale in
MutationType::StepScaled) - Use bandwidths equal to the full
allele_rangeto achieve effectiveMutationType::Randommutation - Now
max_generationsandmax_stale_generationshandle scale transitions in the same manner everywhere - It is a good idea to use a prolonged period of full Random sampling (exploration phase, so several 100% bandwidth scales), which then transitions quite fast to the smaller bandwidths (exploitation phase, a few smaller bandwidth scales).
- Add
MutationType::Step(T)for completeness - Add
max_generationsas scale trigger as well inEvolveandHillClimb. Nowmax_generationsandmax_stale_generationsbehave the same:- Ending condition for current scale, go to next scale and restart counting generations
- Final ending condition if no scales left (or no scaling at all)
- Both
max_generationsandmax_stale_generationscan be set at the same time, the first to reach the condition triggers and reset both
- Support unsigned integers for
MutationType::RangeandMutationType::Stepas well (and scaled versions) - Allow for zero bandwidth/step in
MutationType::RangeandMutationType::Step(and scaled versions), which lets the mutation die out (more for robustness than usefulness) - Add
examples/visualize_evolve_mutation_typesandexamples/visualize_permutate_mutation_types, which generate visualizations showing exploration patterns of different mutation strategies
- Remove
increment_generation()andreset_generation()fromGenotypeand remove hook in Strategies, as all scaled MutationTypes now work in the same manner - Remove unused
Vec<Chromosome<T>>intoPopulation<T>implementation
- Change
MutationType::Scaled(Vec<RangeInclusive<T>)toMutationType::ScaledSteps(Vec<T>)- removes the range as the steps are symmetrical by definition: -T, +T (also less error prone)
- removes potential confusion about inner range samping v. step up/down behaviour by explicit naming
- Change
MutationType::Relative(RangeInclusive<T>)toMutationType::RelativeRange(T)- removes the range as it is symmetrical by definition, only the bandwidth is needed to make a [-T,T] range (also less error prone)
- removes potential confusion about inner range samping v. step up/down behaviour by explicit naming
- Change
MutationType::Transition(usize, usize, RangeInclusive<T>)toMutationType::Transition(usize, usize, T)- follow the
MutationType::RelativeRangeparameters
- follow the
Transition(random_until_generation, relative_from_generation, relative_mutation_range)
Smoothly transitions from Random to Relative mutation over a specified generation range.
Provides a natural exploration-to-exploitation schedule without abrupt behavioral changes.
Parameters:
random_until_generation: Use pure Random mutation until this generationrelative_from_generation: Use pure Relative mutation from this generation onrelative_mutation_range: The final relative range to use after transition
Example: Transition(100, 500, -5.0..=5.0) means:
- Generations 0-99: Pure Random mutation (full exploration)
- Generations 100-499: Gradual transition with decreasing mutation range
- Generations 500+: Pure Relative mutation with ±5.0 range (exploitation)
Use case: Problems requiring initial exploration followed by convergence, evolutionary algorithms with scheduled exploration decay, parameter optimization where good regions are unknown initially.
- Implement
MutationType::TransitionforRangeGenotypeandMultiRangeGenotype - Add associated type
GenotypetoExtensionas well for better custom implementations (just likeMutate&Crossover) - Add associated type
GenotypetoSelectas well for symmetry reasons (it was the only one without associated type left) - Add
increment_generation()andreset_generation()toGenotypeand hook up in Strategies (for keeping track ofMutationType::Transitionprogess inside the genotype) - Add
SelectEvent(String)andCrossoverEvent(String)tuple struct and handlers to reporting (on_select_event&on_crossover_event)
- Add mutation details to
MutationTypeenum as tuple structs:- Relative now holds the mutation range for a gene:
MutationType::Relative(RangeInclusive<T> - Scaled now holds the mutation scales for a gene:
MutationType::Scaled(Vec<RangeInclusive<T>>) - Random and Discrete don't have details, so they stay the same
.with_mutation_type(s)replaces the deprecatedwith_allele_mutation_range(s)andwith_allele_mutation_scaled_range(s)builder steps, now able to hold all the mutation details by itself
- Relative now holds the mutation range for a gene:
- Remove
MutateEventandExtensionEventenums, all events are just plain texts wrapped in a singleMutateEvent(String)orExtensionEvent(String)tuple struct - Rename
Genotypecustom methodsample_alleletosample_gene_randomfor symmetry reasons withsample_gene_delta,even though technically it is not always a gene (no index) - Drop
u64,u128,usize,i64,i128&isizesupport forRangeAlleleand add more numeric Traits (Add,Sub,AddAssign,Into<f64>,SampleUniform) to allow for more arithmetic in the genotype - Rename
on_new_generationreporting event inEvolvetoon_selection_completeto clearly state intent. Rename otheron_new_generationreporting events toon_generation_complete(HillClimb,Permutateand new forEvolve). ForEvolvetheon_selection_completehook, is a more interesting point in the loop, as the population and cardinality are refreshed after selection. Theon_generation_completehook contains all the new mutations, most of which will be immediately selected out. This gives the false impression of good cardinality while there is actually little.
- Drop
Genotype'sgene_slicemethod as all genes are owned anyway
with_allele_mutation_range&with_allele_mutation_scaled_rangenow have deprecation warnings, as they are replaced bywith_mutation_typewith_allele_mutation_ranges&with_allele_mutation_scaled_rangesnow have deprecation warnings, as they are replaced bywith_mutation_types
MultiRangeGenotype now supports heterogeneous chromosomes that mix different
gene semantics (continuous values, discrete choices, booleans) within a single
numeric type T. See MultiRangeGenotype documentation for example.
Use .with_mutation_types(vec![...]) to specify behavior for each gene individually:
MutationType::Random- Samples uniformly from the full allele rangeMutationType::Relative- Mutates within a relative range around the current value (set for gene throughallele_mutation_ranges)MutationType::Scaled- Progressive refinement through multiple scale levels around the current value (set for gene throughallele_mutation_scaled_ranges)MutationType::Discrete- Rounded-to-integer values with uniform selection (likeListGenotype).- Mutations ignore current value - all rounded-to-integer in range equally likely
- Range
0.0..=4.0yields values: 0.0, 1.0, 2.0, 3.0, 4.0 (with equal probability) - Useful for encoding: enums (0.0..=4.0), booleans (0.0..=1.0), or discrete choices
- Neighbours and permutations include all integer values in the allele range
For backward compatibility, mutation type per gene is auto-detected from provided ranges in the following order (as it was before, implicitly):
- Has
allele_mutation_scaled_ranges→ scaled for all genes - Has
allele_mutation_ranges→ relative for all genes - Else → random for all genes
Explicit .with_mutation_types() overrides any auto-detected mutation range settings.
- Add optional
.with_mutation_types()to MultiRangeGenotype builder
- Move
.mutation_type()fromGenotypeTrait to individual genotype implementations, as it is no longer a single uniform value for all genotypes.
The primary goal was easier custom Mutate and Crossover implementations,
which would require a high level of Genotype unification and simplification.
This was mostly blocked by the GPU optimization premise provided by the
centralized DynamicMatrixGenotype and StaticMatrixGenotype with all their
added centralized complexity (ChromosomeManager and GenesPointer v. GenesOwner
etc...).
The first attempt was to split the library in two: centralized v. distributed, where the distributed track could become less genotype-heavy and more flexible. But it also happens the GPU zero-copy optimization premise was flawed as memory transfers are required regardless of pre-transfer layout. So on the end we just dropped the whole centralized approach and archived it in archive/centralized-gpu-experiment branch for later use, when zero-copy actually becomes viable.
Now the library is restructured to a simpler form, moving a lot of
responsibilities away from Genotype which was becoming too heavy and
centralized: All genes are now Vec<Allele> and owned by the Chromosome
(which now only has one implementation, no genotype specific variants anymore).
Chromosome recycling has been moved from the Genotype (ChromosomeManager)
to the Population, the enabling flag is on Genotype. So when making custom
implementations remember to use the population's new_chromosome(),
drop_chromomsome(), truncate(), truncate_external() &
extend_from_within() methods for the chromosomes. Or just disable the
recycling (no risk of errors).
However, Genotype unification still proved impossible - each type has
fundamentally different requirements. So the best route to allow for easier
custom Mutate and Crossover implementations, was to make them user-genotype
specific using an associated type Genotype on Mutate and Crossover traits
(following existing Fitness pattern). The Genotypes now also have some
implementation-specific helper methods to support custom implementations:
sample_allele(), sample_gene_delta(), sample_gene_index(),
sample_gene_indices().
Skip the associated type Genotype on Select and Extension for now, as
these mainly work with the chromosome metadata and are not genotype specific.
General usage by client has little impact, most is internal.
- Replace
BinaryChromosomewithChromosome<bool> - Remove all other
Chromosomestruct genotype specific prefixes (e.g.ListChromosome<..>->Chromosome<..>) - Use
genetic_algorithm::impl_allele!(CustomAllele);to implementAllelefor your CustomAllele
- Add associated type
GenotypetoMutateandCrossovertraits (following existingFitnesspattern). - Move chromosome recycling from
ChromosomeManagertoPopulation - Move genes hashing from the
GenotypetoChromosomemaking theAlleletrait responsible for the hashing implementation (withimpl_allele!macro for default implementation) - Moved
current_scale_indexfromStrategyStatetoGenotype. This is the only change addingGenotypelogic. The scaling is only implemented byRangeGenotypeandMultiRangeGenotype, so it felt more genotype specific. It does make theGenotypemutable again, which is an accepted tradeoff.
Caution
Remember to .reset_scale_index() when reusing your genotype with a custom
implementation of multiple runs! (the library's implementation for multiple
runs, e.g. call_repeatedly(), clones the builder and then casts it to the
specific strategy, so each run starts with a cloned unused genotype)
- Remove matrix genotypes (
DynamicMatrixGenotype,StaticMatrixGenotype) - Remove
ChromosomeManagertrait - Remove
GenesOwnerandGenesPointertraits - Remove
BitGenotype- useBinaryGenotypewithVec<bool>instead - Remove
calculate_for_population()as the population-level fitness calculation user hook. All is nowcalculate_for_chromosome()which can now be compile time guarded
- Add
PermutableGenotypesupport for scaledRangeGenotypeand scaledMultiRangeGenotype.This approach implements a increasingly localized grid search with increasing precision using theallele_mutation_scaled_range(s)to define the search scope and grid steps- First scale (index = 0) traverses the whole
allele_range(s)with the upper bound of the first scale as step size. - Other scales (index > 0) center around the best chromosome of the previous scale, traversing the previous scale bounds around the best chromosome with the upper bound of the current scale as step size.
- Scale down and repeat after grid is fully traversed
- Note: Not implemented for relative or random mutation types
- Note: Not implemented for
DynamicMatrixGenotypeandStaticMatrixGenotype, as matrix has no use in permutation - Note: Scales should be symmetrical around zero, as always, but good to remember
- Note: As an added benefit the generic
StrategyBuilderwith deferred specialization can now also be used forRangeGenotypeandMultiRangeGenotype
- First scale (index = 0) traverses the whole
- Keep track of
scale_generationnext tocurrent_generationinStrategyState, resets every scale increment, no use yet - Add
mutation_type_allows_permutation()guard onGenotypeand check inPermutatestrategy builder
- Add
chromosomeandscale_indexparameters toPermutateGenotypechromosome_permutations_into_iter()function, ignore for all existing implementations, used byRangeGenotypeandMultiRangeGenotype
- The
replacement_ratein selection was not working, as the age was incremented before selection leading to only parents and no offspring during selection. Effectively the whole population was selected against without partitioninig. Fix by moving the age increment from the start of the new evolve generation (before selection) to just before crossover (after selection and after extension, as extension is form of selection where offspring information might still be relevant as well). - For
GenotypeMultiRange, remove the weighted probability of mutating a gene, depending on its allele_range size. The assumption of weighted mutation probability holds forGenotypeMultiListandGenotypeMultiUnique, in order to avoid over mutating the smaller sets with regard to the larger sets. But it does not forGenotypeMultiRange. We cannot make the assumption that the range size says anything about the search space size. Now probability of mutating a gene is uniform, regardless of its allele_range size inGenotypeMultiRange.
MutateMultiGeneandMutateMultiGeneDynamicnow avoid mutating the same genes twiceMutateMultiGeneandMutateMultiGeneDynamicnow mutate the exactnumber_of_mutationstimes if mutation is occuring
- Add
MutateMultiGeneRangeto provide the previousMutateMultiGenebehaviour. This samples thenumber_of_mutationsfrom the providednumber_of_mutations_range. And it allows for duplicate mutations of the same gene (as it is less strict anyway) - Add parents and offspring size to
on_new_generationreporting ofEvolveReporterSimple
- Initialize first
HillClimbSteepestAscentpopulation with theseed_genes_list, to ensure the best seed is taken as the starting point when seeds are provided.
- Add
max_generationsending condition onEvolveandHillClimbstrategies (also blocked by optionalvalid_fitness_score,asmax_stale_generationsis) - Add
ExtensionMassDeduplicationwhich requireswith_genes_hashing(true)on theGenotype(otherwise ignored) - Add
Populationunique_chromosome_indices()andbest_unique_chromosome_indices()support functions
- Nuance
ExtensionMassGenesistrying to use unique Adam en Eve Chromosomes when genes_hashes are availble, otherwise just take 2 best chromosomes (possibly duplicates) - Decided not to use unique best chromosomes for the elitism_rate in
ExtensionMassDegenerationandExtensionMassExtinction. Just use the best chromosomes (possibly duplicates), as uniqueness hard-limits the amount of selected elites in the typical low cardinality situation, which seems unwanted behaviour
- Add
elitism_ratetoExtensionMassExtinctionandExtensionMassDegeneration. Theelitism_rateensures the passing of the best chromosomes before extinction or degeneration is applied
- Fix
best_chromosome_indices()onPopulationin case where there are no fitness values other than None
- Completely redo
Selectionin order to align with general genetic-algorithm terminology:replacement_rate: the target fraction of the population which exists of children. Generational Replacement and Steady-State Replacement can both be modelled with this parameter by setting it respectively to 1.0 and 0.2-0.8. High values converge faster, but risk losing good solutions. Low values convergence slower. If there is a shortage of population after the ideal fraction, firstly remaining non-selected children and secondly remaining non-selected parents will be used to fill the shortage to avoid population collapse.elitism_rate: a non-generational elite gate, which ensures passing of the best chromosomes before selection and replacement takes place. Value should typically be very low, between 0.01 and 0.05. Relevant forSelectTournamentwhere the best chromosome is not guaranteed to be selected for a tournament if thepopulation_sizeis larger than thetarget_population_size
- Completely redo
Crossoverin order to align with general genetic-algorithm terminology:selection_rate: the fraction of parents which are selected for reproduction. This selection adds offspring to the population, the other parents do not. The population now grows by the added offspring, as the parents are not replaced yet. Value should typically be between 0.4 and 0.8. High values risk of premature convergence. Low values reduce diversity if overused.crossover_rate(or recombination-rate): the fraction of selected parents to crossover, the remaining parents just clone as offspring. Value should typically be between 0.5 and 0.8. High values converge faster, but risk losing good solutions. Low values have poor exploration and risk of premature convergence
- Note that
max_chromosome_ageis implemented at theEvolveBuilderlevel, it will remove old chromosomes in the evolve loop before selection. Theelitism_ratewill not save them. - Note that
population_sizenow grows with added offspring during crossover and no longer replaces parents in-place with children. This means theStaticMatrixGenotypeneeds to reserve extra population space beyond thetarget_population_size - Note that in previous releases all parents had an in-place crossover. The behaviour from previous releases can be achieved by setting the replacement_rate to 1.0 (drop parents), the selection_rate to 1.0 (all crossover) and the crossover_rate to 1.0 (all crossover). The new release is a bit slower as in-place crossover was faster than keeping the parents around and dropping them during selection
- Rename and refactor some internal
Chromosomerecycling methods
- Add
best_chromosome_indices()onPopulation, used to implementelitism_ratewithout the need for a full sort - Add
CrossoverRejuvenateas new limited cloning implementation for the oldCrossoverClonebehaviour, which had limited cloning. The newCrossoverClonenow adds actual clones as offspring without removing the parents, which is much more cloning than the original. ForCrossoverRejuvenate: drop non-selected parents, then clone top parents to repopulate, then rejuvenate selected parents to children in place. No copying of chromosomes for creating the offspring itself, only for repopulating the dropped non-selected parents (smaller fraction). However the cloning of top-parents is crucial for driving the Evolve process as experimenting with the evolve_nqueens example demonstrated.
- Remove
reference_id: usizefromChromosomeas user controlled alternative to genes_hash, as genes_hash is now formally supported for all types.
- Add
with_fitness_cache(size)builder step to theHillClimbBuideras well. There are some use cases with long tails andreplace_on_equal_fitness(true)where caching information is useful. - Add
FitnessGenes<Self>type alias for better fitness API (next toFitnessChromosome<Self>etc.)
- Forgot to
calculate_genes_hash()afterchromosome_constructor_genes(), refactor a bunch to ensure this never happens again.
- Cycle through the
seed_genes_listto fill the initial population forEvolvestrategy (instead of random sampling from the seed genes). This is done to ensure all seed genes reach the initial population (if thetarget_population_sizeis larger than theseed_genes_list). TheHillClimbstrategy still samples a single random starting seed gene as the starting point for each run (not cycling through them in repeated runs)
- Silently ignore zero cache size in
with_fitness_cache(size), to support superset builder strategies where cache size is derived from unset parameters (defaulting to zero)
- Add
with_fitness_cache(size)builder step to theEvolveBuilder. When applying this step, a thread-safeFitnessCacheobject is stored on theEvolveConfigwhich manages anArc-wrapped LRU cache of fitness values for genes.- Note that caching only works when the
genes_hashis stored on chromosome as well (through thewith_genes_hashing()builder step), as this is the cache key. - Note the
FitnessCacheis stored onEvolveConfig, notEvolveState, as the cache is external to the strategy (and reused over multiple repeated runs). - Note that caching is only useful for long stale runs, but it is better to
avoid those in general. This makes the cache hit/miss reported in the
EvolveReporterSimplemore of a hint where the hyperparameters should be adjusted to increase population diversity. I don't think caching is the proper solution to ovelry revisiting the same genes. Keeping the feature for now though, as the hint is valuable in itself. - Note that +0.0 and -0.0 hash differently on floats when using
with_genes_hashing(). - Decided not to support the fitness cache in the
PermutateandHillClimbstrategies as these should (almost) never revisit the same genes anyway.
- Note that caching only works when the
- Update pprof to v0.14.0 due to security issue on v0.13.0
- Change some internal
Fitnesstrait method parameters as theStrategyConfigandFitnessCacheneed to be passed around.
- Use
rustc_hash::FxHasherinstead ofDefaultHasherfor 2x-5x faster genes hashing
- Option to store
genes_hashonChromosomeby settingwith_genes_hashing(true)on theGenotype.This can be used for better population cardinality estimation (with respect to the default fitness score based estimate), but has a relatively high overhead on the mainEvolveloop (mostly noticable inCrossoverduration). - Store
population_cardinalityonEvolveState, usinggenes_hashcardinality if set, otherwise fallback to fitness score cardinality. Update thepopulation_cardinalityjust after selection in theEvolveloop and use for rest of the generation.Crossoveronly extends existing cardinality if present.Mutationalways adds cardinality, but might also all be dropped in selection again. So using the cardinality at the beginning of theEvolveloop is a good enough estimate
- No longer count
Nonesas unique fitness score cardinality. Instead makefitness_score_cardinality()returnNoneof no values can be used to estimating the cardinality (ensuring we keep avoiding the immediateExtensiontrigger at the start of the iteration) - Make
MutateSingleGeneDynamic,MutateMultiGeneDynamic,ExtensionMassGenesis,ExtensionMassExtinctionandExtensionMassDegenerationuseEvolveStatepopulation_cardinalityas guard check
- Remove old
GenesKeytype andgenes_key()function, replaced byGenesHash - Disallow
f32andf64asAlleleforListandUniquegenotypes by requiring theAlleleto implementHashfor thoseGenotypes(including tuple Alleles). Now we can use standard Hashing for normal Alleles and usebytemuckHashing forRangeAllele. This allows for easier implementation of genes based cardinality
- Make
MutateMultiGeneandMutateMultiGeneDynamicmutate up to the providednumber_of_mutations(instead of always the exact amount of mutations)
- Add
on_exit()event toStrategyReporter - Add
StrategyVariantinfo inon_enter()andon_exit()events - Add
fitness_duration_rate()toStrategyStateand use inStrategyReporter
- Permutation with
seed_genes_listnow only permutates over the seeded genes (useful to calculate only a specific predefined set) - Add cleanup step in strategies. As the repeated/speciated calls keep the runs around, it seems better to cleanup the population and genotype storage
- Rename
StrategyAction::InittoStrategyAction::SetupAndCleanup - Rename
on_init()event toon_enter()inStrategyReporter - Move duration reporting to
on_exit()in reporters to include the cleanup duration
- Add a buffered option to all Reporters (through
new_with_buffer())- When the buffer is off (default), the reporter will print to stdout
- When the buffer is on, the reporter will print to the internal buffer,
which can be read out through
flush_reporter()on the strategy later
- Change
call_repeatedly(),call_par_repeatedly(),call_speciated()andcall_par_speciated()to return a tuple(best_run, other_runs). This way the reporter buffers of the other runs can be read out as well afterwards
- Drop
StrategyReporterBuffer, as all reporters now have a buffered option
- Add
StrategyReporterBufferimplementation with an internal buffer instead of stdout - Add
flush_reporter()inStrategyas the strategy can be boxed and we need a way to get to the buffer of theReporter
- Implement
StrategyReportertrait- It was held off in previous releases, because of the generics in the API,
but it is needed for the superset
StrategyBuilder - Provide
StrategyReporterNoop,StrategyReporterDurationandStrategyReporterSimplereporters, usable by all strategies (but less informative) - Re-export
StrategyReporterNoopasEvolveReporterNoop,HillClimbReporterNoopandPermutateReporterNoop - Re-export
StrategyReporterDurationasEvolveReporterDuration,HillClimbReporterDurationandPermutateReporterDuration - Re-implement
EvolveReporterSimple,HillClimbReporterSimple,PermutateReporterSimpleas strategy specialized versions
- It was held off in previous releases, because of the generics in the API,
but it is needed for the superset
- Implement
EvolveGenotypefor all genotypes (as it was implicit) and move crossover functions over fromGenotype - Rename
IncrementalGenotypetoHillClimbGenotype - Rename
PermutableGenotypetoPermutateGenotype - Move
Extensionstep to follow afterSelectstep, so thefitness_score_cardinalityof the selected population can be taken as a trigger - Move reporter event
on_new_generationto after selection forEvolve, as it is a more informative location in the loop
- Add
StrategyBuilder,a superset builder which delays specialization toEvolve,HillClimborPermutate. Only usable by genotypes that implement all strategies (i.e. not forRangeGenotypeand friends) - Add
call(),call_repeatedly(),call_par_repeatedly(),call_speciated()andcall_par_speciated()toStrategyBuilder(fallback to equivalent of not available on specialized strategy) - Add
EvolveVariant(Standard only) andPermutateVariant(Standard only) for symmetry next toHillClimbVariant - Implement
mutation_type()for all genotypes. Use it to trigger scaling logic inEvolveandHillClimb. - Implement
MutationType::Randominfill_neighbouring_population()forRangeGenotype,MultiRangeGenotype,DynamicMatrixGenotypeandStaticMatrixGenotype. It is not advised to use inHillClimbcontext, but a panic is worse.
- Drop
EvolveReporterLog,HillClimbReporterLogandPermutateReporterLog
- In order to support future GPU acceleration, the possibilty for the Genotype
to store the genes of the whole population in a single contiguous memory
location has been added. This has the following effects:
- The Genotype has to be mutable and passed along for most operations. Affects a lot of interal function paramters
- Chromosomes no longer always own their own genes, but can also just point
to a section of the genotype's data storage. New triats
GenesOwner(has genes) andGenesPointer(has row_id) for chromosomes - Each Genotype now has its own type of Chromosome, which implements the
Genotype's genes storage model. Genotypes get an associated Chromosome type,
all with their own alias. This leads to three core chromosome
implementations:
VectorChromosome, storesVec<Allele>in genes field (the original chromosome)BitChromosome, storesFixedBitSetin genes fieldRowChromosome, stores genotype data row in row_id fields
- Chromosomes can't just be created, cloned and dropped, as the genotype
needs to keep track of which sections of the data storage are in use.
Therefore Genotype now is the constructor and destructor of chromosomes.
Added
ChromosomeManagertrait to implement on the Genotoype. The strategies and plugins now need to properly call population reduction and regrow methods through this ChromosomeManager trait - The
Fitnessnow has two implementation points, the normalcalculate_for_chromosome(...)and a population levelcalculate_for_population(...). The latter is optional and can be used to calculate the genotype data strucure as a whole. - Because the chromosomes no longer always hold the genes, returning a
best_chromosome()as end result from the strategies is not really suitable anymore. The method still is available when using standard chromosomes, but the newbest_genes_and_fitness_score()is the preferred method as it works for all chromosome types. - The best_genes are now stored on the Genotype, instead of in a chromosome clone on the StrategyState. The latter would require the Genotype internal storage to keep a row reserved for the best_chromosome clone, which isn't nice.
- Because the Genotype now constructs and destructs chromosomes, this feature can be leveraged to recycle chromosome allocations for all Genotypes. This leads to an overal performance improvement, especially noticable for large genes sizes and low survival rates.
- The
calculate_for_chromosome(...)client implementation now gets a reference toGenotype, which can subsequently be ignored for standard use. - The
EvolveReporter,HillClimbReporterandPermutateReportertraits now also get a reference toGenotypeon all functions, for the same reason - Add
FitnessGenotype<Self>,FitnessChromosome<Self>&FitnessPopulation<Self>type aliases for better fitness API
- Add
DynamicMatrixGenotype, storing the population's genes in a single contiguous memoryVec<Allele>on the heap. All other features are likeRangeGenotype. - Add
StaticMatrixGenotype, storing the population's genes with a single contiguous memoryBox<[[T; N]; M]>on the heap. All other features are likeRangeGenotype.Nis the genes size andMis the population size:- For
Evolve,Mwould be thetarget_population_size - For
HillClimbVariant::SteepesAscent,Mwould be theneighbouring_population_size
- For
- Add
calculate_for_population(...)client implementation option inFitness, only usable for the new matrix genotypes above. - Add
best_genes_and_fitness_score()function on Evolve, HillClimb and Permutate. Prefer this use overbest_chromosome(). - Add utility methods
genes_slice(chromosome)andbest_genes_slice()toGenotype, returning the genes as a slice for all chromosome types (GenesOwnedorGenesPointer) - Add
FitnessplaceholderSumDynamicMatrixforDynamicMatrixGenotype(with optional precision) - Add
FitnessplaceholderSumStaticMatrixforStaticMatrixGenotype(with optional precision) - Add
EvolveReporterDuration,HillClimbReporterDurationandPermutateReporterDurationto report only on the duration of the different phases of the strategies
- Drop
HillClimbVariant::StochasticSecondaryandHillClimbVariant::SteepestAscentSecondaryascall_repeatedly(...)on the basic variant is much more efficient - Drop
CrossoverParMultiPointas it conflicts with storage owningGenotypes. You would have to provide a mutable Genotype in a Mutex, which is not worth the effort
- Rename
CompetetoSelect - Redo
Select/Crossoverselection & survival rates:- Remove
Crossoverparent_survival_rateand addSelectselection_rate Selectnow reduces the population andCrossoverrestores the population with the best parents after creating new offspring- Simulate the old behaviour of keeping all the parents by setting the
selection_rate = 0.5and doubling the target_population_size
- Remove
- Implement
Allelefor()and setBitGenotype::Allele = ()as it is not used
- Drop
BinaryAlleletype alias forbool. It is the only of it's kind and never used
- Fix
SelectElitesorting (should be best first, was best last). Effect was that the best part of population was dropped instead of kept, resulting in slow to no solution inEvolve
- Fix
CompeteElitesorting (should be best first, was best last). Effect was that the best part of population was dropped instead of kept, resulting in slow to no solution inEvolve
This is a major breaking release (back to pre-v0.9.0 API), see Changed:
- Add formal
Genestrait:Genes: Clone + Send + Sync + Debug - Change associated type from
AlleletoGenotypefor:Fitness,EvolveReporter,HillClimbReporterandPermutateReporter - Change generic type
AlleletoGenotypefor:Chromosome,Populationand other structs/functions using these types - Store
Genotype::Genesinstead ofVec<Genotype::Allele>in theChromosomegenesfield
- Allow for non-
Vecbased genes inGenotype.Most existingGenotypeimplementations useVec<Allele>as genes, but now alternatives are possible - Add
BitGenotypeusingFixedBitSetfor genes storage. Functionally the same asBinaryGenotype,but better for large genes sizes as storage is much more efficient thanVec<bool>. - Add
FitnessplaceholderCountOnesforBitGenotype
- Fix
CompeteElitesorting (should be best first, was best last). Effect was that the best part of population was dropped instead of kept, resulting in slow to no solution inEvolve
- Change
Crossover'skeep_parentparameter toparent_survival_rate. This keeps a fraction of the top parents, instead of the previous all or nothing boolean. - Add duration tracking of interal actions for
Evolve,HillClimb&Permutate - Add duration tracking results in
EvolveReporterSimple,HillClimbReporterSimpleandPermutateReporterSimple - Remove
Genotypeparameter fromEvolveReporter,HillClimbReporterandPermutateReporteron_startevent, useon_initevent for reporting aboutGenotype
- Drop
ExtensionMassInvasionas reseeding the population conflicts with scaling. And reseeding can better be done bycall_repeatedlyorcall_speciatedanyway
- Remove yanked package warning for
bytemuck v1.16.1in Cargo.lock by updating all dependencies to latest versions
- Add performance considerations in documentation
- Add
CrossoverMultiGene,CrossoverMultiPointandCrossoverParMultiPoint - Add
FitnessplaceholdersCountdownandCountdownNoisy
- Move all crossover and mutation logic to
Genotype. The goal is to limit the knowledge of the internal genes structure to the Genotype only. Lots of internal changes, not relevant for API. - Improve
CrossoverSinglePointandCrossoverMultiPointperformance by avoiding cloning of genes - Reimplement
CrossoverUniformasCrossoverMultiGenewithnumber_of_crossovers = genes_size / 2andallow_duplicates = true, no API change
- Drop
CrossoverParUniformin favor ofCrossoverParMultiPointas a more useful example implementation, although parallel execution of crossovers have no performance benefits for most situations
- Add
with_rng_seed_from_u64_option()function toEvolveBuilderandHillClimbBuiderfor more flexible API
- Move
PartialEqrequirement from generalAlleletoListGenotypeandMultiListGenotypespecific allele requirements
- Implement
Alleletrait for tuple sizes 1 to 12, as 12 is the limit forPartialEqin tuples
- Remove
Defaultrequirement onAlleleforRangeGenotypeandMultiRangeGenotype(no longer used) - Remove
Zerorequirement onAlleleforRangeGenotypeandMultiRangeGenotype(no longer used)
- Add Copy to Allele trait, this drops support for String Alleles, but is fine for primitives, enum and structs
- Make the randomness provider internal to the API. You no longer need to provide it in the
call()methods - Add
with_rng_seed_from_u64()functions toEvolveBuilderandHillClimbBuiderfor reproducible runs (e.g. testing) - Align all multithreading approaches of
Evolve,HillClimb&Permutateusing rayon::iter and std::sync::mpsc - Distinguish between internal and external multithreading:
- Internal multithreading means: parallel execution within an
Evolve,HillClimborPermutaterun (mainlyFitnesscalculations) - External multithreading means: parallel execution of multiple independent
EvolveorHillClimbruns. - Note that
Permutateonly has internal multithreading as repeated calls make no sense - Note that internal and external multithreading can be combined
- Note that internal multithreading has been explored for
Compete,CrossoverandMutate. But the overhead of parallel execution was too high, generally resulting in degradation of performance. The breakeven point was found only for huge populations or genes_sizes, and only where each gene was part of the calculation (e.g.CrossoverUniform). SinceFitnessis a client implementation which could be very heavy depending on the domain, an explicitwith_par_fitness()is used for enabling internal multithreading of the fitness calculation only. Addingwith_par_crossover()and friends has been considered, but due to the little expected gain, separate implementations where possibly beneficial are added instead (e.g.CrossoverParUniform).
- Internal multithreading means: parallel execution within an
- Rename
with_multithreadingtowith_par_fitness(), as to properly reflect it's effects only in the fitness calculations (internal multithreading) - Require
Send + Syncto Compete, Crossover, Extension and Mutate - Change
chromosome_permutations_into_iter()return type fromBox<dyn Iterator>toimpl Iterator - Rename
with_multithreading()to explicitwith_par_fitness()for clarity of the effect
- Add
call_par_repeatedly()andcall_par_speciated()toEvolveBuilder(external multithreading) - Add
call_par_repeatedly()toHillClimbBuilder(external multithreading) - Add short-circuit for
call_speciated()andcall_par_speciated()when target_fitness_score is reached during speciation - Add
CountTrueWithSleepfitness placeholder for use in multithreading examples and benchmarking - Add
CrossoverParUniformfor a multithreaded implemenation ofCrossoverUniform - Add
reference_id: usizetoChromosomeas user controlled alternative togenes_key()for the GPU calculation use case described in issue 5
This is a major breaking release, see Changed:
- Add formal
Alleletrait:Allele: Clone + Send + Sync + PartialEq + Debug - Change associated type from
GenotypetoAllelefor:Fitness,EvolveReporter,HillClimbReporterandPermutateReporter - Change generic type
GenotypetoAllelefor:Chromosome,Populationand other structs/functions using these types - Rename
DiscreteGenotypetoListGenotype(incl. Multi) - Rename
ContinuousGenotypetoRangeGenotype(incl Multi) - Generalize
RangeGenotypefor numeric types (incl. Multi, default still f32, but other float and integer types are now supported) - Replace
RangewithRangeInclusivefor all ranges inRangeGenotypein order to handle integer ranges more intuitively (incl. Multi) - Change
FitnessplaceholdersSumContinuousAlleleandSumDiscreteAlleleto generalizedSumGenes(with optional precision) - Reimplement scaling completely now
RangeGenotypeis generalized.- Drop f32
Scalinglogic - Set
allele_mutation_scaled_rangeonRangeGenotypeto define scaling (incl. Multi) instead ofwith_scaling()inHillClimbbuild step - Mutation distance only on edges of current scale (e.g. -1 and +1 for -1..-1 scale)
- Scale down after
max_stale_generationsis reached and reset newstale_generationscounter to zero - Only trigger
max_stale_generationsending condition when already reached the smallest scale
- Drop f32
- How to mutate now fully controlled by
Genotypewith random, relative or scaled mutations options (relative and scaled only possible for RangeGenotype, incl. Multi)- Rename
MutateSingleGeneRandomtoMutateSingleGeneas it just callsmutate_chromosome()onGenotype - Rename
MutateSingleGeneRandomDynamictoMutateSingleGeneDynamicas it just callsmutate_chromosome()onGenotype - Rename
MutateMultiGeneRandomtoMutateMultiGeneas it just callsmutate_chromosome()onGenotype - Rename
MutateMultiGeneRandomDynamictoMutateMultiGeneDynamicas it just callsmutate_chromosome()onGenotype - Rename
allele_neighbour_rangetoallele_mutation_rangeinRangeGenotype(incl. Multi) to define relative mutation - Add
allele_mutation_scaled_rangetoRangeGenotype(incl. Multi) to define scaled mutation
- Rename
- All changes to
RangeGenotypeare reflected inMultiRangeGenotypeas well
- Allow relative mutations for
Evolveas well, as it is aGenotyperesponsibility now - Allow scaled mutations for
Evolveas well, as it is aGenotyperesponsibility now- Scale down after
max_stale_generationsis reached and resetstale_generationsto zero - Only trigger
max_stale_generationsending condition when already reached the smallest scale
- Scale down after
- Add
replace_on_equal_fitnessto builders to allow for lateral moves in search spaceEvolve: defaults to false, maybe useful to avoid repeatedly seeding with the same best chromosomes after mass extinction eventsHillClimb: defaults to true, crucial for some type of problems with discrete fitness steps like nqueensPermutate: defaults to false, makes no sense to use in this strategy
- Drop
MutateSingleGeneDistanceas random, relative or scaled mutations are now handled byGenotypeand not the caller
- Improve bootstrapped reporter outputs
EvolveReporterSimple,HillClimbReporterSimpleandPermutateReporterSimple - Implement
ExtensionNoopdefault forEvolveBuilderand remove now optionalwith_extension()steps in examples - Implement
EvolveReporterNoopdefault forEvolveBuilderand remove now optionalwith_reporter()steps in examples - Implement
HillClimbReporterNoopdefault forHillClimbBuilderand remove now optionalwith_reporter()steps in examples - Implement
PermutateReporterNoopdefault forPermutateBuilderand remove now optionalwith_reporter()steps in examples
- Align
EvolveReporter,HillClimbReporterandPermutateReportertraits to takeEvolveStateandEvolveConfigas parameters in further aligment withMutate,Compete,CrossoverandExtensiontraits. - Add
Synctrait everywhere whereSendtrait was required.
- Fix major issue where cardinality starts out as 0 as there are no fitness
calculations yet. This triggers the optional extension event, if set, at the
start of the evolve loop (killing seed population and diversity). Issue was
introduced in v0.8.0 with
fitness_score_cardinality(). Solve by adding None fitness counts to cardinality.
- Always implement
new()next todefault(). Usenew()in public API examples
- Rename
new()tonew_with_flags()for more verbose reporting inEvolveReporterSimple,HillClimbReporterSimpleandPermutateReporterSimple - Add simpler
new()to only takeperiod: usizeand set all flags to false (as this is the sensible less noisy default) inEvolveReporterSimple,HillClimbReporterSimpleandPermutateReporterSimple
- Add
PermutateConfigandPermutateStateto align structure withEvolveandHillClimb - Extract
StrategyConfigtrait and use forEvolveConfig,HillClimbConfigandPermutateConfig - Extract
StrategyStatetrait and use forEvolveState,HillClimbStateandPermutateState - Add pluggable
EvolveReportertoEvolvestrategy- Set in builder using
with_reporter() - Custom implementations by client are encouraged, the API resembles the Fitness API
- Add bootstrap implementations
EvolveReporterNoop,EvolveReporterSimpleandEvolveReporterLog
- Set in builder using
- Add pluggable
HillClimbReportertoHillClimbstrategy- Set in builder using
with_reporter() - Custom implementations by client are encouraged, the API resembles the Fitness API
- Add bootstrap implementations
HillClimbReporterNoop,HillClimbReporterSimpleandHillClimbReporterLog
- Set in builder using
- Add pluggable
PermutateReportertoPermutatestrategy- Set in builder using
with_reporter() - Custom implementations by client are encouraged, the API resembles the Fitness API
- Add bootstrap implementations
PermutateReporterNoop,PermutateReporterSimpleandPermutateReporterLog
- Set in builder using
- Add
fitness_score_cardinality()toPopulation - Add
MutateMultiGeneRandomDynamic(generalize to any number of mutations) - Add
MutateSingleGeneDistance(only forContinuousGenotype)
- Drop
fitness_score_uniformity()andfitness_score_prevalence()fromPopulation - Drop
MutateDynamicRounds
- Align
Mutate,Compete,CrossoverandExtensiontraits to takeEvolveState,EvolveConfig,EvolveReporteras parameters - Reimplement
MutateOnceasMutateSingleGeneRandom - Reimplement
MutateTwiceasMutateMultiGeneRandom(generalize to any number of mutations) - Reimplement
MutateDynamicOnceasMutateSingleGeneRandomDynamic(also fix InvalidProbabilty issue) - Replace
target_uniformitywithtarget_cardinalityinMutateSingleGeneRandomDynamicandMutateMultiGeneRandomDynamicas uniformity is ill defined - Replace
uniformity_thresholdwithcardinality_thresholdinExtensionimplementations, as uniformity is ill defined - Move permutation
total_population_sizefromPermutateConfigtoPermutateState, so progress can be reported on inPermutateReporterSimple - Move
env_loggerdependency to dev-dependencies as this crate is a library, not an executable
- Note that
HillClimbscaling needs review as it doesn't feel right in its design approach. Possibly align withMutateSingleGeneDistanceapproach? - Extract
StrategyReportertrait, but don't use because of error E0658: associated type defaults are unstable. So forEvolveReporter,HillClimbReporterandPermutateReporterthe trait is shadowed as if it is implemented
- Add
Wrappers instead ofDispatchers as they keep state, behaviour is the same usinginto()(e.g.MutateOnce::new(0.2).into())
- Extract Meta logic to separate crate genetic_algorithm_meta
- Phase out the
Dispatchers as they are replaced byWrappers
- MSRV bumped to 1.71.1
- Solve RUSTSEC-2021-0145
- Add
Mutateimplementations:MutateTwice, support some form of swap-like behaviour whereUniqueGenotypedoesn't match with the problem spaceMutateDynamicOnce, increase mutation probability when population uniformity is above threshold and vice versaMutateDynamicRounds, increase mutation rounds when population uniformity is above threshold and vice versa
- Add
HillClimbVariant::StochasticSecondaryandHillClimbVariant::SteepestAscentSecondaryas well for the same reasons asMutateTwice - Add
call_speciatednext to the existingcall_repeatedlyinEvolveBuilder. This runs multiple independent evolve strategies and then competes their best chromosomes as starting population against each other in one final evolve strategy - Add
Chromosomeage and optionalwith_max_chromosome_agetoEvolveBuilder. Filtering chromosomes past the maximum age from the next generation - Add
best_generation()andbest_fitness_score()toStrategy, so client implementation can report and switch more easily over different strategies. Return zero forPermutate::best_generation()as there is no concept of best generation there - Add
Extensionstep toEvolve, addingwith_extensiontoEvolveBuilder, with several implementations:ExtensionNoop, for no extensionExtensionMassExtinction, trigger mass extinction to allow for cambrian explosion (no competition for a while, which allows for more diversity)ExtensionMassGenesis, likeExtensionMassExtinction, but only a pair of best chromosomes (adam and eve) are taken as the start for the next generationsExtensionMassInvasion, likeExtensionMassExtinction, but replace extinct population with random population (respecting seed_genes if present)ExtensionMassDegeneration, simulate cambrian explosion by apply several rounds of uncontrolled mutation directly
- Add
Population::fitness_score_unformity()as measure for uniformity (fraction between 0 and 1). Use as triggers forMutateDynamic*andExtension - Add dispatch
FromtoEvolveplugins for use inMetaConfigBuilder, instead of manual wrapping (e.g.MutateOnce::new(0.2).into()instead ofMutateDispatch(MutateOnce::new(0.2))
- Refactor
Compete,CrossoverandMutatefrom tuple structs to struct, initialize with::new(), because the structs now have some mutable internal properties (e.g.MutateDynamicOnce). Make all plugins mutable for consistency - Split off internal config and state structs for
EvolveandHillClimb, leavePermutateuntouched weighing overkill v. symmetry different there - Split off internal plugins for
Evolve(i.e.Mutate/Crossover/Compete/Extension) - Change
seed_genestoseed_genes_listto allow for multiple seed genes taken randomly (used incall_speciated) - Only mutate children in the
Mutatestep, in earlier versions parents and children were mutated equally - Refactor
Evolvepopulation_sizeproperty totarget_population_size, thus also replacingwith_population_sizewithwith_target_population_size - Add
env_logger::init()to all examples, so theRUST_LOGenvironment variable works as expected - Change
HillClimbBuilder::with_scalingparameter from tuple to structScaling
- Phase out the
with_mass_degenerationinEvolveBuilderas it is replaced byExtensionMassDegeneration
- Calculate initial chromosome fitness in
HillClimbto lock in on original seed if present
- Remove
random_chromosome_probabilitytoHillClimbas it was hackish
- Add
valid_fitness_scoreto block ending conditions until met forEvolveandHillClimbstrategies
- Tweak TRACE logging
- Add env_logger and some INFO/DEBUG/TRACE logging
- Count generation zero based
- Solve lock-in to single best chromosome in stale
HillClimbVariant::SteepestAscentby shuffling chromosomes before taking best
- Add
IncrementalGenotypeTrait with neighbouring chromosome implementations - Implement
IncrementalGenotypefor allGenotypes - Add
allele_neighbour_rangetoContinuousGenotype - Add
allele_neighbour_rangesforMultiContinuousGenotype - Add
HillClimbVariant::StochasticandHillClimbVariant::SteepestAscent - Add
HillClimbscaling (forContinuousGenotype&MultiContinuousGenotype) to scale down neighbours on each round and use as ending condition - Add
random_chromosome_probabilitytoHillClimbto avoid local optima - Add multithreading to
Permutate(parallel processing of chromosome generator) - Add multithreading to
Evolve(fitness execution for population) - Add multithreading to
HillClimb(fitness execution forHillClimbVariant::SteepestAscentpopulation only) - Add
call_repeatedlyforEvolveBuilderandHillClimbBuilder - Add examples/evolve_milp.rs
- Add examples/evolve_scrabble.rs
- Add examples/hill_climb_scrabble.rs
- Add examples/hill_climb_milp.rs
- Add examples/permutate_scrabble.rs
- Require
IncrementalGenotypeforHillClimbstrategy - Refactor
allele_valuestoallele_list - Refactor
allele_multi_valuestoallele_lists - Refactor
allele_multi_rangetoallele_ranges - Add median/mean/stddev to
report_roundinEvolveandHillClimb - Add precision to
SumContinuousGenotypeandSumMultiContinuousGenotypeplaceholders for better handling of decimal changes on cast to isize
- Use SPDX license in Cargo.toml as the existing LICENSE file (MIT) was marked as non-standard by crates.io
- Add Apache 2.0 license
- Note degeneration_range use as case by case configuration
- Add
Strategytrait and implement forEvolveandPermutate - Add
HillClimbstrategy for when crossover is impossible or inefficient - Add
MultiUniqueGenotype - Add table_seating example (hill_climb and evolve)
- Move
Evolve&Permutatetostrategymodule - Remove
Genotype::is_uniqueandCrossover::allow_unique_genotypemethods- Replace with
Genotype::crossover_indexesandCrossover::require_crossover_indexes - Replace with
Genotype::crossover_pointsandCrossover::require_crossover_points
- Replace with
- Rename
UniqueDiscreteGenotypetoUniqueGenotypeas it is discrete by definition - Rename
PermutableGenotype::allele_valuestoPermutableGenotype::allele_values_for_chromosome_permutationsfor clarity of purpose - Hide
EvolveandPermutateinternal fields (to align withStrategytrait)
- forgot to update version in
Cargo.toml
- Make proper distinction between gene and allele as in the book "Genetic Algorithms in Elixir"
- Add option to
call()fromEvolveBuilder&PermutateBuilderdirectly - Add
Fitness::Zeroplaceholder
- Refactor
Evolve&Permutatetocall(&mut self, ...) - Refactor
Fitness,Crossover,Mutate&Competeto take mutable population reference - Improve performance in
Crossoverwhen not keeping parents - Rename
gene_value*toallele_value* - Rename
gene_rangestoallele_multi_rangefor symmetry reasons withallele_multi_values - Rename
gene_sizetogenes_sizeas it is not the size of a gene - Rename
CrossoverSingletoCrossoverSingleGene - Rename
CrossoverRangetoCrossoverSinglePoint - Rename
CrossoverAlltoCrossoverUniform
- Drop SetGenotype as it is always better implemented using BinaryGenotype
- Cleanup examples
- Add
SetGenotype<T>, withexamples/evolve_knapsack_set.rsandexamples/permutate_knapsack_set.rs
- Refactor fitness placeholders to placeholders module to emphasize that production use is not intended
- Rename
Fitness::call_for_chromosome()toFitness::calculate_for_chromosome() - Replaced
PermutableGenotype::population_factory()withPermutableGenotype::chromosome_permutations_into_iter() - Use
PermutableGenotype::chromosome_permutations_into_iter()inPermutate::call()instead of fully instantiated population - Rename
PermutableGenotype::population_factory_size()toPermutableGenotype::chromosome_permutations_size() - Use
num::BigUintforPermutableGenotype::chromosome_permutations_size()as it overflows easily - Rename existing
examples/evove_knapsack_set.rstoexamples/evolve_knapsack_discrete.rsto note is usesDiscreteGenotype<T>
- Improve rustdocs, refer to docs.rs documentation from general README.md
- Added rustdocs, refer to crate.io documentation from general README.md
Initial version