-
Notifications
You must be signed in to change notification settings - Fork 1
/
throw_away.fsx
234 lines (190 loc) · 6.38 KB
/
throw_away.fsx
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
fsi.ShowDeclarationValues <- false
// The Domain
[<RequireQualifiedAccess>]
type JobType =
| A
| B
type Job = {
Id : int
JobType : JobType
Size : float
} with
override this.ToString () =
$"Job_{this.Id}"
type Machine = {
Id : int
JobTypes : Set<JobType>
} with
override this.ToString () =
$"Machine_{this.Id}"
// Set of JobTypes for iterating over and sampling from
let jobTypes =
[|
JobType.A
JobType.B
|]
// Some theoretical JobTypeSets to be used in generating
// random Machines
let jobTypeSets =
[|
Set jobTypes
Set jobTypes.[..0]
Set jobTypes.[1..]
|]
// Setting up parameters for the example
let rng = System.Random(123)
let numberOfJobs = 5_000
let numberOfMachines = 70
let minJobSize = 1
let maxJobSize = 3
let possibleJobSizes =
[|
15.0
30.0
60.0
|]
let maxWorkDifference = 240.0
let randomJobSize (rng: System.Random) =
// rng.Next(minJobSize, maxJobSize)
// |> float
possibleJobSizes.[rng.Next(0, 2)]
let randomJobType (rng: System.Random) =
jobTypes.[rng.Next(0, jobTypes.Length - 1)]
let randomJobTypeSet (rng: System.Random) =
jobTypeSets.[rng.Next(0, jobTypeSets.Length - 1)]
module Map =
// Useful when you want to look up a key in a Map but you want it to provide
// a default value if the key is missing
let tryFindDefault (key: 'a) (defaultValue: 'v) (m: Map<'a, 'v>) =
match Map.tryFind key m with
| Some v -> v
| None -> defaultValue
// Create some examples jobs
let jobs =
[1..numberOfJobs]
|> List.map (fun id -> {
Id = id
JobType = randomJobType rng
Size = randomJobSize rng
})
// Create some test machines
let machines =
[1..numberOfMachines]
|> List.map (fun id -> {
Id = id
JobTypes = randomJobTypeSet rng
})
#r "nuget: Flips"
open Flips
open Flips.Types
open Flips.SliceMap
// A Map from JobType to the Jobs which are of that type
let jobsForJobType =
jobs
|> List.groupBy (fun job -> job.JobType)
|> Map
// A SliceMap where the key is a Job and the value is the length of the Job
let jobSizes =
jobs
|> List.map (fun job -> job, job.Size)
|> SMap
// The Decisions which represent assigning a Job to a Machine
// The JobType index allows us to slice along the job type
// which is useful in some of the constraints
let assignments =
DecisionBuilder "Assignment" {
for machine in machines do
for jobType in jobTypes do
for job in Map.tryFindDefault jobType [] jobsForJobType ->
Boolean
} |> SMap3
// Each job must be assigned
let jobsAssignmentConstraints =
ConstraintBuilder "JobAssignment" {
for job in jobs ->
sum assignments.[All, All, job] == 1.0
}
// A Decision which is meant to represent the MaxWork value across all Machines
let maxWork = Decision.createContinuous "MaxWork" 0.0 infinity
// A Decision which is meant to represent the MinWork value across all Machines
let minWork = Decision.createContinuous "MinWork" 0.0 infinity
// We constrain the difference between the most heavily loaded machine
// and the least loaded
let maxWorkDifferenceConstraint =
Constraint.create "MaxWorkDifferent" (maxWork - minWork <== maxWorkDifference)
// The maxWork Decision must be greater or equal to all of the total work
// for each Machine
let maxWorkConstraints =
ConstraintBuilder "MaxWork" {
for machine in machines ->
maxWork >== sum (assignments.[machine, All, All] .* jobSizes)
}
// The minWork Decision must be less or equal to all of the total work
// for each Machine
let minWorkConstraints =
ConstraintBuilder "MinWork" {
for machine in machines ->
minWork <== sum (assignments.[machine, All, All] .* jobSizes)
}
// A Decision which indicates whether we setup a given Machine for a
// JobType at any point
let setups =
DecisionBuilder "Setups" {
for machine in machines do
for jobType in jobTypes ->
Boolean
} |> SMap2
// We must turn the setups value for a given Machine and JobType to 1
// if we assign a Job of the given JobType to the Machine
let setupConstraints =
ConstraintBuilder "SetupRequired" {
for machine in machines do
for jobType in jobTypes ->
sum (assignments.[machine, jobType, All]) <== (float numberOfJobs) * setups.[machine, jobType]
}
// An expression which is the sum of the Setups that will need to be performed
let numberSetupsExpression = sum setups
// We want to minimize the number of setups
let minSetupsObjective = Objective.create "MinSetups" Minimize numberSetupsExpression
// Compose the model
let model =
Model.create minSetupsObjective
|> Model.addConstraints maxWorkConstraints
|> Model.addConstraints minWorkConstraints
|> Model.addConstraint maxWorkDifferenceConstraint
|> Model.addConstraints setupConstraints
|> Model.addConstraints jobsAssignmentConstraints
// Give the solver plenty of time to find a solution
let settings = { Settings.basic with MaxDuration = 60_000L }
let result = Solver.solve settings model
match result with
| Optimal solution ->
// Get the assignments the solver is suggesting
let machineAssignments =
Solution.getValues solution assignments
|> Map.filter (fun _ v -> v = 1.0)
|> Map.toList
|> List.map (fun ((machine, _, job), _) -> machine, job)
|> List.sortBy (fun (machine, job) -> machine.Id, job.Id)
|> List.groupBy fst
|> List.map (fun (machine, jobs) -> machine, jobs |> List.map snd)
printfn "Assignments:"
for (machine, jobs) in machineAssignments do
printfn $"Machine: {machine.Id}"
for job in jobs do
printfn $"\tJob: {job.Id}"
// Calculate how much each machine is loaded
let machineLoads =
machineAssignments
|> List.map (fun (machine, jobs) -> machine, jobs |> List.sumBy (fun j -> j.Size))
printfn ""
printfn "Machine Loading:"
for (machine, load) in machineLoads do
printfn $"Machine: {machine.Id} | Total Load: {load}"
// Find the min and max loads and calculate the difference
let maxDifference =
let loads = machineLoads |> List.map snd
(List.max loads) - (List.min loads)
printfn ""
printfn $"Max Diffence In Loading: { maxDifference }"
| _ -> printfn "%A" result