duty_scheduler.Scheduler.java Source code

Java tutorial

Introduction

Here is the source code for duty_scheduler.Scheduler.java

Source

package duty_scheduler;

/**
 * Copyright (C) 2015 Matthew Mussomele
 *
 *  This file is part of ChoiceOptimizationAlgorithm
 *  
 *  ChoiceOptimizationAlgorithm is free software: you can redistribute it 
 *  and/or modify it under the terms of the GNU General Public License as
 *  published by the Free Software Foundation, either version 3 of the 
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.File;

import java.text.SimpleDateFormat;

import java.util.HashMap;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import java.util.Date;

import org.json.JSONArray;
import org.json.JSONObject;
import org.json.JSONException;

/**
 * Main class for running the RA duty scheduling algorithm. 
 * 
 * @author Matthew Mussomele
 */
public class Scheduler {

    private static final long NANOS_PER_SEC = 1000000000;
    private static final double MUTATE_DEFAULT = 0.21602435146951632;
    private static final int HRS_PER_DAY = 24;
    private static final int MINS_PER_HR = 60;
    private static final int SECS_PER_MINUTE = 60;
    private static final int MILLIS_PER_SEC = 1000;

    static final int INVALID_ITEM_PRIORITY = 0;
    static final int SEED_COUNT;
    static final int EVOLVE_ITERS;
    static final int NUM_RUNS;
    static final int RESOURCE_FACTOR;
    static final int ALLOWED_SEED_ATTEMPTS;
    static final double MUTATION_CHANCE;
    static final boolean ALLOW_ILLEGALS;
    static final boolean ALLOW_GREEDY;
    static final boolean CONSIDER_ADJACENTS;
    static final boolean ANALYZE;
    static final String DATA_FILE;

    private static ArrayList<RA> raList;
    private static ArrayList<Duty> dutyList;
    private static HashMap<String, Duty> dutyLookup;
    private static double[][] analytics;

    /**
     * The following initializer reads all necessary data from the config file. If the file
     * is absent the program uses the default global package constant values. If the file is
     * formatted improperly, then the program logs an exception and exits.
     */
    static {
        int defaultsc = 30;
        int defaultei = 1000;
        int defaultnr = 20;
        int defaultrf = 10;
        int defaultasa = 1000;
        double defaultmc = MUTATE_DEFAULT;
        boolean defaultai = false;
        boolean defaultag = false;
        boolean defaultca = true;
        boolean defaultv = false;
        String defaultdf = "data.json";
        BufferedReader reader = null;
        try {
            int lineNumber = 0;
            reader = new BufferedReader(new FileReader("scheduler.config"));
            String line = reader.readLine();
            while (line != null) {
                String[] data = line.split("=");
                String fieldName = data[0];
                String fieldValue = data[1];
                switch (fieldName) {
                case "SEED_COUNT":
                    defaultsc = Integer.parseInt(fieldValue);
                    if (defaultsc % 2 == 1 || defaultsc <= 0) {
                        throw new IllegalArgumentException("SEED_COUNT must be positive and " + "even.");
                    }
                    break;
                case "EVOLVE_ITERS":
                    defaultei = Integer.parseInt(fieldValue);
                    if (defaultei <= 0) {
                        throw new IllegalArgumentException("EVOLVE_ITERS must be positive.");
                    }
                    break;
                case "NUM_RUNS":
                    defaultnr = Integer.parseInt(fieldValue);
                    if (defaultnr <= 0) {
                        throw new IllegalArgumentException("NUM_RUNS must be positive.");
                    }
                    break;
                case "RESOURCE_FACTOR":
                    defaultrf = Integer.parseInt(fieldValue);
                    if (defaultrf <= 1) {
                        throw new IllegalArgumentException("RESOURCE_FACTOR must be greater " + "than one");
                    }
                    break;
                case "ALLOWED_SEED_ATTEMPTS":
                    defaultasa = Integer.parseInt(fieldValue);
                    if (defaultasa <= 0) {
                        throw new IllegalArgumentException("ALLOWED_SEED_ATTEMPTS must be " + "positive.");
                    }
                    break;
                case "MUTATION_CHANCE":
                    defaultmc = Double.parseDouble(fieldValue);
                    if (defaultmc <= 0 || defaultmc >= 1) {
                        throw new IllegalArgumentException("MUTATION_CHANCE must be within " + "(0, 1)");
                    }
                    break;
                case "ALLOW_ILLEGALS":
                    defaultai = Boolean.parseBoolean(fieldValue);
                    break;
                case "ALLOW_GREEDY":
                    defaultag = Boolean.parseBoolean(fieldValue);
                    break;
                case "CONSIDER_ADJACENTS":
                    defaultca = Boolean.parseBoolean(fieldValue);
                    break;
                case "ANALYZE":
                    defaultv = Boolean.parseBoolean(fieldValue);
                    break;
                case "DATA_FILE":
                    if (!fieldValue.endsWith(".json")) {
                        throw new IllegalArgumentException(String.format(
                                "Data file must " + "be a json file, was a .%s.", fieldValue.split(".")[1]));
                    } else {
                        defaultdf = fieldValue;
                    }
                    break;
                default:
                    throw new IllegalArgumentException(
                            String.format("Invalid field name" + " %s on line %d.", fieldName, lineNumber));
                }
                lineNumber += 1;
                line = reader.readLine();
            }
        } catch (IOException e) {
            System.out.println("Using default values.");
        } catch (IllegalArgumentException e) {
            ErrorChecker.printExceptionToLog(e);
        } finally {
            SEED_COUNT = defaultsc;
            EVOLVE_ITERS = defaultei;
            NUM_RUNS = defaultnr;
            RESOURCE_FACTOR = defaultrf;
            ALLOWED_SEED_ATTEMPTS = defaultasa;
            MUTATION_CHANCE = defaultmc;
            ALLOW_ILLEGALS = defaultai;
            ALLOW_GREEDY = defaultag;
            CONSIDER_ADJACENTS = defaultca;
            ANALYZE = defaultv;
            DATA_FILE = defaultdf;
            dutyList = new ArrayList<Duty>();
            raList = new ArrayList<RA>();
            try {
                if (reader != null) {
                    reader.close();
                }
            } catch (IOException e) {
                System.out.println("Could not close config reader.");
            }
        }
    }

