org.unitime.timetable.server.solver.TimetableGridSolverHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.unitime.timetable.server.solver.TimetableGridSolverHelper.java

Source

/*
 * Licensed to The Apereo Foundation under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for
 * additional information regarding copyright ownership.
 *
 * The Apereo Foundation licenses this file to you 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 org.unitime.timetable.server.solver;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeSet;
import java.util.Vector;

import org.cpsolver.coursett.constraint.DepartmentSpreadConstraint;
import org.cpsolver.coursett.constraint.DiscouragedRoomConstraint;
import org.cpsolver.coursett.constraint.GroupConstraint;
import org.cpsolver.coursett.constraint.InstructorConstraint;
import org.cpsolver.coursett.constraint.JenrlConstraint;
import org.cpsolver.coursett.constraint.RoomConstraint;
import org.cpsolver.coursett.criteria.TooBigRooms;
import org.cpsolver.coursett.model.Lecture;
import org.cpsolver.coursett.model.Placement;
import org.cpsolver.coursett.model.RoomLocation;
import org.cpsolver.coursett.model.RoomSharingModel;
import org.cpsolver.coursett.model.Student;
import org.cpsolver.coursett.model.StudentGroup;
import org.cpsolver.coursett.model.TimeLocation;
import org.cpsolver.coursett.model.TimetableModel;
import org.cpsolver.ifs.assignment.Assignment;
import org.cpsolver.ifs.model.Constraint;
import org.hibernate.type.LongType;
import org.unitime.localization.impl.Localization;
import org.unitime.timetable.defaults.ApplicationProperty;
import org.unitime.timetable.gwt.resources.GwtConstants;
import org.unitime.timetable.gwt.shared.TimetableGridInterface.TimetableGridBackground;
import org.unitime.timetable.gwt.shared.TimetableGridInterface.TimetableGridCell;
import org.unitime.timetable.gwt.shared.TimetableGridInterface.TimetableGridCell.Property;
import org.unitime.timetable.gwt.shared.TimetableGridInterface.TimetableGridCell.Type;
import org.unitime.timetable.gwt.shared.TimetableGridInterface.TimetableGridModel;
import org.unitime.timetable.model.Class_;
import org.unitime.timetable.model.CourseOffering;
import org.unitime.timetable.model.DepartmentalInstructor;
import org.unitime.timetable.model.PreferenceLevel;
import org.unitime.timetable.model.dao.Class_DAO;
import org.unitime.timetable.model.dao.CurriculumDAO;
import org.unitime.timetable.solver.TimetableSolver;
import org.unitime.timetable.solver.ui.StudentGroupInfo;
import org.unitime.timetable.util.Constants;
import org.unitime.timetable.webutil.timegrid.SolverGridModel;

/**
 * @author Tomas Muller
 */
public class TimetableGridSolverHelper extends TimetableGridHelper {
    protected static final GwtConstants CONSTANTS = Localization.create(GwtConstants.class);

    public static TimetableGridModel createModel(TimetableSolver solver, RoomConstraint room,
            TimetableGridContext context) {
        TimetableGridModel model = new TimetableGridModel(ResourceType.ROOM.ordinal(), room.getResourceId());
        model.setName(room.getRoomName());
        model.setSize(room.getCapacity());
        model.setType(room.getType());
        model.setFirstDay(context.getFirstDay());
        model.setFirstSessionDay(context.getFirstSessionDay());
        model.setFirstDate(context.getFirstDate());
        if (room instanceof DiscouragedRoomConstraint)
            model.setNameColor(PreferenceLevel.prolog2color(PreferenceLevel.sStronglyDiscouraged));

        Assignment<Lecture, Placement> assignment = solver.currentSolution().getAssignment();
        List<Placement> assignments = new ArrayList<Placement>();
        BitSet week = null;
        if (context.getFirstDay() >= 0) {
            week = new BitSet();
            for (int i = 0; i < 7; i++) {
                int d = context.getFirstDay() + i - context.getWeekOffset();
                if (d >= 0)
                    week.set(d);
            }
            week = null;
        }
        for (Lecture lecture : room.variables()) {
            Placement placement = assignment.getValue(lecture);
            if (placement == null)
                continue;
            if (placement.hasRoomLocation(model.getResourceId())) {
                if (week == null || placement.getTimeLocation().shareWeeks(week))
                    assignments.add(placement);
            }
        }
        createCells(model, solver, assignments, context, false);

        Set<Long> deptIds = new HashSet<Long>();
        String deptIdsStr = solver.getProperties().getProperty("General.DepartmentIds");
        if (deptIdsStr != null) {
            for (StringTokenizer stk = new StringTokenizer(deptIdsStr, ","); stk.hasMoreTokens();) {
                deptIds.add(Long.valueOf(stk.nextToken()));
            }
        }

        RoomSharingModel sharing = room.getSharingModel();
        if (sharing != null) {
            for (int i = 0; i < Constants.DAY_CODES.length; i++) {
                int start = 0;
                Boolean av = null;
                for (int j = 0; j < Constants.SLOTS_PER_DAY; j++) {
                    Boolean available;
                    if (sharing.isFreeForAll(i, j)) {
                        available = true;
                    } else if (sharing.isNotAvailable(i, j)) {
                        available = false;
                    } else {
                        Long dept = sharing.getDepartmentId(i, j);
                        available = (dept == null || deptIds.contains(dept));
                    }
                    if (av == null) {
                        av = available;
                        start = j;
                    } else if (!av.equals(available)) {
                        if (!av) {
                            TimetableGridBackground bg = new TimetableGridBackground();
                            bg.setBackground(sBgColorNotAvailable);
                            bg.setSlot(start);
                            bg.setLength(j - start);
                            bg.setDay(i);
                            bg.setAvailable(false);
                            model.addBackground(bg);
                        }
                        av = available;
                        start = j;
                    }
                }
                if (av != null && !av) {
                    TimetableGridBackground bg = new TimetableGridBackground();
                    bg.setBackground(sBgColorNotAvailable);
                    bg.setSlot(start);
                    bg.setLength(Constants.SLOTS_PER_DAY - start);
                    bg.setDay(i);
                    bg.setAvailable(false);
                    model.addBackground(bg);
                }
            }
        }

        if (room.getAvailableArray() != null) {
            Set<Placement> done = new HashSet<Placement>();
            for (int i = 0; i < Constants.DAY_CODES.length; i++) {
                for (int j = 0; j < Constants.SLOTS_PER_DAY; j++) {
                    List<Placement> placements = room.getAvailableArray()[i * Constants.SLOTS_PER_DAY + j];
                    if (placements != null && !placements.isEmpty()) {
                        for (Placement p : placements) {
                            if ((context.isShowEvents() || p.getAssignmentId() != null) && done.add(p)
                                    && (week == null || p.getTimeLocation().shareWeeks(week)))
                                createCells(model, solver, p, context, true);
                        }
                    }
                }
            }
        }

        return model;
    }

    public static TimetableGridModel createModel(TimetableSolver solver, InstructorConstraint instructor,
            TimetableGridContext context) {
        TimetableGridModel model = new TimetableGridModel(ResourceType.INSTRUCTOR.ordinal(), instructor.getId());
        model.setName(instructor.getName());
        model.setType(instructor.getType());
        model.setFirstDay(context.getFirstDay());
        model.setFirstSessionDay(context.getFirstSessionDay());
        model.setFirstDate(context.getFirstDate());

        Assignment<Lecture, Placement> assignment = solver.currentSolution().getAssignment();
        List<Placement> assignments = new ArrayList<Placement>();
        BitSet week = null;
        if (context.getFirstDay() >= 0) {
            week = new BitSet();
            for (int i = 0; i < 7; i++) {
                int d = context.getFirstDay() + i - context.getWeekOffset();
                if (d >= 0)
                    week.set(d);
            }
        }
        for (Lecture lecture : instructor.variables()) {
            Placement placement = assignment.getValue(lecture);
            if (placement == null)
                continue;
            if (week == null || placement.getTimeLocation().shareWeeks(week))
                assignments.add(placement);
        }
        createCells(model, solver, assignments, context, false);

        if (instructor.getUnavailabilities() != null) {
            for (Placement p : instructor.getUnavailabilities()) {
                if ((context.isShowEvents() || p.getAssignmentId() != null)
                        && (week == null || p.getTimeLocation().shareWeeks(week)))
                    createCells(model, solver, p, context, true);
            }
        }
        for (Student student : ((TimetableModel) solver.currentSolution().getModel()).getAllStudents()) {
            if (instructor.equals(student.getInstructor())) {
                for (Lecture lecture : student.getLectures()) {
                    Placement placement = assignment.getValue(lecture);
                    if (placement != null && !instructor.variables().contains(lecture)
                            && (week == null || placement.getTimeLocation().shareWeeks(week))) {
                        for (TimetableGridCell cell : createCells(model, solver, placement, context, false)) {
                            cell.setItalics(true);
                        }
                    }
                }
            }
        }

        return model;
    }

    public static TimetableGridModel createModel(TimetableSolver solver, DepartmentSpreadConstraint department,
            TimetableGridContext context) {
        TimetableGridModel model = new TimetableGridModel(ResourceType.DEPARTMENT.ordinal(),
                department.getDepartmentId());
        model.setName(department.getName());
        model.setSize(department.variables().size());
        model.setFirstDay(context.getFirstDay());
        model.setFirstSessionDay(context.getFirstSessionDay());
        model.setFirstDate(context.getFirstDate());

        Assignment<Lecture, Placement> assignment = solver.currentSolution().getAssignment();
        List<Placement> placements = new ArrayList<Placement>();
        for (Lecture lecture : department.variables()) {
            Placement placement = assignment.getValue(lecture);
            if (placement != null)
                placements.add(placement);
        }
        createCells(model, solver, placements, context, false);

        return model;
    }

    public static TimetableGridModel createModel(TimetableSolver solver, int resourceType, long resourceId,
            String name, int size, Collection<Placement> placements, TimetableGridContext context) {
        TimetableGridModel model = new TimetableGridModel(resourceType, resourceId);
        model.setName(name);
        model.setSize(size);
        model.setFirstDay(context.getFirstDay());
        model.setFirstSessionDay(context.getFirstSessionDay());
        model.setFirstDate(context.getFirstDate());
        createCells(model, solver, placements, context, false);
        return model;
    }

    public static TimetableGridModel createModel(TimetableSolver solver, String name, List<Student> students,
            TimetableGridContext context) {
        TimetableGridModel model = new TimetableGridModel(ResourceType.CURRICULUM.ordinal(), -1l);
        model.setName(name);
        model.setFirstDay(context.getFirstDay());
        model.setFirstSessionDay(context.getFirstSessionDay());
        model.setFirstDate(context.getFirstDate());

        Assignment<Lecture, Placement> assignment = solver.currentSolution().getAssignment();
        Hashtable<Long, String> groups = new Hashtable<Long, String>();
        for (Object[] o : (List<Object[]>) CurriculumDAO.getInstance().getSession().createQuery(
                "select c.course.instructionalOffering.uniqueId, g.name from CurriculumCourse c inner join c.groups g where "
                        + "c.classification.curriculum.abbv || ' ' || c.classification.academicClassification.code = :name and "
                        + "c.classification.curriculum.department.session.uniqueId = :sessionId")
                .setString("name", name)
                .setLong("sessionId", solver.getProperties().getPropertyLong("General.SessionId", null))
                .setCacheable(true).list()) {
            Long courseId = (Long) o[0];
            String group = (String) o[1];
            String old = groups.get(courseId);
            groups.put(courseId, (old == null ? "" : old + ", ") + group);
        }
        double size = 0;
        Hashtable<Placement, Double> placements = new Hashtable<Placement, Double>();
        for (Student student : students) {
            int cnt = 0;
            double w = 0;
            for (Lecture lecture : student.getLectures()) {
                w += student.getOfferingWeight(lecture.getConfiguration());
                cnt++;
                Placement placement = assignment.getValue(lecture);
                if (placement != null) {
                    Double old = placements.get(placement);
                    placements.put(placement,
                            student.getOfferingWeight(lecture.getConfiguration()) + (old == null ? 0 : old));
                }
            }
            if (student.getCommitedPlacements() != null)
                for (Placement placement : student.getCommitedPlacements()) {
                    w += student.getOfferingWeight(placement.variable().getConfiguration());
                    cnt++;
                    Double old = placements.get(placement);
                    placements.put(placement, student.getOfferingWeight(placement.variable().getConfiguration())
                            + (old == null ? 0 : old));
                }
            if (cnt > 0)
                size += w / cnt;
        }
        model.setSize((int) Math.round(size));
        model.setUtilization(countUtilization(context, placements.keySet()));
        for (Map.Entry<Placement, Double> entry : placements.entrySet()) {
            for (TimetableGridCell cell : createCells(model, solver, entry.getKey(), context,
                    entry.getKey().variable().isCommitted())) {
                String group = (entry.getKey().variable().getConfiguration() == null ? null
                        : groups.get(entry.getKey().variable().getConfiguration().getOfferingId()));
                cell.setGroup("(" + Math.round(entry.getValue()) + (group == null ? "" : ", " + group) + ")");
            }
        }

        return model;
    }

    public static TimetableGridModel createModel(TimetableSolver solver, StudentGroup group,
            TimetableGridContext context) {
        TimetableGridModel model = new TimetableGridModel(ResourceType.STUDENT_GROUP.ordinal(), group.getId());
        model.setName(group.getName());
        model.setFirstDay(context.getFirstDay());
        model.setFirstSessionDay(context.getFirstSessionDay());
        model.setFirstDate(context.getFirstDate());

        Assignment<Lecture, Placement> assignment = solver.currentSolution().getAssignment();
        double size = 0;
        Hashtable<Placement, Double> placements = new Hashtable<Placement, Double>();
        for (Student student : group.getStudents()) {
            int cnt = 0;
            double w = 0;
            for (Lecture lecture : student.getLectures()) {
                w += student.getOfferingWeight(lecture.getConfiguration());
                cnt++;
                Placement placement = assignment.getValue(lecture);
                if (placement != null) {
                    Double old = placements.get(placement);
                    placements.put(placement,
                            student.getOfferingWeight(lecture.getConfiguration()) + (old == null ? 0 : old));
                }
            }
            if (student.getCommitedPlacements() != null)
                for (Placement placement : student.getCommitedPlacements()) {
                    w += student.getOfferingWeight(placement.variable().getConfiguration());
                    cnt++;
                    Double old = placements.get(placement);
                    placements.put(placement, student.getOfferingWeight(placement.variable().getConfiguration())
                            + (old == null ? 0 : old));
                }
            if (cnt > 0)
                size += w / cnt;
        }

        model.setSize((int) Math.round(size));
        model.setUtilization(StudentGroupInfo.value(group));
        for (Map.Entry<Placement, Double> entry : placements.entrySet()) {
            for (TimetableGridCell cell : createCells(model, solver, entry.getKey(), context,
                    entry.getKey().variable().isCommitted())) {
                cell.setGroup("(" + Math.round(entry.getValue()) + ")");
            }
        }

        return model;
    }

    protected static void createCells(TimetableGridModel model, TimetableSolver solver,
            Collection<Placement> placements, TimetableGridContext context, boolean notAvailable) {
        model.setUtilization(countUtilization(context, placements));
        for (Placement placement : placements) {
            if (placement.variable().isCommitted())
                continue;
            createCells(model, solver, placement, context, notAvailable);
        }
    }

    protected static void createCells(TimetableGridModel model, TimetableSolver solver, Placement[] resource,
            TimetableGridContext context, int firstDay, int lastDay) {
        Map<Lecture, TimetableGridCell> processed = new HashMap<Lecture, TimetableGridCell>();
        List<Placement> placements = new ArrayList<Placement>();
        for (int i = firstDay; i < lastDay; i++) {
            for (int j = 0; j < Constants.SLOTS_PER_DAY; j++) {
                Placement placement = resource[i * Constants.SLOTS_PER_DAY + j];
                if (placement == null)
                    continue;
                Lecture lecture = placement.variable();
                if (lecture.isCommitted())
                    continue;
                TimetableGridCell cell = processed.get(lecture);
                if (cell == null) {
                    cell = createCell(model, solver, i, j, (Lecture) placement.variable(), placement, context,
                            false);
                    processed.put(lecture, cell);
                    placements.add(placement);
                } else {
                    cell = new TimetableGridCell(cell, i, placement.getTimeLocation().getDatePatternName());
                }
                model.addCell(cell);
                j += placement.getTimeLocation().getNrSlotsPerMeeting() - 1;
            }
        }
        model.setUtilization(countUtilization(context, placements));
    }

    protected static List<TimetableGridCell> createCells(TimetableGridModel model, TimetableSolver solver,
            Placement placement, TimetableGridContext context, boolean notAvailable) {
        List<TimetableGridCell> cells = new ArrayList<TimetableGridCell>();
        TimetableGridCell cell = null;

        for (Enumeration<Integer> f = placement.getTimeLocation().getStartSlots(); f.hasMoreElements();) {
            int slot = f.nextElement();
            int idx = (7 + slot / Constants.SLOTS_PER_DAY - context.getWeekOffset()) % 7;
            if (context.getFirstDay() >= 0
                    && !placement.getTimeLocation().getWeekCode().get(context.getFirstDay() + idx))
                continue;

            if (cell == null) {
                cell = createCell(model, solver, slot / Constants.SLOTS_PER_DAY, slot % Constants.SLOTS_PER_DAY,
                        (Lecture) placement.variable(), placement, context, notAvailable);
            } else {
                cell = new TimetableGridCell(cell, slot / Constants.SLOTS_PER_DAY,
                        placement.getTimeLocation().getDatePatternName());
            }

            model.addCell(cell);
            cells.add(cell);
        }

        return cells;
    }

    protected static TimetableGridCell createCell(TimetableGridModel model, TimetableSolver solver, int day,
            int slot, Lecture lecture, Placement placement, TimetableGridContext context, boolean notAvailable) {
        TimetableGridCell cell = new TimetableGridCell();
        if (lecture.getClassId() < 0) {
            cell.setType(TimetableGridCell.Type.Event);
            cell.setId(-lecture.getClassId());
        } else {
            cell.setType(TimetableGridCell.Type.Class);
            cell.setId(lecture.getClassId());
            cell.setCommitted(lecture.isCommitted());
        }
        cell.addName(lecture.getName());
        cell.setDay(day);
        cell.setSlot(slot);
        cell.setLength(placement.getTimeLocation().getNrSlotsPerMeeting());

        int bgMode = context.getBgMode();
        // cell.setBackground(sBgColorNeutral);
        if (notAvailable)
            cell.setBackground(sBgColorNotAvailable);

        DecimalFormat df = new DecimalFormat("0.0");

        Assignment<Lecture, Placement> assignment = solver.currentSolution().getAssignment();

        int studConf = lecture.countStudentConflicts(assignment, placement)
                + lecture.getCommitedConflicts(placement);
        double penalty = 0.0;
        if (solver.getPerturbationsCounter() != null) {
            penalty = solver.getPerturbationsCounter().getPerturbationPenalty(assignment,
                    solver.currentSolution().getModel(), placement, new Vector());
        }
        DepartmentSpreadConstraint deptConstraint = null;
        for (Constraint c : lecture.constraints()) {
            if (c instanceof DepartmentSpreadConstraint) {
                deptConstraint = (DepartmentSpreadConstraint) c;
                break;
            }
        }

        switch (BgMode.values()[bgMode]) {
        case TimePref:
            if (notAvailable)
                break;
            int timePref = placement.getTimeLocation().getPreference();
            if (PreferenceLevel.sNeutral.equals(PreferenceLevel.int2prolog(timePref))
                    && lecture.nrTimeLocations() == 1)
                timePref = PreferenceLevel.sIntLevelRequired;
            cell.setBackground(pref2color(timePref));
            break;
        case RoomPref:
            if (notAvailable)
                break;
            int roomPref = placement.getRoomPreference();
            if (model.getResourceType() == ResourceType.ROOM.ordinal() && model.getResourceId() != null)
                roomPref = placement.getRoomLocation(model.getResourceId()).getPreference();
            if (PreferenceLevel.sNeutral.equals(PreferenceLevel.int2prolog(roomPref))
                    && lecture.nrRoomLocations() == lecture.getNrRooms())
                roomPref = PreferenceLevel.sIntLevelRequired;
            cell.setBackground(pref2color(roomPref));
            break;
        case StudentConf:
            cell.setBackground(conflicts2color(studConf));
            if (model.getResourceType() == ResourceType.INSTRUCTOR.ordinal())
                jenrl: for (JenrlConstraint jenrl : lecture.jenrlConstraints())
                    if (jenrl.getNrInstructors() > 0 && jenrl.isInConflict(assignment)) {
                        for (Student student : jenrl.getInstructors()) {
                            if (model.getResourceId().equals(student.getInstructor().getResourceId())
                                    && !student.getInstructor().variables().contains(lecture)) {
                                cell.setBackground(sBgColorRequired);
                                break jenrl;
                            }
                        }
                    }
            break;
        case InstructorBtbPref:
            int instrPref = 0;
            for (InstructorConstraint ic : lecture.getInstructorConstraints()) {
                instrPref += ic.getPreferenceCombination(assignment, placement);
            }
            cell.setBackground(pref2color(instrPref));
            break;
        case Perturbations:
            if (notAvailable)
                break;
            String perPref = PreferenceLevel.sNeutral;
            if (lecture.getInitialAssignment() != null) {
                if (placement.equals(lecture.getInitialAssignment()))
                    perPref = PreferenceLevel.sStronglyPreferred;
                else if (placement.sameTime((Placement) lecture.getInitialAssignment()))
                    perPref = PreferenceLevel.sDiscouraged;
                else if (placement.sameRooms((Placement) lecture.getInitialAssignment()))
                    perPref = PreferenceLevel.sStronglyDiscouraged;
                else
                    perPref = PreferenceLevel.sProhibited;
            }
            cell.setBackground(pref2color(perPref));
            break;
        case PerturbationPenalty:
            if (notAvailable)
                break;
            cell.setBackground(conflicts2color((int) Math.ceil(penalty)));
            break;
        case HardConflicts:
            if (notAvailable)
                break;
            cell.setBackground(pref2color(SolverGridModel.hardConflicts2pref(assignment, lecture, placement)));
            break;
        case DepartmentalBalancing:
            if (notAvailable)
                break;
            if (deptConstraint != null)
                cell.setBackground(conflicts2colorFast(deptConstraint.getMaxPenalty(assignment, placement)));
            break;
        case TooBigRooms:
            if (notAvailable)
                break;
            long minRoomSize = lecture.minRoomSize();
            int roomSize = placement.getRoomSize();
            if (roomSize < lecture.minRoomSize())
                cell.setBackground(pref2color(PreferenceLevel.sRequired));
            else
                cell.setBackground(pref2color(
                        ((TooBigRooms) solver.currentSolution().getModel().getCriterion(TooBigRooms.class))
                                .getPreference(placement)));
            if (lecture.getNrRooms() > 0) {
                cell.setPreference((lecture.nrRoomLocations() == 1 ? "<u>" : "") + lecture.minRoomUse()
                        + (lecture.maxRoomUse() != lecture.minRoomUse() ? " - " + lecture.maxRoomUse() : "") + " / "
                        + (minRoomSize == Integer.MAX_VALUE ? "-" : String.valueOf(minRoomSize)) + " / " + roomSize
                        + (lecture.nrRoomLocations() == 1 ? "</u>" : ""));
            }
            break;
        case StudentGroups:
            TimetableModel tm = (TimetableModel) solver.currentSolution().getModel();
            if (!tm.getStudentGroups().isEmpty()) {
                int nrGroups = 0;
                double value = 0;
                int allAssigned = 0, grandTotal = 0;
                for (StudentGroup group : tm.getStudentGroups()) {
                    int total = 0, assigned = 0;
                    if (model.getResourceType() == ResourceType.STUDENT_GROUP.ordinal()
                            && !model.getResourceId().equals(group.getId()))
                        continue;
                    for (Student student : group.getStudents()) {
                        if (lecture.getConfiguration() == null
                                || !student.hasOffering(lecture.getConfiguration().getOfferingId()))
                            continue;
                        total++;
                        if (lecture.students().contains(student))
                            assigned++;
                    }
                    if (total > 1 && assigned > 0) {
                        allAssigned += assigned;
                        grandTotal += total;
                        int limit = Math.max(lecture.students().size(), lecture.classLimit(assignment));
                        if (total > limit)
                            total = limit;
                        nrGroups++;
                        value += ((double) assigned) / total;
                    }
                }
                if (nrGroups > 0) {
                    cell.setBackground(percentage2color((int) Math.round(100.0 * value / nrGroups)));
                    cell.setPreference((nrGroups == 1 ? allAssigned + " of " + grandTotal : nrGroups + " groups"));
                }
            }
            break;
        }

        if (!notAvailable) {
            int roomPref = placement.getRoomPreference();
            if (model.getResourceType() == ResourceType.ROOM.ordinal() && model.getResourceId() != null)
                roomPref = placement.getRoomLocation(model.getResourceId()).getPreference();
            if (!cell.hasPreference()) {
                cell.setPreference(
                        (lecture.getBestTimePreference() < placement.getTimeLocation().getNormalizedPreference()
                                ? "<span style='color:red'>"
                                        + (int) (placement.getTimeLocation().getNormalizedPreference()
                                                - lecture.getBestTimePreference())
                                        + "</span>"
                                : "" + (int) (placement.getTimeLocation().getNormalizedPreference()
                                        - lecture.getBestTimePreference()))
                                + ", "
                                + (studConf > 0 ? "<span style='color:rgb(20,130,10)'>" + studConf + "</span>"
                                        : "" + studConf)
                                + ", "
                                + (lecture.getBestRoomPreference() < roomPref
                                        ? "<span style='color:blue'>" + (roomPref - lecture.getBestRoomPreference())
                                                + "</span>"
                                        : "" + (roomPref - lecture.getBestRoomPreference())));
            }

            int btbInstrPref = 0;
            for (InstructorConstraint ic : lecture.getInstructorConstraints()) {
                btbInstrPref += ic.getPreferenceCombination(assignment, placement);
            }
            cell.setProperty(Property.TimePreference, (int) placement.getTimeLocation().getNormalizedPreference());
            cell.setProperty(Property.StudentConflicts, (lecture.countStudentConflicts(assignment, placement)
                    + lecture.getCommitedConflicts(placement)));
            cell.setProperty(Property.StudentConflictsCommitted,
                    (lecture.countCommittedStudentConflicts(assignment, placement)
                            + lecture.getCommitedConflicts(placement)));
            cell.setProperty(Property.StudentConflictsDistance,
                    lecture.countDistanceStudentConflicts(assignment, placement));
            cell.setProperty(Property.StudentConflictsHard,
                    lecture.countHardStudentConflicts(assignment, placement));
            cell.setProperty(Property.RoomPreference, roomPref);
            if (!lecture.getInstructorConstraints().isEmpty())
                cell.setProperty(Property.InstructorPreference, btbInstrPref);
            if (lecture.getInitialAssignment() != null) {
                cell.setProperty(Property.InitialAssignment, (lecture.getInitialAssignment().equals(placement) ? "-"
                        : lecture.getInitialAssignment().getName()));
                cell.setProperty(Property.PerturbationPenalty, df.format(penalty));
            }
            if (deptConstraint != null)
                cell.setProperty(Property.DepartmentBalance, deptConstraint.getMaxPenalty(assignment, placement));

            int gcPref = 0;
            for (Constraint c : lecture.constraints()) {
                if (!(c instanceof GroupConstraint))
                    continue;
                GroupConstraint gc = (GroupConstraint) c;
                if (gc.isHard())
                    continue;
                if (gc.getPreference() > 0 && gc.getCurrentPreference(assignment) == 0)
                    continue;
                if (gc.getPreference() < 0 && gc.getCurrentPreference(assignment) < 0)
                    continue;
                gcPref = Math.max(gcPref, Math.abs(gc.getPreference()));
            }
            cell.setProperty(Property.DistributionPreference, gcPref);
            if (bgMode == BgMode.DistributionConstPref.ordinal())
                cell.setBackground(pref2color(gcPref));
        }

        cell.setDays(placement.getTimeLocation().getDayHeader());
        cell.setTime(placement.getTimeLocation().getStartTimeHeader(CONSTANTS.useAmPm()) + " - "
                + placement.getTimeLocation().getEndTimeHeader(CONSTANTS.useAmPm()));
        cell.setDate(placement.getTimeLocation().getDatePatternName());
        cell.setWeekCode(pattern2string(placement.getTimeLocation().getWeekCode()));

        if (placement.isMultiRoom())
            for (RoomLocation room : placement.getRoomLocations())
                cell.addRoom(room.getName());
        else if (placement.getRoomLocation() != null)
            cell.addRoom(placement.getRoomLocation().getName());

        for (InstructorConstraint ic : lecture.getInstructorConstraints())
            cell.addInstructor(ic.getName());

        return cell;
    }

    protected static double countUtilization(TimetableGridContext context, Iterable<Placement> placements) {
        Set<Integer> slots = new HashSet<Integer>();
        for (Placement p : placements) {
            TimeLocation t = (p == null ? null : p.getTimeLocation());
            if (t == null)
                continue;
            int start = Math.max(context.getFirstSlot(), t.getStartSlot());
            int stop = Math.min(context.getLastSlot(), t.getStartSlot() + t.getLength() - 1);
            if (start > stop)
                continue;
            if (context.getFirstDay() >= 0) {
                for (int idx = context.getFirstDay(); idx < 7 + context.getFirstDay(); idx++) {
                    int dow = ((idx + context.getStartDayDayOfWeek()) % 7);
                    if (t.getWeekCode().get(idx) && (t.getDayCode() & Constants.DAY_CODES[dow]) != 0
                            && (context.getDayCode() & Constants.DAY_CODES[dow]) != 0) {
                        for (int slot = start; slot <= stop; slot++)
                            slots.add(288 * idx + slot);
                    }
                }
            } else {
                int idx = -1;
                while ((idx = t.getWeekCode().nextSetBit(1 + idx)) >= 0) {
                    int dow = ((idx + context.getStartDayDayOfWeek()) % 7);
                    if (context.getDefaultDatePattern().get(idx) && (t.getDayCode() & Constants.DAY_CODES[dow]) != 0
                            && (context.getDayCode() & Constants.DAY_CODES[dow]) != 0) {
                        for (int slot = start; slot <= stop; slot++)
                            slots.add(288 * idx + slot);
                    }
                }
            }
        }
        return slots.size() / (context.getNumberOfWeeks() * 12);
    }

    public static void addCrosslistedNames(TimetableGridModel model, TimetableGridContext context) {
        Map<Long, List<TimetableGridCell>> id2cells = new HashMap<Long, List<TimetableGridCell>>();
        for (TimetableGridCell cell : model.getCells()) {
            if (cell.getType() != Type.Class || cell.getId() == null || cell.getId() < 0)
                continue;
            List<TimetableGridCell> cells = id2cells.get(cell.getId());
            if (cells == null) {
                cells = new ArrayList<TimetableGridCell>();
                id2cells.put(cell.getId(), cells);
            }
            cells.add(cell);
        }
        if (id2cells.isEmpty())
            return;
        if (id2cells.size() <= 1000) {
            for (Object[] o : (List<Object[]>) Class_DAO.getInstance().getSession().createQuery(
                    "select c, co from Class_ c inner join c.schedulingSubpart.instrOfferingConfig.instructionalOffering.courseOfferings co where "
                            + "co.isControl = false and c.uniqueId in :classIds order by co.subjectAreaAbbv, co.courseNbr")
                    .setParameterList("classIds", id2cells.keySet(), LongType.INSTANCE).setCacheable(true).list()) {
                Class_ clazz = (Class_) o[0];
                CourseOffering course = (CourseOffering) o[1];
                for (TimetableGridCell cell : id2cells.get(clazz.getUniqueId()))
                    cell.addName(
                            clazz.getClassLabel(course, context.isShowClassSuffix(), context.isShowConfigName()));
            }
        } else {
            List<Long> ids = new ArrayList<Long>(1000);
            for (Long id : id2cells.keySet()) {
                ids.add(id);
                if (ids.size() == 1000) {
                    for (Object[] o : (List<Object[]>) Class_DAO.getInstance().getSession().createQuery(
                            "select c, co from Class_ c inner join c.schedulingSubpart.instrOfferingConfig.instructionalOffering.courseOfferings co where "
                                    + "co.isControl = false and c.uniqueId in :classIds order by co.subjectAreaAbbv, co.courseNbr")
                            .setParameterList("classIds", ids, LongType.INSTANCE).setCacheable(true).list()) {
                        Class_ clazz = (Class_) o[0];
                        CourseOffering course = (CourseOffering) o[1];
                        for (TimetableGridCell cell : id2cells.get(clazz.getUniqueId()))
                            cell.addName(clazz.getClassLabel(course, context.isShowClassSuffix(),
                                    context.isShowConfigName()));
                    }
                    ids.clear();
                }
            }
            if (!ids.isEmpty()) {
                for (Object[] o : (List<Object[]>) Class_DAO.getInstance().getSession().createQuery(
                        "select c, co from Class_ c inner join c.schedulingSubpart.instrOfferingConfig.instructionalOffering.courseOfferings co where "
                                + "co.isControl = false and c.uniqueId in :classIds order by co.subjectAreaAbbv, co.courseNbr")
                        .setParameterList("classIds", ids, LongType.INSTANCE).setCacheable(true).list()) {
                    Class_ clazz = (Class_) o[0];
                    CourseOffering course = (CourseOffering) o[1];
                    for (TimetableGridCell cell : id2cells.get(clazz.getUniqueId()))
                        cell.addName(clazz.getClassLabel(course, context.isShowClassSuffix(),
                                context.isShowConfigName()));
                }
            }
        }
    }

