Topic 3: Network Design - Two-Echelon Multi-Commodity (TEC)

05115130 Supply Chain Modelling and Optimisation

Lecturer: Sam Wiwatanapataphee

School of EECMS, Curtin University

7 Jun 2026

Goals today

  1. Two-Echelon Multi-Commodity (TEC) Models
    • Introduction to TEMC
    • Commodity types
    • Network structure and decisions
    • Objectives, decision variables, and constraints
  2. TEMC Examples
    • Example 1
      • Model formulation
      • Python implementation (Case analysis)
    • Example 2
      • Model formulation
      • Python implementation

1. Two-Echelon Multi-Commodity Location Model (TEMC)

1.1 Introduction to TEMC

A TEMC is an optimisation framework used in logistics and SCM for decisions involving facility location and routing of multiple commodities across a two-tier distribution system. It involves optimal selection of facilities (e.g., distribution centres) and routing of commodities from origins to destinations.

Key Characteristics

  • Multi-Commodity Framework
    • Handles multiple products simultaneously
    • Each product may have:
      • Different supplier capacities
      • Different customer demands
      • Different transportation costs
  • Two-Echelon Structure
    • Echelon 1:
      Suppliers → DCs
    • Echelon 2:
      DCs → Customers
  • Objective: minimises total cost
  • Decision variables: facility location and flow decisions

Example: Supply chain for a supermarket chain

Suppliers

Multiple suppliers providing different products (e.g., fresh produce, packaged goods) to distribution centres.

DCs

Centralised facilities that receive products from suppliers, store them, and distribute to retail stores.

Customers

Retail stores that require various products to meet consumer demand.

  • Echelon 1: Suppliers → DCs
  • Echelon 2: DCs → Customers
  • Commodities: Fresh produce (perishable), packaged food (non-perishable), personal care, electronics (low volume, high value)

1.2 Commodity Types

Type Description Examples
Perishable goods Require refrigeration/fast delivery Fresh fruits and vegetables, meat, dairy products (milk, cheese)
Packaged consumer goods (non-perishable) Standard shelf-stable products Canned food, snacks, beverages, cleaning supplies, detergents
Health and personal care Often require clean handling, sometimes regulated Medicines and pharmaceuticals, cosmetics
Electronics and Appliances Sensitive, often high-value Smart phones, laptops, home appliances, batteries and chargers
Industrial goods and raw materials Used in manufacturing or construction Steel, copper, aluminum, machinery parts, building materials, chemicals
Apparel and textiles Non-perishable, high volume Clothing, shoes, beddings, fabrics
Hazardous materials Special safety and transport regulations apply Flammable liquids, gases under pressure, toxic substances, explosives
Automotive products Bulk or spare parts Tires, engine parts, lubricants, windshields
  • Multicommodity flows: Manages multiple products with distinct characteristics.

1.3 Network Structure and Decisions

%%{init: {
  "flowchart": {
    "padding": 70
  },
  "themeVariables": {
    "fontSize": "44px"
  }
}}%%
flowchart LR
    S1((" S1 "))
    S2((" S2 "))
    DC1((" DC1 "))
    DC2((" DC2 "))
    C1((" C1 "))
    C2((" C2 "))
    C3((" C3 "))

    S1 --> DC1
    S1 --> DC2
    S2 --> DC1
    S2 --> DC2
    DC1 --> C1
    DC1 --> C2
    DC1 --> C3
    DC2 --> C1
    DC2 --> C2
    DC2 --> C3

  1. Where to locate:
    • Suppliers (tier 1)
    • DCs (tier 2)
  2. Which facility serves whom:
    • Which DCs are supplied by which suppliers
    • Which DCs serve which retail stores
  3. How much each commodity flows:
    • From Supplier → DC
    • From DC → Stores

1.4 Objectives, Decision Variables, and Constraints

Objectives

  • Minimising fixed facility opening costs
  • Minimising total transportation costs for each commodity and path
  • Minimising possible storage/handling costs
  • Ensuring service-level compliance

Decision Variables

  • Facility Location Variables: Indicate opening of DCs.
  • Commodity Allocation Variables: Routing of commodities.
  • Flow Variables: Quantities of each commodity shipped along each path.

Constraints

  • Capacity constraints: Maximum throughput at each facility.
  • Demand fulfilment: Customer demands must be satisfied.
  • Flow conservation: Balance of commodity flows at intermediate nodes.

2. TEMC Examples

2.1 TEMC Example 1

Consider a two-echelon, multi-product distribution network.

  • First echelon: suppliers S1 and S2 ship products to potential distribution centres DC1 and DC2, which consolidate and forward goods to customers.
  • Second echelon: customers C1, C2, and C3 receive two products, P1 and P2, each with specific supply and demand levels.

Problem statements

  1. Which DCs (DC1, DC2, or both) should be operational to minimise total cost?
  2. How much of each product (P1, P2) should be shipped from each supplier (S1, S2) to each selected DC?
  3. How much of each product should be shipped from the selected DCs to each customer (C1, C2, C3)?
  4. What is the optimal allocation strategy to minimise total costs (including transportation, fixed, and handling costs)?
Table 1. Supplier Capacities and Supplier → DC Transportation Cost
Supplier Product Capacity (units) Cost to DC1 ($/unit) Cost to DC2 ($/unit)
S1 P1 100 3 4
S1 P2 150 3 4
S2 P1 200 2 5
S2 P2 100 2 5
Table 2. DC Capacity and DC → Customer Transportation Cost
DC Capacity (units) Cost to C1 ($/unit) Cost to C2 ($/unit) Cost to C3 ($/unit)
DC1 350 4 3 6
DC2 200 5 4 2
Table 3. Customer Demands (units)
Customer Demand P1 Demand P2
C1 80 50
C2 120 100
C3 60 70
Table 4. DC Fixed and Variable Costs
DC Fixed Cost ($) Variable Cost ($/unit)
DC1 500 1
DC2 400 1.5

2.1.1 Model Formulation

I. Set Model Variables

  • \(V_1 = \{\text{S1}, \text{S2}\}:\) Suppliers
  • \(V_2 = \{\text{DC1}, \text{DC2}\}:\) DCs
  • \(V_3 = \{\text{C1}, \text{C2}, \text{C3}\}:\) Customers
  • \(H = \{\text{P1}, \text{P2}\}:\) Products

II. Model Parameters

  • \(p_i^h:\) Supplier capacities
  • \(K_j:\) DC capacities
  • \(D_k^h:\) Customer demand
  • \(c_{ijk}^h:\) Transportation costs
  • \(f_j:\) Fixed costs of DCs
  • \(g_j:\) Variable costs of DCs

III. Decision Variables

  • \(x_{ijk}^h \ge 0:\)Flow of product \(h\) from supplier \(i\) via DC \(j\) to customer \(k\)

  • \(y_{jk} \in \{0,1\}\): 1 if DC \(j\) serves customer \(k\), 0 otherwise

  • \(z_j \in \{0,1\}\): 1 if DC \(j\) is opened, 0 otherwise

IV. Objective Function

\[ \text{Min } \color{blue}{ \sum_{i \in V_1}\sum_{j \in V_2}\sum_{k \in V_3}\sum_{h \in H} c_{ijk}^h x_{ijk}^h} + \sum_{j \in V_2} \left( \color{red}{f_j z_j} + \color{green}{g_j \sum_{k \in V_3}\sum_{h \in H} D_k^h y_{jk}} \right) \]

Transportation cost

Facility opening cost

Variable handling cost

  • Transportation Costs: Both echelons (Supplier → DC and DC → Customer)
    • E.g. Transportation cost from S1 → DC1 → C1 is given by

\[ \sum_{h\in \{P1,P2\}} [c_{S1,DC1}^h + c_{DC1,C1}^h] \cdot x_{S1,DC1,C1}^h \\ = [c_{S1,DC1}^{P1} + c_{DC1,C1}^{P1}] \cdot x_{S1,DC1,C1}^{P1} + [c_{S1,DC1}^{P2} + c_{DC1,C1}^{P2}] \cdot x_{S1,DC1,C1}^{P2} \]

How many transportation terms do we have in total?

IV. Objective Function

\[ \text{Min } \color{blue}{ \sum_{i \in V_1}\sum_{j \in V_2}\sum_{k \in V_3}\sum_{h \in H} c_{ijk}^h x_{ijk}^h} + \sum_{j \in V_2} \left( \color{red}{f_j z_j} + \color{green}{g_j \sum_{k \in V_3}\sum_{h \in H} D_k^h y_{jk}} \right) \]

Transportation cost

Facility opening cost

Variable handling cost

  • Facility Opening Costs:

\[ 500 z_{DC1} + 400 z_{DC2} \]

  • Variable Handling Costs: cost of processing all demand handled by each DC

\[ 1 \times (\text{total units handled by DC1}) + 1.5 \times (\text{total units handled by DC2}) \\ = 1 \times \sum_{k \in V_3}\sum_{h \in H} D_k^h y_{DC1,k} + 1.5 \times \sum_{k \in V_3}\sum_{h \in H} D_k^h y_{DC2,k} \]

V. Constraints

(i) Supplier Capacity Constraints

For each supplier \(𝑖\) and product \(ℎ\), the total quantity shipped (via any DC \(𝑗\) to any customer \(𝑘\)) must not exceed the supplier’s capacity for that product.

\[ \sum_{j \in V_2}\sum_{k \in V_3} x_{ijk}^h \le p_i^h, \quad \forall i \in V_1, h \in H \]

E.g. for Supplier S1, Product P1, the possible flows are:

  • S1 → DC1 → C1
  • S1 → DC1 → C2
  • S1 → DC1 → C3
  • S1 → DC2 → C1
  • S1 → DC2 → C2
  • S1 → DC2 → C3

\[ x_{S1,DC1,C1}^{P1} + x_{S1,DC1,C2}^{P1} + x_{S1,DC1,C3}^{P1} + x_{S1,DC2,C1}^{P1} + x_{S1,DC2,C2}^{P1} + x_{S1,DC2,C3}^{P1} \le 100 \]

How many supplier capacity constraints do we have in total?

(ii) Demand Satisfaction Constraints (under single-sourcing assumption)

\[ \sum_{i \in V_1} x_{ijk}^h = D_k^h y_{jk}, \quad \forall j,k,h \]

\[ \sum_{j \in V_2} y_{jk} = 1, \quad \forall k \in V_3 \]

Each customer \(𝑘\) must receive their full demand for product \(ℎ\) only from the DC \(𝑗\) that serves them

= 1 if DC \(j\) serves customer \(k\)

E.g. for Customer C1 and Product P1:

\[ x_{S1,DC1,C1}^{P1} + x_{S2,DC1,C1}^{P1} = 80 y_{DC1,C1} \]

\[ y_{DC1,C1} + y_{DC2,C1} = 1 \]

If DC1 serves C1, it must deliver all 80 units of P1 via flows from S1 and/or S2

Ensures that each customer is assigned to exactly one DC (i.e., single-sourcing)

How many demand satisfaction constraints do we have in total?

(iii) DC Capacity Constraints

\[ \sum_{k \in V_3}\sum_{h \in H} D_k^h y_{jk} \le K_j z_j, \; \forall j \in V_2 \]

For each DCs \(𝑗\), the total demand it is responsible for (across all customers and all products) must not exceed its capacity \(𝐾_𝑗​\) — and only if it’s open.

E.g. DC1 capacity constraint:

\[ \left(80+50\right)y_{DC1,C1}+\left(120+100\right)y_{DC1,C2}+\left(60+70\right)y_{DC1,C3}\le350z_{DC1} \]

(iv) DC Opening Constraints

\[ y_{jk} \le z_j, \quad \forall j,k \]

Links customer assignment to the DC opening decision.

(v) Number of DCs Constraint

\[ \sum_{j \in V_2} z_j = p \]

Ensure that exactly \(𝑝\) DCs are opened, depending on the objectives.

(vi) Variable Bounds and Binary Constraints

\[ x_{ijk}^h \ge 0;\quad y_{jk} \in \{0,1\}; \quad z_j \in \{0,1\} \]

2.1.2 Python Implementation of TEMC (Case I: Ignoring Facility Costs)

Code
import pulp

# Initialize the model
model = pulp.LpProblem("TEMC", pulp.LpMinimize)

# Sets
suppliers = ['S1', 'S2']
dcs = ['DC1', 'DC2']
customers = ['C1', 'C2', 'C3']
products = ['P1', 'P2']

# Parameters
supply = {('S1','P1'):100, ('S1','P2'):150,
          ('S2','P1'):200, ('S2','P2'):100}

dc_capacity = {'DC1':350, 'DC2':200}

demand = {('C1','P1'):80, ('C1','P2'):50,
          ('C2','P1'):120, ('C2','P2'):100,
          ('C3','P1'):60, ('C3','P2'):70}

cost_sup_dc = {('S1','DC1'):3, ('S1','DC2'):4,
               ('S2','DC1'):2, ('S2','DC2'):5}

cost_dc_cust = {('DC1','C1'):4, ('DC1','C2'):3, ('DC1','C3'):6,
                ('DC2','C1'):5, ('DC2','C2'):4, ('DC2','C3'):2}

# Decision variables
x = pulp.LpVariable.dicts('flow_sup_dc', (suppliers, dcs, products), lowBound=0, cat='Continuous')
y = pulp.LpVariable.dicts('flow_dc_cust', (dcs, customers, products), lowBound=0, cat='Continuous')
z = pulp.LpVariable.dicts('dc_open', dcs, cat='Binary')

# Objective Function
model += (
    pulp.lpSum(cost_sup_dc[s,dc] * x[s][dc][p] for s in suppliers for dc in dcs for p in products) +
    pulp.lpSum(cost_dc_cust[dc,c] * y[dc][c][p] for dc in dcs for c in customers for p in products)
)

# Constraints
# Supplier capacities
for s in suppliers:
    for p in products:
        model += pulp.lpSum(x[s][dc][p] for dc in dcs) <= supply[s,p]

# DC capacities
for dc in dcs:
    model += pulp.lpSum(y[dc][c][p] for c in customers for p in products) <= dc_capacity[dc] * z[dc]

# Demand satisfaction
for c in customers:
    for p in products:
        model += pulp.lpSum(y[dc][c][p] for dc in dcs) == demand[c,p]

# Flow conservation (DC level)
for dc in dcs:
    for p in products:
        model += pulp.lpSum(x[s][dc][p] for s in suppliers) == pulp.lpSum(y[dc][c][p] for c in customers)

# Solve the model
model.solve(pulp.PULP_CBC_CMD(msg=0))

# Output the results
print("Status:", pulp.LpStatus[model.status],
      ", Total Cost =", pulp.value(model.objective))

for v in model.variables():
    if v.varValue > 0:
        print(v.name, "=", v.varValue)
Status: Optimal , Total Cost = 2710.0
dc_open_DC1 = 1.0
dc_open_DC2 = 1.0
flow_dc_cust_DC1_C1_P1 = 80.0
flow_dc_cust_DC1_C1_P2 = 50.0
flow_dc_cust_DC1_C2_P1 = 120.0
flow_dc_cust_DC1_C2_P2 = 100.0
flow_dc_cust_DC2_C3_P1 = 60.0
flow_dc_cust_DC2_C3_P2 = 70.0
flow_sup_dc_S1_DC1_P2 = 50.0
flow_sup_dc_S1_DC2_P1 = 60.0
flow_sup_dc_S1_DC2_P2 = 70.0
flow_sup_dc_S2_DC1_P1 = 200.0
flow_sup_dc_S2_DC1_P2 = 100.0

Case I (Ignoring Facility Costs): Optimal Solution

Table 5. Supplier \(\rightarrow\) DC Flows (units)
Supplier DC P1 P2 Total
S1 DC1 0 50 50
S1 DC2 60 70 130
S2 DC1 200 100 300
S2 DC2 0 0 0
Table 6. DC \(\rightarrow\) Customer Flows (units)
DC Customer P1 P2 Total
DC1 C1 80 50 130
DC1 C2 120 100 220
DC2 C3 60 70 130

%%{init: {
  "flowchart": {
    "padding": 40,
    "nodeSpacing": 50,
    "rankSpacing": 70
  },
  "themeVariables": {
    "fontSize": "20px"
  }
}}%%

flowchart LR

    S1((S1))
    S2((S2))

    DC1((DC1))
    DC2((DC2))

    C1((C1))
    C2((C2))
    C3((C3))

    %% Supplier -> DC
    S1 -->|P2: 50| DC1

    S2 -->|P1: 200<br>P2: 100| DC1

    S1 -->|P1: 60<br>P2: 70| DC2

    %% DC -> Customer
    DC1 -->|P1: 80<br>P2: 50| C1

    DC1 -->|P1: 120<br>P2: 100| C2

    DC2 -->|P1: 60<br>P2: 70| C3

    %% Highlight open DCs
    style DC1 fill:#c8e6c9
    style DC2 fill:#c8e6c9

  • Total cost: $2,710
  • Decision: Open both DCs
  • DC1 serves C1 and C2, using
    130 + 220 = 350 units of its 350-unit capacity (100% utilisation).
  • DC2 serves C3, using 130 units of its 200-unit capacity (65% utilisation).
Table 1. Supplier Capacities and Supplier → DC Transportation Cost
Supplier Product Capacity (units) Cost to DC1 ($/unit) Cost to DC2 ($/unit)
S1 P1 100 3 4
S1 P2 150 3 4
S2 P1 200 2 5
S2 P2 100 2 5

%%{init: {
  "flowchart": {
    "padding": 30,
    "nodeSpacing": 50,
    "rankSpacing": 60
  },
  "themeVariables": {
    "fontSize": "20px"
  }
}}%%

flowchart LR

    S1((S1))
    S2((S2))

    DC1((DC1))
    DC2((DC2))

    C1((C1))
    C2((C2))
    C3((C3))

    %% Supplier -> DC
    S1 -->|P2: 50| DC1

    S2 -->|P1: 200<br>P2: 100| DC1

    S1 -->|P1: 60<br>P2: 70| DC2

    %% DC -> Customer
    DC1 -->|P1: 80<br>P2: 50| C1

    DC1 -->|P1: 120<br>P2: 100| C2

    DC2 -->|P1: 60<br>P2: 70| C3

    %% Highlight open DCs
    style DC1 fill:#c8e6c9
    style DC2 fill:#c8e6c9

Table 7. Transportation Cost Breakdown
Arc $/unit Total Units Total Cost ($)
S1 → DC1 3 50 150
S1 → DC2 4 130 520
S2 → DC1 2 300 600
DC1 → C1 4 130 520
DC1 → C2 3 220 660
DC2 → C3 2 130 260
Total 2,710

2.1.3 Python Implementation of TEMC (Case II: Including Facility Costs)

Code
import pulp

# Initialise the model
model = pulp.LpProblem("TEMC_with_Facility_Costs_Integer", pulp.LpMinimize)

# Sets
suppliers = ['S1', 'S2']
dcs = ['DC1', 'DC2']
customers = ['C1', 'C2', 'C3']
products = ['P1', 'P2']

# Parameters
supply = {('S1','P1'): 100, ('S1','P2'): 150,
          ('S2','P1'): 200, ('S2','P2'): 100}
dc_capacity = {'DC1': 350, 'DC2': 200}

demand = {('C1','P1'): 80, ('C1','P2'): 50,
          ('C2','P1'): 120, ('C2','P2'): 100,
          ('C3','P1'): 60, ('C3','P2'): 70}

cost_sup_dc = {('S1','DC1'): 3, ('S1','DC2'): 4,
               ('S2','DC1'): 2, ('S2','DC2'): 5}

cost_dc_cust = {('DC1','C1'): 4, ('DC1','C2'): 3, ('DC1','C3'): 6,
                ('DC2','C1'): 5, ('DC2','C2'): 4, ('DC2','C3'): 2}

# Facility costs
fixed_cost = {'DC1': 500, 'DC2': 400}
handling_cost = {'DC1': 1, 'DC2': 1.5}

# Decision variables
x = pulp.LpVariable.dicts('flow_sup_dc', (suppliers, dcs, products), lowBound=0, cat='Continuous')
y = pulp.LpVariable.dicts('flow_dc_cust', (dcs, customers, products), lowBound=0, cat='Continuous')
z = pulp.LpVariable.dicts('dc_open', dcs, cat='Binary')

# Objective Function (Transportation + Facility Costs)
model += (
    pulp.lpSum(cost_sup_dc[s,dc] * x[s][dc][p] for s in suppliers for dc in dcs for p in products) +
    pulp.lpSum(cost_dc_cust[dc,c] * y[dc][c][p] for dc in dcs for c in customers for p in products) +
    pulp.lpSum(fixed_cost[dc] * z[dc] + handling_cost[dc] * pulp.lpSum(y[dc][c][p] for c in customers for p in products) for dc in dcs)
)

# Supplier capacities
for s in suppliers:
    for p in products:
        model += pulp.lpSum(x[s][dc][p] for dc in dcs) <= supply[s, p]

# DC capacity constraints (flows only allowed if facility is open)
for dc in dcs:
    model += pulp.lpSum(y[dc][c][p] for c in customers for p in products) <= dc_capacity[dc] * z[dc]

# Demand satisfaction constraints
for c in customers:
    for p in products:
        model += pulp.lpSum(y[dc][c][p] for dc in dcs) == demand[c, p]

# Flow conservation constraints at DCs (inflow equals outflow for each product)
for dc in dcs:
    for p in products:
        model += pulp.lpSum(x[s][dc][p] for s in suppliers) == pulp.lpSum(y[dc][c][p] for c in customers)

# Solve the model
model.solve(pulp.PULP_CBC_CMD(msg=0))

# Output results
print("Status:", pulp.LpStatus[model.status])
print("Total Cost =", pulp.value(model.objective))

for v in model.variables():
    if v.varValue > 0:
        print(v.name, "=", v.varValue)
