Skip to content

feat: add HGS algorithm#2383

Draft
zepfred wants to merge 8 commits into
TimefoldAI:mainfrom
zepfred:research/hgs
Draft

feat: add HGS algorithm#2383
zepfred wants to merge 8 commits into
TimefoldAI:mainfrom
zepfred:research/hgs

Conversation

@zepfred

@zepfred zepfred commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Implements the Hybrid Genetic Search algorithm as a new solver phase (), based on:

▎ Hybrid Genetic Search for the CVRP: Open-Source Implementation and SWAP* Neighborhood — Thibaut Vidal


What's new

New solver phase: EvolutionaryAlgorithmPhase

A population-based solver phase that runs the HGS loop alongside (or after) existing local search phases. The algorithm operates in three stages:

  1. Initialize — fills the population with populationSize individuals, each built via construction heuristic + local search.
  2. Evolve — each step selects two distinct-score individuals via binary tournament, applies a crossover strategy to produce an offspring, and adds it with survival selection (biased-fitness criterion trims back to populationSize).
  3. Restart — if no improvement occurs for populationRestartCount steps, the population is cleared except for the eliteSolutionSize best individuals, and fresh individuals are generated.

Support for both variable types

  • Basic variables: BasicVariableIndividual, BasicOXCrossover, BasicSolutionStateManager, BasicRuinRecreateIndividualStrategy
  • List variables: ListVariableIndividual, ListOXCrossover, ListRXCrossover, ListSolutionStateManager, ListRuinRecreateIndividualStrategy, ListSwapStarPhase (SWAP* neighborhood)

Crossover operators

  • OX (Order Crossover) for both basic and list variables
  • RX crossover for list variables
  • Crossover uses SolutionState save/restore to isolate working solution mutations per individual

Adaptive optimization profiles

The exploratoryRate is auto-selected based on problem scale:

  • Scale < 427 → exploratory profile (rate = 0.95) — more perturbation for simpler problems
  • Scale ≥ 427 → conservative profile (rate = 0.05) — less perturbation for complex problems

Can be overridden via workerConfig.exploratoryRate.

Nearby support for sublist moves

SubListChangeMoveSelectorConfig and SubListSwapMoveSelectorConfig now support nearby selectors, enabling distance-aware move generation in the EA's local search phase.

Performance improvements

  • Logging and solution-change events are suppressed during EA phase execution
  • SupplyManager gains demand-removal support to free supplies no longer needed
  • Infrastructure prepared for a future multi-threaded worker implementation (EVOLUTIONARY_AGENT_THREAD child thread type)

Copilot AI review requested due to automatic review settings June 17, 2026 13:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new preview “Evolutionary Algorithm” phase (Hybrid Genetic Search/HGS-oriented) into Timefold Solver, including XML configuration support, module exports, and a set of supporting population/individual/crossover/state-management abstractions.

Changes:

  • Add a new evolutionaryAlgorithm phase type/config (JAXB + XSD) and wire it into phase creation and preview-feature gating.
  • Implement core evolutionary building blocks (population/individual representations, construction strategies, crossover strategies, solution state save/restore, best-solution updating).
  • Extend/adjust several solver internals to support the new execution mode (thread types, event firing control, logging toggles, nearby selection for sublist moves, assorted test updates).

Reviewed changes

Copilot reviewed 110 out of 110 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
tools/benchmark/src/main/resources/benchmark.xsd Adds benchmark schema support for the new evolutionary algorithm phase.
core/src/main/resources/solver.xsd Adds solver schema support for configuring the evolutionary algorithm phase.
core/src/main/java/ai/timefold/solver/core/config/evolutionaryalgorithm/package-info.java Declares JAXB XML namespace/schema settings for the new config package.
core/src/main/java/ai/timefold/solver/core/config/evolutionaryalgorithm/EvolutionaryAlgorithmPhaseConfig.java Adds top-level evolutionary algorithm phase configuration model.
core/src/main/java/ai/timefold/solver/core/config/evolutionaryalgorithm/EvolutionaryPopulationConfig.java Adds population sizing/restart configuration model.
core/src/main/java/ai/timefold/solver/core/config/evolutionaryalgorithm/EvolutionaryWorkerConfig.java Adds worker behavior configuration model (exploratory rate, generator, LS).
core/src/main/java/ai/timefold/solver/core/config/evolutionaryalgorithm/EvolutionaryIndividualGeneratorConfig.java Adds configuration for individual generation (phase commands, CH config, properties).
core/src/main/java/ai/timefold/solver/core/config/evolutionaryalgorithm/EvolutionaryLocalSearchConfig.java Adds configuration wrapper for local search used by the evolutionary algorithm.
core/src/main/java/ai/timefold/solver/core/config/solver/PreviewFeature.java Adds EVOLUTIONARY_ALGORITHM preview feature flag.
core/src/main/java/ai/timefold/solver/core/config/solver/SolverConfig.java Registers the new phase config type for JAXB solver config parsing.
core/src/main/java/ai/timefold/solver/core/config/phase/PhaseConfig.java Extends JAXB @XmlSeeAlso to include the new phase config.
core/src/main/java/ai/timefold/solver/core/impl/phase/PhaseType.java Adds EVOLUTIONARY_ALGORITHM to phase type enumeration.
core/src/main/java/ai/timefold/solver/core/impl/phase/PhaseFactory.java Wires phase factory creation to handle EvolutionaryAlgorithmPhaseConfig.
core/src/main/java/ai/timefold/solver/core/impl/phase/AbstractPhaseFactory.java Maps the new phase config to its phase scope type.
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/EvolutionaryAlgorithmPhase.java Adds a marker interface for evolutionary algorithm phases.
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/DefaultEvolutionaryAlgorithmPhase.java Implements the evolutionary algorithm phase loop (load population, evolve, termination).
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/decider/EvolutionaryDecider.java Defines the contract for evolutionary deciders (load/evolve + lifecycle).
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/decider/AbstractHybridGeneticSearchDecider.java Adds shared restart/lifecycle logic for HGS-style deciders.
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/decider/HybridGeneticSearchDecider.java Implements HGS decider logic (selection, crossover, restarts, worker setup).
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/decider/HybridGeneticSearchWorkerContext.java Captures worker strategy/context inputs (construction, crossover, state manager, etc.).
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/population/Population.java Introduces population contract (add/select/restart/best/stats).
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/population/DefaultPopulation.java Provides a default population implementation (delegating to abstract base).
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/population/PopulationStatistics.java Adds population evolution statistics record.
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/population/PopulationDiffMap.java Adds an identity-based diff cache to support diversity/survival selection.
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/population/individual/Individual.java Adds the individual contract (solution/chromosome/diff/score/parents/clone).
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/population/individual/IndividualBuilder.java Adds a functional interface for building individuals.
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/population/individual/ChromosomeEntry.java Adds chromosome entry representation used by individuals and operators.
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/population/individual/AbstractIndividual.java Adds common implementation for individual score/feasibility/ordering.
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/population/individual/BasicVariableIndividual.java Adds chromosome/diff/clone implementation for basic variables.
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/population/individual/ListVariableIndividual.java Adds chromosome/diff/clone implementation for list variables.
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/population/individual/generator/ConstructionIndividualStrategy.java Adds contract for construction strategies used to generate individuals.
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/population/individual/generator/DefaultConstructionIndividualStrategy.java Adds a default deterministic/shuffled construction strategy for population seeding.
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/population/individual/generator/basic/BasicRuinRecreateIndividualStrategy.java Adds ruin/recreate construction strategy for basic variables.
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/population/individual/generator/list/ListRuinRecreateIndividualStrategy.java Adds ruin/recreate construction strategy for list variables.
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/crossover/CrossoverContext.java Adds crossover execution context (phase scope + two parents).
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/crossover/CrossoverResult.java Adds crossover result record (offspring + parent score provenance).
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/crossover/CrossoverStrategy.java Adds crossover contract (apply + phases used).
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/crossover/basic/BasicOXCrossover.java Implements OX crossover for basic variables.
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/crossover/list/AbstractListCrossover.java Adds shared list crossover helpers, including best-fit insertion logic.
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/crossover/list/ListOXCrossover.java Implements OX crossover for list variables.
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/crossover/list/ListRXCrossover.java Implements RX/SREX-style crossover for list variables.
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/common/Utils.java Adds shared indexing/cut-point utilities for evolutionary operators.
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/common/scope/EvolutionaryAlgorithmPhaseScope.java Adds phase scope for the evolutionary algorithm.
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/common/scope/EvolutionaryAlgorithmStepScope.java Adds step scope for the evolutionary algorithm.
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/common/phase/EvolutionaryAlgorithmPhaseLifecycleListener.java Adds lifecycle listener contract for the new phase type.
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/common/bestsolution/BestSolutionUpdater.java Adds contract for updating best solution from worker progress.
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/common/bestsolution/DefaultBestSolutionUpdater.java Implements a best-solution updater using solution-state transfer.
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/common/state/SolutionState.java Adds solution-state abstraction (solution + score).
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/common/state/SolutionStateManager.java Adds state save/restore contract used by workers/operators.
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/common/state/basic/BasicSolutionState.java Adds basic-variable solution state record.
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/common/state/basic/BasicSolutionStateManager.java Implements save/restore for basic-variable solutions.
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/common/state/basic/BasicValueState.java Adds per-variable captured state for basic-variable solutions.
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/common/state/list/ListSolutionState.java Adds list-variable solution state record.
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/common/state/list/ListSolutionStateManager.java Implements save/restore for list-variable solutions.
core/src/main/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/common/state/list/ListValueState.java Adds per-value captured state for list-variable solutions.
core/src/main/java/ai/timefold/solver/core/impl/solver/thread/ChildThreadType.java Adds evolutionary-agent child thread type.
core/src/main/java/ai/timefold/solver/core/impl/solver/scope/SolverScope.java Adds best-solution-event trigger flag and evolutionary child-scope initialization.
core/src/main/java/ai/timefold/solver/core/impl/solver/recaller/BestSolutionRecaller.java Gates best-solution event firing on the new solver-scope flag.
core/src/main/java/ai/timefold/solver/core/impl/score/director/AbstractScoreDirector.java Extends child-thread score director creation to support evolutionary threads.
core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverFactory.java Adds constraint-count computation to config policy for evolutionary decisions.
core/src/main/java/ai/timefold/solver/core/impl/heuristic/HeuristicConfigPolicy.java Adds constraint-count field and thread prefix for evolutionary threads.
core/src/main/java/ai/timefold/solver/core/impl/phase/AbstractPhase.java Adds enable/disable logging toggle to phases.
core/src/main/java/ai/timefold/solver/core/impl/localsearch/DefaultLocalSearchPhase.java Respects phase logging toggle for key logs.
core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/DefaultConstructionHeuristicPhase.java Respects phase logging toggle for step/phase logs.
core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/RuinRecreateConstructionHeuristicPhase.java Disables logging when the ruin-recreate CH phase starts.
core/src/main/java/ai/timefold/solver/core/impl/solver/termination/DiminishedReturnsTermination.java Resets grace-period state on phase end.
core/src/main/java/ai/timefold/solver/core/impl/solver/termination/MockablePhaseTermination.java Makes interface public for use outside the package.
core/src/main/java/ai/timefold/solver/core/impl/domain/variable/supply/SupplyManager.java Adds demand cancellation toggles and cancel-all operation.
core/src/main/java/ai/timefold/solver/core/impl/domain/variable/listener/support/VariableListenerSupport.java Implements new supply manager cancellation semantics.
core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/SubListSelector.java Adds min/max sublist size accessors to selector contract.
core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/RandomSubListSelector.java Implements the new sublist size accessors.
core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/mimic/SubListMimicRecorder.java Adds min/max sublist size accessors to mimic recorder contract.
core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/mimic/MimicRecordingSubListSelector.java Delegates new sublist size accessors through recording selector.
core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/mimic/MimicReplayingSubListSelector.java Delegates new sublist size accessors through replaying selector.
core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/SubListSelectorFactory.java Generalizes nearby-selection wiring to accept any SubListSelector.
core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/NearbyUtil.java Adds nearby auto-configuration support for sublist change/swap selectors.
core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/generic/list/SubListChangeMoveSelectorConfig.java Enables nearby auto-configuration and config inheritance via inheritConfig.
core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/generic/list/SubListSwapMoveSelectorConfig.java Enables nearby auto-configuration and config inheritance via inheritConfig.
core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/SelectorBasedTwoOptListMove.java Adds reverse-tail option and updates describe/rebase behavior.
core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/SelectorBasedTwoOptListMoveTest.java Adds coverage for reverse-tail 2-opt behavior and undo semantics.
core/src/test/java/ai/timefold/solver/core/impl/solver/SolverMetricsIT.java Updates expected move key naming to include reverseTail flag.
core/src/test/java/ai/timefold/solver/core/testutil/PlannerTestUtils.java Extends ScoreDirector mocking to support lookUpWorkingObject delegation.
core/src/test/java/ai/timefold/solver/core/testdomain/multivar/TestdataMultiVarEntity.java Adds a helper to obtain a tertiary variable descriptor.
core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/RandomSubListSwapMoveSelectorFactoryTest.java Fixes class name to match file and test focus.
core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/RandomSubListChangeMoveSelectorFactoryTest.java Fixes class name to match file and test focus.
core/src/test/java/ai/timefold/solver/core/impl/constructionheuristic/placer/entity/QueuedEntityPlacerFactoryTest.java Updates call signature for entity selector config building.
core/src/test/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfigTest.java Removes sublist move selectors from a disabled-autoconfig assertion list.
core/src/test/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/DefaultEvolutionaryAlgorithmPhaseTest.java Adds integration-level tests validating the new phase can solve basic/list/multivar domains.
core/src/test/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/population/individual/generator/DefaultConstructionIndividualStrategyTest.java Adds unit tests for default construction strategy behavior.
core/src/test/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/population/individual/generator/basic/BasicRuinRecreateIndividualStrategyTest.java Adds unit tests for basic ruin-recreate strategy behavior.
core/src/test/java/ai/timefold/solver/core/impl/evolutionaryalgorithm/population/individual/generator/list/ListRuinRecreateIndividualStrategyTest.java Adds unit tests for list ruin-recreate strategy behavior.
core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/AbstractEntityPlacerFactory.java Makes change-move selector config builder static for reuse.
core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/QueuedValuePlacerFactory.java Refactors internal move selector config building method name/signature.
core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/QueuedEntityPlacerFactory.java Refactors selector config building and default move selector config generation.
core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/DefaultConstructionHeuristicPhaseFactory.java Adjusts default placer config creation to pass phase config through.
core/src/main/java/ai/timefold/solver/core/impl/AbstractFromConfigFactory.java Adds static overloads to deduce entity descriptors using the config for better errors.
core/src/main/java/ai/timefold/solver/core/api/solver/event/EventProducerId.java Adds evolutionary algorithm phase event producer ID factory method.
core/src/main/java/ai/timefold/solver/core/enterprise/TimefoldSolverEnterpriseService.java Extends enterprise service API for building HGS decider and nearby selection with SubListSelector.
core/src/main/java/module-info.java Exports/opens new evolutionary algorithm packages for module/JAXB use.
core/src/build/revapi-differences.json Updates revapi ignore list for the new JAXB @XmlSeeAlso type.

