From 7a32f5175bd25c161462d4b736735887494b9490 Mon Sep 17 00:00:00 2001 From: Massimiliano Zanoni Date: Fri, 6 Dec 2019 14:32:31 +0100 Subject: [PATCH] Lecture 13 --- .../Faces_interactiveselection/Button.pde | 49 ++++++ .../Faces_interactiveselection/DNA.pde | 43 +++++ .../Faces_interactiveselection/Face.pde | 97 ++++++++++++ .../Faces_interactiveselection.pde | 44 ++++++ .../Faces_interactiveselection/Population.pde | 95 +++++++++++ .../Faces_interactiveselection/Rectangle.pde | 21 +++ Lectures/Lecture13/Shakespeare/DNA.pde | 65 ++++++++ Lectures/Lecture13/Shakespeare/Population.pde | 122 +++++++++++++++ .../Lecture13/Shakespeare/Shakespeare.pde | 80 ++++++++++ .../Lecture13/Shakespeare/sketch.properties | 2 + Lectures/Lecture13/SmartRockets/DNA.pde | 54 +++++++ .../Lecture13/SmartRockets/Population.pde | 99 ++++++++++++ Lectures/Lecture13/SmartRockets/Rocket.pde | 102 ++++++++++++ .../Lecture13/SmartRockets/SmartRockets.pde | 61 ++++++++ .../Lecture13/SmartRocketsObstacles/DNA.pde | 65 ++++++++ .../SmartRocketsObstacles/Obstacle.pde | 37 +++++ .../SmartRocketsObstacles/Population.pde | 110 +++++++++++++ .../SmartRocketsObstacles/Rocket.pde | 147 ++++++++++++++++++ .../SmartRocketsObstacles.pde | 93 +++++++++++ .../SmartRocketsObstacles/sketch.properties | 1 + 20 files changed, 1387 insertions(+) create mode 100644 Lectures/Lecture13/Faces_interactiveselection/Button.pde create mode 100644 Lectures/Lecture13/Faces_interactiveselection/DNA.pde create mode 100644 Lectures/Lecture13/Faces_interactiveselection/Face.pde create mode 100644 Lectures/Lecture13/Faces_interactiveselection/Faces_interactiveselection.pde create mode 100644 Lectures/Lecture13/Faces_interactiveselection/Population.pde create mode 100644 Lectures/Lecture13/Faces_interactiveselection/Rectangle.pde create mode 100644 Lectures/Lecture13/Shakespeare/DNA.pde create mode 100644 Lectures/Lecture13/Shakespeare/Population.pde create mode 100644 Lectures/Lecture13/Shakespeare/Shakespeare.pde create mode 100644 Lectures/Lecture13/Shakespeare/sketch.properties create mode 100644 Lectures/Lecture13/SmartRockets/DNA.pde create mode 100644 Lectures/Lecture13/SmartRockets/Population.pde create mode 100644 Lectures/Lecture13/SmartRockets/Rocket.pde create mode 100644 Lectures/Lecture13/SmartRockets/SmartRockets.pde create mode 100644 Lectures/Lecture13/SmartRocketsObstacles/DNA.pde create mode 100644 Lectures/Lecture13/SmartRocketsObstacles/Obstacle.pde create mode 100644 Lectures/Lecture13/SmartRocketsObstacles/Population.pde create mode 100644 Lectures/Lecture13/SmartRocketsObstacles/Rocket.pde create mode 100644 Lectures/Lecture13/SmartRocketsObstacles/SmartRocketsObstacles.pde create mode 100644 Lectures/Lecture13/SmartRocketsObstacles/sketch.properties diff --git a/Lectures/Lecture13/Faces_interactiveselection/Button.pde b/Lectures/Lecture13/Faces_interactiveselection/Button.pde new file mode 100644 index 0000000..e0a1bed --- /dev/null +++ b/Lectures/Lecture13/Faces_interactiveselection/Button.pde @@ -0,0 +1,49 @@ + + +class Button { + Rectangle r; // Button's rectangle + String txt; // Button's text + boolean clickedOn; // Did i click on it? + boolean rolloverOn; // Did i rollover it? + + Button(int x, int y, int w, int h, String s) { + r = new Rectangle(x,y,w,h); + txt = s; + } + + void display() { + // Draw rectangle and text based on whether rollover or clicked + rectMode(CORNER); + stroke(0); noFill(); + if (rolloverOn) fill(0.5); + if (clickedOn) fill(0); + rect(r.x,r.y,r.width,r.height); + float b = 0.0; + if (clickedOn) b = 1; + else if (rolloverOn) b = 0.2; + else b = 0; + fill(b); + textAlign(LEFT); + text(txt,r.x+10,r.y+14); + + } + + + // Methods to check rollover, clicked, or released (must be called from appropriate + // Places in draw, mousePressed, mouseReleased + boolean rollover(int mx, int my) { + if (r.contains(mx,my)) rolloverOn = true; + else rolloverOn = false; + return rolloverOn; + } + + boolean clicked(int mx, int my) { + if (r.contains(mx,my)) clickedOn = true; + return clickedOn; + } + + void released() { + clickedOn = false; + } + +} diff --git a/Lectures/Lecture13/Faces_interactiveselection/DNA.pde b/Lectures/Lecture13/Faces_interactiveselection/DNA.pde new file mode 100644 index 0000000..b75223c --- /dev/null +++ b/Lectures/Lecture13/Faces_interactiveselection/DNA.pde @@ -0,0 +1,43 @@ + +class DNA { + + // The genetic sequence + float[] genes; + int len = 20; // Arbitrary length + + //Constructor (makes a random DNA) + DNA() { + // DNA is random floating point values between 0 and 1 (!!) + genes = new float[len]; + for (int i = 0; i < genes.length; i++) { + genes[i] = random(0,1); + } + } + + DNA(float[] newgenes) { + genes = newgenes; + } + + + // Crossover + // Creates new DNA sequence from two (this & + DNA crossover(DNA partner) { + float[] child = new float[genes.length]; + int crossover = int(random(genes.length)); + for (int i = 0; i < genes.length; i++) { + if (i > crossover) child[i] = genes[i]; + else child[i] = partner.genes[i]; + } + DNA newgenes = new DNA(child); + return newgenes; + } + + // Based on a mutation probability, picks a new random character in array spots + void mutate(float m) { + for (int i = 0; i < genes.length; i++) { + if (random(1) < m) { + genes[i] = random(0,1); + } + } + } +} diff --git a/Lectures/Lecture13/Faces_interactiveselection/Face.pde b/Lectures/Lecture13/Faces_interactiveselection/Face.pde new file mode 100644 index 0000000..efac86a --- /dev/null +++ b/Lectures/Lecture13/Faces_interactiveselection/Face.pde @@ -0,0 +1,97 @@ + +// The class for our "face", contains DNA sequence, fitness value, position on screen + +// Fitness Function f(t) = t (where t is "time" mouse rolls over face) + +class Face { + + DNA dna; // Face's DNA + float fitness; // How good is this face? + float x, y; // Position on screen + int wh = 70; // Size of square enclosing face + boolean rolloverOn; // Are we rolling over this face? + + Rectangle r; + + // Create a new face + Face(DNA dna_, float x_, float y_) { + dna = dna_; + x = x_; + y = y_; + fitness = 1; + // Using java.awt.Rectangle (see: http://java.sun.com/j2se/1.4.2/docs/api/java/awt/Rectangle.html) + r = new Rectangle(int(x-wh/2), int(y-wh/2), int(wh), int(wh)); + } + + // Display the face + void display() { + // We are using the face's DNA to pick properties for this face + // such as: head size, color, eye position, etc. + // Now, since every gene is a floating point between 0 and 1, we map the values + float r = map(dna.genes[0],0,1,0,70); + color c = color(dna.genes[1],dna.genes[2],dna.genes[3]); + float eye_y = map(dna.genes[4],0,1,0,5); + float eye_x = map(dna.genes[5],0,1,0,10); + float eye_size = map(dna.genes[5],0,1,0,10); + color eyecolor = color(dna.genes[4],dna.genes[5],dna.genes[6]); + color mouthColor = color(dna.genes[7],dna.genes[8],dna.genes[9]); + float mouth_y = map(dna.genes[5],0,1,0,25); + float mouth_x = map(dna.genes[5],0,1,-25,25); + float mouthw = map(dna.genes[5],0,1,0,50); + float mouthh = map(dna.genes[5],0,1,0,10); + + // Once we calculate all the above properties, we use those variables to draw rects, ellipses, etc. + pushMatrix(); + translate(x, y); + noStroke(); + + // Draw the head + smooth(); + fill(c); + ellipseMode(CENTER); + ellipse(0, 0, r, r); + + // Draw the eyes + fill(eyecolor); + rectMode(CENTER); + rect(-eye_x, -eye_y, eye_size, eye_size); + rect( eye_x, -eye_y, eye_size, eye_size); + + // Draw the mouth + fill(mouthColor); + rectMode(CENTER); + rect(mouth_x, mouth_y, mouthw, mouthh); + + // Draw the bounding box + stroke(0.25); + if (rolloverOn) fill(0, 0.25); + else noFill(); + rectMode(CENTER); + rect(0, 0, wh, wh); + popMatrix(); + + // Display fitness value + textAlign(CENTER); + if (rolloverOn) fill(0); + else fill(0.25); + text(int(fitness), x, y+55); + } + + float getFitness() { + return fitness; + } + + DNA getDNA() { + return dna; + } + + // Increment fitness if mouse is rolling over face + void rollover(int mx, int my) { + if (r.contains(mx, my)) { + rolloverOn = true; + fitness += 0.25; + } else { + rolloverOn = false; + } + } +} diff --git a/Lectures/Lecture13/Faces_interactiveselection/Faces_interactiveselection.pde b/Lectures/Lecture13/Faces_interactiveselection/Faces_interactiveselection.pde new file mode 100644 index 0000000..80624ad --- /dev/null +++ b/Lectures/Lecture13/Faces_interactiveselection/Faces_interactiveselection.pde @@ -0,0 +1,44 @@ + + +Population population; +Button button; + +void setup() { + size(800,200); + colorMode(RGB,1.0); + smooth(); + int popmax = 10; + float mutationRate = 0.05; // A pretty high mutation rate here, our population is rather small we need to enforce variety + // Create a population with a target phrase, mutation rate, and population max + population = new Population(mutationRate,popmax); + // A simple button class + button = new Button(15,150,160,20, "evolve new generation"); +} + +void draw() { + background(1.0); + // Display the faces + population.display(); + population.rollover(mouseX,mouseY); + // Display some text + textAlign(LEFT); + fill(0); + text("Generation #:" + population.getGenerations(),15,190); + + // Display the button + button.display(); + button.rollover(mouseX,mouseY); + +} + +// If the button is clicked, evolve next generation +void mousePressed() { + if (button.clicked(mouseX,mouseY)) { + population.selection(); + population.reproduction(); + } +} + +void mouseReleased() { + button.released(); +} diff --git a/Lectures/Lecture13/Faces_interactiveselection/Population.pde b/Lectures/Lecture13/Faces_interactiveselection/Population.pde new file mode 100644 index 0000000..6121b5c --- /dev/null +++ b/Lectures/Lecture13/Faces_interactiveselection/Population.pde @@ -0,0 +1,95 @@ + +// A class to describe a population of faces +// this hasn't changed very much from example to example + +class Population { + + float mutationRate; // Mutation rate + Face[] population; // array to hold the current population + ArrayList matingPool; // ArrayList which we will use for our "mating pool" + int generations; // Number of generations + + // Create the population + Population(float m, int num) { + mutationRate = m; + population = new Face[num]; + matingPool = new ArrayList(); + generations = 0; + for (int i = 0; i < population.length; i++) { + population[i] = new Face(new DNA(), 50+i*75, 60); + } + } + + // Display all faces + void display() { + for (int i = 0; i < population.length; i++) { + population[i].display(); + } + } + + // Are we rolling over any of the faces? + void rollover(int mx, int my) { + for (int i = 0; i < population.length; i++) { + population[i].rollover(mx, my); + } + } + + // Generate a mating pool + void selection() { + // Clear the ArrayList + matingPool.clear(); + + // Calculate total fitness of whole population + float maxFitness = getMaxFitness(); + + // Calculate fitness for each member of the population (scaled to value between 0 and 1) + // Based on fitness, each member will get added to the mating pool a certain number of times + // A higher fitness = more entries to mating pool = more likely to be picked as a parent + // A lower fitness = fewer entries to mating pool = less likely to be picked as a parent + for (int i = 0; i < population.length; i++) { + float fitnessNormal = map(population[i].getFitness(), 0, maxFitness, 0, 1); + int n = (int) (fitnessNormal * 100); // Arbitrary multiplier + for (int j = 0; j < n; j++) { + matingPool.add(population[i]); + } + } + } + + // Making the next generation + void reproduction() { + // Refill the population with children from the mating pool + for (int i = 0; i < population.length; i++) { + // Sping the wheel of fortune to pick two parents + int m = int(random(matingPool.size())); + int d = int(random(matingPool.size())); + // Pick two parents + Face mom = matingPool.get(m); + Face dad = matingPool.get(d); + // Get their genes + DNA momgenes = mom.getDNA(); + DNA dadgenes = dad.getDNA(); + // Mate their genes + DNA child = momgenes.crossover(dadgenes); + // Mutate their genes + child.mutate(mutationRate); + // Fill the new population with the new child + population[i] = new Face(child, 50+i*75, 60); + } + generations++; + } + + int getGenerations() { + return generations; + } + + // Find highest fintess for the population + float getMaxFitness() { + float record = 0; + for (int i = 0; i < population.length; i++) { + if (population[i].getFitness() > record) { + record = population[i].getFitness(); + } + } + return record; + } +} diff --git a/Lectures/Lecture13/Faces_interactiveselection/Rectangle.pde b/Lectures/Lecture13/Faces_interactiveselection/Rectangle.pde new file mode 100644 index 0000000..7f6ad96 --- /dev/null +++ b/Lectures/Lecture13/Faces_interactiveselection/Rectangle.pde @@ -0,0 +1,21 @@ +// Re-implementing java.awt.Rectangle +// so JS mode works + +class Rectangle { + int x; + int y; + int width; + int height; + + Rectangle(int x_, int y_, int w, int h) { + x = x_; + y = y_; + width = w; + height = h; + } + + boolean contains(int px, int py) { + return (px > x && px < x + width && py > y && py < y + height); + } + +} diff --git a/Lectures/Lecture13/Shakespeare/DNA.pde b/Lectures/Lecture13/Shakespeare/DNA.pde new file mode 100644 index 0000000..8cffa1c --- /dev/null +++ b/Lectures/Lecture13/Shakespeare/DNA.pde @@ -0,0 +1,65 @@ + +// A class to describe a psuedo-DNA, i.e. genotype +// Here, a virtual organism's DNA is an array of character. +// Functionality: +// -- convert DNA into a string +// -- calculate DNA's "fitness" +// -- mate DNA with another set of DNA +// -- mutate DNA + + +class DNA { + + // The genetic sequence + char[] genes; + + float fitness; + + // Constructor (makes a random DNA) + DNA(int num) { + genes = new char[num]; + for (int i = 0; i < genes.length; i++) { + genes[i] = (char) random(32,128); // Pick from range of chars + } + } + + // Converts character array to a String + String getPhrase() { + return new String(genes); + } + + // Fitness function (returns floating point % of "correct" characters) + void fitness (String target) { + int score = 0; + for (int i = 0; i < genes.length; i++) { + if (genes[i] == target.charAt(i)) { + score++; + } + } + fitness = (float)score / (float)target.length(); + } + + // Crossover + DNA crossover(DNA partner) { + // A new child + DNA child = new DNA(genes.length); + + int midpoint = int(random(genes.length)); // Pick a midpoint + + // Half from one, half from the other + for (int i = 0; i < genes.length; i++) { + if (i > midpoint) child.genes[i] = genes[i]; + else child.genes[i] = partner.genes[i]; + } + return child; + } + + // Based on a mutation probability, picks a new random character + void mutate(float mutationRate) { + for (int i = 0; i < genes.length; i++) { + if (random(1) < mutationRate) { + genes[i] = (char) random(32,128); + } + } + } +} diff --git a/Lectures/Lecture13/Shakespeare/Population.pde b/Lectures/Lecture13/Shakespeare/Population.pde new file mode 100644 index 0000000..e706d43 --- /dev/null +++ b/Lectures/Lecture13/Shakespeare/Population.pde @@ -0,0 +1,122 @@ + +// A class to describe a population of virtual organisms +// In this case, each organism is just an instance of a DNA object + + +class Population { + + float mutationRate; // Mutation rate + DNA[] population; // Array to hold the current population + ArrayList matingPool; // ArrayList which we will use for our "mating pool" + String target; // Target phrase + int generations; // Number of generations + boolean finished; // Are we finished evolving? + int perfectScore; + + Population(String p, float m, int num) { + target = p; + mutationRate = m; + population = new DNA[num]; + for (int i = 0; i < population.length; i++) { + population[i] = new DNA(target.length()); + } + calcFitness(); + matingPool = new ArrayList(); + finished = false; + generations = 0; + + perfectScore = int(pow(2,target.length())); + } + + // Fill our fitness array with a value for every member of the population + void calcFitness() { + for (int i = 0; i < population.length; i++) { + population[i].fitness(target); + } + } + + // Generate a mating pool + void naturalSelection() { + // Clear the ArrayList + matingPool.clear(); + + float maxFitness = 0; + for (int i = 0; i < population.length; i++) { + if (population[i].fitness > maxFitness) { + maxFitness = population[i].fitness; + } + } + + // Based on fitness, each member will get added to the mating pool a certain number of times + // a higher fitness = more entries to mating pool = more likely to be picked as a parent + // a lower fitness = fewer entries to mating pool = less likely to be picked as a parent + for (int i = 0; i < population.length; i++) { + + float fitness = map(population[i].fitness,0,maxFitness,0,1); + int n = int(fitness * 100); // Arbitrary multiplier, we can also use monte carlo method + for (int j = 0; j < n; j++) { // and pick two random numbers + matingPool.add(population[i]); + } + } + } + + // Create a new generation + void generate() { + // Refill the population with children from the mating pool + for (int i = 0; i < population.length; i++) { + int a = int(random(matingPool.size())); + int b = int(random(matingPool.size())); + DNA partnerA = matingPool.get(a); + DNA partnerB = matingPool.get(b); + DNA child = partnerA.crossover(partnerB); + child.mutate(mutationRate); + population[i] = child; + } + generations++; + } + + + // Compute the current "most fit" member of the population + String getBest() { + float worldrecord = 0.0f; + int index = 0; + for (int i = 0; i < population.length; i++) { + if (population[i].fitness > worldrecord) { + index = i; + worldrecord = population[i].fitness; + } + } + + if (worldrecord == perfectScore ) finished = true; + return population[index].getPhrase(); + } + + boolean finished() { + return finished; + } + + int getGenerations() { + return generations; + } + + // Compute average fitness for the population + float getAverageFitness() { + float total = 0; + for (int i = 0; i < population.length; i++) { + total += population[i].fitness; + } + return total / (population.length); + } + + String allPhrases() { + String everything = ""; + + int displayLimit = min(population.length,50); + + + for (int i = 0; i < displayLimit; i++) { + everything += population[i].getPhrase() + "\n"; + } + return everything; + } +} diff --git a/Lectures/Lecture13/Shakespeare/Shakespeare.pde b/Lectures/Lecture13/Shakespeare/Shakespeare.pde new file mode 100644 index 0000000..3709211 --- /dev/null +++ b/Lectures/Lecture13/Shakespeare/Shakespeare.pde @@ -0,0 +1,80 @@ + +// setup() +// # Step 1: The Population +// # Create an empty population (an array or ArrayList) +// # Fill it with DNA encoded objects (pick random values to start) + +// draw() +// # Step 1: Selection +// # Create an empty mating pool (an empty ArrayList) +// # For every member of the population, evaluate its fitness based on some criteria / function, +// and add it to the mating pool in a manner consistant with its fitness, i.e. the more fit it +// is the more times it appears in the mating pool, in order to be more likely picked for reproduction. + +// # Step 2: Reproduction Create a new empty population +// # Fill the new population by executing the following steps: +// 1. Pick two "parent" objects from the mating pool. +// 2. Crossover -- create a "child" object by mating these two parents. +// 3. Mutation -- mutate the child's DNA based on a given probability. +// 4. Add the child object to the new population. +// # Replace the old population with the new population +// +// # Rinse and repeat + + +PFont f; +String target; +int popmax; +float mutationRate; +Population population; + +void setup() { + size(800, 200); + f = createFont("Courier", 32, true); + target = "To be or not to be."; + popmax = 150; + mutationRate = 0.01; + + // Create a populationation with a target phrase, mutation rate, and populationation max + population = new Population(target, mutationRate, popmax); +} + +void draw() { + // Generate mating pool + population.naturalSelection(); + //Create next generation + population.generate(); + // Calculate fitness + population.calcFitness(); + displayInfo(); + + // If we found the target phrase, stop + if (population.finished()) { + println(millis()/1000.0); + noLoop(); + } +} + +void displayInfo() { + background(255); + // Display current status of populationation + String answer = population.getBest(); + textFont(f); + textAlign(LEFT); + fill(0); + + + textSize(16); + text("Best phrase:",20,30); + textSize(32); + text(answer, 20, 75); + + textSize(12); + text("total generations: " + population.getGenerations(), 20, 140); + text("average fitness: " + nf(population.getAverageFitness(), 0, 2), 20, 155); + text("total populationation: " + popmax, 20, 170); + text("mutation rate: " + int(mutationRate * 100) + "%", 20, 185); + + textSize(10); + text("All phrases:\n" + population.allPhrases(), 650, 10); +} diff --git a/Lectures/Lecture13/Shakespeare/sketch.properties b/Lectures/Lecture13/Shakespeare/sketch.properties new file mode 100644 index 0000000..b3cbe60 --- /dev/null +++ b/Lectures/Lecture13/Shakespeare/sketch.properties @@ -0,0 +1,2 @@ +mode.id=processing.mode.java.JavaMode +mode=Standard diff --git a/Lectures/Lecture13/SmartRockets/DNA.pde b/Lectures/Lecture13/SmartRockets/DNA.pde new file mode 100644 index 0000000..221e054 --- /dev/null +++ b/Lectures/Lecture13/SmartRockets/DNA.pde @@ -0,0 +1,54 @@ + +// DNA is an array of vectors + +class DNA { + + // The genetic sequence + PVector[] genes; + + // The maximum strength of the forces + float maxforce = 0.1; + + // Constructor (makes a DNA of random PVectors) + DNA() { + genes = new PVector[lifetime]; + for (int i = 0; i < genes.length; i++) { + float angle = random(TWO_PI); + genes[i] = new PVector(cos(angle), sin(angle)); + genes[i].mult(random(0, maxforce)); + } + } + + // Constructor #2, creates the instance based on an existing array + DNA(PVector[] newgenes) { + // We could make a copy if necessary + // genes = (PVector []) newgenes.clone(); + genes = newgenes; + } + + // CROSSOVER + // Creates new DNA sequence from two (this & and a partner) + DNA crossover(DNA partner) { + PVector[] child = new PVector[genes.length]; + // Pick a midpoint + int crossover = int(random(genes.length)); + // Take "half" from one and "half" from the other + for (int i = 0; i < genes.length; i++) { + if (i > crossover) child[i] = genes[i]; + else child[i] = partner.genes[i]; + } + DNA newgenes = new DNA(child); + return newgenes; + } + + // Based on a mutation probability, picks a new random Vector + void mutate(float m) { + for (int i = 0; i < genes.length; i++) { + if (random(1) < m) { + float angle = random(TWO_PI); + genes[i] = new PVector(cos(angle), sin(angle)); + genes[i].mult(random(0, maxforce)); + } + } + } +} diff --git a/Lectures/Lecture13/SmartRockets/Population.pde b/Lectures/Lecture13/SmartRockets/Population.pde new file mode 100644 index 0000000..480045a --- /dev/null +++ b/Lectures/Lecture13/SmartRockets/Population.pde @@ -0,0 +1,99 @@ + + +// A class to describe a population of "creatures" + +class Population { + + float mutationRate; // Mutation rate + Rocket[] population; // Array to hold the current population + ArrayList matingPool; // ArrayList which we will use for our "mating pool" + int generations; // Number of generations + + // Initialize the population + Population(float m, int num) { + mutationRate = m; + population = new Rocket[num]; + matingPool = new ArrayList(); + generations = 0; + //make a new set of creatures + for (int i = 0; i < population.length; i++) { + PVector location = new PVector(width/2,height+20); + population[i] = new Rocket(location, new DNA()); + } + } + + void live () { + // Run every rocket + for (int i = 0; i < population.length; i++) { + population[i].run(); + } + } + + // Calculate fitness for each creature + void fitness() { + for (int i = 0; i < population.length; i++) { + population[i].fitness(); + } + } + + // Generate a mating pool + void selection() { + // Clear the ArrayList + matingPool.clear(); + + // Calculate total fitness of whole population + float maxFitness = getMaxFitness(); + + // Calculate fitness for each member of the population (scaled to value between 0 and 1) + // Based on fitness, each member will get added to the mating pool a certain number of times + // A higher fitness = more entries to mating pool = more likely to be picked as a parent + // A lower fitness = fewer entries to mating pool = less likely to be picked as a parent + for (int i = 0; i < population.length; i++) { + float fitnessNormal = map(population[i].getFitness(),0,maxFitness,0,1); + int n = (int) (fitnessNormal * 100); // Arbitrary multiplier + for (int j = 0; j < n; j++) { + matingPool.add(population[i]); + } + } + } + + // Making the next generation + void reproduction() { + // Refill the population with children from the mating pool + for (int i = 0; i < population.length; i++) { + // Sping the wheel of fortune to pick two parents + int m = int(random(matingPool.size())); + int d = int(random(matingPool.size())); + // Pick two parents + Rocket mom = matingPool.get(m); + Rocket dad = matingPool.get(d); + // Get their genes + DNA momgenes = mom.getDNA(); + DNA dadgenes = dad.getDNA(); + // Mate their genes + DNA child = momgenes.crossover(dadgenes); + // Mutate their genes + child.mutate(mutationRate); + // Fill the new population with the new child + PVector location = new PVector(width/2,height+20); + population[i] = new Rocket(location, child); + } + generations++; + } + + int getGenerations() { + return generations; + } + + // Find highest fintess for the population + float getMaxFitness() { + float record = 0; + for (int i = 0; i < population.length; i++) { + if(population[i].getFitness() > record) { + record = population[i].getFitness(); + } + } + return record; + } + +} diff --git a/Lectures/Lecture13/SmartRockets/Rocket.pde b/Lectures/Lecture13/SmartRockets/Rocket.pde new file mode 100644 index 0000000..10d9643 --- /dev/null +++ b/Lectures/Lecture13/SmartRockets/Rocket.pde @@ -0,0 +1,102 @@ + +// Rocket class -- this is just like our Boid / Particle class +// the only difference is that it has DNA & fitness + +class Rocket { + + // All of our physics stuff + PVector location; + PVector velocity; + PVector acceleration; + + // Size + float r; + + // Fitness and DNA + float fitness; + DNA dna; + // To count which force we're on in the genes + int geneCounter = 0; + + boolean hitTarget = false; // Did I reach the target + + //constructor + Rocket(PVector l, DNA dna_) { + acceleration = new PVector(); + velocity = new PVector(); + location = l.get(); + r = 4; + dna = dna_; + } + + // Fitness function + // fitness = one divided by distance squared + void fitness() { + float d = dist(location.x, location.y, target.x, target.y); + fitness = pow(1/d, 2); + } + + // Run in relation to all the obstacles + // If I'm stuck, don't bother updating or checking for intersection + void run() { + checkTarget(); // Check to see if we've reached the target + if (!hitTarget) { + applyForce(dna.genes[geneCounter]); + geneCounter = (geneCounter + 1) % dna.genes.length; + update(); + } + display(); + } + + // Did I make it to the target? + void checkTarget() { + float d = dist(location.x, location.y, target.x, target.y); + if (d < 12) { + hitTarget = true; + } + } + + void applyForce(PVector f) { + acceleration.add(f); + } + + void update() { + velocity.add(acceleration); + location.add(velocity); + acceleration.mult(0); + } + + void display() { + float theta = velocity.heading2D() + PI/2; + fill(200, 100); + stroke(0); + pushMatrix(); + translate(location.x, location.y); + rotate(theta); + + // Thrusters + rectMode(CENTER); + fill(0); + rect(-r/2, r*2, r/2, r); + rect(r/2, r*2, r/2, r); + + // Rocket body + fill(175); + beginShape(TRIANGLES); + vertex(0, -r*2); + vertex(-r, r*2); + vertex(r, r*2); + endShape(); + + popMatrix(); + } + + float getFitness() { + return fitness; + } + + DNA getDNA() { + return dna; + } + +} diff --git a/Lectures/Lecture13/SmartRockets/SmartRockets.pde b/Lectures/Lecture13/SmartRockets/SmartRockets.pde new file mode 100644 index 0000000..3d08ef4 --- /dev/null +++ b/Lectures/Lecture13/SmartRockets/SmartRockets.pde @@ -0,0 +1,61 @@ + + +int lifetime; // How long should each generation live + +Population population; // Population + +int lifeCounter; // Timer for cycle of generation + +PVector target; // Target location + +void setup() { + size(800, 200); + smooth(); + + // The number of cycles we will allow a generation to live + lifetime = 200; + + // Initialize variables + lifeCounter = 0; + + target = new PVector(width/2, 24); + + // Create a population with a mutation rate, and population max + float mutationRate = 0.01; + population = new Population(mutationRate, 50); + +} + +void draw() { + background(255); + + // Draw the start and target locations + fill(0); + ellipse(target.x,target.y,24,24); + + + // If the generation hasn't ended yet + if (lifeCounter < lifetime) { + population.live(); + lifeCounter++; + // Otherwise a new generation + } + else { + lifeCounter = 0; + population.fitness(); + population.selection(); + population.reproduction(); + } + + // Display some info + fill(0); + text("Generation #: " + population.getGenerations(), 10, 18); + text("Cycles left: " + (lifetime-lifeCounter), 10, 36); +} + +// Move the target if the mouse is pressed +// System will adapt to new target +void mousePressed() { + target.x = mouseX; + target.y = mouseY; +} diff --git a/Lectures/Lecture13/SmartRocketsObstacles/DNA.pde b/Lectures/Lecture13/SmartRocketsObstacles/DNA.pde new file mode 100644 index 0000000..6243526 --- /dev/null +++ b/Lectures/Lecture13/SmartRocketsObstacles/DNA.pde @@ -0,0 +1,65 @@ +// Pathfinding w/ Genetic Algorithms +// Daniel Shiffman + +// DNA is an array of vectors + +class DNA { + + // The genetic sequence + PVector[] genes; + + // The maximum strength of the forces + float maxforce = 0.1; + + // Constructor (makes a DNA of random PVectors) + DNA() { + genes = new PVector[lifetime]; + for (int i = 0; i < genes.length; i++) { + float angle = random(TWO_PI); + genes[i] = new PVector(cos(angle), sin(angle)); + genes[i].mult(random(0, maxforce)); + } + + // Let's give each Rocket an extra boost of strength for its first frame + genes[0].normalize(); + } + + // Constructor #2, creates the instance based on an existing array + DNA(PVector[] newgenes) { + // We could make a copy if necessary + // genes = (PVector []) newgenes.clone(); + genes = newgenes; + } + + // CROSSOVER + // Creates new DNA sequence from two (this & and a partner) + DNA crossover(DNA partner) { + PVector[] child = new PVector[genes.length]; + // Pick a midpoint + int crossover = int(random(genes.length)); + // Take "half" from one and "half" from the other + for (int i = 0; i < genes.length; i++) { + if (i > crossover) child[i] = genes[i]; + else child[i] = partner.genes[i]; + } + DNA newgenes = new DNA(child); + return newgenes; + } + + // Based on a mutation probability, picks a new random Vector + void mutate(float m) { + for (int i = 0; i < genes.length; i++) { + if (random(1) < m) { + float angle = random(TWO_PI); + genes[i] = new PVector(cos(angle), sin(angle)); + genes[i].mult(random(0, maxforce)); + // float angle = random(-0.1,0.1); + // genes[i].rotate(angle); + // float factor = random(0.9,1.1); + // genes[i].mult(factor); + if (i ==0) genes[i].normalize(); + } + } + } +} + diff --git a/Lectures/Lecture13/SmartRocketsObstacles/Obstacle.pde b/Lectures/Lecture13/SmartRocketsObstacles/Obstacle.pde new file mode 100644 index 0000000..e8d4207 --- /dev/null +++ b/Lectures/Lecture13/SmartRocketsObstacles/Obstacle.pde @@ -0,0 +1,37 @@ +// Pathfinding w/ Genetic Algorithms +// Daniel Shiffman + +// A class for an obstacle, just a simple rectangle that is drawn +// and can check if a Rocket touches it + +// Also using this class for target location + + +class Obstacle { + + PVector location; + float w,h; + + Obstacle(float x, float y, float w_, float h_) { + location = new PVector(x,y); + w = w_; + h = h_; + } + + void display() { + stroke(0); + fill(175); + strokeWeight(2); + rectMode(CORNER); + rect(location.x,location.y,w,h); + } + + boolean contains(PVector spot) { + if (spot.x > location.x && spot.x < location.x + w && spot.y > location.y && spot.y < location.y + h) { + return true; + } else { + return false; + } + } + +} diff --git a/Lectures/Lecture13/SmartRocketsObstacles/Population.pde b/Lectures/Lecture13/SmartRocketsObstacles/Population.pde new file mode 100644 index 0000000..b5a8ccb --- /dev/null +++ b/Lectures/Lecture13/SmartRocketsObstacles/Population.pde @@ -0,0 +1,110 @@ +// Pathfinding w/ Genetic Algorithms +// Daniel Shiffman + +// A class to describe a population of "creatures" + +class Population { + + float mutationRate; // Mutation rate + Rocket[] population; // Array to hold the current population + ArrayList matingPool; // ArrayList which we will use for our "mating pool" + int generations; // Number of generations + + // Initialize the population + Population(float m, int num) { + mutationRate = m; + population = new Rocket[num]; + matingPool = new ArrayList(); + generations = 0; + //make a new set of creatures + for (int i = 0; i < population.length; i++) { + PVector location = new PVector(width/2,height+20); + population[i] = new Rocket(location, new DNA(),population.length); + } + } + + void live (ArrayList os) { + // For every creature + for (int i = 0; i < population.length; i++) { + // If it finishes, mark it down as done! + population[i].checkTarget(); + population[i].run(os); + } + } + + // Did anything finish? + boolean targetReached() { + for (int i = 0; i < population.length; i++) { + if (population[i].hitTarget) return true; + } + return false; + } + + // Calculate fitness for each creature + void fitness() { + for (int i = 0; i < population.length; i++) { + population[i].fitness(); + } + } + + // Generate a mating pool + void selection() { + // Clear the ArrayList + matingPool.clear(); + + // Calculate total fitness of whole population + float maxFitness = getMaxFitness(); + + // Calculate fitness for each member of the population (scaled to value between 0 and 1) + // Based on fitness, each member will get added to the mating pool a certain number of times + // A higher fitness = more entries to mating pool = more likely to be picked as a parent + // A lower fitness = fewer entries to mating pool = less likely to be picked as a parent + for (int i = 0; i < population.length; i++) { + float fitnessNormal = map(population[i].getFitness(),0,maxFitness,0,1); + int n = (int) (fitnessNormal * 100); // Arbitrary multiplier + for (int j = 0; j < n; j++) { + matingPool.add(population[i]); + } + } + } + + // Making the next generation + void reproduction() { + // Refill the population with children from the mating pool + for (int i = 0; i < population.length; i++) { + // Sping the wheel of fortune to pick two parents + int m = int(random(matingPool.size())); + int d = int(random(matingPool.size())); + // Pick two parents + Rocket mom = matingPool.get(m); + Rocket dad = matingPool.get(d); + // Get their genes + DNA momgenes = mom.getDNA(); + DNA dadgenes = dad.getDNA(); + // Mate their genes + DNA child = momgenes.crossover(dadgenes); + // Mutate their genes + child.mutate(mutationRate); + // Fill the new population with the new child + PVector location = new PVector(width/2,height+20); + population[i] = new Rocket(location, child,population.length); + } + generations++; + } + + int getGenerations() { + return generations; + } + + // Find highest fintess for the population + float getMaxFitness() { + float record = 0; + for (int i = 0; i < population.length; i++) { + if(population[i].getFitness() > record) { + record = population[i].getFitness(); + } + } + return record; + } + +} diff --git a/Lectures/Lecture13/SmartRocketsObstacles/Rocket.pde b/Lectures/Lecture13/SmartRocketsObstacles/Rocket.pde new file mode 100644 index 0000000..488b9ea --- /dev/null +++ b/Lectures/Lecture13/SmartRocketsObstacles/Rocket.pde @@ -0,0 +1,147 @@ +// Pathfinding w/ Genetic Algorithms +// Daniel Shiffman + +// Rocket class -- this is just like our Boid / Particle class +// the only difference is that it has DNA & fitness + +class Rocket { + + // All of our physics stuff + PVector location; + PVector velocity; + PVector acceleration; + + // Size + float r; + + // How close did it get to the target + float recordDist; + + // Fitness and DNA + float fitness; + DNA dna; + // To count which force we're on in the genes + int geneCounter = 0; + + boolean hitObstacle = false; // Am I stuck on an obstacle? + boolean hitTarget = false; // Did I reach the target + int finishTime; // What was my finish time? + + //constructor + Rocket(PVector l, DNA dna_, int totalRockets) { + acceleration = new PVector(); + velocity = new PVector(); + location = l.get(); + r = 4; + dna = dna_; + finishTime = 0; // We're going to count how long it takes to reach target + recordDist = 10000; // Some high number that will be beat instantly + } + + // FITNESS FUNCTION + // distance = distance from target + // finish = what order did i finish (first, second, etc. . .) + // f(distance,finish) = (1.0f / finish^1.5) * (1.0f / distance^6); + // a lower finish is rewarded (exponentially) and/or shorter distance to target (exponetially) + void fitness() { + if (recordDist < 1) recordDist = 1; + + // Reward finishing faster and getting close + fitness = (1/(finishTime*recordDist)); + + // Make the function exponential + fitness = pow(fitness, 4); + + if (hitObstacle) fitness *= 0.1; // lose 90% of fitness hitting an obstacle + if (hitTarget) fitness *= 2; // twice the fitness for finishing! + } + + // Run in relation to all the obstacles + // If I'm stuck, don't bother updating or checking for intersection + void run(ArrayList os) { + if (!hitObstacle && !hitTarget) { + applyForce(dna.genes[geneCounter]); + geneCounter = (geneCounter + 1) % dna.genes.length; + update(); + // If I hit an edge or an obstacle + obstacles(os); + } + // Draw me! + if (!hitObstacle) { + display(); + } + } + + // Did I make it to the target? + void checkTarget() { + float d = dist(location.x, location.y, target.location.x, target.location.y); + if (d < recordDist) recordDist = d; + + if (target.contains(location) && !hitTarget) { + hitTarget = true; + } + else if (!hitTarget) { + finishTime++; + } + } + + // Did I hit an obstacle? + void obstacles(ArrayList os) { + for (Obstacle obs : os) { + if (obs.contains(location)) { + hitObstacle = true; + } + } + } + + void applyForce(PVector f) { + acceleration.add(f); + } + + + void update() { + velocity.add(acceleration); + location.add(velocity); + acceleration.mult(0); + } + + void display() { + //background(255,0,0); + float theta = velocity.heading2D() + PI/2; + fill(200, 100); + stroke(0); + strokeWeight(1); + pushMatrix(); + translate(location.x, location.y); + rotate(theta); + + // Thrusters + rectMode(CENTER); + fill(0); + rect(-r/2, r*2, r/2, r); + rect(r/2, r*2, r/2, r); + + // Rocket body + fill(175); + beginShape(TRIANGLES); + vertex(0, -r*2); + vertex(-r, r*2); + vertex(r, r*2); + endShape(); + + popMatrix(); + } + + float getFitness() { + return fitness; + } + + DNA getDNA() { + return dna; + } + + boolean stopped() { + return hitObstacle; + } +} + diff --git a/Lectures/Lecture13/SmartRocketsObstacles/SmartRocketsObstacles.pde b/Lectures/Lecture13/SmartRocketsObstacles/SmartRocketsObstacles.pde new file mode 100644 index 0000000..46f7d6d --- /dev/null +++ b/Lectures/Lecture13/SmartRocketsObstacles/SmartRocketsObstacles.pde @@ -0,0 +1,93 @@ +// Smart Rockets w/ Genetic Algorithms +// Daniel Shiffman + +// Each Rocket's DNA is an array of PVectors +// Each PVector acts as a force for each frame of animation +// Imagine an booster on the end of the rocket that can point in any direction +// and fire at any strength every frame + +// The Rocket's fitness is a function of how close it gets to the target as well as how fast it gets there + +// This example is inspired by Jer Thorp's Smart Rockets +// http://www.blprnt.com/smartrockets/ + +int lifetime; // How long should each generation live + +Population population; // Population + +int lifecycle; // Timer for cycle of generation +int recordtime; // Fastest time to target + +Obstacle target; // Target location + +//int diam = 24; // Size of target + +ArrayList obstacles; //an array list to keep track of all the obstacles! + +void setup() { + size(800, 200); + smooth(); + + // The number of cycles we will allow a generation to live + lifetime = 300; + + // Initialize variables + lifecycle = 0; + recordtime = lifetime; + + target = new Obstacle(width/2-12, 24, 24, 24); + + // Create a population with a mutation rate, and population max + float mutationRate = 0.01; + population = new Population(mutationRate, 50); + + // Create the obstacle course + obstacles = new ArrayList(); + obstacles.add(new Obstacle(300, height/2, width-600, 10)); +} + +void draw() { + background(255); + + // Draw the start and target locations + target.display(); + + + // If the generation hasn't ended yet + if (lifecycle < lifetime) { + population.live(obstacles); + if ((population.targetReached()) && (lifecycle < recordtime)) { + recordtime = lifecycle; + } + lifecycle++; + // Otherwise a new generation + } + else { + lifecycle = 0; + population.fitness(); + population.selection(); + population.reproduction(); + } + + // Draw the obstacles + for (Obstacle obs : obstacles) { + obs.display(); + } + + // Display some info + fill(0); + text("Generation #: " + population.getGenerations(), 10, 18); + text("Cycles left: " + (lifetime-lifecycle), 10, 36); + text("Record cycles: " + recordtime, 10, 54); + + +} + +// Move the target if the mouse is pressed +// System will adapt to new target +void mousePressed() { + target.location.x = mouseX; + target.location.y = mouseY; + recordtime = lifetime; +} + diff --git a/Lectures/Lecture13/SmartRocketsObstacles/sketch.properties b/Lectures/Lecture13/SmartRocketsObstacles/sketch.properties new file mode 100644 index 0000000..28faa58 --- /dev/null +++ b/Lectures/Lecture13/SmartRocketsObstacles/sketch.properties @@ -0,0 +1 @@ +mode=Standard