    public static void fixClassName(TimetableGridContext context, TimetableGridCell cell, Class_ clazz,
            CourseOffering course) {
        cell.clearName();
        if (context.isShowClassNameTwoLines()) {
            cell.addName(clazz.getCourseName());
            String label = clazz.getItypeDesc().trim() + " " + clazz.getSectionNumberString();
            if (context.isShowClassSuffix()) {
                String extId = clazz.getClassSuffix(course);
                if (extId != null && !extId.isEmpty() && !extId.equalsIgnoreCase(clazz.getSectionNumberString()))
                    label += " - " + extId;
            }
            if (context.isShowConfigName()
                    && course.getInstructionalOffering().getInstrOfferingConfigs().size() > 1) {
                label += " (" + clazz.getSchedulingSubpart().getInstrOfferingConfig().getName() + ")";
            }
            cell.addName(label);
        } else {
            cell.addName(clazz.getClassLabel(course, context.isShowClassSuffix(), context.isShowConfigName()));
        }
        if (context.isShowCourseTitle() && course.getTitle() != null && !course.getTitle().isEmpty()) {
            cell.addName(course.getTitle());
        }
        if (context.isShowCourseTitle()) {
            cell.addTitle(clazz.getClassLabel(course, context.isShowClassSuffix(), context.isShowConfigName())
                    + (course.getTitle() != null && !course.getTitle().isEmpty() ? " - " + course.getTitle() : ""));
        } else if (context.isShowClassNameTwoLines()) {
            cell.addTitle(clazz.getClassLabel(course, context.isShowClassSuffix(), context.isShowConfigName()));
        }
        if (context.isShowCrossLists()) {
            Set<CourseOffering> courses = course.getInstructionalOffering().getCourseOfferings();
            if (courses.size() > 1) {
                for (CourseOffering co : new TreeSet<CourseOffering>(courses)) {
                    if (co.isIsControl())
                        continue;
                    cell.addName(clazz.getClassLabel(co, context.isShowClassSuffix(), context.isShowConfigName()));
                    if (context.isShowCourseTitle()) {
                        if (co.getTitle() != null && !co.getTitle().isEmpty()) {
                            cell.addName(co.getTitle());
                            cell.addTitle(
                                    clazz.getClassLabel(co, context.isShowClassSuffix(), context.isShowConfigName())
                                            + " - " + co.getTitle());
                        } else {
                            cell.addTitle(clazz.getClassLabel(co, context.isShowClassSuffix(),
                                    context.isShowConfigName()));
                        }
                    } else if (context.isShowClassNameTwoLines()) {
                        cell.addTitle(
                                clazz.getClassLabel(co, context.isShowClassSuffix(), context.isShowConfigName()));
                    }
                }
            }
        }
    }