Comment on lines +442 to +445
switch (childThreadType) {
case ChildThreadType.PART_THREAD -> {
var childThreadScoreDirector =
scoreDirectorFactory.createScoreDirectorBuilder().withLookUpEnabled(lookUpEnabled)
Comment on lines +451 to +455
}
case ChildThreadType.EVOLUTIONARY_AGENT_THREAD, ChildThreadType.MOVE_THREAD -> {
var childThreadScoreDirector = scoreDirectorFactory.createScoreDirectorBuilder().withLookUpEnabled(true)
.withConstraintMatchPolicy(constraintMatchPolicy).buildDerived();
childThreadScoreDirector.setWorkingSolution(cloneWorkingSolution());
Comment on lines +121 to +126
var newSolution = scoreDirector.cloneSolution(solution);
var newPredecessorAndSuccessorMap = HashMap.<Object, PositionPair> newHashMap(predecessorAndSuccessorMap.size());
var chromosomeList = new ArrayList<ChromosomeEntry>(chromosome.length);
load(scoreDirector.getSolutionDescriptor().getListVariableDescriptor(), planningIdAccessor, solution, chromosomeList,
newPredecessorAndSuccessorMap);
var newChromosome = chromosomeList.toArray(ChromosomeEntry[]::new);
diff++;
}
}
return (double) diff / (double) predecessorAndSuccessorMap.size();
/**
* @return the score of the first parent that generated this individual.
*/
InnerScore<Score_> getFirstParentScore();
/**
* @return the score of the second parent that generated this individual.
*/
InnerScore<Score_> getSecondParentScore();
Comment on lines +27 to +29
* {@link SolutionStateManager#saveSolutionState} captures a snapshot of the current working solution by cloning it
* and recording which planning values are assigned to each planning entity.
* <p>
@zepfred

zepfred commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

Here are some discussion points I would like assistance with that may not be clear in the review:

I tried to simplify the configuration structure, but I'm not happy with that:

<enablePreviewFeature>EVOLUTIONARY_ALGORITHM</enablePreviewFeature>
    <evolutionaryAlgorithm>
        <workerConfig>
            <individualGeneratorConfig>
                <customPhaseCommandClass>
                   ...
                </customPhaseCommandClass>
                <constructionHeuristic>
                    <queuedEntityPlacer>
                         ...
                    </queuedEntityPlacer>
                </constructionHeuristic>
            </individualGeneratorConfig>
            <localSearchConfig>
                <localSearch>
                    <localSearchType>DIVERSIFIED_LATE_ACCEPTANCE</localSearchType>
                </localSearch>
            </localSearchConfig>
        </workerConfig>
    </evolutionaryAlgorithm>

The current implementation increments the move for each generated individual, but I'm unsure if that's a good idea and would probably need to change.

18:24:16.828  INFO [pool-5-thread-2] Evolutionary Algorithm phase (0) ended: time spent (3600005), best score (-23520hard/-3240000medium/-653236soft), best generation (0), best iteration (22), generation total (0), iteration total (26).
18:24:16.841  INFO [pool-5-thread-2] Solving ended: time spent (3600010), best score (-23520hard/-3240000medium/-653236soft), move evaluation speed (0/sec), phase total (1), environment mode (PHASE_ASSERT), move thread count (NONE).

@triceo triceo marked this pull request as draft June 17, 2026 13:51
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
55.6% Coverage on New Code (required ≥ 70%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants