GithubHelp home page GithubHelp logo

Comments (7)

dwdyer avatar dwdyer commented on September 19, 2024

Your understanding of how it should work is correct. It's fairly fundamental to how the evolution engine works so I'd be surprised if there was a bug that hadn't been spotted until now. Is your fitness evaluator repeatable (i.e. does it always return the same number for the same candidate)? Each candidate will be re-evaluated in each generation, even if they haven't changed. So if there's some random element to your fitness evaluation (e.g. if you're testing them against some randomly generated/sampled data) you could get a different score.

from watchmaker.

indrusiak avatar indrusiak commented on September 19, 2024

from watchmaker.

dwdyer avatar dwdyer commented on September 19, 2024

Are you both using the GenerationalEvolutionEngine? If either of you can share any code that exhibits the problem I can take a look.

from watchmaker.

Panthrakz avatar Panthrakz commented on September 19, 2024

I am using the GenerationalEvolutionEngine. I have noticed the elitism problem whether I use just mutation, or just crossover - I have included my implementations of those at the end, in case I am somehow operating on the previous generation's population, rather than mutating the new generation.

Here is the evolveSchedule method I call in main, with no arguments:

public static void evolveSchedule(){
    ScheduleFactory factory = new ScheduleFactory();
    
    List<EvolutionaryOperator<Schedule>> operators = new LinkedList<EvolutionaryOperator<Schedule>>();
    operators.add(new ScheduleCrossover());
    operators.add(new ScheduleMutator(new Probability(0.02)));
   //operators.add(new IdentityOperator());

    EvolutionaryOperator<Schedule> pipeline = new EvolutionPipeline<Schedule>(operators);
    
    EvolutionEngine<Schedule> engine = new GenerationalEvolutionEngine<Schedule>(factory, 
                                                                                 pipeline,
                                                                                 new ScheduleEvaluator(),
                                                                                 new StochasticUniversalSampling(),
                                                                                 new MersenneTwisterRNG());
    
    engine.addEvolutionObserver(new EvolutionObserver<Schedule>(){
    
        public void populationUpdate(PopulationData<? extends Schedule> data){
            
            System.out.println();
            System.out.println("Generation " + data.getGenerationNumber() + " has maximum fitness: " + data.getBestCandidateFitness());
            System.out.println();
            
            
            
            if(data.getBestCandidateFitness() > 90){

                Section[][] allsections = data.getBestCandidate().getAllSections();
                String format = "%-25s%-25s%-25s%-25s%-25s%-25s%-25s%-25s%n";
                String[] s = new String[8];
                
                for (int j = 0; j < allsections[0].length; j++){   
                            
                    for(int i = 0; i < 8; i++){ 

                        if(i==0){
                                s[i] = allsections[i][j].getTeacher().getName();
                        }
                        
                        else if(allsections[i-1][j]!=null){
                                s[i] = allsections[i-1][j].getName();
                        }
                    }

                System.out.printf(format, s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7]);
                
                }
            }
        }
    });
    
    Schedule result = engine.evolve(10, 1, new GenerationCount(5));

}

If the problem were with my ScheduleEvaluator, then it would be in this class:

public class ScheduleEvaluator implements FitnessEvaluator<Schedule>{

private static final String TEACHERPATH = "c:/Users/steve/OneDrive/Desktop/Scheduleproject/Teachers.xlsx";
private static final String STUDENTPATH = "c:/Users/steve/OneDrive/Desktop/Scheduleproject/Students.xlsx";
private static final String SECTIONPATH = "c:/Users/steve/OneDrive/Desktop/Scheduleproject/Sections.xlsx";
Student[] studentlist = getMasterStudentList();


public double getFitness(Schedule candidate, List<? extends Schedule> population){
    
    double fitness = studentlist.length;
    //System.out.println(studentlist.length);
    
    String[] conflictedstudents = new String[studentlist.length];  

    //Check for students class conflicts requires building each student's
    //mini-schedule from the available Master schedule, and then looking for conflicts:
    
    for(int i = 0; i < studentlist.length; i++){
        
            if(doesInsurmountableConflictExist(studentlist[i], candidate) == 1){
                fitness--;
                conflictedstudents[i] = studentlist[i].getName();
            }
    }

   // if(fitness > 115){
        
   //     System.out.println("These are the conflicted students: ");
        
   //     for(int j = 0; j < studentlist.length; j++){
   //         if(conflictedstudents[j] !=null){
   //             System.out.println(conflictedstudents[j]+ " ");
   //         }
   //     }
   // }
    
    return fitness;
}

public boolean isNatural()
{
    return true;
}



