0
# Utility Functions
1
2
Supporting functions for combinatorial operations, data structure manipulation, and system utilities that enhance PuLP's modeling capabilities. These utilities provide essential tools for complex optimization problem formulation and data handling.
3
4
## Capabilities
5
6
### Data Structure Functions
7
8
Functions for creating and manipulating dictionaries and structured data, particularly useful for organizing variables and parameters in large-scale optimization problems.
9
10
```python { .api }
11
def makeDict(headers, array, default=None):
12
"""
13
Create a dictionary from column headers and data array.
14
15
Parameters:
16
- headers (list): List of header names for dictionary keys
17
- array (list of lists): Data rows where each row contains values for headers
18
- default: Default value for missing entries
19
20
Returns:
21
dict: Dictionary with headers as keys and corresponding data as values
22
23
Examples:
24
headers = ['name', 'cost', 'capacity']
25
data = [['plant1', 100, 500], ['plant2', 120, 600]]
26
result = makeDict(headers, data)
27
# Returns: {'name': ['plant1', 'plant2'], 'cost': [100, 120], 'capacity': [500, 600]}
28
"""
29
30
def splitDict(data):
31
"""
32
Split a dictionary containing lists into multiple dictionaries.
33
34
Parameters:
35
- data (dict): Dictionary where values are lists of equal length
36
37
Returns:
38
list: List of dictionaries, one for each index position
39
40
Examples:
41
data = {'name': ['A', 'B'], 'cost': [10, 20], 'capacity': [100, 200]}
42
result = splitDict(data)
43
# Returns: [{'name': 'A', 'cost': 10, 'capacity': 100},
44
# {'name': 'B', 'cost': 20, 'capacity': 200}]
45
"""
46
47
def read_table(data, coerce_type, transpose=False):
48
"""
49
Read table data into dictionary structure with type coercion.
50
51
Parameters:
52
- data: Input data in various formats (list of lists, nested structure)
53
- coerce_type: Function or type to apply to data elements
54
- transpose (bool): Whether to transpose the data structure
55
56
Returns:
57
dict: Processed data dictionary
58
59
Examples:
60
data = [['1', '2'], ['3', '4']]
61
result = read_table(data, int)
62
# Converts string data to integers
63
"""
64
```
65
66
Usage examples:
67
68
```python
69
# Organize problem data
70
facilities = ['F1', 'F2', 'F3']
71
costs = [100, 150, 120]
72
capacities = [500, 600, 400]
73
74
# Create structured data dictionary
75
facility_data = makeDict(['facility', 'cost', 'capacity'],
76
[facilities, costs, capacities])
77
78
# Split into individual facility records
79
facility_records = splitDict(facility_data)
80
for record in facility_records:
81
print(f"Facility {record['facility']}: cost={record['cost']}, capacity={record['capacity']}")
82
83
# Process CSV-like data for optimization
84
raw_data = [
85
['10', '20', '30'],
86
['15', '25', '35'],
87
['12', '22', '32']
88
]
89
cost_matrix = read_table(raw_data, float)
90
91
# Use in variable creation
92
plants = list(facility_data['facility'])
93
production = LpVariable.dicts('prod', plants, 0)
94
95
# Create cost constraints using structured data
96
for i, plant in enumerate(plants):
97
prob += facility_data['cost'][i] * production[plant] <= facility_data['capacity'][i]
98
```
99
100
### Combinatorial Functions
101
102
Functions for generating combinations and permutations, essential for modeling complex assignment and scheduling problems.
103
104
```python { .api }
105
def allcombinations(orgset, k):
106
"""
107
Generate all combinations of elements from orgset with up to k items.
108
109
Parameters:
110
- orgset (iterable): Set of elements to combine
111
- k (int): Maximum number of elements in each combination
112
113
Returns:
114
list: List of all combinations as tuples
115
116
Examples:
117
allcombinations(['A', 'B', 'C'], 2)
118
# Returns: [(), ('A',), ('B',), ('C',), ('A', 'B'), ('A', 'C'), ('B', 'C')]
119
"""
120
121
def allpermutations(orgset, k):
122
"""
123
Generate all permutations of elements from orgset with up to k items.
124
125
Parameters:
126
- orgset (iterable): Set of elements to permute
127
- k (int): Maximum number of elements in each permutation
128
129
Returns:
130
list: List of all permutations as tuples
131
132
Examples:
133
allpermutations(['A', 'B'], 2)
134
# Returns: [(), ('A',), ('B',), ('A', 'B'), ('B', 'A')]
135
"""
136
137
def combination(iterable, r):
138
"""
139
Alias for itertools.combinations - generate r-length combinations.
140
141
Parameters:
142
- iterable: Elements to combine
143
- r (int): Length of each combination
144
145
Returns:
146
itertools.combinations: Iterator of r-length combinations
147
"""
148
149
def permutation(iterable, r):
150
"""
151
Alias for itertools.permutations - generate r-length permutations.
152
153
Parameters:
154
- iterable: Elements to permute
155
- r (int): Length of each permutation
156
157
Returns:
158
itertools.permutations: Iterator of r-length permutations
159
"""
160
```
161
162
Usage examples:
163
164
```python
165
from itertools import combinations, permutations
166
167
# Assignment problem - select teams for projects
168
teams = ['TeamA', 'TeamB', 'TeamC', 'TeamD']
169
projects = ['Proj1', 'Proj2', 'Proj3']
170
171
# All possible assignments of 2 teams to each project
172
team_combinations = list(combination(teams, 2))
173
for combo in team_combinations:
174
for project in projects:
175
# Create binary variable for each team combination-project pair
176
var_name = f"assign_{combo[0]}_{combo[1]}_{project}"
177
assignment_var = LpVariable(var_name, cat=LpBinary)
178
179
# Scheduling problem - all possible orderings
180
tasks = ['TaskA', 'TaskB', 'TaskC']
181
all_sequences = list(permutation(tasks, len(tasks)))
182
183
# Create variables for each possible sequence
184
sequence_vars = {}
185
for i, seq in enumerate(all_sequences):
186
sequence_vars[seq] = LpVariable(f"sequence_{i}", cat=LpBinary)
187
188
# Constraint: exactly one sequence must be selected
189
prob += lpSum(sequence_vars.values()) == 1
190
191
# Resource allocation with combinations
192
resources = ['R1', 'R2', 'R3', 'R4']
193
max_resources_per_task = 2
194
195
# All valid resource combinations for tasks
196
valid_combinations = list(allcombinations(resources, max_resources_per_task))
197
for task in tasks:
198
task_resource_vars = {}
199
for combo in valid_combinations:
200
if combo: # Skip empty combination
201
var_name = f"use_{task}_{'_'.join(combo)}"
202
task_resource_vars[combo] = LpVariable(var_name, cat=LpBinary)
203
204
# Each task must use exactly one resource combination
205
if task_resource_vars:
206
prob += lpSum(task_resource_vars.values()) == 1
207
```
208
209
### Value and Type Checking Functions
210
211
Functions for handling numeric values and type validation in optimization contexts.
212
213
```python { .api }
214
def valueOrDefault(x):
215
"""
216
Get the value of a variable/expression or return a default within bounds.
217
218
Parameters:
219
- x (LpVariable or LpAffineExpression): Object to evaluate
220
221
Returns:
222
float: Value of x, or a default value if not solved
223
224
Note: For variables, returns a value within the variable's bounds.
225
For expressions, returns the constant term or calculated default.
226
"""
227
228
def isNumber(x):
229
"""
230
Check if x is a numeric value (int or float).
231
232
Parameters:
233
- x: Object to check
234
235
Returns:
236
bool: True if x is int or float, False otherwise
237
"""
238
```
239
240
Usage examples:
241
242
```python
243
# Robust value extraction after solving
244
variables = [x, y, z]
245
solution_values = {}
246
247
for var in variables:
248
val = value(var)
249
if val is not None:
250
solution_values[var.name] = val
251
else:
252
# Use default value within bounds
253
solution_values[var.name] = valueOrDefault(var)
254
255
# Type validation for parameters
256
def create_cost_constraint(variables, costs, limit):
257
# Validate that all costs are numeric
258
if not all(isNumber(cost) for cost in costs):
259
raise ValueError("All costs must be numeric")
260
261
# Create constraint
262
total_cost = lpDot(variables, costs)
263
return total_cost <= limit
264
265
# Safe arithmetic operations
266
def safe_multiply(var, coeff):
267
if isNumber(coeff):
268
return coeff * var
269
else:
270
raise TypeError(f"Coefficient must be numeric, got {type(coeff)}")
271
```
272
273
### System Utilities
274
275
Functions for performance monitoring and system resource tracking.
276
277
```python { .api }
278
def resource_clock():
279
"""
280
Return the current resource usage time for performance monitoring.
281
282
Returns:
283
float: Resource time in seconds
284
285
Note: Used for tracking solver performance and optimization runtime.
286
"""
287
```
288
289
Usage examples:
290
291
```python
292
# Performance monitoring for optimization
293
start_time = resource_clock()
294
295
# Build and solve problem
296
prob = LpProblem("Performance_Test", LpMinimize)
297
variables = LpVariable.dicts("x", range(1000), 0, 1)
298
prob += lpSum(variables)
299
300
# Add many constraints
301
for i in range(500):
302
prob += lpSum(variables[j] for j in range(i, min(i+10, 1000))) <= 5
303
304
solve_start = resource_clock()
305
status = prob.solve()
306
solve_time = resource_clock() - solve_start
307
total_time = resource_clock() - start_time
308
309
print(f"Model building time: {solve_start - start_time:.3f} seconds")
310
print(f"Solve time: {solve_time:.3f} seconds")
311
print(f"Total time: {total_time:.3f} seconds")
312
313
# Benchmark different approaches
314
def benchmark_approach(approach_func, *args):
315
start = resource_clock()
316
result = approach_func(*args)
317
elapsed = resource_clock() - start
318
return result, elapsed
319
320
# Compare different formulation methods
321
formulation1_result, time1 = benchmark_approach(create_formulation_v1, data)
322
formulation2_result, time2 = benchmark_approach(create_formulation_v2, data)
323
324
print(f"Formulation 1: {time1:.3f}s")
325
print(f"Formulation 2: {time2:.3f}s")
326
if time1 < time2:
327
print("Formulation 1 is faster")
328
else:
329
print("Formulation 2 is faster")
330
```
331
332
### Advanced Data Processing
333
334
Complex data manipulation patterns for large-scale optimization problems.
335
336
```python
337
# Multi-dimensional data organization
338
def organize_transportation_data(supply_points, demand_points, costs, capacities, demands):
339
"""
340
Organize transportation problem data into structured format.
341
"""
342
# Create cost matrix
343
cost_data = {}
344
for i, supply in enumerate(supply_points):
345
cost_data[supply] = {}
346
for j, demand in enumerate(demand_points):
347
cost_data[supply][demand] = costs[i][j]
348
349
# Combine with capacity and demand data
350
supply_data = dict(zip(supply_points, capacities))
351
demand_data = dict(zip(demand_points, demands))
352
353
return cost_data, supply_data, demand_data
354
355
# Use organized data in optimization
356
cost_matrix, supply_capacity, demand_requirement = organize_transportation_data(
357
['S1', 'S2', 'S3'],
358
['D1', 'D2', 'D3', 'D4'],
359
[[10, 15, 20, 25], [12, 18, 22, 28], [8, 14, 16, 30]],
360
[100, 150, 120],
361
[80, 90, 100, 70]
362
)
363
364
# Create transportation variables and constraints
365
transport_vars = LpVariable.matrix("transport",
366
list(supply_capacity.keys()),
367
list(demand_requirement.keys()), 0)
368
369
# Supply constraints
370
for supply_point in supply_capacity:
371
prob += lpSum([transport_vars[supply_point][demand_point]
372
for demand_point in demand_requirement]) <= supply_capacity[supply_point]
373
374
# Demand constraints
375
for demand_point in demand_requirement:
376
prob += lpSum([transport_vars[supply_point][demand_point]
377
for supply_point in supply_capacity]) >= demand_requirement[demand_point]
378
379
# Objective function using organized cost data
380
total_cost = lpSum([cost_matrix[s][d] * transport_vars[s][d]
381
for s in supply_capacity
382
for d in demand_requirement])
383
prob += total_cost
384
```