    public static void fixClassNames(TimetableGridModel model, TimetableGridContext context) {
        Map<Long, List<TimetableGridCell>> id2cells = new HashMap<Long, List<TimetableGridCell>>();
        for (TimetableGridCell cell : model.getCells()) {
            if (cell.getType() != Type.Class || cell.getId() == null || cell.getId() < 0)
                continue;
            List<TimetableGridCell> cells = id2cells.get(cell.getId());
            if (cells == null) {
                cells = new ArrayList<TimetableGridCell>();
                id2cells.put(cell.getId(), cells);
            }
            cells.add(cell);
        }
        if (id2cells.isEmpty())
            return;
        if (id2cells.size() <= 1000) {
            for (Object[] o : (List<Object[]>) Class_DAO.getInstance().getSession().createQuery(
                    "select c, co from Class_ c inner join c.schedulingSubpart.instrOfferingConfig.instructionalOffering.courseOfferings co where "
                            + "co.isControl = true and c.uniqueId in :classIds order by co.subjectAreaAbbv, co.courseNbr")
                    .setParameterList("classIds", id2cells.keySet(), LongType.INSTANCE).setCacheable(true).list()) {
                Class_ clazz = (Class_) o[0];
                CourseOffering course = (CourseOffering) o[1];
                for (TimetableGridCell cell : id2cells.get(clazz.getUniqueId()))
                    fixClassName(context, cell, clazz, course);
            }
        } else {
            List<Long> ids = new ArrayList<Long>(1000);
            for (Long id : id2cells.keySet()) {
                ids.add(id);
                if (ids.size() == 1000) {
                    for (Object[] o : (List<Object[]>) Class_DAO.getInstance().getSession().createQuery(
                            "select c, co from Class_ c inner join c.schedulingSubpart.instrOfferingConfig.instructionalOffering.courseOfferings co where "
                                    + "co.isControl = true and c.uniqueId in :classIds order by co.subjectAreaAbbv, co.courseNbr")
                            .setParameterList("classIds", ids, LongType.INSTANCE).setCacheable(true).list()) {
                        Class_ clazz = (Class_) o[0];
                        CourseOffering course = (CourseOffering) o[1];
                        for (TimetableGridCell cell : id2cells.get(clazz.getUniqueId()))
                            fixClassName(context, cell, clazz, course);
                    }
                    ids.clear();
                }
            }
            if (!ids.isEmpty()) {
                for (Object[] o : (List<Object[]>) Class_DAO.getInstance().getSession().createQuery(
                        "select c, co from Class_ c inner join c.schedulingSubpart.instrOfferingConfig.instructionalOffering.courseOfferings co where "
                                + "co.isControl = true and c.uniqueId in :classIds order by co.subjectAreaAbbv, co.courseNbr")
                        .setParameterList("classIds", ids, LongType.INSTANCE).setCacheable(true).list()) {
                    Class_ clazz = (Class_) o[0];
                    CourseOffering course = (CourseOffering) o[1];
                    for (TimetableGridCell cell : id2cells.get(clazz.getUniqueId()))
                        fixClassName(context, cell, clazz, course);
                }
            }
        }
    }

    public static void fixInstructors(TimetableGridModel model, TimetableGridContext context) {
        Map<Long, List<TimetableGridCell>> id2cells = new HashMap<Long, List<TimetableGridCell>>();
        for (TimetableGridCell cell : model.getCells()) {
            if (cell.getType() != Type.Class || cell.getId() == null || cell.getId() < 0 || !cell.isCommitted())
                continue;
            List<TimetableGridCell> cells = id2cells.get(cell.getId());
            if (cells == null) {
                cells = new ArrayList<TimetableGridCell>();
                id2cells.put(cell.getId(), cells);
            }
            cell.resetInstructors();
            cells.add(cell);
        }
        if (id2cells.isEmpty())
            return;

        String query = null;
        if (ApplicationProperty.TimetableGridUseClassInstructors.isTrue()) {
            query = "select ci.classInstructing.uniqueId, ci.instructor from ClassInstructor ci "
                    + "where ci.classInstructing.uniqueId in :classIds";
            if (ApplicationProperty.TimetableGridUseClassInstructorsCheckClassDisplayInstructors.isTrue())
                query += " and ci.classInstructing.displayInstructor = true";
            if (ApplicationProperty.TimetableGridUseClassInstructorsCheckLead.isTrue())
                query += " and ci.lead = true";
        } else {
            query = "select a.clazz.uniqueId, i from Assignment a inner join a.instructors i "
                    + "where a.solution.commited = true and a.clazz.uniqueId in :classIds";
        }

        if (id2cells.size() <= 1000) {
            for (Object[] o : (List<Object[]>) Class_DAO.getInstance().getSession().createQuery(query)
                    .setParameterList("classIds", id2cells.keySet(), LongType.INSTANCE).setCacheable(true).list()) {
                Long classId = (Long) o[0];
                DepartmentalInstructor instructor = (DepartmentalInstructor) o[1];
                for (TimetableGridCell cell : id2cells.get(classId)) {
                    cell.addInstructor(instructor.getName(context.getInstructorNameFormat()));
                }
            }
        } else {
            List<Long> ids = new ArrayList<Long>(1000);
            for (Long id : id2cells.keySet()) {
                ids.add(id);
                if (ids.size() == 1000) {
                    for (Object[] o : (List<Object[]>) Class_DAO.getInstance().getSession().createQuery(query)
                            .setParameterList("classIds", ids, LongType.INSTANCE).setCacheable(true).list()) {
                        Long classId = (Long) o[0];
                        DepartmentalInstructor instructor = (DepartmentalInstructor) o[1];
                        for (TimetableGridCell cell : id2cells.get(classId)) {
                            cell.addInstructor(instructor.getName(context.getInstructorNameFormat()));
                        }
                    }
                    ids.clear();
                }
            }
            if (!ids.isEmpty()) {
                for (Object[] o : (List<Object[]>) Class_DAO.getInstance().getSession().createQuery(query)
                        .setParameterList("classIds", ids, LongType.INSTANCE).setCacheable(true).list()) {
                    Long classId = (Long) o[0];
                    DepartmentalInstructor instructor = (DepartmentalInstructor) o[1];
                    for (TimetableGridCell cell : id2cells.get(classId)) {
                        cell.addInstructor(instructor.getName(context.getInstructorNameFormat()));
                    }
                }
            }
        }
    }
}