exchangealgebra-0.6.0.0: src/ExchangeAlgebra/Simulation/Network/Flows.hs
{-# LANGUAGE GADTs #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE OverloadedStrings #-}
{- | Generate monetary flows for an ordered industrial network.
This Simulation module uses the network accessors and a private numerical
helper. Import "ExchangeAlgebra.Simulation.Network" for the network itself.
-}
module ExchangeAlgebra.Simulation.Network.Flows
( TaxRate(..), taxOf, IndustrialFlows(..), FlowOptions(..)
, defaultFlowOptions, industrialFlows, industrialFlowsWith ) where
import Control.DeepSeq (NFData (..))
import Data.List (sortBy)
import qualified Data.Map.Strict as M
import Data.Map.Strict (Map)
import qualified Data.Set as S
import ExchangeAlgebra.Simulation.Network
import ExchangeAlgebra.Simulation.Network.Representation (finitePositive)
-- | A tax rate represented by an integer numerator and denominator.
data TaxRate = TaxRate
{ taxNumerator :: !Integer
, taxDenominator :: !Integer
} deriving (Eq, Show)
instance NFData TaxRate where
rnf (TaxRate num den) = rnf num `seq` rnf den
-- | Integer tax on an integer amount. Generated industrial flows are aligned
-- to the denominator, so this division is exact for their amounts.
taxOf :: TaxRate -> Integer -> Integer
taxOf (TaxRate num den) amount
| den <= 0 = error "taxOf: denominator must be positive"
| num < 0 = error "taxOf: numerator must be non-negative"
| amount < 0 = error "taxOf: amount must be non-negative"
| otherwise = amount * num `div` den
-- | One-period demand-driven monetary flows for an industrial economy.
data IndustrialFlows k = IndustrialFlows
{ flowTrade :: !(Map (k, k) Integer)
, flowOutput :: !(Map k Integer)
, flowInput :: !(Map k Integer)
, flowValueAdded :: !(Map k Integer)
, flowFinalDemand :: !(Map k Integer)
} deriving (Eq, Show)
instance NFData k => NFData (IndustrialFlows k) where
rnf (IndustrialFlows z x inp va f) =
rnf z `seq` rnf x `seq` rnf inp `seq` rnf va `seq` rnf f
-- | Options for the demand-driven backward substitution.
data FlowOptions = FlowOptions
{ foMeanFinalDemand :: !Integer
, foInputShare :: !Double
} deriving (Eq, Show)
instance NFData FlowOptions where
rnf (FlowOptions f a) = rnf f `seq` rnf a
-- | Mean final demand @1,000,000@ yen and intermediate-input share @0.5@.
defaultFlowOptions :: FlowOptions
defaultFlowOptions = FlowOptions
{ foMeanFinalDemand = 1000000
, foInputShare = 0.5
}
-- | Generate one-period flows with 'defaultFlowOptions'.
industrialFlows :: Ord k
=> TaxRate -> IndustrialEconomy k -> IndustrialFlows k
industrialFlows = industrialFlowsWith defaultFlowOptions
-- | Generate exact integer flows by a single downstream-to-upstream backward
-- substitution. Final demand and every trade amount are positive-denominator
-- multiples. Trade amounts may be zero when a buyer's input units are fewer
-- than its suppliers. For an economy produced by 'industrialNetworkWith', the
-- identities @x_j = sum_i z_ij + v_j = sum_m z_jm + f_j@ and
-- @sum_j v_j = sum_j f_j@ hold exactly. Complexity is
-- @O(N*log N + |E|*log N)@ with ordered 'Map' updates.
industrialFlowsWith
:: Ord k
=> FlowOptions -> TaxRate -> IndustrialEconomy k -> IndustrialFlows k
industrialFlowsWith opts (TaxRate num den) economy
| den <= 0 = error "industrialFlowsWith: tax denominator must be positive"
| num < 0 = error "industrialFlowsWith: tax numerator must be non-negative"
| not (a >= 0 && a < 1) || isNaN a || isInfinite a =
error "industrialFlowsWith: foInputShare must be finite and in [0,1)"
| any (not . validOrderedEdge) (industrialEdges economy) =
error "industrialFlowsWith: economy contains an edge outside the ordered sector DAG"
| otherwise = IndustrialFlows zMap xMap inputMap vaMap finalMap
where
a = foInputShare opts
validOrderedEdge (i, j) =
case (M.lookup i (ieSector economy), M.lookup j (ieSector economy)) of
(Just si, Just sj) -> (si, i) < (sj, j)
_ -> False
ks = firms economy
count = length ks
sizeOf j = let w = M.findWithDefault 1 j (ieSize economy)
in if finitePositive w then w else 1
meanSize = if count == 0
then 1
else sum (map sizeOf ks) / fromIntegral count
meanFinal = max 0 (foMeanFinalDemand opts)
finalMap = M.fromList
[ (j, den * max 1 (round (fromIntegral meanFinal * sizeOf j
/ meanSize / fromIntegral den)))
| j <- ks ]
order = sortBy downstreamFirst ks
downstreamFirst i j =
compare (M.findWithDefault 0 j (ieSector economy), j)
(M.findWithDefault 0 i (ieSector economy), i)
(_, zMap, xMap, inputMap, vaMap) =
foldl' solveFirm (M.empty, M.empty, M.empty, M.empty, M.empty) order
solveFirm (orders, zs, xs, ins, vas) j =
let revenue = M.findWithDefault 0 j orders
finalD = M.findWithDefault den j finalMap
output = revenue + finalD
suppliers = suppliersOf (ieNetwork economy) j
input
| null suppliers = 0
| otherwise = den * floor (a * fromIntegral output / fromIntegral den)
units = input `div` den
allocations = apportionInteger units [ (i, sizeOf i) | i <- suppliers ]
zs' = foldl' (\m i -> M.insert (i, j) (den * M.findWithDefault 0 i allocations) m)
zs suppliers
orders' = foldl'
(\m i -> M.insertWith (+) i (den * M.findWithDefault 0 i allocations) m)
orders suppliers
valueAdded = output - input
in ( orders'
, zs'
, M.insert j output xs
, M.insert j input ins
, M.insert j valueAdded vas )
-- | Per-sector cumulative weights used by two-level supplier sampling.
apportionInteger :: Ord k => Integer -> [(k, Double)] -> Map k Integer
apportionInteger amount rows
| amount <= 0 || null rows = M.fromList [ (key, 0) | (key, _) <- rows ]
| otherwise = foldl' addRemainder bases (take (fromInteger remainder) ranked)
where
positiveRows = [ (key, if finitePositive weight then weight else 1) | (key, weight) <- rows ]
total = sum (map snd positiveRows)
quotas = [ (key, fromIntegral amount * weight / total) | (key, weight) <- positiveRows ]
floors = [ (key, floor quota, quota - fromIntegral (floor quota :: Integer)) | (key, quota) <- quotas ]
bases = M.fromList [ (key, base) | (key, base, _) <- floors ]
remainder = max 0 (amount - sum [ base | (_, base, _) <- floors ])
ranked = map (\(key, _, _) -> key) $ sortBy compareRemainder floors
compareRemainder (keyA, _, fracA) (keyB, _, fracB) =
compare fracB fracA <> compare keyA keyB
addRemainder m key = M.insertWith (+) key 1 m
------------------------------------------------------------------