diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1fac4d5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ +.kotlin + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..2a65317 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..f16dea7 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/assignement.md b/assignement.md new file mode 100644 index 0000000..3e70280 --- /dev/null +++ b/assignement.md @@ -0,0 +1,26 @@ +## Refactor + +### Change dependency graph +- A circular dependency between SimpleGeneticAlgorithm and Individual were implemented using service locator pattern and +static classes. + +### Population +- Add a second constructor. size parameter were never used if createNew was false + +### Individual +- Move getFitness method from SimpleGeneticAlgorithm to Individual + +### Prepare support for class inheritance + +## Limit number of generations + + + +## Roulette selection +To be done + +## Cross-over rate +To be done + +## Variable-length bit strings +To be done diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..4d95ff3 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,28 @@ +plugins { + id("java") +} + +group = "be.naaturel" +version = "1.0-SNAPSHOT" + +repositories { + mavenCentral() +} + +dependencies { + implementation("io.jenetics:jenetics:5.1.0") + + compileOnly("org.projectlombok:lombok:1.18.48") + annotationProcessor("org.projectlombok:lombok:1.18.48") + + testCompileOnly("org.projectlombok:lombok:1.18.48") + testAnnotationProcessor("org.projectlombok:lombok:1.18.48") + + testImplementation(platform("org.junit:junit-bom:5.10.0")) + testImplementation("org.junit.jupiter:junit-jupiter") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") +} + +tasks.test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..249e583 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..a5560a0 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Sep 23 14:50:08 CEST 2026 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..1b6c787 --- /dev/null +++ b/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..0045ff8 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "GAN" \ No newline at end of file diff --git a/src/main/java/Main.java b/src/main/java/Main.java new file mode 100644 index 0000000..365e72f --- /dev/null +++ b/src/main/java/Main.java @@ -0,0 +1,41 @@ +import domain.annealing.SimulatedAnnealing; +import domain.ant_colony.AntColonyOptimization; +import domain.binary.algorithm.SimpleGeneticAlgorithm; + +import java.util.Scanner; + +public class Main { + public static void main(String[] args) { + + Scanner in = new Scanner(System.in); + System.out.println("Run algorithm:"); + System.out.println("1 - Simulated Annealing"); + System.out.println("2 - Simple Genetic Algorithm"); + System.out.println("3 - Ant Colony"); + int decision = in.nextInt(); + switch (decision) { + case 1: + System.out.println("Optimized distance for travel: " + SimulatedAnnealing.simulateAnnealing(10, 10000, 0.9995)); + break; + case 2: + SimpleGeneticAlgorithm ga = new SimpleGeneticAlgorithm( + 0.5, + 0.025, + 5, + true, + new byte[64] + ); + ga.runAlgorithm(50, "1011000100000100010000100000100111001000000100000100000000001111"); + break; + case 3: + AntColonyOptimization antColony = new AntColonyOptimization(21); + antColony.startAntOptimization(); + break; + default: + System.out.println("Unknown option"); + break; + } + in.close(); + + } +} diff --git a/src/main/java/domain/annealing/City.java b/src/main/java/domain/annealing/City.java new file mode 100644 index 0000000..d8aed4f --- /dev/null +++ b/src/main/java/domain/annealing/City.java @@ -0,0 +1,22 @@ +package domain.annealing; + +import lombok.Data; + +@Data +public class City { + + private int x; + private int y; + + public City() { + this.x = (int) (Math.random() * 500); + this.y = (int) (Math.random() * 500); + } + + public double distanceToCity(City city) { + int x = Math.abs(getX() - city.getX()); + int y = Math.abs(getY() - city.getY()); + return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); + } + +} diff --git a/src/main/java/domain/annealing/SimulatedAnnealing.java b/src/main/java/domain/annealing/SimulatedAnnealing.java new file mode 100644 index 0000000..756ba07 --- /dev/null +++ b/src/main/java/domain/annealing/SimulatedAnnealing.java @@ -0,0 +1,36 @@ +package domain.annealing; + +public class SimulatedAnnealing { + + private static Travel travel = new Travel(10); + + public static double simulateAnnealing(double startingTemperature, int numberOfIterations, double coolingRate) { + System.out.println("Starting SA with temperature: " + startingTemperature + ", # of iterations: " + numberOfIterations + " and colling rate: " + coolingRate); + double t = startingTemperature; + travel.generateInitialTravel(); + double bestDistance = travel.getDistance(); + System.out.println("Initial distance of travel: " + bestDistance); + Travel bestSolution = travel; + Travel currentSolution = bestSolution; + + for (int i = 0; i < numberOfIterations; i++) { + if (t > 0.1) { + currentSolution.swapCities(); + double currentDistance = currentSolution.getDistance(); + if (currentDistance < bestDistance) { + bestDistance = currentDistance; + } else if (Math.exp((bestDistance - currentDistance) / t) < Math.random()) { + currentSolution.revertSwap(); + } + t *= coolingRate; + } else { + continue; + } + if (i % 100 == 0) { + System.out.println("Iteration #" + i); + } + } + return bestDistance; + } + +} diff --git a/src/main/java/domain/annealing/Travel.java b/src/main/java/domain/annealing/Travel.java new file mode 100644 index 0000000..c694240 --- /dev/null +++ b/src/main/java/domain/annealing/Travel.java @@ -0,0 +1,63 @@ +package domain.annealing; + +import java.util.ArrayList; +import java.util.Collections; + +import lombok.Data; + +@Data +public class Travel { + + private ArrayList travel = new ArrayList<>(); + private ArrayList previousTravel = new ArrayList<>(); + + public Travel(int numberOfCities) { + for (int i = 0; i < numberOfCities; i++) { + travel.add(new City()); + } + } + + public void generateInitialTravel() { + if (travel.isEmpty()) + new Travel(10); + Collections.shuffle(travel); + } + + public void swapCities() { + int a = generateRandomIndex(); + int b = generateRandomIndex(); + previousTravel = travel; + City x = travel.get(a); + City y = travel.get(b); + travel.set(a, y); + travel.set(b, x); + } + + public void revertSwap() { + travel = previousTravel; + } + + private int generateRandomIndex() { + return (int) (Math.random() * travel.size()); + } + + public City getCity(int index) { + return travel.get(index); + } + + public int getDistance() { + int distance = 0; + for (int index = 0; index < travel.size(); index++) { + City starting = getCity(index); + City destination; + if (index + 1 < travel.size()) { + destination = getCity(index + 1); + } else { + destination = getCity(0); + } + distance += starting.distanceToCity(destination); + } + return distance; + } + +} diff --git a/src/main/java/domain/ant_colony/Ant.java b/src/main/java/domain/ant_colony/Ant.java new file mode 100644 index 0000000..804ed65 --- /dev/null +++ b/src/main/java/domain/ant_colony/Ant.java @@ -0,0 +1,37 @@ +package domain.ant_colony; + +public class Ant { + + protected int trailSize; + protected int trail[]; + protected boolean visited[]; + + public Ant(int tourSize) { + this.trailSize = tourSize; + this.trail = new int[tourSize]; + this.visited = new boolean[tourSize]; + } + + protected void visitCity(int currentIndex, int city) { + trail[currentIndex + 1] = city; + visited[city] = true; + } + + protected boolean visited(int i) { + return visited[i]; + } + + protected double trailLength(double graph[][]) { + double length = graph[trail[trailSize - 1]][trail[0]]; + for (int i = 0; i < trailSize - 1; i++) { + length += graph[trail[i]][trail[i + 1]]; + } + return length; + } + + protected void clear() { + for (int i = 0; i < trailSize; i++) + visited[i] = false; + } + +} diff --git a/src/main/java/domain/ant_colony/AntColonyOptimization.java b/src/main/java/domain/ant_colony/AntColonyOptimization.java new file mode 100644 index 0000000..cb251dd --- /dev/null +++ b/src/main/java/domain/ant_colony/AntColonyOptimization.java @@ -0,0 +1,203 @@ +package domain.ant_colony; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.OptionalInt; +import java.util.Random; +import java.util.stream.IntStream; + +public class AntColonyOptimization { + + private double c = 1.0; + private double alpha = 1; + private double beta = 5; + private double evaporation = 0.5; + private double Q = 500; + private double antFactor = 0.8; + private double randomFactor = 0.01; + + private int maxIterations = 1000; + + private int numberOfCities; + private int numberOfAnts; + private double graph[][]; + private double trails[][]; + private List ants = new ArrayList<>(); + private Random random = new Random(); + private double probabilities[]; + + private int currentIndex; + + private int[] bestTourOrder; + private double bestTourLength; + + public AntColonyOptimization(int noOfCities) { + graph = generateRandomMatrix(noOfCities); + numberOfCities = graph.length; + numberOfAnts = (int) (numberOfCities * antFactor); + + trails = new double[numberOfCities][numberOfCities]; + probabilities = new double[numberOfCities]; + IntStream.range(0, numberOfAnts) + .forEach(i -> ants.add(new Ant(numberOfCities))); + } + + /** + * Generate initial solution + */ + public double[][] generateRandomMatrix(int n) { + double[][] randomMatrix = new double[n][n]; + IntStream.range(0, n) + .forEach(i -> IntStream.range(0, n) + .forEach(j -> randomMatrix[i][j] = Math.abs(random.nextInt(100) + 1))); + return randomMatrix; + } + + /** + * Perform ant optimization + */ + public void startAntOptimization() { + IntStream.rangeClosed(1, 3) + .forEach(i -> { + System.out.println("Attempt #" + i); + solve(); + }); + } + + /** + * Use this method to run the main logic + */ + public int[] solve() { + setupAnts(); + clearTrails(); + IntStream.range(0, maxIterations) + .forEach(i -> { + moveAnts(); + updateTrails(); + updateBest(); + }); + System.out.println("Best tour length: " + (bestTourLength - numberOfCities)); + System.out.println("Best tour order: " + Arrays.toString(bestTourOrder)); + return bestTourOrder.clone(); + } + + /** + * Prepare ants for the simulation + */ + private void setupAnts() { + IntStream.range(0, numberOfAnts) + .forEach(i -> { + ants.forEach(ant -> { + ant.clear(); + ant.visitCity(-1, random.nextInt(numberOfCities)); + }); + }); + currentIndex = 0; + } + + /** + * At each iteration, move ants + */ + private void moveAnts() { + IntStream.range(currentIndex, numberOfCities - 1) + .forEach(i -> { + ants.forEach(ant -> ant.visitCity(currentIndex, selectNextCity(ant))); + currentIndex++; + }); + } + + /** + * Select next city for each ant + */ + private int selectNextCity(Ant ant) { + int t = random.nextInt(numberOfCities - currentIndex); + if (random.nextDouble() < randomFactor) { + OptionalInt cityIndex = IntStream.range(0, numberOfCities) + .filter(i -> i == t && !ant.visited(i)) + .findFirst(); + if (cityIndex.isPresent()) { + return cityIndex.getAsInt(); + } + } + calculateProbabilities(ant); + double r = random.nextDouble(); + double total = 0; + for (int i = 0; i < numberOfCities; i++) { + total += probabilities[i]; + if (total >= r) { + return i; + } + } + + throw new RuntimeException("There are no other cities"); + } + + /** + * Calculate the next city picks probabilites + */ + public void calculateProbabilities(Ant ant) { + int i = ant.trail[currentIndex]; + double pheromone = 0.0; + for (int l = 0; l < numberOfCities; l++) { + if (!ant.visited(l)) { + pheromone += Math.pow(trails[i][l], alpha) * Math.pow(1.0 / graph[i][l], beta); + } + } + for (int j = 0; j < numberOfCities; j++) { + if (ant.visited(j)) { + probabilities[j] = 0.0; + } else { + double numerator = Math.pow(trails[i][j], alpha) * Math.pow(1.0 / graph[i][j], beta); + probabilities[j] = numerator / pheromone; + } + } + } + + /** + * Update trails that ants used + */ + private void updateTrails() { + for (int i = 0; i < numberOfCities; i++) { + for (int j = 0; j < numberOfCities; j++) { + trails[i][j] *= evaporation; + } + } + for (Ant a : ants) { + double contribution = Q / a.trailLength(graph); + for (int i = 0; i < numberOfCities - 1; i++) { + trails[a.trail[i]][a.trail[i + 1]] += contribution; + } + trails[a.trail[numberOfCities - 1]][a.trail[0]] += contribution; + } + } + + /** + * Update the best solution + */ + private void updateBest() { + if (bestTourOrder == null) { + bestTourOrder = ants.get(0).trail; + bestTourLength = ants.get(0) + .trailLength(graph); + } + for (Ant a : ants) { + if (a.trailLength(graph) < bestTourLength) { + bestTourLength = a.trailLength(graph); + bestTourOrder = a.trail.clone(); + } + } + } + + /** + * Clear trails after simulation + */ + private void clearTrails() { + IntStream.range(0, numberOfCities) + .forEach(i -> { + IntStream.range(0, numberOfCities) + .forEach(j -> trails[i][j] = c); + }); + } + +} diff --git a/src/main/java/domain/binary/Individual.java b/src/main/java/domain/binary/Individual.java new file mode 100644 index 0000000..9626f49 --- /dev/null +++ b/src/main/java/domain/binary/Individual.java @@ -0,0 +1,52 @@ +package domain.binary; + +import domain.binary.algorithm.SimpleGeneticAlgorithm; +import lombok.Data; + +@Data +public class Individual { + + protected int defaultGeneLength = 64; + private byte[] genes = new byte[defaultGeneLength]; + private int fitness = 0; + + public Individual(byte[] solution) { + for (int i = 0; i < genes.length; i++) { + byte gene = (byte) Math.round(Math.random()); + genes[i] = gene; + } + + this.fitness = computeFitness(solution); + } + + public byte getSingleGene(int index) { + return genes[index]; + } + + public void setSingleGene(int index, byte value) { + genes[index] = value; + fitness = 0; + } + + public void recomputeFitness(byte[] solution) { + this.fitness = computeFitness(solution); + } + + private int computeFitness(byte[] solution) { + int fitness = 0; + for (int i = 0; i < this.getDefaultGeneLength() && i < solution.length; i++) { + if (this.getSingleGene(i) != solution[i]) continue; + fitness++; + } + return fitness; + } + + @Override + public String toString() { + StringBuilder geneString = new StringBuilder(); + for (int i = 0; i < genes.length; i++) { + geneString.append(getSingleGene(i)); + } + return geneString.toString(); + } +} diff --git a/src/main/java/domain/binary/Population.java b/src/main/java/domain/binary/Population.java new file mode 100644 index 0000000..d9c5540 --- /dev/null +++ b/src/main/java/domain/binary/Population.java @@ -0,0 +1,130 @@ +package domain.binary; + +import java.util.ArrayList; +import java.util.List; + +import lombok.Data; + +@Data +public class Population { + + private final int size; + private final int tournamentSize; + private final double mutationRate; + private final double uniformRate; + private Individual[] individuals; + + public Population( + int size, + double uniformRate, + double mutationRate, + int tournamentSize, + byte[] solution + ) { + this(size, uniformRate, mutationRate, tournamentSize); + this.individuals = createNewPopulation(size, solution); + } + + private Population( + int size, + double uniformRate, + double mutationRate, + int tournamentSize + ) { + this.size = size; + this.uniformRate = uniformRate; + this.mutationRate = mutationRate; + this.tournamentSize = tournamentSize; + individuals = new Individual[size]; + } + + public Individual getIndividual(int index) { + return individuals[index]; + } + + public Individual setIndividual(int index, Individual individual) { + return individuals[index] = individual; + } + + public Individual getFittest() { + Individual fittest = individuals[0]; + for (int i = 0; i < individuals.length; i++) { + if (fittest.getFitness() <= getIndividual(i).getFitness()) { + fittest = getIndividual(i); + } + } + return fittest; + } + + public Population evolve(boolean withElitism, byte[] solution) { + int elitismOffset; + Individual[] newIndividuals = new Individual[size]; + + if (withElitism) { + newIndividuals[0] = getFittest(); + elitismOffset = 1; + } else { + elitismOffset = 0; + } + + for (int i = elitismOffset; i < getIndividuals().length; i++) { + Individual indiv1 = tournamentSelection(); + Individual indiv2 = tournamentSelection(); + Individual newIndiv = crossover(indiv1, indiv2, solution); + newIndividuals[i] = newIndiv; + } + + for (int i = elitismOffset; i < newIndividuals.length; i++) { + mutate(newIndividuals[i]); + newIndividuals[i].recomputeFitness(solution); + } + + Population newPop = this.copy(); + newPop.setIndividuals(newIndividuals); + return newPop; + } + + private Individual crossover(Individual indiv1, Individual indiv2, byte[] solution) { + Individual newSol = new Individual(solution); + for (int i = 0; i < newSol.getDefaultGeneLength(); i++) { + newSol.setSingleGene(i, Math.random() <= uniformRate + ? indiv1.getSingleGene(i) + : indiv2.getSingleGene(i)); + } + newSol.recomputeFitness(solution); + return newSol; + } + + private void mutate(Individual indiv) { + for (int i = 0; i < indiv.getDefaultGeneLength(); i++) { + if (Math.random() <= mutationRate) { + byte gene = (byte) Math.round(Math.random()); + indiv.setSingleGene(i, gene); + } + } + } + + private Individual tournamentSelection() { + Population tournament = this.copy(); + for (int i = 0; i < tournamentSize; i++) { + int randomId = (int) (Math.random() * this.getIndividuals().length); + tournament.setIndividual(i, this.getIndividual(randomId)); + } + return tournament.getFittest(); + } + + private Individual[] createNewPopulation(int size, byte[] solution) { + Individual[] newIndividuals = new Individual[size]; + for (int i = 0; i < size; i++) { + Individual newIndividual = new Individual(solution); + newIndividuals[i] = newIndividual; + } + return newIndividuals; + } + + public Population copy() { + Population p = new Population(size, uniformRate, mutationRate, tournamentSize); + p.setIndividuals(this.individuals); + return p; + } +} diff --git a/src/main/java/domain/binary/algorithm/CustomGeneticAlgorithm.java b/src/main/java/domain/binary/algorithm/CustomGeneticAlgorithm.java new file mode 100644 index 0000000..a744aea --- /dev/null +++ b/src/main/java/domain/binary/algorithm/CustomGeneticAlgorithm.java @@ -0,0 +1,27 @@ +package domain.binary.algorithm; + +import domain.binary.Population; + +public class CustomGeneticAlgorithm extends SimpleGeneticAlgorithm { + + public CustomGeneticAlgorithm( + double uniformRate, + double mutationRate, + int tournamentSize, + boolean elitism, + byte[] solution + ) { + super(uniformRate, mutationRate, tournamentSize, elitism, solution); + } + + @Override + public boolean runAlgorithm(int populationSize, String solution){ + return false; + } + + @Override + public boolean checkStopCriteria(Population pop) { + return false; + } + +} diff --git a/src/main/java/domain/binary/algorithm/SimpleGeneticAlgorithm.java b/src/main/java/domain/binary/algorithm/SimpleGeneticAlgorithm.java new file mode 100644 index 0000000..c7515fe --- /dev/null +++ b/src/main/java/domain/binary/algorithm/SimpleGeneticAlgorithm.java @@ -0,0 +1,81 @@ +package domain.binary.algorithm; + +import domain.binary.Individual; +import domain.binary.Population; +import lombok.Data; + +@Data +public class SimpleGeneticAlgorithm { + + private final double uniformRate; + private final double mutationRate; + private final int tournamentSize; + private final boolean elitism; + private byte[] solution; + + public SimpleGeneticAlgorithm( + double uniformRate, + double mutationRate, + int tournamentSize, + boolean elitism, + byte[] solution + ){ + this.uniformRate = uniformRate; + this.mutationRate = mutationRate; + this.tournamentSize = tournamentSize; + this.elitism = elitism; + this.solution = solution; + } + + public boolean runAlgorithm(int populationSize, String solution) { + setSolution(solution); + Population myPop = new Population( + populationSize, + uniformRate, + mutationRate, + tournamentSize, + this.solution); + + int generationCount = 1; + + while (checkStopCriteria(myPop)) { + System.out.println("Generation: " + generationCount + " Correct genes found: " + myPop.getFittest().getFitness()); + myPop = myPop.evolve(elitism, this.solution); + generationCount++; + } + + System.out.println("Solution found!"); + System.out.println("Generation: " + generationCount); + System.out.println("Genes: "); + System.out.println(myPop.getFittest()); + return true; + } + + public boolean checkStopCriteria(Population pop) { + int fitness = pop.getFittest().getFitness(); + int maxFitness = getMaxFitness(); + return fitness < maxFitness; + } + + protected int getMaxFitness() { + int maxFitness = solution.length; + return maxFitness; + } + + protected void setSolution(String newSolution) { + if (newSolution.length() != this.solution.length) { + throw new RuntimeException("The solution needs to have " + this.solution.length + " bytes"); + } + + solution = new byte[newSolution.length()]; + for (int i = 0; i < newSolution.length(); i++) { + String character = newSolution.substring(i, i + 1); + if (character.contains("0") || character.contains("1")) { + solution[i] = Byte.parseByte(character); + } else { + solution[i] = 0; + } + } + } + +} diff --git a/src/main/java/domain/jenetics/Knapsack.java b/src/main/java/domain/jenetics/Knapsack.java new file mode 100644 index 0000000..755db72 --- /dev/null +++ b/src/main/java/domain/jenetics/Knapsack.java @@ -0,0 +1,40 @@ +package domain.jenetics; + +import io.jenetics.*; +import io.jenetics.engine.Engine; +import io.jenetics.engine.EvolutionStatistics; + +import java.util.stream.Stream; + +import static io.jenetics.engine.EvolutionResult.toBestPhenotype; +import static io.jenetics.engine.Limits.bySteadyFitness; + +public class Knapsack { + + public static void main(String[] args) { + int nItems = 15; + double ksSize = nItems * 100.0 / 3.0; + + KnapsackFF ff = new KnapsackFF(Stream.generate(KnapsackItem::random) + .limit(nItems) + .toArray(KnapsackItem[]::new), ksSize); + + Engine engine = Engine.builder(ff, BitChromosome.of(nItems, 0.5)) + .populationSize(500) + .survivorsSelector(new TournamentSelector<>(5)) + .offspringSelector(new RouletteWheelSelector<>()) + .alterers(new Mutator<>(0.115), new SinglePointCrossover<>(0.16)) + .build(); + + EvolutionStatistics statistics = EvolutionStatistics.ofNumber(); + + Phenotype best = engine.stream() + .limit(bySteadyFitness(7)) + .limit(100) + .peek(statistics) + .collect(toBestPhenotype()); + + System.out.println(statistics); + System.out.println(best); + } +} \ No newline at end of file diff --git a/src/main/java/domain/jenetics/KnapsackFF.java b/src/main/java/domain/jenetics/KnapsackFF.java new file mode 100644 index 0000000..0946323 --- /dev/null +++ b/src/main/java/domain/jenetics/KnapsackFF.java @@ -0,0 +1,27 @@ +package domain.jenetics; + +import io.jenetics.BitChromosome; +import io.jenetics.BitGene; +import io.jenetics.Genotype; + +import java.util.function.Function; + + + +public class KnapsackFF implements Function, Double> { + private KnapsackItem[] items; + private double size; + + public KnapsackFF(KnapsackItem[] items, double size) { + this.items = items; + this.size = size; + } + + @Override + public Double apply(Genotype gt) { + KnapsackItem sum = ((BitChromosome) gt.getChromosome()).ones() + .mapToObj(i -> items[i]) + .collect(KnapsackItem.toSum()); + return sum.size <= this.size ? sum.value : 0; + } +} diff --git a/src/main/java/domain/jenetics/KnapsackItem.java b/src/main/java/domain/jenetics/KnapsackItem.java new file mode 100644 index 0000000..5964bdc --- /dev/null +++ b/src/main/java/domain/jenetics/KnapsackItem.java @@ -0,0 +1,34 @@ +package domain.jenetics; + +import io.jenetics.util.RandomRegistry; + +import java.util.Random; +import java.util.stream.Collector; + +public class KnapsackItem { + + public double size; + public double value; + + public KnapsackItem(double size, double value) { + this.size = size; + this.value = value; + } + + protected static KnapsackItem random() { + Random r = RandomRegistry.getRandom(); + return new KnapsackItem(r.nextDouble() * 100, r.nextDouble() * 100); + } + + protected static Collector toSum() { + return Collector.of(() -> new double[2], (a, b) -> { + a[0] += b.size; + a[1] += b.value; + } , (a, b) -> { + a[0] += b[0]; + a[1] += b[1]; + return a; + } , r -> new KnapsackItem(r[0], r[1])); + } + +} diff --git a/src/main/java/domain/jenetics/SimpleGeneticAlgorithm.java b/src/main/java/domain/jenetics/SimpleGeneticAlgorithm.java new file mode 100644 index 0000000..3cd6366 --- /dev/null +++ b/src/main/java/domain/jenetics/SimpleGeneticAlgorithm.java @@ -0,0 +1,34 @@ +package domain.jenetics; + + +import io.jenetics.BitChromosome; +import io.jenetics.BitGene; +import io.jenetics.Genotype; +import io.jenetics.engine.Engine; +import io.jenetics.engine.EvolutionResult; +import io.jenetics.util.Factory; + +public class SimpleGeneticAlgorithm { + + private static Integer eval(Genotype gt) { + return gt.getChromosome() + .as(BitChromosome.class) + .bitCount(); + } + + public static void main(String[] args) { + Factory> gtf = Genotype.of(BitChromosome.of(10, 0.5)); + System.out.println("Before the evolution:\n" + gtf); + + Engine engine = Engine.builder(SimpleGeneticAlgorithm::eval, gtf) + .build(); + + Genotype result = engine.stream() + .limit(500) + .collect(EvolutionResult.toBestGenotype()); + + System.out.println("After the evolution:\n" + result); + + } + +} diff --git a/src/main/java/domain/jenetics/SpringsteenProblem.java b/src/main/java/domain/jenetics/SpringsteenProblem.java new file mode 100644 index 0000000..f380f75 --- /dev/null +++ b/src/main/java/domain/jenetics/SpringsteenProblem.java @@ -0,0 +1,83 @@ +package domain.jenetics; + +import io.jenetics.BitGene; +import io.jenetics.engine.*; +import io.jenetics.util.ISeq; + +import static java.util.Objects.requireNonNull; + +import java.util.function.Function; +import java.util.stream.Collectors; + + +public class SpringsteenProblem implements Problem, BitGene, Double> { + + private ISeq records; + private double maxPricePerUniqueSong; + + public SpringsteenProblem(ISeq records, double maxPricePerUniqueSong) { + this.records = requireNonNull(records); + this.maxPricePerUniqueSong = maxPricePerUniqueSong; + } + + @Override + public Function, Double> fitness() { + return SpringsteenRecords -> { + double cost = SpringsteenRecords.stream() + .mapToDouble(r -> r.price) + .sum(); + + int uniqueSongCount = SpringsteenRecords.stream() + .flatMap(r -> r.songs.stream()) + .collect(Collectors.toSet()) + .size(); + + double pricePerUniqueSong = cost / uniqueSongCount; + + return pricePerUniqueSong <= maxPricePerUniqueSong ? uniqueSongCount : 0.0; + }; + } + + @Override + public Codec, BitGene> codec() { + return Codecs.ofSubSet(records); + } + + public static void main(String[] args) { + double maxPricePerUniqueSong = 2.5; + + SpringsteenProblem springsteen = new SpringsteenProblem( + ISeq.of(new SpringsteenRecord("SpringsteenRecord1", 25, ISeq.of("Song1", "Song2", "Song3", "Song4", "Song5", "Song6")), new SpringsteenRecord("SpringsteenRecord2", 15, ISeq.of("Song2", "Song3", "Song4", "Song5", "Song6", "Song7")), + new SpringsteenRecord("SpringsteenRecord3", 35, ISeq.of("Song5", "Song6", "Song7", "Song8", "Song9", "Song10")), new SpringsteenRecord("SpringsteenRecord4", 17, ISeq.of("Song9", "Song10", "Song12", "Song4", "Song13", "Song14")), + new SpringsteenRecord("SpringsteenRecord5", 29, ISeq.of("Song1", "Song2", "Song13", "Song14", "Song15", "Song16")), new SpringsteenRecord("SpringsteenRecord6", 5, ISeq.of("Song18", "Song20", "Song30", "Song40"))), + maxPricePerUniqueSong); + + Engine engine = Engine.builder(springsteen) + .build(); + + ISeq result = springsteen.codec() + .decoder() + .apply(engine.stream() + .limit(10) + .collect(EvolutionResult.toBestGenotype())); + + double cost = result.stream() + .mapToDouble(r -> r.price) + .sum(); + + int uniqueSongCount = result.stream() + .flatMap(r -> r.songs.stream()) + .collect(Collectors.toSet()) + .size(); + + double pricePerUniqueSong = cost / uniqueSongCount; + + System.out.println("Overall cost: " + cost); + System.out.println("Unique songs: " + uniqueSongCount); + System.out.println("Cost per song: " + pricePerUniqueSong); + System.out.println("Records: " + result.map(r -> r.name) + .toString(", ")); + + } + +} \ No newline at end of file diff --git a/src/main/java/domain/jenetics/SpringsteenRecord.java b/src/main/java/domain/jenetics/SpringsteenRecord.java new file mode 100644 index 0000000..ed21d5b --- /dev/null +++ b/src/main/java/domain/jenetics/SpringsteenRecord.java @@ -0,0 +1,23 @@ +package domain.jenetics; + +import static java.util.Objects.requireNonNull; + +import io.jenetics.util.ISeq; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class SpringsteenRecord { + + String name; + double price; + ISeq songs; + + public SpringsteenRecord(String name, double price, ISeq songs) { + this.name = requireNonNull(name); + this.price = price; + this.songs = requireNonNull(songs); + } + +} diff --git a/src/main/java/domain/jenetics/TravelingSalesman.java b/src/main/java/domain/jenetics/TravelingSalesman.java new file mode 100644 index 0000000..cb45e50 --- /dev/null +++ b/src/main/java/domain/jenetics/TravelingSalesman.java @@ -0,0 +1,64 @@ +package domain.jenetics; + +import io.jenetics.*; +import io.jenetics.engine.Codecs; +import io.jenetics.engine.Engine; +import io.jenetics.engine.EvolutionStatistics; + +import static io.jenetics.engine.EvolutionResult.toBestPhenotype; +import static io.jenetics.engine.Limits.bySteadyFitness; +import static java.lang.Math.PI; +import static java.lang.Math.abs; +import static java.lang.Math.sin; + +import java.util.stream.IntStream; + + +public class TravelingSalesman { + + private static final int STOPS = 50; + private static final double[][] ADJACENCE = matrix(STOPS); + + private static double[][] matrix(int stops) { + final double radius = 100.0; + double[][] matrix = new double[stops][stops]; + + for (int i = 0; i < stops; ++i) { + for (int j = 0; j < stops; ++j) { + matrix[i][j] = chord(stops, abs(i - j), radius); + } + } + return matrix; + } + + private static double chord(int stops, int i, double r) { + return 2.0 * r * abs(sin(PI * i / stops)); + } + + private static double dist(final int[] path) { + return IntStream.range(0, STOPS) + .mapToDouble(i -> ADJACENCE[path[i]][path[(i + 1) % STOPS]]) + .sum(); + } + + public static void main(String[] args) { + final Engine, Double> engine = Engine.builder(TravelingSalesman::dist, Codecs.ofPermutation(STOPS)) + .optimize(Optimize.MINIMUM) + .maximalPhenotypeAge(11) + .populationSize(500) + .alterers(new SwapMutator<>(0.2), new PartiallyMatchedCrossover<>(0.35)) + .build(); + + final EvolutionStatistics statistics = EvolutionStatistics.ofNumber(); + + final Phenotype, Double> best = engine.stream() + .limit(bySteadyFitness(15)) + .limit(250) + .peek(statistics) + .collect(toBestPhenotype()); + + System.out.println(statistics); + System.out.println(best); + } + +}