org.libreplan.web.montecarlo.MonteCarloModel.java Source code

Java tutorial

Introduction

Here is the source code for org.libreplan.web.montecarlo.MonteCarloModel.java

Source

/*
 * This file is part of LibrePlan
 *
 * Copyright (C) 2009-2010 Fundacin para o Fomento da Calidade Industrial e
 *                         Desenvolvemento Tecnolxico de Galicia
 * Copyright (C) 2010-2011 Igalia, S.L.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.libreplan.web.montecarlo;

import static org.libreplan.web.I18nHelper._;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;

import org.apache.commons.lang3.Validate;
import org.hibernate.Hibernate;
import org.joda.time.LocalDate;
import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.planner.daos.ITaskElementDAO;
import org.libreplan.business.planner.entities.Dependency;
import org.libreplan.business.planner.entities.Task;
import org.libreplan.business.planner.entities.TaskElement;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.zkoss.ganttz.util.LongOperationFeedback.IDesktopUpdatesEmitter;

/**
 * Calculates the MonteCarlo function for a list of tasks.
 * Usually this list of tasks represents a critical path.
 * There could be many critical paths in scheduling.
 *
 * @author Diego Pino Garcia <dpino@igalia.com>
 */
@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class MonteCarloModel implements IMonteCarloModel {

    @Autowired
    private ITaskElementDAO taskDAO;

    private static String criticalPath = _("Critical path");

    private static String defaultCriticalPath = criticalPath + " 1";

    private Map<String, List<MonteCarloTask>> criticalPaths = new HashMap<>();

    private String orderName = "";

    private List<Task> tasksInCriticalPath;

    @Override
    @Transactional(readOnly = true)
    public void setCriticalPath(List<TaskElement> tasksInCriticalPath) {
        if (tasksInCriticalPath.isEmpty()) {
            return;
        }

        this.tasksInCriticalPath = onlyTasks(tasksInCriticalPath);

        if (this.tasksInCriticalPath.isEmpty()) {
            return;
        }

        Collections.sort(this.tasksInCriticalPath, Task.getByStartDateComparator());
        initializeTasksInOrder(getOrderFor(this.tasksInCriticalPath));
        initializeOrderNameFor(this.tasksInCriticalPath);
        feedCriticalPaths(this.tasksInCriticalPath);
    }

    /**
     * @param tasksInCriticalPath Cannot be null or empty
     * @return {@link Order}
     */
    private Order getOrderFor(List<Task> tasksInCriticalPath) {
        return tasksInCriticalPath.get(0).getOrderElement().getOrder();
    }

    private void feedCriticalPaths(List<Task> tasksInCriticalPath) {
        List<List<Task>> allCriticalPaths = buildAllPossibleCriticalPaths(tasksInCriticalPath);
        int i = 1;

        criticalPaths.clear();
        for (List<Task> path : allCriticalPaths) {
            criticalPaths.put(criticalPath + " " + i++, toMonteCarloTaskList(path));
        }
    }

    private List<List<Task>> buildAllPossibleCriticalPaths(List<Task> tasksInCriticalPath) {
        MonteCarloCriticalPathBuilder criticalPathBuilder = MonteCarloCriticalPathBuilder
                .create(tasksInCriticalPath);

        return criticalPathBuilder.buildAllPossibleCriticalPaths();
    }

    /**
     * Calculating all the critical paths, may need to explore other tasks that
     * are not part of the tasks that are on the critical path.
     * So it's necessary to initialize all the tasks in the order, otherwise a lazy initialization may happen.
     *
     * @param root
     */
    private void initializeTasksInOrder(Order root) {
        initializeTask(root);
        for (OrderElement each : root.getAllChildren()) {
            Hibernate.initialize(each);
            initializeTask(each);
        }
    }

    private void initializeTask(OrderElement orderElement) {
        TaskElement task = orderElement.getAssociatedTaskElement();

        if (task != null) {
            taskDAO.reattach(task);
            task.getCalendar();
            initializeDependenciesFor(task);
        }
    }

    private void initializeDependenciesFor(TaskElement task) {
        Set<Dependency> dependencies = task.getDependenciesWithThisDestination();
        Hibernate.initialize(dependencies);

        for (Dependency each : dependencies) {
            Hibernate.initialize(each.getOrigin());
            Hibernate.initialize(each.getDestination());
        }
    }

    private void initializeOrderNameFor(List<Task> tasksInCriticalPath) {
        orderName = tasksInCriticalPath.isEmpty() ? "" : getOrderFor(tasksInCriticalPath).getName();
    }

    @Override
    public List<String> getCriticalPathNames() {
        ArrayList result = new ArrayList(criticalPaths.keySet());
        Collections.sort(result);
        return result;
    }

    @Override
    public List<MonteCarloTask> getCriticalPath(String name) {
        if (name == null || name.isEmpty()) {
            return criticalPaths.get(defaultCriticalPath);
        }
        return criticalPaths.get(name);
    }

    private List<Task> onlyTasks(List<TaskElement> tasks) {
        List<Task> result = new ArrayList<>();

        for (TaskElement each : tasks) {
            if (each instanceof Task) {
                result.add((Task) each);
            }
        }

        return result;
    }

    private List<MonteCarloTask> toMonteCarloTaskList(List<Task> path) {
        List<MonteCarloTask> result = new ArrayList<>();

        for (Task each : path) {
            result.add(MonteCarloTask.create(each));
        }

        return result;
    }

    @Override
    public Map<LocalDate, BigDecimal> calculateMonteCarlo(List<MonteCarloTask> tasks, int iterations,
            IDesktopUpdatesEmitter<Integer> iterationProgress) {

        MonteCarloCalculation monteCarloCalculation = new MonteCarloCalculation(copyOf(tasks), iterations,
                iterationProgress);
        Map<LocalDate, BigDecimal> monteCarloValues = monteCarloCalculation.doCalculation();

        // Convert number of times to probability
        for (LocalDate key : monteCarloValues.keySet()) {
            BigDecimal times = monteCarloValues.get(key);
            BigDecimal probability = times.divide(BigDecimal.valueOf(iterations), 8, RoundingMode.HALF_UP);
            monteCarloValues.put(key, probability);
        }

        return monteCarloValues;
    }

    private List<MonteCarloTask> copyOf(List<MonteCarloTask> tasks) {
        List<MonteCarloTask> result = new ArrayList<>();

        for (MonteCarloTask each : tasks) {
            result.add(MonteCarloTask.copy(each));
        }

        return result;
    }

    @Override
    public String getOrderName() {
        return orderName;
    }

    private static class MonteCarloCalculation {

        private Map<MonteCarloTask, Set<EstimationRange>> estimationRangesForTasks = new HashMap<>();

        private List<MonteCarloTask> tasks;

        private int iterations;

        private IDesktopUpdatesEmitter<Integer> iterationProgress;

        public MonteCarloCalculation(List<MonteCarloTask> tasks, int iterations,
                IDesktopUpdatesEmitter<Integer> iterationProgress) {

            adjustDurationDays(tasks);
            initializeEstimationRanges(tasks);
            this.tasks = tasks;
            this.iterations = iterations;
            this.iterationProgress = iterationProgress;
        }

        public Map<LocalDate, BigDecimal> doCalculation() {
            Map<LocalDate, BigDecimal> result = new HashMap<>();

            final int onePercent = iterations / 100;
            Random randomGenerator = new Random((new Date()).getTime());

            // Calculate number of times a date is repeated
            for (int i = 0; i < iterations; i++) {
                LocalDate endDate = calculateEndDateFor(tasks, randomGenerator);
                BigDecimal times = result.get(endDate);

                times = times != null ? times.add(BigDecimal.ONE) : BigDecimal.ONE;

                result.put(endDate, times);

                if (i % onePercent == 0) {
                    increaseProgressMeter((i * 100) / iterations);
                }
            }

            return result;
        }

        private void increaseProgressMeter(int completedPercent) {
            iterationProgress.doUpdate(completedPercent);
        }

        private LocalDate calculateEndDateFor(List<MonteCarloTask> tasks, Random randomGenerator) {
            Validate.notEmpty(tasks);
            BigDecimal durationDays = BigDecimal.ZERO;
            MonteCarloTask first = tasks.get(0);

            for (MonteCarloTask each : tasks) {
                BigDecimal randomNumber = generate(randomGenerator);
                durationDays = durationDays.add(getDuration(each, randomNumber));
            }

            return first.getStartDate().plusDays(durationDays.intValue());
        }

        private BigDecimal generate(Random randomGenerator) {
            return BigDecimal.valueOf(randomGenerator.nextDouble());
        }

        private BigDecimal getDuration(MonteCarloTask each, BigDecimal random) {
            ESTIMATION_TYPE type = getEstimationType(each, random);
            Validate.notNull(type);
            switch (type) {

            case PESSIMISTIC:
                return each.getPessimisticDuration();

            case NORMAL:
                return each.getNormalDuration();

            case OPTIMISTIC:
                return each.getOptimisticDuration();

            default:
                break;
            }

            return BigDecimal.ZERO;
        }

        private void adjustDurationDays(List<MonteCarloTask> tasks) {
            for (MonteCarloTask each : tasks) {
                each.setPessimisticDuration(
                        MonteCarloTask.calculateRealDurationFor(each, each.getPessimisticDuration()));
                each.setNormalDuration(MonteCarloTask.calculateRealDurationFor(each, each.getNormalDuration()));
                each.setOptimisticDuration(
                        MonteCarloTask.calculateRealDurationFor(each, each.getOptimisticDuration()));
            }
        }

        private void initializeEstimationRanges(List<MonteCarloTask> tasks) {
            estimationRangesForTasks.clear();

            for (MonteCarloTask each : tasks) {
                createEstimationRangesFor(each);
            }
        }

        private void createEstimationRangesFor(MonteCarloTask task) {
            Set<EstimationRange> estimationRanges = new HashSet<>();
            estimationRanges.add(EstimationRange.pessimisticRangeFor(task));
            estimationRanges.add(EstimationRange.normalRangeFor(task));
            estimationRanges.add(EstimationRange.optimisticRangeFor(task));
            estimationRangesForTasks.put(task, estimationRanges);
        }

        public ESTIMATION_TYPE getEstimationType(MonteCarloTask task, BigDecimal random) {
            Set<EstimationRange> estimationRanges = estimationRangesForTasks.get(task);

            for (EstimationRange each : estimationRanges) {
                if (each.contains(random)) {
                    return each.getEstimationType();
                }
            }

            return null;
        }

    }

}