|
| 1 | +// |
| 2 | +// MinConflicts.swift |
| 3 | +// SwiftCSP |
| 4 | +// |
| 5 | +// Copyright (c) 2022 David Kopec |
| 6 | +// |
| 7 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 8 | +// you may not use this file except in compliance with the License. |
| 9 | +// You may obtain a copy of the License at |
| 10 | +// |
| 11 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 12 | +// |
| 13 | +// Unless required by applicable law or agreed to in writing, software |
| 14 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 15 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 16 | +// See the License for the specific language governing permissions and |
| 17 | +// limitations under the License. |
| 18 | + |
| 19 | +/// Use the MinConflicts algorithm to try to resolve the CSP |
| 20 | +/// |
| 21 | +/// - parameter csp: The CSP to operate on. |
| 22 | +/// - parameter maxSteps: Maxmimum number of steps to try |
| 23 | +/// - parameter assignment: Optionally, an already full assignment, . |
| 24 | +/// - returns: the assignment (solution), and any remaining conflicted variables |
| 25 | +public func minConflicts<V, D>(csp: CSP<V, D>, maxSteps: Int = 100000, assignment: Dictionary<V, D>?) -> (Dictionary<V, D>, Array<V>) |
| 26 | +{ |
| 27 | + var current = (assignment != nil) ? assignment! : randomAssignment(csp: csp) |
| 28 | + for _ in 0..<maxSteps { |
| 29 | + let conflicted = conflictedVariables(csp: csp, assignment: current) |
| 30 | + if conflicted.isEmpty { return (current, []) } |
| 31 | + let variable = conflicted.randomElement()! |
| 32 | + let value = minimallyConflictedValue(csp: csp, variable: variable, assignment: current) |
| 33 | + current[variable] = value |
| 34 | + } |
| 35 | + return (current, conflictedVariables(csp: csp, assignment: current)) |
| 36 | +} |
| 37 | + |
| 38 | +/// First uses the MinConflicts algorithm to try to resolve the CSP. |
| 39 | +/// Then, if it's not solved after *maxSteps*, runs a backtracking search to find |
| 40 | +/// domain values for the remaining conflicted variables. |
| 41 | +/// |
| 42 | +/// - parameter csp: The CSP to operate on. |
| 43 | +/// - parameter maxSteps: Maxmimum number of steps to try for the MinConflicts portion |
| 44 | +/// - parameter assignment: Optionally, an already full assignment. |
| 45 | +/// - parameter mrv: Should it use the mrv heuristic to try to improve performance (default false) |
| 46 | +/// - parameter lcv: SHould it use the lcv heuristic to try to improve performance (default false) |
| 47 | +/// - parameter mac3: SHould it use the mac3 heuristic to try to improve performance (default false) NOT IMPLEMENTED YET |
| 48 | +/// - returns: the assignment (solution), and any remaining conflicted variables |
| 49 | +//public func hybridSearch<V, D>(csp: CSP<V, D>, maxSteps: Int = 100000, assignment: Dictionary<V, D>?, mrv: Bool = false, lcv: Bool = false, mac3: Bool = false) -> Dictionary<V, D>? { |
| 50 | +// let (current, conflicted) = minConflicts(csp: csp, maxSteps: maxSteps, assignment: assignment) |
| 51 | +// if conflicted.isEmpty { |
| 52 | +// return current |
| 53 | +// } |
| 54 | +// // Incomplete assignment, so remove conflicting variables from it |
| 55 | +// let partial = current.filter { !conflicted.contains($0.0) } |
| 56 | +// return backtrackingSearch(csp: csp, assignment: partial, mrv: mrv, lcv: lcv, mac3: mac3) |
| 57 | +//} |
| 58 | + |
| 59 | +public func minimallyConflictedValue<V, D>(csp: CSP<V, D>, variable: V, assignment: Dictionary<V, D>) -> D { |
| 60 | + let domain = csp.domains[variable]! |
| 61 | + var minimallyConstrained = domain[0] |
| 62 | + var minimumConflictCount = countConflicts(csp: csp, variable: variable, value: minimallyConstrained, assignment: assignment) |
| 63 | + for domainValue in domain.dropFirst() { |
| 64 | + let conflictCount = countConflicts(csp: csp, variable: variable, value: domainValue, assignment: assignment) |
| 65 | + if conflictCount < minimumConflictCount { |
| 66 | + minimumConflictCount = conflictCount |
| 67 | + minimallyConstrained = domainValue |
| 68 | + } |
| 69 | + } |
| 70 | + return minimallyConstrained |
| 71 | +} |
| 72 | + |
| 73 | +public func countConflicts<V, D>(csp: CSP<V, D>, variable: V, value: D, assignment: Dictionary<V, D>) -> Int { |
| 74 | + var current = assignment |
| 75 | + current[variable] = value |
| 76 | + var count = 0 |
| 77 | + for constraint in csp.constraints[variable]! { |
| 78 | + if !constraint.isSatisfied(assignment: current) { |
| 79 | + count += 1 |
| 80 | + } |
| 81 | + } |
| 82 | + return count |
| 83 | +} |
| 84 | + |
| 85 | +public func conflictedVariables<V, D>(csp: CSP<V, D>, assignment: Dictionary<V, D>) -> [V] { |
| 86 | + var conflicted: [V] = [] |
| 87 | +outer: for variable in csp.variables { |
| 88 | + for constraint in csp.constraints[variable]! { |
| 89 | + if !constraint.isSatisfied(assignment: assignment) { |
| 90 | + conflicted.append(variable) |
| 91 | + continue outer |
| 92 | + } |
| 93 | + } |
| 94 | + } |
| 95 | + return conflicted |
| 96 | +} |
| 97 | + |
| 98 | +public func randomAssignment<V, D>(csp: CSP<V, D>) -> Dictionary<V, D> |
| 99 | +{ |
| 100 | + var assignment = Dictionary<V, D>() |
| 101 | + for variable in csp.variables { |
| 102 | + assignment[variable] = csp.domains[variable]!.randomElement() |
| 103 | + } |
| 104 | + return assignment |
| 105 | +} |
0 commit comments