    /**
     * Private construction to prevent instantiation.
     */
    private Scheduler() {
        throw new AssertionError();
    }

    /**
     * Reads in a JSON data file and constructs RA and Duty instances from it. 
     */
    private static void parseData() {
        JSONObject data = new JSONObject(new String(readFile()));
        createDutyList(data.getJSONArray("dates"));
        dutyLookup = new HashMap<String, Duty>(dutyList.size());
        for (Duty duty : dutyList) {
            dutyLookup.put(duty.toString(), duty);
        }
        createRAList(data.getJSONArray("residentAssistants"));
        try {
            ErrorChecker.evalPrefs(raList, dutyList);
            ErrorChecker.checkConsistency();
            if (!ALLOW_ILLEGALS) {
                ErrorChecker.checkImpossible();
            }
            if (!ALLOW_GREEDY) {
                ErrorChecker.checkGreedy(raList);
            }
        } catch (RuntimeException e) {
            ErrorChecker.printExceptionToLog(e);
        }
    }

    /**
     * Gets the content of a plain text file.
     * 
     * @return The contents of the data file as a String
     */
    private static String readFile() {
        BufferedReader reader = null;
        String result = "";
        try {
            File dataFile = new File(DATA_FILE);
            if (!dataFile.exists()) {
                throw new IOException("Missing data file: " + DATA_FILE);
            }
            reader = new BufferedReader(new FileReader(dataFile));
            String line = reader.readLine();
            while (line != null) {
                result += line;
                line = reader.readLine();
            }
        } catch (IOException e) {
            ErrorChecker.printExceptionToLog(e);
        } finally {
            try {
                reader.close();
            } catch (IOException e) {
                System.out.println("Could not close data reader.");
            }
        }
        return result;
    }

    /**
     * Creates an ArrayList of RA instances from a JSONArray of encoded data
     * 
     * @param jsonRAs A JSONArray contains information about RAs
     */
    private static void createRAList(JSONArray jsonRAs) {
        try {
            for (int i = 0; i < jsonRAs.length(); i += 1) {
                JSONObject ra = jsonRAs.getJSONObject(i);
                RA.RABuilder builder = new RA.RABuilder(ra.getString("name"), dutyList.size(), ra.getInt("duties"));
                JSONArray prefs = ra.getJSONArray("preferences");
                for (int j = 0; j < prefs.length(); j += 1) {
                    JSONObject pref = prefs.getJSONObject(j);
                    builder.putPreference(dutyLookup.get(pref.getString("duty")), pref.getInt("prefVal"));
                }
                raList.add(builder.build());
            }
        } catch (JSONException e) {
            ErrorChecker.printExceptionToLog(e);
        }
    }

