Building a 3D Bin Packing Engine in F#: From Business Problem to Production API
The logistics and supply chain industry is undergoing a massive transformation. With the full enforcement of ESG (Environmental, Social, and Governance) reporting standards and carbon taxes in Europe by 2026, shipping empty space inside trucks and maritime containers is no longer just an operational loss—it is a direct financial penalty.
When a company ships a container that is only 70% full, they are paying for 100% of the fuel and 100% of the carbon emissions. Maximizing the packing density—fitting as many boxes as possible into a single container—has become a critical engineering priority.
In this article, we will explore how to build a custom 3D Bin Packing engine from scratch using F#. We will start by understanding the business problem, selecting the right mathematical approach, and then writing a complete, production-ready backend service.
1. The Business Context: Why Standard Systems Fail
Most Warehouse Management Systems (WMS) come with basic volume calculators. They sum up the volume of the items and compare it to the volume of the container. If Total Item Volume < Container Volume, the system assumes they fit.
However, in the real world, objects are rigid 3D shapes. You cannot melt boxes to fill empty gaps. Furthermore, there are physical constraints:
- Orientation limits: A box containing liquids must always stay upright.
- Weight distribution: You cannot place a 50kg steel part on top of fragile glassware.
- Sequence: You need to pack items in an order that makes sense for the warehouse worker or a robotic arm.
When logistics companies realize this, they try to implement basic packing algorithms in their databases or using simple Python scripts. They usually start with a “First Fit Decreasing” heuristic: sort the boxes by size, from largest to smallest, and push them into the container layer by layer. This approach is fast but terrible at utilizing space, often leaving 20% to 30% of the container empty because it creates irregular gaps.
The business needs a system that can calculate the absolute optimal packing arrangement, returning exact 3D coordinates (X, Y, Z) and rotation angles for every single box, and it needs to do this in less than a second to not block the warehouse API.
2. Finding the Right Algorithm
When we look at the 3D Bin Packing Problem (3D-BPP), we quickly realize it belongs to a class of mathematical problems known as NP-Hard. This means that as the number of boxes increases, the number of possible packing combinations grows factorially.
If you have 50 different boxes, the number of possible packing sequences is 50! (a number with 64 zeros). If you add 6 possible rotations for each box, the search space becomes astronomically large.
We evaluated several approaches to solve this:
- Pure Mathematical Optimization (MIP Solvers like Gurobi or CPLEX). We can formulate this as a Mixed-Integer Programming model. The solver will mathematically guarantee the absolute best solution. The problem: Finding that absolute best solution for 50 boxes might take 5 hours of compute time. We need a response in 1 second.
- Machine Learning / Deep Reinforcement Learning. Training a neural network to play “3D Tetris”. The problem: AI models struggle with strict, non-negotiable physical constraints. If the AI hallucinates and overlaps two boxes by just 1 millimeter, the physical robot will crush the goods on the actual warehouse floor.
- Metaheuristics (Genetic Algorithm + Extreme Point Heuristic). This is the hybrid approach we ultimately selected. It provides near-optimal solutions (90-95% density) in milliseconds.
Here is how we divided the responsibility:
- We use a Deterministic Geometric Heuristic to figure out where a box can be placed inside the 3D space.
- We use a Genetic Algorithm to figure out which box to place next and how to rotate it.
How the Extreme Point Heuristic Works
Standard 3D grid systems divide the container into millions of tiny cubes (voxels) and check each one. This consumes too much memory. The Extreme Point Heuristic (EPH) is much smarter.
Instead of tracking empty space, EPH tracks the “corners” where a new box can be placed.
- The container starts empty. There is only one extreme point: the origin coordinate (0, 0, 0).
- We place the first box at (0, 0, 0).
- Placing this box consumes the (0, 0, 0) point. However, it generates up to three new extreme points for future boxes: one point extending along the X-axis (width), one along the Y-axis (height), and one along the Z-axis (depth) based on the corners of the box we just placed.
- For the next box, the algorithm checks the available extreme points, finds the one closest to the back-bottom-left corner of the container, and places the box there (provided it does not overlap with already placed boxes).
What We Expect as the Result
Our F# engine will take a JSON payload containing the dimensions of the shipping container and a list of items to pack.
The algorithm will process thousands of permutations using the Genetic Algorithm.
The output will be a JSON response containing an array of objects. Each object will represent a packed box, containing its original ID, its assigned 3D coordinate (X, Y, Z) indicating its bottom-left-back corner, and the specific rotation applied to it. This data can be directly fed into a React/Three.js frontend to visualize the packing process step-by-step.
Now, let us move to the complete implementation.
3. Implementation Step 1: Domain-Driven Design
F# is a functional-first language with a powerful type system. We will start by defining our domain models. By using strict types, algebraic data types (discriminated unions), and immutable records, we eliminate entire classes of bugs (like negative dimensions or invalid rotations) at compile time.
We use the [<Struct>] attribute for basic geometric types to allocate them on the stack. This reduces Garbage Collection (GC) pressure, which is crucial because our Genetic Algorithm will create millions of these objects per second during its search.
Code: Domain.fs
module BinPacking.Domain
// Represents a point or a dimension in 3D space.
// Using Struct for performance optimization during high-frequency calculations.
[<Struct>]
type Vector3D = {
X: float
Y: float
Z: float
}
[<Struct>]
type Dimensions = {
Width: float
Height: float
Depth: float
}
// The 6 possible orthogonal rotations of a 3D box.
// W = Width, H = Height, D = Depth.
// Example: WHD means the box is placed normally. HWD means it is tilted on its side.
type Rotation =
| WHD
| HWD
| HDW
| DWH
| DHW
| WDH
// Represents an item that needs to be packed.
type Box = {
Id: string
BaseDimensions: Dimensions
AllowedRotations: Rotation list // E.g., Liquids might only allow WHD and DWH
Weight: float
}
// Represents the container we are packing into (e.g., a pallet or a truck).
type Container = {
Id: string
Dimensions: Dimensions
MaxWeight: float
}
// Represents a box that has been successfully assigned a position in 3D space.
type PlacedBox = {
Box: Box
Position: Vector3D
Rotation: Rotation
ActiveDimensions: Dimensions // Dimensions after the rotation is applied
}
// A helper function to calculate the active dimensions of a box based on its rotation.
let applyRotation (dim: Dimensions) (rot: Rotation) : Dimensions =
match rot with
| WHD -> dim
| HWD -> { Width = dim.Height; Height = dim.Width; Depth = dim.Depth }
| HDW -> { Width = dim.Height; Height = dim.Depth; Depth = dim.Width }
| DWH -> { Width = dim.Depth; Height = dim.Width; Depth = dim.Height }
| DHW -> { Width = dim.Depth; Height = dim.Height; Depth = dim.Width }
| WDH -> { Width = dim.Width; Height = dim.Depth; Depth = dim.Height }
// Helper to calculate the volume of any dimension record
let getVolume (dim: Dimensions) =
dim.Width * dim.Height * dim.Depth
4. Implementation Step 2: 3D Geometry and Intersections
To safely place boxes inside the container, we need to know two things: does the box fit inside the container walls, and does it overlap with any box we have already packed?
We implement an Axis-Aligned Bounding Box (AABB) collision detection algorithm. Since all our boxes are orthogonal (aligned with the X, Y, and Z axes), we do not need complex polygon intersection math. We just check if the projections of two boxes overlap on all three axes simultaneously. If they overlap on X, Y, and Z, it means the boxes are colliding in 3D space.
Code: Geometry.fs
module BinPacking.Geometry
open BinPacking.Domain
// Checks if a placed box fits entirely within the boundaries of the container.
let isWithinContainer (containerDim: Dimensions) (box: PlacedBox) : bool =
// The starting coordinate + the length of the box must be less than or equal to the container limit.
let fitX = box.Position.X + box.ActiveDimensions.Width <= containerDim.Width
let fitY = box.Position.Y + box.ActiveDimensions.Height <= containerDim.Height
let fitZ = box.Position.Z + box.ActiveDimensions.Depth <= containerDim.Depth
fitX && fitY && fitZ
// AABB Collision detection between two placed boxes.
// It returns true if the two boxes intersect each other.
let doIntersect (b1: PlacedBox) (b2: PlacedBox) : bool =
// Check overlap on X axis.
// b1's left edge is less than b2's right edge, AND b1's right edge is greater than b2's left edge.
let overlapX =
b1.Position.X < (b2.Position.X + b2.ActiveDimensions.Width) &&
(b1.Position.X + b1.ActiveDimensions.Width) > b2.Position.X
let overlapY =
b1.Position.Y < (b2.Position.Y + b2.ActiveDimensions.Height) &&
(b1.Position.Y + b1.ActiveDimensions.Height) > b2.Position.Y
let overlapZ =
b1.Position.Z < (b2.Position.Z + b2.ActiveDimensions.Depth) &&
(b1.Position.Z + b1.ActiveDimensions.Depth) > b2.Position.Z
// For a 3D intersection to occur, there must be an overlap on all three axes.
overlapX && overlapY && overlapZ
// Checks if a candidate placement intersects with ANY already placed box.
let hasCollision (placedBoxes: PlacedBox list) (candidate: PlacedBox) : bool =
placedBoxes |> List.exists (fun existingBox -> doIntersect existingBox candidate)
// Validates if a coordinate + rotation is a valid placement for a box.
let isValidPlacement (containerDim: Dimensions) (placedBoxes: PlacedBox list) (candidate: PlacedBox) : bool =
isWithinContainer containerDim candidate && not (hasCollision placedBoxes candidate)
5. Implementation Step 3: Extreme Point Heuristic Core
This module is the geometric heart of the engine. It maintains a list of ExtremePoints.
When we try to pack a box, we take the available points, sort them (we prefer placing boxes as low, as far left, and as far back as possible to keep the packing dense), and test the box at each point with all its allowed rotations.
If the box fits at a point, we add it to the PlacedBox list, remove the used point, and generate up to three new points based on the outer limits of the newly placed box.
Code: ExtremePoint.fs
module BinPacking.ExtremePoint
open BinPacking.Domain
open BinPacking.Geometry
// Generates new extreme points after a box is successfully placed.
// When a box is placed at (X, Y, Z), it creates three new potential corners for future boxes.
let generateNewPoints (placedBox: PlacedBox) : Vector3D list =
let maxX = placedBox.Position.X + placedBox.ActiveDimensions.Width
let maxY = placedBox.Position.Y + placedBox.ActiveDimensions.Height
let maxZ = placedBox.Position.Z + placedBox.ActiveDimensions.Depth
[
// Point along the X axis (Right side of the box)
{ placedBox.Position with X = maxX }
// Point along the Y axis (Top of the box)
{ placedBox.Position with Y = maxY }
// Point along the Z axis (Front of the box)
{ placedBox.Position with Z = maxZ }
]
// Removes duplicate points and points that are hidden inside already placed boxes
let filterPoints (container: Dimensions) (placedBoxes: PlacedBox list) (points: Vector3D list) : Vector3D list =
points
|> List.distinct
|> List.filter (fun p ->
// Filter out points that fall outside the container limits
p.X < container.Width && p.Y < container.Height && p.Z < container.Depth)
|> List.filter (fun p ->
// Filter out points that are strictly inside any placed box
let isInsideAny = placedBoxes |> List.exists (fun box ->
p.X > box.Position.X && p.X < box.Position.X + box.ActiveDimensions.Width &&
p.Y > box.Position.Y && p.Y < box.Position.Y + box.ActiveDimensions.Height &&
p.Z > box.Position.Z && p.Z < box.Position.Z + box.ActiveDimensions.Depth
)
not isInsideAny
)
// Attempts to pack a single box into the container using the current extreme points.
// Returns the PlacedBox and the updated list of extreme points if successful.
let tryPackSingleBox (container: Dimensions) (placedBoxes: PlacedBox list) (points: Vector3D list) (box: Box) (forcedRotation: Rotation option) =
// Sort points: Y (Bottom), then Z (Back), then X (Left) to ensure dense packing.
let sortedPoints =
points
|> List.sortBy (fun p -> (p.Y, p.Z, p.X))
// Determine which rotations to test
let rotationsToTest =
match forcedRotation with
| Some r -> [r] // Used when the Genetic Algorithm dictates the rotation
| None -> box.AllowedRotations
// Find the first combination of point and rotation that is valid
let validPlacement =
sortedPoints
|> List.tryPick (fun point ->
rotationsToTest
|> List.tryPick (fun rot ->
let activeDim = applyRotation box.BaseDimensions rot
let candidate = {
Box = box
Position = point
Rotation = rot
ActiveDimensions = activeDim
}
if isValidPlacement container placedBoxes candidate then
Some (candidate, point)
else
None
)
)
match validPlacement with
| Some (placedBox, usedPoint) ->
// Generate new points
let newPoints = generateNewPoints placedBox
// Remove the used point and add the new ones, then filter
let updatedPoints =
points
|> List.filter (fun p -> p <> usedPoint)
|> List.append newPoints
|> filterPoints container (placedBox :: placedBoxes)
Some (placedBox, updatedPoints)
| None ->
None // Box could not be packed
// Packs a sequence of boxes based on a specific order and set of rotations.
let packSequence (container: Dimensions) (boxes: Box[]) (rotations: Rotation[]) =
// State accumulator for the fold operation
let initialState = ([], [{ X = 0.0; Y = 0.0; Z = 0.0 }]) // (PlacedBoxes, ExtremePoints)
let folder (packedBoxes, currentPoints) i =
let currentBox = boxes.[i]
let currentRotation = rotations.[i]
match tryPackSingleBox container packedBoxes currentPoints currentBox (Some currentRotation) with
| Some (placedBox, newPoints) ->
// Successfully packed, update state
(placedBox :: packedBoxes, newPoints)
| None ->
// Skip box if it doesn't fit in this sequence
(packedBoxes, currentPoints)
let finalPlaced, _ =
seq { 0 .. boxes.Length - 1 }
|> Seq.fold folder initialState
finalPlaced |> List.rev // Return in the order they were packed
6. Implementation Step 4: The Genetic Algorithm (GA)
The Extreme Point Heuristic is deterministic. If you give it the same list of boxes, it will pack them exactly the same way. The problem is that the order in which you feed the boxes dictates the quality of the packing.
We use a Genetic Algorithm to search for the best sequence of boxes.
- Chromosome: Represents a single solution. It contains an array of integers (the order of box IDs) and an array of rotations.
- Fitness: We pack the sequence using our
ExtremePointmodule and calculate the total packed volume. The higher the volume, the better the fitness score. - Crossover: We combine two good sequences to create offspring. Because the sequence must contain every box exactly once, we use Order Crossover (OX1).
- Mutation: Occasionally, we randomly swap two boxes in the sequence to prevent the algorithm from getting stuck in local optima.
F# is exceptionally powerful here. Because our packSequence logic uses immutable data, it is completely thread-safe. We can evaluate an entire population of thousands of chromosomes simultaneously across all CPU cores using Array.Parallel.
Code: GeneticAlgorithm.fs
module BinPacking.GeneticAlgorithm
open BinPacking.Domain
open BinPacking.ExtremePoint
open System
// Represents one potential solution in the population
type Chromosome = {
Sequence: int[] // The order in which to pack the boxes (indices of the original array)
Rotations: Rotation[] // The specific rotation for each box in the sequence
}
let rng = Random()
// Evaluates how good a chromosome is. Returns a float between 0.0 and 1.0 (100% volume utilization).
let calculateFitness (container: Dimensions) (boxes: Box[]) (chromosome: Chromosome) : float =
// Extract the ordered boxes and rotations based on the chromosome's genes
let orderedBoxes = Array.init boxes.Length (fun i -> boxes.[chromosome.Sequence.[i]])
let orderedRotations = chromosome.Rotations
// Try to pack them using the geometric heuristic
let packedBoxes = packSequence container orderedBoxes orderedRotations
// Calculate volume utilization
let packedVolume =
packedBoxes
|> List.sumBy (fun pb -> getVolume pb.ActiveDimensions)
let totalVolume = getVolume container
packedVolume / totalVolume
// Order 1 Crossover (OX1) - Ensures no missing or duplicated indices in the sequence
let crossover (parent1: Chromosome) (parent2: Chromosome) : Chromosome =
let len = parent1.Sequence.Length
let p1 = rng.Next(0, len / 2)
let p2 = rng.Next(len / 2, len)
let childSequence = Array.create len -1
// Copy a random segment from Parent 1 to the Child
for i in p1 .. p2 do
childSequence.[i] <- parent1.Sequence.[i]
// Fill the remaining slots with elements from Parent 2, maintaining their relative order
let mutable p2Index = 0
for i in 0 .. len - 1 do
if childSequence.[i] = -1 then
// Find the next element in Parent 2 that is not already in the child
while Array.contains parent2.Sequence.[p2Index] childSequence do
p2Index <- p2Index + 1
childSequence.[i] <- parent2.Sequence.[p2Index]
// For rotations, we take a simple single-point crossover
let childRotations = Array.create len WHD
for i in 0 .. len - 1 do
if rng.NextDouble() > 0.5 then
childRotations.[i] <- parent1.Rotations.[i]
else
childRotations.[i] <- parent2.Rotations.[i]
{ Sequence = childSequence; Rotations = childRotations }
// Mutation: Randomly swap two genes in the sequence to introduce variety
let mutate (chromosome: Chromosome) (mutationRate: float) : Chromosome =
let len = chromosome.Sequence.Length
let newSequence = Array.copy chromosome.Sequence
let newRotations = Array.copy chromosome.Rotations
if rng.NextDouble() < mutationRate then
let idx1 = rng.Next(len)
let idx2 = rng.Next(len)
// Swap sequence elements
let temp = newSequence.[idx1]
newSequence.[idx1] <- newSequence.[idx2]
newSequence.[idx2] <- temp
if rng.NextDouble() < mutationRate then
let rIdx = rng.Next(len)
// Assign a random base rotation for mutation (simplification)
newRotations.[rIdx] <- WHD
{ Sequence = newSequence; Rotations = newRotations }
// The main Evolution Loop
let runEvolution (container: Dimensions) (boxes: Box[]) (populationSize: int) (generations: int) =
let boxCount = boxes.Length
// Initialize random population
let mutable population =
Array.init populationSize (fun _ ->
let seq = [| 0 .. boxCount - 1 |] |> Array.sortBy (fun _ -> rng.Next())
let rots = Array.init boxCount (fun i -> boxes.[i].AllowedRotations.Head) // Start with default rotation
{ Sequence = seq; Rotations = rots }
)
let mutable bestOverall: (Chromosome * float) option = None
for gen in 1 .. generations do
// Evaluate population in parallel utilizing all CPU cores
let evaluated =
population
|> Array.Parallel.map (fun chrom ->
let fitness = calculateFitness container boxes chrom
(chrom, fitness)
)
|> Array.sortByDescending snd // Sort by fitness (highest first)
let bestInGen = evaluated.[0]
match bestOverall with
| None -> bestOverall <- Some bestInGen
| Some (_, bestFit) ->
if snd bestInGen > bestFit then
bestOverall <- Some bestInGen
// Elitism: Keep top 10% automatically
let eliteCount = populationSize / 10
let elites = evaluated |> Array.take eliteCount |> Array.map fst
// Generate rest of the population
let newPop = Array.create populationSize population.[0]
Array.blit elites 0 newPop 0 eliteCount
for i in eliteCount .. populationSize - 1 do
// Tournament selection (pick random two, take the best)
let p1 = evaluated.[rng.Next(populationSize / 2)].fst // Bias towards top half
let p2 = evaluated.[rng.Next(populationSize / 2)].fst
let child = crossover p1 p2
let mutatedChild = mutate child 0.1 // 10% mutation rate
newPop.[i] <- mutatedChild
population <- newPop
match bestOverall with
| Some (bestChrom, _) ->
let orderedBoxes = Array.init boxCount (fun i -> boxes.[bestChrom.Sequence.[i]])
packSequence container orderedBoxes bestChrom.Rotations
| None -> []
7. Implementation Step 5: Production Web API (Giraffe)
To make this engine useful for the business, it needs to be accessible via an HTTP API. We wrap the F# modules into a lightweight, stateless microservice using Giraffe and ASP.NET Core.
Since the math core is completely stateless, the web API requires no database. It simply receives the payload, computes the algorithm, and returns the result. This makes it perfect for horizontal scaling.
Code: Api.fs and Program.fs
module BinPacking.Api
open Microsoft.AspNetCore.Http
open Giraffe
open BinPacking.Domain
open BinPacking.GeneticAlgorithm
// Data Transfer Objects for JSON parsing
type PackRequest = {
Container: Dimensions
Boxes: Box array
Generations: int
PopulationSize: int
}
type PackedBoxResponse = {
BoxId: string
X: float
Y: float
Z: float
UsedRotation: string
}
type PackResponse = {
UtilizationPercentage: float
PackedBoxes: PackedBoxResponse list
}
// HTTP Handler
let handlePackRequest : HttpHandler =
fun (next: HttpFunc) (ctx: HttpContext) ->
task {
// Deserialize incoming JSON to strictly typed F# records
let! req = ctx.BindJsonAsync<PackRequest>()
// Run the algorithm
let resultBoxes = runEvolution req.Container req.Boxes req.PopulationSize req.Generations
// Calculate final metrics
let packedVol = resultBoxes |> List.sumBy (fun b -> getVolume b.ActiveDimensions)
let util = packedVol / getVolume req.Container
// Map domain model to HTTP response model
let responseItems =
resultBoxes |> List.map (fun pb ->
{
BoxId = pb.Box.Id
X = pb.Position.X
Y = pb.Position.Y
Z = pb.Position.Z
UsedRotation = sprintf "%A" pb.Rotation
})
let response = {
UtilizationPercentage = util * 100.0
PackedBoxes = responseItems
}
// Return JSON response
return! json response next ctx
}
// Routing definition
let webApp =
choose [
POST >=> route "/api/v1/pack" >=> handlePackRequest
GET >=> route "/health" >=> text "Engine is healthy."
]
To run this, the main entry point wire up the ASP.NET Core framework:
module BinPacking.Program
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Hosting
open Microsoft.Extensions.Hosting
open Microsoft.Extensions.DependencyInjection
open Giraffe
open BinPacking.Api
let configureApp (app : IApplicationBuilder) =
app.UseGiraffe webApp
let configureServices (services : IServiceCollection) =
services.AddGiraffe() |> ignore
[<EntryPoint>]
let main _ =
Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(fun webHostBuilder ->
webHostBuilder
.Configure(configureApp)
.ConfigureServices(configureServices)
|> ignore)
.Build()
.Run()
0
8. Deployment and Infrastructure
Because we built this in F# and .NET 8, the resulting binary is highly optimized and cross-platform. To deploy it to a production environment (like Google Cloud Run or AWS App Runner), we use Docker.
We utilize a multi-stage Dockerfile. The SDK image compiles the code, and the runtime image (Alpine Linux) keeps the final image size extremely small (often under 100MB).
# Build Stage
FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build
WORKDIR /src
COPY . .
# Restore dependencies and publish a self-contained release
RUN dotnet publish -c Release -o /app/publish /p:UseAppHost=false
# Runtime Stage
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine AS runtime
WORKDIR /app
COPY --from=build /app/publish .
# Expose standard web port
EXPOSE 8080
ENV ASPNETCORE_URLS=http://+:8080
ENTRYPOINT ["dotnet", "BinPackingEngine.dll"]
By deploying this container to Google Cloud Run, the infrastructure dynamically provisions CPU resources based on incoming warehouse API requests. If there are no packing requests, it scales down to zero, costing the business nothing. When a massive wave of warehouse processing hits, it scales horizontally, allowing hundreds of parallel genetic algorithm computations across different container instances.
9. Production Considerations and Conceptual Simplifications
It is important to note that the code snippets provided in this article are designed to clearly illustrate the architectural concept and the mathematical approach. They serve as an educational blueprint rather than a copy-paste production library.
When transitioning this mathematical core into a real-world warehouse environment, several critical technical refinements and edge cases must be implemented:
- Thread-Safe Randomness (Race Conditions): In the Genetic Algorithm, evaluating fitness and applying mutations happen in parallel across multiple threads (using
Array.Parallel.map). Using a standard, sharedSystem.Random()instance is not thread-safe. It will cause race conditions, leading to identical number generation and population degradation. A production implementation must useSystem.Random.Shared(available in modern .NET) or strict thread-local random generators. - Maintaining Sequence Integrity (Box Duplication): A naive implementation of the mutation operator might simply replace a gene at a random index. In the context of physical order fulfillment, this is a critical bug: it duplicates one box and deletes another from the order. Mutation must strictly use a “Swap” operation to ensure the exact set of required SKUs remains intact.
- Absolute Boundary Validations: While the core Extreme Point heuristic focuses heavily on preventing collisions between boxes (AABB intersection), the engine must also rigidly enforce the outer boundaries. If a box is placed on top of a stack, the algorithm must verify that its top edge does not protrude beyond the maximum height of the truck trailer or shipping container.
- Business and Physical Constraints: Mathematically, a 3D box has six possible rotational states. However, real-world physics dictate otherwise. Liquids cannot be placed on their sides, and heavy engine parts cannot be stacked on fragile goods. A production engine must actively filter the
AllowedRotationslist based on strict business flags (e.g., “Upright Only”) before any geometry is calculated.
Conclusion
Transitioning from simple heuristic packing algorithms to an advanced 3D Bin Packing engine is a massive leap forward for logistics businesses facing modern ecological and financial pressures.
By leveraging the type safety and functional paradigms of F#, we built an architecture that is not only mathematically robust but also fiercely fast thanks to zero-cost parallelism during the Genetic Algorithm’s evolutionary cycle. While the conceptual logic requires further tuning for edge cases, this approach directly translates raw dimensions into precise 3D placement coordinates, ensuring that containers leave the facility packed to their absolute physical limits.
