12 KiB
Constraint Satisfaction Problems
Note
From now on States have a factored representation of the following type
- Variables (
\mathcal{X}): Actual variables in the State- Domains(
\mathcal{D}): Set of values that a variable can get (one per variable)- Constraints (
\mathcal{C}): Constraints about values assignable to variables
These problems concern about finding the right assignment of values to all variables so that the assignment respects all constraints. For example, let's imagine that we could only get cats in spaces with other pets, but small ones. This is indeed a CSP problem where we need to find a solution where cats are never with small pets.
class StateCSP(State):
def __init__(
self,
variables: dict[str, Value],
domains: dict[str, set[Value]],
# The scope is represented as a list of strings
# because these are variables in this representation
constraints: dict[list[str], Rule | set[Value]]
):
self.__variables = variables
self.__domains = domains
self.__constraints = constraints
In this space, in constrast with the atomic one, we can see if we are doing a mistake during assignments. Whenever we detect such mistakes, we prune that path, since it is not admissable. This makes the computation a bit faster, since we can detect early failures.
Constraints
Caution
Constraints can be of these types:
- Unary: One variable involved (For example Bob does not like Shirts)
- Binary: Two variables involved
- Global: More than Two variables are involved
Preference Contstraints
These are constraints that should be followed, but technically can be ignored as far as the problem is concerned.
In order to account for these preferences, it is possible to assign a higher cost to moves that break these constraints
Note
In these cases we could also talk about COPs (Constraint Optimization Problems)
Common Global Constraints
-
Aldiff (all different): Each variable in the constraint must have a unique value
-
Heuristic: If the set of all values has a size lower than the number of variables, then it is impossible
-
Heuristic Algorithm:
def aldiff_heuristic(csp_problem): # May be optimized with a heap or priority queue # sorted by domain_size variables : Queue = csp_problem.variables possible_values = set() for variable in variables: prossible_values.extend( variable.domain ) halt = False while not halt: # Count variables to avoid overflow remaining_variables = len(variables) # Check if there were eliminable variables variables_with_singleton_domain = 0 # Check if any of these variables in a singleton # variable for _ in range(0, remaining_variables): candidate_variable = variables.pop() domain_size = len(variable.domain) # Check for domain size match(domain_size): # Empty domain, problem can't be solved case 0: return false # Singleton domain case 1: # Add count variables_with_singleton_domain += 1 # Take value value = candidate_variable.domain[0] # Remove value from all possible values possible_values.remove(value) # Remove value from all domains for variable in variables: variable.domain.remove(value) break # Variable has more values, reinsert in queue _: variables.push(variable) # No singleton variables found, algorithm # would stall, halt now if variables_with_singleton_domain == 0: halt = True # Check that we have enough values to # assign to all variables return len(possible_values) >= len(variables)
-
-
Atmost: It defines a maximum amount of a resource
- Heuristic: Check if the sum of all minimum values in all scope variables domain is higher than the max value
Constraint Propagation
It's a step in which we prune some values from domains. This step can be alternated with search or be a preprocessing step.
For the sake of this step, we'll construct some graphs where variables are nodes and binary-constraints are edges.
Warning
We are not talking about search graphs, as this is another step unreleated with search
Tip
Even though it's not a search, sometimes it can solve our problem before even beginning to search. Moreover each global constraint can become a binary constraint, making this step always possible to implement (at a higher computational cost of either the machine or the human transforming these constraints)
Node Consistency
A node is node-consistent when all values in its domain satifies its unary constraints
Arc Consistency
A node is arc-consistent when all values in its domain satisfies all of binary constraints it is involved to
AC3 is an Algorithm that can help in achieving this with a Time Complexity of O(n^3) or O(\text{constraints} \cdot \text{size\_values}^3)
def AC3(csp_problem):
# Take all constraints
queue : queue[Constraint] = csp_problem.edges
# Examine all constraints
while len(queue) > 0:
# Take the first and initialize relevant variables
constraint = queue.pop
X = constraint.scope[0]
Y = constraint.scope[1]
# Correct domain if needed
correction_applied = apply_constraint([X, Y], rule)
# If correction was applied, check that there
# are still admissable variables
if correction_applied:
# If there aren't, then notify problem is not
# solvable
if len(X.domain) == 0:
return False
# If there are still admissable variables, focus
# on its neighbours
for neighbour in (X.neighbours - Y):
queue.push(csp_problem.search(neighbour, X))
# Notify that problem has been correctly constrained
return True
def apply_constraint(variables: list[Variable], rule: Rule):
# Gather all sets of values
x_domain = set(variables[0].domain)
y_domain = set(variables[1].domain)
# Set modified to false
x_domain_modified = False
# Check if all variables satisfy constraint
for x in x_domain:
rule_satisfied = False
# If a single y makes the couple satisfy the constraint,
# break early
for y in y_domain
if rule.check(x, y):
rule_satisfied = True
break
# If no y satisfied the couple (x, y) then remove x
if not rule_satisfied:
x_domain.remove(x)
x_domain_modified = True
# Return if domain was modified
return x_domain_modified
Path Consistency
A set of 2 variables is path consistent to a 3rd variable if for each valid assignment satisfying their arc consistency there exists at least a value for a 3rd variable so that it satisfies its binary constraints with the other two variables
K-Consistency
A variable is k-consistent if it's also (k-1)-consistent down to 1-consistent (node-consistent)
Bound Propagation
Sometimes, when we deal with lots of numbers, it's better to represent possible values as a bound (or a union of bounds).
In this case we need to check that there exists a solution for both the maximum and minimum values for all variables in order to have bound-consistency
Backtracking Search
This is a step coming after the constraint propagation when we have multiple values assignable to variables and is a traditional search over the problem.
It can be interleaved with constraint propagation at each step to see if we are breaking a constraint
Commutativity
Since our problem may not care about the specific value we may take, we can just consider the number of assignments. In other words, we don't care much about the value we are assigning, but rather about the assignment itself
Heuristics
To make the algorithm faster, we can have some heuristics to help us choose the best strategy:
- Choose Variable to Assign:
- minimum-remaining-values (MRV): consists on choosing the variable with the least amount of values in its domain
- Pros:
- Detection of Failures is faster
- Cons:
- There may be still ties among variables
- Pros:
- degree heuristic: choose the variable involved the most in constraints
- Pros:
- Fast detection of Failures
- Can be a tie-breaker combined with MRV
- Cons:
- Worse results than MRV
- Pros:
- minimum-remaining-values (MRV): consists on choosing the variable with the least amount of values in its domain
- Choose Value to assign:
- least-constraining-value: choose the value that rules out the fewest choices among the variable neighbours
- Pros:
- We only case about assigning, not what we assign
- Pros:
- least-constraining-value: choose the value that rules out the fewest choices among the variable neighbours
- Inference:
- forward checking: propagate constraintsfor all unassigned variables
- Pros:
- Fast
- Cons:
- Does not detect all inconsistencies
- Pros:
- MAC - Maintaining Arc Consistency: It's a call to AC3 only with constrainst involving our assigned variable
- Pros:
- Detects all inconsistencies
- Cons:
- Slower than forward checking
- Pros:
- forward checking: propagate constraintsfor all unassigned variables
- Backtracking - Where should we go back once we fail
- chronological backtracing: go a step back
- Pros:
- Easy to implement
- Cons:
- Slower time of computation
- Pros:
- backjumping: We go to a step where we made an assignment that led to a conflict. This is accomplished by having conflicts sets, sets where we track
variable assignments to variables involved in constraints- Pros:
- Faster time of computation
- Cons:
- Need to track a conflict set
- Useless if any inference heuristic has been used
- Pros:
- conflict-directed backjumping: We jump to the assignment that led to a chain of conflicts. Now, each time an we detect a failure, we propagate the conflict
set over the backtraced variable recursively- Pros:
- Faster time of computation
- Cons:
- Harder to implement
- Pros:
- chronological backtracing: go a step back
- Constraint Learning
- no-good: We record a set of variables that lead to a failure. Whenever we meet such a set, we fail immediately, or we avoid it completely
- Pros:
- Faster time of computation
- Cons:
- Memory expensive
- Pros:
- no-good: We record a set of variables that lead to a failure. Whenever we meet such a set, we fail immediately, or we avoid it completely
Local Search
The idea is that instead of building our goal state, we start from a completely assigned state (usually randomically) and we proceed from there on.
Can also be useful during online searching
Local Search Heuristics
- min-conflicts: Change a random variable (that has conflcts) to a value that would minimize conflicts
- Pros:
- Almost problem size independent
- Somewhat fast
- Cons:
- Subsceptible to Plateaus (use taboo-search or simulated annealing)
- Pros:
- constraint weighting: Concentrate the search over solving constraints that have a higher value (each one starting with a value of 1). Choose the
pair variable/value that minimizes this value and increment each unsolved constraint by one- Pros:
- Adds Topology to plateaus
- Cons:
- Difficult to implement
- Pros: