Skip to content

Commit 3dd7112

Browse files
authored
Merge pull request #13 from isPANN/xw/native_ob
Refactor to use native interfaces in `OptimalBranching.jl`
2 parents 0832bbf + 9043e32 commit 3dd7112

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+4052
-1095
lines changed

.gitignore

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,24 @@
1+
# Julia
12
*.jl.*.cov
23
*.jl.cov
34
*.jl.mem
45
/Manifest.toml
5-
/docs/Manifest.toml
6-
/docs/build/
6+
7+
# Benchmark data
8+
benchmarks/data/
9+
benchmarks/Manifest.toml
10+
11+
# IDE
712
.vscode/
8-
backup/
9-
datas/
10-
.DS_Store
13+
.idea/
14+
15+
# OS
16+
.DS_Store
17+
Thumbs.db
18+
19+
# Temporary files
20+
*.tmp
21+
*.temp
22+
23+
statprof/
24+
OptimalBranching.jl/

Project.toml

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,24 @@
11
name = "BooleanInference"
22
uuid = "d12a6243-8379-43cb-9459-098119da1519"
3-
authors = ["nzy1997"]
43
version = "1.0.0-DEV"
4+
authors = ["nzy1997"]
55

66
[deps]
7+
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
78
GenericTensorNetworks = "3521c873-ad32-4bb4-b63d-f4f178f42b49"
8-
HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b"
9-
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
10-
KaHyPar = "2a6221f6-aa48-11e9-3542-2d9e0ef01880"
119
OptimalBranchingCore = "c76e7b22-e1d2-40e8-b0f1-f659837787b8"
12-
Primes = "27ebfcd6-29c5-5fa9-bf4b-fb8fc14df3ae"
1310
ProblemReductions = "899c297d-f7d2-4ebf-8815-a35996def416"
1411
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
1512
TropicalNumbers = "b3a74e9c-7526-4576-a4eb-79c0d4c32334"
1613

1714
[compat]
18-
GenericTensorNetworks = "3"
19-
HiGHS = "1.12.2"
20-
JuMP = "1.23.5"
21-
KaHyPar = "0.3.1"
15+
DataStructures = "0.18.22"
16+
GenericTensorNetworks = "4"
2217
OptimalBranchingCore = "0.1"
23-
Primes = "0.5.6"
18+
ProblemReductions = "0.3.5"
2419
SparseArrays = "1.11.0"
2520
TropicalNumbers = "0.6.2"
26-
julia = "1.11"
21+
julia = "1"
2722

2823
[extras]
2924
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

README.md

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,114 @@
1-
# BooleanInference
1+
# BooleanInference.jl
22

33
[![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://nzy1997.github.io/BooleanInference.jl/stable/)
44
[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://nzy1997.github.io/BooleanInference.jl/dev/)
55
[![Build Status](https://github.com/nzy1997/BooleanInference.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/nzy1997/BooleanInference.jl/actions/workflows/CI.yml?query=branch%3Amain)
66
[![Coverage](https://codecov.io/gh/nzy1997/BooleanInference.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/nzy1997/BooleanInference.jl)
7+
8+
A high-performance Julia package for solving Boolean satisfiability problems using tensor network contraction and optimal branching strategies.
9+
10+
## Features
11+
12+
- **Tensor Network Representation**: Efficiently represents Boolean satisfiability problems as tensor networks
13+
- **Optimal Branching**: Uses advanced branching strategies to minimize search space
14+
- **Multiple Problem Types**: Supports CNF, circuit, and factoring problems
15+
- **High Performance**: Optimized for speed with efficient propagation and contraction algorithms
16+
- **Flexible Interface**: Easy-to-use API for various constraint satisfaction problems
17+
18+
## Installation
19+
20+
```julia
21+
using Pkg
22+
Pkg.add("BooleanInference")
23+
```
24+
25+
## Quick Start
26+
27+
### Solving SAT Problems
28+
29+
```julia
30+
using BooleanInference
31+
using GenericTensorNetworks: , , ¬
32+
33+
# Define a CNF formula
34+
@bools a b c d e f g
35+
cnf = ((a, b, ¬d, ¬e), (¬a, d, e, ¬f), (f, g), (¬b, c), (¬a))
36+
37+
# Solve and get assignments
38+
sat = Satisfiability(cnf; use_constraints=true)
39+
satisfiable, assignments, depth = solve_sat_with_assignments(sat)
40+
41+
println("Satisfiable: ", satisfiable)
42+
println("Assignments: ", assignments)
43+
```
44+
45+
### Solving Factoring Problems
46+
47+
```julia
48+
# Factor a semiprime number
49+
a, b = solve_factoring(5, 5, 31*29)
50+
println("Factors: $a × $b = $(a*b)")
51+
```
52+
53+
### Circuit Problems
54+
55+
```julia
56+
# Solve circuit satisfiability
57+
circuit = @circuit begin
58+
c = x y
59+
end
60+
push!(circuit.exprs, Assignment([:c], BooleanExpr(true)))
61+
62+
tnproblem = setup_from_circuit(circuit)
63+
result, depth = solve(tnproblem, BranchingStrategy(), NoReducer())
64+
```
65+
66+
## Core Components
67+
68+
### Problem Types
69+
- `TNProblem`: Main problem representation
70+
- `TNStatic`: Static problem structure
71+
- `DomainMask`: Variable domain representation
72+
73+
### Solvers
74+
- `TNContractionSolver`: Tensor network contraction-based solver
75+
- `LeastOccurrenceSelector`: Variable selection strategy
76+
- `NumUnfixedVars`: Measurement strategy
77+
78+
### Key Functions
79+
- `solve()`: Main solving function
80+
- `setup_from_cnf()`: Setup from CNF formulas
81+
- `setup_from_circuit()`: Setup from circuit descriptions
82+
- `solve_factoring()`: Solve integer factoring problems
83+
84+
## Advanced Usage
85+
86+
### Custom Branching Strategy
87+
88+
```julia
89+
using OptimalBranchingCore: BranchingStrategy
90+
91+
# Configure custom solver
92+
bsconfig = BranchingStrategy(
93+
table_solver=TNContractionSolver(),
94+
selector=LeastOccurrenceSelector(2, 10),
95+
measure=NumUnfixedVars()
96+
)
97+
98+
# Solve with custom configuration
99+
result, depth = solve(problem, bsconfig, NoReducer())
100+
```
101+
102+
### Benchmarking
103+
104+
The package includes comprehensive benchmarking tools:
105+
106+
```julia
107+
using BooleanInferenceBenchmarks
108+
109+
# Compare different solvers
110+
configs = [(10,10), (12,12), (14,14)]
111+
results = run_solver_comparison(FactoringProblem, configs)
112+
print_solver_comparison_summary(results)
113+
```
114+

benchmarks/Project.toml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name = "BooleanInferenceBenchmarks"
2+
uuid = "3611a03f-6421-4259-a011-a603192356a0"
3+
version = "0.1.0"
4+
authors = ["Xiwei Pan <xiwei.pan@connect.hkust-gz.edu.cn>"]
5+
6+
[deps]
7+
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
8+
BooleanInference = "d12a6243-8379-43cb-9459-098119da1519"
9+
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
10+
Gurobi = "2e9cd046-0924-5485-92f1-d5272153d98b"
11+
JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
12+
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
13+
Primes = "27ebfcd6-29c5-5fa9-bf4b-fb8fc14df3ae"
14+
ProblemReductions = "899c297d-f7d2-4ebf-8815-a35996def416"
15+
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
16+
SHA = "ea8e919c-243c-51af-8825-aaa63cd721ce"
17+
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
18+
19+
[compat]
20+
BenchmarkTools = "1.6.0"
21+
Dates = "1.11.0"
22+
Gurobi = "1.7.6"
23+
JSON3 = "1.14.3"
24+
JuMP = "1.29.1"
25+
ProblemReductions = "0.3.5"
26+
Random = "1.11.0"
27+
Statistics = "1.11.1"

benchmarks/README.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# BooleanInference Benchmarks
2+
3+
This directory contains benchmarking code for BooleanInference.jl, organized as a separate Julia package with a modern **multiple dispatch architecture** to keep benchmark dependencies isolated from the main package.
4+
5+
## Architecture
6+
7+
The benchmark system uses Julia's **multiple dispatch** with abstract types to provide a clean, extensible interface for different problem types.
8+
9+
## Structure
10+
11+
```text
12+
benchmark/
13+
├── Project.toml # Benchmark package dependencies
14+
├── src/
15+
│ ├── BooleanInferenceBenchmarks.jl # Main benchmark module
16+
│ ├── abstract_types.jl # Abstract type definitions and interfaces
17+
│ ├── generic_benchmark.jl # Generic benchmark framework
18+
│ ├── factoring_problem.jl # Factoring problem implementation
19+
│ └── utils.jl # Generic utilities
20+
├── scripts/
21+
│ ├── run_benchmarks.jl # Standalone benchmark runner
22+
│ └── example_usage.jl # Usage examples
23+
├── data/ # Generated datasets (gitignored)
24+
└── README.md # This file
25+
```
26+
27+
## Usage
28+
29+
### Quick Start
30+
31+
```bash
32+
# Run example usage
33+
julia --project=benchmark benchmark/scripts/example_usage.jl
34+
```
35+
36+
### Programmatic Usage
37+
38+
```julia
39+
using Pkg; Pkg.activate("benchmark")
40+
using BooleanInferenceBenchmarks
41+
42+
# Create problem configurations
43+
configs = [FactoringConfig(10, 10), FactoringConfig(12, 12)]
44+
45+
# Generate datasets using multiple dispatch
46+
generate_datasets(FactoringProblem; configs=configs, per_config=100)
47+
48+
# Run benchmarks using multiple dispatch
49+
results = benchmark_problem(FactoringProblem; configs=configs, samples_per_config=5)
50+
51+
# Run complete benchmark suite
52+
full_results = run_full_benchmark(FactoringProblem)
53+
```
54+
55+
## Adding New Problem Types
56+
57+
The multiple dispatch architecture makes adding new problem types extremely simple:
58+
59+
```julia
60+
# 1. Define problem and config types
61+
struct YourProblem <: AbstractBenchmarkProblem end
62+
struct YourConfig <: AbstractProblemConfig
63+
param1::Int
64+
param2::String
65+
end
66+
67+
# 2. Implement the 5 required interface methods
68+
function generate_instance(::Type{YourProblem}, config::YourConfig; rng, include_solution=false)
69+
# Generate problem instance
70+
end
71+
72+
function solve_instance(::Type{YourProblem}, instance)
73+
# Solve the instance
74+
end
75+
76+
function problem_id(::Type{YourProblem}, config::YourConfig, data)
77+
# Generate unique ID
78+
end
79+
80+
function default_configs(::Type{YourProblem})
81+
# Return default configurations
82+
end
83+
84+
function filename_pattern(::Type{YourProblem}, config::YourConfig)
85+
# Generate filename pattern
86+
end
87+
88+
# 3. That's it! Use the same generic functions:
89+
generate_datasets(YourProblem)
90+
benchmark_problem(YourProblem)
91+
run_full_benchmark(YourProblem)
92+
```
93+
94+
## Key Advantages
95+
96+
- **DRY Principle**: Write benchmark logic once, use for all problem types
97+
- **Type Safety**: Julia's type system catches errors at compile time
98+
- **Extensibility**: Adding new problems requires minimal code
99+
- **Consistency**: All problem types use the same interface
100+
- **Performance**: Multiple dispatch enables efficient, optimized code
101+
102+
## Data Management
103+
104+
- Datasets are generated in `benchmark/data/`
105+
- Add `benchmark/data/` to `.gitignore` to avoid committing large files
106+
- Use JSONL format for datasets (one JSON object per line)
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Comprehensive solver comparison benchmark script
2+
# Demonstrates both automatic and manual solver comparison approaches
3+
# Usage: julia --project=benchmark benchmark/examples/run_benchmarks.jl
4+
5+
using Pkg
6+
Pkg.activate(joinpath(@__DIR__, ".."))
7+
8+
using BooleanInferenceBenchmarks
9+
10+
using Gurobi
11+
const GUROBI_ENV = Gurobi.Env()
12+
13+
function main()
14+
println("Running BooleanInference Solver Comparison Benchmarks...")
15+
16+
# Show available solvers
17+
println("\nAvailable solvers:")
18+
list_available_solvers(FactoringProblem)
19+
20+
# Configure benchmark parameters
21+
configs = [(10,10), (12,12), (14,14)]
22+
dataset_per_config = 10
23+
24+
println("\nComparing BooleanInference vs IP Solver...")
25+
println("Configurations: $configs")
26+
println("Instances per config: $dataset_per_config")
27+
28+
# Option 1: Automatic comparison of all available solvers
29+
# println("\n=== Automatic Solver Comparison (All Available Solvers) ===")
30+
all_results = run_solver_comparison(FactoringProblem, configs;
31+
dataset_per_config=dataset_per_config)
32+
33+
# Print beautiful comparison table
34+
print_solver_comparison_summary(all_results)
35+
# Persist comparison results
36+
save_solver_comparison(FactoringProblem, all_results; note="demo run")
37+
38+
# # Option 2: Manual head-to-head comparison for detailed analysis
39+
# println("\n=== Head-to-Head Comparison (Detailed Analysis) ===")
40+
41+
# # Test BooleanInference solver
42+
# println("Benchmarking BooleanInference solver...")
43+
# bi_results = run_full_benchmark(FactoringProblem, configs;
44+
# dataset_per_config=dataset_per_config,
45+
# solver=BooleanInferenceSolver())
46+
47+
# # Test IP solver
48+
# println("Benchmarking IP solver...")
49+
# ip_results = run_full_benchmark(FactoringProblem, configs;
50+
# dataset_per_config=dataset_per_config,
51+
# solver=IPSolver(Gurobi.Optimizer, GUROBI_ENV))
52+
53+
# # Side-by-side comparison
54+
# println("\nHead-to-Head Results:")
55+
# compare_solver_results("BooleanInference", bi_results, "IP-Gurobi", ip_results)
56+
57+
# println("\nAll benchmarks completed!")
58+
# println("Datasets cached in: benchmarks/data/factoring/")
59+
# println("Results saved in: benchmarks/data/factoring_benchmark_results.json")
60+
61+
# println("\nFor quick one-liner comparisons, you can also use:")
62+
# println(" # Compare all solvers automatically:")
63+
# println(" run_solver_comparison(FactoringProblem, [(10,10)])")
64+
# println(" # Or test a specific solver:")
65+
# println(" run_full_benchmark(FactoringProblem, [(10,10)]; solver=BooleanInferenceSolver())")
66+
end
67+
68+
if abspath(PROGRAM_FILE) == @__FILE__
69+
main()
70+
end

0 commit comments

Comments
 (0)