forked from tmbb/dantzig
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtutorial_examples.exs
More file actions
397 lines (333 loc) · 14.9 KB
/
Copy pathtutorial_examples.exs
File metadata and controls
397 lines (333 loc) · 14.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
# Comprehensive Tutorial Examples for Modern Dantzig DSL
# ==========================================================
#
# This tutorial demonstrates all the features of the modern Dantzig DSL system
# through practical examples of classic optimization problems.
#
# Business Context
# ----------------
# Optimization problems are everywhere in business and engineering:
#
# - **Resource Allocation**: Assigning limited resources efficiently
# - **Scheduling**: Finding optimal time assignments for tasks
# - **Routing**: Determining best paths through networks
# - **Assignment**: Matching entities under constraints
# - **Facility Location**: Deciding where to place resources
#
# Mathematical Foundation
# -----------------------
# All optimization problems share the same structure:
#
# minimize/maximize: f(x₁, x₂, ..., xₙ)
# subject to: gᵢ(x) ≤ bᵢ for i = 1..m
# hⱼ(x) = cⱼ for j = 1..p
# xₖ ∈ {0,1} or ℝ or ℤ for each variable
#
# DSL Syntax Overview
# --------------------
#
# **Problem Definition:**
# Problem.define do
# new(name: "Problem", direction: :minimize)
# variables("x", [i <- 1..n], :binary, "Description")
# constraints([i <- 1..n], expression, "Description")
# objective(expression, direction: :minimize)
# end
#
# **Variable Patterns:**
# - Single index: x(i)
# - Multi-index: x(i, j, k)
# - Aggregations: x(i, :_) sums over second index
# - Generators: [i <- 1..n, j <- list]
#
# **Constraint Types:**
# - Equality: x + y == 1
# - Inequality: x + y <= 10
# - Aggregations: sum(x(:_)) <= 5
#
# Common Gotchas
# --------------
# 1. **Generator Scope**: Variables in generators are only available within that constraint
# 2. **Wildcard Order**: x(i, :_) ≠ x(:_, i) - order matters for multi-dimensional variables
# 3. **Variable Types**: Choose :binary for yes/no, :continuous for real numbers, :integer for whole numbers
# 4. **Performance**: Large binary problems can be computationally expensive
#
# Learning Objectives
# -------------------
# After this tutorial, you will understand:
# - How to define optimization problems using the DSL
# - Variable creation with generators and patterns
# - Constraint formulation with mathematical expressions
# - Multi-dimensional variable handling
# - Aggregation operations (sum, max, min)
# - Real-world problem modeling techniques
require Dantzig.Problem, as: Problem
require Dantzig.Problem.DSL, as: DSL
IO.puts("=== DANTZIG DSL COMPREHENSIVE TUTORIAL ===")
IO.puts("This tutorial covers all major DSL features through practical examples")
IO.puts("")
# ============================================================================
# EXAMPLE 1: N-QUEENS PROBLEM - Constraint Satisfaction
# ============================================================================
#
# Business Context: Resource placement and conflict avoidance
# - Applications: Meeting room scheduling, exam timetabling, employee shift assignment
# - Challenge: Ensure no two resources occupy the same "row" or "column" simultaneously
#
# Mathematical Formulation:
# - Variables: x[i,j] ∈ {0,1} for i,j ∈ {1,2,3,4}
# - Constraints:
# - Row uniqueness: Σⱼ x[i,j] = 1 ∀i (exactly one queen per row)
# - Column uniqueness: Σᵢ x[i,j] = 1 ∀j (exactly one queen per column)
# - Note: Diagonal constraints omitted for simplicity in this tutorial
#
# DSL Learning Points:
# - Multi-dimensional variable creation with nested generators
# - Sum aggregation with wildcards (:_)
# - Pattern-based constraint generation
IO.puts("1. N-QUEENS PROBLEM - Constraint Satisfaction")
IO.puts("=============================================")
IO.puts("Place 4 queens on a 4×4 chessboard so no two queens threaten each other.")
IO.puts("This demonstrates resource allocation with mutual exclusion constraints.")
IO.puts("")
# Create the optimization problem using DSL syntax
problem =
Problem.define do
new(name: "N-Queens Tutorial", direction: :minimize)
# Variables: x[i,j] = 1 if queen is placed at position (i,j)
# Generator [i <- 1..4, j <- 1..4] creates 4×4 = 16 binary variables
# Type :binary means each variable can only be 0 or 1
variables("x", [i <- 1..4, j <- 1..4], :binary, "Queen position")
# Constraint: exactly one queen per row
# For each row i, sum x[i,j] over all columns j must equal 1
# Pattern x(i, :_) means "sum over all values of second index"
constraints([i <- 1..4], sum(x(i, :_)) == 1, "One queen per row")
# Constraint: exactly one queen per column
# For each column j, sum x[i,j] over all rows i must equal 1
# Pattern x(:_, j) means "sum over all values of first index"
constraints([j <- 1..4], sum(x(:_, j)) == 1, "One queen per column")
end
# Check variables created
var_map = Problem.get_variables_nd(problem, "x")
IO.puts("✓ Created #{map_size(var_map)} variables (4×4 = 16)")
IO.puts("✓ Added constraints: #{map_size(problem.constraints)} total constraints")
IO.puts("")
# ============================================================================
# EXAMPLE 2: TRAVELING SALESMAN PROBLEM - Network Routing
# ============================================================================
#
# Business Context: Route optimization and logistics
# - Applications: Delivery routing, circuit board design, DNA sequencing
# - Challenge: Find optimal path through network with visit constraints
#
# Mathematical Formulation:
# - Variables: x[i,j] ∈ {0,1} for i,j ∈ cities (1 if edge used in tour)
# - Constraints:
# - Degree constraints: Each city has exactly one incoming and one outgoing edge
# - Outgoing: Σⱼ x[i,j] = 1 ∀i (leave each city exactly once)
# - Incoming: Σᵢ x[i,j] = 1 ∀j (arrive at each city exactly once)
# - Objective: minimize ΣᵢΣⱼ c[i,j] * x[i,j] (total distance)
# - Note: This simplified version lacks subtour elimination; real TSP needs more constraints
#
# DSL Learning Points:
# - Using lists as generator domains
# - Bidirectional constraints (outgoing vs incoming)
# - Network flow modeling patterns
IO.puts("2. TRAVELING SALESMAN PROBLEM - Network Routing")
IO.puts("================================================")
IO.puts("Find the shortest route visiting each of 4 cities exactly once.")
IO.puts("This demonstrates routing problems with degree constraints.")
IO.puts("")
# Define the cities to visit (could be city names or coordinates)
cities = [1, 2, 3, 4]
problem2 =
Problem.define do
new(name: "TSP Tutorial", direction: :minimize)
# Variables: x[i,j] = 1 if we travel from city i to city j
# Generator uses a list of cities, creating variables for all pairs
# Note: In practice, you'd exclude self-loops (i != j)
variables("x", [i <- cities, j <- cities], :binary, "Edge used in tour")
# Constraint: each city has exactly one outgoing edge (leave each city once)
# For each starting city i, sum over all destination cities j
constraints([i <- cities], sum(x(i, :_)) == 1, "One outgoing edge per city")
# Constraint: each city has exactly one incoming edge (arrive at each city once)
# For each destination city j, sum over all starting cities i
constraints([j <- cities], sum(x(:_, j)) == 1, "One incoming edge per city")
end
var_map2 = Problem.get_variables_nd(problem2, "x")
IO.puts("✓ Created #{map_size(var_map2)} variables (4×4 = 16)")
IO.puts("✓ Added degree constraints: #{map_size(problem2.constraints)} total constraints")
IO.puts("")
# ============================================================================
# EXAMPLE 3: CLASSROOM TIMETABLING
# ============================================================================
IO.puts("3. CLASSROOM TIMETABLING")
IO.puts("========================")
IO.puts("Schedule courses in time slots and rooms with constraints.")
IO.puts("")
courses = [1, 2, 3]
times = [1, 2, 3, 4]
rooms = [1, 2]
problem3 =
Problem.define do
new(name: "Timetabling", direction: :minimize)
# Variables: x[c,t,r] = 1 if course c is scheduled at time t in room r
variables("x", [c <- courses, t <- times, r <- rooms], :binary, "Course schedule")
# Constraint 1: each course scheduled exactly once
constraints([c <- courses], sum(x(c, :_, :_)) == 1, "Course scheduled once")
# Constraint 2: no room double-booking
constraints([t <- times, r <- rooms], sum(x(:_, t, r)) <= 1, "No room double-booking")
end
var_map3 = Problem.get_variables_nd(problem3, "x")
IO.puts("✓ Created #{map_size(var_map3)} variables (3×4×2 = 24)")
IO.puts("✓ Added scheduling constraints: #{map_size(problem3.constraints)} total constraints")
IO.puts("")
# ============================================================================
# EXAMPLE 4: KNAPSACK PROBLEM
# ============================================================================
IO.puts("4. KNAPSACK PROBLEM")
IO.puts("===================")
IO.puts("Select items to maximize value while staying within weight limit.")
IO.puts("")
items = [1, 2, 3, 4, 5]
problem4 =
Problem.define do
new(name: "Knapsack", direction: :maximize)
# Variables: x[i] = 1 if item i is selected
variables("x", [i <- items], :binary, "Item selected")
# Constraint: weight limit (simplified - using sum over all items)
constraints([], sum(x(:_)) <= 3, "Weight limit")
end
var_map4 = Problem.get_variables_nd(problem4, "x")
IO.puts("✓ Created #{map_size(var_map4)} variables (5 items)")
IO.puts("✓ Added weight constraint: #{map_size(problem4.constraints)} total constraints")
IO.puts("")
# ============================================================================
# EXAMPLE 5: ASSIGNMENT PROBLEM
# ============================================================================
IO.puts("5. ASSIGNMENT PROBLEM")
IO.puts("=====================")
IO.puts("Assign people to tasks with one-to-one matching.")
IO.puts("")
people = [1, 2, 3]
tasks = [1, 2, 3]
problem5 =
Problem.define do
new(name: "Assignment", direction: :minimize)
# Variables: x[i,j] = 1 if person i is assigned to task j
variables("x", [i <- people, j <- tasks], :binary, "Assignment")
# Constraint 1: each person assigned to exactly one task
constraints([i <- people], sum(x(i, :_)) == 1, "Person assigned once")
# Constraint 2: each task assigned to exactly one person
constraints([j <- tasks], sum(x(:_, j)) == 1, "Task assigned once")
end
var_map5 = Problem.get_variables_nd(problem5, "x")
IO.puts("✓ Created #{map_size(var_map5)} variables (3×3 = 9)")
IO.puts("✓ Added assignment constraints: #{map_size(problem5.constraints)} total constraints")
IO.puts("")
# ============================================================================
# EXAMPLE 6: FACILITY LOCATION PROBLEM
# ============================================================================
IO.puts("6. FACILITY LOCATION PROBLEM")
IO.puts("=============================")
IO.puts("Decide which facilities to open and which customers to serve.")
IO.puts("")
facilities = [1, 2, 3]
customers = [1, 2, 3, 4]
problem6 =
Problem.define do
new(name: "Facility Location", direction: :minimize)
# Variables: x[i] = 1 if facility i is opened
variables("x", [i <- facilities], :binary, "Facility opened")
# Variables: y[i,j] = 1 if customer j is served by facility i
variables("y", [i <- facilities, j <- customers], :binary, "Customer served")
# Constraint 1: customer can only be served by open facility
# This would require a more complex constraint: y[i,j] <= x[i]
# For now, we'll add a simpler constraint
constraints([i <- facilities, j <- customers], y(i, j) <= 1, "Service constraint")
# Constraint 2: each customer served by exactly one facility
constraints([j <- customers], sum(y(:_, j)) == 1, "Customer served once")
end
x_map = Problem.get_variables_nd(problem6, "x")
y_map = Problem.get_variables_nd(problem6, "y")
IO.puts(
"✓ Created #{map_size(x_map)} facility variables and #{map_size(y_map)} service variables"
)
IO.puts("✓ Added facility constraints: #{map_size(problem6.constraints)} total constraints")
IO.puts("")
# ============================================================================
# EXAMPLE 7: 3D PROBLEM WITH FILTERS
# ============================================================================
IO.puts("7. 3D PROBLEM WITH FILTERS")
IO.puts("==========================")
IO.puts("Demonstrate multi-dimensional variables with filtering.")
IO.puts("")
# Variables: x[i,j,k] = 1 for valid combinations
# We'll create all combinations and then filter manually
dim1 = [1, 2]
dim2 = [1, 2]
dim3 = [1, 2]
# Create all combinations
all_combinations = for i <- dim1, j <- dim2, k <- dim3, i + j + k <= 4, do: {i, j, k}
IO.puts("✓ Valid combinations: #{inspect(all_combinations)}")
problem7 =
Problem.define do
new(name: "3D Problem", direction: :minimize)
# For this example, we'll use a simpler approach
variables("x", [i <- dim1, j <- dim2, k <- dim3], :binary, "3D variable")
# Constraint: sum over one dimension
constraints([i <- dim1, j <- dim2], sum(x(i, j, :_)) <= 1, "3D constraint")
end
var_map7 = Problem.get_variables_nd(problem7, "x")
IO.puts("✓ Created #{map_size(var_map7)} variables (2×2×2 = 8)")
IO.puts("✓ Added 3D constraints: #{map_size(problem7.constraints)} total constraints")
IO.puts("")
# ============================================================================
# SUMMARY
# ============================================================================
IO.puts("SUMMARY")
IO.puts("=======")
IO.puts(
"✓ N-Queens: " <>
"#{map_size(Problem.get_variables_nd(problem, "x"))} variables, " <>
"#{map_size(problem.constraints)} constraints"
)
IO.puts(
"✓ TSP: " <>
"#{map_size(Problem.get_variables_nd(problem2, "x"))} variables, " <>
"#{map_size(problem2.constraints)} constraints"
)
IO.puts(
"✓ Timetabling: " <>
"#{map_size(Problem.get_variables_nd(problem3, "x"))} variables, " <>
"#{map_size(problem3.constraints)} constraints"
)
IO.puts(
"✓ Knapsack: " <>
"#{map_size(Problem.get_variables_nd(problem4, "x"))} variables, " <>
"#{map_size(problem4.constraints)} constraints"
)
IO.puts(
"✓ Assignment: " <>
"#{map_size(Problem.get_variables_nd(problem5, "x"))} variables, " <>
"#{map_size(problem5.constraints)} constraints"
)
IO.puts(
"✓ Facility Location: " <>
"#{map_size(Problem.get_variables_nd(problem6, "x")) + map_size(Problem.get_variables_nd(problem6, "y"))} variables, " <>
"#{map_size(problem6.constraints)} constraints"
)
IO.puts(
"✓ 3D Problem: " <>
"#{map_size(Problem.get_variables_nd(problem7, "x"))} variables, " <>
"#{map_size(problem7.constraints)} constraints"
)
IO.puts("")
IO.puts("🎉 All examples completed successfully!")
IO.puts("")
IO.puts("KEY FEATURES DEMONSTRATED:")
IO.puts("• Clean syntax for variable creation with generators")
IO.puts("• Pattern matching for constraint creation (e.g., {i, :_}, {:_, j})")
IO.puts("• Multi-dimensional variables (2D, 3D)")
IO.puts("• Multiple variable sets in the same problem")
IO.puts("• Automatic constraint generation from patterns")