Model Assumptions
The following assumptions hold in the current implementation, but can be modified in future releases.
- Production lead times are independent of the amount being produced.
- Transportation costs are paid to a third party (not a node in the network).
- Replenishment orders are placed in topological order. This means that upstream nodes place orders first. This allows the following scenario: if the lead time is 0, the ordered inventory will immediately be available so that the node can use it to fulfill downstream orders.
Model Limitations
The following features are not currently supported:
- Alternate bills of materials (see Model Inputs) for the same material are not currently supported. This is particularly relevant for chemical systems (e.g., there are two reactions that can be used to make the same product).
- Capacity limitations on shared feedstock inventory among
producer
nodes (e.g., shared inventory tanks) are not enforced in a straightforward way sinceproducer
nodes have dedicated raw material storage. Shared feedstock inventory can be modeled by having an upstream storage node with zero lead time to each of theproducer
nodes. Each of theproducer
nodes should use an(s,S)
replenishment policy withs = 0, S = 0
. When a production order is of sizex
is going to be placed in a period, the policy will assume the feedstock position at theproducer
node is going to derop to-x
and will orderx
to bring the position up to0
. Since the lead time is0
and orders are placed with topological sorting (see Sequence of Events), the inventory will be immediately sent to theproducer
node and be available for when the production order comes in. However, if there is not enough production capacity to processx
, the excess will be left in the dedicated storage for thatproducer
node, making it possible to violate the shared inventory capacity constraint. - If a
producer
can produce more than 1 material, it is possible for it to produce all materials it is capable of producing simultaneously (if there are enough raw materials). This occurs because the model does not account for resource constraints (e.g., single reactor can only do reaction 1 or reaction 2, but not both simultaneously). However, these can be enforced manually with the reorder actions. Potential fixes (requires changing the source code):- Drop inventory capacities to 0 when the production equipment is occupied. Requires modeling each production unit as its own node.
- Develop a production model (perhaps based on the Resource-Task Network paradigm)
Creating a Supply Chain Network
The supply network topology must be mapped on a network graph using Graphs.jl. The system parameters are stored in the network's metadata using MetaGraphs.jl. The network can be generated by using the MetaDiGraph
function and passing one of the following:
- Number of nodes in the network, which can the be connected via the
connect_nodes!
function:
net = MetaDiGraph(3)
connect_nodes!(net,
1 => 2,
2 => 3
)
- An adjacency matrix for the network:
net = MetaDiGraph(
[0 1 0;
0 0 1;
0 0 0]
)
- An existing
DiGraph
, such as a serial directed graph (path_digraph
):
net = MetaDiGraph(path_digraph(3))
General network, node-specific, and arc-specific metadata can be added using the set_prop!
and set_props!
functions. The following subsections describe the property keys accepted for the supply chain network metadata.
General Network Parameters
The graph metadata should have the following fields in its metadata:
:materials::Vector
with a list of all materials in the system.
Node-specific Parameters
Producers
will have the following fields in their node metadata:
:initial_inventory::Dict
: initial inventory for each material (keys
). Default =0
.:inventory_capacity::Dict
: maximum inventory for each material (keys
). Default =Inf
.:holding_cost::Dict
: unit holding cost for each material (keys
). Default =0
.:early_fulfillment::Dict
: (only when the node has at least 1 supplier) (true
/false
) on if the node accepts orders being fulfilled before their due date for each material (keys
). Default =true
.:partial_fulfillment::Dict
: (only when the node has at least 1 supplier) (true
/false
) on if the node accepts orders being fulfilled partially for each material (keys
). Default =true
.:supplier_priority::Dict
: (only when the node at least 1 supplier)Vector
of suppliers (from high to low priority) for each material (keys
). When a request cannot be fulfilled due to insufficient production capacity or on-hand inventory, the system will try to reallocate it to the supplier that is next in line on the priority list (ifreallocate = true
). Default =[node] ∪ inneighbors(SupplyChainEnv.network, node)
.:customer_priority::Dict
: (only when the node has at least 1 customer)Vector
of customers (from high to low priority) for each material (keys
). Downstream requests are fulfilled by this prioritization (after giving priority first to due and then expired orders). Default =outneighbors(SupplyChainEnv.network, node)
.:production_capacity::Dict
: maximum production capacity for each material (keys
). Default =Inf
.:make_to_order::Vector
: list of materials that are make-to-order. Default =[]
.:bill_of_materials::Union{Dict,NamedArray}
:keys
are materialTuples
, where the first element is the input material and the second element is the product/output material; thevalues
indicate the amount of input material consumed to produce 1 unit of output material. Alternatively, aNamedArray
can be passed where the input materials are the rows and the output materials are the columns. The following convention is used for the bill of material (BOM) values:zero
: input not involved in production of output.negative number
: input is consumed in the production of output.positive number
: input is a co-product of the output.
Distributors
will have the following fields in their node metadata:
:initial_inventory::Dict
: initial inventory for each material (keys
). Default =0
.:inventory_capacity::Dict
: maximum inventory for each material (keys
). Default =Inf
.:holding_cost::Dict
: unit holding cost for each material (keys
). Default =0
.:early_fulfillment::Dict
: (only when the node has at least 1 supplier) (true
/false
) on if the node accepts orders being fulfilled before their due date for each material (keys
). Default =true
.:partial_fulfillment::Dict
: (only when the node has at least 1 supplier) (true
/false
) on if the node accepts orders being fulfilled partially for each material (keys
). Default =true
.:supplier_priority::Dict
: (only when the node has at least 1 supplier)Vector
of suppliers (from high to low priority) for each material (keys
). When a request cannot be fulfilled due to insufficient production capacity or on-hand inventory, the system will try to reallocate it to the supplier that is next in line on the priority list (ifreallocate = true
). Default =inneighbors(SupplyChainEnv.network, node)
.:customer_priority::Dict
: (only when the node has at least 1 customer)Vector
of customers (from high to low priority) for each material (keys
). Downstream requests are fulfilled by this prioritization (after giving priority first to due and then expired orders). Default =outneighbors(SupplyChainEnv.network, node)
.
Markets
will have the following fields in their node metadata:
:initial_inventory::Dict
: initial inventory for each material (keys
). Default =0
.:inventory_capacity::Dict
: maximum inventory for each material (keys
). Default =Inf
.:holding_cost::Dict
: unit holding cost for each material (keys
). Default =0
.:early_fulfillment::Dict
: (only when the node has at least 1 supplier) (true
/false
) indicates if the node accepts orders being fulfilled before their due date for each material (keys
). Default =true
.:market_early_fulfillment::Dict
: (true
/false
) indicates if the market accepts orders being fulfilled before their due date for each material (keys
). Default =true
.:partial_fulfillment::Dict
: (only when the node has at least 1 supplier) (true
/false
) indicates if the node accepts orders being fulfilled partially for each material (keys
). Default =true
.:market_partial_fulfillment::Dict
: (true
/false
) indicates if the market accepts orders being fulfilled partially for each material (keys
). Default =true
.:supplier_priority::Dict
: (only when the node has at least 1 supplier)Vector
of suppliers (from high to low priority) for each material (keys
). When a request cannot be fulfilled due to insufficient production capacity or on-hand inventory, the system will try to reallocate it to the supplier that is next in line on the priority list (ifreallocate = true
). Default =inneighbors(SupplyChainEnv.network, node)
.:demand_distribution::Dict
: probability distributions from Distributions.jl for the market demands for each material (keys
). For deterministic demand, instead of using a probability distribution, useD where D <: Number
. Default =0
.:demand_frequency::Dict
: number of times demand occurs per period on average for each material (keys
). Default =1
.:demand_data::Dict
: Vector of orders for each period in the simulation, for each material (keys
). For each period, a single order amount can be specified, or aTuple
/Vector
of orders amounts. Demand data takes precedence over any demand distribution/demand frequency provided. Default =nothing
.:sales_price::Dict
: market sales price for each material (keys
). Default =0
.:unfulfilled_penalty::Dict
: unit penalty for unsatisfied market demand for each material (keys
). Default =0
.:service_lead_time::Dict
: service lead time (probability distribution or deterministic value) allowed to fulfill market demand for each material (keys
). Default =0
.
Arc-specific Parameters
All arcs have the following fields in their metadata:
:sales_price::Dict
: unit sales price for inventory sent on that edge (from supplier to receiver) for each material (keys
). Default =0
.:transportation_cost::Dict
: unit transportation cost for shipped inventory for each material (keys
). Default =0
.:pipeline_holding_cost::Dict
: unit holding cost per period for inventory in-transit for each material (keys
). Default =0
.:unfulfilled_penalty::Dict
: unit penalty for unsatisfied internal demand for each material (keys
). Default =0
.:lead_time::Distribution{Univariate, Discrete}
: probability distributions from Distributions.jl for the lead times for each material (keys
) on that edge. Lead times are transportation times when the edge has twodistributor
nodes and production times when the edge joins theproducer
anddistributor
nodes in a plant. For deterministic lead times, instead of using a probability distribution, useL where L <: Number
. Default =0
.:lead_time_data::Dict
: Vector of lead times with a non-negative value for each period in the simulation, for each material (keys
) on that edge. Lead time data takes precedence over any lead time distribution/value. Default =nothing
.:service_lead_time::Dict
: service lead time (probability distribution or deterministic value) allowed to fulfill internal demand for each material (keys
). Default =0
.
Creating a Supply Chain Environment
The SupplyChainEnv
function can be used to create a SupplyChainEnv
Constructor. This function takes the following inputs:
- Positional Arguments:
Network::MetaDiGraph
: supply chain network with embedded metadatanum_periods::Int
: number of periods to simulate
- Keyword Arguments (system options):
backlog::Bool = true
: backlogging allowed iftrue
; otherwise, orders that reach their due date and are not fulfilled become lost sales.reallocate::Bool = false
: the system try to reallocate requests if they cannot be satisfied iftrue
; otherwise, no reallocation is attempted.guaranteed_service::Bool = false
: the simulation will operate under the assumptions in the Guaranteed Service Model (GSM). Iftrue
,backlog = true
will be forced. Orders that are open and within the service lead time window will be backlogged. Once the service lead time expires, the orders become lost sales. In order to replicate the GSM assumption that extraordinary measures will be used to fulfill any expired orders, a dummy node with infinite supply can be attached to each node and set as the lowest priority supplier to that node.adjusted_stock::Bool = true
: the simulation will discount orders that have been placed, but have not been fulfilled from the inventory position (and echelon stock) from the supplier node, and will add them to the on-order inventory of the requested node.capacitated_inventory::Bool = true
: the simulation will enforce inventory capacity constraints by discarding excess inventory at the end of each period iftrue
; otherwise, the system will allow the inventory to exceed the specified capacity.evaluate_profit::Bool = true
: the simulation will calculate the proft at each node iftrue
and save the results inSupplyChainEnv.profit
.
- Aditional Keyword Arguments:
discount::Float64 = 0.
: discount factor (i.e., interest rate) to account for the time-value of money.numerical_precision::Int = 6
: Numerical precision (number of digits) for external demand sampling.seed::Int = 0
: random seed for simulation.
Simulation Outputs
The SupplyChainEnv
Constructor has the following fields to store the simulation results in DataFrames
:
inventory
: on-hand, level, position, echelon, pipeline, and discarded inventory for eachlocation
(arc
ornode
),material
, andperiod
. Discarded inventory is marked whencapacitated_inventory = true
.orders
: internal and external orders for eachmaterial
on eacharc
(for internal demand) and eachnode
(for external demand). The ID, creation date, and quantity are stored for each order.open_orders
: open (not yet fulfilled) internal and external orders for eachmaterial
on eacharc
(for internal demand) and eachnode
(for external demand). The ID, creation date, and quantity are stored for each open order. Thedue
column indicates the time left until the order is due (as specified by theservice_lead_time
).fulfillments
: order fulfillment quantities for each order ID. Thetype
column indicates if the amount on that row is materialsent
,delivered
, or is alost sale
.shipments
: current in-transit inventory for eacharc
andmaterial
with remaining lead time.profit
: time-discounted profit for eachnode
at eachperiod
.metrics
: service metrics (service level and fillrate) on eacharc
for eachmaterial
.