Tutorial
Understanding Constraint Satisfaction Problems in ProblemReductions
This tutorial explains how to work with constraint satisfaction problems using the ProblemReductions package in Julia.
Basic Setup
First, let's import the required packages and create a simple problem:
julia> using ProblemReductions, Graphsjulia> problem = IndependentSet(smallgraph(:diamond))IndependentSet{SimpleGraph{Int64}, Int64, UnitWeight}(SimpleGraph{Int64}(5, [[2, 3], [1, 3, 4], [1, 2, 4], [2, 3]]), [1, 1, 1, 1])
This creates an Independent Set problem based on a diamond-shaped graph.
Problem Properties
You can query various properties of the problem:
1. Basic properties
julia> num_variables(problem) # number of variables4julia> num_flavors(problem) # number of flavors of each variable2julia> problem_size(problem) # size of the problem(num_vertices = 4, num_edges = 5)
2. Weights (for weighted problems)
julia> ProblemReductions.weights(problem) # weights of the problem4-element UnitWeight: 1 1 1 1julia> ProblemReductions.set_weights(problem, [1, 2, 2, -1, -1, -2]) # set the weights of the problemIndependentSet{SimpleGraph{Int64}, Int64, Vector{Int64}}(SimpleGraph{Int64}(5, [[2, 3], [1, 3, 4], [1, 2, 4], [2, 3]]), [1, 2, 2, -1, -1, -2])
3. Objectives and Constraints
julia> ProblemReductions.objectives(problem) # View the objective functions4-element Vector{ProblemReductions.LocalSolutionSize{Int64}}: LocalSolutionSize{Int64} on [1] ┌───────────────┬──────┐ │ Configuration │ Size │ ├───────────────┼──────┤ │ [0] │ 0 │ │ [1] │ 1 │ └───────────────┴──────┘ LocalSolutionSize{Int64} on [2] ┌───────────────┬──────┐ │ Configuration │ Size │ ├───────────────┼──────┤ │ [0] │ 0 │ │ [1] │ 1 │ └───────────────┴──────┘ LocalSolutionSize{Int64} on [3] ┌───────────────┬──────┐ │ Configuration │ Size │ ├───────────────┼──────┤ │ [0] │ 0 │ │ [1] │ 1 │ └───────────────┴──────┘ LocalSolutionSize{Int64} on [4] ┌───────────────┬──────┐ │ Configuration │ Size │ ├───────────────┼──────┤ │ [0] │ 0 │ │ [1] │ 1 │ └───────────────┴──────┘julia> ProblemReductions.constraints(problem) # View the constraints5-element Vector{ProblemReductions.LocalConstraint}: LocalConstraint on [1, 2] ┌───────────────┬───────┐ │ Configuration │ Valid │ ├───────────────┼───────┤ │ [0, 0] │ true │ │ [1, 0] │ true │ │ [0, 1] │ true │ │ [1, 1] │ false │ └───────────────┴───────┘ LocalConstraint on [1, 3] ┌───────────────┬───────┐ │ Configuration │ Valid │ ├───────────────┼───────┤ │ [0, 0] │ true │ │ [1, 0] │ true │ │ [0, 1] │ true │ │ [1, 1] │ false │ └───────────────┴───────┘ LocalConstraint on [2, 3] ┌───────────────┬───────┐ │ Configuration │ Valid │ ├───────────────┼───────┤ │ [0, 0] │ true │ │ [1, 0] │ true │ │ [0, 1] │ true │ │ [1, 1] │ false │ └───────────────┴───────┘ LocalConstraint on [2, 4] ┌───────────────┬───────┐ │ Configuration │ Valid │ ├───────────────┼───────┤ │ [0, 0] │ true │ │ [1, 0] │ true │ │ [0, 1] │ true │ │ [1, 1] │ false │ └───────────────┴───────┘ LocalConstraint on [3, 4] ┌───────────────┬───────┐ │ Configuration │ Valid │ ├───────────────┼───────┤ │ [0, 0] │ true │ │ [1, 0] │ true │ │ [0, 1] │ true │ │ [1, 1] │ false │ └───────────────┴───────┘julia> ProblemReductions.energy_mode(problem) # View the energy mode of the problemLargerSizeIsBetter()
Remarks
- All
ConstraintSatisfactionProblems useobjectivesandconstraintsto define the problem. - The
constraintsreturns a vector of local constraints. Each local constraint is a boolean function of the variables.truemeans the constraint is satisfied by the current assignment to local variables. - The energy is defined as
-solution_sizeifenergy_mode(problem) == LargerSizeIsBetter()andsolution_sizeifenergy_mode(problem) == SmallerSizeIsBetter(). The energy is infinity if the solution is invalid.
Working with Solutions
You can evaluate different solutions (configurations) using these methods:
1. Single Solution Evaluation
julia> solution_size(problem, [1, 0, 1, 0]) # Calculate solution sizeSolutionSize{Int64}(2, false)julia> energy(problem, [1, 0, 1, 0]) # Calculate energy3037000500
The solution size is defined as the sum of the sizes of the variables. It returns a SolutionSize object, which has two fields:
size: the size of the solutionis_valid: whether the solution satisfies the constraints
2. Multiple Solutions Evaluation (faster than multiple single evaluations)
julia> solution_size_multiple(problem, [[1, 0, 0, 0], [0, 1, 0, 1]]) # Evaluate multiple configurations2-element Vector{SolutionSize{Int64}}: SolutionSize{Int64}(1, true) SolutionSize{Int64}(2, false)
3. Finding the Optimal Solution
julia> ProblemReductions.findbest(problem, BruteForce()) # Find the best configuration using brute force1-element Vector{Vector{Int64}}: [1, 0, 0, 1]
Remarks
- Configuration Format: Solutions are represented as vectors of integers in the range of
0:num_flavors(problem)-1, where each element represents a variable's state. - Optimization: The
findbestmethod allows you to find optimal solutions using different algorithms (likeBruteForcein this example). Alternatively, you can use theGTNSolverin theGenericTensorNetworkspackage to find optimal solutions using tensor network methods.
This API provides a comprehensive toolkit for defining, manipulating, and solving constraint satisfaction problems in Julia.