   public int doesInsurmountableConflictExist(Student student, Schedule schedule){
    
    //This method makes a mini schedule for a given student that is an array with
    //1 (or more!) in every period and section that a student may take, and a 0
    //elsewhere. Returns conflicts, which is a 0 if there are none, and 1 if there
    //are any. Doesn't actually make a student schedule - just checks if it's possible!
    
    int conflicts;
    
    String studentname = student.getName();
    Course[] studentcourses = student.getCourseList();

    //If a student has no courses, pass them through with no conflicts.
    
    if(studentcourses.length == 0){
        conflicts = 0;
    }
    
    //Otherwise, go through the whole rigamarole:
    
    else{
    
        //done and possiblesections check to make sure everything in the excel is spelled right.
        //If there are zero sections for a course in a student's list of courses,
        //then something is mistyped in the excel and needs to be fixed.
        
        boolean[] done = new boolean[7];
        int possiblesections = 0;
        
        //baseschedule is a 7 x (numberofcoursestakenbystudent) array of sections;
        //rows are periods, and columns are sections of a course.
        
        int[][] baseschedule = new int[7][7];
        Section[][] allsections = schedule.getAllSections();
    
        //Then, look in allsections to find out how many sections there
        //are of each such course, and create a baseschedule that lists 
        //only those sections the student could possibly take. 
        
        for (int q = 0; q < 7; q++){
        
            //We have the courses the student is taking - let's build the baseschedule:
            // Baseschedule is an array of 0s and ns - 0s mean the course is not offered
            //during the given period, and an n means there are n such instances
            //that period.
        
            //i is periods, j is teachers
            
            for(int i = 0; i < allsections[0].length; i++){

                for (int j = 0; j < 7; j++){

                    if(allsections[j][i].getCourse().getName().equals(studentcourses[q].getName())){

                        done[q] = true;
                        baseschedule[j][q]++;
                        possiblesections++;
                        
                    }
                }
            }
        }

        //If done isn't full of 'true,' that means there's a typo in the excel files.
        
        for(int z = 0; z < 6; z++){
            
            if(done[z] == false){
                                            
                System.out.println(studentname + " has a typo in their courses. " +  z);
                    
            }
        }
            
        conflicts = pathfinder(baseschedule, 1);

    }
    
    return conflicts;
    
    }


public static int pathfinder(int[][] sections, int path){
    
  //  System.out.println("");
    
    //for(int z = 0; z < sections.length; z++){
    //    for(int y = 0; y < sections[0].length; y++){
    //        System.out.print(sections[z][y]);
    //    }
    //    System.out.println();
    // }
    
    
    if(path == 0){
        //System.out.println("ding ding dingggg!");
        return path;
    }
    
    else{
    
        if((sections[0][0] > 0)&&(sections.length==1)&&(sections[0].length==1)){
            path = 0;   
            return path;
        }   
    
        else if((sections[0][0] == 0)&&(sections.length==1)&&(sections[0].length==1)){
            path = 1;
            return path;
        }
    
        else{
            for(int i = 0; (i < sections.length); i++){
            
                    if(sections[i][0] > 0){
                        
                        int[][] buffer = arrayShrinker(sections, i, 0);
                        path = pathfinder(buffer, path);
                    }
                    
            }
        }
    }

    return path;

}

public static int[][] arrayShrinker(int[][] sections, int row, int column){
    
    int[][] buffer = new int[sections.length - 1][sections[0].length - 1];
    
    for(int i = 0; i < sections.length-1; i++){
        for(int j = 0; j < sections[0].length-1; j++){
            
            if((i < row) && (j<column))  {
                buffer[i][j] = sections[i][j];
            }
            
            else if ((i < row) && (j >= column)){
                buffer[i][j] = sections[i][j+1];
            }
            
            else if ((i >= row) && (j < column)){
                buffer[i][j] = sections[i+1][j];
            }
            
            else if ((i >= row) && (j >= column)){
                buffer[i][j] = sections[i+1][j+1];
            }
        }
    }
     
    return buffer;

}

}

I've elided the methods which read the excel files I'm using to build schedules(e.g., 'getMasterStudentList()'), because they're definitely always going to behave the same way each time they're called. What follows below are the 'mate' and 'mutate' methods inside the ScheduleMutator and ScheduleCrossover operators called by the evolutionary pipeline:

protected List<Schedule> mate(Schedule sch1, Schedule sch2, int numberOfCrossoverPoints, Random r){
    
    List<Schedule> babies = new ArrayList<Schedule>();
    
    int low = 0;
    int high = sch1.getAllSections()[0].length;
    
    Schedule baby1 = new Schedule(sch1.getAllSections());
    Schedule baby2 = new Schedule(sch2.getAllSections());
    
    
    for(int i = 0; i < numberOfCrossoverPoints; i++){
        
        int result = r.nextInt(high-low) + low;
        SwapColumns(baby1, baby2, result);
        
    }
    
    babies.add(baby1);
    babies.add(baby2);
    
    //System.out.println("crossed-over!");
    
    return babies;
}


public void SwapColumns(Schedule sch1, Schedule sch2, int column){
    
    Section[] hold1 = new Section[7];
    Section[][] sch1sections = sch1.getAllSections();
    
    Section[] hold2 = new Section[7];
    Section[][] sch2sections = sch2.getAllSections();
    
    for(int i = 0; i < 7; i++){
        hold1[i] = sch1sections[i][column];
        hold2[i] = sch2sections[i][column];
    }
    
    sch1.replaceColumn(column, hold2);
    sch2.replaceColumn(column, hold1);
    
}

This is the mutate part:

private Schedule mutateSchedule(Schedule sch1, Random rng){
        
        int low = 0;
        int high = sch1.getAllSections()[0].length;
        int column;
        
        Schedule buffer = new Schedule(sch1.getAllSections());
        
        for(int i = 0; i < buffer.getAllSections()[0].length; i++){
            
            if(mutationProbability.nextValue().nextEvent(rng)){
            
                column = rng.nextInt(high - low) + low;
                
                twoSwap(buffer, column, rng);

            }    
        }
        
        return buffer;
    }

public void twoSwap(Schedule schedule, int column, Random rng){
    
        Section[] array = new Section[7];
    
        for(int i = 0; i < 7; i++){
            array[i] =  schedule.getAllSections()[i][column];
        }
        
        int index1 = rng.nextInt(7);
        Section buffer1 = array[index1];
        int index2 = rng.nextInt(7);
        Section buffer2 = array[index2];
        
        array[index1] = buffer2;
        array[index2] = buffer1;
        
        schedule.replaceColumn(column, array);
    
    }

I think that's all the pertinent code.

from watchmaker.

dwdyer avatar dwdyer commented on September 19, 2024

I can't see anything obviously wrong there. One thing to be aware of is that the cross-over and mutation methods need to return objects that are entirely independent of the parents. Is there anything in your Schedule or Section classes that might be relevant there? For example, it looks like you're passing an array from one Schedule to the constructor of another. Does that mean that it's possible that two Schedules have references to the same mutable array object?

Another thing to try would be to call setSingleThreaded on the evolution engine before calling evolve and see if you still see the same problem. It will be slower but will rule out any concurrency problems.

from watchmaker.

Panthrakz avatar Panthrakz commented on September 19, 2024

It's as you say - there were constructors set up to generate 'references to the same mutable array object.' I changed my 'gets' to include a .clone(), and most of the issues went away. 10/10 on responses to a 10 year old project, Mr. Dyer!

from watchmaker.

dwdyer avatar dwdyer commented on September 19, 2024

Glad you got it sorted.

from watchmaker.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.