s = uwt / ucu = 4.444444444444445
qmax = maxpayld(s; Kwt = Kwt, Kcu = Kcu) = 6.111111111111112
n = f / q = 3.2727272727272725
3.2727272727272725
ISE 754: Logistics Engineering, Fall 2026
Carrying inventory costs money; transporting empty air is inefficient. Optimal shipment size is the best balance of the two.
Lecture 3.2 was about one-time shipments, where the shipper makes an operational decision: they know the shipment size q, and they know when and how much to ship. Periodic shipments call for a tactical decision instead: the shipper must determine the size q, and so will need to determine how often and how much to ship. The one-time decision can largely be left to a facility’s transportation department. Determining how much to ship is more likely to fall to an industrial engineer, because it is a supply chain analysis rather than an execution task. Once the size is determined, someone else can choose among carriers, perhaps the one with the better contract.
The size is worth determining because freight is lumpy, and the lumpiest part of it is the driver. Trucking depends on paying a driver for every trip, which is what makes big trucks and makes a big load much cheaper to ship than a small one. So every shipment carries a large fixed cost, and how much to ship is a trade-off between spreading that cost and not having enough on hand. Driverless trucks would let smaller loads move economically and shrink the problem, but for now it is a real decision. A household living next door to a grocery store treats the store as its pantry and plans nothing; one living twenty miles out and shopping once a week has to determine how much to buy, because each trip is a big fixed cost. Trucking is the same.
When demand for truck transport between origin-destination (O-D) points occurs repeatedly over a period of time, multiple demands are usually combined into shipments that occur periodically. Multiple demands are combined into single shipments because of the economies of scale associated with truck transport. Unlike a one-time shipment, where its size and time of occurrence is predetermined, periodic shipments require that the size and interval between shipments be determined. The exact timing and size of each shipment is determined by considering both the total transport costs (TC) and the total shipment-related inventory costs (IC), or what is termed the total logistics cost:
TLC = TC + IC = n\,c + IC \tag{1}
where
From a tactical point of view, a one-time shipment is the exception. A manufacturer has regular suppliers and regular customers, so its freight moves on some frequency rather than as one-off shipments. When the manufacturer is the customer, receiving goods from a supplier, and it controls how those goods are shipped to it, it has a choice to make about how frequently to receive them: a truckload every day, every week, every two weeks, or once a month. The two sides of that choice pull in opposite directions. The supplier might want to send large truckloads, because they are more convenient for it to ship. From the customer’s point of view it would be much better to receive smaller quantities, because the cost of carrying the inventory that a large shipment brings is too high. Determining the best frequency for a shipment means finding the balance between the two, and since a frequency n fixes the shipment size q = f/n, it is the same decision as choosing how much to ship each time.
Since there is no cost savings associated with using more than a single truckload for each periodic shipment, a truck’s maximum payload provides an upper bound on the size of each shipment:
q \le q_{\max} \tag{2}
where
As a result, the number of shipments per year equals the number of truckloads. Note that this number is not restricted to integer values, which is reasonable if the demand will continue indefinitely. The reason is that the unit of time should not affect the result. A year is only the unit chosen for convenience. Measured per month instead, the same demand is a fraction of a truck, and rounding it to a whole number of trucks each month gives a different annual total from rounding per year. Measured per week or per day it would round to zero, as though nothing ever needed to ship. So for a tactical decision the answer must be independent of the unit of time, and rounding in any unit breaks that.
Whether to round depends on the level of the decision. An operational decision is executed: orders are written and trucks go, so it is almost always discrete, since there is no fractional truck. Even there a fraction can be handled by breaking the load into pieces: a one-time shipment that fills, say, 3.27 truckloads can go as three full truckloads, with the remaining 0.27 of a load sent less-than-truckload (LTL). A fraction in a periodic shipment, such as the 3.27 truckloads a year of Ex. 1(a) below, is not that kind of fraction: it is only an artifact of choosing a year as the unit of time, and with a month or any other period it would not occur, so nothing is left over to ship. A tactical decision is planning for a future that is not known, so the number of trucks is never actually executed, and quantities such as demand keep their fractional values. At the strategic level the only things rounded are those that must physically be discrete: a firm cannot locate 3.2 facilities, however long the horizon. The one rule to take away is to keep the fractions.
Example 1: Periodic truck shipment from Raleigh to Gainesville
A product is shipped in cartons from Raleigh, NC (27606) to Gainesville, FL (32606). Each identical carton weighs 40 lb and occupies 9 ft3 (its cube). The linear dimensions of each unit are not known for truckload (TL) and LTL, and the cartons can be stacked on top of each other in a trailer. Additional information and data is presented only when it is needed to determine an answer.
Going from a one-time shipment to periodic ones needs some idea of demand, which, as in the location problems, is always the key input. It is also the input most often guesstimated: a detailed market survey and forecast is rare, and demand is the one thing everybody is fuzzy about. Here the example assumes a constant annual demand of 20 tons.
Example 1(a): Full truckloads per year
Continuing with the example, and assuming a constant annual demand for the product of 20 tons, determine the number of full truckloads per year, and account for why this number should not be rounded to an integer value.
s = uwt / ucu = 4.444444444444445
qmax = maxpayld(s; Kwt = Kwt, Kcu = Kcu) = 6.111111111111112
n = f / q = 3.2727272727272725
3.2727272727272725
n is the average shipment frequency, and is not restricted to integer values.
n = 3.2727 TL/yr, each a full truckload of q = q_{\max} = 6.1111 ton.
Example 1(b): Shipment interval
Determine the shipment interval, and how many days there are between shipments.
The interval is the reciprocal of the frequency:
\begin{aligned} t &= \frac{1}{n} = \frac{q}{f} && \text{(yr/TL), average shipment interval} \\ t \times 365.25 &= \frac{365.25}{n} && \text{(day/TL), with 365.25 day/yr} \end{aligned} \tag{3}
The interval is one over the frequency, and the days between shipments are the interval in years times 365.25. The quarter day is there because the calculation is not tied to a particular year, which may or may not be a leap year: one year in four is, so using 365.25 keeps all of the accuracy.
t = q / f = 0.3055555555555556
365.25 / n = 111.60416666666667
111.60416666666667
t = 0.3056 yr/TL, about 111.6 days between truckloads.
A baseline frequency of roughly three trips per year sounds operationally manageable, but service agreements often impose a minimum frequency, so the interval between shipments is what governs feasibility.
If inventory costs are small enough relative to transport costs, then a single shipment will always be comprised of an entire full truckload (FTL), so that q = q_{\max}. In other situations, it is also likely that a consolidated load comprised of many different shipments, each with a different cube and weight, will only be transported as a full truckload (e.g., many big-box retailers only transport full truckloads from a DC to a store). In this case, although the different shipments could be combined into a single aggregate shipment, it is often more convenient to determine the transport rate for each shipment per ton-mile assuming that r is allocated to the product based on its maximum payload, the FTL rate ($/ton-mi):
r_{FTL} = \frac{r}{q_{\max}} \tag{4}
The FTL transport cost ($/yr) is
TC_{FTL} = f\,r_{FTL}\,d = n\,r\,d = w\,d \tag{5}
where
When it is reasonable to assume that a shipment will always be transported along with other shipments as part of a full truckload, then using r_{FTL} as the truckload transport rate for the shipment allows its transport cost to be considered in a location decision without having to know the exact mix of other shipments being transported (and the mix, size, and timing can change with each shipment without impacting the decision since only f, the annual demand, is used in the analysis) and its cost per mile for transport is independent of shipment size and distance. The formulation using w is useful in location analysis where w corresponds to the criterion used for the minisum transport-oriented single-facility location problem. The two forms of Eq. 5 give the same result: the FTL rate in dollars per ton-mile times the tons and the distance, or the number of truckloads times the rate per truckload-mile times the distance. Either product, f\,r_{FTL} or n\,r, is the monetary weight w of the Location lectures, so this is how the details of a particular product to be transported are translated into the monetary weight a location problem needs.
Example 1(c): Annual FTL transport cost
Determine the annual full-truckload transport cost.
rTL = (2.0ppiTL) / 102.7 = 2.5511197663096397
rFTL = rTL / qmax = 0.4174559617597592
TC_FTL = n * rTL * d = 4441.731433123838
4441.731433123838
r_{FTL} = $0.4175/ton-mi, so TC_{FTL} = $4,441.73/yr.
The FTL cost alone does not show whether shipping full truckloads was a good idea. That is the silo problem of organizations in which people are evaluated on their own subgroup. From a transport point of view a shipment would always go full truckload: a manager whose bonus depends on what the company spends on transport would require every shipment to be a full truckload, since that maximizes the utilization of each truck and gives the minimum number of trucks per year. The downside for the company falls on whoever runs the warehouse and is evaluated on its cost. One full truckload may be five years’ usage of some washer, and that inventory has to be insured, heated and air-conditioned, so the warehouse’s costs go through the roof, and from that side the preference is to order just what is needed, in LTL shipments. Many companies still have not integrated the two, although IT systems such as SAP that control several parts of an organization help, since once a decision is built into the software much of the politics drops out. This is the classic trade-off between transport and inventory, the more general name for the warehousing side, since inventory does not have to be in a warehouse. The piece still missing is the inventory side of the ledger.
Another means of indirectly accounting for inventory costs when assuming full-truckload shipments is to place an upper limit on the average shipment interval. If t_{\max} is the maximum shipment interval, then the FTL shipment size is the lesser of the following, the FTL rate with shipment interval constraint ($/ton-mi):
r_{FTL} = \frac{r}{\min\bigl\{ f\,t_{\max},\, q_{\max} \bigr\}} \tag{6}
where
An interval constraint is a poor man’s version of including the inventory cost. Without getting into the details of exactly what the inventory costs are, a shipper may simply want deliveries every week or two because things are changing so much, rather than guess at what will be needed over the next six weeks: six weeks might be the optimum, but the shipper is willing to pay a little more to ship more frequently. An interval constraint also captures multi-stop routing. The reason is that multiple products might each have a different interval on their own, but when they are shipped together on the same truck in multi-stop routing, a single interval constraint is the best compromise among them. Everything here considers only point-to-point shipments, which is limiting, and it makes more sense for a vehicle to stop at several customers along a route, which is the subject of the routing lectures, Lectures 5.1 to 5.3. The only way to evaluate the impact of an interval constraint is to put it into the total logistics cost.
Full truckloads are often a matter of bargaining with the warehouse manager. The full-truckload interval in Ex. 1(b) was about 111 days, and three months is 90 days, so a common compromise is to switch from a truck every 111 days to one every three months. The question is then the impact: how much would the transport cost increase?
Example 1(d): FTL cost with a three-month interval
Determine the cost if the shipments were to be made at least every three months.
A three-month maximum interval sets a minimum frequency, and the transport cost follows from the larger of the two frequencies:
\begin{aligned} t_{\max} &= \frac{3}{12} \text{ yr/TL} \\ \Rightarrow\quad n_{\min} &= \frac{1}{t_{\max}} \text{ TL/yr} \\ \Rightarrow\quad q &= \frac{f}{\max\{n,\, n_{\min}\}} \\ TC'_{FTL} &= \max\{n,\, n_{\min}\}\, r\, d \end{aligned} \tag{7}
where
Shipping every three months needs about four truckloads a year against the 3.27 being shipped, so the cost will go up; the question is by how much. Three months is one quarter of a year, so a minimum of four truckloads a year is needed, and the shipment size follows from the demand and the larger of the two frequencies. The maximum is there for other problems. Here the number of full truckloads is less than four, but if demand were high enough that five or six full truckloads were already being shipped each year, the minimum of four would not dominate and the cost would not increase, since shipments would already be more frequent than every three months. The maximum handles the case where n is already greater than n_{\min}.
tmax = 3 / 12 = 0.25
nmin = 1 / tmax = 4.0
q = f / max(n, nmin) = 5.0
TC′_FTL = max(n, nmin) * rTL * d = 5428.782862706913
5428.782862706913
q = 5.0 ton, so TC'_{FTL} = $5,428.78/yr, $987.05 a year more than full truckloads.
The engineer’s role, when the transport and warehouse managers are arguing and the warehouse manager wants 90-day shipments, is to answer what it will cost in additional transport: for this product, about $1,000 a year. That number is then one piece of whatever negotiation takes place inside the company, and without the bigger picture there is no knowing whether the change is a good or a bad decision. This course is not focused on high-level strategic managerial decisions, but instead provides the modeling so that the decision process can incorporate realistic factors into the decision. Previously, because of computational friction, there was never time to get up from the engine room to the bridge to help with navigation: just getting the engines working and keeping them working was the problem. The course is still primarily focused on modeling these low-level, computationally focused details, but with AI it is possible to include more big-picture managerial issues in the analysis. In some sense, the analysis needs to work with the high level and not be stuck in the engine room.
One thing about the full truckload is unrealistic: for many products, nobody wants a full truckload of widgets. A supplier typically makes not one product but dozens, so it makes more sense to put large aggregates of several products onto a truck than to ship products individually. The truck may still go out full, but that full truckload is mixed with many other products, so from any one product’s point of view it is an allocated full truckload. The subscript zero in c_0 means independent, whether LTL or truckload: the product is not mixed with any other, and that charge is the upper bound, the most that would ever be paid. It applies to a supplier somewhere in northern Canada making one product, with nothing nearby that could be combined onto a truck. Coming south into the United States, almost every state has many other companies and suppliers, and carriers with trucks available, so a dedicated truck is almost never needed and some mixing with other products is possible. The best case is to fill the truck exactly, shipping allocated full truckloads, and that is a lower bound on the cost. The actual cost is likely to fall somewhere in between.
Fig. 1 shows the range of possible transport charges for a single shipment. The independent-shipment charge (c_0 via the formula of Lecture 3.2, Sec. 4.3) corresponds to the maximum charge, while the full-truckload charge (c_{FTL} = r_{FTL}\,q\,d) corresponds to the minimum likely charge. When multiple shipments are carried on a single truck as part of a consolidated load, each shipment’s charge corresponds to an allocated portion of the total transport charge and is represented in the figure as any of the charges between the independent and full-truckload charges. The consolidated charge approaches the full-truckload charge as the load reaches truck capacity. All of the charges are the same at the maximum payload. Note also that an allocated charge can be less than the minimum charge for an independent shipment. For a shipment of at most one truckload, the two bounds are:
q \leq q_{\max} \;\Rightarrow\; [\, UB ,\, LB \,] = \bigl[\, c_{0}(q) ,\; q\, r_{FTL}\, d \,\bigr] \tag{8}
where
ppiLTL = 177.4 # LTL PPI for Jan 2018, as in 3.2
cTLh(q) = charge_tl(q, s, d; r = rTL, Kwt = Kwt,
Kcu = Kcu, ppi = ppiTL)
cLTLh(q) = charge_ltl(q, s, d; ppi = ppiLTL)
c0(q) = min(cTLh(q), cLTLh(q)) # independent charge, the upper bound
cFTL(q) = rFTL*q*d # allocated full-truckload, the lower
# The TL/LTL indifference point, searched only up to the LTL
# estimate's cube bound of 650 ft^3.
qLTLmax = 650s/2000
qI = optimize(q -> abs(cTLh(q) - cLTLh(q)), 0.075, qLTLmax).minimizer
MC_LTL = mincharge_ltl(d; ppi = ppiLTL)
# A split horizontal axis: the first 1.5 tons take 45% of the width,
# the rest out to two and a half truckloads the other 55%, linear
# inside each piece.
QSPLIT, QEND = 1.5, 2.5qmax
u(q) = q <= QSPLIT ? 0.45q/QSPLIT :
0.45 + 0.55(q - QSPLIT)/(QEND - QSPLIT)
blue = RGBf(0.13, 0.35, 0.60)
purple = RGBf(0.58, 0.20, 0.72)
red = RGBf(0.85, 0.10, 0.10)
olive = RGBf(0.40, 0.48, 0.20)
fig = Figure(size = (687, 480))
ax = Axis(fig[1, 1];
xlabel = "Shipment size (ton)",
ylabel = "Transport charge (\$)",
title = "Transport charge for a shipment",
titlesize = 17, xlabelsize = 16, ylabelsize = 16,
xticklabelsize = 14, yticklabelsize = 14)
vlines!(ax, [u(QSPLIT)]; color = (:black, 0.45), linewidth = 1.2,
linestyle = :dashdot)
qs = range(1/2000, QEND; length = 4000)
lines!(ax, u.(qs), cFTL.(qs); color = purple, linewidth = 2.6)
# Dotted: LTL past the indifference point, and the one-truckload
# charge short of it.
qL = range(qI, qLTLmax; length = 200)
lines!(ax, u.(qL), cLTLh.(qL); color = blue, linewidth = 1.6,
linestyle = :dot)
lines!(ax, [0, u(qI)], [rTL*d, rTL*d]; color = blue, linewidth = 1.6,
linestyle = :dot)
lines!(ax, u.(qs), c0.(qs); color = blue, linewidth = 2.6)
# Both axes start at zero, so the origin sits in the corner. The limits
# and ticks come before the labels, because a label set along a curve
# takes the curve's angle as drawn, and that depends on both.
xlims!(ax, 0, 1)
ylims!(ax, 0, 1.1*3rTL*d)
ax.xticks = (u.([150/2000, qI, qmax, 2qmax]),
["150/2000", @sprintf("%.4f", qI),
@sprintf("%.2f", qmax), @sprintf("%.2f", 2qmax)])
# The LTL minimum charge (MC) is named on its own tick: the floor it
# labels is too short for a label inside the plot to clear the curve.
ax.yticks = ([MC_LTL, rTL*d, 2rTL*d, 3rTL*d],
["MC " * @sprintf("%.2f", MC_LTL);
string.(round.(Int, (1:3) .* rTL*d))])
# The angle of a curve on the page, measured in pixels between two of
# its points, so that a label rotated to it runs parallel to the curve.
function page_angle(c, q1, q2)
p1 = Makie.project(ax.scene, :data, :pixel, Point3f(u(q1), c(q1), 0))
p2 = Makie.project(ax.scene, :data, :pixel, Point3f(u(q2), c(q2), 0))
return atan(p2[2] - p1[2], p2[1] - p1[1])
end
text!(ax, u(0.4), cLTLh(0.4); text = "Independent", color = red,
font = :bold, fontsize = 15,
rotation = page_angle(cLTLh, 0.35, 0.45),
align = (:center, :bottom), offset = (-8, 6))
text!(ax, u(0.95), 650; text = "Allocated\ntruckload", color = red,
font = :bold, fontsize = 15, align = (:center, :center))
text!(ax, u(9.0), cFTL(9.0); text = "Allocated full-truckload",
color = red, font = :bold, fontsize = 15,
rotation = page_angle(cFTL, 8.0, 10.0),
align = (:center, :top), offset = (6, -8))
# The split is a change of scale, not data, so it is named where it is
# drawn rather than left for the caption to explain.
text!(ax, u(QSPLIT), 1.1*3rTL*d; text = "scale changes at 1.5 ton",
color = (:black, 0.6), fontsize = 12, rotation = pi/2,
align = (:right, :bottom), offset = (-3, -6))
text!(ax, u(1.25), cLTLh(1.25); text = "LTL", color = olive,
font = :bold, fontsize = 14, align = (:right, :bottom),
offset = (-6, 0))
for (k, qk) in zip(1:3, [0.5qmax, 1.5qmax, 2.25qmax])
text!(ax, u(qk), k*rTL*d; text = "$k TL", color = olive,
font = :bold, fontsize = 14, align = (:center, :bottom),
offset = (0, 4))
end
figThe purple line is probably never achieved exactly, since that would take a perfectly full truck. Nor does the shipper have to pay the independent charge, which is what hiring the whole truck costs, paid for whether it is filled or not. What is paid falls somewhere in the range between, and that is the allocated truckload charge. The point of all of this is negotiating with the carrier. The independent charge for the shipment could already be calculated; now the allocated full-truckload charge can be too. It is the lower bound, below which the charge is unlikely to go, and the independent charge is the most that should be paid, so a shipper negotiating with a carrier can try to settle on something in the middle. That is the range of negotiation.
So far there are two possibilities: ship full truckloads, or put a restriction such as a time interval constraint on them, as with shipments every three months. The question is always whether three months is the best: would shipping every two months be better? The way to make it a real decision is to look at it from an inventory perspective.
Inventory is taken up in more detail at the end of the course, but some of it is needed now. Inventory can be viewed as a contrast between its role and its position in a supply chain.
Total logistics costs are any of the relevant costs associated with providing a logistics service, where a relevant cost is a cost that differs when comparing multiple alternatives and, as such, can be used in making a decision between the alternatives. Total logistics cost decomposes into three such terms, and its inventory cost into one term for each role that inventory plays:
\hphantom{\underset{\displaystyle IC}{TLC}}\mathllap{TLC} = \mathrlap{TC + IC + PC}\hphantom{\underset{\displaystyle IC_\text{working} + IC_\text{economic} + IC_\text{safety}}{TC + IC + PC}} \tag{9}
\hphantom{\underset{\displaystyle IC}{TLC}}\mathllap{IC} = \mathrlap{IC_\text{working} + IC_\text{economic} + IC_\text{safety}}\hphantom{\underset{\displaystyle IC_\text{working} + IC_\text{economic} + IC_\text{safety}}{TC + IC + PC}} \tag{10}
where
With respect to transportation, there is almost always a transportation cost component of total logistics cost even for a single origin and destination, since differences in the speed and reliability of transport affect cost. Since it is usually not economic to ship individual units of an item, multiple units are batched together in a single shipment, and unless they are used immediately, those batched units incur an inventory cost for storage while they are consumed. The cost to purchase items can differ if different suppliers are considered, and typically a lower purchase cost for an item is associated with a higher transport cost. Inventory is classified along two independent axes: role (why it is held) and position (where it sits in the materials flow). Any role can occur at any position; Table 1 gives, for each role, why it is held and what it is at each position.
| Role | Why it is held | Position | What it is there |
|---|---|---|---|
| Working stock | to allow production, including transport | Raw material | in transit from supplier |
| Work in process | queuing between operations | ||
| Finished goods | in transit to customers, or presentation stock | ||
| Economic stock | to make purchasing or production cheaper | Raw material | cycle stock from truckloads |
| Work in process | batches spreading setup cost | ||
| Finished goods | seasonal pre-build | ||
| Safety stock | to buffer uncertainty | Raw material | buffer against supplier delay |
| Work in process | buffer between operations | ||
| Finished goods | buffer against uncertain demand |
Working stock is held as part of the production process, with transportation viewed as a production process. Product on a ship coming from China is in-transit inventory, and somebody has to finance it even though it is sitting in a container on a ship. Safety stock covers anything that is not perfect; in the models of this lecture everything is assumed perfectly constant, like clockwork, with no uncertainty, which is not realistic, and accounting for uncertainty is what safety stock does, the subject of Lectures 6.1 and 6.2. This lecture considers only economic stock. Even in a perfect world, freight is lumpy and there are scale economies that make shipping larger units advantageous, and that is cycle stock, the only kind of inventory dealt with in this lecture.
Working stock is what determines the choice between air cargo and ocean. From Singapore, air takes a day or two and a ship a couple of weeks, and the trade-off depends on the value: millions of dollars of high-end textiles fit on one cargo plane, so even at many times the cost of ocean, the cost of financing that inventory for two weeks would probably make air cargo preferable. For the choice of mode, the in-transit inventory cost is probably the main driver.
In transport, safety stock is not only stock kept on hand just in case. A construction site installing a large air-conditioning unit next Tuesday could have it arrive Tuesday morning, which would minimize transport cost, but more realistically it would arrive a few days early in case of disruptions, and financing a $10 million unit for those extra two or three days is safety stock, even for a one-time shipment. In transport, safety stock is often a matter of when a shipment is scheduled to arrive: a drive that normally takes 30 minutes but occasionally an hour means leaving an hour early most days, and the extra 30 minutes of arriving early on average is a kind of safety stock of earliness.
Total logistics cost is, in effect, all costs that could change as a result of a logistics-related decision: the transport cost and an inventory cost that can include all three roles. The same unit of inventory often serves several purposes. An item that arrived early because of uncertainty in delivery time is safety stock, but it also arrived in a bigger quantity for economic reasons, so the roles combine and cannot simply be separated and added together. Purchase cost enters as a question of who, since there is often a sourcing option: a domestic supplier might charge $100 per unit and an overseas one $60. That is better from a purchase point of view, but what matters is the landed cost of getting the product to where it is needed, which adds the freight transport and the inventory cost of, say, two weeks on a ship. With just one supplier, purchase cost is a fixed quantity and not part of total logistics cost; when there are options, it can be part of the equation. For periodic truck shipments the focus is on cycle stock, which is the most realistic case for trucking. Transit times differ by only a day or two, so unless the product is worth millions of dollars, the difference in working, in-transit stock is insignificant. And a truckload arrives 12 hours after it leaves, without going from 12 hours to 48 even if the truck breaks down, since another tractor can replace it, so trucking carries little of the uncertainty that safety stock covers; rail carries more, and ocean shipping much more, as the strike that closed the East and Gulf Coast ports for three days in October 2024 showed1. Purchase cost can still differ, between two domestic suppliers or, for an overseas one, in the trucking from the port against from a domestic supplier. Cycle stock and purchase cost are the two factors for trucking.
So total logistics cost is not a single term. It is an umbrella term for whatever factors are important to the decision being made: transport and inventory cost always, and purchase cost when two suppliers, perhaps one overseas and one domestic, would charge different prices.
The annual cost of cycle inventory is the annual cost of holding one ton times the average annual inventory level:
IC_\text{cycle} \;=\; \underbrace{vh}_{\text{cost of holding 1 ton 1 yr}} \;\times\; \underbrace{\alpha q}_{\text{average inventory level}} \tag{11}
where
The cost of holding one ton of inventory for one year is vh ($/ton-yr). The inventory carrying rate is expressed as a fraction since the cost per dollar of inventory per year ($/$/yr) reduces to 1/yr. This is highly formalized and is used throughout the course, with the same standard terminology. The unit value v is in dollars per ton; other units are possible, but tons are convenient. Besides being convenient, tons are the default unit for most freight transport and throughout the course, except for the very smallest shipments, package express and air freight, which are weighed in pounds or kilograms2. The carrying rate h is in dollars per dollar of inventory per year, so the dollars cancel and its units are one over a year, and the product vh is in dollars per ton-year. That is multiplied by the fraction \alpha, which is 1 here for a reason given in Sec. 3.4, and by the shipment size q in tons, so the tons cancel and IC_\text{cycle} is in dollars per year. Unit analysis is always important. Total logistics cost is the sum of the transport cost, the inventory cost and, where it applies, the purchase cost, all in dollars per year, so it is encouraging that the inventory cost works out in those units.
The value for v can be determined from the purchase price or production cost. That is, v is on a cost basis: what it is costing the company to keep the product in inventory, not the opportunity cost of selling the product for a higher value.
Components of h are interest, warehousing, and obsolescence:
h = \text{interest} + \text{warehousing} + \text{obsolescence}, \tag{12}
where the interest (5%) and warehousing (6%)3 rates are the financial-cost and storage rows of Table 2 in Lecture 3.1, each as a fraction of the $3,202 billion of total U.S. inventory, and so averages over everything from sand to high-tech computer products, and the obsolescence component, which is what really drives cycle stock since it differs a lot between products, follows from the default yearly rate for a manufactured product, which Table 3 in Lecture 3.2 puts at 25–30% for general durable manufactured goods, h = 0.3 \Rightarrow h_\text{obs} \approx 0.2.
A good rule of thumb for most manufactured products, whatever kind of widgets they are, is about 20% for obsolescence and about 10% for interest and warehousing together, so for a manufactured product with no more detail, h of about 0.3 is a good default value, and it is usually pretty robust. For a widget or any kind of discrete manufactured product, when no rate is given, 0.3 is the value to use. Working as a logistics engineer, nobody typically gives these numbers, so it is good practice to determine what would be reasonable: for data not immediately available, whether a study is needed or a reasonable default value will do as a first cut. That is why default values, the design constants of Lecture 3.2, matter: the idea is to get a result quickly.
For most items, interest and warehousing costs are fairly constant while obsolescence cost can differ significantly between items. For a product with a low finished-goods inventory (FGI) cost, measured on a yearly scale, h = h_\text{int} + h_\text{wh} + h_\text{obs}. For a product with a high FGI cost, measured on an hourly scale, h \approx h_\text{obs} and interest and warehousing can be ignored, since (h_\text{int} + h_\text{wh})/H = (0.05 + 0.06)/2000 = 0.000055, where H is the number of operating hours per year.
For products with a low finished-goods inventory cost, handled on a yearly basis, h really is the sum of the three components: obsolescence can be the biggest, but the other two are still significant. For items with a high finished-goods inventory cost, h is essentially the obsolescence cost alone, and interest and warehousing can be ignored, because the product degrades significantly on an hourly basis. Ice degrades almost 100% in 30 minutes unless it is refrigerated, and food is another example: a $50 restaurant meal produced ahead for efficiency and kept under a lamp has a crazy inventory cost. The same formulas that apply to manufactured products apply to food, and they say how bananas should be shipped. When the 2024 strike closed the East and Gulf Coast ports, bananas were among the first products expected to run short, since more than three quarters of U.S. bananas arrive through those ports4. Much else could be sourced through the West Coast: apples can be stored for a long time, so they could come in through Oakland instead of Savannah, but bananas cannot take that route to serve Raleigh because their obsolescence is so significant. Sand is the other extreme, where nothing goes obsolete and h might be 2% or 5%; bananas are at the far end. For some products the cost of obsolescence can overwhelm both the interest and the warehousing portions, and for a product with a high finished-goods inventory cost, inventory might be measured in hours: the question is not the inventory cost per year but the cost per hour. Fries at a restaurant that have to be thrown out after 20 or 30 minutes in the hopper are an example; whatever it cost to make them, their inventory is measured in minutes. Depending on how perishable a product is, its obsolescence cost can be very high.
The obsolescence component h_\text{obs} can be estimated using the “percent-reduction interval” method: given the time t_h at which a product loses x_h-percent of its original value v, it follows that:
\begin{gathered} h_\text{obs}\,t_h\,v = x_h\,v \;\Rightarrow\; h_\text{obs}\,t_h = x_h \;\Rightarrow\; \boxed{h_\text{obs} = \frac{x_h}{t_h}}, \\ \text{and } t_h = \frac{x_h}{h_\text{obs}} \end{gathered} \tag{13}
where
In practical terms, bananas lose 80% or even 100% of their value after one week, and another product might lose half its value after three months, or 20% after two months. Framing it that way is useful because somebody in industry can usually answer a question such as how much the product could be sold for after some number of weeks, and those two pieces of information translate into the equivalent effective obsolescence cost. It is important that t_h be in the same time units as t_{CT}, the cycle time of Lecture 1.3.
For example, if a product loses 80% of its value after 2 hours 40 minutes:
t_h = 2 + \frac{40}{60} = 2.67~\text{hr} \;\Rightarrow\; h = \frac{x_h}{t_h} = \frac{0.8}{2.67} = 0.3.
It is probably not food, since two hours and 40 minutes is too long, but in certain service industries something could be worth only 20% of its original value after that time. With the time as 2.67 hours, h is 0.3, and that is 0.3 on an hourly basis. Including interest and warehousing would make it 0.300055, which makes no difference, so obsolescence alone is the value to use for h. It can then be put into the total logistics cost to get sensible decisions about operating on an hourly, daily, weekly or any other basis, provided the time unit is the same as that of the activity.
Since demand is constant throughout the year, one half of a shipment is stored at the destination, on average. Assuming that the production rate is also constant, one half of a shipment will also be stored at the origin, on average. So \alpha = \alpha_O + \alpha_D = \tfrac{1}{2} + \tfrac{1}{2} = 1, the average annual inventory level that Fig. 2 draws for three successive shipments. Fig. 3 draws all four production–consumption regimes with \alpha_O and \alpha_D marked on each, and Table 2 collects the resulting \alpha. In the constant-production case, as soon as the truck delivering the last shipment has left the production facility, the inventory there is at zero. The facility then produces at a constant rate until the inventory reaches the quantity to be shipped, q, at which point the truck picks it up and it is in transit. At the destination, the inventory was zero the instant before the truck arrived, is q once it is dropped off, and then goes down at a constant rate to zero, when the next truck arrives. Over successive shipments everything lines up: the origin goes from zero to q and immediately back to zero, again and again, and the destination starts at q and goes down to zero as each truck arrives. With everything drawn as straight lines, the average at each end is q/2. The average inventory level is then q/2 at the origin plus q/2 at the destination, one half plus one half times q, and the two together give the value of 1 used for \alpha.
blue = RGBf(0.13, 0.35, 0.60)
red = RGBf(0.85, 0.10, 0.10)
ink = RGBf(0.15, 0.15, 0.15) # the time axis and the labels
tc, τ = 1.0, 0.5 # cycle and transit time, schematic
XEND = 4tc + τ + 0.3 # three shipments, room for the arrow
fig = Figure(size = (687, 520))
axs = [Axis(fig[k, 1]) for k in 1:3]
for (k, axk) in enumerate(axs)
# t0, not s: a name assigned in a top-level loop is the global one,
# and s is the product's density.
t0 = (k - 1)*tc # one cycle after the last shipment
hidedecorations!(axk); hidespines!(axk)
# the time axis, with an arrowhead
lines!(axk, [0, XEND - 0.08], [0, 0]; color = ink, linewidth = 2.5)
scatter!(axk, [XEND - 0.08], [0]; marker = :rtriangle,
markersize = 16, color = ink)
# origin: builds from 0 to q while it is produced
lines!(axk, [t0, t0 + tc], [0, 1]; color = blue, linewidth = 2.6)
# destination: drawn down from q to 0 while it is consumed
lines!(axk, [t0 + tc + τ, t0 + 2tc + τ], [1, 0]; color = blue,
linewidth = 2.6)
# the average at each end, q/2
lines!(axk, [t0, t0 + tc], [0.5, 0.5]; color = red, linewidth = 2,
linestyle = :dash)
lines!(axk, [t0 + tc + τ, t0 + 2tc + τ], [0.5, 0.5]; color = red,
linewidth = 2, linestyle = :dash)
xlims!(axk, -0.35, XEND)
ylims!(axk, k == 1 ? -0.45 : -0.08, k == 1 ? 1.35 : 1.15)
end
# The first row carries the labels.
ax1 = axs[1]
for (x, a) in ((0.0, :right), (2tc + τ, :left))
dx = a == :right ? -6 : 6
text!(ax1, x, 0; text = "0", align = (a, :bottom), fontsize = 15,
offset = (dx, 2))
text!(ax1, x, 0.5; text = L"\frac{q}{2}", align = (a, :center),
fontsize = 17, offset = (dx, 0))
end
# The peak labels sit above the peaks, clear of the lines rising and
# falling into them.
text!(ax1, tc, 1; text = L"q", align = (:center, :bottom), fontsize = 17,
offset = (0, 12))
text!(ax1, tc + τ, 1; text = L"q", align = (:center, :bottom),
fontsize = 17, offset = (0, 12))
bracket!(ax1, tc, 1.02, tc + τ, 1.02; text = "In-transit",
orientation = :down, fontsize = 15, color = ink,
textcolor = ink)
text!(ax1, tc/2, -0.08; text = "Origin", align = (:center, :top),
fontsize = 16)
text!(ax1, tc + τ + tc/2, -0.08; text = "Destination",
align = (:center, :top), fontsize = 16)
# The first row's range is taller, for its labels, so it gets a taller
# row in proportion and every row draws q at the same height.
rowsize!(fig.layout, 1, Auto((1.35 + 0.45)/(1.15 + 0.08)))
rowgap!(fig.layout, 4)
figFrom Fig. 2, the average annual inventory level is
\frac{q}{2} + \frac{q}{2} = \left( \frac{1}{2} + \frac{1}{2} \right) q = (1)\, q \Rightarrow \alpha = 1 \tag{14}
The four inter-shipment inventory fraction alternatives in Fig. 3 each add a fraction held at the origin to one held at the destination:
\alpha = \alpha_{O} + \alpha_{D} \tag{15}
where
# One regime: how the origin builds a shipment, how the destination
# uses it, and the average (dashed) at each end.
function regime!(axr, prod, cons)
hidedecorations!(axr); hidespines!(axr)
lines!(axr, [0, 1.02], [0, 0]; color = ink, linewidth = 2.5)
scatter!(axr, [1.02], [0]; marker = :rtriangle, markersize = 14,
color = ink)
if prod == :constant # builds steadily over the cycle
lines!(axr, [0, 0.4], [0, 1]; color = blue, linewidth = 2.6)
lines!(axr, [0, 0.4], [0.5, 0.5]; color = red, linewidth = 2,
linestyle = :dash)
text!(axr, 0, 0.5; text = L"\frac{q}{2}",
align = (:right, :center), fontsize = 16, offset = (-4, 0))
else # made in one short batch before pickup
lines!(axr, [0, 0.35], [0.015, 0.015]; color = red,
linewidth = 2, linestyle = :dash)
lines!(axr, [0.35, 0.4], [0, 1]; color = blue, linewidth = 2.6)
text!(axr, 0, 0.015; text = L"\approx 0",
align = (:right, :bottom), fontsize = 16, offset = (-2, 2))
end
if cons == :constant # drawn down steadily over the cycle
lines!(axr, [0.56, 0.96], [1, 0]; color = blue, linewidth = 2.6)
lines!(axr, [0.56, 0.96], [0.5, 0.5]; color = red, linewidth = 2,
linestyle = :dash)
text!(axr, 0.96, 0.5; text = L"\frac{q}{2}",
align = (:left, :center), fontsize = 16, offset = (4, 0))
else # used as soon as it arrives
lines!(axr, [0.56, 0.61], [1, 0]; color = blue, linewidth = 2.6)
lines!(axr, [0.61, 0.96], [0.015, 0.015]; color = red,
linewidth = 2, linestyle = :dash)
text!(axr, 0.96, 0.015; text = L"\approx 0",
align = (:left, :bottom), fontsize = 16, offset = (2, 2))
end
text!(axr, 0.2, -0.06; text = "$(titlecase(string(prod))) production",
align = (:center, :top), fontsize = 14)
text!(axr, 0.78, -0.06;
text = cons == :constant ? "Constant consumption" :
"Immediate consumption",
align = (:center, :top), fontsize = 14)
xlims!(axr, -0.14, 1.12); ylims!(axr, -0.3, 1.05)
return axr
end
fig = Figure(size = (687, 470))
cases = [(:constant, :constant,
L"\alpha = \frac{1}{2} + \frac{1}{2} = 1"),
(:constant, :immediate,
L"\alpha = \frac{1}{2} + 0 = \frac{1}{2}"),
(:batch, :constant,
L"\alpha = 0 + \frac{1}{2} = \frac{1}{2}"),
(:batch, :immediate, L"\alpha = 0 + 0 = 0")]
for (i, (p, c, lab)) in enumerate(cases)
regime!(Axis(fig[fld1(i, 2), mod1(i, 2)]; title = lab,
titlesize = 17), p, c)
end
fig| Regime | \alpha_O | \alpha_D | \alpha |
|---|---|---|---|
| Constant production / constant consumption | ½ | ½ | 1 |
| Constant production / immediate consumption | ½ | 0 | ½ |
| Batch production / constant consumption | 0 | ½ | ½ |
| Batch production / immediate consumption | 0 | 0 | 0 |
Constant production is not terribly realistic. Most companies are not dedicated to producing a single product for a single customer; more realistically they do batch production, with many different customers, and produce any one customer’s product over a really short period of time, so there is almost zero inventory at the origin. With a two-month shipping interval, for example, instead of producing at a constant rate over two months they might produce for one or two days, which is essentially zero. With batch production and constant consumption, \alpha is zero plus one half, or one half. A problem will not say to use an \alpha of 0.5; it will say that production occurs in batches at the supplier and consumption is constant at the destination, and that is what indicates an \alpha of one half. Constant production would take a dedicated production line, such as an auto supplier near Spartanburg that supplies only BMW, and even then it is possible but not likely. Batch production with constant consumption is the typical case for most manufacturing.
Batch production with immediate consumption would fit, for example, a company that builds large HVAC systems and ships them to commercial construction sites all over the country: it would produce in batches, load the truck and ship, and each system, arriving perhaps a little early as safety stock against uncertainty, would be installed immediately. Inventory would be almost zero and play no part, since inventory only becomes an issue when stock is on hand over a long period of time.
Dedicated production and consumption is also unlikely to be practical for another reason: with both dedicated, why not co-locate them and not bother with transport at all? The usual reason for going to the trouble of transporting a product is to produce batches of many different products in one location, with economies of scale and much more efficiently, and then transport them to the many locations where customers consume them at a constant rate. That is why batch production with constant consumption is the most common case.
It could be argued that a shipper does not pay a supplier’s inventory cost, so that with constant production at the supplier the inventory held there is of no concern. But, as with the free-on-board argument of Lecture 2.2, Sec. 2, about who actually pays the transport cost, it all comes out in the wash as a first approximation: the supplier has to carry that inventory and make money, so the shipper pays for it indirectly in a higher cost for the product, even though it is not a line item in the budget. In a calculation it is a good idea to include the origin’s inventory cost as if the shipper were paying it directly.
The parameter \alpha denotes the average fraction of the shipment size q that is held, in total, across the origin and the destination. For example, assuming that, on an annual basis, the supply and demand rates between a given O-D pair are constant, then, for a shipment of size q, the expected cycle inventory is q/2 at each end,5 meaning that the total cycle inventory across the origin and the destination is q and, therefore, \alpha = 1; also, if supply is not constant (e.g., batch production) but the time of production is not coordinated with the time of shipment, then \alpha = 1 (assuming production is equally likely to have occurred at anytime between shipments). If production at the origin is instantaneous, with a constant demand rate at the destination (i.e., the traditional EOQ model), then \alpha = 0.5. If, however, both production and consumption are instantaneous, then \alpha = 0 (more on this case below). Finally, if the supplier is another firm and there are no negotiations with the supplier, e.g., to share the benefits of using an optimal shipment size that accounts for the supplier’s inventory costs, then (from the point of view of the shipper-customer) \alpha = 0.5. Thus, annual inventory holding costs are given by \alpha v h q.
Example 1(e): Cycle inventory cost at FTL
Give a reasonable estimate for the total annual cycle inventory cost at the FTL shipment size q_{\max}, with v = \$25{,}000/ton, h = 0.3~\text{yr}^{-1}, and \alpha = 1.
The carrying rate of 0.3 comes from the product losing 5 percent of its value after 3 months, which, with the average values for interest on funds and for warehousing, makes it approximately 0.3; almost 20% of it is the obsolescence factor. The example assumes constant production and constant consumption, so \alpha is 1, and the carrying rate is the estimate for a manufactured product. Table 2 in Lecture 3.1 put the average for the entire United States at about 16%, but that includes a lot of low-value construction products, so 16% is the average for everything and not the rate to use. A product known to be manufactured is typically of much higher value, and 0.3 is a good estimate.
IC_FTL = α * v * h * qmax = 45833.333333333336
45833.333333333336
IC_{FTL} = $45,833.33/yr.
Example 1(f): Total logistics cost at FTL
Combine the transport and inventory costs to obtain the annual total logistics cost (TLC) for full-truckload TL shipments.
The total logistics cost of full truckloads is the sum of the two:
TLC_{FTL} = TC_{FTL} + IC_{\text{cycle}} = n\, r\, d + \alpha v h q \tag{16}
where
TC_FTL = 4441.731433123838
IC_FTL = 45833.333333333336
TLC_FTL = TC_FTL + IC_FTL = 50275.06476645717
50275.06476645717
TLC_{FTL} = $4,441.73 + $45,833.33 = $50,275.06/yr.
The inventory cost really dominates, which says that a full truckload is far too large a shipment size. At the optimal truckload size, the transport and inventory costs should be of the same order of magnitude, right in the same ballpark if not exactly the same, and when they are exactly the same the size is optimal. Ignoring purchase cost, the total logistics cost is around $50,000. The question is whether it can be done better, rather than stopping here, and what engineers like to do is optimize.
Transport costs typically decrease with increasing size due to economies of scale associated with providing the transport, while inventory costs increase since more inventory is held when shipment size increases. The optimal shipment size corresponds to the size that minimizes the sum of transport and inventory costs. Selecting the shipment size on the basis of its TLC requires that both inventory carrying and transportation costs be specified as a function of q. Considering only cycle inventory costs,
TLC(q) = TC(q) + IC(q) = \frac{f}{q}\,c(q) + \alpha v h q, \tag{17}
with \alpha, v, and h as defined above. Model 1 states the problem this section solves.
minimize: total logistics cost of transporting a product point-to-point (P2P) periodically, transport plus cycle inventory
solve for:
(a) average shipment size, a positive weight in tons;
(b) service that carries it, TL or LTL.
subject to:
(a) payload: a TL shipment is no larger than one full truckload;
(b) validity: an LTL shipment stays within the range the LTL rate estimate was fitted on.
return: independent shipment size with the lower total logistics cost
assumptions:
(a) annual demand is known and constant;
(b) product value, carrying rate and inventory fraction do not depend on shipment size;
(c) in-transit inventory is the same for TL and LTL, so it is not a relevant cost.
The model returns only the shipment size because the size alone determines the service: with both services evaluated at that size, the selected one always has the lower total logistics cost. The reason is that the selected service’s optimal cost is no more than the other service’s optimal cost, which is no more than what the other service would cost at the returned size, if it can carry that size at all.
Model 1 is solved one service at a time, and its first formulation is for TL service, where the optimal shipment size has a closed form.
The total annual logistics cost for TL shipments is given by the sum of the transportation cost (TC) and the cycle inventory costs (IC) at the origin and destination, specifically
TLC_{TL}(q) = \frac{f}{q}\max\bigl\{ c_{TL}, MC_{TL} \bigr\} + \alpha v h q \approx \frac{f}{q}\, r d + \alpha v h q. \tag{18}
Minimizing with respect to q yields
q_{TL}^{*} = \min\left\{ \sqrt{\frac{f \max\{ r d, MC_{TL} \}}{\alpha v h}},\; q_{\max} \right\} \approx \sqrt{\frac{f r d}{\alpha v h}}, \tag{19}
where the approximation ignores the minimum charge and maximum payload restrictions.
For TL shipments the size can be found in closed form. With q in the denominator of the transport term and in the numerator of the inventory term, the standard calculus approach of setting the derivative to zero and solving for q gives a square-root formula. It is the economic order quantity of inventory courses, the same idea that appears anywhere economic stock is calculated, such as the batch size a production line should produce, so it is nothing specific to freight transport: the transport terms are in the numerator and the inventory terms in the denominator. What is interesting, compared with the full truckload, is that distance is a component of the optimal size. As the distance gets smaller the optimal size gets smaller too, and as it goes to zero essentially no cycle stock is carried. That makes sense: a supplier shipping to an auto assembly plant hundreds of miles away wants to utilize its trucks, and as the distance goes to zero the supplier is effectively co-located, in the same facility or across the street. Driving down I-85 through Greenville, the suppliers of the BMW plant line the side of the road, their distance near zero, which makes just-in-time delivery possible. So at least qualitatively the formula moves in the right direction. In a location problem, the optimal quantity is then different at every location; full truckloads are about the only case without that complication.
The practical problem with the square-root formula is that the quantity could in principle go down to a pound, and shipping one pound by truck makes no sense: there is a minimum charge, so the charge relative to distance is replaced by the larger of it and the minimum charge. At the other extreme, a result of 20 tons would be far beyond what a truck can physically carry of the product in Ex. 1: its trailer’s weight capacity is 25 tons, but the product’s cube fills the trailer at q_{\max} = 6.11 tons, so the size is cut off at q_{\max}. A proper function includes both restrictions, but as long as the numbers are not extreme, the square-root formula is a good approximation.
Example 1(g): Optimal TL shipment size
Allowing shipment sizes less than a full truckload, determine the TL shipment size q_{TL}^{*} that minimizes the annual TLC for TL service, and the resulting TLC_{TL}^{*}.
qᵒTL = sqrt((f * rTL * d) / (α * v * h)) = 1.9024165093737864
TCᵒ_TL = (f / qᵒTL) * rTL * d = 14268.123820303399
ICᵒ_TL = α * v * h * qᵒTL = 14268.123820303397
TLCᵒ_TL = TCᵒ_TL + ICᵒ_TL = 28536.247640606794
28536.247640606794
q_{TL}^{*} = 1.9024 ton, with TC = $14,268.12 and IC = $14,268.12, so TLC_{TL}^{*} = $28,536.25/yr.
The optimal size of about 1.9 tons is a big reduction from the full truckload of 6.11 tons, and with it the transport and inventory costs are each about $14,200, summing to about $28,500, just over half the cost of full truckloads. It is not an accident that the two are exactly equal: for this equation, inventory and transport cost are always exactly the same at the optimal solution. That gives a way of knowing how far a solution is from optimal. The big imbalance at full truckloads, about $4,400 of transport against about $45,800 of inventory, shows that it is not close to optimal from a truckload point of view; transport and inventory costs close to each other indicate a size close to optimal.
Example 1(h): Allocated FTL
Including the minimum-charge and maximum-payload restrictions, determine the TLC if a shipment of size q_{TL}^{*} could be made as an allocated full-truckload (sharing the truck across many shipments).
Charged at the FTL rate, a shipment of size q_{TL}^{*} costs:
\begin{aligned} TLC_\text{AllocFTL}(q_{TL}^{*}) &= \frac{f}{q_{TL}^{*}} \bigl(q_{TL}^{*} r_{FTL} d\bigr) + \alpha v h q_{TL}^{*} \\ &= f \frac{r}{q_{\max}} d + \alpha v h q_{TL}^{*} \end{aligned} \tag{20}
where
TLC_AllocFTL = ((f * rTL) / qmax) * d + α * v * h * qᵒTL = 18709.855253427235
18709.855253427235
TLC_\text{AllocFTL} = $18,709.86/yr, against $28,536.25/yr as an independent TL shipment.
The allocated full truckload, about $18,700, is the lower bound, and the optimal independent truckload, about $28,500, the upper; in reality the cost would fall somewhere in between. A carrier contacted for this product will not dedicate a truck to it, but will pick it up along with other products from other shippers, so the shipper should not be paying $28,500 and is unlikely to get $18,700 either, since the carrier’s truck will not be 100% efficient; a reasonable guess is somewhere around $22,000 to $23,000. The equality of transport and inventory cost holds only at the optimal truckload size, not for the allocated charge, which uses that size rather than its own optimum. With the allocated charge the inventory would drive the size down to zero, so the allocated problem is not well formed: its optimum would be to ship one unit at a time, which is never seen in practice. The point to take away is that shipping everything as an independent truckload that occupies 1.9 tons of a truck with a six-ton capacity is not realistic. A recommendation to send trucks out with only a third of their capacity used will not last long, since someone will ask why other products are not added to the truck; that is never a winning game. The reality is to contact a carrier that can mix the product with others. So the independent optimum carries a red star: it keeps the analysis simple, but it is not the end of the game.
Note that in-transit inventory costs are ignored because, for distances greater than one-day’s travel, single-driver TL and LTL transit times are approximately equal (while team drivers can be used for faster TL transit, the cost per mile increases because of the additional labor costs). Also, the increase in value associated with a load reaching its destination, which is at a minimum equal to the cost of transporting the load, is ignored. Finally, for the case discussed above where \alpha = 0, total logistics cost consists only of transportation costs, and technically, the value of q that minimizes TLC_{TL} is q = \infty; however, by Eq. 19 this case results in q_{TL}^{*} = q_{\max}.
The second formulation of Model 1 is for LTL service, where the LTL rate estimate of Lecture 3.2 takes the place of the TL charge and the optimal size has no closed form.
Using a similar approach, for a given shipment density s and road distance d, such that r_{LTL}(s,q,d) can be expressed simply as r_{LTL}(q), the total annual logistics cost for LTL shipments is
\begin{aligned} TLC_{LTL}(q) &= \frac{f}{q}\max\bigl\{ c_{LTL}(q), MC_{LTL} \bigr\} + \alpha v h q \\ &= f \max\left\{ r_{LTL}(q)\, d, \frac{MC_{LTL}}{q} \right\} + \alpha v h q \\ &\approx \frac{f}{q}\, r_{LTL}(q)\, q d + \alpha v h q = f\, r_{LTL}(q)\, d + \alpha v h q. \end{aligned} \tag{21}
This expression cannot be solved in closed-form to determine the optimal LTL shipment size and, instead, must be solved numerically (using, e.g., Solver in Excel or fminsearch in MATLAB):
q_{LTL}^{*} = \arg\min_{\frac{150}{2000} \le q \le 5} TLC_{LTL}(q) \tag{22}
where
Example 1(i): Optimal LTL shipment size
Determine the LTL shipment size q_{LTL}^{*} that minimizes the annual TLC for LTL service.
Care is needed in picking bounds for optimization since the LTL formula is only valid for a limited range of values:
\begin{gathered} r_{LTL} = PPI_{LTL} \left[ \frac{\frac{s^{2}}{8} + 14}{\left( q^{\frac{1}{7}} d^{\frac{15}{29}} - \frac{7}{2} \right)\left( s^{2} + 2 s + 14 \right)} \right] \\ \begin{aligned} \text{valid for}\quad 37 &\leq d \leq 3354 && \text{(distance, mi)} \\ \frac{150}{2{,}000} &\leq q \leq \frac{10{,}000}{2{,}000} && \text{(weight, ton)} \\ 2{,}000\, \frac{q}{s} &\leq 650 && \text{(cube, ft}^{3}\text{)} \end{aligned} \end{gathered} \tag{23}
where
Together the weight and cube ranges bound the search; the upper bound of 5 tons in Eq. 22 is the case where the cube bound does not bind:
\frac{150}{2000} \leq q \leq \min \left\{ \frac{10{,}000}{2{,}000} , \frac{650 s}{2000} \right\} \tag{24}
For LTL there is no closed form, and numerical approximation takes over: the TL formula can be done by hand, and the optimal LTL size needs a computer. The LTL rate is a regression, fitted only over a range of values for each parameter. No distance in the data was more than 3,300 miles, so 10,000 miles would be far beyond it; no LTL shipment was more than five tons; and none had more than 650 ft3. A carrier’s website such as FedEx’s or Coyote’s will not even quote an LTL shipment beyond those limits, saying the product cannot be shipped that way, and that is why the optimization must include them.
# Code block 9: optimal LTL shipment size
ppiLTL = 177.4 # LTL PPI for Jan 2018
TLC_LTLh(q) = (f/q) * rate_ltl(q, s, d; ppi=ppiLTL) * q * d +
α*v*h*q
@show LB = 150/2000 # ton (lower bound on q)
@show UB = min(5, 650*s/2000) # ton (upper bound on q)
@show qᵒLTL = optimize(TLC_LTLh, LB, UB).minimizer # tonLB = 150 / 2000 = 0.075
UB = min(5, (650s) / 2000) = 1.4444444444444446
qᵒLTL = (optimize(TLC_LTLh, LB, UB)).minimizer = 0.7621710347459996
0.7621710347459996
q_{LTL}^{*} = 0.7622 ton, inside the valid range from 0.075 to 1.4444 ton.
In the random sample of U.S. shipments behind the regression, the shortest distance was about 37 miles and the longest about 3,300, East Coast to West Coast; there is probably nowhere in the world to send LTL 6,000 miles. The lower bound is written as 150 pounds over 2,000 rather than as a small decimal fraction of a ton because 150 pounds is the most that UPS and FedEx will take as a package. A 200-pound shipment has to go by their LTL service, which will also deliver to a residence, so LTL is not only for companies.
The bounds are not a formality, as an unbounded search from a starting point shows. Started at q_{\max}, which is above the five-ton limit, the search returns infinity, because the LTL rate is infinite for a shipment larger than 5 tons: the data behind the estimate did not include values in that range, so it would not be valid to extrapolate the formula to them, and the search just spins its wheels. Started instead at the truckload size of 1.9 tons, under 5 tons, it returns infinity again, now because of cube: 1.9 tons of this product is over 850 cubic feet, and an LTL shipment has to be less than 650. A carrier’s website would likewise refuse a quote for a shipment of that size and point to some type of truckload service, which here would likely be a multi-stop shipment, combined by a carrier with other shipments, rather than an independent point-to-point truck; LTL is like UPS or FedEx for a couple of pallets. Started at zero, the search returns zero, below the minimum of 150 pounds, for the same reason: there were no LTL shipments of less than 150 pounds in the data. FedEx’s website would flag such a shipment as invalid or redirect it to its package express service, which takes nothing over 150 pounds; UPS limits a package to 70 pounds, what one person can carry without a hand truck, and other services cover 70 to 150 pounds. LTL starts at 150 pounds and runs to five tons. With the upper bound set by 650 ft3 and the lower by 150 pounds, a starting point between them, such as their geometric mean, is known to be feasible.
Code block 9 needs no starting point, since optimize searches only between the two bounds.
Overall, therefore, the optimal independent shipment size is
q_{0}^{*} = \arg\min_{q} \left\{ TLC_{TL}\bigl( q_{TL}^{*} \bigr),\; TLC_{LTL}\bigl( q_{LTL}^{*} \bigr) \right\} \tag{25}
where
Example 1(j): Service choice
Given the optimal TL and LTL shipment sizes, determine which service minimizes annual TLC.
At the optimal LTL size, the total logistics cost of LTL is
TLC_{LTL}\bigl(q_{LTL}^{*}\bigr) = TC_{LTL}\bigl(q_{LTL}^{*}\bigr) + IC\bigl(q_{LTL}^{*}\bigr) \tag{26}
# Code block 10: TL or LTL at $25,000 per ton
rLTL = rate_ltl(qᵒLTL, s, d; ppi=ppiLTL)
@show rLTL # $/ton-mi
@show cLTL = rLTL * qᵒLTL * d # $
@show TCᵒ_LTL = (f/qᵒLTL) * cLTL # $/yr
@show ICᵒ_LTL = α*v*h*qᵒLTL # $/yr
@show TLCᵒ_LTL = TCᵒ_LTL + ICᵒ_LTL # $/yr
@show TLCᵒ_TL
TLCᵒ_TL > TLCᵒ_LTL ?
println("LTL selected") : println("TL selected");rLTL = 3.228317890509784
cLTL = rLTL * qᵒLTL * d = 1309.002165936596
TCᵒ_LTL = (f / qᵒLTL) * cLTL = 34349.3023550241
ICᵒ_LTL = α * v * h * qᵒLTL = 5716.282760594997
TLCᵒ_LTL = TCᵒ_LTL + ICᵒ_LTL = 40065.58511561909
TLCᵒ_TL = 28536.247640606794
TL selected
TLC_{TL}^{*} = $28,536.25/yr against TLC_{LTL}^{*} = $40,065.59/yr, so TL is selected at $25,000 per ton.
# The curves the slides draw, for a product worth v $/ton.
TC_TL(q) = (f/q)*charge_tl(q, s, d; r = rTL, Kwt = Kwt, Kcu = Kcu,
ppi = ppiTL)
TC_LTL(q) = (f/q)*charge_ltl(q, s, d; ppi = ppiLTL)
IC(q, v) = α*v*h*q
qopt_TL(v) = sqrt((f*rTL*d)/(α*v*h))
qopt_LTL(v) = optimize(q -> TC_LTL(q) + IC(q, v), LB, UB).minimizer
cred, cblue = RGBf(0.85, 0.10, 0.10), RGBf(0.13, 0.35, 0.60)
cgreen = RGBf(0.10, 0.62, 0.20)
function costcurves!(ax, v)
qT, qL = qopt_TL(v), qopt_LTL(v)
rT = range(0.75qL, 1.5qT; length = 400) # TL curves
rL = range(0.5qL, 0.99UB; length = 400)
rI = range(0.5qL, 1.5qT; length = 2)
TLC_TL(q) = TC_TL(q) + IC(q, v)
TLC_LTL(q) = TC_LTL(q) + IC(q, v)
lines!(ax, rT, TLC_TL; color = cred, linewidth = 2.2)
lines!(ax, rL, TLC_LTL; color = cred, linewidth = 2.2)
lines!(ax, rT, TC_TL; color = cblue, linewidth = 2.2)
lines!(ax, rL, TC_LTL; color = cblue, linewidth = 2.2)
lines!(ax, rI, q -> IC(q, v); color = cgreen, linewidth = 2.2)
yT, yL = TLC_TL(qT), TLC_LTL(qL)
scatter!(ax, [qL, qT], [yL, yT]; color = ink, markersize = 11)
ax.xticks = ([qL, qT], [@sprintf("%.2f", qL), @sprintf("%.2f", qT)])
ys = sort([yL, yT])
ax.yticks = (ys, [commafmt(round(Int, y)) for y in ys])
# Labels a fixed fraction of the way across the axis.
x0, x1 = 0.5qL, 1.5qT
at(p) = x0 + p*(x1 - x0)
top = max(1.25max(yL, yT), 1.12TLC_LTL(0.99UB))
lab!(x, y, t, al, o) = text!(ax, x, y; text = t, align = al,
fontsize = 15, color = ink, offset = o)
# TLC_TL leaves the top of the axis on the left, so its label goes
# where the curve comes back into view.
xs = range(at(0.12), x1; length = 400)
xT = xs[something(findfirst(q -> TLC_TL(q) <= 0.9top, xs), 1)]
lab!(xT, TLC_TL(xT), L"TLC_{TL}", (:left, :bottom), (6, 2))
lab!(0.99UB, TLC_LTL(0.99UB), L"TLC_{LTL}", (:right, :bottom), (0, 6))
lab!(at(0.32), TC_TL(at(0.32)), L"TC_{TL}", (:left, :bottom), (6, 2))
# TC_LTL hangs under its curve at the left edge, below the curve's
# height a fifth of the way in, so the steep TC_TL stays clear even
# in the narrow panels of the next figure.
lab!(at(0.0), TC_LTL(at(0.2)), L"TC_{LTL}", (:left, :top), (4, -8))
lab!(at(0.35), IC(at(0.35), v), L"IC", (:left, :top), (6, -4))
xlims!(ax, x0, x1)
ylims!(ax, IC(x0, v), top)
return ax
end
fig = Figure(size = (687, 460))
ax = Axis(fig[1, 1]; xlabel = "Shipment size (ton)",
ylabel = "\$ per year", xlabelsize = 16, ylabelsize = 16,
xticklabelsize = 14, yticklabelsize = 14)
costcurves!(ax, 25_000)
figIn Fig. 4 the red lines are the total logistics costs for truckload and for LTL, the blue lines the transport costs, and the green line the inventory cost. There is only one inventory line because the horizontal axis is the size of the load, and once the size is known the inventory cost is the same whichever service carries it; only the transport cost differs, and it differs quite a bit. For this problem, shipping truckload is clearly the better option: 1.9 tons at a time for a minimum total logistics cost of about $28,500, against about $40,000 for LTL, which ships a much smaller size but pays much more for it. For LTL the transport and inventory costs are not equal at the optimum; that equality holds only for the truckload square-root formula.
It is better to pick from separate optimal TL and LTL solutions because the independent charge has two local minima:
\begin{aligned} q^{*}_0 &= \arg\min_q \bigl\{ TLC_\text{TL}(q),\; TLC_\text{LTL}(q) \bigr\} \\ \text{vs.} \quad q^{*}_0 &\overset{!}{=} \arg\min_q \left\{ \frac{f}{q}\, c_0(q) + \alpha v h q \right\}. \end{aligned} \tag{27}
Fig. 5 shows the two cases: Fig. 5 (a) is the v=\$25{,}000 product (Ex. 1(j), TL selected) and Fig. 5 (b) the v=\$85{,}000 product (Ex. 1(k), LTL selected), each plotted as \ln TLC_0(q) over the full range of q. Fig. 5 (a) has two clear local optimal solutions that differ from the two optimal solutions for Fig. 5 (b): one shows the case where truckload is better (Fig. 5 (a)), the other where LTL is better (Fig. 5 (b)). Because the independent charge c_0(q) has a kink at the TL/LTL crossover, traditional univariate nonlinear optimization techniques could get stuck in the second-best local optimal solution. By taking the best of TL and LTL, first the TL is computed directly (analytically), and then the LTL has a single local optimal solution, the resulting procedure is much more robust and better able to find the global optimum, which is what minTLC does, in the pseudocode below.
# c0, the independent charge of Sec. 2.3's figure, takes the cheaper
# service at every size, so minimizing TLC with it directly faces both
# services' minima at once.
lnTLC0(q, v) = log((f/q)*c0(q) + IC(q, v))
function lnpanel(vi, qhi)
fig = Figure(size = (340, 380))
axv = Axis(fig[1, 1]; xlabel = "Shipment size (ton)",
ylabel = L"\ln TLC_0(q)", xlabelsize = 15,
ylabelsize = 15, xticklabelsize = 13, yticklabelsize = 13)
qs = range(LB, qhi; length = 1500)
lines!(axv, qs, q -> lnTLC0(q, vi); color = cblue, linewidth = 2)
qT, qL = qopt_TL(vi), qopt_LTL(vi)
scatter!(axv, [qL, qT], [lnTLC0(qL, vi), lnTLC0(qT, vi)];
color = ink, markersize = 10)
xlims!(axv, LB, qhi)
return fig
end
lnpanel(25_000, qmax)
lnpanel(85_000, 1.8qopt_TL(85_000))One way to solve the problem with both services would be to combine them, letting the transport cost at each size be whichever of LTL and truckload is cheaper, and then find the quantity that minimizes that combined equation. Plotted over a range of sizes, as in Fig. 5, the combined total logistics cost has the point to pick, but an optimization finds only local minima, so it would have to start in the right basin to reach it; with different numbers the lower cost would be in the other region, so determining where to look is itself a difficult problem. The way to avoid it is to calculate the truckload optimum first, then the LTL optimum, and take the smaller of the two. Combining them may seem the obvious way; this way is less immediately obvious, but much more robust, and it avoids the problem of two local minima altogether.
algorithm minTLC;
{ input: shipment f, s, α, v, h, d; carrier r, K_wt, K_cu, PPI_TL;
and PPI_LTL }
{ output: size q*, cost TLC*, and whether LTL is chosen }
begin
q_TL := min{ √( f · max{r·d, MC_TL} / (α·v·h) ), q_max };
q* := q_TL, TLC* := TLC_TL(q_TL), isLTL := false;
q_LTL := arg min{ TLC_LTL(q) : 150/2000 ≤ q ≤ min{5, 650·s/2000} };
if TLC_LTL(q_LTL) < TLC* then
q* := q_LTL, TLC* := TLC_LTL(q_LTL), isLTL := true;
return (q*, TLC*, isLTL);
end;Example 1(k): Service choice at higher product value
If the unit value of the product rises to v = \$85{,}000/ton, re-determine the optimal shipment sizes and which service is selected.
For a given lane and a given product (density and weight) at a given annual demand, the value of the load, v, becomes the critical factor in determining whether LTL or TL is the preferred mode since cycle inventory costs increase as the load value increases, but transportation costs remain unchanged. This situation is illustrated in Fig. 6.
# Code block 11: TL or LTL at $85,000 per ton
v = 85000 # was 25000
@show qᵒTL = sqrt((f*rTL*d)/(α*v*h)) # ton
@show TCᵒ_TL = (f/qᵒTL)*rTL*d # $/yr
@show ICᵒ_TL = α*v*h*qᵒTL # $/yr
@show TLCᵒ_TL = TCᵒ_TL + ICᵒ_TL # $/yr
@show qᵒLTL = optimize(TLC_LTLh, LB, UB).minimizer # ton
rLTL = rate_ltl(qᵒLTL, s, d; ppi=ppiLTL)
@show rLTL # $/ton-mi
@show cLTL = rLTL * qᵒLTL * d # $
@show TCᵒ_LTL = (f/qᵒLTL) * cLTL # $/yr
@show ICᵒ_LTL = α*v*h*qᵒLTL # $/yr
@show TLCᵒ_LTL = TCᵒ_LTL + ICᵒ_LTL # $/yr
TLCᵒ_TL > TLCᵒ_LTL ?
println("LTL selected") : println("TL selected");qᵒTL = sqrt((f * rTL * d) / (α * v * h)) = 1.0317302108505633
TCᵒ_TL = (f / qᵒTL) * rTL * d = 26309.12037668936
ICᵒ_TL = α * v * h * qᵒTL = 26309.120376689363
TLCᵒ_TL = TCᵒ_TL + ICᵒ_TL = 52618.24075337872
qᵒLTL = (optimize(TLC_LTLh, LB, UB)).minimizer = 0.2735447301881209
rLTL = 3.836994200243633
cLTL = rLTL * qᵒLTL * d = 558.3816370031636
TCᵒ_LTL = (f / qᵒLTL) * cLTL = 40825.618290592254
ICᵒ_LTL = α * v * h * qᵒLTL = 6975.390619797084
TLCᵒ_LTL = TCᵒ_LTL + ICᵒ_LTL = 47801.00891038934
LTL selected
q_{TL}^{*} = 1.0317 ton at $52,618.24/yr against q_{LTL}^{*} = 0.2735 ton at $47,801.01/yr, so LTL is selected at $85,000 per ton.
fig = Figure(size = (687, 420))
for (i, vi) in enumerate((25_000, 85_000))
axv = Axis(fig[1, i]; xlabel = "Shipment size (ton)",
ylabel = i == 1 ? "\$ per year" : "",
title = "($('a' + i - 1)) \$$(commafmt(vi)) per ton",
titlesize = 16, xlabelsize = 15, ylabelsize = 15,
xticklabelsize = 13, yticklabelsize = 13)
costcurves!(axv, vi)
end
figWith everything else the same and only the value raised from $25,000 to $85,000 per ton, things flip: truckload now costs about $5,000 a year more than LTL. The higher value has raised the inventory cost, inventory is more significant, and LTL is the better choice. That is the direction to expect: if the value were to triple, LTL should become more likely, not less. This is the Nudge check of Lecture 1.2: raise one input and confirm that the answer moves the way it should. Getting the general trends right always matters when working with a computer, because it is easy to put numbers in and get extreme results. A mental model of the overall trends to expect is what flags a result that has gone wrong, before giving a manager a result that goes exactly the wrong direction. As the value of the product goes higher and higher, the shipment size gets smaller, and eventually so small that it is better to ship LTL, even though the transport is more expensive, because LTL can ship smaller quantities. The value works like a slider from $25,000 to $85,000 per ton, along which the decision switches from truckload to LTL, and the switch is strictly a function of the value going up.
To make it easier to work with shipment data, a named tuple tr can be used to store rate and capacity limits for TL shipments, and shipment-related information can be stored in a DataFrame sh. These can then be used as inputs into a series of functions.
# Model: minimum-TLC shipment size
# sh: Per-shipment parameters; a NamedTuple or DataFrameRow with fields f
# (demand, ton/yr), s (density, lb/ft³), a (inventory fraction), v
# (unit value, $/ton), h (holding rate, 1/yr), d (distance, mi).
# tr: Carrier NamedTuple with fields r, Kwt, Kcu, ppi (TL). Pass nothing
# for LTL-only mode.
# ppi: LTL Producer Price Index scalar. Pass nothing for TL-only mode.
minTLC # (sh) -> qᵒres.qᵒ = 0.27354473025205917
res.TLCᵒ = 47801.00891038934
res.isLTL = true
true
minTLC(sh, tr [, ppi]) returns a NamedTuple (qᵒ, TLCᵒ, isLTL), the cost-minimizing shipment size, the resulting TLC, and a Boolean indicating whether LTL was chosen, for a single shipment row, letting the next two parts stay short.
Up until this point, only one product has been handled. The most important idea when shipping multiple products together is that all of the products can be treated as a single aggregate product: every formula for the optimal shipment size of a single product carries over to the aggregate product, with each model parameter replaced by its aggregate counterpart. This aggregate shipment typically never occurs in practice because there might be thousands of different potential items; instead, different small subsets of these items would comprise each actual load that is transported over the course of a year.
A second product is now shipped point-to-point from the same origin to the same destination, and its shipment is determined on its own before the two are combined in Sec. 5.2.
Example 1(l): A second product
On Jan 10, 2018, determine the optimal independent shipment size and TLC for a second product, 80 ton/yr of Class 60 material worth $5,000/ton, shipped point-to-point from Raleigh to Gainesville, as before.
The size of the second product is not given, only that it is a Class 60 product, and what is needed is its density. The class-density table, Table 8 in Lecture 3.2, gives the average density for each class, so for Class 60 the density is 32.16 lb/ft3. Everything else is the same as for the first product.
f s a v h d qmax qᵒ TLCᵒ isLTL
───────────────────────────────────────────────────────────────────
1 20 4.44 1 85,000 0.3000 532 6.11 0.27 47,801.01 1
2 80 32.16 1 5,000 0.3000 532 25.00 8.51 25,523.60 0
Second product: q_0^{*} = 8.5079 ton by TL, TLC^{*} = $25,523.60/yr; its maximum payload, 25.0 ton, is set by weight.
What differs from the first product is that its weight and cube per carton were given, so its density could be worked out, and density matters twice: it is a parameter in the maximum payload, q_{\max}, and in the LTL rate. For the second product only the class is given, so its density comes from the table.
Since for periodic shipments q is not given and must be determined, f can be used in place of q when determining the aggregate demand and density of multiple items shipped together as part of a single load (cf. the aggregate shipment formulas, Eq. 10 in Lecture 3.2):
f_{agg} = \sum_{i=1}^{m} f_i , \qquad s_{agg} = \frac{f_{agg}}{\sum_{i=1}^{m} \frac{f_i}{s_i}} . \tag{28}
Also, the aggregate value is the demand-weighted sum of the values of the items:
v_{agg} = \sum_{i=1}^{m} \frac{f_i}{f_{agg}}\, v_i \tag{29}
where
The second product is not introduced just to repeat the calculation. With two products going from the same facility to the same customer, there would never be two separate trucks, one for each, which is what the separate calculations assume, whether TL or LTL; the products would be put on the same truck, and that means treating them as one aggregate product. With the same origin and destination the distance is the same, and here \alpha and h are the same too; what differs is the quantity, 20 plus 80 for 100 tons in aggregate. The value is a demand-weighted average, each product’s value weighted by its share of total demand, which gives $21,000 per ton between the $85,000 and the $5,000, and h and \alpha could be averaged the same way. Density is a different animal. Taking density as a weighted average does not work out right; the math says it is the aggregate weight over the aggregate cube, which puts the aggregate demand in the numerator and the sum of each product’s demand over its density in the denominator, giving 14.31 lb/ft3. Density is the one aggregate that is not obvious.
Example 1(m): Aggregate shipment
Determine the annual TLC if the two products are always shipped together on the same truck, and compare to the sum of their independent TLCs.
For this example, with d_1 = d_2, h_1 = h_2, and \alpha_1 = \alpha_2, the optimal TL size of the aggregate is
q_{TL}^{*} = \sqrt{\frac{f_{agg}\, r d}{\alpha\, v_{agg}\, h}} \tag{30}
# Code block 14: the aggregate shipment
@show fagg = sum(sh.f) # ton/yr
@show sagg = fagg/sum(sh.f ./ sh.s) # lb/ft^3
@show vagg = sum(sh.f .* sh.v)/fagg # $/ton
ash = aggshmt(sh[:, [:f, :s, :a, :v, :h, :d]]) # the same, by Logjam
@show qmax_agg = maxpayld(ash, tr) # ton
resa = minTLC(ash, tr) # TL only
@show TLC_indep = sum(sh.TLCᵒ) # $/yr, shipped separately
@show TLC_agg = resa.TLCᵒ # $/yr, shipped togetherfagg = sum(sh.f) = 100
sagg = fagg / sum(sh.f ./ sh.s) = 14.311142755428978
vagg = sum(sh.f .* sh.v) / fagg = 21000.0
qmax_agg = maxpayld(ash, tr) = 19.677821288714846
TLC_indep = sum(sh.TLCᵒ) = 73324.60472925525
TLC_agg = resa.TLCᵒ = 58481.90492370572
58481.90492370572
Shipped separately, the two products cost $73,324.60/yr; shipped together as one aggregate in loads of 4.6414 ton, they cost $58,481.90/yr, a saving of $14,842.70/yr.
# Code block 15: the two products and their aggregate, as a table
tdays(q, f) = 365.25q/f # interval between shipments, days
fmt(x, dg) = x === missing ? "" : @sprintf("%.*f", dg, x)
cm(x) = x === missing ? "" : commafmt(round(Int, x))
rows = [("1", sh.f[1], sh.s[1], sh.v[1], sh.qmax[1], sh.TLCᵒ[1],
sh.qᵒ[1]),
("2", sh.f[2], sh.s[2], sh.v[2], sh.qmax[2], sh.TLCᵒ[2],
sh.qᵒ[2]),
("1 + 2", missing, missing, missing, missing, TLC_indep, missing),
("Aggregate", ash.f, ash.s, ash.v, qmax_agg, TLC_agg, resa.qᵒ)]
println("| | \$f\$ | \$s\$ | \$v\$ | \$q_{\\max}\$ | \$TLC\$ | ",
"\$q^{*}\$ | \$t\$ |")
println("|:--|--:|--:|--:|--:|--:|--:|--:|")
for (lab, f_, s_, v_, qm, tlc, q_) in rows
t_ = (q_ === missing) ? missing : tdays(q_, f_)
println("| ", lab, " | ", cm(f_), " | ", fmt(s_, 2), " | ", cm(v_),
" | ", fmt(qm, 2), " | ", tlc === missing ? "" : usd(tlc),
" | ", fmt(q_, 2), " | ", fmt(t_, 2), " |")
end
println("\n: The two products shipped separately and as one aggregate: ",
"\$f\$ in ton/yr, \$s\$ in lb/ft^3^, \$v\$ in \\\$/ton, ",
"\$q_{\\max}\$ and \$q^{*}\$ in ton, \$TLC\$ in \\\$/yr, and ",
"the interval \$t\$ in days. ",
"{#tbl-shipment-economics .dense .fit}\n")| f | s | v | q_{\max} | TLC | q^{*} | t | |
|---|---|---|---|---|---|---|---|
| 1 | 20 | 4.44 | 85,000 | 6.11 | 47,801.01 | 0.27 | 5.00 |
| 2 | 80 | 32.16 | 5,000 | 25.00 | 25,523.60 | 8.51 | 38.84 |
| 1 + 2 | 73,324.60 | ||||||
| Aggregate | 100 | 14.31 | 21,000 | 19.68 | 58,481.90 | 4.64 | 16.95 |
Table 3 sets the two products against their aggregate. Shipped separately at their own optima, the two products cost about $73,000 a year in total logistics cost, and as an aggregate about $58,000, so aggregate shipments save money. What makes the technique particularly useful is that it is not limited to two products. For a company with thousands of different SKUs, a single aggregate product can represent all of them: any one truck carries maybe several dozen or a hundred of the thousand, a kind of random draw, but the aggregate of the thousand can be used for many location problems. The aggregate shipment assumption works especially well with hundreds or thousands of products, and it makes location trade-offs and much other analysis possible, which is what makes it so powerful.
The assumption is not that every shipment to every customer carries all 1,000 products. On average perhaps 20 or 25 different products go on a truck, like drawing a sample of 25 chips out of a bucket of 1,000 for each customer, and on average all of the chips can be treated as a single aggregate shipment. Any one shipment will have a density, and a value, a little higher or a little lower than the aggregate, so the aggregate is not accurate for one shipment. But over more than 10 or 15 customers, and even with as few as a few dozen products, the law of large numbers takes over: sometimes a little too much, sometimes a little too little, and done enough times the result approaches the aggregate almost exactly. So instead of a detailed analysis, a company with 1,000 products shipping to 100 customers can quickly get an estimate of its cost, probably to within plus or minus 5%, which is enough to start making decisions. It works surprisingly well. More detailed analysis, when tried, turned out to give results that were probably worse, because a more complicated model brings in assumptions that are not right and more room for error. Simple is better.
Table 3 also shows a Bounds check, from Lecture 1.2, on an aggregation: the aggregate’s value should fall within the range of the values being aggregated, as $21,000 falls between $85,000 and $5,000, and if it were greater or less than both, something would be wrong in the aggregation. The same holds for density, which should be somewhere in the middle of the densities it is composed from; the annual demands are simply added.
Example 2: Interval-constrained full truckload
On average, 200 tons of components are shipped 750 miles from a fabrication plant to an assembly plant each year. The components are produced and consumed at a constant rate throughout the year. Currently, full truckloads of the material are shipped. Determine the impact on total annual logistics costs if TL shipments were made every two weeks. The revenue per loaded truck-mile is $2.00; a truck’s cubic and weight capacities are 3,000 ft3 and 24 tons, respectively; each ton of the material is valued at $5,000 and has a density of 10 lb per ft3; the material loses 30% of its value after 18 months; and in-transit inventory costs can be ignored.
The truck capacities here are not the standard ones. The loss of 30% of the material’s value after 18 months is its obsolescence, and it gives the obsolescence component of cost. With a problem statement like this, it is good practice to determine how it will be modeled and to relate the problem to the model’s variables. The obsolescence factor follows directly from Eq. 13: the 0.3 reduction in value over 18 months, or one and a half years, gives 0.2, and combining it with the default interest and warehousing components gives 0.31 as the overall carrying rate. Production is constant here; with batch production, which is probably more common, \alpha would be zero plus one half, or one half. The full-truckload size is the maximum payload. LTL is not considered, since the problem concerns only truckloads, although in general it is a good idea at least to consider it: shipping more frequently makes the loads smaller, and LTL may then be the better option.
# Code block 16: TLC of full truckloads
d2 = 750 # mi
s2 = 10 # lb/ft^3
Kwt2, Kcu2 = 24, 3000 # ton, ft^3
r2 = 2 # $/mi
f2 = 200 # ton/yr
v2 = 5000 # $/ton
@show hobs2 = 0.3/1.5 # 1/yr: 30% of value lost in 1.5 yr
@show h2 = 0.11 + hobs2 # 1/yr: interest + warehousing 0.11
a2 = 1 # constant production and consumption
@show qmax2 = maxpayld(s2; Kwt = Kwt2, Kcu = Kcu2) # ton
@show cFTL2 = charge_tl(qmax2, s2, d2; r = r2,
Kwt = Kwt2, Kcu = Kcu2) # $
@show TLC_FTL2 = totlogcost(qmax2, cFTL2, f2, a2, v2, h2) # $/yrhobs2 = 0.3 / 1.5 = 0.19999999999999998
h2 = 0.11 + hobs2 = 0.31
qmax2 = maxpayld(s2; Kwt = Kwt2, Kcu = Kcu2) = 15.0
cFTL2 = charge_tl(qmax2, s2, d2; r = r2, Kwt = Kwt2, Kcu = Kcu2) = 1500.0
TLC_FTL2 = totlogcost(qmax2, cFTL2, f2, a2, v2, h2) = 43250.0
43250.0
# Code block 17: TLC of two-week shipments
@show nFTL2 = f2/qmax2 # TL/yr
@show tmax2 = 2*7/365.25 # yr/TL
@show nmin2 = 1/tmax2 # TL/yr
@show q2wk = f2/max(nFTL2, nmin2) # ton
@show c2wk = charge_tl(q2wk, s2, d2; r = r2,
Kwt = Kwt2, Kcu = Kcu2) # $
@show TLC_2wk = totlogcost(q2wk, c2wk, f2, a2, v2, h2) # $/yr
@show ΔTLC = TLC_2wk - TLC_FTL2 # $/yrnFTL2 = f2 / qmax2 = 13.333333333333334
tmax2 = (2 * 7) / 365.25 = 0.038329911019849415
nmin2 = 1 / tmax2 = 26.089285714285715
q2wk = f2 / max(nFTL2, nmin2) = 7.665982203969883
c2wk = charge_tl(q2wk, s2, d2; r = r2, Kwt = Kwt2, Kcu = Kcu2) = 1500.0
TLC_2wk = totlogcost(q2wk, c2wk, f2, a2, v2, h2) = 51016.20098758189
ΔTLC = TLC_2wk - TLC_FTL2 = 7766.200987581891
7766.200987581891
TLC_{FTL} = $43,250.00/yr and TLC_{2wk} = $51,016.20/yr, so shipping every two weeks increases total logistics cost by $7,766.20 per year. At 7.67 ton the two-week shipments are still truckloads, so LTL is not considered.
As a logistics engineer, that is where the analysis stops: the manager takes it further and determines whether about $7,000 a year is important and worth the change, but at least there is a dollar figure for the impact. A two-week interval would typically come from the destination: a manager at the assembly plant who does not want monthly shipments because there is nowhere to store the product. Or the 200 tons a year, assumed constant, may in reality change quite a bit, so that with whole truckloads an increase in demand could force a smaller extra truckload to be shipped just because of running out of stock; the real world is not constant. The 5% and 6% for interest and warehousing are default values to use when a problem gives none, just as 2,750 ft3 and 25 tons are the truck capacities unless others are called out. The assumption of constant demand and production is the most bothersome one, since very few products have that kind of consistency. But even under it, the problem can simply be recalculated every two weeks, so assuming constancy is not locking in a year, and in practice it is not much of an issue.
The cost might still be worth incurring if the customer wants to be able to change its order size every two weeks rather than receive a month’s worth of inventory at a time, because things change so much that it does not want to be stuck with obsolete inventory. If the customer is willing to do this despite a calculated increase of about $7,000, that only means the model does not incorporate all of the factors: some of the customer’s considerations are not in it.
Kate Gibson, “What products could be affected by the port strike?” CBS News, October 3, 2024: “More than 75% of U.S. bananas arrive at ports handled by the International Longshoremen’s Association.” https://www.cbsnews.com/news/port-strike-east-gulf-chocolate-bananas-whiskey/ (accessed 2026-10-02).↩︎
IATA, “Air Cargo Tariffs and Rules: What You Need to Know,” July 27, 2021: “The rate is the amount charged by the carrier for the transportation of the shipment by a specific unit of weight (kg or lb).” https://www.iata.org/en/publications/newsletters/iata-knowledge-hub/air-cargo-tariffs-and-rules-what-you-need-to-know/ (accessed 2026-10-02).↩︎
CSCMP’s 33rd Annual State of Logistics Report, as reported in John Schulz, “State of Logistics 2022: Out of sync,” Logistics Management, 2022.↩︎
Kate Gibson, “What products could be affected by the port strike?” CBS News, October 3, 2024: “More than 75% of U.S. bananas arrive at ports handled by the International Longshoremen’s Association.” https://www.cbsnews.com/news/port-strike-east-gulf-chocolate-bananas-whiskey/ (accessed 2026-10-02).↩︎
Hall, R.W., 1985, “Dependence between shipment size and mode in freight transportation,” Transportation Science 19(4):436–444.↩︎