jsdp.app.inventory.univariate.StochasticLotSizing.java Source code

Java tutorial

Introduction

Here is the source code for jsdp.app.inventory.univariate.StochasticLotSizing.java

Source

/**
 * jsdp: A Java Stochastic Dynamic Programming Library
 * 
 * MIT License
 * 
 * Copyright (c) 2016 Roberto Rossi
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy 
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package jsdp.app.inventory.univariate;

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.Locale;
import java.util.function.Function;
import java.util.stream.IntStream;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartFrame;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;

import jsdp.sdp.Action;
import jsdp.sdp.HashType;
import jsdp.sdp.ImmediateValueFunction;
import jsdp.sdp.RandomOutcomeFunction;
import jsdp.sdp.State;
import jsdp.sdp.Recursion.OptimisationDirection;
import jsdp.sdp.impl.univariate.*;
import jsdp.app.inventory.univariate.simulation.SimulatePolicies;
import jsdp.app.inventory.univariate.simulation.sS_Policy;

import umontreal.ssj.probdist.Distribution;
import umontreal.ssj.probdist.PoissonDist;
import umontreal.ssj.probdist.NormalDist;

/**
 *  We formulate the stochastic lot sizing problem as defined in  
 *  
 *  Herbert E. Scarf. Optimality of (s, S) policies in the dynamic inventory problem. In K. J. Arrow, S. Karlin, 
 *  and P. Suppes, editors, Mathematical Methods in the Social Sciences, pages 196202. Stanford University
 *  Press, Stanford, CA, 1960.
 *  
 *  as a stochastic dynamic programming problem. We use backward recursion and sampling to find optimal policies.
 *  
 *  Run with VM arguments -d64 -Xms512m -Xmx4g
 *  
 * @author Roberto Rossi
 *
 */
public class StochasticLotSizing {

    public static void main(String args[]) {

        boolean simulate = true;

        /*******************************************************************
         * Problem parameters
         */
        double fixedOrderingCost = 300;
        double proportionalOrderingCost = 0;
        double holdingCost = 1;
        double penaltyCost = 10;

        double[] meanDemand = { 10, 20, 15, 20, 15, 10 };
        double coefficientOfVariation = 0.2;
        double truncationQuantile = 0.999;

        // Random variables

        Distribution[] distributions = IntStream.iterate(0, i -> i + 1).limit(meanDemand.length)
                //.mapToObj(i -> new NormalDist(meanDemand[i],meanDemand[i]*coefficientOfVariation))
                .mapToObj(i -> new PoissonDist(meanDemand[i])).toArray(Distribution[]::new);
        double[] supportLB = IntStream.iterate(0, i -> i + 1).limit(meanDemand.length)
                .mapToDouble(i -> distributions[i].inverseF(1 - truncationQuantile)).toArray();
        double[] supportUB = IntStream.iterate(0, i -> i + 1).limit(meanDemand.length)
                .mapToDouble(i -> distributions[i].inverseF(truncationQuantile)).toArray();

        double initialInventory = 0;

        /*******************************************************************
         * Model definition
         */

        // State space

        double stepSize = 1; //Stepsize must be 1 for discrete distributions
        double minState = -50; //Inventory level lower bound in each period
        double maxState = 150; //Inventory level upper bound in each period
        StateImpl.setStateBoundaries(stepSize, minState, maxState);

        // Actions

        Function<State, ArrayList<Action>> buildActionList = s -> {
            StateImpl state = (StateImpl) s;
            ArrayList<Action> feasibleActions = new ArrayList<Action>();
            for (double i = state.getInitialState(); i <= StateImpl.getMaxState(); i += StateImpl.getStepSize()) {
                feasibleActions.add(new ActionImpl(state, i - state.getInitialState()));
            }
            return feasibleActions;
        };

        Function<State, Action> idempotentAction = s -> new ActionImpl(s, 0.0);

        // Immediate Value Function

        ImmediateValueFunction<State, Action, Double> immediateValueFunction = (initialState, action,
                finalState) -> {
            ActionImpl a = (ActionImpl) action;
            StateImpl fs = (StateImpl) finalState;
            double orderingCost = a.getAction() > 0 ? (fixedOrderingCost + a.getAction() * proportionalOrderingCost)
                    : 0;
            double holdingAndPenaltyCost = holdingCost * Math.max(fs.getInitialState(), 0)
                    + penaltyCost * Math.max(-fs.getInitialState(), 0);
            return orderingCost + holdingAndPenaltyCost;
        };

        // Random Outcome Function

        RandomOutcomeFunction<State, Action, Double> randomOutcomeFunction = (initialState, action, finalState) -> {
            double realizedDemand = ((StateImpl) initialState).getInitialState() + ((ActionImpl) action).getAction()
                    - ((StateImpl) finalState).getInitialState();
            return realizedDemand;
        };

        /*******************************************************************
         * Solve
         */

        // Sampling scheme

        SamplingScheme samplingScheme = SamplingScheme.NONE;
        int maxSampleSize = 100;
        double reductionFactorPerStage = 1;

        // Value Function Processing Method: backward recursion
        double discountFactor = 1.0;
        int stateSpaceLowerBound = 10000000;
        float loadFactor = 0.8F;
        BackwardRecursionImpl recursion = new BackwardRecursionImpl(OptimisationDirection.MIN, distributions,
                supportLB, supportUB, immediateValueFunction, randomOutcomeFunction, buildActionList,
                idempotentAction, discountFactor, samplingScheme, maxSampleSize, reductionFactorPerStage,
                stateSpaceLowerBound, loadFactor, HashType.THASHMAP);

        System.out.println("--------------Backward recursion--------------");
        recursion.runBackwardRecursionMonitoring();
        System.out.println();
        double ETC = recursion.getExpectedCost(initialInventory);
        StateDescriptorImpl initialState = new StateDescriptorImpl(0, initialInventory);
        double action = recursion.getOptimalAction(initialState).getAction();
        long percent = recursion.getMonitoringInterfaceBackward().getPercentCPU();
        System.out.println(
                "Expected total cost (assuming an initial inventory level " + initialInventory + "): " + ETC);
        System.out.println("Optimal initial action: " + action);
        System.out.println("Time elapsed: " + recursion.getMonitoringInterfaceBackward().getTime());
        System.out
                .println("Cpu usage: " + percent + "% (" + Runtime.getRuntime().availableProcessors() + " cores)");
        System.out.println();

        /*******************************************************************
         * Charting
         */
        System.out.println("--------------Charting--------------");
        int targetPeriod = 0;
        plotOptimalPolicyAction(targetPeriod, recursion); //Plot optimal policy action
        BackwardRecursionImpl recursionPlot = new BackwardRecursionImpl(OptimisationDirection.MIN, distributions,
                supportLB, supportUB, immediateValueFunction, randomOutcomeFunction, buildActionList,
                idempotentAction, discountFactor, targetPeriod > 0 ? SamplingScheme.NONE : samplingScheme,
                maxSampleSize, reductionFactorPerStage, HashType.THASHMAP);
        plotOptimalPolicyCost(targetPeriod, recursionPlot); //Plot optimal policy cost 
        System.out.println();

        /*******************************************************************
         * Simulation
         */
        System.out.println("--------------Simulation--------------");
        double confidence = 0.95; //Simulation confidence level 
        double errorTolerance = 0.0001; //Simulation error threshold

        if (simulate && samplingScheme == SamplingScheme.NONE)
            simulate(distributions, fixedOrderingCost, holdingCost, penaltyCost, proportionalOrderingCost,
                    initialInventory, recursion, confidence, errorTolerance);
        else {
            if (!simulate)
                System.out.println("Simulation disabled.");
            if (samplingScheme != SamplingScheme.NONE)
                System.out.println(
                        "Cannot simulate a sampled solution, please disable sampling: set samplingScheme == SamplingScheme.NONE.");
        }
    }

