Skip to content

Swap genes when the random mutation finds no free value - #373

Merged
ahmedfgad merged 4 commits into
ahmedfgad:masterfrom
tachsin:fix-permutation-random-mutation
Oct 8, 2026
Merged

ahmedfgad merged 4 commits into
ahmedfgad:masterfrom
tachsin:fix-permutation-random-mutation

Conversation

@tachsin

@tachsin tachsin commented Sep 28, 2026

Copy link
Copy Markdown
Contributor

Fixes #372.

With allow_duplicate_genes=False and a gene space of num_genes values (the permutation setup of pygad.benchmarks.tsp), every value of the space is used, so select_unique_value() kept the gene's value and the random mutation never changed a solution.

This adds swap_gene_by_space() to the Mutation class. In the 4 mutations by space (mutation_by_space, mutation_probs_by_space, adaptive_mutation_by_space, adaptive_mutation_probs_by_space), when the picked value equals the gene's value and duplicates aren't allowed, the gene swaps values with another gene, picked at random among those whose value is in its space and whose space holds its value. The swap keeps the genes unique, so the duplicate resolution isn't needed. Genes with a gene_constraint aren't swapped, and nothing changes when duplicates are allowed or a free value exists.

On examples/benchmarks/example_tsp.py's settings with 12 cities on a circle (200 generations, seeds 0 to 2), the best tours go from lengths 11.7, 12.5 and 9.6 to the optimum, 6.2.

Tests: 3 tests in tests/test_crossover_mutation.py mutate 100 permutations of 8 (with mutation_num_genes, with mutation_probability=1.0, and with a nested gene space) and check that each one changes and stays a permutation. They fail on master and pass with this change.

pytest tests (without test_kerasga.py and test_torchga.py, which need TensorFlow and PyTorch): 874 passed, 1 skipped. The only failure is test_submodule_versions.py::test_changed_submodule_is_version_bumped[utils], since pygad/utils changed; I left the version bump to you for the release.

With allow_duplicate_genes=False and a gene space of num_genes values, as
for permutations, every value of the space is used, so a mutated gene
kept its value and the mutation never changed a solution. In the 4
mutations by space, swap the gene with another gene whose value is in
its space and whose space holds its value instead.

Fixes ahmedfgad#372

@ahmedfgad ahmedfgad left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thanks for reporting this and adding the permutation tests. The fallback addresses a real issue, but please fix these two cases before we merge it.

  1. Preserve each gene's declared type when swapping.

The assignments in swap_gene_by_space() move the values directly, without converting them for the destination gene. With gene_space=[0, 1], gene_type=[int, float], allow_duplicate_genes=False, and mutation_num_genes=1, mutating an object array containing [0, 1.0] produces [1.0, 0]. The first gene is now a float and the second is an int, contrary to their declared types.

Please convert and round both replacement values for their destination genes before committing the swap. Check that the converted values still satisfy both gene spaces and preserve uniqueness; if they do not, skip that candidate. Add a regression test for mixed gene types.

  1. Avoid always undoing the mutation on a two-gene permutation.

With gene_space=[0, 1], gene_type=int, and allow_duplicate_genes=False, mutating [0, 1] with either mutation_num_genes=2 or mutation_probability=1.0 swaps the two genes once for each selected gene. The second swap reverses the first, so the solution always ends unchanged. This leaves the original problem unresolved for this configuration.

Please prevent the fallback from processing the same swapped pair twice in one mutation pass, or use another approach that allows this configuration to change. Add tests for both mutation settings. Since the fallback is also added to adaptive mutation, please cover its count-based and probability-based paths as well.

The existing permutation tests pass, but they do not cover these cases. Please include the pygad.utils version bump required by the submodule-version check when updating the PR.

Track swapped positions per mutation pass to avoid cancelling two-gene swaps. Validate destination conversions, spaces, and dependent constraints before changing the solution. Add regression coverage and document the recent operator fixes.
Run the metadata-only chart job with the trusted base workflow. Add a manual trigger to verify and refresh summaries without checking out contributor code.
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown

📊 Change summary

11 files changed · +478 / −8 lines

pie showData
    title Lines changed per file (486 total)
    "tests/test_permutation_mutation.py" : 228
    "pygad/utils/mutation.py" : 145
    "tests/test_crossover_mutation.py" : 45
    ".github/workflows/pr-change-chart.yml" : 22
    "docs/source/utils.md" : 19
    "docs/source/releases.md" : 12
    "docs/source/pygad.md" : 5
    "docs/source/gene_values.md" : 4
    "docs/source/adaptive_mutation.md" : 2
    "docs/source/benchmarks.md" : 2
    "pygad/utils/__init__.py" : 2
Loading
Per-file breakdown
File + − total
tests/test_permutation_mutation.py 228 0 228
pygad/utils/mutation.py 145 0 145
tests/test_crossover_mutation.py 45 0 45
.github/workflows/pr-change-chart.yml 17 5 22
docs/source/utils.md 18 1 19
docs/source/releases.md 12 0 12
docs/source/pygad.md 5 0 5
docs/source/gene_values.md 3 1 4
docs/source/adaptive_mutation.md 2 0 2
docs/source/benchmarks.md 2 0 2
pygad/utils/__init__.py 1 1 2
Total (11 files) 478 8 486

@ahmedfgad ahmedfgad left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

The requested fixes are complete. Fallback swaps preserve destination gene types, numeric values after casting and rounding, both gene spaces, uniqueness, and dependent constraints. Tracking swapped positions within each mutation pass prevents a two-gene permutation from immediately swapping back.

The regression coverage includes all four random/adaptive mutation paths, mixed types, constrained and nested spaces, single-gene permutations, zero mutation probability, and seeded GA runs. Validation passed: 995 local tests, the Python 3.8–3.14 test matrix, all SDK compatibility jobs, package build and installed-wheel checks, and a clean documentation build with warnings treated as errors.

The documentation now explains the fallback and the recent crossover and swap corrections. The chart workflow's fork-token permission failure is also fixed. Its metadata-only job was verified successfully from github-actions: https://github.com/ahmedfgad/GeneticAlgorithmPython/actions/runs/37733700942

Python test matrix: https://github.com/ahmedfgad/GeneticAlgorithmPython/actions/runs/37733466235
SDK compatibility: https://github.com/ahmedfgad/GeneticAlgorithmPython/actions/runs/37733699797

Approved.

@ahmedfgad
ahmedfgad merged commit f05c1a1 into ahmedfgad:master Oct 8, 2026
7 checks passed
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.

With allow_duplicate_genes=False, the random mutation never changes a permutation

2 participants