# Conceptual Model — Synthetic Mine Ore Haulage (Benchmark 001)
A discrete-event simulation (SimPy) of an 8-hour ore haulage shift in a
synthetic open-pit mine. It estimates ore throughput to the primary crusher
and quantifies the effect of fleet size, ramp capacity, and crusher service
time. This document is the design record; numeric results live in
`summary.json` / `results.csv` and are discussed in `README.md`.
---
## 1. System boundary
### Included
- The ore-haulage cycle from the truck park (`PARK`) to the two ore faces
(`LOAD_N`, `LOAD_S`) and to the primary crusher (`CRUSH`).
- The directed road graph from `nodes.csv` / `edges.csv`, including the main
ramp and the western/eastern bypass routes.
- The two loaders (`L_N`, `L_S`) and the crusher (`D_CRUSH`) as service
resources.
- The eight single-lane (capacity-1) road segments as shared resources.
- Stochastic travel, loading, and dumping times.
### Excluded (out of scope)
- **Waste haulage and the waste dump (`WASTE` / `D_WASTE`).** The task is ore
to the crusher; waste edges remain in the graph for completeness but carry
no traffic.
- **Maintenance / refuelling (`MAINT`).** No truck ever visits the
maintenance bay; trucks are available for the whole shift.
- **Truck breakdowns and availability < 1.0.** `trucks.csv` lists
`availability = 1.00`; we honour that, so the estimate is an upper bound on
a fully-available fleet.
- **Operator behaviour:** shift handover, breaks, and manual dispatcher
overrides are not modelled.
- **Downstream of the crusher:** no stockpile back-pressure or full-bin
blocking; the crusher is always ready to receive.
---
## 2. Entities
| Entity | Role | Source |
|---|---|---|
| **Truck** | The only active (moving) entity. One SimPy *process* per truck runs the haul cycle: choose loader → travel empty → load → travel loaded → dump → repeat. | `trucks.csv` (T01–T12; first *N* used per scenario) |
| **Ore payload** | Not a separate process. Carried implicitly by a loaded truck and credited as `payload_tonnes` (100 t) at each completed dump. | `trucks.csv` |
All trucks are homogeneous: 100 t payload, empty speed factor 1.00, loaded
speed factor 0.85, and they all start at `PARK`.
---
## 3. Resources (what constrains the system)
Every constrained resource is a SimPy `Resource` with capacity 1 (one truck
served at a time, others queue FIFO).
| Resource | Count | Service parameters |
|---|---|---|
| **Loaders** `L_N`, `L_S` | 2 | truncated-normal load time; `L_N` 6.5 / 1.2 min, `L_S` 4.5 / 1.0 min |
| **Crusher** `D_CRUSH` | 1 | truncated-normal dump time 3.5 / 0.8 min (7.0 / 1.5 in `crusher_slowdown`) |
| **Capacity-1 edges** | 8 | one resource per *directed* single-lane segment |
The eight capacity-1 edges (the only `capacity = 1` rows in `edges.csv`) are:
- `E03_UP`, `E03_DOWN` — the narrow main ramp (J2 ↔ J3), the "intended
transport bottleneck".
- `E05_TO_CRUSH`, `E05_FROM_CRUSH` — the crusher approach road (J4 ↔ CRUSH).
- `E07_TO_LOAD_N`, `E07_FROM_LOAD_N` — the North pit-face road (J5 ↔ LOAD_N).
- `E09_TO_LOAD_S`, `E09_FROM_LOAD_S` — the South pit-face road (J6 ↔ LOAD_S).
Each physical direction is modelled as its **own** capacity-1 resource (e.g.
`E03_UP` and `E03_DOWN` are independent), so opposing trucks do not contend
for a single shared lane. This is a documented simplification (see §6).
All other edges have `capacity = 999` and are modelled as plain time delays
(no resource contention).
---
## 4. Events
The per-truck cycle generates these events (all recorded to `event_log.csv`):
1. `dispatch` — truck released at `PARK` at t = 0.
2. `edge_enter` / `edge_leave` — acquiring / releasing each capacity-1 edge
along a route (brackets the time the truck holds the single lane).
3. `arrive_loader` — truck reaches the chosen loading face and joins its queue.
4. `start_load` / `end_load` — loader service begins / ends.
5. `depart_loader` — truck leaves loaded.
6. `arrive_crusher` — truck reaches the crusher and joins its queue.
7. `start_dump` / `end_dump` — crusher service begins / ends. **Tonnes are
credited at `end_dump`** (and only if it closes before the shift cut).
8. `depart_crusher` — truck leaves empty; the loop repeats.
Free-flow (capacity-999) edges advance time with a plain `timeout` and emit no
edge events, keeping the log focused on the constrained segments.
---
## 5. State variables
**Tracked during the run:**
- Truck location and loaded/empty status (implicit in the process position).
- Queue length at each loader, the crusher, and each capacity-1 edge
(`count + len(queue)`), sampled into the event log.
- Resource busy time (loaders, crusher, edges) for utilisation.
- Per-truck productive time (sum of completed wait + service + travel phases).
- Per-truck completed cycle count and total cycle time.
- Completed dumps and cumulative tonnes.
**Derived at end-of-shift** (see §7).
---
## 6. Assumptions
### Derived from the data
- Eight `capacity = 1` edges become single-lane resources; everything with
`capacity = 999` is unconstrained. (From `edges.csv`.)
- Loader and crusher mean/sd service times. (From `loaders.csv`,
`dump_points.csv`.)
- Homogeneous 100 t fleet, speed factors 1.00 / 0.85, all starting at `PARK`,
`availability = 1.00`. (From `trucks.csv`.)
- Edge free-flow time = `distance_m / (max_speed_kph × 1000 / 60)`. (From
`edges.csv` geometry.)
- Scenario overrides (fleet size, ramp capacity/speed, ramp closure, crusher
slowdown) come straight from the scenario YAML inheritance chain.
### Introduced (modelling choices not dictated by the data)
- **Hard shift cut** at t = 480 min via `env.run(until=480)`. Only dumps that
close strictly before 480 count — "tonnes closed at shift end".
- **Routing:** static shortest-*time* routing, computed once per scenario by
Dijkstra on free-flow edge times and recomputed when a scenario closes or
upgrades edges. A truck commits to its path at dispatch.
- **Dispatch (dynamic loader choice):** an empty truck picks the loader that
minimises `travel_to_loader + queue_len × mean_load_time + own_mean_load`,
where `queue_len` counts trucks in service plus waiting; ties break by lower
`loader_id`. The route is static, the loader choice is dynamic.
- **Stochasticity:** per-edge-traversal lognormal travel multiplier (mean 1,
cv = 0.10); truncated-normal load/dump times floored at `max(0.1, sample)`.
- **All trucks released simultaneously at t = 0**; no warm-up.
- **Reproducibility:** per-replication seed = `12345 + replication_index`,
with independent RNG streams per stochastic source.
### Limitations
- **Separate ramp directions.** `E03_UP` and `E03_DOWN` are two independent
single-lane resources. A genuinely shared single lane would congest worse.
- **Static routing.** Trucks do not re-route around a queue that builds on a
capacity-1 edge, so single-lane queueing is an upper bound; a smarter
dispatcher could divert via the bypass.
- **Boundary under-count.** Productive time and tonnes accrue only on
*completed* phases, so a phase straddling t = 480 contributes nothing —
utilisation and the final partial cycle are slightly under-counted.
- **Crusher never blocks downstream;** no stockpile/bin back-pressure.
- **Homogeneous payload;** no ore blending or grade-dependent processing.
- **Free-flow edges have unlimited capacity;** no headway/following effects on
multi-lane haul roads.
- **No warm-up trimming;** the empty-system start is a small (non-zero) bias.
- Node coordinates are used for visualisation only; road grade/bends beyond
`distance_m` and `max_speed_kph` are not modelled.
---
## 7. Performance measures
Per replication (see `metrics.py`), then aggregated across 30 replications
with a Student-t (n − 1 = 29) 95% confidence interval (see `aggregate.py`):
| Measure | Definition |
|---|---|
| `total_tonnes_delivered` | `payload ×` completed dumps before the cut |
| `tonnes_per_hour` | `total_tonnes_delivered / 8` |
| `average_truck_cycle_time_min` | mean completed-cycle duration (first cycle `dispatch → end_dump`, then `end_dump → end_dump`) |
| `average_truck_utilisation` | mean over trucks of productive time / 480 |
| `crusher_utilisation`, `loader_utilisation` | busy time / 480 |
| `average_loader_queue_time_min`, `average_crusher_queue_time_min` | mean wait per service |
| `top_bottlenecks` | loaders + crusher + capacity-1 edges ranked by the composite score **utilisation × mean queue wait** (top 5) |
The composite bottleneck score deliberately combines *how busy* a resource is
with *how long trucks wait for it*, so a resource that is occasionally used but
causes long waits (the ramp) is separated from one that is the true throughput
ceiling (the crusher). See `README.md` for the interpretation.
conceptual_model.md
← Back to submission · View raw on GitHub