# 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. ```python 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**: ```python 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)$ ```python 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](#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 - **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 - **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 - **Inference**: - **forward checking**: **propagate constraintsfor all unassigned variables** - **Pros**: - Fast - **Cons**: - Does not detect all inconsistencies - **MAC - Maintaining Arc Consistency**: It's a **call to [AC3](#arc-consistency)** only with **constrainst involving our assigned variable** - **Pros**: - Detects all inconsistencies - **Cons**: - Slower than forward checking - **Backtracking - Where should we go back once we fail** - **chronological backtracing**: go a step back - **Pros**: - Easy to implement - **Cons**: - Slower time of computation - **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 - **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 - **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 ## 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) - **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