Status: Optimal
Total Cost = 4155.0
dc_open_DC1 = 1.0
dc_open_DC2 = 1.0
flow_dc_cust_DC1_C1_P1 = 80.0
flow_dc_cust_DC1_C1_P2 = 50.0
flow_dc_cust_DC1_C2_P1 = 120.0
flow_dc_cust_DC1_C2_P2 = 100.0
flow_dc_cust_DC2_C3_P1 = 60.0
flow_dc_cust_DC2_C3_P2 = 70.0
flow_sup_dc_S1_DC1_P2 = 50.0
flow_sup_dc_S1_DC2_P1 = 60.0
flow_sup_dc_S1_DC2_P2 = 70.0
flow_sup_dc_S2_DC1_P1 = 200.0
flow_sup_dc_S2_DC1_P2 = 100.0

Case II: Optimal Solution

  • The flow and DCs opening decisions remain the same as in Case I, but now we also consider the facility opening and handling costs in cost calculation.
Table 8. Cost Breakdown for Case II
Cost Component Value
Transportation (same as Case I) $2,710
Fixed Facility Costs \((500 + 400)\) $900
Handling Costs \(((1 \times 350) + (1.5 \times 130))\) $545
Total Cost $4,155
  • The addition of fixed and handling costs does not change the optimal network design; it only increases the total cost from 2,710 to 4,155.
  • Opening both DCs remains economically necessary to satisfy demand given the capacity constraints.

2.2 TEMC Example 2

  • An electronics company plans to optimise the distribution of two products: Smartphones (P1) and Laptops (P2).
  • The company has two factories (FA and FB) that produce these products.
  • The company is considering opening two potential DCs (DCE and DCW).
  • These DCs would serve three customer regions (RC, RS, RN).
  • The goal is to minimise total costs while meeting customer demands and respecting supplier and DC capacities.

Problem Statement

  1. Which DCs (DCE, DCW, or both) should be operational to minimise total cost?
  2. How much of each product (P1, P2) should be shipped from each supplier (FA, FB) to each selected DC?
  3. How much of each product should be shipped from the selected DCs to each customer region (RC, RS, RN)?
  4. What is the optimal allocation strategy to minimise total costs (including transportation, fixed, and handling costs)?
Table 1. Supplier Capacities (units)
Supplier P1 P2
FA 500 300
FB 400 400
Table 2. DC Information
DC Capacity Fixed Cost ($) Handling Cost ($/unit)
DCE 700 1000 2
DCW 600 1200 1.5


Table 3. Customer Demands (units)
Customer P1 P2
RC 200 150
RS 300 250
RN 200 150
Table 4. Transportation Costs (Suppliers → DCs) ($/unit)
Supplier DCE DCW
FA 5 7
FB 6 3
Table 5. Transportation Costs (DCs → Customers) ($/unit)
DC RC RS RN
DCE 3 7 4
DCW 4 2 5

2.2.1 Model Formulation

I. Sets and Indices

  • \(I\): Set of suppliers (indexed by \(i\))
  • \(J\): Set of candidate DCs (indexed by \(j\))
  • \(K\): Set of customers (indexed by \(k\))
  • \(H\): Set of products (indexed by \(h\))

II. Decision Variables

  • \(x_{ij}^{h}\): Quantity of product \(h\) shipped from supplier \(i\) to DC \(j\)
  • \(y_{jk}^{h}\): Quantity of product \(h\) shipped from DC \(j\) to customer \(k\)
  • \(z_{j}\): Binary variable (1 if DC \(j\) is opened, 0 otherwise)

III. Parameters

  • \(P_{i}^{h}\): Production capacity of supplier \(i\) for product \(h\)
  • \(K_{j}\): Maximum capacity of DC \(j\)
  • \(D_{k}^{h}\): Demand of customer \(k\) for product \(h\)
  • \(C_{ij}\): Cost per unit from supplier \(i\) to DC \(j\)
  • \(C_{jk}\): Cost per unit from DC \(j\) to customer \(k\)
  • \(f_j\): Fixed cost of opening DC \(j\)
  • \(g_j\): Handling cost per unit at DC \(j\)

IV. Objective Function

\[ \min \color{blue}{\sum_{i \in I}\sum_{j \in J}\sum_{h \in H} C_{ij} x_{ij}^{h}} + \color{green}{\sum_{j \in J}\sum_{k \in K}\sum_{h \in H} C_{jk} y_{jk}^{h}} + \color{purple}{\sum_{j \in J}\left(f_j z_{j} + g_j\sum_{k \in K}\sum_{h \in H} y_{jk}^{h}\right)} \]

Supplier → DC
transportation cost

DC → Customer
transportation cost

DC fixed +
handling cost

\(\underbrace{\hspace{8em}}\)
Mathematically equivalent to Example 1’s transportation costs if we define \(C_{ijk}^h = C_{ij}^h + C_{jk}^h\)


\(𝑦_{𝑗𝑘}^ℎ\) is a flow variable not binary; thus, we don’t need to multiply \(𝐷_𝑘^ℎ\)

V. Constraints

(i) Supplier Capacity: Total shipments from a supplier must not exceed its capacity

\[ \sum_{j \in J} x_{ij}^{h} \leq P_{i}^{h}, \quad \forall i \in I, h \in H \]

(ii) DC Capacity: DC shipments must not exceed the DC’s capacity if opened

\[ \sum_{k \in K}\sum_{h \in H} y_{jk}^{h} \leq K_{j} z_{j}, \quad \forall j \in J \]

(iii) Demand Fulfillment: Demand for each product at each customer must be fully met

\[ \sum_{j \in J} y_{jk}^{h} = D_{k}^{h}, \quad \forall k \in K, h \in H \]

(iv) Flow Conservation at DCs: The quantity entering a DC must equal quantity leaving

\[ \sum_{i \in I} x_{ij}^{h} = \sum_{k \in K} y_{jk}^{h}, \quad \forall j \in J, h \in H \]

(v) Facility Opening (Binary): Ensure that DC opening decisions remain binary

\[ z_j \in \{0,1\}, \quad \forall j \in J \]

(vi) Non-Negativity

\[ x_{ij}^{h}, y_{jk}^{h} \geq 0, \quad \forall i,j,k,h \]

2.2.2 Python Implementation of TEMC

Code
import pulp

# Initialize the model
model = pulp.LpProblem("TEMC_Example2", pulp.LpMinimize)

# Sets
suppliers = ['FA', 'FB']
dcs = ['DCE', 'DCW']
customers = ['RC', 'RS', 'RN']
products = ['P1', 'P2']

# Parameters
supply = {
    ('FA', 'P1'): 500, ('FA', 'P2'): 300,
    ('FB', 'P1'): 400, ('FB', 'P2'): 400
}

dc_capacity = {'DCE': 700, 'DCW': 600}

demand = {
    ('RC', 'P1'): 200, ('RC', 'P2'): 150,
    ('RS', 'P1'): 300, ('RS', 'P2'): 250,
    ('RN', 'P1'): 200, ('RN', 'P2'): 200
}

cost_sup_dc = {
    ('FA', 'DCE'): 5, ('FA', 'DCW'): 7,
    ('FB', 'DCE'): 6, ('FB', 'DCW'): 3
}

cost_dc_cust = {
    ('DCE', 'RC'): 3, ('DCE', 'RS'): 7, ('DCE', 'RN'): 4,
    ('DCW', 'RC'): 4, ('DCW', 'RS'): 2, ('DCW', 'RN'): 5
}

fixed_cost = {'DCE': 1000, 'DCW': 1200}
handling_cost = {'DCE': 2, 'DCW': 1.5}

# Decision variables
x = pulp.LpVariable.dicts('flow_sup_dc', ((i,j,p) for i in suppliers for j in dcs for p in products), lowBound=0, cat='Continuous')
y = pulp.LpVariable.dicts('flow_dc_cust', ((j,k,p) for j in dcs for k in customers for p in products), lowBound=0, cat='Continuous')
z = pulp.LpVariable.dicts('dc_open', dcs, cat='Binary')

# Create Objective function
model += (
    pulp.lpSum(cost_sup_dc[i,j]*x[i,j,p] for i in suppliers for j in dcs for p in products) +
    pulp.lpSum(cost_dc_cust[j,k]*y[j,k,p] for j in dcs for k in customers for p in products) +
    pulp.lpSum(fixed_cost[j]*z[j] for j in dcs) +
    pulp.lpSum(handling_cost[j]*y[j,k,p] for j in dcs for k in customers for p in products)
), "Total_Cost"

# Supplier capacity constraints
for i in suppliers:
    for p in products:
        model += pulp.lpSum(x[i,j,p] for j in dcs) <= supply[i,p]

# DC capacity constraints (only if opened)
for j in dcs:
    model += pulp.lpSum(y[j,k,p] for k in customers for p in products) <= dc_capacity[j] * z[j]

# Demand fulfillment constraints
for k in customers:
    for p in products:
        model += pulp.lpSum(y[j,k,p] for j in dcs) == demand[k,p]