    static void plotOptimalPolicyCost(int targetPeriod, BackwardRecursionImpl recursion) {
        recursion.runBackwardRecursionMonitoring(targetPeriod);
        XYSeries series = new XYSeries("Optimal policy");
        for (double i = StateImpl.getMinState(); i <= StateImpl.getMaxState(); i += StateImpl.getStepSize()) {
            StateDescriptorImpl stateDescriptor = new StateDescriptorImpl(targetPeriod, i);
            series.add(i, recursion.getExpectedCost(stateDescriptor));
        }
        XYDataset xyDataset = new XYSeriesCollection(series);
        JFreeChart chart = ChartFactory.createXYLineChart(
                "Optimal policy policy - period " + targetPeriod + " expected total cost",
                "Opening inventory level", "Expected total cost", xyDataset, PlotOrientation.VERTICAL, false, true,
                false);
        ChartFrame frame = new ChartFrame("Optimal policy", chart);
        frame.setVisible(true);
        frame.setSize(500, 400);
    }

    static void plotOptimalPolicyAction(int targetPeriod, BackwardRecursionImpl recursion) {
        XYSeries series = new XYSeries("Optimal policy");
        for (double i = StateImpl.getMinState(); i <= StateImpl.getMaxState(); i += StateImpl.getStepSize()) {
            StateDescriptorImpl stateDescriptor = new StateDescriptorImpl(targetPeriod, i);
            series.add(i, recursion.getOptimalAction(stateDescriptor).getAction());
        }

        XYDataset xyDataset = new XYSeriesCollection(series);
        JFreeChart chart = ChartFactory.createXYLineChart(
                "Optimal policy - period " + targetPeriod + " order quantity", "Opening inventory level",
                "Order quantity", xyDataset, PlotOrientation.VERTICAL, false, true, false);
        ChartFrame frame = new ChartFrame("Optimal policy", chart);
        frame.setVisible(true);
        frame.setSize(500, 400);
    }

    static void simulate(Distribution[] distributions, double fixedOrderingCost, double holdingCost,
            double penaltyCost, double proportionalOrderingCost, double initialInventory,
            BackwardRecursionImpl recursion, double confidence, double errorTolerance) {
        DecimalFormatSymbols otherSymbols = new DecimalFormatSymbols(Locale.ENGLISH);
        DecimalFormat df = new DecimalFormat("#.00", otherSymbols);

        sS_Policy policy = new sS_Policy(recursion, distributions.length);
        double[][] optimalPolicy = policy.getOptimalPolicy(initialInventory);
        double[] s = optimalPolicy[0];
        double[] S = optimalPolicy[1];
        for (int i = 0; i < distributions.length; i++) {
            System.out.println("S[" + (i + 1) + "]:" + df.format(S[i]) + "\ts[" + (i + 1) + "]:" + df.format(s[i]));
        }

        double[] results = SimulatePolicies.simulate_sS(distributions, fixedOrderingCost, holdingCost, penaltyCost,
                proportionalOrderingCost, initialInventory, S, s, confidence, errorTolerance);
        System.out.println();
        System.out.println("Simulated cost: " + df.format(results[0]) + " Confidence interval=("
                + df.format(results[0] - results[1]) + "," + df.format(results[0] + results[1]) + ")@"
                + df.format(confidence * 100) + "% confidence");
        System.out.println();
    }
}