Skip to content

Instantly share code, notes, and snippets.

@NeatMonster
Created October 17, 2015 23:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save NeatMonster/40c6ddf26cf913b43cba to your computer and use it in GitHub Desktop.
Save NeatMonster/40c6ddf26cf913b43cba to your computer and use it in GitHub Desktop.
Another demonstration of Genetic Algorithms (GAs)
package fr.neatmonster.ga;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Polygon;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class Main extends JFrame {
private static final Random rnd = new Random();
// Genetics
private static int POPULATION_SIZE = 49;
private static float SELECTION_CUTOFF = 0.15f;
private static float MUTATION_CHANCE = 0.01f;
private static float MUTATION_AMOUNT = 0.10f;
private static boolean RANDOM_INHERITANCE = false;
private static boolean DIFF_SQUARED = true;
private static boolean FITTEST_SURVIVE = false;
// Graphics
private static int POLYGONS = 125;
private static int VERTICES = 3;
private static boolean FILL_POLYGONS = true;
// Internal
private static int GENE_SIZE;
private static String FILENAME = null;
private static int WIDTH;
private static int HEIGHT;
private static int STOP = -1;
// Logging
private static String LOG_FILE = null;
private static boolean LOG_IMAGES = false;
private static boolean LOG_FITNESS = false;
public static void main(final String[] argsArray) {
final List<String> args = Arrays.asList(argsArray);
final Iterator<String> it = args.iterator();
String s;
while (it.hasNext()) {
switch ((s = it.next()).toLowerCase()) {
case "--population-size":
POPULATION_SIZE = Integer.parseInt(it.next());
break;
case "--selection-cutoff":
SELECTION_CUTOFF = Float.parseFloat(it.next());
break;
case "--mutation-chance":
MUTATION_CHANCE = Float.parseFloat(it.next());
break;
case "--mutation-amount":
MUTATION_AMOUNT = Float.parseFloat(it.next());
break;
case "--random-inheritance":
RANDOM_INHERITANCE = Boolean.parseBoolean(it.next());
break;
case "--diff-squared":
DIFF_SQUARED = Boolean.parseBoolean(it.next());
break;
case "--fittest-survive":
FITTEST_SURVIVE = Boolean.parseBoolean(it.next());
break;
case "--polygons":
POLYGONS = Integer.parseInt(it.next());
break;
case "--vertices":
VERTICES = Integer.parseInt(it.next());
break;
case "--fill-polygons":
FILL_POLYGONS = Boolean.parseBoolean(it.next());
break;
case "--log-images":
LOG_IMAGES = Boolean.parseBoolean(it.next());
break;
case "--log-fitness":
LOG_FITNESS = Boolean.parseBoolean(it.next());
break;
case "--log-file":
LOG_FILE = it.next();
break;
case "--stop":
STOP = Integer.parseInt(it.next());
break;
case "--help":
System.out.println("Syntax: [options] <filename>");
System.out.println("Available Options:");
System.out.println(" --population-size (int)");
System.out.println(" --selection-cutoff (float)");
System.out.println(" --mutation-chance (float)");
System.out.println(" --mutation-amount (float)");
System.out.println(" --random-inheritance (boolean)");
System.out.println(" --diff-squared (boolean)");
System.out.println(" --fittest-survive (boolean)");
System.out.println("");
System.out.println(" --polygons (int)");
System.out.println(" --vertices (int)");
System.out.println(" --fill-polygons (boolean)");
System.out.println("");
System.out.println(" --log-images (boolean)");
System.out.println(" --log-fitness (boolean)");
System.out.println(" --log-file (string)");
System.out.println("");
System.out.println(" --stop (int)");
System.exit(0);
break;
default:
FILENAME = s;
break;
}
}
if (FILENAME == null) {
System.out.println("No filename specified!");
System.exit(0);
} else if (LOG_FILE == null)
LOG_FILE = FILENAME;
GENE_SIZE = 4 + (VERTICES > 1 ? VERTICES * 2 : 3);
System.out.println("Filename: " + FILENAME);
System.out.println("Options:");
System.out.println(" Population Size: " + POPULATION_SIZE);
System.out.println(" Selection Cutoff: " + SELECTION_CUTOFF);
System.out.println(" Mutation Chance: " + MUTATION_CHANCE);
System.out.println(" Mutation Amount: " + MUTATION_AMOUNT);
System.out.println(" Random Inheritance: " + RANDOM_INHERITANCE);
System.out.println(" Diff Squared: " + DIFF_SQUARED);
System.out.println(" Fittest Survice: " + FITTEST_SURVIVE);
System.out.println("");
System.out.println(" Polygons: " + POLYGONS);
System.out.println(" Vertices: " + VERTICES);
System.out.println(" Fill Polygons: " + FILL_POLYGONS);
System.out.println("");
System.out.println(" Log Images: " + LOG_IMAGES);
System.out.println(" Log Fitness: " + LOG_FITNESS);
System.out.println(" Log File: " + LOG_FILE);
System.out.println("");
System.out.println(" Stop: " + (STOP > 0 ? STOP : "never"));
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
new Main();
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
public class Individual {
private final float[][] genome;
private BufferedImage image = null;
private float fitness = -1f;
private int individualId;
public Individual() {
genome = new float[POLYGONS][GENE_SIZE];
for (int i = 0; i < POLYGONS; ++i) {
genome[i] = new float[GENE_SIZE];
for (int j = 0; j < GENE_SIZE; ++j)
genome[i][j] = rnd.nextFloat();
}
individualId = nextIndividualId++;
}
public Individual(final Individual mother, final Individual father) {
genome = new float[POLYGONS][GENE_SIZE];
final int inheritSplit = rnd.nextInt(POLYGONS);
for (int i = 0; i < POLYGONS; ++i) {
float[] inheritedGene;
if (RANDOM_INHERITANCE)
inheritedGene = i < inheritSplit ? mother.genome[i]
: father.genome[i];
else
inheritedGene = rnd.nextBoolean() ? mother.genome[i]
: father.genome[i];
System.arraycopy(inheritedGene, 0, genome[i], 0, GENE_SIZE);
for (int j = 0; j < GENE_SIZE; ++j)
if (rnd.nextFloat() < MUTATION_CHANCE) {
genome[i][j] += rnd.nextFloat() * 2 * MUTATION_AMOUNT
- MUTATION_AMOUNT;
if (genome[i][j] < 0f)
genome[i][j] = 0f;
if (genome[i][j] > 1f)
genome[i][j] = 1f;
}
}
}
public BufferedImage getRender() {
if (image != null)
return image;
image = new BufferedImage(WIDTH, HEIGHT,
BufferedImage.TYPE_INT_ARGB);
final Graphics g = image.getGraphics();
for (final float[] gene : genome) {
g.setColor(new Color(gene[0], gene[1], gene[2], gene[3]));
if (VERTICES == 1) {
final int x = (int) (gene[4] * WIDTH);
final int y = (int) (gene[5] * HEIGHT);
final int r = (int) (gene[6] * WIDTH);
if (FILL_POLYGONS)
g.fillOval(x - r / 2, y - r / 2, r, r);
else
g.drawOval(x - r / 2, y - r / 2, r, r);
} else if (VERTICES == 2) {
final int x = (int) (gene[4] * WIDTH);
final int y = (int) (gene[5] * HEIGHT);
final int w = (int) (gene[6] * WIDTH);
final int h = (int) (gene[7] * HEIGHT);
if (FILL_POLYGONS)
g.fillOval(x - w / 2, y - h / 2, w, h);
else
g.drawOval(x - w / 2, y - h / 2, w, h);
} else {
final int[] xs = new int[VERTICES];
final int[] ys = new int[VERTICES];
for (int i = 0; i < VERTICES; ++i) {
xs[i] = (int) (gene[4 + 2 * i] * 255);
ys[i] = (int) (gene[5 + 2 * i] * 255);
}
if (FILL_POLYGONS)
g.fillPolygon(new Polygon(xs, ys, VERTICES));
else
g.drawPolygon(new Polygon(xs, ys, VERTICES));
}
}
g.dispose();
return image;
}
public float getFitness() {
if (fitness != -1f)
return fitness;
getRender();
fitness = 0;
for (int y = 0; y < HEIGHT; ++y)
for (int x = 0; x < WIDTH; ++x) {
final int imgRGB = Main.this.image.getRGB(x, y);
final int imgA = (imgRGB >> 24) & 255;
final int imgR = (imgRGB >> 16) & 255;
final int imgG = (imgRGB >> 8) & 255;
final int imgB = imgRGB & 255;
final int thisRGB = image.getRGB(x, y);
final int thisA = (thisRGB >> 24) & 255;
final int thisR = (thisRGB >> 16) & 255;
final int thisG = (thisRGB >> 8) & 255;
final int thisB = thisRGB & 255;
if (DIFF_SQUARED) {
final int diffA = imgA - thisA;
fitness += diffA * diffA;
final int diffR = imgR - thisR;
fitness += diffR * diffR;
final int diffG = imgG - thisG;
fitness += diffG * diffG;
final int diffB = imgB - thisB;
fitness += diffB * diffB;
} else {
fitness += Math.abs(imgA - thisA);
fitness += Math.abs(imgR - thisR);
fitness += Math.abs(imgG - thisG);
fitness += Math.abs(imgB - thisB);
}
}
if (DIFF_SQUARED)
fitness = 1f - fitness / (WIDTH * HEIGHT * 4L * 256 * 256);
else
fitness = 1f - fitness / (WIDTH * HEIGHT * 4L * 256);
return fitness;
}
public int getIndividualId() {
return individualId;
}
}
private final Individual[] population;
private int generation = 0;
private BufferedImage image;
private final BufferedImage[] images;
private int nextIndividualId = 1;
public Main() throws IOException {
try {
image = ImageIO.read(new File(FILENAME));
} catch (final IOException e) {
System.out.println("Couldn't read '" + FILENAME + "'");
System.out.println(e.getMessage());
System.exit(0);
}
WIDTH = image.getWidth();
HEIGHT = image.getHeight();
population = new Individual[POPULATION_SIZE];
for (int i = 0; i < POPULATION_SIZE; ++i)
population[i] = new Individual();
setResizable(false);
setTitle("Genetic Algorithm");
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final int rows = (int) Math.sqrt(POPULATION_SIZE);
final int cols = POPULATION_SIZE / rows;
setLayout(new GridLayout(rows, cols));
images = new BufferedImage[POLYGONS];
for (int i = 0; i < POPULATION_SIZE; ++i) {
images[i] = new BufferedImage(WIDTH, HEIGHT,
BufferedImage.TYPE_INT_ARGB);
add(new JLabel(new ImageIcon(images[i])));
}
pack();
setVisible(true);
new Thread(new Runnable() {
@Override
public void run() {
while (STOP < 0 || generation < STOP) {
tick();
render();
}
}
}).start();
}
public void tick() {
if (generation == 0)
sort();
if (population.length > 1) {
final int size = population.length;
final int selectCount = (int) Math.floor(size * SELECTION_CUTOFF);
final int randomCount = FITTEST_SURVIVE ? size - 1 : size;
final Individual[] children = new Individual[randomCount];
for (int i = 0; i < randomCount; ++i)
children[i] = new Individual(population[i % selectCount],
population[rnd.nextInt(size)]);
System.arraycopy(children, 0, population, FITTEST_SURVIVE ? 1 : 0,
randomCount);
} else {
final Individual parent = population[0];
final Individual child = new Individual(parent, parent);
if (child.getFitness() > parent.getFitness())
population[0] = child;
}
sort();
++generation;
}
public void sort() {
Arrays.sort(population, new Comparator<Individual>() {
@Override
public int compare(final Individual i1, final Individual i2) {
final float cmp = i1.getFitness() - i2.getFitness();
if (cmp < 0f)
return 1;
if (cmp > 0f)
return -1;
return i1.getIndividualId() - i2.getIndividualId();
}
});
}
private float best = 0f;
public void render() {
setTitle("Genetic Algorithm [" + generation + "]");
for (int i = 0; i < POPULATION_SIZE; ++i) {
images[i].setData(population[i].getRender().getRaster());
final Graphics g = images[i].getGraphics();
g.drawString(Float.toString(population[i].getFitness()), 0, 10);
}
if ((STOP > 0 && generation == STOP) || population[0].getFitness() > best) {
best = population[0].getFitness();
if (LOG_IMAGES)
try {
if (!new File(LOG_FILE).exists())
new File(LOG_FILE).mkdir();
ImageIO.write(population[0].getRender(), "PNG",
new File(String.format(LOG_FILE + "/image%07d.png",
generation)));
} catch (IOException e) {
e.printStackTrace();
}
}
if (LOG_FITNESS)
try {
if (!new File(LOG_FILE + ".csv").exists())
new File(LOG_FILE + ".csv").createNewFile();
Files.write(Paths.get(LOG_FILE + ".csv"),
(generation + "," + best + "\n").getBytes(),
StandardOpenOption.APPEND);
} catch (IOException e) {
e.printStackTrace();
}
revalidate();
repaint();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment