OptimalBranchingCore
Literal, Clause and DNF
Literals, clauses and disjunctive normal form (DNF) are basic concepts in boolean logic, where literals are boolean variables, clauses are boolean expressions, and DNF is a disjunction of one or more conjunctions of literals.
Here is an example, given a truth table as follows:
a | b | c | value |
---|---|---|---|
0 | 0 | 0 | 1 |
0 | 0 | 1 | 1 |
0 | 1 | 0 | 0 |
0 | 1 | 1 | 0 |
1 | 0 | 0 | 0 |
1 | 0 | 1 | 1 |
1 | 1 | 0 | 0 |
1 | 1 | 1 | 0 |
where $a, b, c$ are boolean variables called literals. The true statements can be represented as a conjunction of literals, for example,
\[\neg a \land \neg b \land \neg c, \neg a \land \neg b \land c, a \land \neg b \land c\]
and these clauses can be combined into a DNF:
\[(\neg a \land \neg b) \lor (a \land \neg b \land c).\]
In OptimalBranchingCore
, a clause is represented by the Clause
type, and a DNF is represented by the DNF
type, based on the BitBasis.jl
package.
julia> using OptimalBranchingCore, BitBasis
julia> c1 = Clause(bit"011", bit"000")
Clause{BitBasis.DitStr{2, 3, Int64}}: ¬#1 ∧ ¬#2
julia> c2 = Clause(bit"111", bit"101")
Clause{BitBasis.DitStr{2, 3, Int64}}: #1 ∧ ¬#2 ∧ #3
julia> dnf = DNF(c1, c2)
DNF{BitBasis.DitStr{2, 3, Int64}}: (¬#1 ∧ ¬#2) ∨ (#1 ∧ ¬#2 ∧ #3)
The branch and bound algorithm
The branch and bound algorithm is a method to exactly solve the combinatorial optimization problems.
API
OptimalBranchingCore.apply_branch
— Functionapply_branch(problem::AbstractProblem, clause::Clause, vertices::Vector)::Tuple
Create a branch from the given clause applied to the specified vertices.
Arguments
problem
: The problem instance.clause
: The clause that containing the information about how to fix the values of the variables.vertices
: A vector of vertices to be considered for the branch.
Returns
AbstractProblem
: A new instance ofAbstractProblem
with reduced size.
OptimalBranchingCore.branch_and_reduce
— Methodbranch_and_reduce(problem::AbstractProblem, config::BranchingStrategy; reducer::AbstractReducer=NoReducer(), result_type=Int)
Branch the given problem using the specified solver configuration.
Arguments
problem
: The problem instance to solve.config
: The configuration for the solver, which is aBranchingStrategy
instance.
Keyword Arguments
reducer::AbstractReducer=NoReducer()
: The reducer to reduce the problem size.result_type::Type{TR}
: The type of the result that the solver will produce.
Returns
The resulting value, which may have different type depending on the result_type
.
OptimalBranchingCore.branching_table
— Functionbranching_table(problem::AbstractProblem, table_solver::AbstractTableSolver, variables::Vector{Int})
Obtains the branching table for a given problem using a specified table solver.
Arguments
problem
: The problem instance.table_solver
: The table solver, which is a subtype ofAbstractTableSolver
.variables
: A vector of indices of the variables to be considered for the branching table.
Returns
A branching table, which is a BranchingTable
object.
OptimalBranchingCore.candidate_clauses
— Methodcandidate_clauses(tbl::BranchingTable{INT}) where {INT}
Generates candidate clauses from a branching table.
Arguments
tbl::BranchingTable{INT}
: The branching table containing bit strings.
Returns
Vector{Clause{INT}}
: A vector ofClause
objects generated from the branching table.
OptimalBranchingCore.complexity_bv
— Methodcomplexity_bv(branching_vector::Vector)::Float64
Calculates the complexity that associated with the provided branching vector by solving the equation:
\[γ^0 = \sum_{δρ \in \text{branching_vector}} γ^{-δρ}\]
Arguments
branching_vector
: a vector of problem size reductions in the branches.
Returns
Float64
: The computed γ value.
OptimalBranchingCore.covered_by
— Methodcovered_by(t::BranchingTable, dnf::DNF)
Check if the branching table t
is covered by the logic expression dnf
. Returns true
if there exists at least one bitstring in each group of t
that satisfies dnf
, false
otherwise.
OptimalBranchingCore.covered_by
— Methodcovered_by(a::Integer, clause_or_dnf)
Check if a
is covered by the logic expression clause_or_dnf
.
Arguments
Returns
true
if a
satisfies clause_or_dnf
, false
otherwise.
OptimalBranchingCore.is_false_literal
— Methodis_false_literal(c::Clause)
Check if the clause is a false literal.
OptimalBranchingCore.is_true_literal
— Methodis_true_literal(c::Clause)
Check if the clause is a true literal.
OptimalBranchingCore.literals
— Methodliterals(c::Clause)
Return all literals in the clause.
OptimalBranchingCore.measure
— Functionmeasure(problem::AbstractProblem, measure::AbstractMeasure)::Number
Calculate the size of the problem, reducing which serves as the guiding principle for the branching strategy.
Arguments
problem
: The problem instance.measure
: The measure of the problem size.
Returns
A real number representing the problem size.
OptimalBranchingCore.minimize_γ
— Methodminimize_γ(table::BranchingTable, candidates::Vector{Clause}, Δρ::Vector, solver)
Finds the optimal cover based on the provided vector of problem size reduction. This function implements a cover selection algorithm using an iterative process. It utilizes an integer programming solver to optimize the selection of sub-covers based on their complexity.
Arguments
table::BranchingTable
: A branching table containing clauses that need to be covered, a table entry is covered by a clause if one of its bit strings satisfies the clause. Please refer tocovered_by
for more details.candidates::Vector{Clause}
: A vector of candidate clauses to form the branching rule (in the form ofDNF
).Δρ::Vector
: A vector of problem size reduction for each candidate clause.solver
: The solver to be used. It can be an instance ofLPSolver
orIPSolver
.
Keyword Arguments
γ0::Float64
: The initial γ value.
Returns
A tuple of two elements: (indices of selected subsets, γ)
OptimalBranchingCore.optimal_branching_rule
— Methodoptimal_branching_rule(table::BranchingTable, variables::Vector, problem::AbstractProblem, measure::AbstractMeasure, solver::AbstractSetCoverSolver)::OptimalBranchingResult
Generate an optimal branching rule from a given branching table.
Arguments
table
: ABranchingTable
instance containing candidate clauses.variables
: A vector of variables to perform the branching.problem
: The problem instance being solved.measure
: The measure used for evaluating the problem size reduction in the branches.solver
: The solver used for the weighted minimum set cover problem, which can be eitherLPSolver
orIPSolver
.
Returns
A OptimalBranchingResult
object representing the optimal branching rule.
OptimalBranchingCore.reduce_problem
— Methodreduce_problem(::Type{R}, problem::AbstractProblem, reducer::AbstractReducer) where R
Reduces the problem size directly, e.g. by graph rewriting. It is a crucial step in the reduce and branch strategy.
Arguments
R
: The element type used for computing the size of solution. The should have an additive commutative monoid structure.problem
: The problem instance.reducer
: The reducer.
Returns
A tuple of two values:
AbstractProblem
: A new instance ofAbstractProblem
with reduced size.Number
: The local gain of the reduction, which will be added to the global gain.
OptimalBranchingCore.select_variables
— Functionselect_variables(problem::AbstractProblem, measure::AbstractMeasure, selector::AbstractSelector)::Vector{Int}
Selects a branching strategy for a AbstractProblem
instance.
Arguments
problem
: The problem instance.measure
: The measure of the problem size.selector
: The variables selection strategy, which is a subtype ofAbstractSelector
.
Returns
A vector of indices of the selected variables.
OptimalBranchingCore.weighted_minimum_set_cover
— Methodweighted_minimum_set_cover(solver, weights::AbstractVector, subsets::Vector{Vector{Int}}, num_items::Int)
Solves the weighted minimum set cover problem.
Arguments
solver
: The solver to be used. It can be an instance ofLPSolver
orIPSolver
.weights::AbstractVector
: The weights of the subsets.subsets::Vector{Vector{Int}}
: A vector of subsets.num_items::Int
: The number of elements to cover.
Returns
A vector of indices of selected subsets.
OptimalBranchingCore.AbstractMeasure
— TypeAbstractMeasure
The base type for the measure of the problem size in terms of computational hardness. Some widely used measures include the number of variables, the vertices with connectivity of at least 3, etc.
OptimalBranchingCore.AbstractProblem
— TypeAbstractProblem
The problem type that can be used in the optimal branching framework.
OptimalBranchingCore.AbstractReducer
— TypeAbstractReducer
An abstract type representing a reducer in the context of branching problems. This serves as a base type for all specific reducer implementations.
OptimalBranchingCore.AbstractSelector
— TypeAbstractSelector
An abstract type for the strategy of selecting a subset of variables to be branched.
OptimalBranchingCore.AbstractSetCoverSolver
— TypeAbstractSetCoverSolver
An abstract type for the strategy of solving the set covering problem.
OptimalBranchingCore.AbstractTableSolver
— TypeAbstractTableSolver
An abstract type for the strategy of obtaining the branching table.
OptimalBranchingCore.BranchingStrategy
— TypeBranchingStrategy
BranchingStrategy(; kwargs...)
A struct representing the configuration for a solver, including the reducer and branching strategy.
Fields
table_solver::AbstractTableSolver
: The solver to resolve the relevant bit strings and generate a branching table.set_cover_solver::AbstractSetCoverSolver = IPSolver()
: The solver to solve the set covering problem.selector::AbstractSelector
: The selector to select the next branching variable or decision.m::AbstractMeasure
: The measure to evaluate the performance of the branching strategy.
OptimalBranchingCore.BranchingTable
— TypeBranchingTable{INT <: Integer}
A list of groupped bitstrings, which is used for designing the branching rule. A valid branching rule, which is represented by a logic expression in Disjunctive Normal Form (DNF), should cover at least one bitstring from each group, where by cover
, we mean there exists at least one bitstring in the group that satisfies the logic expression. Please check covered_by
for more details.
Fields
bit_length::Int
: The length of the bit string.table::Vector{Vector{INT}}
: The list of groupped bitstrings used for branching, where each group is a vector of bitstrings. The bitstrings usesINT
type to store the bitstring.
OptimalBranchingCore.CandidateClause
— TypeCandidateClause{INT <: Integer}
A candidate clause is a clause containing the formation related to how it can cover the items in the branching table.
Fields
covered_items::Set{Int}
: The items in the branching table that are covered by the clause.clause::Clause{INT}
: The clause itself.
OptimalBranchingCore.Clause
— TypeClause{INT <: Integer}
A Clause is conjunction of literals, which is specified by a pair of bit strings. The type parameter INT
is the integer type for storing the bit strings.
Fields
mask
: A bit string that indicates the variables involved in the clause.val
: A bit string that indicates the positive literals in the clause.
Examples
To check if a bit string satisfies a clause, use OptimalBranchingCore.covered_by
.
julia> using OptimalBranchingCore
julia> clause = Clause(0b1110, 0b1010)
Clause{UInt8}: #2 ∧ ¬#3 ∧ #4
julia> OptimalBranchingCore.covered_by(0b1110, clause)
false
julia> OptimalBranchingCore.covered_by(0b1010, clause)
true
OptimalBranchingCore.DNF
— TypeDNF{INT}
A data structure representing a logic expression in Disjunctive Normal Form (DNF), which is a disjunction of one or more conjunctions of literals. In OptimalBranchingCore, a DNF is used to represent the branching rule.
Fields
clauses::Vector{Clause{INT}}
: A vector ofClause
objects.
OptimalBranchingCore.IPSolver
— TypeIPSolver <: AbstractSetCoverSolver
IPSolver(; optimizer = HiGHS.Optimizer, max_itr::Int = 5, verbose::Bool = false)
An integer programming solver for set covering problems.
Fields
optimizer
: The optimizer to be used.max_itr::Int
: The maximum number of iterations to be performed.verbose::Bool
: Whether to print the solver's output.
OptimalBranchingCore.LPSolver
— TypeLPSolver <: AbstractSetCoverSolver
LPSolver(; optimizer = HiGHS.Optimizer, max_itr::Int = 5, verbose::Bool = false)
A linear programming solver for set covering problems.
Fields
optimizer
: The optimizer to be used.max_itr::Int
: The maximum number of iterations to be performed.verbose::Bool
: Whether to print the solver's output.
OptimalBranchingCore.MaxSize
— TypeMaxSize
A struct representing the maximum size of a result. (actually a tropical int)
Fields
size::Int
: The maximum size value.
Constructors
MaxSize(size::Int)
: Creates aMaxSize
instance with the specified size.
OptimalBranchingCore.MaxSizeBranchCount
— Typestruct MaxSizeBranchCount
Reture both the max size of the results and number of branches.
Fields
size::Int
: The max size of the results.count::Int
: The number of branches of that size.
Constructors
MaxSizeBranchCount(size::Int)
: Creates aMaxSizeBranchCount
with the given size and initializes the count to 1.MaxSizeBranchCount(size::Int, count::Int)
: Creates aMaxSizeBranchCount
with the specified size and count.
OptimalBranchingCore.OptimalBranchingResult
— TypeOptimalBranchingResult{INT <: Integer}
The result type for the optimal branching rule.
Fields
selected_ids::Vector{Int}
: The indices of the selected rows in the branching table.optimal_rule::DNF{INT}
: The optimal branching rule.branching_vector::Vector{T<:Real}
: The branching vector that records the size reduction in each subproblem.γ::Float64
: The optimal γ value (the complexity of the branching rule).