Package de.timefinder.algo.ncp

Source Code of de.timefinder.algo.ncp.NoCollisionPrinciple

/*
* This file is part of the TimeFinder project.
*  Visit http://www.timefinder.de for more information.
*  Copyright (c) 2009 the original author or 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
*
*        http://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.
*/
package de.timefinder.algo.ncp;

import de.timefinder.algo.Algorithm;
import de.timefinder.algo.AlgorithmCondition;
import de.timefinder.algo.AlgorithmConditionTime;
import de.timefinder.algo.ConflictMatrix;
import de.timefinder.algo.ConsoleStatusBar;
import de.timefinder.data.algo.DataPoolSettings;
import de.timefinder.algo.MyStatusBar;
import de.timefinder.algo.constraint.DifferentDayConstraint;
import de.timefinder.data.algo.Assignment;
import de.timefinder.data.algo.Solution;
import de.timefinder.algo.constraint.EventOrderConstraint;
import de.timefinder.algo.constraint.MinGapsConstraint;
import de.timefinder.algo.constraint.RasterConstraint;
import de.timefinder.data.DataPool;
import de.timefinder.data.Event;
import de.timefinder.data.Location;
import de.timefinder.data.Person;
import de.timefinder.data.algo.Constraint;
import de.timefinder.data.set.BitRaster;
import de.timefinder.data.set.Raster;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import javolution.util.FastMap;
import javolution.util.FastSet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
* This class defines the common methods for the "No-Collision-Principle".
* Commonly known as 'clustering' where an 'event cluster' is built
* from events with no collision.
* <p/>
* This is one first approach to generate a timetable without a
* collision of persons, rooms, 'events+rooms its features',
* 'event its rasters' and 'event its order'.
*
* @author Peter Karich, peat_hal 'at' users 'dot' sourceforge 'dot' net
*/
public class NoCollisionPrinciple implements Algorithm {

    private final Lock lock = new ReentrantLock();
    private Solution globalBestSolution;
    private Solution localBestSolution;
    private ConflictMatrix conflictMatrix;
    private Random random;
    private Log logger = LogFactory.getLog(getClass());
    private DataPool dataPool;
    private MyStatusBar statusBar;
    private DataPoolSettings dataPoolSettings;
    private List<Pair> orderActions;
    private AlgorithmCondition condition;

    public NoCollisionPrinciple() {
        statusBar = new ConsoleStatusBar();
        condition = new AlgorithmConditionTime(5 * 60);
    }

    @Override
    public void setCondition(AlgorithmCondition condition) {
        this.condition = condition;
    }

    @Override
    public void setDataPoolSettings(DataPoolSettings dps) {
        dataPoolSettings = dps;
    }

    public void setRandom(Random random) {
        this.random = random;
    }

    @Override
    public void setDataPool(DataPool collection) {
        dataPool = collection;
    }

    @Override
    public DataPool getDataPool() {
        return dataPool;
    }

    @Override
    public void setStatusBar(MyStatusBar sBar) {
        statusBar = sBar;
    }

    public String getName() {
        return "No Collision Principle";
    }

    public void clearResults() {
        globalBestSolution = null;
        localBestSolution = null;
    }

    /**
     * This method returns the hard constraint violations if any calculation was
     * done.
     */
    public int getHardConflicts() {
        if (globalBestSolution != null) {
            return globalBestSolution.getHardConflicts();
        } else {
            return -1;
        }
    }

    @Override
    public Solution doWork() {
        try {
            if (!lock.tryLock(3, TimeUnit.SECONDS)) {
                throw new IllegalStateException("Already Started!");
            }
        } catch (InterruptedException ex) {
            throw new IllegalStateException("Interrupted while waiting on finishing the algorithm!");
        }

        try {
            clearResults();
            createTimeTable();

            // Use Event.Start instead of the index startTime which is
            // used in Period
            if (globalBestSolution == null)
                throw new IllegalStateException("global week should not be null");

            globalBestSolution.applyStartValuesOnEvents();
            dataPool.getDao(Event.class).refresh();
            dataPool.getDao(Location.class).refresh();

            //The following is normally not good in real life applications, but was
            //necessary for the international timetabling competition.
            //It removes the events with hard constraint violations
            //autoTT.removeHardConstraintViolations(getDataPool());

            return globalBestSolution;
        } finally {
            lock.unlock();
        }
    }

    public Solution getBestSolution() {
        return globalBestSolution;
    }

    /**
     * This method tries to place all events in one week.
     * It avoids 'Order', BitRaster, Feature, Person and Location collision
     * by construction. It does not insert those violating events in a
     * AssignmentManager. Soft constraints will be optimized, too.
     * Please visit http://timefinder.sourceforge.net/doc/dev/papers.html
     * for more information and the details of every step.
     */
    private void createTimeTable() {
        boolean initFromOldData = true;
        // Step 1
        Period currentWeek = getInitialPeriod(initFromOldData);
        OptimizerHelper oHelper = new DynamicOptimizer();

        long firstMillis = System.currentTimeMillis() - 1;
        int improvingCounter = 0;
        int noOfAllEvents = currentWeek.getAll().size();

        int cAss = currentWeek.getInvalidAssignments().size();
        logger.info(cAss + " hard conflicts");
        statusBar.setMessage(cAss + " hard conflicts");

        condition.init();

        NCPEventTSGraph graph = null;
        if (true)
            graph = new NCPEventTSGraph(dataPoolSettings.getTimeslotsPerDay(), dataPoolSettings.getNumberOfDays());

        if (graph != null)
            conflictMatrix.setEnabled(false);

        //************** START of the main optimization loop ***************
        // call Event.setStart only at the end (for the best week)
        while (true) {
            if (logger.isDebugEnabled() && noOfAllEvents != currentWeek.getAll().size()) {
                logger.fatal("Events in canister does not match!!");
                logger.fatal("week.all.size:" + currentWeek.getAll().size());
                logger.fatal("But expected:" + noOfAllEvents);
                logger.fatal("Unassigned intervals (should be 0):" + currentWeek.getHardConstraintsViolations());
            }

            correctOrderWithBubbleSort(currentWeek.getInvalidAssignments());

//            logger.info(oHelper.getMainCounter() + ":" + currentWeek.getHardConstraintsViolations() + "/" + currentWeek.getSoftConstraintsViolations());           

            if (graph != null)
                graph.startOptimize(currentWeek);
            else {
                // Step 2: Spread the events, with respect to its raster
                spreadEvents(currentWeek);

                // Step 3: Try to place the unassigned events of the currentWeek.
                currentWeek.compress();
            }

//            logger.info(oHelper.getMainCounter() + ":" + currentWeek.getHardConstraintsViolations() + "/" + currentWeek.getSoftConstraintsViolations() + " " + oHelper.toString());

            // Step 5: a) Calculate the percentage to prepare the canister for the next iteration.
            oHelper.nextIteration(currentWeek);

            // Step 4: Select the best
            Improvement result = selectBest(currentWeek, oHelper);

            if (result == Improvement.YES) {
                // this improves quality, but drops down performance
//                Collections.sort(currentWeek.getInvalidAssignments(), new PersonComparator());
                improvingCounter = oHelper.getMainCounter();
            } else {
                if (result == Improvement.FINISHED)
                    break;

                // TODO Improve Optima: only good for softconstraint of ITC files
//                if (oHelper.getMainCounter() - improvingCounter < 0) {
//                    // Decrease of changes (percentage) if the new optimum was
//                    // found only some small 'time' ago
//                    oHelper.addToPercentage(-0.01);
//                }
            }

            // live updating the calc-duration should be possible
            if (!condition.canContinue())
                break;

            if (statusBar.getMyProgressMonitor().isCanceled())
                break;

//            if (currentWeek.getHardConstraintsViolations() < 1)
//                break;

            //***************************************************************
            // Step 5: b) REMOVE some difficult or random events from
            // the week for the next iteration
            currentWeek.prepare4NextIteration(oHelper.getPercentage(), true);
        }

        logger.info("#END HardConstraint violations:" + globalBestSolution.getHardConflicts()
                + " \tSoftConstraint violations:" + globalBestSolution.getSoftConflicts()
                + " \t time:" + (System.currentTimeMillis() - firstMillis) / 1000.0f
                + " \t[" + oHelper + "]");
    }

    private void doIntegrityChecks(Collection<Assignment> assignments, Collection<Location> locations) {
        int rasterIsNull = 0, rasterIsEmpty = 0;

        if (assignments.size() == 0) {
            throw new IllegalArgumentException("There should be more than 0 events to optimize!");
        }

        for (Assignment ass : assignments) {
            Event ev = ass.getEvent();
            RasterConstraint rc = ev.getConstraint(RasterConstraint.class);
            if (rc == null) {
                rasterIsNull++;
                continue;
            }

            BitRaster raster = rc.getRaster().getForbidden();
            if (raster.getAssignments() >= raster.getLength()) {
                rasterIsEmpty++;
            }
        }

        if (rasterIsEmpty > 0) {
            logger.warn(rasterIsEmpty + " events with an empty raster detected");
        }

        if (rasterIsNull > 0) {
            logger.warn(rasterIsNull + " events without a raster detected");
        }

        if (locations.size() < 2) {
            logger.warn("Only " + locations.size() + " locations - is this correct?");
        }
    }

    private void spreadEvents(Period currentWeek) {
        boolean assigned;
        List<Assignment> canister = new ArrayList<Assignment>(currentWeek.getInvalidAssignments());
        currentWeek.getInvalidAssignments().clear();
        for (Assignment ass : canister) {
            assigned = false;
            if (ass.getStart() >= 0)
                throw new IllegalStateException("at this point assignments' start cannot be positive:" + ass);
            // 3 b) The order limits the search interval:
            int searchInterval[] = currentWeek.getSearchInterval(ass);
            if (searchInterval != null) {
                Raster raster = conflictMatrix.getConflictingRaster(ass);

                // Step 4: Place Event in a timeslot or put it into
                // the unassigned array, if adding was not successfull
                int duration = ass.getEvent().getDuration();
                for (int startTime = raster.getNextFree(searchInterval[0], duration);
                        startTime >= 0 && startTime < searchInterval[1];
                        startTime = raster.getNextFree(startTime + 1, duration)) {

                    if (currentWeek.add(ass, startTime)) {
                        assigned = true;
//                        logger.info("\n\n\n#################################################");
//                        logger.info(ass);
//                        logger.info("                                          ");
//                        logger.info(currentWeek.toString(currentWeek.getAll(startTime, 5)));
//                        Solution sol = currentWeek.export();
//                        sol.applyStartValuesOnEvents();
//                        ConstraintChecker.printStatistics(sol, true);
                        break;
                    }
                }//for raster
            }//if valid search interval
            if (!assigned)
                currentWeek.add(ass, -1);

        }// for canister
    }

    /**
     * Either the program detects that a previously week was optimized, then
     * it will use the optimization results. Or it will create a fresh week.
     */
    Period getInitialPeriod(boolean initialize) {
        List<Location> allLocations = new ArrayList<Location>();
        for (Location loc : dataPool.getDao(Location.class).getAll()) {
            if (loc.getCapacity() > 0)
                allLocations.add(loc);
        }

        Map<Person, Set<Assignment>> personToAssignments = FastMap.newInstance();
        Map<Event, Assignment> eventToAss = FastMap.newInstance();
        Collection<? extends Person> persons = dataPool.getDao(Person.class).getAll();
        for (Person person : persons) {
            if (person.getEvents().size() == 0)
                continue;

            Set<Assignment> set = FastSet.newInstance();
            for (Event event : person.getEvents()) {
                Assignment ass = eventToAss.get(event);
                if (ass == null) {
                    ass = new Assignment(event);
                    eventToAss.put(event, ass);
                }
                set.add(ass);
            }
            personToAssignments.put(person, set);
        }
        for (Person person : persons) {
            for (Constraint c : person.getConstraints()) {
                c.transformEvents(eventToAss);
            }
        }

        conflictMatrix = new ConflictMatrix(dataPoolSettings.getTimeslotsPerWeek(), dataPoolSettings.getTimeslotsPerDay());

        // TODO LATER: use only as many assignment as it should (e.g. if a course is splitted into several sections)      
        List<Assignment> allAssignments = new ArrayList<Assignment>(conflictMatrix.initFromResources(personToAssignments));

        doIntegrityChecks(allAssignments, allLocations);

        logger.info("Start Algorithm - number of slots per week:" + dataPoolSettings.getTimeslotsPerWeek()
                + "; number of slots per day:" + dataPoolSettings.getTimeslotsPerDay()
                + "; events:" + allAssignments.size()
                + "; persons:" + personToAssignments.size()
                + "; locations:" + allLocations.size());

        Map<Assignment, Set<Assignment>> followers = FastMap.newInstance();
        Map<Assignment, Set<Assignment>> befores = FastMap.newInstance();
        for (Assignment ass : allAssignments) {
            if (ass.getEvent().getPersons().size() == 0)
                logger.fatal("relationship events to persons has to be bidirectional!" + ass);

            // TODO put this 'calculation' into OrderConstraint class
            befores.put(ass, createBeforesAssignments(ass, allAssignments));
            followers.put(ass, createFollowsAssignments(ass, allAssignments));

            for (Constraint constr : ass.getEvent().getConstraints()) {
                if (constr instanceof DifferentDayConstraint
                        || constr instanceof MinGapsConstraint) {
                    constr.transformEvents(eventToAss);
                }
            }
        }

        Period currentWeek = new Period(dataPoolSettings.getTimeslotsPerWeek(),
                allAssignments, befores, followers,
                allLocations, personToAssignments, conflictMatrix);
        currentWeek.setRandom(random);

        if (initialize) {
            for (Assignment ass : allAssignments) {
                if (ass.getStart() >= 0) {
                    if (!currentWeek.add(ass, ass.getStart())) {
                        currentWeek.add(ass, -1);
                    }
                } else
                    currentWeek.add(ass, -1);
            }
        }
        return currentWeek;
    }

    private Improvement selectBest(Period currentWeek, OptimizerHelper oHelper) {
        final int MAX_HC_FOR_SC_OPTIMIZATION = 1;
        final int SC_BORDER = 1;
        Improvement result = Improvement.NO;
        if (localBestSolution == null) {
            localBestSolution = currentWeek.export();
            globalBestSolution = localBestSolution;
            result = Improvement.YES;
            logger.debug("#HC:" + currentWeek.getHardConstraintsViolations() + " \t[" + oHelper.toString() + "]");
            statusBar.setMessage(currentWeek.getHardConstraintsViolations() + " hard conflicts");
        }

        int currentHC = currentWeek.getHardConstraintsViolations();
        int localHC = localBestSolution.getHardConflicts();

        if (currentHC < localHC) {
            int globalHC = globalBestSolution.getHardConflicts();
            int currentSC = currentWeek.getSoftConstraintsViolations();

            // Deep copy to global best, before it is possible that we
            // are leaving the loop
            localBestSolution = currentWeek.export();
            result = Improvement.YES;

            if (currentHC < globalHC) {
                //now only link to cloned week, to save some time:
                globalBestSolution = localBestSolution;
                // removed globalBestWeek.resetComplexity();
                currentWeek.resetComplexity();

                logger.debug("#HC:" + currentHC + " \tSC:" + currentSC
                        + " \t[" + oHelper.toString() + "]");
                statusBar.setMessage(currentHC + " hard- / " + currentSC + " softconflicts");
            }
        } else if (currentHC < MAX_HC_FOR_SC_OPTIMIZATION) {
            // Soft constraint optimization only for low hc's,
            // because sc calculation is not cheap

            int currentSC = currentWeek.getSoftConstraintsViolations();
            int localSC = localBestSolution.getSoftConflicts();
            double alpha = 10;

            // TODO NOW in order to use simulated annealing
//                currentWeek.apply(localBestWeek);

            if (currentSC < localSC || currentSC == 0) {
                localBestSolution = currentWeek.export();
                int globalSC = globalBestSolution.getSoftConflicts();
                int globalHC = globalBestSolution.getHardConflicts();
                if (globalHC < MAX_HC_FOR_SC_OPTIMIZATION)
                    result = Improvement.YES;

                if (currentSC < globalSC || currentSC == 0) {
                    globalBestSolution = localBestSolution;
                    currentWeek.getSoftConstraintsViolations(true);
                    logger.debug("HC:" + currentHC + " \tSC:" + currentSC
                            + " \t[" + oHelper.toString() + "]");
                    statusBar.setMessage(currentHC + " hard- / " + currentSC + " softconflicts");
                    // Stop algorithm: Best solution.
                    // No more improvements possible
                    if (currentSC < SC_BORDER) {
                        result = Improvement.FINISHED;
                    }
                }
            } // local sc optimum
        } // sc's are enabled?    

        return result;
    }

    /**
     * This method corrects the violation of ordering (of events) if
     * there is any. It uses a bubble sort like algorithm for topological die
     * sorting -> TODO replace with TopologicalFastSorter implementation
     */
    void correctOrderWithBubbleSort(List<Assignment> conflictingAssignments) {
        // save indices to boost performance
        Map<Assignment, Integer> orderIndices = new FastMap<Assignment, Integer>();
        for (int i = 0; i < conflictingAssignments.size(); i++) {
            orderIndices.put(conflictingAssignments.get(i), i);
        }

        // here we save the current Event and one follower
        if (orderActions == null) {
            // no matter if assignemnt is valid or not! (apply ordering on both!)
            orderActions = new ArrayList<Pair>(conflictingAssignments.size());
            for (Assignment ass : conflictingAssignments) {
                for (Assignment tmpAss : createFollowsAssignments(ass, conflictingAssignments)) {
                    orderActions.add(new Pair(ass, tmpAss));
                }
            }
            logger.debug("orderActions.size:" + orderActions.size());
        }

        boolean again = true;
        // there are maximal n-1 reorderings necessary if we have the biggest
        // element on the right and want it on the left. But we need one more
        // iteration to check that there are no changes.               
        int n = orderActions.size();
        for (int counter = 0; again && counter < n; counter++) {
            again = false;
            for (Pair p : orderActions) {
                Integer f = orderIndices.get(p.first);
                Integer s = orderIndices.get(p.second);
                if (f != null && s != null && f > s) {
                    //swap
                    conflictingAssignments.set(f, p.second);
                    conflictingAssignments.set(s, p.first);

                    //save indices
                    orderIndices.put(p.second, f);
                    orderIndices.put(p.first, s);
                    again = true;
                }
            }
        }

        if (n > 0 && again) {
            //Sth. is wrong here because runtime of bubble sort is maximal n^2.
            //This could be a wrong order definition e.g. A<B, B<C and C<A is wrong,
            //but would lead to this result.

            if (logger.isDebugEnabled()) {
                throw new RuntimeException("Check the ordering rules!");
            }
        }
    }

    private Set<Assignment> createFollowsAssignments(Assignment ass, Collection<Assignment> allAssignments) {
        Event event = ass.getEvent();
        EventOrderConstraint oc = event.getConstraint(EventOrderConstraint.class);
        if (oc == null)
            return Collections.EMPTY_SET;

        Set<Assignment> res = FastSet.newInstance();
        for (Event ev : oc.getFollows()) {
            for (Assignment tmpAss : allAssignments) {
                if (tmpAss.getEvent() == ev) {
                    res.add(tmpAss);
                    break;
                }
            }
        }
        return res;
    }

    private Set<Assignment> createBeforesAssignments(Assignment ass, Collection<Assignment> allAssignments) {
        Event event = ass.getEvent();
        EventOrderConstraint oc = event.getConstraint(EventOrderConstraint.class);
        if (oc == null)
            return Collections.EMPTY_SET;

        Set<Assignment> res = FastSet.newInstance();
        for (Event ev : oc.getBefores()) {
            for (Assignment tmpAss : allAssignments) {
                if (tmpAss.getEvent() == ev) {
                    res.add(tmpAss);
                    break;
                }
            }
        }
        return res;
    }

    private enum Improvement {

        NO, YES, FINISHED
    }

    private static class Pair {

        Assignment first;
        Assignment second;

        Pair(Assignment f, Assignment s) {
            first = f;
            second = s;
        }
    }
}
TOP

Related Classes of de.timefinder.algo.ncp.NoCollisionPrinciple

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.
', 'UA-20639858-1', 'auto'); ga('send', 'pageview');