-- | We model an M/M/1 queue, i.e. a single-server queue with Poisson
-- arrivals and service times.
--
-- See "Stochastic Dynamic Programming and the Control of Queueing
-- Systems", Linn I. Sennot,, p. 242 for details.
module Algorithms.MDP.Examples.MM1 where

import qualified Algorithms.MDP.CTMDP as CTMDP

-- | A description of an MDP.
data Scenario = Scenario
                { _arrivalRate  :: Double
                , _serviceRates :: [Double]
                , _serviceCosts :: [Double]
                , _holdingCosts :: Int -> Double
                , _maxWaiting   :: Int
                , _scenarioCost :: Double
                }

-- | The state space is the count of customers in the queue.
newtype State = State Int
              deriving (Show, Eq)

-- | There are a number of services we can provide each customer, and
-- if there are no customers we do nothing.
data Action = NullAction
            | Action Int
            deriving (Show, Eq)

-- | Generate an MDP from a Scenario.
mkInstance :: Scenario -> CTMDP.CTMDP State Action Double
mkInstance scenario =
  let
    -- (State i) represents i customers waiting in the queue.
    states  = map State  [0..(_maxWaiting scenario)]

    -- (Action i) represents serving a customer with the ith service
    -- profile, while the NullAction represents what we do in the
    -- empty queue (wait).
    actions = NullAction : map Action [0..length (_serviceRates scenario) - 1]

    -- All actions but the null action have an associated cost
    rateCost (Action ac) (State i) = hc + sc
      where
        hc = _holdingCosts scenario i
        sc = _serviceCosts scenario !! ac
    rateCost NullAction  _         = 0

    -- There can always be an arrival, and if we don't take the null
    -- action there can be a departure.
    rates (Action ac) _  = _arrivalRate scenario + _serviceRates scenario !! ac
    rates NullAction  _  = _arrivalRate scenario

    -- There are no fixed costs.
    fixedCost _ _= 0

    -- We can only take the null action in state 0, and can take any
    -- other action in all other states.
    actionSet (State 0) = [NullAction]
    actionSet _         = (tail actions)

    -- If we take the null action, we wait for an arrival. Otherwise,
    -- we can increase or decrease the length of the queue by 1.
    --
    -- Note that since we cannot transition about the maximum state,
    -- we instead allow a self-transition.
    trans NullAction  (State 0) (State 1) = 1
    trans NullAction  _         _         = 0
    trans (Action ac) (State i) (State j) 
      | j == i + 1          = lambda / (lambda + a)
      | j == i && i == maxN = lambda / (lambda + a)
      | j == i - 1          = a / (lambda + a)
      | otherwise           = 0
      where
        maxN = _maxWaiting scenario
        lambda = _arrivalRate scenario
        a = _serviceRates scenario !! ac
  in
    CTMDP.mkCTMDP states actions trans rates fixedCost rateCost actionSet 1.0

-- | A specific scenario.
scenario1 :: Scenario
scenario1 = Scenario
            { _arrivalRate  = 3
            , _serviceRates = [2, 4, 8]
            , _serviceCosts = [9, 13, 21]
            , _holdingCosts = \i -> fromIntegral i
            , _maxWaiting   = 48
            , _scenarioCost = 8.475
            }

-- | A specific scenario.
scenario2 :: Scenario
scenario2 = Scenario
            { _arrivalRate  = 2.0
            , _serviceRates = [1, 4, 7]
            , _serviceCosts = [1, 50, 500]
            , _holdingCosts = \i -> fromIntegral i
            , _maxWaiting   = 84
            , _scenarioCost = 21.091
            }
            
-- | A specific scenario.
scenario3 :: Scenario
scenario3 = Scenario
            { _arrivalRate  = 2.0
            , _serviceRates = [1, 4, 7]
            , _serviceCosts = [1, 50, 150]
            , _holdingCosts = \i -> fromIntegral i
            , _maxWaiting   = 84
            , _scenarioCost = 21.091
            }

-- | A specific scenario.
scenario4 :: Scenario
scenario4 = Scenario
            { _arrivalRate  = 2.0
            , _serviceRates = [1, 4, 7]
            , _serviceCosts = [1, 50, 100]
            , _holdingCosts = \i -> fromIntegral i
            , _maxWaiting   = 84
            , _scenarioCost = 21.971
            }

-- | A specific scenario.
scenario5 :: Scenario
scenario5 = Scenario
            { _arrivalRate  = 2.0
            , _serviceRates = [5.0, 5.5, 5.8]
            , _serviceCosts = [0, 10, 100]
            , _holdingCosts = \i -> fromIntegral i
            , _maxWaiting   = 84
            , _scenarioCost = 17.043
            }

-- | A specific scenario.
scenario6 :: Scenario
scenario6 = Scenario
            { _arrivalRate  = 5.0
            , _serviceRates = [5.1, 5.3, 6.0]
            , _serviceCosts = [0, 10, 25]
            , _holdingCosts = \i -> fromIntegral i
            , _maxWaiting   = 84
            , _scenarioCost = 15.193
            }

-- | A specific scenario.
scenario7 :: Scenario
scenario7 = Scenario
            { _arrivalRate  = 10.0
            , _serviceRates = [10.2, 10.6, 12]
            , _serviceCosts = [0, 10, 25]
            , _holdingCosts = \i -> fromIntegral i
            , _maxWaiting   = 84
            , _scenarioCost = 15.193
            }

-- | A specific scenario.
scenario8 :: Scenario
scenario8 = Scenario
            { _arrivalRate  = 20.0
            , _serviceRates = [24, 27, 30]
            , _serviceCosts = [1, 1.5, 5.0]
            , _holdingCosts = \i -> fromIntegral i
            , _maxWaiting   = 84
            , _scenarioCost = 3.902
            }