# Flow conservation constraints at DCs
for j in dcs:
    for p in products:
        model += pulp.lpSum(x[i,j,p] for i in suppliers) == pulp.lpSum(y[j,k,p] for k in customers)

# Solve the optimization problem
model.solve(pulp.PULP_CBC_CMD(msg=0))

# Output results
print("Status:", pulp.LpStatus[model.status])
print("Total Cost = $", pulp.value(model.objective))

# Facility decision outputs
for j in dcs:
    print(f"DC {j} Open:", z[j].varValue)

# Flow outputs (Supplier -> DC)
for i in suppliers:
    for j in dcs:
        for p in products:
            if x[i,j,p].varValue > 0:
                print(f"Flow from Supplier {i} to DC {j}, Product {p}: {x[i,j,p].varValue}")

# Flow outputs (DC -> Customer)
for j in dcs:
    for k in customers:
        for p in products:
            if y[j,k,p].varValue > 0:
                print(f"Flow from DC {j} to Customer {k}, Product {p}: {y[j,k,p].varValue}")
Status: Optimal
Total Cost = $ 13600.0
DC DCE Open: 1.0
DC DCW Open: 1.0
Flow from Supplier FA to DC DCE, Product P1: 400.0
Flow from Supplier FA to DC DCE, Product P2: 300.0
Flow from Supplier FB to DC DCW, Product P1: 300.0
Flow from Supplier FB to DC DCW, Product P2: 300.0
Flow from DC DCE to Customer RC, Product P1: 200.0
Flow from DC DCE to Customer RC, Product P2: 100.0
Flow from DC DCE to Customer RN, Product P1: 200.0
Flow from DC DCE to Customer RN, Product P2: 200.0
Flow from DC DCW to Customer RC, Product P2: 50.0
Flow from DC DCW to Customer RS, Product P1: 300.0
Flow from DC DCW to Customer RS, Product P2: 250.0

Optimal Solution

Table 6. Supplier \(\rightarrow\) DC Flows
Factory DC P1 P2 Total
FA DCE 400 300 700
FB DCW 300 300 600

%%{init: {
  "flowchart": {
    "padding": 60,
    "nodeSpacing": 70,
    "rankSpacing": 70
  },
  "themeVariables": {
    "fontSize": "20px"
  }
}}%%
flowchart LR

    FA((FA))
    FB((FB))

    DCE((DCE))
    DCW((DCW))

    RC((RC))
    RS((RS))
    RN((RN))

    %% Factory to DC flows
    FA -->|P1: 400<br>P2: 300| DCE
    FB -->|P1: 300<br>P2: 300| DCW

    %% DC to customer region flows
    DCE -->|P1: 200<br>P2: 100| RC
    DCE -->|P1: 200<br>P2: 200| RN

    DCW -->|P2: 50| RC
    DCW -->|P1: 300<br>P2: 250| RS

    %% Open DCs
    style DCE fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px
    style DCW fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px

Table 7. DC \(\rightarrow\) Customer Flows
DC Region P1 P2 Total
DCE RC 200 100 300
DCE RN 200 200 400
DCW RC 0 50 50
DCW RS 300 250 550
  • Total cost = $13,600
  • Decision: Open both DCs
  • DCE is supplied entirely by FA and serves RC and RN.
  • DCW is supplied entirely by FB and serves RS and RC.
  • RC receives products from both DCs, while RS is served only by DCW and RN only by DCE.

Total Cost Breakdown

Table 8. Supplier → DC Transportation Cost
Factory DC Product Flow Unit cost Cost
FA DCE P1 400 5 2,000
FA DCE P2 300 5 1,500
FB DCW P1 300 3 900
FB DCW P2 300 3 900
Total 1,300 5,300
Table 9. DC → Customer Transportation Cost
DC Region Product Flow Unit cost Cost
DCE RC P1 200 3 600
DCE RC P2 100 3 300
DCE RN P1 200 4 800
DCE RN P2 200 4 800
DCW RC P2 50 4 200
DCW RS P1 300 2 600
DCW RS P2 250 2 500
Total 1,300 3,800
Table 10. DC Fixed Costs
DC Open? Fixed cost Cost
DCE 1 1,000 1,000
DCW 1 1,200 1,200
Total 2,200
Table 11. DC Handling Costs
DC Total flow handled Unit handling cost Cost
DCE 700 2.00 1,400
DCW 600 1.50 900
Total 1,300 2,300
Table 12. Total Cost Breakdown
Cost component Cost
Factory → DC transportation 5,300
DC → Customer transportation 3,800
Fixed DC opening cost 2,200
Handling cost 2,300
Total cost 13,600