# Conceptual Model — Synthetic Mine Throughput
A discrete-event simulation (DES) of ore haulage in a synthetic open-pit mine,
built in SimPy. The model estimates ore tonnage delivered to the primary
crusher over an 8-hour shift and the queueing behaviour of the haulage system.
This document is the conceptual model: it states *what* is modelled and *why*,
independently of the code. The implementation lives in `mine_sim.py`
(model) and `run_experiment.py` (experiment harness).
---
## 1. System boundary
**Purpose / question.** How much ore reaches the primary crusher (`CRUSH`)
during one 8-hour shift, where are the bottlenecks, and how does throughput
respond to fleet size, a ramp upgrade, a ramp closure, and a slower crusher?
**Included in the model**
- The directed road network from `nodes.csv` / `edges.csv` (14 nodes, 34
directed edges) and travel along it as time-consuming events.
- The haulage fleet (trucks) as active entities that cycle
load → haul → dump → return.
- The two ore loaders (`L_N` at the North face, `L_S` at the South face) as
single-server resources.
- The primary crusher (`D_CRUSH` at node `CRUSH`) as the ore dump, a
single-server resource.
- Capacity-constrained road segments (capacity < 999 in the data) as shared
resources: the narrow ramp (`E03_*`), the crusher approach (`E05_*`) and the
two single-lane pit-access roads (`E07_*`, `E09_*`).
- Stochastic load, dump and travel times.
**Excluded (out of boundary)**
- Waste mining and the waste dump (`WASTE` / `D_WASTE`). The production config
sends ore only to `CRUSH`; the waste route exists in the graph but is never a
destination, so no waste cycle is modelled.
- The maintenance bay (`MAINT`): availability is 1.0 for all equipment in the
data, so no breakdowns, refuelling or scheduled maintenance are modelled.
- Crusher *downstream* processing (stockpile, plant): the crusher is modelled
only as a dump server with a service time.
- Shift changes, breaks, blasting windows, weather and operator skill.
- Grade/rimpull physics: edge speed limits are taken directly from the data.
---
## 2. Entities (things that move through the system)
| Entity | Count | Attributes (from data) |
|---|---|---|
| Truck | 4 / 8 / 12 by scenario | `payload_tonnes` = 100, `empty_speed_factor` = 1.00, `loaded_speed_factor` = 0.85, `start_node` = `PARK` |
Ore is **not** modelled as a separate entity; each completed dump moves a fixed
truck payload (100 t) to the crusher, so tonnage is a counter incremented on
dump completion. Trucks are the only active SimPy processes.
---
## 3. Resources (things that constrain the system)
| Resource | Servers (capacity) | Service / hold time | Source |
|---|---|---|---|
| Loader `L_N` | 1 | load ~N(6.5, 1.2) min | `loaders.csv` |
| Loader `L_S` | 1 | load ~N(4.5, 1.0) min | `loaders.csv` |
| Crusher `D_CRUSH` | 1 | dump ~N(3.5, 0.8) min | `dump_points.csv` |
| Ramp `E03_UP`, `E03_DOWN` | 1 each | edge travel time | `edges.csv` (`capacity = 1`) |
| Crusher approach `E05_TO_CRUSH`, `E05_FROM_CRUSH` | 1 each | edge travel time | `edges.csv` (`capacity = 1`) |
| Pit road `E07_*` (North), `E09_*` (South) | 1 each | edge travel time | `edges.csv` (`capacity = 1`) |
Edges with `capacity ≥ 999` are treated as unconstrained (free-flowing) and
incur only a travel delay, not a resource request. A truck **holds** a
constrained edge resource for the duration of its traversal, so only one truck
occupies a single-lane segment at a time.
---
## 4. Events
The per-truck cycle generates the following discrete events (all logged for
replication 0 of each scenario in `event_log.csv`):
1. `dispatch` — truck is assigned to a loader (start of shift, or after a dump).
2. `enter_edge` — truck begins traversing a road segment (one per edge on the
shortest-time route; records `from_node`/`to_node` and, for constrained
edges, the queue length).
3. `queue_loader` — truck joins the loader queue (records queue length).
4. `load_start` / `load_end` — loading begins / ends (payload acquired).
5. `enter_edge` (loaded) — loaded haul toward the crusher.
6. `queue_crusher` — truck joins the crusher dump queue (records queue length).
7. `dump_start` / `dump_end` — dumping begins / ends. **Tonnage is recorded on
`dump_end`** (a completed dump), which is the throughput measure.
The empty return to the next loader is the first leg of the next `dispatch`,
so the cycle repeats until the shift clock (`env.run(until = 480 min)`) stops
the simulation. Activities still in progress at the clock boundary are not
counted, so only completed dumps contribute tonnage.
---
## 5. State variables
**Per truck:** current node/location; loaded vs empty; cumulative travel, load
and dump time; cumulative time queued at loaders and at the crusher; timestamps
of successive loadings (for cycle-time calculation).
**Per resource:** busy time (for utilisation); queue length (instantaneous) and
queue waiting time (per request).
**System:** simulation clock; total tonnes delivered; number of completed
dumps; committed-assignment counts per loader (used by the dispatcher).
---
## 6. Assumptions
### 6a. Derived from the data (facts the data dictates)
- Two ore faces (`LOAD_N`, `LOAD_S`); one ore dump (`CRUSH`). Baseline fleet is
8 trucks; sensitivity runs use 4 and 12.
- Payload 100 t; loaded trucks travel at 0.85× the posted edge speed, empty at
1.00×.
- Loader, crusher and edge capacities and service-time distributions are taken
verbatim from the CSVs; scenario YAMLs override them via `*_overrides`.
- The ramp `E03`, crusher approach `E05`, and pit roads `E07`/`E09` are the only
capacity-1 (single-lane) segments; everything else has capacity 999.
- **Topology fact (important):** on the supplied graph the shortest-time loaded
haul from either pit to the crusher (`LOAD_N → J5 → J3 → J4 → CRUSH`,
`LOAD_S → J6 → J4 → CRUSH`) does **not** use the ramp. The ramp connects the
parking/gate area to the production area, so it is traversed only when a truck
first positions itself at the South pit (the North pit is reached faster via
the bypass even in the baseline). This is verifiable from `edges.csv` and is
the reason the ramp is a *start-of-shift* constraint, not a per-cycle one.
### 6b. Introduced by the modeller (choices not dictated by the data)
- **Trucks start empty at `PARK`** and are dispatched to a loader; they then
cycle loader↔crusher and are **not** returned to `PARK` between cycles.
- **Routing:** shortest-*time* path (edge weight = distance ÷ max speed), per
the baseline `routing.objective`. A single graph serves loaded and empty
routing because the truck speed factor is a uniform multiplier.
- **Dispatch:** nearest-available-loader. Each loader is scored by the estimated
time until *this* truck would start loading = travel time + (committed trucks
ahead × mean load time); ties and the documented secondary objective are
broken by shortest expected full cycle time. A committed-assignment counter
prevents all trucks herding onto the nominally nearest loader.
- **Stochasticity:** load and dump times are truncated normal (resampled to stay
positive). Each edge traversal time is multiplied by independent log-normal
noise with mean 1 and CV 0.10 (`travel_time_noise_cv`).
- **Bidirectional single-lane roads** are modelled as two independent one-way
capacity-1 resources (matching the data's separate-edge representation).
- **No warm-up** (`warmup_minutes = 0` in every scenario): the whole shift,
including the start-up transient, is measured, which matches a whole-shift
tonnage question. The start-up transient is visible in the event log.
- **Truck utilisation** counts only productive time (travel + load + dump);
time queued at a resource is treated as non-productive.
### 6c. Limitations
See `summary.json → model_limitations`. In brief: the ramp is off the loaded
haul cycle on this topology (so ramp scenarios move throughput only a few
percent); two-way roads are modelled as independent one-way lanes; there is no
grade/rimpull haul-physics engine; loaders/crusher are simple single servers
with no spotting time, breaks or breakdowns.
---
## 7. Performance measures
Computed per replication, then aggregated across replications with a 95%
Student-t confidence interval (n = 30):
| Measure | Definition |
|---|---|
| `total_tonnes_delivered` | payload × completed dumps at `CRUSH` within the shift |
| `tonnes_per_hour` | total tonnes ÷ measured hours (8) |
| `average_truck_cycle_time_min` | mean time between successive loadings of a truck |
| `average_truck_utilisation` | mean over trucks of (travel + load + dump) ÷ shift |
| `crusher_utilisation` | crusher busy time ÷ (shift × crusher capacity) |
| `loader_utilisation` (per loader) | loader busy time ÷ (shift × loader capacity) |
| road-segment utilisation | constrained-edge busy time ÷ (shift × edge capacity) |
| `average_loader_queue_time_min` | mean wait in the loader queue per load |
| `average_crusher_queue_time_min` | mean wait in the crusher queue per dump |
| `top_bottlenecks` | resources ranked by mean utilisation, with mean queue time |
Throughput is therefore an *emergent* result of the simulated load–haul–dump–
return cycle and resource contention, never a static or closed-form
calculation.
conceptual_model.md
← Back to submission · View raw on GitHub