    /**
     * Creates an ArrayList of Duty instances from a JSONArray of encoded data
     * 
     * @param jsonDuties A JSONArray contains information about Duty instances
     */
    private static void createDutyList(JSONArray jsonDuties) {
        try {
            for (int i = 0; i < jsonDuties.length(); i += 1) {
                JSONObject duty = jsonDuties.getJSONObject(i);
                dutyList.add(new Duty(duty.getInt("year"), duty.getInt("month"), duty.getInt("day")));
            }
        } catch (JSONException e) {
            ErrorChecker.printExceptionToLog(e);
        }
    }

    /**
     * Runs the choice optimization algorithm on the data and finds a good schedule
     * 
     * @return The best schedule found
     */
    private static Schedule run() {
        Schedule best = null;
        Schedule localBest = null;
        Generation thisGen = null;
        if (ANALYZE) {
            analytics = new double[NUM_RUNS][];
        }
        for (int i = 0; i < NUM_RUNS; i += 1) {
            thisGen = new Generation();
            thisGen.seed(raList, dutyList);
            localBest = thisGen.evolve();
            if (ANALYZE) {
                analytics[i] = thisGen.getHistory();
            }
            if (best == null || localBest.getCost() < best.getCost()) {
                best = localBest;
            }
        }
        return best;
    }

    /**
     * Generates a String representation of the runtime of the algorithm
     * 
     * @param nanos The number of nanoseconds that the algorithm took to run
     * @return A string describing how long the algorithm took to finish execution
     */
    private static String runTime(long nanos) {
        long minutes = TimeUnit.NANOSECONDS.toMinutes(nanos) - (TimeUnit.NANOSECONDS.toHours(nanos) * MINS_PER_HR);
        long seconds = TimeUnit.NANOSECONDS.toSeconds(nanos)
                - (TimeUnit.NANOSECONDS.toMinutes(nanos) * SECS_PER_MINUTE);
        long millis = TimeUnit.NANOSECONDS.toMillis(nanos)
                - (TimeUnit.NANOSECONDS.toSeconds(nanos) * MILLIS_PER_SEC);
        return String.format("Time Elapsed: %d minutes, %d seconds, %d milliseconds", minutes, seconds, millis);
    }

    /**
     * Prints the results of the algorithm to a file
     * 
     * @param best The best Schedule found during the run
     * @param runTimeReport A String describing the runtime of the algorithm
     */
    private static void printResults(Schedule best, String runTimeReport) {
        String resultsFile = "schedule_" + (new SimpleDateFormat("MM-dd-yyyy-hh:mm")).format(new Date()) + ".sched";
        PrintWriter dataOut = null;
        try {
            dataOut = new PrintWriter(resultsFile);
            dataOut.println(runTimeReport);
            dataOut.println("Duty Assignments:\n\n");
            dataOut.println(best.toString());
        } catch (IOException e) {
            ErrorChecker.printExceptionToLog(e);
        } finally {
            dataOut.close();
        }
    }

    /**
     * Prints analytics data to a space separated file called analytics.txt
     */
    private static void printAnalytics() {
        String analyticsFile = "analytics.txt";
        PrintWriter analysisOut = null;
        try {
            analysisOut = new PrintWriter(analyticsFile);
            for (double[] generationData : analytics) {
                for (double dataPoint : generationData) {
                    analysisOut.print(String.format("%.3f ", dataPoint));
                }
                analysisOut.println();
            }
        } catch (IOException e) {
            ErrorChecker.printExceptionToLog(e);
        } finally {
            analysisOut.close();
        }
    }

    /**
     * Main
     * 
     * @param args Command line arguments (This code takes nonec)
     */
    public static void main(String[] args) {
        try {
            long timeElapsed = System.nanoTime();
            parseData();
            Schedule best = run();
            printResults(best, runTime(System.nanoTime() - timeElapsed));
            if (ANALYZE) {
                printAnalytics();
            }
        } catch (Exception e) {
            ErrorChecker.printExceptionToLog(e);
        }
    }

}