Ore zone extraction optimization using VariableDict for named decision variables
Published
May 28, 2026
1 Overview
This example demonstrates VariableDict — a dict-indexed variable collection where decision variables are keyed by descriptive string names rather than integer indices. This is a natural fit for mining problems where ore zones, pits, and stockpiles have names.
We’ll solve a mine production planning problem: choosing how much ore to extract from each zone to maximize profit while meeting mill blend grade targets and throughput constraints.
1.1 What is VariableDict?
VariableDict creates a set of decision variables indexed by string keys, much like a Python dictionary. It’s ideal when your variables correspond to named entities — ore zones, equipment, products — rather than numeric indices.
Key methods demonstrated:
Method
Purpose
VariableDict(name, keys, lb, ub)
Create with per-key bounds
vd['key']
Access individual variable
vd.sum()
Sum all variables
vd.sum(subset)
Sum a subset of variables
vd.prod(coefficients)
Weighted sum (grade blending, costs)
vd.keys(), values(), items()
Dict-like iteration
len(vd), 'key' in vd
Length and membership
solution[vd]
Extract all results as a dict
2 Problem Data
An open-pit mining operation has five ore zones, each with different copper grades, extraction costs, and tonnage limits.
from optyx import VariableDict, Problem# Ore zoneszones = ["North Pit", "South Pit", "East Bench", "West Cutback", "Stockpile"]# Ore grade (% copper) per zonegrade = {"North Pit": 0.85,"South Pit": 0.62,"East Bench": 1.20,"West Cutback": 0.45,"Stockpile": 0.55,}# Extraction cost ($/tonne) per zonecost = {"North Pit": 12.50,"South Pit": 9.80,"East Bench": 18.00,"West Cutback": 8.50,"Stockpile": 5.00,}# Revenue per unit of contained metalcu_price =85.0# $/unit grade-tonne# Maximum extractable tonnage per zone (kt)max_tonnes = {"North Pit": 500,"South Pit": 800,"East Bench": 300,"West Cutback": 600,"Stockpile": 200,}# Mill constraintsmill_capacity =1500# kt total throughputmin_feed_grade =0.60# minimum blend grade (% Cu)max_feed_grade =1.00# maximum blend grade (% Cu)# Zone groupshigh_grade_zones = ["North Pit", "East Bench"]low_grade_zones = ["South Pit", "West Cutback", "Stockpile"]print(f"{'Zone':<18}{'Grade (%Cu)':>12}{'Cost ($/t)':>12}{'Max (kt)':>10}")print("-"*55)for z in zones:print(f"{z:<18}{grade[z]:>12.2f}{cost[z]:>12.2f}{max_tonnes[z]:>10}")print(f"\nMill capacity: {mill_capacity} kt")print(f"Feed grade window: {min_feed_grade}% – {max_feed_grade}% Cu")
Zone Grade (%Cu) Cost ($/t) Max (kt)
-------------------------------------------------------
North Pit 0.85 12.50 500
South Pit 0.62 9.80 800
East Bench 1.20 18.00 300
West Cutback 0.45 8.50 600
Stockpile 0.55 5.00 200
Mill capacity: 1500 kt
Feed grade window: 0.6% – 1.0% Cu
3 Creating a VariableDict
Unlike VectorVariable (indexed by integers), VariableDict uses string keys. Each zone becomes a named decision variable with its own bounds.
We use prod() to compute weighted sums — the grade-weighted revenue and cost-weighted total:
prob = Problem(name="mine_plan")# Revenue coefficients: cu_price × grade per zonerevenue_coeffs = {z: cu_price * grade[z] for z in zones}# prod() computes the weighted sum: Σ coefficient[z] × extract[z]revenue = extract.prod(revenue_coeffs)total_cost = extract.prod(cost)prob.maximize(revenue - total_cost)print("Net profit per tonne by zone:")for z in zones: net = revenue_coeffs[z] - cost[z]print(f" {z:<18} ${net:.2f}/t")
Net profit per tonne by zone:
North Pit $59.75/t
South Pit $42.90/t
East Bench $84.00/t
West Cutback $29.75/t
Stockpile $41.75/t
4.2 Constraint 1: Mill Throughput
sum() returns the sum over all keys — total extraction must fit the mill:
sum(subset) sums only selected keys — useful for grouping zones:
# High-grade zones must contribute at least 300 ktprob.subject_to(extract.sum(high_grade_zones) >=300)# Low-grade zones capped at 60% of total feedprob.subject_to(extract.sum(low_grade_zones) <=0.6* extract.sum())print(f"High-grade zones ({', '.join(high_grade_zones)}): ≥ 300 kt")print(f"Low-grade zones ({', '.join(low_grade_zones)}): ≤ 60% of feed")
High-grade zones (North Pit, East Bench): ≥ 300 kt
Low-grade zones (South Pit, West Cutback, Stockpile): ≤ 60% of feed
4.5 Constraint 6: Minimum Extraction
Use items() to iterate over (key, variable) pairs and add per-zone constraints:
min_extract =50# kt minimum per zonefor key, var in extract.items(): prob.subject_to(var >= min_extract)print(f"Minimum {min_extract} kt per zone (equipment utilization)")
print(f"\n{'Zone':<18}{'Extract (kt)':>14}{'Grade':>8}{'Revenue ($k)':>14}{'Cost ($k)':>12}")print("-"*70)total_tonnes =0total_metal =0total_revenue =0total_cost_val =0for zone in zones: t = result[zone] metal = t * grade[zone] rev = t * revenue_coeffs[zone] cst = t * cost[zone] total_tonnes += t total_metal += metal total_revenue += rev total_cost_val += cstprint(f"{zone:<18}{t:>14.1f}{grade[zone]:>7.2f}% {rev:>14.1f}{cst:>12.1f}")print("-"*70)blend_grade = total_metal / total_tonnes if total_tonnes >0else0profit = total_revenue - total_cost_valprint(f"{'TOTAL':<18}{total_tonnes:>14.1f}{blend_grade:>7.2f}% {total_revenue:>14.1f}{total_cost_val:>12.1f}")print(f"\nProfit: ${profit:,.0f}k")
Zone Extract (kt) Grade Revenue ($k) Cost ($k)
----------------------------------------------------------------------
North Pit 500.0 0.85% 36125.0 6250.0
South Pit 600.0 0.62% 31620.0 5880.0
East Bench 300.0 1.20% 30600.0 5400.0
West Cutback 50.0 0.45% 1912.5 425.0
Stockpile 50.0 0.55% 2337.5 250.0
----------------------------------------------------------------------
TOTAL 1500.0 0.80% 102595.0 18205.0
Profit: $84,390k
6.3 Key Metrics
print(f"Blend grade: {blend_grade:.3f}% Cu (target: {min_feed_grade}–{max_feed_grade}%)")print(f"Mill utilization: {total_tonnes / mill_capacity *100:.1f}%")hg_total =sum(result[z] for z in high_grade_zones)lg_total =sum(result[z] for z in low_grade_zones)print(f"High-grade zones: {hg_total:.0f} kt ({hg_total / total_tonnes *100:.0f}%)")print(f"Low-grade zones: {lg_total:.0f} kt ({lg_total / total_tonnes *100:.0f}%)")
VariableDict provides several introspection methods:
# get_variables() — list of underlying Variable objectsall_vars = extract.get_variables()print(f"get_variables(): {len(all_vars)} Variable objects")# values() — variables in key order (like dict.values())print(f"values(): {[v.name for v in extract.values()]}")# keys() — list of keysprint(f"keys(): {extract.keys()}")
Use VectorVariable when you have large, uniform arrays (1000+ variables). Use VariableDict when your variables represent named entities and readability matters.
9 Summary
VariableDict makes optimization models more readable by using descriptive string keys:
prod(coefficients) handles weighted sums for grade blending and cost calculations
sum(subset) enables constraints on zone groups without manual bookkeeping
items() provides natural iteration for per-entity constraints
solution[vd] extracts all results as a ready-to-use dictionary