Package de.timefinder.algo.roomassignment

Source Code of de.timefinder.algo.roomassignment.AssignmentManager

/*
* 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.roomassignment;

import de.timefinder.algo.constraint.EventOrderConstraint;
import de.timefinder.data.algo.Assignment;
import de.timefinder.data.Event;
import de.timefinder.data.Feature;
import de.timefinder.data.Location;
import de.timefinder.data.util.MapEntry;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javolution.util.FastMap;
import javolution.util.FastSet;

/**
* This method calulates the assignment of Room's to the added Event's.
* <p/>
* It holds a matrix:
* Event1, Event2, ...
* Room1        (dSize, dFeature)
* Room2
* ....
* <p/>
* Where the entry "(dSize, dFeature)" discribes how many free seats and left
* features there are. The entry won't be assignable if one of both numbers
* is negative.
*
* @author Peter Karich, peat_hal 'at' users 'dot' sourceforge 'dot' net
*/
public class AssignmentManager {

    private final static Class<? extends AssignmentAlgorithm> clazz;

    static {
        clazz = UnweightedMatchingAlgorithm.class;
        //clazz = BruteForceAssignment.cass;
//        clazz = HungarianAlgorithm.class;
        //clazz = KuhnMunkresAlgorithm.class;
        //clazz = PathGrowingAlgorithm.class;
        //clazz = TooSimpleApprox.class;

        System.out.println("algorithm:" + clazz);
    }
    private AssignmentAlgorithm algo;
    //TODO should we calculate the maximal number of Event
    //or shouldn't we allow '#TIs > #rooms'       
    private List<Assignment> allAssignments;
    /**
     * Calculates which events shouldn't be added into this group
     * because of its followers and 'beforers'
     */
    private Map<Event, Integer> orderContainer;
    /**
     * From this matrix we recreate the assignment matrix on every
     * recalculation call.
     */
    private AssignmentMatrix origMatrix;
    private List<Location> allLocations;
    private FastSet<Location> availableLocations;
    private int slot;

    public AssignmentManager(int slot, List<Location> possibleRooms) {
        this(slot, possibleRooms, possibleRooms);
    }

    private AssignmentManager(int slot, List<Location> possibleRooms, Collection<Location> newAvailableLocations) {
        try {
            algo = clazz.newInstance();
        } catch (Exception ex) {
            throw new RuntimeException("Cannot create instance for algorithm:" + clazz, ex);
        }

        this.slot = slot;
        allLocations = possibleRooms;
        origMatrix = new SimpleAssignmentMatrixImpl(possibleRooms.size());
        allAssignments = new ArrayList<Assignment>(possibleRooms.size() / 2);
        availableLocations = FastSet.newInstance();
        availableLocations.addAll(newAvailableLocations);
        orderContainer = new FastMap<Event, Integer>();
    }

    void setAlgorithm(AssignmentAlgorithm algo) {
        this.algo = algo;
    }

    Collection<Location> getAvailableLocations() {
        return availableLocations;
    }

    public List<Assignment> getAll() {
        return allAssignments;
    }

    /**
     * This method adds the specified Event if the assignment manager
     * finds a free room for it.
     *
     * @return rollback data for this assign
     */
    public final Object assign(Assignment ass) {
        if (!checkPreconditionsPass(ass))
            return null;

        Object rollbackData = createRollbackAssignData();
        Location loc = getPossibleLocation(ass);
        float[] column = createColumn(ass);
        if (loc != null)
            assign(ass, loc, column);
        else {
            //TODO PERFORMANCE: introduce possibility of skipping recalculation which
            //depends on getFreeRooms().size() and a 'cantAddTimeInterval' counter
            if (!reassign(ass, column))
                return null;
        }

        return rollbackData;
    }

    /**
     * This method calculates if the specified location could be used
     * for the specified assignment
     */
    public boolean isForcedAssignmentPossible(Assignment assignment, Location loc) {
        if (!checkPreconditionsPass(assignment))
            return false;

        // before directly returning the previous check of all assignments is necessary (start < mainAssStart etc)
        if (availableLocations.contains(loc))
            return true;

        // replace the colum of the event with specified loc
        int assIndex = 0;
        float prevAssColumn[] = null;
        for (; assIndex < allAssignments.size(); assIndex++) {
            Assignment ass = allAssignments.get(assIndex);
            if (ass.getLocation() == loc) {
                prevAssColumn = origMatrix.getColumn(assIndex);
                break;
            }
        }

        if (prevAssColumn == null)
            throw new IllegalStateException("Cannot find event for location:" + loc);

        // try if prevAss can "live with out the already assigned location loc",
        // so that we could use loc for assignment

        for (int ii = 0; ii < prevAssColumn.length; ii++) {
            if (allLocations.get(ii) != loc && prevAssColumn[ii] < Float.MAX_VALUE)
                return true;
        }
        return false;
    }

    public Location calculateLocation(Assignment assignment) {
        if (!checkPreconditionsPass(assignment))
            return null;

        // check against start + end
        Location loc = getPossibleLocation(assignment);
        if (loc != null)
            return loc;

        float newEntries[] = createColumn(assignment);
        int assignmentEntries[][] = calculateAssignment(newEntries);
        if (assignmentEntries == null)
            return null;

        if (countValid(assignmentEntries) != allAssignments.size() + 1)
            return null;

        return allLocations.get(assignmentEntries[allAssignments.size()][0]);
    }

    /**
     * Performs check against location size and overlapping assignments
     *
     * @return true if all conditions are valid; false if no locations exist
     * or if there is at least one assignment, which starts earlier or ends later
     * than the specified assignment
     */
    private boolean checkPreconditionsPass(Assignment assignment) {
        if (orderContainer.containsKey(assignment.getEvent()))
            return false;

        //if no free rooms are available then 'close' this EventGroup
        if (getAvailableLocations().size() == 0)
            return false;

//        for (int assIndex = 0; assIndex < allAssignments.size(); assIndex++) {
//            Assignment ass = allAssignments.get(assIndex);
//            int assStart = ass.getStart();
//            int mainAssStart = assignment.getStart();
//
//            if (assStart < mainAssStart ||
//                    assStart + ass.getEvent().getDuration() > mainAssStart + assignment.getEvent().getDuration())
//                return false;
//        }
        return true;
    }

    Location getPossibleLocation(Assignment ass) {
        Event ev = ass.getEvent();
        int visitors = ev.getPersonsMap().size();
        Collection<? extends Feature> features = ev.getFeatures();
        //FeatureSet features = event.getSubject().getFeatureSet();

        // TODO forceAssignment the event to the best suited room,
        // if more than one room is available or shuffle list!
        for (Location loc : getAvailableLocations()) {
            if (loc.getCapacity() >= visitors && loc.getFeatures().containsAll(features))
                return loc;
        }
        return null;
    }

    public Object forceAssignment(Assignment assigment, Location loc) {
        Object rollbackData = createRollbackAssignData();

        // createColumn where only one entry is != MAX_VALUE
        float column[] = new float[allLocations.size()];
        for (int locIndex = 0; locIndex < column.length; locIndex++) {
            if (allLocations.get(locIndex) == loc)
                column[locIndex] = createEntry(loc, assigment);
            else
                column[locIndex] = Float.MAX_VALUE;
        }

        if (availableLocations.contains(loc)) {
            assign(assigment, loc, column);
            return rollbackData;
        }

        if (reassign(assigment, column))
            return rollbackData;
        else
            return null;
    }

    /**
     * Removes the specified assignment
     *
     * @return data necessary to un do this remove action
     */
    public Object remove(Assignment ass) {
        int index = allAssignments.indexOf(ass);
        if (index < 0)
            throw new IllegalStateException("Couldn't find assignment to remove it! " + ass);

        Assignment rmAss = allAssignments.remove(index);
        assert rmAss != null : index + " Cannot remove assignment:" + ass;
        assert index >= 0 : index + " Cannot remove assignment:" + ass;

        float[] oldColumn = origMatrix.getColumn(index);
        Set oldAvailableLocs = FastSet.newInstance();
        oldAvailableLocs.addAll(availableLocations);

        EventOrderConstraint oc = ass.getEvent().getConstraint(EventOrderConstraint.class);
        if (oc != null) {
            for (Event ev : oc.getFollows()) {
                Integer n = orderContainer.remove(ev);
                if (n > 1)
                    orderContainer.put(ev, n - 1);
            }
            for (Event ev : oc.getBefores()) {
                Integer n = orderContainer.remove(ev);
                if (n > 1)
                    orderContainer.put(ev, n - 1);
            }
        }

        origMatrix.removeColumn(index);
        availableLocations.add(ass.getLocation());
        assert check();
        return new Object[]{oldAvailableLocs, oldColumn, ass.getLocation()};
    }

    /**
     * This method recalculates and changes the room assignments
     * for all event's and the new specified one if possible.
     *
     * @return true if an assignment it possible for all Room's, false otherwise.
     */
    private final boolean reassign(Assignment ass, float[] newEntries) {
        int noOfEvents = allAssignments.size();
        //forceAssignment the temporarly new Event only to the weights not to the matrix! (not now)
        int assignment[][] = calculateAssignment(newEntries);
        if (assignment != null && countValid(assignment) == noOfEvents + 1) {
            availableLocations.clear();
            availableLocations.addAll(allLocations);

            for (Assignment tmpAss : allAssignments) {
                tmpAss.setLocation(null);
            }

            for (int assIndex = 0; assIndex < noOfEvents; assIndex++) {
                assert assignment[assIndex] != null :
                        "If you use PathGrowingAlgo check if the correct toBipartiteArray "
                        + "method was called with event:\n" + ass;
                Location loc = allLocations.get(assignment[assIndex][0]);
                removeFromAvailableLocations(allAssignments.get(assIndex), loc);
            }
            Location loc2 = allLocations.get(assignment[noOfEvents][0]);
            assign(ass, loc2, newEntries);
            return true;
        }

        return false;
    }

//    private Object createRollbackAssignData() {
//        Set oldAvailLocs = FastSet.newInstance();
//        oldAvailLocs.addAll(availableLocations);
//        return new Object[]{new SimpleAssignmentMatrixImpl(origMatrix), oldAvailLocs};
//    }
    private Object createRollbackAssignData() {
        List locAssignment = new ArrayList(allAssignments.size());
        for (Assignment ass : allAssignments) {
            locAssignment.add(new MapEntry(ass, ass.getLocation()));
        }
        assert origMatrix.getColumns() == locAssignment.size() : origMatrix + " \n " + locAssignment;
        return new Object[]{new SimpleAssignmentMatrixImpl(origMatrix), locAssignment};
    }

    /**
     * A new matrix will be calculated on 'adding' an event. Now rollback to
     * old matrix with the help of the undoData, so that no new calculation
     * is necessary.
     */
    public final void rollbackAssign(Assignment ass, Object allRollbackData) {
        Object[] data = (Object[]) allRollbackData;

        // TODO PERFORMANCE instead clear + using assign(Assignment, Location, float[]) we
        // should create a separate method where we directly assign the elements!

        orderContainer.clear();
        availableLocations.clear();
        availableLocations.addAll(allLocations);
        allAssignments.clear();
        origMatrix.clear();

        AssignmentMatrix newMatrix = (AssignmentMatrix) data[0];
        List<Entry<Assignment, Location>> locAss = (List<Entry<Assignment, Location>>) data[1];

        int ii = 0;
        for (Entry<Assignment, Location> entry : locAss) {
            assign(entry.getKey(), entry.getValue(), newMatrix.getColumn(ii));
            ii++;
        }

//        origMatrix = (AssignmentMatrix) data[0];
//        FastSet.recycle(availableLocations);
//        availableLocations = (FastSet<Location>) data[1];
//        boolean ret = allAssignments.remove(ass);
//        assert ret : "Cannot remove:" + ass;
        assert check();
    }

    /**
     * On remove no calculation is necessary, but some data changes to internal
     * storage. Now revert these datachanges.
     */
    public final void rollbackRemove(Assignment ass, Object allRollbackData) {
        Object[] data = (Object[]) allRollbackData;

        assert !allAssignments.contains(ass) : "assignment:" + ass;
        addToOrder(ass);

        // TODO duplicate code, @see assign
        allAssignments.add(ass);

        FastSet.recycle(availableLocations);
        availableLocations = (FastSet<Location>) data[0];
        origMatrix.addColumn((float[]) data[1]);
        ass.setLocation((Location) data[2]);

        assert check();
    }

    private void addToOrder(Assignment ass) {
        EventOrderConstraint oc = ass.getEvent().getConstraint(EventOrderConstraint.class);
        if (oc != null) {
            for (Event ev : oc.getFollows()) {
                Integer n = orderContainer.put(ev, 1);
                if (n != null)
                    orderContainer.put(ev, n + 1);
            }
            for (Event ev : oc.getBefores()) {
                Integer n = orderContainer.put(ev, 1);
                if (n != null)
                    orderContainer.put(ev, n + 1);
            }
        }
    }

    /**
     * Now we know that the specified Assignment should use the specified location,
     * so assign assign it to the matrix.
     */
    private final void assign(Assignment ass, Location loc, float[] column) {
        assert createEntry(loc, ass) < Float.MAX_VALUE : "assignment:" + ass + " location:" + loc;
        assert !allAssignments.contains(ass) : "Already contains assignment:" + ass + " location:" + loc;

        addToOrder(ass);
        allAssignments.add(ass);
        origMatrix.addColumn(column);
        removeFromAvailableLocations(ass, loc);
    }

    private final void removeFromAvailableLocations(Assignment ass, Location loc) {
        //necessary for the correct assignment
        boolean ret = availableLocations.remove(loc);
        assert ret : "Cannot remove:" + loc;

        ass.setLocation(loc);
        assert check();
    }

    private boolean checkWithout() {
        return true;
    }

    private boolean check() {
        assert origMatrix.getColumns() == allAssignments.size() : "columns:"
                + origMatrix.getColumns() + " " + origMatrix + " \nassignments:" + allAssignments.size() + " " + allAssignments;
        assert allLocations.size() >= allAssignments.size() : "locations:" + allLocations.size() + "\t assignments:" + allAssignments.size();

        Map<Location, Assignment> usedLocs = FastMap.newInstance();
        int column = 0;
        for (Assignment ass : allAssignments) {
            if (ass.getLocation() == null)
                continue;

            int index = allLocations.indexOf(ass.getLocation());
            if (origMatrix.getColumn(column)[index] == Float.MAX_VALUE)
                throw new IllegalStateException("Wrong data of location or assignment-matrix:" + ass + "\n" + origMatrix);

            column++;
            Assignment old = usedLocs.put(ass.getLocation(), ass);
            if (old != null)
                throw new IllegalStateException("one location is several times assigned! ass1: "
                        + ass + " ass2: " + old + " matrix:\n" + origMatrix);
        }

        return true;
    }

    int[][] calculateAssignment(float[] newEntries) {
        int noOfEvents = allAssignments.size();
        float weights[][] = new float[allLocations.size()][noOfEvents + 1];

        for (int locIndex = 0; locIndex < allLocations.size(); locIndex++) {
            for (int assIndex = 0; assIndex < noOfEvents; assIndex++) {
                //warning: different "column, row" styles!
                weights[locIndex][assIndex] = origMatrix.get(assIndex, locIndex);
            }
        }

        // there should be at least one possible entry (an entry which is smaller than MAX_VALUE)
        // otherwise we can skip the calculation
        int validCounter = 0;

        for (int locIndex = 0; locIndex < allLocations.size(); locIndex++) {
            //different "column, row" styles!
            weights[locIndex][noOfEvents] = newEntries[locIndex];
            if (weights[locIndex][noOfEvents] < Float.MAX_VALUE) {
                validCounter++;
            }
        }

        if (validCounter == 0) {
            return null;
        }

        //TODO PERFORMANCE: use incremental assignment algorithm!
        return algo.computeAssignments(weights);
    }

    /**
     * This method returns the number of valid assignments for the
     * specified result (from an AssignmentAlgorithm)
     */
    final int countValid(int assignmentResult[][]) {
        int tmpCounter = 0;
        for (int ii = 0; ii < assignmentResult.length; ii++) {
            if (assignmentResult[ii] != null) {
                tmpCounter++;
            }
        }
        return tmpCounter;
    }

    private final float[] createColumn(Assignment assignment) {
        float column[] = new float[allLocations.size()];
        for (int locIndex = 0; locIndex < allLocations.size(); locIndex++) {
            column[locIndex] = createEntry(allLocations.get(locIndex), assignment);
        }

        return column;
    }

    final float createEntry(Location room, Assignment ass) {
        Event ev = ass.getEvent();
        float value = room.getCapacity() - ev.getPersonsMap().size();
        if (value >= 0) {
            if (room.getFeatures().containsAll(ev.getFeatures()))
                value += room.getFeatures().size() - ev.getFeatures().size();
            else
                value = Float.MAX_VALUE;
        } else
            value = Float.MAX_VALUE;

        return value;
    }

    @Override
    public String toString() {
        return "slot: " + slot + " Assignments[" + allAssignments.size() + "]: " + allAssignments;
    }
}
TOP

Related Classes of de.timefinder.algo.roomassignment.AssignmentManager

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.