diff --git a/Makefile b/Makefile
index 08cb517a..ba8af969 100644
--- a/Makefile
+++ b/Makefile
@@ -176,9 +176,9 @@ run-plan:
@NL=$$'\n'; \
BRANCH=$$(git branch --show-current); \
if [ "$(AGENT_TYPE)" = "claude" ]; then \
- PROCESS="1. Read the plan file$${NL}2. Use /subagent-driven-development to execute tasks$${NL}3. Push: git push origin $$BRANCH$${NL}4. Post summary"; \
+ PROCESS="1. Read the plan file$${NL}2. Use /subagent-driven-development to execute tasks$${NL}3. Push: git push origin $$BRANCH$${NL}4. Create a pull request"; \
else \
- PROCESS="1. Read the plan file$${NL}2. Execute the tasks step by step. For each task, implement and test before moving on.$${NL}3. Push: git push origin $$BRANCH$${NL}4. Post summary"; \
+ PROCESS="1. Read the plan file$${NL}2. Execute the tasks step by step. For each task, implement and test before moving on.$${NL}3. Push: git push origin $$BRANCH$${NL}4. Create a pull request"; \
fi; \
PROMPT="Execute the plan in '$${PLAN_FILE}'."; \
if [ -n "$(INSTRUCTIONS)" ]; then \
diff --git a/docs/src/design.md b/docs/src/design.md
index af92f855..024ef636 100644
--- a/docs/src/design.md
+++ b/docs/src/design.md
@@ -46,9 +46,7 @@ Every problem implements `Problem`. Optimization problems additionally implement
## Variant System
-A single problem name like `MaximumIndependentSet` can have multiple **variants** — carrying weights on vertices, or defined on a restricted topology (e.g., king's subgraph). Some variants are more specific than others: the king's subgraph is a special case of the unit-disk graph, which is a special case of the simple graph.
-
-In **set** language, variants form **subsets**: independent sets on king's subgraphs are a subset of independent sets on unit-disk graphs. The reduction from a more specific variant to a less specific one is a **variant cast** — an identity mapping where vertex/element indices are preserved. Each such cast is explicitly declared as a `#[reduction]` registration using the `impl_variant_reduction!` macro.
+A single problem name like `MaximumIndependentSet` can have multiple **variants** — carrying weights on vertices, or defined on a restricted topology (e.g., king's subgraph). Variants form a subtype hierarchy: independent sets on king's subgraphs are a subset of independent sets on unit-disk graphs. The reduction from a more specific variant to a less specific one is a **variant cast** — an identity mapping where indices are preserved.
@@ -61,7 +59,7 @@ In **set** language, variants form **subsets**: independent sets on king's subgr
-Arrows indicate the **subset** (subtype) direction. Variant types fall into three categories:
+Variant types fall into three categories:
- **Graph type** — `HyperGraph` (root), `SimpleGraph`, `PlanarGraph`, `BipartiteGraph`, `UnitDiskGraph`, `KingsSubgraph`, `TriangularSubgraph`.
- **Weight type** — `One` (unweighted), `i32`, `f64`.
@@ -78,6 +76,9 @@ Arrows indicate the **subset** (subtype) direction. Variant types fall into thre
+
+Implementation details: VariantParam trait and macros
+
### VariantParam trait
Each variant parameter type implements `VariantParam`, which declares its category, value, and optional parent:
@@ -123,7 +124,7 @@ impl_variant_param!(K3, "k", parent: KN, cast: |_| KN, k: Some(3));
### Variant cast reductions with `impl_variant_reduction!`
-When a more specific variant (e.g., `KingsSubgraph`) needs to be treated as a less specific one (e.g., `UnitDiskGraph`), an explicit variant cast reduction is declared using `impl_variant_reduction!`. This generates a `ReduceTo` impl with `#[reduction]` registration and identity overhead:
+When a more specific variant needs to be treated as a less specific one, an explicit variant cast reduction is declared:
```rust
impl_variant_reduction!(
@@ -135,8 +136,6 @@ impl_variant_reduction!(
);
```
-The problem name appears once, followed by ` => `. This works with any number of type parameters. All variant casts use `ReductionAutoCast` for identity solution mapping (vertex/element indices are preserved) and `ReductionOverhead::identity()` for the overhead fields.
-
### Composing `Problem::variant()`
The `variant_params!` macro composes the `Problem::variant()` body from type parameter names:
@@ -150,13 +149,13 @@ fn variant() -> Vec<(&'static str, &'static str)> {
}
```
+
+
## Reduction Rules
A reduction requires two pieces: a **result struct** and a **`ReduceTo` impl**.
-### Result struct
-
-Holds the target problem and the logic to map solutions back:
+The result struct holds the target problem and the logic to map solutions back:
```rust
#[derive(Debug, Clone)]
@@ -175,7 +174,7 @@ impl ReductionResult for ReductionISToVC {
}
```
-### `ReduceTo` impl with the `#[reduction]` macro
+The `#[reduction]` macro on the `ReduceTo` impl registers the reduction in the compile-time graph:
```rust
#[reduction(
@@ -194,7 +193,8 @@ impl ReduceTo>
}
```
-### What the macro generates
+
+What the #[reduction] macro generates
The `#[reduction]` attribute expands to the original `impl` block plus an `inventory::submit!` call:
@@ -220,106 +220,69 @@ inventory::submit! {
This `ReductionEntry` is collected at compile time by `inventory`, making the reduction discoverable by the `ReductionGraph` without any manual registration. The `reduce_fn` field provides a type-erased executor that enables runtime-discovered paths to chain reductions automatically.
-## Reduction Graph
-
-The `ReductionGraph` is the central runtime data structure. It collects all registered reductions to enable path finding and overhead evaluation.
-
-### Construction
+
-`ReductionGraph::new()` scans `inventory::iter::` and builds a variant-level `petgraph::DiGraph`:
-
-- **Nodes** are unique `(problem_name, variant)` pairs — e.g., `("MaximumIndependentSet", {graph: "KingsSubgraph", weight: "i32"})`. Different variants of the same problem are separate nodes.
-- **Edges** come exclusively from `#[reduction]` registrations. This includes both cross-problem reductions (e.g., MIS → QUBO) and variant casts (e.g., MIS on KingsSubgraph → MIS on UnitDiskGraph).
-
-There are no auto-generated edges. Every edge in the graph corresponds to an explicit `ReduceTo` impl in the source code.
-
-### JSON export
-
-`ReductionGraph::to_json()` produces a `ReductionGraphJson` with all variant nodes and reduction edges:
-
-- [reduction_graph.json](reductions/reduction_graph.json) — all problem variants and reduction edges
-- [problem_schemas.json](reductions/problem_schemas.json) — field definitions for each problem type
-
-## Path Finding
-
-Path finding operates on the variant-level graph. Since each node is a `(name, variant)` pair, the graph directly encodes which variant transitions are possible.
-
-### Name-level paths
-
-`find_paths_by_name(src, dst)` enumerates all simple paths between any variant of the source and any variant of the target, deduplicating consecutive same-name nodes to produce name-level paths. `find_shortest_path_by_name()` returns the one with fewest hops.
-
-### Dijkstra with cost functions
+## Reduction Graph
-For cost-aware routing, `find_cheapest_path()` uses **Dijkstra's algorithm** over the variant-level graph:
+`ReductionGraph::new()` scans `inventory::iter::` and builds a variant-level directed graph:
-```rust
-pub fn find_cheapest_path(
- &self,
- source: &str, // problem name
- source_variant: &BTreeMap, // exact variant
- target: &str,
- target_variant: &BTreeMap,
- input_size: &ProblemSize,
- cost_fn: &C,
-) -> Option
-```
+- **Nodes** are unique `(problem_name, variant)` pairs — e.g., `("MaximumIndependentSet", {graph: "KingsSubgraph", weight: "i32"})`.
+- **Edges** come exclusively from `#[reduction]` registrations — both cross-problem reductions and variant casts. There are no auto-generated edges.
-The variant maps specify the exact source and target nodes in the variant-level graph. Use `ReductionGraph::variant_to_map(&T::variant())` to convert a `Problem::variant()` slice into the required `BTreeMap`. Since variant casts are explicit edges with identity overhead, Dijkstra naturally traverses them when they appear on the cheapest path.
+### Path finding
-### Cost functions
+All path-finding operates on **exact variant nodes**. Use `ReductionGraph::variant_to_map(&T::variant())` to convert a `Problem::variant()` into the required `BTreeMap`.
-The `PathCostFn` trait computes edge cost from overhead and current problem size:
+| Method | Algorithm | Use case |
+|--------|-----------|----------|
+| `find_cheapest_path(src, src_var, dst, dst_var, input_size, cost_fn)` | Dijkstra | Optimal path under a cost function |
+| `find_all_paths(src, src_var, dst, dst_var)` | All simple paths | Enumerate every route |
-```rust
-pub trait PathCostFn {
- fn edge_cost(&self, overhead: &ReductionOverhead, current_size: &ProblemSize) -> f64;
-}
-```
+Use `find_cheapest_path` with `MinimizeSteps` for fewest-hops search.
-Built-in implementations:
+The `PathCostFn` trait (used by `find_cheapest_path`) computes edge cost from overhead and current problem size:
| Cost function | Strategy |
|--------------|----------|
-| `Minimize("field")` | Minimize a single output field |
-| `MinimizeWeighted([(field, w)])` | Weighted sum of output fields |
-| `MinimizeMax([fields])` | Minimize the maximum of fields |
-| `MinimizeLexicographic([fields])` | Lexicographic: minimize first, break ties with rest |
| `MinimizeSteps` | Minimize number of hops (unit edge cost) |
+| `Minimize("field")` | Minimize a single output field |
| `CustomCost(closure)` | User-defined cost function |
-### Example: MIS on KingsSubgraph to MinimumVertexCover
-
-Finding a path from `MIS{KingsSubgraph, i32}` to `VC{SimpleGraph, i32}`:
+**Example:** Finding a path from `MIS{KingsSubgraph, i32}` to `VC{SimpleGraph, i32}`:
```
MIS{KingsSubgraph,i32} -> MIS{UnitDiskGraph,i32} -> MIS{SimpleGraph,i32} -> VC{SimpleGraph,i32}
variant cast variant cast reduction
```
-Each variant cast is an explicit edge registered via `impl_variant_reduction!`, so the path finder treats all edges uniformly.
+### Executable paths
-## Overhead Evaluation
+Convert a `ReductionPath` into a typed `ExecutablePath` via `make_executable()`, then call `reduce()`:
-Each reduction declares how the output problem size relates to the input size, expressed as polynomials.
+```rust
+let rpath = graph.find_cheapest_path("Factoring", &src_var,
+ "SpinGlass", &dst_var, &ProblemSize::new(vec![]), &MinimizeSteps).unwrap();
+let path = graph.make_executable::>(&rpath).unwrap();
-### ProblemSize
+let reduction = path.reduce(&factoring_instance);
+let target = reduction.target_problem();
+let solution = reduction.extract_solution(&target_solution);
+```
-A `ProblemSize` holds named size components — the dimensions that characterize a problem instance:
+Internally, `ExecutablePath` holds a type-erased executor per edge. `ChainedReduction` stores intermediate results and extracts solutions back through the chain in reverse.
-```rust
-let size = ProblemSize::new(vec![("num_vertices", 10), ("num_edges", 15)]);
-assert_eq!(size.get("num_vertices"), Some(10));
-```
+For full type control, you can also chain `ReduceTo::reduce_to()` calls manually at each step.
-### Polynomials
+
+Overhead evaluation
-Output size formulas use `Polynomial` (a sum of `Monomial` terms). The `poly!` macro provides a concise syntax:
+Each reduction declares how the output problem size relates to the input, expressed as polynomials. The `poly!` macro provides concise syntax:
```rust
poly!(num_vertices) // p(x) = num_vertices
-poly!(num_vertices ^ 2) // p(x) = num_vertices^2
-poly!(3 * num_edges) // p(x) = 3 * num_edges
-poly!(num_vertices * num_edges) // p(x) = num_vertices * num_edges
+poly!(num_vertices ^ 2) // p(x) = num_vertices²
+poly!(3 * num_edges) // p(x) = 3 × num_edges
+poly!(num_vertices * num_edges) // p(x) = num_vertices × num_edges
```
A `ReductionOverhead` pairs output field names with their polynomials:
@@ -331,60 +294,16 @@ ReductionOverhead::new(vec![
])
```
-### Evaluating overhead
-
-`ReductionOverhead::evaluate_output_size(input)` substitutes input values into the polynomials and returns a new `ProblemSize`:
+`evaluate_output_size(input)` substitutes input values:
```
Input: ProblemSize { num_vertices: 10, num_edges: 15 }
Output: ProblemSize { num_vars: 25, num_clauses: 45 }
```
-### Composing through a path
-
-For a multi-step reduction path, overhead composes: the output of step $N$ becomes the input of step $N+1$. Each edge carries its own `ReductionOverhead`, so the total output size is computed by chaining `evaluate_output_size` calls through the path. Variant cast edges use `ReductionOverhead::identity()`, passing through all fields unchanged.
-
-## Reduction Execution
-
-The library provides two approaches to executing reductions: **manual chaining** for full type control, and **executable paths** for automatic multi-step reduction.
-
-### Executable paths (automatic)
-
-First find a `ReductionPath` with exact variant matching, then convert it to an `ExecutablePath` via `make_executable()`. Call `reduce()` to execute the path:
-
-```rust
-let graph = ReductionGraph::new();
-let src_var = ReductionGraph::variant_to_map(&KSatisfiability::::variant());
-let dst_var = ReductionGraph::variant_to_map(
- &MaximumIndependentSet::::variant());
-let rpath = graph
- .find_cheapest_path("KSatisfiability", &src_var,
- "MaximumIndependentSet", &dst_var,
- &ProblemSize::new(vec![]), &MinimizeSteps)
- .unwrap();
-let path = graph
- .make_executable::, MaximumIndependentSet>(&rpath)
- .unwrap();
-
-let reduction = path.reduce(&ksat_instance);
-let target = reduction.target_problem(); // &MaximumIndependentSet
-let solution = reduction.extract_solution(&target_solution);
-```
+For multi-step paths, overhead composes: the output of step N becomes the input of step N+1. Variant cast edges use `ReductionOverhead::identity()`, passing through all fields unchanged.
-Internally, `ExecutablePath` holds a `Vec Box>` — one type-erased executor per edge. `make_executable` looks up each edge's `reduce_fn` along the `ReductionPath` steps. Each `reduce_fn` is generated by the `#[reduction]` macro and downcasts the source, calls `ReduceTo::reduce_to()`, and boxes the result. `ChainedReduction` stores the intermediate results and extracts solutions back through the chain in reverse.
-
-### Manual chaining
-
-For full type control, call `ReduceTo::reduce_to()` at each step manually:
-
-```rust
-let r0 = ReduceTo::>::reduce_to(&ksat);
-let r1 = ReduceTo::::reduce_to(r0.target_problem());
-let r2 = ReduceTo::>::reduce_to(r1.target_problem());
-// ... solve r2.target_problem(), then extract in reverse
-```
-
-Both cross-problem reductions and variant casts are dispatched uniformly through `ReduceTo`. Variant cast reductions use `ReductionAutoCast`, which passes indices through unchanged (identity mapping).
+
## Solvers
@@ -397,18 +316,10 @@ pub trait Solver {
}
```
-### BruteForce
-
-Enumerates every configuration in the space defined by `dims()`. Suitable for small instances (<20 variables). In addition to the `Solver` trait methods, provides:
-
-- `find_all_best(problem)` — returns all tied-optimal configurations.
-- `find_all_satisfying(problem)` — returns all satisfying configurations.
-
-Primarily used for **testing and verification** of reductions via closed-loop tests.
-
-### ILPSolver
-
-Feature-gated behind `ilp`. Uses the HiGHS solver via the `good_lp` crate. Additionally provides `solve_reduced()` for problems that implement `ReduceTo` — it reduces, solves the ILP, and extracts the solution in one call.
+| Solver | Description |
+|--------|-------------|
+| **BruteForce** | Enumerates all configurations. Also provides `find_all_best()` and `find_all_satisfying()`. Used for testing and verification. |
+| **ILPSolver** | Feature-gated (`ilp`). Uses HiGHS via `good_lp`. Also provides `solve_reduced()` for problems that implement `ReduceTo`. |
## JSON Serialization
@@ -421,17 +332,12 @@ let json = to_json(&problem)?;
let restored: MaximumIndependentSet = from_json(&json)?;
```
-**Exported JSON files:**
+Exported files:
+
- [reduction_graph.json](reductions/reduction_graph.json) — all problem variants and reduction edges
- [problem_schemas.json](reductions/problem_schemas.json) — field definitions for each problem type
-Regenerate exports:
-
-```bash
-cargo run --example export_graph # docs/src/reductions/reduction_graph.json (default)
-cargo run --example export_graph -- output.json # custom output path
-cargo run --example export_schemas # docs/src/reductions/problem_schemas.json
-```
+Regenerate with `cargo run --example export_graph` and `cargo run --example export_schemas`.
## Contributing
diff --git a/docs/src/reductions/reduction_graph.json b/docs/src/reductions/reduction_graph.json
index 6a47ddb8..01d93b98 100644
--- a/docs/src/reductions/reduction_graph.json
+++ b/docs/src/reductions/reduction_graph.json
@@ -757,6 +757,17 @@
],
"doc_path": "rules/spinglass_qubo/index.html"
},
+ {
+ "source": 21,
+ "target": 0,
+ "overhead": [
+ {
+ "field": "num_variables",
+ "formula": "num_variables + num_clauses + 1"
+ }
+ ],
+ "doc_path": "rules/sat_circuitsat/index.html"
+ },
{
"source": 21,
"target": 3,
diff --git a/examples/chained_reduction_factoring_to_spinglass.rs b/examples/chained_reduction_factoring_to_spinglass.rs
new file mode 100644
index 00000000..fe6dfa3e
--- /dev/null
+++ b/examples/chained_reduction_factoring_to_spinglass.rs
@@ -0,0 +1,51 @@
+// # Chained Reduction: Factoring -> SpinGlass
+//
+// Mirrors Julia's examples/Ising.jl — reduces a Factoring problem
+// to SpinGlass via the reduction graph, then solves and extracts the factors.
+// Uses ILPSolver for the solve step (Julia uses GenericTensorNetworks).
+
+// ANCHOR: imports
+use problemreductions::prelude::*;
+use problemreductions::rules::{MinimizeSteps, ReductionGraph};
+use problemreductions::solvers::ILPSolver;
+use problemreductions::topology::SimpleGraph;
+use problemreductions::types::ProblemSize;
+// ANCHOR_END: imports
+
+pub fn run() {
+ // ANCHOR: example
+ let graph = ReductionGraph::new();
+
+ // Find reduction path: Factoring -> ... -> SpinGlass
+ let src_var = ReductionGraph::variant_to_map(&Factoring::variant());
+ let dst_var =
+ ReductionGraph::variant_to_map(&SpinGlass::::variant());
+ let rpath = graph
+ .find_cheapest_path(
+ "Factoring",
+ &src_var,
+ "SpinGlass",
+ &dst_var,
+ &ProblemSize::new(vec![]),
+ &MinimizeSteps,
+ )
+ .unwrap();
+ println!("Reduction path: {:?}", rpath.type_names());
+
+ // Create: factor 6 = p × q with 2-bit factors (mirrors Julia's Factoring(2, 2, 6))
+ let factoring = Factoring::new(2, 2, 6);
+
+ // Solve Factoring via ILP
+ let solver = ILPSolver::new();
+ let solution = solver.solve_reduced(&factoring).unwrap();
+
+ // Extract and display the factors
+ let (p, q) = factoring.read_factors(&solution);
+ println!("{} = {} × {}", factoring.target(), p, q);
+ assert_eq!(p * q, 6, "Factors should multiply to 6");
+ // ANCHOR_END: example
+}
+
+fn main() {
+ run()
+}
diff --git a/scripts/jl/generate_testdata.jl b/scripts/jl/generate_testdata.jl
index d66e24e3..c15f02eb 100644
--- a/scripts/jl/generate_testdata.jl
+++ b/scripts/jl/generate_testdata.jl
@@ -2,7 +2,7 @@
# Generate JSON test fixtures from ProblemReductions.jl for Rust parity testing.
# Run: cd scripts/jl && julia --project=. generate_testdata.jl
-using ProblemReductions, Graphs, JSON
+using ProblemReductions, Graphs, JSON, Random
const OUTDIR = joinpath(@__DIR__, "..", "..", "tests", "data", "jl")
mkpath(OUTDIR)
@@ -274,6 +274,165 @@ function export_setcovering(sc, label)
return make_instance(label, inst, sc)
end
+"""Evaluate BicliqueCover with flat binary config (matching Rust convention).
+
+Config layout: for each vertex v (0-based) and biclique b (0-based),
+config[v*k + b + 1] == 1 means vertex v is in biclique b.
+Returns (is_valid, size) where is_valid means all edges covered,
+and size is the total number of 1s in the config.
+"""
+function biclique_cover_evaluate(left_size, right_size, edges_0based, k, config)
+ n = left_size + right_size
+ # Check all edges are covered
+ for (l, r) in edges_0based
+ covered = false
+ for b in 0:k-1
+ l_in = config[l * k + b + 1] == 1
+ r_in = config[r * k + b + 1] == 1
+ if l_in && r_in
+ covered = true
+ break
+ end
+ end
+ if !covered
+ return (false, 0)
+ end
+ end
+ return (true, sum(config))
+end
+
+function export_biclique_cover(graph, left_part, k, label)
+ left_size = length(left_part)
+ right_size = nv(graph) - left_size
+ edges_0 = graph_to_edges(graph)
+ n = nv(graph)
+ num_vars = n * k
+
+ inst = Dict(
+ "num_vertices" => n,
+ "edges" => edges_0,
+ "left_size" => left_size,
+ "right_size" => right_size,
+ "k" => k,
+ )
+
+ # Sample configs
+ configs = Set{Vector{Int}}()
+ push!(configs, zeros(Int, num_vars))
+ push!(configs, ones(Int, num_vars))
+ while length(configs) < min(10, 2^num_vars)
+ push!(configs, [rand(0:1) for _ in 1:num_vars])
+ end
+ configs = collect(configs)
+
+ # Evaluate configs
+ evals = []
+ for cfg in configs
+ (valid, sz) = biclique_cover_evaluate(left_size, right_size, edges_0, k, cfg)
+ push!(evals, Dict("config" => cfg, "is_valid" => valid, "size" => valid ? sz : 0))
+ end
+
+ # Brute force: find all best (minimize size among valid covers)
+ best_size = typemax(Int)
+ best_configs = Vector{Int}[]
+ for bits in 0:(2^num_vars - 1)
+ cfg = [(bits >> i) & 1 for i in 0:num_vars-1]
+ (valid, sz) = biclique_cover_evaluate(left_size, right_size, edges_0, k, cfg)
+ if valid
+ if sz < best_size
+ best_size = sz
+ best_configs = [cfg]
+ elseif sz == best_size
+ push!(best_configs, cfg)
+ end
+ end
+ end
+
+ return Dict(
+ "label" => label,
+ "instance" => inst,
+ "evaluations" => evals,
+ "best_solutions" => best_configs,
+ )
+end
+
+"""Evaluate BMF with flat binary config (matching Rust convention).
+
+Config layout: first m*k bits are B (row-major), next k*n bits are C (row-major).
+Returns hamming distance between A and boolean_product(B, C).
+All configs are valid.
+"""
+function bmf_evaluate(A, m, n, k, config)
+ # Extract B (m x k)
+ B = zeros(Bool, m, k)
+ for i in 1:m, j in 1:k
+ B[i,j] = config[(i-1)*k + j] == 1
+ end
+ b_size = m * k
+ # Extract C (k x n)
+ C = zeros(Bool, k, n)
+ for i in 1:k, j in 1:n
+ C[i,j] = config[b_size + (i-1)*n + j] == 1
+ end
+ # Boolean product
+ product = zeros(Bool, m, n)
+ for i in 1:m, j in 1:n
+ product[i,j] = any(kk -> B[i,kk] && C[kk,j], 1:k)
+ end
+ # Hamming distance
+ return sum(A .!= product)
+end
+
+function export_bmf(A, k, label)
+ m, n = size(A)
+ num_vars = m * k + k * n
+
+ inst = Dict(
+ "matrix" => [[Int(A[i,j]) for j in 1:n] for i in 1:m],
+ "m" => m,
+ "n" => n,
+ "k" => k,
+ )
+
+ # Sample configs
+ configs = Set{Vector{Int}}()
+ push!(configs, zeros(Int, num_vars))
+ push!(configs, ones(Int, num_vars))
+ while length(configs) < min(10, 2^num_vars)
+ push!(configs, [rand(0:1) for _ in 1:num_vars])
+ end
+ configs = collect(configs)
+
+ # Evaluate configs
+ evals = []
+ for cfg in configs
+ dist = bmf_evaluate(A, m, n, k, cfg)
+ # All configs are valid for BMF; size = hamming distance
+ push!(evals, Dict("config" => cfg, "is_valid" => true, "size" => dist))
+ end
+
+ # Brute force: find all best (minimize hamming distance)
+ best_dist = typemax(Int)
+ best_configs = Vector{Int}[]
+ for bits in 0:(2^num_vars - 1)
+ cfg = [(bits >> i) & 1 for i in 0:num_vars-1]
+ dist = bmf_evaluate(A, m, n, k, cfg)
+ if dist < best_dist
+ best_dist = dist
+ best_configs = [cfg]
+ elseif dist == best_dist
+ push!(best_configs, cfg)
+ end
+ end
+
+ return Dict(
+ "label" => label,
+ "instance" => inst,
+ "evaluations" => evals,
+ "best_solutions" => best_configs,
+ )
+end
+
# ── reduction exports ────────────────────────────────────────────────
function export_reduction(source, target_type, source_label)
@@ -306,6 +465,7 @@ end
# ── main ─────────────────────────────────────────────────────────────
function main()
+ Random.seed!(42) # pin seed so re-runs produce identical fixtures
println("Generating Julia parity test data...")
# ── Build test instances (matching Julia test/rules/rules.jl) ──
@@ -370,6 +530,16 @@ function main()
# SetCovering docstring
doc_sc = SetCovering([[1, 2, 3], [2, 4], [1, 4]], [1, 2, 3])
+ # BicliqueCover: 6-vertex bipartite graph, 2 bicliques (from Julia test)
+ doc_bc_graph = SimpleGraph(6)
+ for (i,j) in [(1,5), (1,4), (2,5), (2,4), (3,6)]
+ add_edge!(doc_bc_graph, i, j)
+ end
+ doc_bc = BicliqueCover(doc_bc_graph, [1,2,3], 2)
+
+ # BMF: 3x3 all-ones matrix, rank 2 (from Julia test)
+ doc_bmf = BinaryMatrixFactorization(trues(3, 3), 2)
+
# ── Individual rule test instances (from test/rules/*.jl) ──
rule_graph4 = SimpleGraph(Graphs.SimpleEdge.([(1, 2), (1, 3), (3, 4), (2, 3)]))
@@ -547,6 +717,16 @@ function main()
export_setcovering(doc_sc, "doc_3subsets"),
]))
+ # BicliqueCover
+ write_fixture("biclique_cover.json", model_fixture("BicliqueCover", [
+ export_biclique_cover(doc_bc_graph, [1,2,3], 2, "doc_6vertex"),
+ ]))
+
+ # BMF
+ write_fixture("bmf.json", model_fixture("BMF", [
+ export_bmf(trues(3, 3), 2, "doc_3x3_ones"),
+ ]))
+
# ── Export reduction fixtures ──
println("Exporting reduction fixtures...")
@@ -622,6 +802,62 @@ function main()
write_fixture(filename, data)
end
+ # ── Export reduction path fixtures (deterministic, skip if already exist) ──
+ println("Exporting reduction path fixtures...")
+
+ g = reduction_graph()
+
+ if !isfile(joinpath(OUTDIR, "path_maxcut_to_spinglass.json"))
+ # MaxCut → SpinGlass path
+ mc_source = MaxCut(smallgraph(:petersen))
+ mc_paths = reduction_paths(g, MaxCut, SpinGlass)
+ mc_res = reduceto(mc_paths[1], mc_source)
+ mc_best_source = findbest(mc_source, BruteForce())
+ mc_best_target = findbest(target_problem(mc_res), BruteForce())
+ mc_extracted = sort(unique(extract_solution.(Ref(mc_res), mc_best_target)))
+
+ write_fixture("path_maxcut_to_spinglass.json", Dict(
+ "path" => string.(typeof.(mc_paths[1].nodes)),
+ "best_source" => mc_best_source,
+ "best_target" => mc_best_target,
+ "extracted" => mc_extracted,
+ ))
+
+ # MaxCut → QUBO path (uses same mc_source/mc_best_source)
+ mc_qubo_paths = reduction_paths(g, MaxCut, QUBO)
+ mc_qubo_res = reduceto(mc_qubo_paths[1], mc_source)
+ mc_qubo_best_target = findbest(target_problem(mc_qubo_res), BruteForce())
+ mc_qubo_extracted = sort(unique(extract_solution.(Ref(mc_qubo_res), mc_qubo_best_target)))
+
+ write_fixture("path_maxcut_to_qubo.json", Dict(
+ "path" => string.(typeof.(mc_qubo_paths[1].nodes)),
+ "best_source" => mc_best_source,
+ "extracted" => mc_qubo_extracted,
+ ))
+ else
+ println(" skipping path_maxcut_to_*.json (already exist)")
+ end
+
+ if !isfile(joinpath(OUTDIR, "path_factoring_to_spinglass.json"))
+ # Factoring → SpinGlass path (slow BruteForce on SpinGlass target)
+ fact = Factoring(2, 1, 3)
+ fact_paths = reduction_paths(g, Factoring, SpinGlass)
+ fact_res = reduceto(fact_paths[1], fact)
+ fact_best_target = findbest(target_problem(fact_res), BruteForce())
+ fact_extracted = sort(unique(filter(
+ sol -> solution_size(fact, sol) == SolutionSize(0, true),
+ extract_solution.(Ref(fact_res), fact_best_target)
+ )))
+
+ write_fixture("path_factoring_to_spinglass.json", Dict(
+ "path" => string.(typeof.(fact_paths[1].nodes)),
+ "best_source" => findbest(fact, BruteForce()),
+ "extracted" => fact_extracted,
+ ))
+ else
+ println(" skipping path_factoring_to_spinglass.json (already exists)")
+ end
+
println("Done! Generated fixtures in $OUTDIR")
end
diff --git a/src/rules/cost.rs b/src/rules/cost.rs
index 66588877..0cbf1bf1 100644
--- a/src/rules/cost.rs
+++ b/src/rules/cost.rs
@@ -18,48 +18,6 @@ impl PathCostFn for Minimize {
}
}
-/// Minimize weighted sum of output fields.
-pub struct MinimizeWeighted(pub Vec<(&'static str, f64)>);
-
-impl PathCostFn for MinimizeWeighted {
- fn edge_cost(&self, overhead: &ReductionOverhead, size: &ProblemSize) -> f64 {
- let output = overhead.evaluate_output_size(size);
- self.0
- .iter()
- .map(|(field, weight)| weight * output.get(field).unwrap_or(0) as f64)
- .sum()
- }
-}
-
-/// Minimize the maximum of specified fields.
-pub struct MinimizeMax(pub Vec<&'static str>);
-
-impl PathCostFn for MinimizeMax {
- fn edge_cost(&self, overhead: &ReductionOverhead, size: &ProblemSize) -> f64 {
- let output = overhead.evaluate_output_size(size);
- self.0
- .iter()
- .map(|field| output.get(field).unwrap_or(0) as f64)
- .fold(0.0, f64::max)
- }
-}
-
-/// Lexicographic: minimize first field, break ties with subsequent.
-pub struct MinimizeLexicographic(pub Vec<&'static str>);
-
-impl PathCostFn for MinimizeLexicographic {
- fn edge_cost(&self, overhead: &ReductionOverhead, size: &ProblemSize) -> f64 {
- let output = overhead.evaluate_output_size(size);
- let mut cost = 0.0;
- let mut scale = 1.0;
- for field in &self.0 {
- cost += scale * output.get(field).unwrap_or(0) as f64;
- scale *= 1e-10;
- }
- cost
- }
-}
-
/// Minimize number of reduction steps.
pub struct MinimizeSteps;
diff --git a/src/rules/graph.rs b/src/rules/graph.rs
index a297da3e..fdd6fadd 100644
--- a/src/rules/graph.rs
+++ b/src/rules/graph.rs
@@ -381,71 +381,36 @@ impl ReductionGraph {
ReductionPath { steps }
}
- /// Find all paths from source to target type.
+ /// Find all simple paths between two specific problem variants.
///
- /// Uses `Problem::NAME` for lookup. With variant-level nodes, this finds
- /// paths between any variant of S and any variant of T.
- pub fn find_paths(
+ /// Uses `all_simple_paths` on the variant-level graph from the exact
+ /// source variant node to the exact target variant node.
+ pub fn find_all_paths(
&self,
+ source: &str,
+ source_variant: &BTreeMap,
+ target: &str,
+ target_variant: &BTreeMap,
) -> Vec {
- self.find_paths_by_name(S::NAME, T::NAME)
- }
-
- /// Find all paths between problem types by name.
- pub fn find_paths_by_name(&self, src: &str, dst: &str) -> Vec {
- let src_nodes = match self.name_to_nodes.get(src) {
- Some(nodes) => nodes.clone(),
+ let src = match self.lookup_node(source, source_variant) {
+ Some(idx) => idx,
None => return vec![],
};
- let dst_nodes = match self.name_to_nodes.get(dst) {
- Some(nodes) => nodes.clone(),
+ let dst = match self.lookup_node(target, target_variant) {
+ Some(idx) => idx,
None => return vec![],
};
- let mut all_paths = Vec::new();
-
- for &src_idx in &src_nodes {
- for &dst_idx in &dst_nodes {
- let paths: Vec> = all_simple_paths::<
- Vec,
- _,
- std::hash::RandomState,
- >(
- &self.graph, src_idx, dst_idx, 0, None
- )
- .collect();
-
- for path in paths {
- let steps: Vec = path
- .iter()
- .map(|&idx| {
- let node = &self.nodes[self.graph[idx]];
- ReductionStep {
- name: node.name.to_string(),
- variant: node.variant.clone(),
- }
- })
- .collect();
- all_paths.push(ReductionPath { steps });
- }
- }
- }
-
- all_paths
- }
-
- /// Find the shortest path from source to target type.
- pub fn find_shortest_path(
- &self,
- ) -> Option {
- let paths = self.find_paths::();
- paths.into_iter().min_by_key(|p| p.len())
- }
+ let paths: Vec> =
+ all_simple_paths::, _, std::hash::RandomState>(
+ &self.graph, src, dst, 0, None,
+ )
+ .collect();
- /// Find the shortest path by name.
- pub fn find_shortest_path_by_name(&self, src: &str, dst: &str) -> Option {
- let paths = self.find_paths_by_name(src, dst);
- paths.into_iter().min_by_key(|p| p.len())
+ paths
+ .iter()
+ .map(|p| self.node_path_to_reduction_path(p))
+ .collect()
}
/// Check if a direct reduction exists from S to T.
@@ -825,3 +790,7 @@ impl ReductionGraph {
#[cfg(test)]
#[path = "../unit_tests/rules/graph.rs"]
mod tests;
+
+#[cfg(test)]
+#[path = "../unit_tests/rules/reduction_path_parity.rs"]
+mod reduction_path_parity_tests;
diff --git a/src/rules/mod.rs b/src/rules/mod.rs
index e2ce2a7c..48113302 100644
--- a/src/rules/mod.rs
+++ b/src/rules/mod.rs
@@ -2,10 +2,7 @@
pub mod cost;
pub mod registry;
-pub use cost::{
- CustomCost, Minimize, MinimizeLexicographic, MinimizeMax, MinimizeSteps, MinimizeWeighted,
- PathCostFn,
-};
+pub use cost::{CustomCost, Minimize, MinimizeSteps, PathCostFn};
pub use registry::{ReductionEntry, ReductionOverhead};
mod circuit_spinglass;
@@ -26,6 +23,7 @@ mod maximumsetpacking_qubo;
mod minimumvertexcover_maximumindependentset;
mod minimumvertexcover_minimumsetcovering;
mod minimumvertexcover_qubo;
+mod sat_circuitsat;
mod sat_coloring;
mod sat_ksat;
mod sat_maximumindependentset;
@@ -80,6 +78,7 @@ pub use maximumsetpacking_qubo::ReductionSPToQUBO;
pub use minimumvertexcover_maximumindependentset::{ReductionISToVC, ReductionVCToIS};
pub use minimumvertexcover_minimumsetcovering::ReductionVCToSC;
pub use minimumvertexcover_qubo::ReductionVCToQUBO;
+pub use sat_circuitsat::ReductionSATToCircuit;
pub use sat_coloring::ReductionSATToColoring;
pub use sat_ksat::{ReductionKSATToSAT, ReductionSATToKSAT};
pub use sat_maximumindependentset::{BoolVar, ReductionSATToIS};
diff --git a/src/rules/sat_circuitsat.rs b/src/rules/sat_circuitsat.rs
new file mode 100644
index 00000000..a7a39597
--- /dev/null
+++ b/src/rules/sat_circuitsat.rs
@@ -0,0 +1,125 @@
+//! Reduction from Satisfiability to CircuitSAT.
+//!
+//! Converts a CNF formula into a boolean circuit by creating
+//! an OR gate for each clause and a final AND gate.
+
+use crate::models::satisfiability::Satisfiability;
+use crate::models::specialized::{Assignment, BooleanExpr, Circuit, CircuitSAT};
+use crate::poly;
+use crate::reduction;
+use crate::rules::registry::ReductionOverhead;
+use crate::rules::traits::{ReduceTo, ReductionResult};
+use crate::traits::Problem;
+
+/// Result of reducing SAT to CircuitSAT.
+#[derive(Debug, Clone)]
+pub struct ReductionSATToCircuit {
+ target: CircuitSAT,
+ /// Indices of original SAT variables in the CircuitSAT variable list.
+ source_var_indices: Vec,
+}
+
+impl ReductionResult for ReductionSATToCircuit {
+ type Source = Satisfiability;
+ type Target = CircuitSAT;
+
+ fn target_problem(&self) -> &CircuitSAT {
+ &self.target
+ }
+
+ fn extract_solution(&self, target_solution: &[usize]) -> Vec {
+ self.source_var_indices
+ .iter()
+ .map(|&idx| target_solution[idx])
+ .collect()
+ }
+}
+
+#[reduction(
+ overhead = {
+ ReductionOverhead::new(vec![
+ ("num_variables", poly!(num_variables) + poly!(num_clauses) + poly!(1)),
+ ])
+ }
+)]
+impl ReduceTo for Satisfiability {
+ type Result = ReductionSATToCircuit;
+
+ fn reduce_to(&self) -> Self::Result {
+ let num_vars = self.num_variables();
+ let clauses = self.clauses();
+
+ let mut assignments = Vec::new();
+ let mut clause_outputs = Vec::new();
+
+ for (i, clause) in clauses.iter().enumerate() {
+ let clause_output = format!("__clause_{}", i);
+ let literal_exprs: Vec = clause
+ .literals
+ .iter()
+ .map(|&lit| {
+ let var_name = format!("x{}", lit.unsigned_abs());
+ let var_expr = BooleanExpr::var(&var_name);
+ if lit < 0 {
+ BooleanExpr::not(var_expr)
+ } else {
+ var_expr
+ }
+ })
+ .collect();
+
+ let clause_expr = if literal_exprs.len() == 1 {
+ literal_exprs.into_iter().next().unwrap()
+ } else {
+ BooleanExpr::or(literal_exprs)
+ };
+
+ assignments.push(Assignment::new(vec![clause_output.clone()], clause_expr));
+ clause_outputs.push(clause_output);
+ }
+
+ // Final AND gate
+ let final_output = "__out".to_string();
+ let and_expr = if clause_outputs.len() == 1 {
+ BooleanExpr::var(&clause_outputs[0])
+ } else {
+ BooleanExpr::and(
+ clause_outputs
+ .iter()
+ .map(|name| BooleanExpr::var(name))
+ .collect(),
+ )
+ };
+ assignments.push(Assignment::new(vec![final_output.clone()], and_expr));
+
+ // Constrain the final output to be true
+ assignments.push(Assignment::new(
+ vec![final_output],
+ BooleanExpr::constant(true),
+ ));
+
+ let circuit = Circuit::new(assignments);
+ let target = CircuitSAT::new(circuit);
+
+ // Map SAT variable indices to CircuitSAT variable indices
+ let var_names = target.variable_names();
+ let source_var_indices: Vec = (1..=num_vars)
+ .map(|i| {
+ let name = format!("x{}", i);
+ var_names
+ .iter()
+ .position(|n| n == &name)
+ .unwrap_or_else(|| panic!("Variable {} not found in CircuitSAT", name))
+ })
+ .collect();
+
+ ReductionSATToCircuit {
+ target,
+ source_var_indices,
+ }
+ }
+}
+
+#[cfg(test)]
+#[path = "../unit_tests/rules/sat_circuitsat.rs"]
+mod tests;
diff --git a/src/unit_tests/models/specialized/biclique_cover.rs b/src/unit_tests/models/specialized/biclique_cover.rs
index 034179bb..c9412e0c 100644
--- a/src/unit_tests/models/specialized/biclique_cover.rs
+++ b/src/unit_tests/models/specialized/biclique_cover.rs
@@ -3,6 +3,8 @@ use crate::solvers::BruteForce;
use crate::traits::{OptimizationProblem, Problem};
use crate::types::{Direction, SolutionSize};
+include!("../../jl_helpers.rs");
+
#[test]
fn test_biclique_cover_creation() {
let problem = BicliqueCover::new(2, 2, vec![(0, 2), (0, 3), (1, 2)], 2);
@@ -171,3 +173,43 @@ fn test_biclique_problem() {
SolutionSize::Valid(0)
);
}
+
+#[test]
+fn test_jl_parity_evaluation() {
+ let data: serde_json::Value =
+ serde_json::from_str(include_str!("../../../../tests/data/jl/biclique_cover.json"))
+ .unwrap();
+ for instance in data["instances"].as_array().unwrap() {
+ let left_size = instance["instance"]["left_size"].as_u64().unwrap() as usize;
+ let right_size = instance["instance"]["right_size"].as_u64().unwrap() as usize;
+ let edges = jl_parse_edges(&instance["instance"]);
+ let k = instance["instance"]["k"].as_u64().unwrap() as usize;
+ let problem = BicliqueCover::new(left_size, right_size, edges, k);
+ for eval in instance["evaluations"].as_array().unwrap() {
+ let config = jl_parse_config(&eval["config"]);
+ let result = problem.evaluate(&config);
+ let jl_valid = eval["is_valid"].as_bool().unwrap();
+ let jl_size = eval["size"].as_i64().unwrap() as i32;
+ if jl_valid {
+ assert_eq!(
+ result,
+ SolutionSize::Valid(jl_size),
+ "BicliqueCover: valid config mismatch"
+ );
+ } else {
+ assert_eq!(
+ result,
+ SolutionSize::Invalid,
+ "BicliqueCover: invalid config should be Invalid"
+ );
+ }
+ }
+ let best = BruteForce::new().find_all_best(&problem);
+ let jl_best = jl_parse_configs_set(&instance["best_solutions"]);
+ let rust_best: HashSet> = best.into_iter().collect();
+ assert_eq!(
+ rust_best, jl_best,
+ "BicliqueCover best solutions mismatch"
+ );
+ }
+}
diff --git a/src/unit_tests/models/specialized/bmf.rs b/src/unit_tests/models/specialized/bmf.rs
index f147dc10..f7e4627e 100644
--- a/src/unit_tests/models/specialized/bmf.rs
+++ b/src/unit_tests/models/specialized/bmf.rs
@@ -3,6 +3,8 @@ use crate::solvers::BruteForce;
use crate::traits::{OptimizationProblem, Problem};
use crate::types::{Direction, SolutionSize};
+include!("../../jl_helpers.rs");
+
#[test]
fn test_bmf_creation() {
let matrix = vec![vec![true, false], vec![false, true]];
@@ -204,3 +206,40 @@ fn test_bmf_problem() {
assert_eq!(Problem::evaluate(&problem, &[1, 1]), SolutionSize::Valid(0)); // Exact
assert_eq!(Problem::evaluate(&problem, &[0, 0]), SolutionSize::Valid(1)); // Distance 1
}
+
+#[test]
+fn test_jl_parity_evaluation() {
+ let data: serde_json::Value =
+ serde_json::from_str(include_str!("../../../../tests/data/jl/bmf.json")).unwrap();
+ for instance in data["instances"].as_array().unwrap() {
+ let matrix_json = instance["instance"]["matrix"].as_array().unwrap();
+ let matrix: Vec> = matrix_json
+ .iter()
+ .map(|row| {
+ row.as_array()
+ .unwrap()
+ .iter()
+ .map(|v| v.as_u64().unwrap() != 0)
+ .collect()
+ })
+ .collect();
+ let k = instance["instance"]["k"].as_u64().unwrap() as usize;
+ let problem = BMF::new(matrix, k);
+ for eval in instance["evaluations"].as_array().unwrap() {
+ let config = jl_parse_config(&eval["config"]);
+ let result = problem.evaluate(&config);
+ let jl_size = eval["size"].as_i64().unwrap() as i32;
+ // BMF always returns Valid(hamming_distance)
+ assert_eq!(
+ result,
+ SolutionSize::Valid(jl_size),
+ "BMF: size mismatch for config {:?}",
+ config
+ );
+ }
+ let best = BruteForce::new().find_all_best(&problem);
+ let jl_best = jl_parse_configs_set(&instance["best_solutions"]);
+ let rust_best: HashSet> = best.into_iter().collect();
+ assert_eq!(rust_best, jl_best, "BMF best solutions mismatch");
+ }
+}
diff --git a/src/unit_tests/reduction_graph.rs b/src/unit_tests/reduction_graph.rs
index b2c79049..5fdc7562 100644
--- a/src/unit_tests/reduction_graph.rs
+++ b/src/unit_tests/reduction_graph.rs
@@ -71,8 +71,10 @@ fn test_find_path_with_cost_function() {
fn test_multi_step_path() {
let graph = ReductionGraph::new();
- // Factoring -> CircuitSAT -> SpinGlass is a 2-step path
- let path = graph.find_shortest_path_by_name("Factoring", "SpinGlass");
+ // Factoring -> CircuitSAT -> SpinGlass is a 2-step path
+ let src = ReductionGraph::variant_to_map(&crate::models::specialized::Factoring::variant());
+ let dst = ReductionGraph::variant_to_map(&SpinGlass::::variant());
+ let path = graph.find_cheapest_path("Factoring", &src, "SpinGlass", &dst, &ProblemSize::new(vec![]), &MinimizeSteps);
assert!(
path.is_some(),
@@ -105,7 +107,9 @@ fn test_problem_size_propagation() {
assert!(path.is_some());
- let path2 = graph.find_shortest_path_by_name("MaximumIndependentSet", "MaximumSetPacking");
+ let src2 = ReductionGraph::variant_to_map(&MaximumIndependentSet::::variant());
+ let dst2 = ReductionGraph::variant_to_map(&MaximumSetPacking::::variant());
+ let path2 = graph.find_cheapest_path("MaximumIndependentSet", &src2, "MaximumSetPacking", &dst2, &ProblemSize::new(vec![]), &MinimizeSteps);
assert!(path2.is_some());
}
@@ -124,7 +128,7 @@ fn test_json_export() {
assert!(categories.len() >= 3, "Should have multiple categories");
}
-// ---- Path finding (typed API) ----
+// ---- Path finding (variant-level API) ----
#[test]
fn test_direct_reduction_exists() {
@@ -141,8 +145,10 @@ fn test_direct_reduction_exists() {
#[test]
fn test_find_direct_path() {
let graph = ReductionGraph::new();
+ let src = ReductionGraph::variant_to_map(&MaximumIndependentSet::::variant());
+ let dst = ReductionGraph::variant_to_map(&MinimumVertexCover::::variant());
- let paths = graph.find_paths::, MinimumVertexCover>();
+ let paths = graph.find_all_paths("MaximumIndependentSet", &src, "MinimumVertexCover", &dst);
assert!(!paths.is_empty());
assert!(
paths.iter().any(|p| p.len() == 1),
@@ -154,13 +160,14 @@ fn test_find_direct_path() {
#[test]
fn test_find_indirect_path() {
let graph = ReductionGraph::new();
+ let src = ReductionGraph::variant_to_map(&MaximumSetPacking::::variant());
+ let dst = ReductionGraph::variant_to_map(&MinimumVertexCover::::variant());
// MaximumSetPacking -> MaximumIndependentSet -> MinimumVertexCover
- let paths = graph.find_paths::, MinimumVertexCover>();
+ let paths = graph.find_all_paths("MaximumSetPacking", &src, "MinimumVertexCover", &dst);
assert!(!paths.is_empty());
- let shortest =
- graph.find_shortest_path::, MinimumVertexCover>();
+ let shortest = graph.find_cheapest_path("MaximumSetPacking", &src, "MinimumVertexCover", &dst, &ProblemSize::new(vec![]), &MinimizeSteps);
assert!(shortest.is_some());
assert_eq!(shortest.unwrap().len(), 2);
}
@@ -168,26 +175,24 @@ fn test_find_indirect_path() {
#[test]
fn test_no_path_exists() {
let graph = ReductionGraph::new();
+ let src = ReductionGraph::variant_to_map(&QUBO::::variant());
+ let dst = ReductionGraph::variant_to_map(&MaximumSetPacking::::variant());
- let paths = graph.find_paths::, MaximumSetPacking>();
+ let paths = graph.find_all_paths("QUBO", &src, "MaximumSetPacking", &dst);
assert!(paths.is_empty());
}
#[test]
fn test_bidirectional_paths() {
let graph = ReductionGraph::new();
+ let is_var = ReductionGraph::variant_to_map(&MaximumIndependentSet::::variant());
+ let vc_var = ReductionGraph::variant_to_map(&MinimumVertexCover::::variant());
+ let sg_var = ReductionGraph::variant_to_map(&SpinGlass::::variant());
+ let qubo_var = ReductionGraph::variant_to_map(&QUBO::::variant());
+
+ assert!(!graph.find_all_paths("MaximumIndependentSet", &is_var, "MinimumVertexCover", &vc_var).is_empty());
+ assert!(!graph.find_all_paths("MinimumVertexCover", &vc_var, "MaximumIndependentSet", &is_var).is_empty());
- assert!(!graph
- .find_paths::, MinimumVertexCover>()
- .is_empty());
- assert!(!graph
- .find_paths::, MaximumIndependentSet>()
- .is_empty());
-
- assert!(!graph
- .find_paths::, QUBO>()
- .is_empty());
- assert!(!graph
- .find_paths::, SpinGlass>()
- .is_empty());
+ assert!(!graph.find_all_paths("SpinGlass", &sg_var, "QUBO", &qubo_var).is_empty());
+ assert!(!graph.find_all_paths("QUBO", &qubo_var, "SpinGlass", &sg_var).is_empty());
}
diff --git a/src/unit_tests/rules/cost.rs b/src/unit_tests/rules/cost.rs
index 571d5cb3..8f3fdb22 100644
--- a/src/unit_tests/rules/cost.rs
+++ b/src/unit_tests/rules/cost.rs
@@ -17,17 +17,6 @@ fn test_minimize_single() {
assert_eq!(cost_fn.edge_cost(&overhead, &size), 20.0); // 2 * 10
}
-#[test]
-fn test_minimize_weighted() {
- let cost_fn = MinimizeWeighted(vec![("n", 1.0), ("m", 2.0)]);
- let size = ProblemSize::new(vec![("n", 10), ("m", 5)]);
- let overhead = test_overhead();
-
- // output n = 20, output m = 5
- // cost = 1.0 * 20 + 2.0 * 5 = 30
- assert_eq!(cost_fn.edge_cost(&overhead, &size), 30.0);
-}
-
#[test]
fn test_minimize_steps() {
let cost_fn = MinimizeSteps;
@@ -37,29 +26,6 @@ fn test_minimize_steps() {
assert_eq!(cost_fn.edge_cost(&overhead, &size), 1.0);
}
-#[test]
-fn test_minimize_max() {
- let cost_fn = MinimizeMax(vec!["n", "m"]);
- let size = ProblemSize::new(vec![("n", 10), ("m", 5)]);
- let overhead = test_overhead();
-
- // output n = 20, output m = 5
- // max(20, 5) = 20
- assert_eq!(cost_fn.edge_cost(&overhead, &size), 20.0);
-}
-
-#[test]
-fn test_minimize_lexicographic() {
- let cost_fn = MinimizeLexicographic(vec!["n", "m"]);
- let size = ProblemSize::new(vec![("n", 10), ("m", 5)]);
- let overhead = test_overhead();
-
- // output n = 20, output m = 5
- // cost = 20 * 1.0 + 5 * 1e-10 = 20.0000000005
- let cost = cost_fn.edge_cost(&overhead, &size);
- assert!(cost > 20.0 && cost < 20.001);
-}
-
#[test]
fn test_custom_cost() {
let cost_fn = CustomCost(|overhead: &ReductionOverhead, size: &ProblemSize| {
@@ -83,11 +49,3 @@ fn test_minimize_missing_field() {
assert_eq!(cost_fn.edge_cost(&overhead, &size), 0.0);
}
-#[test]
-fn test_minimize_max_empty() {
- let cost_fn = MinimizeMax(vec![]);
- let size = ProblemSize::new(vec![("n", 10)]);
- let overhead = test_overhead();
-
- assert_eq!(cost_fn.edge_cost(&overhead, &size), 0.0);
-}
diff --git a/src/unit_tests/rules/graph.rs b/src/unit_tests/rules/graph.rs
index 56d0a625..7420ad44 100644
--- a/src/unit_tests/rules/graph.rs
+++ b/src/unit_tests/rules/graph.rs
@@ -6,11 +6,15 @@ use crate::rules::cost::MinimizeSteps;
use crate::rules::graph::{classify_problem_category, ReductionStep};
use crate::topology::SimpleGraph;
use crate::traits::Problem;
+use crate::types::ProblemSize;
+use std::collections::BTreeMap;
#[test]
fn test_find_direct_path() {
let graph = ReductionGraph::new();
- let paths = graph.find_paths::, MinimumVertexCover>();
+ let src = ReductionGraph::variant_to_map(&MaximumIndependentSet::::variant());
+ let dst = ReductionGraph::variant_to_map(&MinimumVertexCover::::variant());
+ let paths = graph.find_all_paths("MaximumIndependentSet", &src, "MinimumVertexCover", &dst);
assert!(!paths.is_empty());
// At least one path should be a direct reduction (1 edge = 2 steps)
let shortest = paths.iter().min_by_key(|p| p.len()).unwrap();
@@ -21,17 +25,18 @@ fn test_find_direct_path() {
#[test]
fn test_find_indirect_path() {
let graph = ReductionGraph::new();
- // IS -> SP directly
- let paths =
- graph.find_paths::, MaximumSetPacking>();
+ let src = ReductionGraph::variant_to_map(&MaximumIndependentSet::::variant());
+ let dst = ReductionGraph::variant_to_map(&MaximumSetPacking::::variant());
+ let paths = graph.find_all_paths("MaximumIndependentSet", &src, "MaximumSetPacking", &dst);
assert!(!paths.is_empty());
}
#[test]
fn test_find_shortest_path() {
let graph = ReductionGraph::new();
- let path = graph
- .find_shortest_path::, MaximumSetPacking>();
+ let src = ReductionGraph::variant_to_map(&MaximumIndependentSet::::variant());
+ let dst = ReductionGraph::variant_to_map(&MaximumSetPacking::::variant());
+ let path = graph.find_cheapest_path("MaximumIndependentSet", &src, "MaximumSetPacking", &dst, &ProblemSize::new(vec![]), &MinimizeSteps);
assert!(path.is_some());
let path = path.unwrap();
assert_eq!(path.len(), 1); // Direct path exists
@@ -47,44 +52,45 @@ fn test_has_direct_reduction() {
#[test]
fn test_is_to_qubo_path() {
let graph = ReductionGraph::new();
- // IS -> QUBO should now have a direct path
- let path =
- graph.find_shortest_path::, crate::models::optimization::QUBO>();
+ let src = ReductionGraph::variant_to_map(&MaximumIndependentSet::::variant());
+ let dst = ReductionGraph::variant_to_map(&QUBO::::variant());
+ let path = graph.find_cheapest_path("MaximumIndependentSet", &src, "QUBO", &dst, &ProblemSize::new(vec![]), &MinimizeSteps);
assert!(path.is_some());
assert_eq!(path.unwrap().len(), 1); // Direct path
}
#[test]
-fn test_type_erased_paths() {
+fn test_variant_level_paths() {
let graph = ReductionGraph::new();
- // With variant-level nodes, same-name different-variant are different nodes.
- // But find_paths searches all variants of a given name, so both should find paths.
- let paths_i32 = graph.find_paths::<
- crate::models::graph::MaxCut,
- crate::models::optimization::SpinGlass,
- >();
- let paths_f64 = graph.find_paths::<
- crate::models::graph::MaxCut,
- crate::models::optimization::SpinGlass,
- >();
+ // Variant-level path: MaxCut -> SpinGlass
+ let src = ReductionGraph::variant_to_map(&crate::models::graph::MaxCut::::variant());
+ let dst = ReductionGraph::variant_to_map(&crate::models::optimization::SpinGlass::::variant());
+ let paths = graph.find_all_paths("MaxCut", &src, "SpinGlass", &dst);
+ assert!(!paths.is_empty());
+ assert_eq!(paths[0].type_names(), vec!["MaxCut", "SpinGlass"]);
- // Both should find paths
- assert!(!paths_i32.is_empty());
- assert!(!paths_f64.is_empty());
- // Name-level paths should be the same (MaxCut -> SpinGlass)
- assert_eq!(paths_i32[0].type_names(), paths_f64[0].type_names());
+ // Unregistered variant pair returns no paths
+ let src_f64 = ReductionGraph::variant_to_map(&crate::models::graph::MaxCut::::variant());
+ let dst_f64 = ReductionGraph::variant_to_map(&crate::models::optimization::SpinGlass::::variant());
+ let paths_f64 = graph.find_all_paths("MaxCut", &src_f64, "SpinGlass", &dst_f64);
+ // No direct MaxCut -> SpinGlass reduction registered
+ assert!(paths_f64.is_empty());
}
#[test]
-fn test_find_paths_by_name() {
+fn test_find_shortest_path_variants() {
let graph = ReductionGraph::new();
- let shortest = graph.find_shortest_path_by_name("MaxCut", "SpinGlass");
+ let src = ReductionGraph::variant_to_map(&crate::models::graph::MaxCut::::variant());
+ let dst = ReductionGraph::variant_to_map(&crate::models::optimization::SpinGlass::::variant());
+ let shortest = graph.find_cheapest_path("MaxCut", &src, "SpinGlass", &dst, &ProblemSize::new(vec![]), &MinimizeSteps);
assert!(shortest.is_some());
assert_eq!(shortest.unwrap().len(), 1); // Direct path
- let shortest = graph.find_shortest_path_by_name("Factoring", "SpinGlass");
+ let src = ReductionGraph::variant_to_map(&crate::models::specialized::Factoring::variant());
+ let dst = ReductionGraph::variant_to_map(&crate::models::optimization::SpinGlass::::variant());
+ let shortest = graph.find_cheapest_path("Factoring", &src, "SpinGlass", &dst, &ProblemSize::new(vec![]), &MinimizeSteps);
assert!(shortest.is_some());
assert_eq!(shortest.unwrap().len(), 2); // Factoring -> CircuitSAT -> SpinGlass
}
@@ -110,8 +116,10 @@ fn test_graph_statistics() {
#[test]
fn test_reduction_path_methods() {
let graph = ReductionGraph::new();
+ let src = ReductionGraph::variant_to_map(&MaximumIndependentSet::::variant());
+ let dst = ReductionGraph::variant_to_map(&MinimumVertexCover::::variant());
let path = graph
- .find_shortest_path::, MinimumVertexCover>()
+ .find_cheapest_path("MaximumIndependentSet", &src, "MinimumVertexCover", &dst, &ProblemSize::new(vec![]), &MinimizeSteps)
.unwrap();
assert!(!path.is_empty());
@@ -122,13 +130,15 @@ fn test_reduction_path_methods() {
#[test]
fn test_bidirectional_paths() {
let graph = ReductionGraph::new();
+ let is_var = ReductionGraph::variant_to_map(&MaximumIndependentSet::::variant());
+ let vc_var = ReductionGraph::variant_to_map(&MinimumVertexCover::::variant());
// Forward path
- let forward = graph.find_paths::, MinimumVertexCover>();
+ let forward = graph.find_all_paths("MaximumIndependentSet", &is_var, "MinimumVertexCover", &vc_var);
assert!(!forward.is_empty());
// Backward path
- let backward = graph.find_paths::, MaximumIndependentSet>();
+ let backward = graph.find_all_paths("MinimumVertexCover", &vc_var, "MaximumIndependentSet", &is_var);
assert!(!backward.is_empty());
}
@@ -263,12 +273,12 @@ fn test_circuit_reductions() {
// CircuitSAT -> SpinGlass
assert!(graph.has_direct_reduction::>());
- // Find path from Factoring to SpinGlass
- let paths = graph.find_paths::>();
+ // Find path from Factoring to SpinGlass
+ let src = ReductionGraph::variant_to_map(&Factoring::variant());
+ let dst = ReductionGraph::variant_to_map(&SpinGlass::::variant());
+ let paths = graph.find_all_paths("Factoring", &src, "SpinGlass", &dst);
assert!(!paths.is_empty());
- let shortest = graph
- .find_shortest_path::>()
- .unwrap();
+ let shortest = graph.find_cheapest_path("Factoring", &src, "SpinGlass", &dst, &ProblemSize::new(vec![]), &MinimizeSteps).unwrap();
assert_eq!(shortest.len(), 2); // Factoring -> CircuitSAT -> SpinGlass
}
@@ -377,6 +387,8 @@ fn test_to_json_file() {
#[test]
fn test_unknown_name_returns_empty() {
let graph = ReductionGraph::new();
+ let unknown = BTreeMap::new();
+ let is_var = ReductionGraph::variant_to_map(&MaximumIndependentSet::::variant());
// Unknown source
assert!(!graph.has_direct_reduction_by_name("UnknownProblem", "MaximumIndependentSet"));
@@ -385,17 +397,17 @@ fn test_unknown_name_returns_empty() {
// Both unknown
assert!(!graph.has_direct_reduction_by_name("UnknownA", "UnknownB"));
- // find_paths with unknown name
+ // find_all_paths with unknown name
assert!(graph
- .find_paths_by_name("UnknownProblem", "MaximumIndependentSet")
+ .find_all_paths("UnknownProblem", &unknown, "MaximumIndependentSet", &is_var)
.is_empty());
assert!(graph
- .find_paths_by_name("MaximumIndependentSet", "UnknownProblem")
+ .find_all_paths("MaximumIndependentSet", &is_var, "UnknownProblem", &unknown)
.is_empty());
// find_shortest_path with unknown name
assert!(graph
- .find_shortest_path_by_name("UnknownProblem", "MaximumIndependentSet")
+ .find_cheapest_path("UnknownProblem", &unknown, "MaximumIndependentSet", &is_var, &ProblemSize::new(vec![]), &MinimizeSteps)
.is_none());
}
@@ -702,11 +714,10 @@ fn test_classify_problem_category() {
#[test]
fn test_make_executable_direct() {
let graph = ReductionGraph::new();
+ let src = ReductionGraph::variant_to_map(&MaximumIndependentSet::::variant());
+ let dst = ReductionGraph::variant_to_map(&MinimumVertexCover::::variant());
let rpath = graph
- .find_shortest_path::<
- MaximumIndependentSet,
- MinimumVertexCover,
- >()
+ .find_cheapest_path("MaximumIndependentSet", &src, "MinimumVertexCover", &dst, &ProblemSize::new(vec![]), &MinimizeSteps)
.unwrap();
let path = graph.make_executable::<
MaximumIndependentSet,
@@ -721,11 +732,10 @@ fn test_chained_reduction_direct() {
use crate::traits::Problem;
let graph = ReductionGraph::new();
+ let src = ReductionGraph::variant_to_map(&MaximumIndependentSet::::variant());
+ let dst = ReductionGraph::variant_to_map(&MinimumVertexCover::::variant());
let rpath = graph
- .find_shortest_path::<
- MaximumIndependentSet,
- MinimumVertexCover,
- >()
+ .find_cheapest_path("MaximumIndependentSet", &src, "MinimumVertexCover", &dst, &ProblemSize::new(vec![]), &MinimizeSteps)
.unwrap();
let path = graph
.make_executable::<
@@ -751,11 +761,10 @@ fn test_chained_reduction_multi_step() {
use crate::traits::Problem;
let graph = ReductionGraph::new();
+ let src = ReductionGraph::variant_to_map(&MaximumIndependentSet::::variant());
+ let dst = ReductionGraph::variant_to_map(&MaximumSetPacking::::variant());
let rpath = graph
- .find_shortest_path::<
- MaximumIndependentSet,
- MaximumSetPacking,
- >()
+ .find_cheapest_path("MaximumIndependentSet", &src, "MaximumSetPacking", &dst, &ProblemSize::new(vec![]), &MinimizeSteps)
.unwrap();
let path = graph
.make_executable::<
@@ -787,7 +796,7 @@ fn test_chained_reduction_with_variant_casts() {
let graph = ReductionGraph::new();
// MIS -> MIS (variant cast) -> MVC
- // Use find_cheapest_path for exact variant matching (not name-based find_shortest_path)
+ // Use find_cheapest_path for exact variant matching (not name-based)
let src_var = ReductionGraph::variant_to_map(&MaximumIndependentSet::::variant());
let dst_var = ReductionGraph::variant_to_map(&MinimumVertexCover::::variant());
let rpath = graph.find_cheapest_path(
@@ -827,7 +836,7 @@ fn test_chained_reduction_with_variant_casts() {
assert!(metric.is_valid());
// Also test the KSat -> Sat -> MIS multi-step path
- // Use find_cheapest_path for exact variant matching (find_shortest_path is name-based
+ // Use find_cheapest_path for exact variant matching (not name-based
// and may pick a path through a different KSat variant)
let ksat_src = ReductionGraph::variant_to_map(&KSatisfiability::::variant());
let ksat_dst = ReductionGraph::variant_to_map(&MaximumIndependentSet::::variant());
diff --git a/src/unit_tests/rules/reduction_path_parity.rs b/src/unit_tests/rules/reduction_path_parity.rs
new file mode 100644
index 00000000..90989d53
--- /dev/null
+++ b/src/unit_tests/rules/reduction_path_parity.rs
@@ -0,0 +1,173 @@
+//! Reduction path parity tests — mirrors Julia's test/reduction_path.jl.
+//! Verifies that chained reductions via `find_cheapest_path` + `make_executable`
+//! produce correct solutions matching direct source solves.
+
+use crate::models::graph::MaxCut;
+use crate::models::optimization::{SpinGlass, QUBO};
+use crate::models::specialized::Factoring;
+use crate::rules::{MinimizeSteps, ReductionGraph};
+use crate::solvers::{BruteForce, Solver};
+use crate::topology::SimpleGraph;
+use crate::traits::Problem;
+use crate::types::ProblemSize;
+use std::collections::HashSet;
+
+/// Julia: paths = reduction_paths(MaxCut, SpinGlass)
+/// Julia: res = reduceto(paths[1], MaxCut(smallgraph(:petersen)))
+#[test]
+fn test_jl_parity_maxcut_to_spinglass_path() {
+ let graph = ReductionGraph::new();
+ let src_var = ReductionGraph::variant_to_map(&MaxCut::::variant());
+ let dst_var = ReductionGraph::variant_to_map(&SpinGlass::::variant());
+ let rpath = graph
+ .find_cheapest_path(
+ "MaxCut",
+ &src_var,
+ "SpinGlass",
+ &dst_var,
+ &ProblemSize::new(vec![]),
+ &MinimizeSteps,
+ )
+ .expect("Should find path MaxCut -> SpinGlass");
+ let path = graph
+ .make_executable::, SpinGlass>(&rpath)
+ .expect("Should make executable path");
+
+ // Petersen graph: 10 vertices, 15 edges
+ let petersen_edges = vec![
+ (0, 1),
+ (0, 4),
+ (0, 5),
+ (1, 2),
+ (1, 6),
+ (2, 3),
+ (2, 7),
+ (3, 4),
+ (3, 8),
+ (4, 9),
+ (5, 7),
+ (5, 8),
+ (6, 8),
+ (6, 9),
+ (7, 9),
+ ];
+ let source =
+ MaxCut::::unweighted(SimpleGraph::new(10, petersen_edges));
+ let reduction = path.reduce(&source);
+ let target = reduction.target_problem();
+
+ // Verify target is SpinGlass
+ assert_eq!(SpinGlass::::NAME, "SpinGlass");
+
+ let solver = BruteForce::new();
+ let target_solution = solver.find_best(target).unwrap();
+ let source_solution = reduction.extract_solution(&target_solution);
+
+ // Source solution should be valid
+ let metric = source.evaluate(&source_solution);
+ assert!(metric.is_valid());
+}
+
+/// Julia: paths = reduction_paths(MaxCut, QUBO)
+/// Julia: sort(extract_solution.(Ref(res), best2)) == sort(best1)
+#[test]
+fn test_jl_parity_maxcut_to_qubo_path() {
+ let graph = ReductionGraph::new();
+ let src_var = ReductionGraph::variant_to_map(&MaxCut::::variant());
+ let dst_var = ReductionGraph::variant_to_map(&QUBO::::variant());
+ let rpath = graph
+ .find_cheapest_path(
+ "MaxCut",
+ &src_var,
+ "QUBO",
+ &dst_var,
+ &ProblemSize::new(vec![]),
+ &MinimizeSteps,
+ )
+ .expect("Should find path MaxCut -> QUBO");
+ let path = graph
+ .make_executable::, QUBO>(&rpath)
+ .expect("Should make executable path");
+
+ // Use a small graph for brute-force feasibility
+ let petersen_edges = vec![
+ (0, 1),
+ (0, 4),
+ (0, 5),
+ (1, 2),
+ (1, 6),
+ (2, 3),
+ (2, 7),
+ (3, 4),
+ (3, 8),
+ (4, 9),
+ (5, 7),
+ (5, 8),
+ (6, 8),
+ (6, 9),
+ (7, 9),
+ ];
+ let source =
+ MaxCut::::unweighted(SimpleGraph::new(10, petersen_edges));
+ let reduction = path.reduce(&source);
+
+ let solver = BruteForce::new();
+ let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect();
+ let best_target = solver.find_all_best(reduction.target_problem());
+
+ // Julia: sort(extract_solution.(Ref(res), best2)) == sort(best1)
+ let extracted: HashSet> = best_target
+ .iter()
+ .map(|t| reduction.extract_solution(t))
+ .collect();
+ assert_eq!(
+ extracted, best_source,
+ "MaxCut->QUBO path: extracted solutions should match direct source solutions"
+ );
+}
+
+/// Julia: factoring = Factoring(2, 1, 3)
+/// Julia: paths = reduction_paths(Factoring, SpinGlass)
+/// Julia: all(solution_size.(Ref(factoring), extract_solution.(Ref(res), sol)) .== Ref(SolutionSize(0, true)))
+#[test]
+fn test_jl_parity_factoring_to_spinglass_path() {
+ let graph = ReductionGraph::new();
+ let src_var = ReductionGraph::variant_to_map(&Factoring::variant());
+ let dst_var = ReductionGraph::variant_to_map(&SpinGlass::::variant());
+ let rpath = graph
+ .find_cheapest_path(
+ "Factoring",
+ &src_var,
+ "SpinGlass",
+ &dst_var,
+ &ProblemSize::new(vec![]),
+ &MinimizeSteps,
+ )
+ .expect("Should find path Factoring -> SpinGlass");
+ let path = graph
+ .make_executable::>(&rpath)
+ .expect("Should make executable path");
+
+ // Julia: Factoring(2, 1, 3) — factor 3 with 2-bit x 1-bit
+ let factoring = Factoring::new(2, 1, 3);
+ let reduction = path.reduce(&factoring);
+ let target = reduction.target_problem();
+
+ let solver = BruteForce::new();
+ let best_target = solver.find_all_best(target);
+
+ // Every extracted solution should satisfy factoring (distance = 0)
+ for sol in &best_target {
+ let source_sol = reduction.extract_solution(sol);
+ let metric = factoring.evaluate(&source_sol);
+ assert_eq!(
+ metric.unwrap(),
+ 0,
+ "Factoring->SpinGlass path: extracted solution should have distance 0"
+ );
+ }
+ assert!(
+ !best_target.is_empty(),
+ "Should find at least one SpinGlass solution"
+ );
+}
diff --git a/src/unit_tests/rules/sat_circuitsat.rs b/src/unit_tests/rules/sat_circuitsat.rs
new file mode 100644
index 00000000..11c28432
--- /dev/null
+++ b/src/unit_tests/rules/sat_circuitsat.rs
@@ -0,0 +1,97 @@
+use super::*;
+use crate::models::satisfiability::{CNFClause, Satisfiability};
+use crate::models::specialized::CircuitSAT;
+use crate::rules::ReduceTo;
+use crate::solvers::BruteForce;
+use crate::traits::Problem;
+use std::collections::HashSet;
+
+#[test]
+fn test_sat_to_circuitsat_closed_loop() {
+ // 3-variable SAT: (x1 v !x2 v x3) & (!x1 v x2 v !x3)
+ let sat = Satisfiability::new(
+ 3,
+ vec![
+ CNFClause::new(vec![1, -2, 3]),
+ CNFClause::new(vec![-1, 2, -3]),
+ ],
+ );
+ let result = ReduceTo::::reduce_to(&sat);
+ let solver = BruteForce::new();
+
+ // All satisfying assignments of the circuit should map back to SAT solutions
+ let best_target = solver.find_all_satisfying(result.target_problem());
+ assert!(!best_target.is_empty(), "CircuitSAT should have solutions");
+
+ let extracted: HashSet> = best_target
+ .iter()
+ .map(|t| result.extract_solution(t))
+ .collect();
+ let sat_solutions: HashSet> =
+ solver.find_all_satisfying(&sat).into_iter().collect();
+
+ // Every extracted solution must satisfy the original SAT
+ for sol in &extracted {
+ assert!(sat.evaluate(sol), "Extracted solution must satisfy SAT");
+ }
+ // The extracted set should equal all SAT solutions
+ assert_eq!(extracted, sat_solutions);
+}
+
+#[test]
+fn test_sat_to_circuitsat_unsatisfiable() {
+ // Unsatisfiable: (x1) & (!x1)
+ let sat = Satisfiability::new(
+ 1,
+ vec![CNFClause::new(vec![1]), CNFClause::new(vec![-1])],
+ );
+ let result = ReduceTo::::reduce_to(&sat);
+ let solver = BruteForce::new();
+ let best_target = solver.find_all_satisfying(result.target_problem());
+ assert!(
+ best_target.is_empty(),
+ "Unsatisfiable SAT -> CircuitSAT should have no solutions"
+ );
+}
+
+#[test]
+fn test_sat_to_circuitsat_single_clause() {
+ // Single clause: (x1 v x2)
+ let sat = Satisfiability::new(2, vec![CNFClause::new(vec![1, 2])]);
+ let result = ReduceTo::::reduce_to(&sat);
+ let solver = BruteForce::new();
+
+ let best_target = solver.find_all_satisfying(result.target_problem());
+ let extracted: HashSet> = best_target
+ .iter()
+ .map(|t| result.extract_solution(t))
+ .collect();
+ let sat_solutions: HashSet> =
+ solver.find_all_satisfying(&sat).into_iter().collect();
+
+ assert_eq!(extracted, sat_solutions);
+}
+
+#[test]
+fn test_sat_to_circuitsat_single_literal_clause() {
+ // Single literal clause: (x1) & (x2)
+ let sat = Satisfiability::new(
+ 2,
+ vec![CNFClause::new(vec![1]), CNFClause::new(vec![2])],
+ );
+ let result = ReduceTo::::reduce_to(&sat);
+ let solver = BruteForce::new();
+
+ let best_target = solver.find_all_satisfying(result.target_problem());
+ let extracted: HashSet> = best_target
+ .iter()
+ .map(|t| result.extract_solution(t))
+ .collect();
+ let sat_solutions: HashSet> =
+ solver.find_all_satisfying(&sat).into_iter().collect();
+
+ // Only solution should be x1=1, x2=1
+ assert_eq!(extracted, sat_solutions);
+ assert_eq!(sat_solutions.len(), 1);
+ assert!(sat_solutions.contains(&vec![1, 1]));
+}
diff --git a/tests/data/jl/biclique_cover.json b/tests/data/jl/biclique_cover.json
new file mode 100644
index 00000000..06054b5c
--- /dev/null
+++ b/tests/data/jl/biclique_cover.json
@@ -0,0 +1 @@
+{"instances":[{"label":"doc_6vertex","instance":{"left_size":3,"k":2,"num_vertices":6,"right_size":3,"edges":[[0,3],[0,4],[1,3],[1,4],[2,5]]},"evaluations":[{"is_valid":false,"config":[1,0,0,0,1,1,1,1,0,1,1,0],"size":0},{"is_valid":false,"config":[0,1,1,0,0,1,0,1,0,1,1,0],"size":0},{"is_valid":true,"config":[1,1,1,1,1,1,1,1,1,1,1,1],"size":12},{"is_valid":false,"config":[1,1,1,1,0,1,0,0,0,1,1,1],"size":0},{"is_valid":false,"config":[0,0,0,0,0,0,1,0,1,1,0,0],"size":0},{"is_valid":false,"config":[0,0,0,1,1,1,1,0,1,1,0,1],"size":0},{"is_valid":false,"config":[1,1,0,1,0,0,1,0,0,1,1,0],"size":0},{"is_valid":false,"config":[0,1,1,0,1,1,1,0,1,0,1,1],"size":0},{"is_valid":false,"config":[0,0,0,0,0,0,0,0,0,0,0,0],"size":0},{"is_valid":false,"config":[0,0,1,0,0,1,0,1,0,0,0,0],"size":0}],"best_solutions":[[1,0,1,0,1,0,1,0,1,0,1,0],[0,1,0,1,1,0,0,1,0,1,1,0],[1,0,1,0,0,1,1,0,1,0,0,1],[0,1,0,1,0,1,0,1,0,1,0,1]]}],"problem_type":"BicliqueCover"}
\ No newline at end of file
diff --git a/tests/data/jl/bmf.json b/tests/data/jl/bmf.json
new file mode 100644
index 00000000..e92047bd
--- /dev/null
+++ b/tests/data/jl/bmf.json
@@ -0,0 +1 @@
+{"instances":[{"label":"doc_3x3_ones","instance":{"m":3,"k":2,"matrix":[[1,1,1],[1,1,1],[1,1,1]],"n":3},"evaluations":[{"is_valid":true,"config":[1,1,0,1,0,0,1,0,1,1,1,0],"size":4},{"is_valid":true,"config":[0,1,1,0,1,0,1,1,0,1,1,1],"size":2},{"is_valid":true,"config":[1,0,0,0,0,1,0,1,0,0,1,1],"size":6},{"is_valid":true,"config":[1,1,1,1,1,1,1,1,1,1,1,1],"size":0},{"is_valid":true,"config":[1,0,1,0,0,0,0,1,0,0,0,1],"size":7},{"is_valid":true,"config":[0,1,0,0,0,1,0,1,0,1,0,0],"size":7},{"is_valid":true,"config":[1,1,0,0,0,1,0,1,0,0,0,1],"size":6},{"is_valid":true,"config":[0,0,0,0,0,0,0,0,0,0,0,0],"size":9},{"is_valid":true,"config":[0,1,1,1,0,0,0,0,0,1,0,0],"size":7},{"is_valid":true,"config":[1,0,1,0,0,1,1,0,0,0,0,0],"size":7}],"best_solutions":[[1,0,1,0,1,0,1,1,1,0,0,0],[1,1,1,0,1,0,1,1,1,0,0,0],[1,0,1,1,1,0,1,1,1,0,0,0],[1,1,1,1,1,0,1,1,1,0,0,0],[1,0,1,0,1,1,1,1,1,0,0,0],[1,1,1,0,1,1,1,1,1,0,0,0],[1,0,1,1,1,1,1,1,1,0,0,0],[1,1,1,1,1,1,1,1,1,0,0,0],[1,1,1,1,1,1,0,1,1,1,0,0],[1,0,1,0,1,0,1,1,1,1,0,0],[1,1,1,0,1,0,1,1,1,1,0,0],[1,0,1,1,1,0,1,1,1,1,0,0],[1,1,1,1,1,0,1,1,1,1,0,0],[1,0,1,0,1,1,1,1,1,1,0,0],[1,1,1,0,1,1,1,1,1,1,0,0],[1,0,1,1,1,1,1,1,1,1,0,0],[1,1,1,1,1,1,1,1,1,1,0,0],[1,1,1,1,1,1,1,0,1,0,1,0],[1,0,1,0,1,0,1,1,1,0,1,0],[1,1,1,0,1,0,1,1,1,0,1,0],[1,0,1,1,1,0,1,1,1,0,1,0],[1,1,1,1,1,0,1,1,1,0,1,0],[1,0,1,0,1,1,1,1,1,0,1,0],[1,1,1,0,1,1,1,1,1,0,1,0],[1,0,1,1,1,1,1,1,1,0,1,0],[1,1,1,1,1,1,1,1,1,0,1,0],[1,1,1,1,1,1,0,0,1,1,1,0],[1,1,1,1,1,1,1,0,1,1,1,0],[1,1,1,1,1,1,0,1,1,1,1,0],[1,0,1,0,1,0,1,1,1,1,1,0],[1,1,1,0,1,0,1,1,1,1,1,0],[1,0,1,1,1,0,1,1,1,1,1,0],[1,1,1,1,1,0,1,1,1,1,1,0],[1,0,1,0,1,1,1,1,1,1,1,0],[1,1,1,0,1,1,1,1,1,1,1,0],[1,0,1,1,1,1,1,1,1,1,1,0],[1,1,1,1,1,1,1,1,1,1,1,0],[1,1,1,1,1,1,1,1,0,0,0,1],[1,0,1,0,1,0,1,1,1,0,0,1],[1,1,1,0,1,0,1,1,1,0,0,1],[1,0,1,1,1,0,1,1,1,0,0,1],[1,1,1,1,1,0,1,1,1,0,0,1],[1,0,1,0,1,1,1,1,1,0,0,1],[1,1,1,0,1,1,1,1,1,0,0,1],[1,0,1,1,1,1,1,1,1,0,0,1],[1,1,1,1,1,1,1,1,1,0,0,1],[1,1,1,1,1,1,0,1,0,1,0,1],[1,1,1,1,1,1,1,1,0,1,0,1],[1,1,1,1,1,1,0,1,1,1,0,1],[1,0,1,0,1,0,1,1,1,1,0,1],[1,1,1,0,1,0,1,1,1,1,0,1],[1,0,1,1,1,0,1,1,1,1,0,1],[1,1,1,1,1,0,1,1,1,1,0,1],[1,0,1,0,1,1,1,1,1,1,0,1],[1,1,1,0,1,1,1,1,1,1,0,1],[1,0,1,1,1,1,1,1,1,1,0,1],[1,1,1,1,1,1,1,1,1,1,0,1],[1,1,1,1,1,1,1,0,0,0,1,1],[1,1,1,1,1,1,1,1,0,0,1,1],[1,1,1,1,1,1,1,0,1,0,1,1],[1,0,1,0,1,0,1,1,1,0,1,1],[1,1,1,0,1,0,1,1,1,0,1,1],[1,0,1,1,1,0,1,1,1,0,1,1],[1,1,1,1,1,0,1,1,1,0,1,1],[1,0,1,0,1,1,1,1,1,0,1,1],[1,1,1,0,1,1,1,1,1,0,1,1],[1,0,1,1,1,1,1,1,1,0,1,1],[1,1,1,1,1,1,1,1,1,0,1,1],[0,1,0,1,0,1,0,0,0,1,1,1],[1,1,0,1,0,1,0,0,0,1,1,1],[0,1,1,1,0,1,0,0,0,1,1,1],[1,1,1,1,0,1,0,0,0,1,1,1],[0,1,0,1,1,1,0,0,0,1,1,1],[1,1,0,1,1,1,0,0,0,1,1,1],[0,1,1,1,1,1,0,0,0,1,1,1],[1,1,1,1,1,1,0,0,0,1,1,1],[0,1,0,1,0,1,1,0,0,1,1,1],[1,1,0,1,0,1,1,0,0,1,1,1],[0,1,1,1,0,1,1,0,0,1,1,1],[1,1,1,1,0,1,1,0,0,1,1,1],[0,1,0,1,1,1,1,0,0,1,1,1],[1,1,0,1,1,1,1,0,0,1,1,1],[0,1,1,1,1,1,1,0,0,1,1,1],[1,1,1,1,1,1,1,0,0,1,1,1],[0,1,0,1,0,1,0,1,0,1,1,1],[1,1,0,1,0,1,0,1,0,1,1,1],[0,1,1,1,0,1,0,1,0,1,1,1],[1,1,1,1,0,1,0,1,0,1,1,1],[0,1,0,1,1,1,0,1,0,1,1,1],[1,1,0,1,1,1,0,1,0,1,1,1],[0,1,1,1,1,1,0,1,0,1,1,1],[1,1,1,1,1,1,0,1,0,1,1,1],[0,1,0,1,0,1,1,1,0,1,1,1],[1,1,0,1,0,1,1,1,0,1,1,1],[0,1,1,1,0,1,1,1,0,1,1,1],[1,1,1,1,0,1,1,1,0,1,1,1],[0,1,0,1,1,1,1,1,0,1,1,1],[1,1,0,1,1,1,1,1,0,1,1,1],[0,1,1,1,1,1,1,1,0,1,1,1],[1,1,1,1,1,1,1,1,0,1,1,1],[0,1,0,1,0,1,0,0,1,1,1,1],[1,1,0,1,0,1,0,0,1,1,1,1],[0,1,1,1,0,1,0,0,1,1,1,1],[1,1,1,1,0,1,0,0,1,1,1,1],[0,1,0,1,1,1,0,0,1,1,1,1],[1,1,0,1,1,1,0,0,1,1,1,1],[0,1,1,1,1,1,0,0,1,1,1,1],[1,1,1,1,1,1,0,0,1,1,1,1],[0,1,0,1,0,1,1,0,1,1,1,1],[1,1,0,1,0,1,1,0,1,1,1,1],[0,1,1,1,0,1,1,0,1,1,1,1],[1,1,1,1,0,1,1,0,1,1,1,1],[0,1,0,1,1,1,1,0,1,1,1,1],[1,1,0,1,1,1,1,0,1,1,1,1],[0,1,1,1,1,1,1,0,1,1,1,1],[1,1,1,1,1,1,1,0,1,1,1,1],[0,1,0,1,0,1,0,1,1,1,1,1],[1,1,0,1,0,1,0,1,1,1,1,1],[0,1,1,1,0,1,0,1,1,1,1,1],[1,1,1,1,0,1,0,1,1,1,1,1],[0,1,0,1,1,1,0,1,1,1,1,1],[1,1,0,1,1,1,0,1,1,1,1,1],[0,1,1,1,1,1,0,1,1,1,1,1],[1,1,1,1,1,1,0,1,1,1,1,1],[1,0,1,0,1,0,1,1,1,1,1,1],[0,1,1,0,1,0,1,1,1,1,1,1],[1,1,1,0,1,0,1,1,1,1,1,1],[1,0,0,1,1,0,1,1,1,1,1,1],[0,1,0,1,1,0,1,1,1,1,1,1],[1,1,0,1,1,0,1,1,1,1,1,1],[1,0,1,1,1,0,1,1,1,1,1,1],[0,1,1,1,1,0,1,1,1,1,1,1],[1,1,1,1,1,0,1,1,1,1,1,1],[1,0,1,0,0,1,1,1,1,1,1,1],[0,1,1,0,0,1,1,1,1,1,1,1],[1,1,1,0,0,1,1,1,1,1,1,1],[1,0,0,1,0,1,1,1,1,1,1,1],[0,1,0,1,0,1,1,1,1,1,1,1],[1,1,0,1,0,1,1,1,1,1,1,1],[1,0,1,1,0,1,1,1,1,1,1,1],[0,1,1,1,0,1,1,1,1,1,1,1],[1,1,1,1,0,1,1,1,1,1,1,1],[1,0,1,0,1,1,1,1,1,1,1,1],[0,1,1,0,1,1,1,1,1,1,1,1],[1,1,1,0,1,1,1,1,1,1,1,1],[1,0,0,1,1,1,1,1,1,1,1,1],[0,1,0,1,1,1,1,1,1,1,1,1],[1,1,0,1,1,1,1,1,1,1,1,1],[1,0,1,1,1,1,1,1,1,1,1,1],[0,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1]]}],"problem_type":"BMF"}
\ No newline at end of file
diff --git a/tests/data/jl/coloring.json b/tests/data/jl/coloring.json
index fb46f99a..2678cb23 100644
--- a/tests/data/jl/coloring.json
+++ b/tests/data/jl/coloring.json
@@ -1 +1 @@
-{"instances":[{"label":"doc_petersen_3color","instance":{"k":3,"num_vertices":10,"edges":[[0,1],[0,4],[0,5],[1,2],[1,6],[2,3],[2,7],[3,4],[3,8],[4,9],[5,7],[5,8],[6,8],[6,9],[7,9]]},"evaluations":[{"is_valid":true,"config":[1,2,0,1,0,0,2,2,0,0],"size":12},{"is_valid":true,"config":[0,1,2,2,0,2,0,1,2,1],"size":10},{"is_valid":true,"config":[1,1,1,1,1,2,0,0,1,2],"size":9},{"is_valid":true,"config":[2,0,1,1,1,2,1,2,0,0],"size":11},{"is_valid":true,"config":[0,0,0,0,0,0,0,0,0,0],"size":0},{"is_valid":true,"config":[1,0,0,1,0,1,0,0,0,1],"size":10},{"is_valid":true,"config":[0,0,0,0,2,2,2,1,1,0],"size":12},{"is_valid":true,"config":[1,0,0,0,0,2,1,0,0,0],"size":8},{"is_valid":true,"config":[2,1,1,0,0,2,0,2,1,1],"size":11},{"is_valid":true,"config":[0,0,2,0,0,0,0,0,0,1],"size":6}],"best_solutions":[[0,2,0,2,1,2,1,1,0,0],[0,2,0,1,2,2,1,1,0,0],[1,2,0,1,2,2,1,1,0,0],[1,0,2,1,2,2,1,1,0,0],[0,1,0,2,1,2,2,1,0,0],[0,1,0,1,2,2,2,1,0,0],[1,0,2,1,2,2,2,1,0,0],[0,1,2,1,2,2,2,1,0,0],[0,2,0,2,1,1,1,2,0,0],[2,0,1,2,1,1,1,2,0,0],[0,2,1,2,1,1,1,2,0,0],[0,2,0,1,2,1,1,2,0,0],[0,1,0,2,1,1,2,2,0,0],[2,1,0,2,1,1,2,2,0,0],[2,0,1,2,1,1,2,2,0,0],[0,1,0,1,2,1,2,2,0,0],[2,0,2,0,1,0,2,1,1,0],[2,1,2,0,1,0,2,1,1,0],[2,1,0,2,1,0,2,1,1,0],[1,0,2,0,2,0,2,1,1,0],[0,1,2,0,1,2,2,1,1,0],[0,1,0,2,1,2,2,1,1,0],[1,0,2,0,2,2,2,1,1,0],[0,1,2,0,2,2,2,1,1,0],[2,0,1,0,1,0,2,2,1,0],[2,1,0,2,1,0,2,2,1,0],[2,0,1,2,1,0,2,2,1,0],[1,0,1,0,2,0,2,2,1,0],[2,0,2,0,1,0,1,1,2,0],[1,0,2,0,2,0,1,1,2,0],[1,2,0,1,2,0,1,1,2,0],[1,0,2,1,2,0,1,1,2,0],[2,0,1,0,1,0,1,2,2,0],[1,0,1,0,2,0,1,2,2,0],[1,2,1,0,2,0,1,2,2,0],[1,2,0,1,2,0,1,2,2,0],[2,0,1,0,1,1,1,2,2,0],[0,2,1,0,1,1,1,2,2,0],[0,2,1,0,2,1,1,2,2,0],[0,2,0,1,2,1,1,2,2,0],[2,0,2,1,0,1,2,0,0,1],[2,1,2,1,0,1,2,0,0,1],[2,0,1,2,0,1,2,0,0,1],[0,1,2,1,2,1,2,0,0,1],[1,0,2,1,0,2,2,0,0,1],[1,0,1,2,0,2,2,0,0,1],[1,0,2,1,2,2,2,0,0,1],[0,1,2,1,2,2,2,0,0,1],[2,1,0,1,0,1,2,2,0,1],[2,1,0,2,0,1,2,2,0,1],[2,0,1,2,0,1,2,2,0,1],[0,1,0,1,2,1,2,2,0,1],[1,2,1,2,0,2,0,0,1,1],[0,2,1,0,2,2,0,0,1,1],[1,2,1,0,2,2,0,0,1,1],[0,1,2,0,2,2,0,0,1,1],[1,0,1,2,0,2,2,0,1,1],[1,0,1,0,2,2,2,0,1,1],[1,0,2,0,2,2,2,0,1,1],[0,1,2,0,2,2,2,0,1,1],[2,1,0,2,0,0,0,2,1,1],[1,2,0,2,0,0,0,2,1,1],[1,2,1,2,0,0,0,2,1,1],[1,2,1,0,2,0,0,2,1,1],[2,1,0,2,0,0,2,2,1,1],[1,0,1,2,0,0,2,2,1,1],[2,0,1,2,0,0,2,2,1,1],[1,0,1,0,2,0,2,2,1,1],[2,1,2,1,0,1,0,0,2,1],[0,2,1,0,2,1,0,0,2,1],[0,1,2,0,2,1,0,0,2,1],[0,1,2,1,2,1,0,0,2,1],[2,1,0,1,0,0,0,2,2,1],[1,2,0,1,0,0,0,2,2,1],[1,2,1,0,2,0,0,2,2,1],[1,2,0,1,2,0,0,2,2,1],[2,1,0,1,0,1,0,2,2,1],[0,2,1,0,2,1,0,2,2,1],[0,1,0,1,2,1,0,2,2,1],[0,2,0,1,2,1,0,2,2,1],[2,0,2,1,0,1,1,0,0,2],[2,0,1,2,0,1,1,0,0,2],[2,0,1,2,1,1,1,0,0,2],[0,2,1,2,1,1,1,0,0,2],[1,0,2,1,0,2,1,0,0,2],[1,0,1,2,0,2,1,0,0,2],[1,2,1,2,0,2,1,0,0,2],[0,2,1,2,1,2,1,0,0,2],[1,2,0,1,0,2,1,1,0,2],[1,0,2,1,0,2,1,1,0,2],[1,2,0,2,0,2,1,1,0,2],[0,2,0,2,1,2,1,1,0,2],[1,2,1,2,0,2,0,0,1,2],[0,2,1,0,1,2,0,0,1,2],[0,1,2,0,1,2,0,0,1,2],[0,2,1,2,1,2,0,0,1,2],[2,1,0,2,0,0,0,1,1,2],[1,2,0,2,0,0,0,1,1,2],[2,1,2,0,1,0,0,1,1,2],[2,1,0,2,1,0,0,1,1,2],[1,2,0,2,0,2,0,1,1,2],[0,1,2,0,1,2,0,1,1,2],[0,1,0,2,1,2,0,1,1,2],[0,2,0,2,1,2,0,1,1,2],[2,1,2,1,0,1,0,0,2,2],[0,2,1,0,1,1,0,0,2,2],[0,1,2,0,1,1,0,0,2,2],[2,1,2,0,1,1,0,0,2,2],[2,0,2,1,0,1,1,0,2,2],[2,0,1,0,1,1,1,0,2,2],[0,2,1,0,1,1,1,0,2,2],[2,0,2,0,1,1,1,0,2,2],[2,1,0,1,0,0,0,1,2,2],[1,2,0,1,0,0,0,1,2,2],[2,1,2,1,0,0,0,1,2,2],[2,1,2,0,1,0,0,1,2,2],[1,2,0,1,0,0,1,1,2,2],[1,0,2,1,0,0,1,1,2,2],[2,0,2,1,0,0,1,1,2,2],[2,0,2,0,1,0,1,1,2,2]]}],"problem_type":"Coloring"}
\ No newline at end of file
+{"instances":[{"label":"doc_petersen_3color","instance":{"k":3,"num_vertices":10,"edges":[[0,1],[0,4],[0,5],[1,2],[1,6],[2,3],[2,7],[3,4],[3,8],[4,9],[5,7],[5,8],[6,8],[6,9],[7,9]]},"evaluations":[{"is_valid":true,"config":[1,1,0,1,1,0,1,2,1,1],"size":7},{"is_valid":true,"config":[0,0,2,0,0,0,1,1,0,0],"size":8},{"is_valid":true,"config":[1,2,1,2,0,0,2,0,0,0],"size":10},{"is_valid":true,"config":[2,1,0,2,2,1,1,2,1,0],"size":10},{"is_valid":true,"config":[0,0,1,2,1,1,2,0,1,0],"size":12},{"is_valid":true,"config":[0,2,2,1,0,0,1,1,0,2],"size":11},{"is_valid":true,"config":[0,0,0,0,0,0,0,0,0,0],"size":0},{"is_valid":true,"config":[2,2,0,0,0,1,2,0,2,2],"size":8},{"is_valid":true,"config":[0,2,0,1,2,0,1,1,2,2],"size":13},{"is_valid":true,"config":[1,0,0,1,2,2,0,2,1,1],"size":11}],"best_solutions":[[0,2,0,2,1,2,1,1,0,0],[0,2,0,1,2,2,1,1,0,0],[1,2,0,1,2,2,1,1,0,0],[1,0,2,1,2,2,1,1,0,0],[0,1,0,2,1,2,2,1,0,0],[0,1,0,1,2,2,2,1,0,0],[1,0,2,1,2,2,2,1,0,0],[0,1,2,1,2,2,2,1,0,0],[0,2,0,2,1,1,1,2,0,0],[2,0,1,2,1,1,1,2,0,0],[0,2,1,2,1,1,1,2,0,0],[0,2,0,1,2,1,1,2,0,0],[0,1,0,2,1,1,2,2,0,0],[2,1,0,2,1,1,2,2,0,0],[2,0,1,2,1,1,2,2,0,0],[0,1,0,1,2,1,2,2,0,0],[2,0,2,0,1,0,2,1,1,0],[2,1,2,0,1,0,2,1,1,0],[2,1,0,2,1,0,2,1,1,0],[1,0,2,0,2,0,2,1,1,0],[0,1,2,0,1,2,2,1,1,0],[0,1,0,2,1,2,2,1,1,0],[1,0,2,0,2,2,2,1,1,0],[0,1,2,0,2,2,2,1,1,0],[2,0,1,0,1,0,2,2,1,0],[2,1,0,2,1,0,2,2,1,0],[2,0,1,2,1,0,2,2,1,0],[1,0,1,0,2,0,2,2,1,0],[2,0,2,0,1,0,1,1,2,0],[1,0,2,0,2,0,1,1,2,0],[1,2,0,1,2,0,1,1,2,0],[1,0,2,1,2,0,1,1,2,0],[2,0,1,0,1,0,1,2,2,0],[1,0,1,0,2,0,1,2,2,0],[1,2,1,0,2,0,1,2,2,0],[1,2,0,1,2,0,1,2,2,0],[2,0,1,0,1,1,1,2,2,0],[0,2,1,0,1,1,1,2,2,0],[0,2,1,0,2,1,1,2,2,0],[0,2,0,1,2,1,1,2,2,0],[2,0,2,1,0,1,2,0,0,1],[2,1,2,1,0,1,2,0,0,1],[2,0,1,2,0,1,2,0,0,1],[0,1,2,1,2,1,2,0,0,1],[1,0,2,1,0,2,2,0,0,1],[1,0,1,2,0,2,2,0,0,1],[1,0,2,1,2,2,2,0,0,1],[0,1,2,1,2,2,2,0,0,1],[2,1,0,1,0,1,2,2,0,1],[2,1,0,2,0,1,2,2,0,1],[2,0,1,2,0,1,2,2,0,1],[0,1,0,1,2,1,2,2,0,1],[1,2,1,2,0,2,0,0,1,1],[0,2,1,0,2,2,0,0,1,1],[1,2,1,0,2,2,0,0,1,1],[0,1,2,0,2,2,0,0,1,1],[1,0,1,2,0,2,2,0,1,1],[1,0,1,0,2,2,2,0,1,1],[1,0,2,0,2,2,2,0,1,1],[0,1,2,0,2,2,2,0,1,1],[2,1,0,2,0,0,0,2,1,1],[1,2,0,2,0,0,0,2,1,1],[1,2,1,2,0,0,0,2,1,1],[1,2,1,0,2,0,0,2,1,1],[2,1,0,2,0,0,2,2,1,1],[1,0,1,2,0,0,2,2,1,1],[2,0,1,2,0,0,2,2,1,1],[1,0,1,0,2,0,2,2,1,1],[2,1,2,1,0,1,0,0,2,1],[0,2,1,0,2,1,0,0,2,1],[0,1,2,0,2,1,0,0,2,1],[0,1,2,1,2,1,0,0,2,1],[2,1,0,1,0,0,0,2,2,1],[1,2,0,1,0,0,0,2,2,1],[1,2,1,0,2,0,0,2,2,1],[1,2,0,1,2,0,0,2,2,1],[2,1,0,1,0,1,0,2,2,1],[0,2,1,0,2,1,0,2,2,1],[0,1,0,1,2,1,0,2,2,1],[0,2,0,1,2,1,0,2,2,1],[2,0,2,1,0,1,1,0,0,2],[2,0,1,2,0,1,1,0,0,2],[2,0,1,2,1,1,1,0,0,2],[0,2,1,2,1,1,1,0,0,2],[1,0,2,1,0,2,1,0,0,2],[1,0,1,2,0,2,1,0,0,2],[1,2,1,2,0,2,1,0,0,2],[0,2,1,2,1,2,1,0,0,2],[1,2,0,1,0,2,1,1,0,2],[1,0,2,1,0,2,1,1,0,2],[1,2,0,2,0,2,1,1,0,2],[0,2,0,2,1,2,1,1,0,2],[1,2,1,2,0,2,0,0,1,2],[0,2,1,0,1,2,0,0,1,2],[0,1,2,0,1,2,0,0,1,2],[0,2,1,2,1,2,0,0,1,2],[2,1,0,2,0,0,0,1,1,2],[1,2,0,2,0,0,0,1,1,2],[2,1,2,0,1,0,0,1,1,2],[2,1,0,2,1,0,0,1,1,2],[1,2,0,2,0,2,0,1,1,2],[0,1,2,0,1,2,0,1,1,2],[0,1,0,2,1,2,0,1,1,2],[0,2,0,2,1,2,0,1,1,2],[2,1,2,1,0,1,0,0,2,2],[0,2,1,0,1,1,0,0,2,2],[0,1,2,0,1,1,0,0,2,2],[2,1,2,0,1,1,0,0,2,2],[2,0,2,1,0,1,1,0,2,2],[2,0,1,0,1,1,1,0,2,2],[0,2,1,0,1,1,1,0,2,2],[2,0,2,0,1,1,1,0,2,2],[2,1,0,1,0,0,0,1,2,2],[1,2,0,1,0,0,0,1,2,2],[2,1,2,1,0,0,0,1,2,2],[2,1,2,0,1,0,0,1,2,2],[1,2,0,1,0,0,1,1,2,2],[1,0,2,1,0,0,1,1,2,2],[2,0,2,1,0,0,1,1,2,2],[2,0,2,0,1,0,1,1,2,2]]}],"problem_type":"Coloring"}
\ No newline at end of file
diff --git a/tests/data/jl/dominatingset.json b/tests/data/jl/dominatingset.json
index d9735f67..26992291 100644
--- a/tests/data/jl/dominatingset.json
+++ b/tests/data/jl/dominatingset.json
@@ -1 +1 @@
-{"instances":[{"label":"doc_path5","instance":{"weights":[1,1,1,1,1],"num_vertices":5,"edges":[[0,1],[1,2],[2,3],[3,4]]},"evaluations":[{"is_valid":false,"config":[0,0,0,0,1],"size":1},{"is_valid":false,"config":[0,0,0,0,0],"size":0},{"is_valid":false,"config":[0,0,1,0,0],"size":1},{"is_valid":true,"config":[0,1,1,0,1],"size":3},{"is_valid":true,"config":[1,0,0,1,1],"size":3},{"is_valid":false,"config":[0,1,1,0,0],"size":2},{"is_valid":true,"config":[1,0,1,1,1],"size":4},{"is_valid":true,"config":[1,1,1,1,1],"size":5},{"is_valid":false,"config":[1,0,1,0,0],"size":2},{"is_valid":false,"config":[1,0,0,0,0],"size":1}],"best_solutions":[[1,0,0,1,0],[0,1,0,1,0],[0,1,0,0,1]]}],"problem_type":"DominatingSet"}
\ No newline at end of file
+{"instances":[{"label":"doc_path5","instance":{"weights":[1,1,1,1,1],"num_vertices":5,"edges":[[0,1],[1,2],[2,3],[3,4]]},"evaluations":[{"is_valid":false,"config":[0,0,0,0,0],"size":0},{"is_valid":true,"config":[1,1,0,0,1],"size":3},{"is_valid":false,"config":[1,1,0,0,0],"size":2},{"is_valid":true,"config":[1,1,1,1,1],"size":5},{"is_valid":false,"config":[0,1,0,0,0],"size":1},{"is_valid":true,"config":[1,1,1,1,0],"size":4},{"is_valid":true,"config":[1,1,0,1,1],"size":4},{"is_valid":true,"config":[1,1,0,1,0],"size":3},{"is_valid":true,"config":[0,1,1,1,0],"size":3},{"is_valid":false,"config":[0,0,1,0,0],"size":1}],"best_solutions":[[1,0,0,1,0],[0,1,0,1,0],[0,1,0,0,1]]}],"problem_type":"DominatingSet"}
\ No newline at end of file
diff --git a/tests/data/jl/factoring.json b/tests/data/jl/factoring.json
index 03606810..36910ef4 100644
--- a/tests/data/jl/factoring.json
+++ b/tests/data/jl/factoring.json
@@ -1 +1 @@
-{"instances":[{"label":"1x1_factor_1","instance":{"m":1,"input":1,"n":1},"evaluations":[{"is_valid":false,"config":[0,0],"size":0},{"is_valid":true,"config":[1,1],"size":0},{"is_valid":false,"config":[1,0],"size":0},{"is_valid":false,"config":[0,1],"size":0}],"best_solutions":[[1,1]]},{"label":"2x1_factor_2","instance":{"m":2,"input":2,"n":1},"evaluations":[{"is_valid":false,"config":[0,1,0],"size":0},{"is_valid":false,"config":[1,0,1],"size":0},{"is_valid":false,"config":[1,0,0],"size":0},{"is_valid":false,"config":[0,0,1],"size":0},{"is_valid":false,"config":[0,0,0],"size":0},{"is_valid":false,"config":[1,1,1],"size":0},{"is_valid":true,"config":[0,1,1],"size":0},{"is_valid":false,"config":[1,1,0],"size":0}],"best_solutions":[[0,1,1]]},{"label":"2x1_factor_3","instance":{"m":2,"input":3,"n":1},"evaluations":[{"is_valid":false,"config":[0,1,0],"size":0},{"is_valid":false,"config":[1,0,1],"size":0},{"is_valid":false,"config":[0,0,1],"size":0},{"is_valid":false,"config":[1,0,0],"size":0},{"is_valid":false,"config":[0,0,0],"size":0},{"is_valid":true,"config":[1,1,1],"size":0},{"is_valid":false,"config":[1,1,0],"size":0},{"is_valid":false,"config":[0,1,1],"size":0}],"best_solutions":[[1,1,1]]},{"label":"doc_factor6","instance":{"m":2,"input":6,"n":2},"evaluations":[{"is_valid":false,"config":[1,1,1,0],"size":0},{"is_valid":false,"config":[1,0,1,1],"size":0},{"is_valid":false,"config":[1,0,1,0],"size":0},{"is_valid":false,"config":[0,1,0,1],"size":0},{"is_valid":false,"config":[0,0,0,0],"size":0},{"is_valid":true,"config":[0,1,1,1],"size":0},{"is_valid":true,"config":[1,1,0,1],"size":0},{"is_valid":false,"config":[1,0,0,1],"size":0},{"is_valid":false,"config":[1,0,0,0],"size":0},{"is_valid":false,"config":[1,1,1,1],"size":0}],"best_solutions":[[1,1,0,1],[0,1,1,1]]}],"problem_type":"Factoring"}
\ No newline at end of file
+{"instances":[{"label":"1x1_factor_1","instance":{"m":1,"input":1,"n":1},"evaluations":[{"is_valid":false,"config":[0,0],"size":0},{"is_valid":true,"config":[1,1],"size":0},{"is_valid":false,"config":[1,0],"size":0},{"is_valid":false,"config":[0,1],"size":0}],"best_solutions":[[1,1]]},{"label":"2x1_factor_2","instance":{"m":2,"input":2,"n":1},"evaluations":[{"is_valid":false,"config":[0,1,0],"size":0},{"is_valid":false,"config":[1,0,1],"size":0},{"is_valid":false,"config":[0,0,1],"size":0},{"is_valid":false,"config":[1,0,0],"size":0},{"is_valid":false,"config":[0,0,0],"size":0},{"is_valid":false,"config":[1,1,1],"size":0},{"is_valid":true,"config":[0,1,1],"size":0},{"is_valid":false,"config":[1,1,0],"size":0}],"best_solutions":[[0,1,1]]},{"label":"2x1_factor_3","instance":{"m":2,"input":3,"n":1},"evaluations":[{"is_valid":false,"config":[0,1,0],"size":0},{"is_valid":false,"config":[1,0,1],"size":0},{"is_valid":false,"config":[1,0,0],"size":0},{"is_valid":false,"config":[0,0,1],"size":0},{"is_valid":false,"config":[0,0,0],"size":0},{"is_valid":true,"config":[1,1,1],"size":0},{"is_valid":false,"config":[1,1,0],"size":0},{"is_valid":false,"config":[0,1,1],"size":0}],"best_solutions":[[1,1,1]]},{"label":"doc_factor6","instance":{"m":2,"input":6,"n":2},"evaluations":[{"is_valid":false,"config":[1,1,1,0],"size":0},{"is_valid":false,"config":[0,0,0,1],"size":0},{"is_valid":false,"config":[0,1,0,0],"size":0},{"is_valid":false,"config":[0,0,0,0],"size":0},{"is_valid":true,"config":[1,1,0,1],"size":0},{"is_valid":false,"config":[0,1,1,0],"size":0},{"is_valid":false,"config":[0,0,1,1],"size":0},{"is_valid":false,"config":[0,0,1,0],"size":0},{"is_valid":false,"config":[1,0,0,0],"size":0},{"is_valid":false,"config":[1,1,1,1],"size":0}],"best_solutions":[[1,1,0,1],[0,1,1,1]]}],"problem_type":"Factoring"}
\ No newline at end of file
diff --git a/tests/data/jl/independentset.json b/tests/data/jl/independentset.json
index 95789eac..713b1354 100644
--- a/tests/data/jl/independentset.json
+++ b/tests/data/jl/independentset.json
@@ -1 +1 @@
-{"instances":[{"label":"petersen","instance":{"weights":[1,1,1,1,1,1,1,1,1,1],"num_vertices":10,"edges":[[0,1],[0,4],[0,5],[1,2],[1,6],[2,3],[2,7],[3,4],[3,8],[4,9],[5,7],[5,8],[6,8],[6,9],[7,9]]},"evaluations":[{"is_valid":false,"config":[0,1,0,1,0,0,1,0,1,1],"size":5},{"is_valid":false,"config":[0,0,0,1,1,1,0,0,1,0],"size":4},{"is_valid":false,"config":[0,0,0,1,0,0,1,1,1,0],"size":4},{"is_valid":false,"config":[1,0,0,0,1,0,0,0,0,0],"size":2},{"is_valid":false,"config":[1,0,1,1,1,1,0,0,0,0],"size":5},{"is_valid":true,"config":[0,0,0,0,0,0,0,0,0,0],"size":0},{"is_valid":false,"config":[1,0,1,1,1,0,0,0,0,0],"size":4},{"is_valid":false,"config":[1,1,1,1,1,0,0,1,1,1],"size":8},{"is_valid":false,"config":[1,1,1,1,1,1,1,1,1,1],"size":10},{"is_valid":false,"config":[0,1,1,0,0,0,0,0,0,0],"size":2}],"best_solutions":[[0,0,1,0,1,1,1,0,0,0],[1,0,0,1,0,0,1,1,0,0],[0,1,0,0,1,0,0,1,1,0],[0,1,0,1,0,1,0,0,0,1],[1,0,1,0,0,0,0,0,1,1]]},{"label":"doc_4vertex","instance":{"weights":[1,1,1,1],"num_vertices":4,"edges":[[0,1],[0,2],[1,2],[2,3]]},"evaluations":[{"is_valid":false,"config":[1,1,1,0],"size":3},{"is_valid":true,"config":[0,0,0,1],"size":1},{"is_valid":true,"config":[0,0,0,0],"size":0},{"is_valid":false,"config":[0,1,1,1],"size":3},{"is_valid":true,"config":[1,0,0,1],"size":2},{"is_valid":false,"config":[0,0,1,1],"size":2},{"is_valid":true,"config":[1,0,0,0],"size":1},{"is_valid":true,"config":[0,0,1,0],"size":1},{"is_valid":false,"config":[1,1,1,1],"size":4},{"is_valid":false,"config":[0,1,1,0],"size":2}],"best_solutions":[[1,0,0,1],[0,1,0,1]]},{"label":"doc_diamond","instance":{"weights":[1,1,1,1],"num_vertices":4,"edges":[[0,1],[0,2],[1,2],[1,3],[2,3]]},"evaluations":[{"is_valid":false,"config":[1,0,1,1],"size":3},{"is_valid":false,"config":[1,0,1,0],"size":2},{"is_valid":true,"config":[0,0,0,0],"size":0},{"is_valid":false,"config":[0,1,1,1],"size":3},{"is_valid":false,"config":[1,1,0,1],"size":3},{"is_valid":false,"config":[0,1,1,0],"size":2},{"is_valid":false,"config":[0,0,1,1],"size":2},{"is_valid":true,"config":[0,0,1,0],"size":1},{"is_valid":true,"config":[1,0,0,1],"size":2},{"is_valid":false,"config":[1,1,1,1],"size":4}],"best_solutions":[[1,0,0,1]]}],"problem_type":"IndependentSet"}
\ No newline at end of file
+{"instances":[{"label":"petersen","instance":{"weights":[1,1,1,1,1,1,1,1,1,1],"num_vertices":10,"edges":[[0,1],[0,4],[0,5],[1,2],[1,6],[2,3],[2,7],[3,4],[3,8],[4,9],[5,7],[5,8],[6,8],[6,9],[7,9]]},"evaluations":[{"is_valid":false,"config":[1,0,0,1,1,1,0,0,0,1],"size":5},{"is_valid":false,"config":[0,1,0,1,0,0,0,1,0,1],"size":4},{"is_valid":false,"config":[0,0,1,1,0,0,0,1,1,0],"size":4},{"is_valid":false,"config":[1,1,0,0,1,0,0,0,0,0],"size":3},{"is_valid":false,"config":[1,0,1,0,0,1,0,1,1,1],"size":6},{"is_valid":false,"config":[1,0,0,1,1,0,1,1,0,0],"size":5},{"is_valid":true,"config":[0,0,0,0,0,0,0,0,0,0],"size":0},{"is_valid":false,"config":[0,0,1,0,0,0,1,0,1,0],"size":3},{"is_valid":false,"config":[1,1,1,1,1,1,1,1,1,1],"size":10},{"is_valid":false,"config":[0,1,1,1,0,1,0,1,1,0],"size":6}],"best_solutions":[[0,0,1,0,1,1,1,0,0,0],[1,0,0,1,0,0,1,1,0,0],[0,1,0,0,1,0,0,1,1,0],[0,1,0,1,0,1,0,0,0,1],[1,0,1,0,0,0,0,0,1,1]]},{"label":"doc_4vertex","instance":{"weights":[1,1,1,1],"num_vertices":4,"edges":[[0,1],[0,2],[1,2],[2,3]]},"evaluations":[{"is_valid":false,"config":[1,1,1,0],"size":3},{"is_valid":true,"config":[0,1,0,1],"size":2},{"is_valid":true,"config":[0,0,0,0],"size":0},{"is_valid":false,"config":[1,1,0,1],"size":3},{"is_valid":false,"config":[1,1,0,0],"size":2},{"is_valid":false,"config":[0,1,1,0],"size":2},{"is_valid":true,"config":[1,0,0,0],"size":1},{"is_valid":true,"config":[1,0,0,1],"size":2},{"is_valid":false,"config":[1,1,1,1],"size":4},{"is_valid":false,"config":[0,0,1,1],"size":2}],"best_solutions":[[1,0,0,1],[0,1,0,1]]},{"label":"doc_diamond","instance":{"weights":[1,1,1,1],"num_vertices":4,"edges":[[0,1],[0,2],[1,2],[1,3],[2,3]]},"evaluations":[{"is_valid":false,"config":[0,1,0,1],"size":2},{"is_valid":false,"config":[1,0,1,0],"size":2},{"is_valid":true,"config":[0,0,0,0],"size":0},{"is_valid":false,"config":[1,1,0,1],"size":3},{"is_valid":false,"config":[1,1,0,0],"size":2},{"is_valid":false,"config":[0,0,1,1],"size":2},{"is_valid":true,"config":[1,0,0,0],"size":1},{"is_valid":false,"config":[0,1,1,0],"size":2},{"is_valid":false,"config":[1,1,1,1],"size":4},{"is_valid":true,"config":[1,0,0,1],"size":2}],"best_solutions":[[1,0,0,1]]}],"problem_type":"IndependentSet"}
\ No newline at end of file
diff --git a/tests/data/jl/matching.json b/tests/data/jl/matching.json
index f4dc3d65..e8924f62 100644
--- a/tests/data/jl/matching.json
+++ b/tests/data/jl/matching.json
@@ -1 +1 @@
-{"instances":[{"label":"petersen","instance":{"weights":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],"num_vertices":10,"edges":[[0,1],[0,4],[0,5],[1,2],[1,6],[2,3],[2,7],[3,4],[3,8],[4,9],[5,7],[5,8],[6,8],[6,9],[7,9]]},"evaluations":[{"is_valid":false,"config":[0,1,0,1,1,1,1,1,0,1,1,1,0,0,0],"size":9},{"is_valid":true,"config":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"size":0},{"is_valid":false,"config":[1,0,1,0,0,0,1,1,0,0,1,1,1,0,0],"size":7},{"is_valid":false,"config":[0,0,1,0,0,1,0,0,1,1,1,1,0,1,0],"size":7},{"is_valid":false,"config":[0,0,1,1,1,1,0,0,0,1,0,1,0,0,0],"size":6},{"is_valid":false,"config":[0,1,1,1,0,0,1,0,0,0,0,0,1,0,0],"size":5},{"is_valid":false,"config":[1,0,0,0,0,1,0,1,0,1,1,1,1,1,1],"size":9},{"is_valid":false,"config":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],"size":15},{"is_valid":false,"config":[1,1,0,1,0,0,1,1,0,0,1,0,1,0,0],"size":7},{"is_valid":false,"config":[1,0,1,0,0,1,1,0,1,1,0,1,1,1,1],"size":10}],"best_solutions":[[0,0,1,0,1,0,1,0,1,1,0,0,0,0,0],[1,0,0,0,0,1,0,0,0,1,1,0,1,0,0],[0,1,0,1,0,0,0,0,1,0,1,0,0,1,0],[1,0,0,0,0,0,1,1,0,0,0,1,0,1,0],[0,1,0,0,1,1,0,0,0,0,0,1,0,0,1],[0,0,1,1,0,0,0,1,0,0,0,0,1,0,1]]},{"label":"rule_4vertex","instance":{"weights":[1,1,1,1],"num_vertices":4,"edges":[[0,1],[0,2],[1,2],[2,3]]},"evaluations":[{"is_valid":false,"config":[1,1,1,0],"size":3},{"is_valid":false,"config":[1,0,1,0],"size":2},{"is_valid":false,"config":[0,1,0,1],"size":2},{"is_valid":true,"config":[0,0,0,1],"size":1},{"is_valid":true,"config":[0,0,0,0],"size":0},{"is_valid":false,"config":[1,1,0,1],"size":3},{"is_valid":false,"config":[0,1,1,1],"size":3},{"is_valid":true,"config":[1,0,0,1],"size":2},{"is_valid":true,"config":[0,0,1,0],"size":1},{"is_valid":false,"config":[1,1,1,1],"size":4}],"best_solutions":[[1,0,0,1]]},{"label":"rule_4vertex_weighted","instance":{"weights":[1,2,3,4],"num_vertices":4,"edges":[[0,1],[0,2],[1,2],[2,3]]},"evaluations":[{"is_valid":false,"config":[1,1,1,0],"size":6},{"is_valid":false,"config":[1,0,1,1],"size":8},{"is_valid":false,"config":[0,1,0,1],"size":6},{"is_valid":true,"config":[0,0,0,0],"size":0},{"is_valid":false,"config":[0,1,1,1],"size":9},{"is_valid":false,"config":[1,1,0,1],"size":7},{"is_valid":false,"config":[0,1,1,0],"size":5},{"is_valid":true,"config":[1,0,0,0],"size":1},{"is_valid":true,"config":[0,0,1,0],"size":3},{"is_valid":false,"config":[1,1,1,1],"size":10}],"best_solutions":[[1,0,0,1]]}],"problem_type":"Matching"}
\ No newline at end of file
+{"instances":[{"label":"petersen","instance":{"weights":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],"num_vertices":10,"edges":[[0,1],[0,4],[0,5],[1,2],[1,6],[2,3],[2,7],[3,4],[3,8],[4,9],[5,7],[5,8],[6,8],[6,9],[7,9]]},"evaluations":[{"is_valid":false,"config":[1,0,0,0,0,1,0,1,1,0,0,0,0,1,1],"size":6},{"is_valid":true,"config":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"size":0},{"is_valid":false,"config":[0,1,1,1,1,0,0,0,1,0,1,1,0,1,0],"size":8},{"is_valid":false,"config":[1,1,1,1,1,1,0,0,0,1,0,0,0,0,1],"size":8},{"is_valid":false,"config":[1,1,0,1,0,1,0,1,1,0,1,0,1,1,0],"size":9},{"is_valid":false,"config":[0,0,0,0,0,1,0,1,0,0,1,0,1,0,1],"size":5},{"is_valid":false,"config":[1,1,0,1,0,1,1,1,1,0,1,1,1,1,1],"size":12},{"is_valid":false,"config":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],"size":15},{"is_valid":false,"config":[1,0,0,0,1,1,0,0,1,1,0,0,1,1,0],"size":7},{"is_valid":false,"config":[0,1,0,1,1,1,0,1,1,1,1,1,0,0,1],"size":10}],"best_solutions":[[0,0,1,0,1,0,1,0,1,1,0,0,0,0,0],[1,0,0,0,0,1,0,0,0,1,1,0,1,0,0],[0,1,0,1,0,0,0,0,1,0,1,0,0,1,0],[1,0,0,0,0,0,1,1,0,0,0,1,0,1,0],[0,1,0,0,1,1,0,0,0,0,0,1,0,0,1],[0,0,1,1,0,0,0,1,0,0,0,0,1,0,1]]},{"label":"rule_4vertex","instance":{"weights":[1,1,1,1],"num_vertices":4,"edges":[[0,1],[0,2],[1,2],[2,3]]},"evaluations":[{"is_valid":false,"config":[1,1,1,0],"size":3},{"is_valid":false,"config":[1,0,1,0],"size":2},{"is_valid":true,"config":[0,0,0,1],"size":1},{"is_valid":true,"config":[0,0,0,0],"size":0},{"is_valid":false,"config":[1,1,0,1],"size":3},{"is_valid":true,"config":[1,0,0,1],"size":2},{"is_valid":false,"config":[1,1,0,0],"size":2},{"is_valid":true,"config":[0,0,1,0],"size":1},{"is_valid":false,"config":[0,1,1,0],"size":2},{"is_valid":false,"config":[1,1,1,1],"size":4}],"best_solutions":[[1,0,0,1]]},{"label":"rule_4vertex_weighted","instance":{"weights":[1,2,3,4],"num_vertices":4,"edges":[[0,1],[0,2],[1,2],[2,3]]},"evaluations":[{"is_valid":false,"config":[1,0,1,0],"size":4},{"is_valid":true,"config":[0,0,0,1],"size":4},{"is_valid":true,"config":[0,0,0,0],"size":0},{"is_valid":false,"config":[1,1,0,1],"size":7},{"is_valid":false,"config":[0,1,1,1],"size":9},{"is_valid":false,"config":[1,1,0,0],"size":3},{"is_valid":false,"config":[0,0,1,1],"size":7},{"is_valid":true,"config":[1,0,0,0],"size":1},{"is_valid":true,"config":[0,0,1,0],"size":3},{"is_valid":false,"config":[1,1,1,1],"size":10}],"best_solutions":[[1,0,0,1]]}],"problem_type":"Matching"}
\ No newline at end of file
diff --git a/tests/data/jl/maxcut.json b/tests/data/jl/maxcut.json
index 91c12428..569a83ab 100644
--- a/tests/data/jl/maxcut.json
+++ b/tests/data/jl/maxcut.json
@@ -1 +1 @@
-{"instances":[{"label":"petersen","instance":{"weights":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],"num_vertices":10,"edges":[[0,1],[0,4],[0,5],[1,2],[1,6],[2,3],[2,7],[3,4],[3,8],[4,9],[5,7],[5,8],[6,8],[6,9],[7,9]]},"evaluations":[{"is_valid":true,"config":[0,1,1,1,1,1,0,0,0,0],"size":9},{"is_valid":true,"config":[1,1,0,0,1,0,1,0,1,0],"size":7},{"is_valid":true,"config":[0,1,0,1,0,1,1,1,0,1],"size":10},{"is_valid":true,"config":[1,1,1,1,0,1,0,0,1,0],"size":6},{"is_valid":true,"config":[0,0,0,0,1,0,1,0,0,0],"size":6},{"is_valid":true,"config":[0,0,0,0,0,0,0,0,0,0],"size":0},{"is_valid":true,"config":[0,0,0,1,0,1,0,0,1,1],"size":8},{"is_valid":true,"config":[0,1,1,1,0,1,0,0,0,0],"size":8},{"is_valid":true,"config":[1,1,1,1,1,1,1,1,1,1],"size":0},{"is_valid":true,"config":[0,0,1,1,1,1,0,1,0,1],"size":6}],"best_solutions":[[0,0,1,0,1,1,1,0,0,0],[1,0,0,1,0,0,1,1,0,0],[0,1,0,1,1,1,1,1,0,0],[0,1,0,0,1,0,0,1,1,0],[1,0,1,0,1,0,1,1,1,0],[0,1,0,1,0,1,0,0,0,1],[1,0,1,1,0,1,1,0,0,1],[1,0,1,0,0,0,0,0,1,1],[0,1,1,0,1,1,0,0,1,1],[1,1,0,1,0,0,0,1,1,1]]},{"label":"doc_k3","instance":{"weights":[1,2,3],"num_vertices":3,"edges":[[0,1],[0,2],[1,2]]},"evaluations":[{"is_valid":true,"config":[0,1,0],"size":4},{"is_valid":true,"config":[1,0,1],"size":4},{"is_valid":true,"config":[1,0,0],"size":3},{"is_valid":true,"config":[0,0,1],"size":5},{"is_valid":true,"config":[0,0,0],"size":0},{"is_valid":true,"config":[1,1,1],"size":0},{"is_valid":true,"config":[1,1,0],"size":5},{"is_valid":true,"config":[0,1,1],"size":3}],"best_solutions":[[1,1,0],[0,0,1]]},{"label":"rule_4vertex","instance":{"weights":[1,3,1,4],"num_vertices":4,"edges":[[0,1],[0,2],[1,2],[2,3]]},"evaluations":[{"is_valid":true,"config":[1,1,1,0],"size":4},{"is_valid":true,"config":[1,0,1,1],"size":2},{"is_valid":true,"config":[1,0,1,0],"size":6},{"is_valid":true,"config":[0,0,0,1],"size":4},{"is_valid":true,"config":[0,0,0,0],"size":0},{"is_valid":true,"config":[1,1,0,0],"size":4},{"is_valid":true,"config":[1,0,0,1],"size":8},{"is_valid":true,"config":[0,0,1,0],"size":8},{"is_valid":true,"config":[0,0,1,1],"size":4},{"is_valid":true,"config":[1,1,1,1],"size":0}],"best_solutions":[[0,0,1,0],[0,1,1,0],[1,0,0,1],[1,1,0,1]]}],"problem_type":"MaxCut"}
\ No newline at end of file
+{"instances":[{"label":"petersen","instance":{"weights":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],"num_vertices":10,"edges":[[0,1],[0,4],[0,5],[1,2],[1,6],[2,3],[2,7],[3,4],[3,8],[4,9],[5,7],[5,8],[6,8],[6,9],[7,9]]},"evaluations":[{"is_valid":true,"config":[0,0,1,0,0,0,0,0,0,0],"size":3},{"is_valid":true,"config":[1,1,0,1,1,1,0,1,1,1],"size":6},{"is_valid":true,"config":[1,1,0,1,1,0,0,1,1,1],"size":9},{"is_valid":true,"config":[0,1,0,0,1,1,1,1,1,1],"size":7},{"is_valid":true,"config":[0,1,0,0,1,0,1,0,0,0],"size":7},{"is_valid":true,"config":[0,1,1,0,1,0,1,1,0,0],"size":9},{"is_valid":true,"config":[0,0,0,0,0,0,0,0,0,0],"size":0},{"is_valid":true,"config":[0,0,0,0,0,1,1,1,0,1],"size":6},{"is_valid":true,"config":[1,1,1,1,1,1,1,1,1,1],"size":0},{"is_valid":true,"config":[0,0,0,1,0,0,0,0,1,0],"size":4}],"best_solutions":[[0,0,1,0,1,1,1,0,0,0],[1,0,0,1,0,0,1,1,0,0],[0,1,0,1,1,1,1,1,0,0],[0,1,0,0,1,0,0,1,1,0],[1,0,1,0,1,0,1,1,1,0],[0,1,0,1,0,1,0,0,0,1],[1,0,1,1,0,1,1,0,0,1],[1,0,1,0,0,0,0,0,1,1],[0,1,1,0,1,1,0,0,1,1],[1,1,0,1,0,0,0,1,1,1]]},{"label":"doc_k3","instance":{"weights":[1,2,3],"num_vertices":3,"edges":[[0,1],[0,2],[1,2]]},"evaluations":[{"is_valid":true,"config":[0,1,0],"size":4},{"is_valid":true,"config":[1,0,1],"size":4},{"is_valid":true,"config":[1,0,0],"size":3},{"is_valid":true,"config":[0,0,1],"size":5},{"is_valid":true,"config":[0,0,0],"size":0},{"is_valid":true,"config":[1,1,1],"size":0},{"is_valid":true,"config":[1,1,0],"size":5},{"is_valid":true,"config":[0,1,1],"size":3}],"best_solutions":[[1,1,0],[0,0,1]]},{"label":"rule_4vertex","instance":{"weights":[1,3,1,4],"num_vertices":4,"edges":[[0,1],[0,2],[1,2],[2,3]]},"evaluations":[{"is_valid":true,"config":[1,0,1,1],"size":2},{"is_valid":true,"config":[0,1,0,1],"size":6},{"is_valid":true,"config":[1,0,1,0],"size":6},{"is_valid":true,"config":[0,0,0,1],"size":4},{"is_valid":true,"config":[0,0,0,0],"size":0},{"is_valid":true,"config":[1,0,0,1],"size":8},{"is_valid":true,"config":[0,1,1,0],"size":8},{"is_valid":true,"config":[0,0,1,0],"size":8},{"is_valid":true,"config":[1,0,0,0],"size":4},{"is_valid":true,"config":[1,1,1,1],"size":0}],"best_solutions":[[0,0,1,0],[0,1,1,0],[1,0,0,1],[1,1,0,1]]}],"problem_type":"MaxCut"}
\ No newline at end of file
diff --git a/tests/data/jl/maximalis.json b/tests/data/jl/maximalis.json
index 52a13471..d6ce3a97 100644
--- a/tests/data/jl/maximalis.json
+++ b/tests/data/jl/maximalis.json
@@ -1 +1 @@
-{"instances":[{"label":"doc_4vertex","instance":{"weights":[1,1,1,1],"num_vertices":4,"edges":[[0,1],[0,2],[0,3],[1,2],[2,3]]},"evaluations":[{"is_valid":false,"config":[1,1,1,0],"size":3},{"is_valid":false,"config":[1,0,1,1],"size":3},{"is_valid":false,"config":[1,0,1,0],"size":2},{"is_valid":true,"config":[0,1,0,1],"size":2},{"is_valid":false,"config":[0,0,0,1],"size":1},{"is_valid":false,"config":[0,0,0,0],"size":0},{"is_valid":false,"config":[1,1,0,1],"size":3},{"is_valid":false,"config":[1,1,0,0],"size":2},{"is_valid":true,"config":[1,0,0,0],"size":1},{"is_valid":false,"config":[1,1,1,1],"size":4}],"best_solutions":[[0,1,0,1]]}],"problem_type":"MaximalIS"}
\ No newline at end of file
+{"instances":[{"label":"doc_4vertex","instance":{"weights":[1,1,1,1],"num_vertices":4,"edges":[[0,1],[0,2],[0,3],[1,2],[2,3]]},"evaluations":[{"is_valid":false,"config":[1,1,1,0],"size":3},{"is_valid":true,"config":[0,1,0,1],"size":2},{"is_valid":false,"config":[0,0,0,0],"size":0},{"is_valid":false,"config":[0,1,1,1],"size":3},{"is_valid":false,"config":[1,1,0,0],"size":2},{"is_valid":false,"config":[1,0,0,1],"size":2},{"is_valid":true,"config":[0,0,1,0],"size":1},{"is_valid":true,"config":[1,0,0,0],"size":1},{"is_valid":false,"config":[1,1,1,1],"size":4},{"is_valid":false,"config":[0,1,1,0],"size":2}],"best_solutions":[[0,1,0,1]]}],"problem_type":"MaximalIS"}
\ No newline at end of file
diff --git a/tests/data/jl/paintshop.json b/tests/data/jl/paintshop.json
index 4c7637bc..55c29e19 100644
--- a/tests/data/jl/paintshop.json
+++ b/tests/data/jl/paintshop.json
@@ -1 +1 @@
-{"instances":[{"label":"doc_abaccb","instance":{"num_cars":3,"sequence":["a","b","a","c","c","b"]},"evaluations":[{"is_valid":true,"config":[0,1,0],"size":4},{"is_valid":true,"config":[1,0,1],"size":4},{"is_valid":true,"config":[1,0,0],"size":2},{"is_valid":true,"config":[0,0,1],"size":3},{"is_valid":true,"config":[0,0,0],"size":3},{"is_valid":true,"config":[1,1,1],"size":3},{"is_valid":true,"config":[0,1,1],"size":2},{"is_valid":true,"config":[1,1,0],"size":3}],"best_solutions":[[1,0,0],[0,1,1]]}],"problem_type":"PaintShop"}
\ No newline at end of file
+{"instances":[{"label":"doc_abaccb","instance":{"num_cars":3,"sequence":["a","b","a","c","c","b"]},"evaluations":[{"is_valid":true,"config":[0,1,0],"size":4},{"is_valid":true,"config":[1,0,1],"size":4},{"is_valid":true,"config":[1,0,0],"size":2},{"is_valid":true,"config":[0,0,1],"size":3},{"is_valid":true,"config":[0,0,0],"size":3},{"is_valid":true,"config":[1,1,1],"size":3},{"is_valid":true,"config":[1,1,0],"size":3},{"is_valid":true,"config":[0,1,1],"size":2}],"best_solutions":[[1,0,0],[0,1,1]]}],"problem_type":"PaintShop"}
\ No newline at end of file
diff --git a/tests/data/jl/path_factoring_to_spinglass.json b/tests/data/jl/path_factoring_to_spinglass.json
new file mode 100644
index 00000000..9927356b
--- /dev/null
+++ b/tests/data/jl/path_factoring_to_spinglass.json
@@ -0,0 +1 @@
+{"extracted":[[1,1,1]],"best_source":[[1,1,1]],"path":["UnionAll","UnionAll","UnionAll","UnionAll"]}
\ No newline at end of file
diff --git a/tests/data/jl/path_maxcut_to_qubo.json b/tests/data/jl/path_maxcut_to_qubo.json
new file mode 100644
index 00000000..ce8447dc
--- /dev/null
+++ b/tests/data/jl/path_maxcut_to_qubo.json
@@ -0,0 +1 @@
+{"extracted":[[0,0,1,0,1,1,1,0,0,0],[0,1,0,0,1,0,0,1,1,0],[0,1,0,1,0,1,0,0,0,1],[0,1,0,1,1,1,1,1,0,0],[0,1,1,0,1,1,0,0,1,1],[1,0,0,1,0,0,1,1,0,0],[1,0,1,0,0,0,0,0,1,1],[1,0,1,0,1,0,1,1,1,0],[1,0,1,1,0,1,1,0,0,1],[1,1,0,1,0,0,0,1,1,1]],"best_source":[[0,0,1,0,1,1,1,0,0,0],[1,0,0,1,0,0,1,1,0,0],[0,1,0,1,1,1,1,1,0,0],[0,1,0,0,1,0,0,1,1,0],[1,0,1,0,1,0,1,1,1,0],[0,1,0,1,0,1,0,0,0,1],[1,0,1,1,0,1,1,0,0,1],[1,0,1,0,0,0,0,0,1,1],[0,1,1,0,1,1,0,0,1,1],[1,1,0,1,0,0,0,1,1,1]],"path":["UnionAll","UnionAll","UnionAll"]}
\ No newline at end of file
diff --git a/tests/data/jl/path_maxcut_to_spinglass.json b/tests/data/jl/path_maxcut_to_spinglass.json
new file mode 100644
index 00000000..30dfd037
--- /dev/null
+++ b/tests/data/jl/path_maxcut_to_spinglass.json
@@ -0,0 +1 @@
+{"extracted":[[0,0,1,0,1,1,1,0,0,0],[0,1,0,0,1,0,0,1,1,0],[0,1,0,1,0,1,0,0,0,1],[0,1,0,1,1,1,1,1,0,0],[0,1,1,0,1,1,0,0,1,1],[1,0,0,1,0,0,1,1,0,0],[1,0,1,0,0,0,0,0,1,1],[1,0,1,0,1,0,1,1,1,0],[1,0,1,1,0,1,1,0,0,1],[1,1,0,1,0,0,0,1,1,1]],"best_source":[[0,0,1,0,1,1,1,0,0,0],[1,0,0,1,0,0,1,1,0,0],[0,1,0,1,1,1,1,1,0,0],[0,1,0,0,1,0,0,1,1,0],[1,0,1,0,1,0,1,1,1,0],[0,1,0,1,0,1,0,0,0,1],[1,0,1,1,0,1,1,0,0,1],[1,0,1,0,0,0,0,0,1,1],[0,1,1,0,1,1,0,0,1,1],[1,1,0,1,0,0,0,1,1,1]],"path":["UnionAll","UnionAll","UnionAll"],"best_target":[[0,0,1,0,1,1,1,0,0,0],[1,0,0,1,0,0,1,1,0,0],[0,1,0,1,1,1,1,1,0,0],[0,1,0,0,1,0,0,1,1,0],[1,0,1,0,1,0,1,1,1,0],[0,1,0,1,0,1,0,0,0,1],[1,0,1,1,0,1,1,0,0,1],[1,0,1,0,0,0,0,0,1,1],[0,1,1,0,1,1,0,0,1,1],[1,1,0,1,0,0,0,1,1,1]]}
\ No newline at end of file
diff --git a/tests/data/jl/qubo.json b/tests/data/jl/qubo.json
index 097dd733..6bfb94d3 100644
--- a/tests/data/jl/qubo.json
+++ b/tests/data/jl/qubo.json
@@ -1 +1 @@
-{"instances":[{"label":"3x3_matrix","instance":{"matrix":[[0,1,-2],[1,0,-2],[-2,-2,6]]},"evaluations":[{"is_valid":true,"config":[0,1,0],"size":0},{"is_valid":true,"config":[1,0,1],"size":2},{"is_valid":true,"config":[1,0,0],"size":0},{"is_valid":true,"config":[0,0,1],"size":6},{"is_valid":true,"config":[0,0,0],"size":0},{"is_valid":true,"config":[1,1,1],"size":0},{"is_valid":true,"config":[1,1,0],"size":2},{"is_valid":true,"config":[0,1,1],"size":2}],"best_solutions":[[0,0,0],[1,0,0],[0,1,0],[1,1,1]]},{"label":"doc_identity","instance":{"matrix":[[1.0,0.0,0.0],[0.0,1.0,0.0],[0.0,0.0,1.0]]},"evaluations":[{"is_valid":true,"config":[0,1,0],"size":1.0},{"is_valid":true,"config":[1,0,1],"size":2.0},{"is_valid":true,"config":[1,0,0],"size":1.0},{"is_valid":true,"config":[0,0,1],"size":1.0},{"is_valid":true,"config":[0,0,0],"size":0.0},{"is_valid":true,"config":[1,1,1],"size":3.0},{"is_valid":true,"config":[1,1,0],"size":2.0},{"is_valid":true,"config":[0,1,1],"size":2.0}],"best_solutions":[[0,0,0]]},{"label":"rule_3x3","instance":{"matrix":[[2,1,-2],[1,2,-2],[-2,-2,2]]},"evaluations":[{"is_valid":true,"config":[0,1,0],"size":2},{"is_valid":true,"config":[1,0,1],"size":0},{"is_valid":true,"config":[1,0,0],"size":2},{"is_valid":true,"config":[0,0,1],"size":2},{"is_valid":true,"config":[0,0,0],"size":0},{"is_valid":true,"config":[1,1,1],"size":0},{"is_valid":true,"config":[1,1,0],"size":6},{"is_valid":true,"config":[0,1,1],"size":0}],"best_solutions":[[0,0,0],[1,0,1],[0,1,1],[1,1,1]]}],"problem_type":"QUBO"}
\ No newline at end of file
+{"instances":[{"label":"3x3_matrix","instance":{"matrix":[[0,1,-2],[1,0,-2],[-2,-2,6]]},"evaluations":[{"is_valid":true,"config":[0,1,0],"size":0},{"is_valid":true,"config":[1,0,1],"size":2},{"is_valid":true,"config":[1,0,0],"size":0},{"is_valid":true,"config":[0,0,1],"size":6},{"is_valid":true,"config":[0,0,0],"size":0},{"is_valid":true,"config":[1,1,1],"size":0},{"is_valid":true,"config":[0,1,1],"size":2},{"is_valid":true,"config":[1,1,0],"size":2}],"best_solutions":[[0,0,0],[1,0,0],[0,1,0],[1,1,1]]},{"label":"doc_identity","instance":{"matrix":[[1.0,0.0,0.0],[0.0,1.0,0.0],[0.0,0.0,1.0]]},"evaluations":[{"is_valid":true,"config":[0,1,0],"size":1.0},{"is_valid":true,"config":[1,0,1],"size":2.0},{"is_valid":true,"config":[0,0,1],"size":1.0},{"is_valid":true,"config":[1,0,0],"size":1.0},{"is_valid":true,"config":[0,0,0],"size":0.0},{"is_valid":true,"config":[1,1,1],"size":3.0},{"is_valid":true,"config":[0,1,1],"size":2.0},{"is_valid":true,"config":[1,1,0],"size":2.0}],"best_solutions":[[0,0,0]]},{"label":"rule_3x3","instance":{"matrix":[[2,1,-2],[1,2,-2],[-2,-2,2]]},"evaluations":[{"is_valid":true,"config":[0,1,0],"size":2},{"is_valid":true,"config":[1,0,1],"size":0},{"is_valid":true,"config":[1,0,0],"size":2},{"is_valid":true,"config":[0,0,1],"size":2},{"is_valid":true,"config":[0,0,0],"size":0},{"is_valid":true,"config":[1,1,1],"size":0},{"is_valid":true,"config":[1,1,0],"size":6},{"is_valid":true,"config":[0,1,1],"size":0}],"best_solutions":[[0,0,0],[1,0,1],[0,1,1],[1,1,1]]}],"problem_type":"QUBO"}
\ No newline at end of file
diff --git a/tests/data/jl/rule_sat01_to_independentset.json b/tests/data/jl/rule_sat01_to_independentset.json
index ee4a6f3e..4b31cea2 100644
--- a/tests/data/jl/rule_sat01_to_independentset.json
+++ b/tests/data/jl/rule_sat01_to_independentset.json
@@ -1 +1 @@
-{"target_type":"IndependentSet","cases":[{"label":"rule_sat01","extracted_single":[[0,0,0],[0,0,1],[1,1,0],[1,1,1]],"extracted_multiple":[[0,0,0],[0,0,1],[1,1,0],[1,1,1]],"best_source":[[0,0,0],[1,1,0],[0,0,1],[1,1,1]],"best_target":[[0,1,0,1,0,0,0,1,0,1,0,0],[0,0,1,1,0,0,0,1,0,1,0,0],[0,1,0,0,0,1,0,1,0,1,0,0],[0,1,0,1,0,0,0,0,1,1,0,0],[0,1,0,0,0,1,0,0,1,1,0,0],[1,0,0,0,1,0,1,0,0,0,1,0],[0,0,1,0,1,0,1,0,0,0,1,0],[1,0,0,0,0,1,1,0,0,0,1,0],[1,0,0,0,1,0,0,0,1,0,1,0],[1,0,0,0,0,1,0,0,1,0,1,0],[1,0,0,0,1,0,1,0,0,0,0,1],[0,0,1,0,1,0,1,0,0,0,0,1],[0,1,0,1,0,0,0,1,0,0,0,1],[0,0,1,1,0,0,0,1,0,0,0,1]]}],"source_type":"rule_SAT01"}
\ No newline at end of file
+{"target_type":"IndependentSet","cases":[{"label":"rule_sat01","extracted_single":[[0,0,1],[0,0,0],[1,1,1],[1,1,0]],"extracted_multiple":[[0,0,0],[0,0,1],[1,1,0],[1,1,1]],"best_source":[[0,0,0],[1,1,0],[0,0,1],[1,1,1]],"best_target":[[0,1,0,1,0,0,0,1,0,1,0,0],[0,0,1,1,0,0,0,1,0,1,0,0],[0,1,0,0,0,1,0,1,0,1,0,0],[0,1,0,1,0,0,0,0,1,1,0,0],[0,1,0,0,0,1,0,0,1,1,0,0],[1,0,0,0,1,0,1,0,0,0,1,0],[0,0,1,0,1,0,1,0,0,0,1,0],[1,0,0,0,0,1,1,0,0,0,1,0],[1,0,0,0,1,0,0,0,1,0,1,0],[1,0,0,0,0,1,0,0,1,0,1,0],[1,0,0,0,1,0,1,0,0,0,0,1],[0,0,1,0,1,0,1,0,0,0,0,1],[0,1,0,1,0,0,0,1,0,0,0,1],[0,0,1,1,0,0,0,1,0,0,0,1]]}],"source_type":"rule_SAT01"}
\ No newline at end of file
diff --git a/tests/data/jl/rule_sat02_to_independentset.json b/tests/data/jl/rule_sat02_to_independentset.json
index 2a41c9a8..93b22648 100644
--- a/tests/data/jl/rule_sat02_to_independentset.json
+++ b/tests/data/jl/rule_sat02_to_independentset.json
@@ -1 +1 @@
-{"target_type":"IndependentSet","cases":[{"label":"rule_sat02","extracted_single":[[1,1,1],[1,0,1],[1,1,0],[0,1,1],[0,0,0]],"extracted_multiple":[[1,1,0],[1,1,1],[1,0,1],[0,1,1],[0,0,0]],"best_source":[[0,0,0],[1,1,0],[1,0,1],[0,1,1],[1,1,1]],"best_target":[[0,1,0,1,0,0,1,0,0],[0,0,1,1,0,0,1,0,0],[0,0,1,0,1,0,1,0,0],[0,1,0,0,0,1,1,0,0],[0,0,1,0,0,1,1,0,0],[0,1,0,1,0,0,0,1,0],[0,0,1,1,0,0,0,1,0],[1,0,0,0,0,1,0,1,0],[0,1,0,0,0,1,0,1,0],[0,0,1,0,0,1,0,1,0],[0,1,0,1,0,0,0,0,1],[1,0,0,0,1,0,0,0,1]]}],"source_type":"rule_SAT02"}
\ No newline at end of file
+{"target_type":"IndependentSet","cases":[{"label":"rule_sat02","extracted_single":[[1,1,0],[1,0,1],[1,1,1],[0,1,1],[0,0,0]],"extracted_multiple":[[1,1,0],[1,1,1],[1,0,1],[0,1,1],[0,0,0]],"best_source":[[0,0,0],[1,1,0],[1,0,1],[0,1,1],[1,1,1]],"best_target":[[0,1,0,1,0,0,1,0,0],[0,0,1,1,0,0,1,0,0],[0,0,1,0,1,0,1,0,0],[0,1,0,0,0,1,1,0,0],[0,0,1,0,0,1,1,0,0],[0,1,0,1,0,0,0,1,0],[0,0,1,1,0,0,0,1,0],[1,0,0,0,0,1,0,1,0],[0,1,0,0,0,1,0,1,0],[0,0,1,0,0,1,0,1,0],[0,1,0,1,0,0,0,0,1],[1,0,0,0,1,0,0,0,1]]}],"source_type":"rule_SAT02"}
\ No newline at end of file
diff --git a/tests/data/jl/rule_sat03_to_independentset.json b/tests/data/jl/rule_sat03_to_independentset.json
index 668a179d..2ea481cf 100644
--- a/tests/data/jl/rule_sat03_to_independentset.json
+++ b/tests/data/jl/rule_sat03_to_independentset.json
@@ -1 +1 @@
-{"target_type":"IndependentSet","cases":[{"label":"rule_sat03","extracted_single":[[0,1,0],[0,1,1],[1,0,0],[1,0,1],[1,1,0]],"extracted_multiple":[[0,1,0],[0,1,1],[0,0,1],[1,0,0],[1,0,1],[1,1,0]],"best_source":[[1,0,0],[0,1,0],[1,1,0],[0,0,1],[1,0,1],[0,1,1]],"best_target":[[0,1,0,1,0,0],[0,0,1,1,0,0],[1,0,0,0,1,0],[0,0,1,0,1,0],[1,0,0,0,0,1],[0,1,0,0,0,1]]}],"source_type":"rule_SAT03"}
\ No newline at end of file
+{"target_type":"IndependentSet","cases":[{"label":"rule_sat03","extracted_single":[[0,1,1],[1,0,1],[0,0,1],[1,0,0],[0,1,0]],"extracted_multiple":[[0,1,0],[0,1,1],[0,0,1],[1,0,0],[1,0,1],[1,1,0]],"best_source":[[1,0,0],[0,1,0],[1,1,0],[0,0,1],[1,0,1],[0,1,1]],"best_target":[[0,1,0,1,0,0],[0,0,1,1,0,0],[1,0,0,0,1,0],[0,0,1,0,1,0],[1,0,0,0,0,1],[0,1,0,0,0,1]]}],"source_type":"rule_SAT03"}
\ No newline at end of file
diff --git a/tests/data/jl/satisfiability.json b/tests/data/jl/satisfiability.json
index 0e47af4e..9d9b5400 100644
--- a/tests/data/jl/satisfiability.json
+++ b/tests/data/jl/satisfiability.json
@@ -1 +1 @@
-{"instances":[{"label":"simple_clause","instance":{"num_variables":2,"clauses":[{"literals":[{"negated":false,"variable":0},{"negated":false,"variable":1}]}]},"evaluations":[{"is_valid":true,"config":[0,0],"size":0},{"is_valid":true,"config":[1,1],"size":1},{"is_valid":true,"config":[1,0],"size":1},{"is_valid":true,"config":[0,1],"size":1}],"best_solutions":[[1,0],[0,1],[1,1]]},{"label":"rule_3sat_multi","instance":{"num_variables":4,"clauses":[{"literals":[{"negated":false,"variable":0}]},{"literals":[{"negated":true,"variable":1},{"negated":false,"variable":2}]},{"literals":[{"negated":false,"variable":0},{"negated":true,"variable":1},{"negated":false,"variable":2},{"negated":false,"variable":3}]}]},"evaluations":[{"is_valid":true,"config":[1,0,1,1],"size":3},{"is_valid":true,"config":[1,0,1,0],"size":3},{"is_valid":true,"config":[0,0,0,1],"size":2},{"is_valid":true,"config":[0,0,0,0],"size":2},{"is_valid":true,"config":[0,1,1,1],"size":2},{"is_valid":true,"config":[1,1,0,1],"size":2},{"is_valid":true,"config":[0,1,1,0],"size":2},{"is_valid":true,"config":[0,0,1,0],"size":2},{"is_valid":true,"config":[1,0,0,0],"size":3},{"is_valid":true,"config":[1,1,1,1],"size":3}],"best_solutions":[[1,0,0,0],[1,0,1,0],[1,1,1,0],[1,0,0,1],[1,0,1,1],[1,1,1,1]]},{"label":"rule_sat01","instance":{"num_variables":3,"clauses":[{"literals":[{"negated":false,"variable":0},{"negated":true,"variable":1},{"negated":false,"variable":2}]},{"literals":[{"negated":true,"variable":0},{"negated":false,"variable":1},{"negated":true,"variable":2}]},{"literals":[{"negated":false,"variable":0},{"negated":true,"variable":1},{"negated":true,"variable":2}]},{"literals":[{"negated":true,"variable":0},{"negated":false,"variable":1},{"negated":false,"variable":2}]}]},"evaluations":[{"is_valid":true,"config":[0,1,0],"size":3},{"is_valid":true,"config":[1,0,1],"size":3},{"is_valid":true,"config":[0,0,1],"size":4},{"is_valid":true,"config":[1,0,0],"size":3},{"is_valid":true,"config":[0,0,0],"size":4},{"is_valid":true,"config":[1,1,1],"size":4},{"is_valid":true,"config":[0,1,1],"size":3},{"is_valid":true,"config":[1,1,0],"size":4}],"best_solutions":[[0,0,0],[1,1,0],[0,0,1],[1,1,1]]},{"label":"rule_sat02","instance":{"num_variables":3,"clauses":[{"literals":[{"negated":true,"variable":0},{"negated":false,"variable":1},{"negated":false,"variable":2}]},{"literals":[{"negated":false,"variable":0},{"negated":true,"variable":1},{"negated":false,"variable":2}]},{"literals":[{"negated":false,"variable":0},{"negated":false,"variable":1},{"negated":true,"variable":2}]}]},"evaluations":[{"is_valid":true,"config":[0,1,0],"size":2},{"is_valid":true,"config":[1,0,1],"size":3},{"is_valid":true,"config":[0,0,1],"size":2},{"is_valid":true,"config":[1,0,0],"size":2},{"is_valid":true,"config":[0,0,0],"size":3},{"is_valid":true,"config":[1,1,1],"size":3},{"is_valid":true,"config":[1,1,0],"size":3},{"is_valid":true,"config":[0,1,1],"size":3}],"best_solutions":[[0,0,0],[1,1,0],[1,0,1],[0,1,1],[1,1,1]]},{"label":"rule_sat03","instance":{"num_variables":3,"clauses":[{"literals":[{"negated":false,"variable":0},{"negated":false,"variable":1},{"negated":false,"variable":2}]},{"literals":[{"negated":true,"variable":0},{"negated":true,"variable":1},{"negated":true,"variable":2}]}]},"evaluations":[{"is_valid":true,"config":[0,1,0],"size":2},{"is_valid":true,"config":[1,0,1],"size":2},{"is_valid":true,"config":[0,0,1],"size":2},{"is_valid":true,"config":[1,0,0],"size":2},{"is_valid":true,"config":[0,0,0],"size":1},{"is_valid":true,"config":[1,1,1],"size":1},{"is_valid":true,"config":[0,1,1],"size":2},{"is_valid":true,"config":[1,1,0],"size":2}],"best_solutions":[[1,0,0],[0,1,0],[1,1,0],[0,0,1],[1,0,1],[0,1,1]]},{"label":"rule_sat04_unsat","instance":{"num_variables":1,"clauses":[{"literals":[{"negated":false,"variable":0},{"negated":false,"variable":0},{"negated":false,"variable":0}]},{"literals":[{"negated":true,"variable":0},{"negated":true,"variable":0},{"negated":true,"variable":0}]}]},"evaluations":[{"is_valid":true,"config":[1],"size":1},{"is_valid":true,"config":[0],"size":1}],"best_solutions":[[0],[1]]},{"label":"rule_sat05_unsat","instance":{"num_variables":1,"clauses":[{"literals":[{"negated":false,"variable":0}]},{"literals":[{"negated":true,"variable":0}]}]},"evaluations":[{"is_valid":true,"config":[1],"size":1},{"is_valid":true,"config":[0],"size":1}],"best_solutions":[[0],[1]]},{"label":"rule_sat06_unsat","instance":{"num_variables":2,"clauses":[{"literals":[{"negated":false,"variable":0},{"negated":false,"variable":1}]},{"literals":[{"negated":false,"variable":0},{"negated":true,"variable":1}]},{"literals":[{"negated":true,"variable":0},{"negated":false,"variable":1}]},{"literals":[{"negated":true,"variable":0},{"negated":true,"variable":1}]}]},"evaluations":[{"is_valid":true,"config":[0,0],"size":3},{"is_valid":true,"config":[1,1],"size":3},{"is_valid":true,"config":[1,0],"size":3},{"is_valid":true,"config":[0,1],"size":3}],"best_solutions":[[0,0],[1,0],[0,1],[1,1]]},{"label":"rule_sat07","instance":{"num_variables":2,"clauses":[{"literals":[{"negated":false,"variable":0},{"negated":false,"variable":1}]},{"literals":[{"negated":false,"variable":0},{"negated":true,"variable":1}]},{"literals":[{"negated":true,"variable":0},{"negated":false,"variable":1}]}]},"evaluations":[{"is_valid":true,"config":[0,0],"size":2},{"is_valid":true,"config":[1,1],"size":3},{"is_valid":true,"config":[1,0],"size":2},{"is_valid":true,"config":[0,1],"size":2}],"best_solutions":[[1,1]]},{"label":"rule_sat_coloring","instance":{"num_variables":2,"clauses":[{"literals":[{"negated":false,"variable":0},{"negated":false,"variable":1}]},{"literals":[{"negated":false,"variable":0},{"negated":true,"variable":1}]}]},"evaluations":[{"is_valid":true,"config":[0,0],"size":1},{"is_valid":true,"config":[1,1],"size":2},{"is_valid":true,"config":[1,0],"size":2},{"is_valid":true,"config":[0,1],"size":1}],"best_solutions":[[1,0],[1,1]]}],"problem_type":"Satisfiability"}
\ No newline at end of file
+{"instances":[{"label":"simple_clause","instance":{"num_variables":2,"clauses":[{"literals":[{"negated":false,"variable":0},{"negated":false,"variable":1}]}]},"evaluations":[{"is_valid":true,"config":[0,0],"size":0},{"is_valid":true,"config":[1,1],"size":1},{"is_valid":true,"config":[1,0],"size":1},{"is_valid":true,"config":[0,1],"size":1}],"best_solutions":[[1,0],[0,1],[1,1]]},{"label":"rule_3sat_multi","instance":{"num_variables":4,"clauses":[{"literals":[{"negated":false,"variable":0}]},{"literals":[{"negated":true,"variable":1},{"negated":false,"variable":2}]},{"literals":[{"negated":false,"variable":0},{"negated":true,"variable":1},{"negated":false,"variable":2},{"negated":false,"variable":3}]}]},"evaluations":[{"is_valid":true,"config":[1,1,1,0],"size":3},{"is_valid":true,"config":[0,1,0,1],"size":1},{"is_valid":true,"config":[1,0,1,0],"size":3},{"is_valid":true,"config":[0,0,0,0],"size":2},{"is_valid":true,"config":[1,1,0,1],"size":2},{"is_valid":true,"config":[1,1,0,0],"size":2},{"is_valid":true,"config":[0,0,1,1],"size":2},{"is_valid":true,"config":[0,0,1,0],"size":2},{"is_valid":true,"config":[1,0,0,0],"size":3},{"is_valid":true,"config":[1,1,1,1],"size":3}],"best_solutions":[[1,0,0,0],[1,0,1,0],[1,1,1,0],[1,0,0,1],[1,0,1,1],[1,1,1,1]]},{"label":"rule_sat01","instance":{"num_variables":3,"clauses":[{"literals":[{"negated":false,"variable":0},{"negated":true,"variable":1},{"negated":false,"variable":2}]},{"literals":[{"negated":true,"variable":0},{"negated":false,"variable":1},{"negated":true,"variable":2}]},{"literals":[{"negated":false,"variable":0},{"negated":true,"variable":1},{"negated":true,"variable":2}]},{"literals":[{"negated":true,"variable":0},{"negated":false,"variable":1},{"negated":false,"variable":2}]}]},"evaluations":[{"is_valid":true,"config":[0,1,0],"size":3},{"is_valid":true,"config":[1,0,1],"size":3},{"is_valid":true,"config":[0,0,1],"size":4},{"is_valid":true,"config":[1,0,0],"size":3},{"is_valid":true,"config":[0,0,0],"size":4},{"is_valid":true,"config":[1,1,1],"size":4},{"is_valid":true,"config":[1,1,0],"size":4},{"is_valid":true,"config":[0,1,1],"size":3}],"best_solutions":[[0,0,0],[1,1,0],[0,0,1],[1,1,1]]},{"label":"rule_sat02","instance":{"num_variables":3,"clauses":[{"literals":[{"negated":true,"variable":0},{"negated":false,"variable":1},{"negated":false,"variable":2}]},{"literals":[{"negated":false,"variable":0},{"negated":true,"variable":1},{"negated":false,"variable":2}]},{"literals":[{"negated":false,"variable":0},{"negated":false,"variable":1},{"negated":true,"variable":2}]}]},"evaluations":[{"is_valid":true,"config":[0,1,0],"size":2},{"is_valid":true,"config":[1,0,1],"size":3},{"is_valid":true,"config":[0,0,1],"size":2},{"is_valid":true,"config":[1,0,0],"size":2},{"is_valid":true,"config":[0,0,0],"size":3},{"is_valid":true,"config":[1,1,1],"size":3},{"is_valid":true,"config":[0,1,1],"size":3},{"is_valid":true,"config":[1,1,0],"size":3}],"best_solutions":[[0,0,0],[1,1,0],[1,0,1],[0,1,1],[1,1,1]]},{"label":"rule_sat03","instance":{"num_variables":3,"clauses":[{"literals":[{"negated":false,"variable":0},{"negated":false,"variable":1},{"negated":false,"variable":2}]},{"literals":[{"negated":true,"variable":0},{"negated":true,"variable":1},{"negated":true,"variable":2}]}]},"evaluations":[{"is_valid":true,"config":[0,1,0],"size":2},{"is_valid":true,"config":[1,0,1],"size":2},{"is_valid":true,"config":[0,0,1],"size":2},{"is_valid":true,"config":[1,0,0],"size":2},{"is_valid":true,"config":[0,0,0],"size":1},{"is_valid":true,"config":[1,1,1],"size":1},{"is_valid":true,"config":[0,1,1],"size":2},{"is_valid":true,"config":[1,1,0],"size":2}],"best_solutions":[[1,0,0],[0,1,0],[1,1,0],[0,0,1],[1,0,1],[0,1,1]]},{"label":"rule_sat04_unsat","instance":{"num_variables":1,"clauses":[{"literals":[{"negated":false,"variable":0},{"negated":false,"variable":0},{"negated":false,"variable":0}]},{"literals":[{"negated":true,"variable":0},{"negated":true,"variable":0},{"negated":true,"variable":0}]}]},"evaluations":[{"is_valid":true,"config":[1],"size":1},{"is_valid":true,"config":[0],"size":1}],"best_solutions":[[0],[1]]},{"label":"rule_sat05_unsat","instance":{"num_variables":1,"clauses":[{"literals":[{"negated":false,"variable":0}]},{"literals":[{"negated":true,"variable":0}]}]},"evaluations":[{"is_valid":true,"config":[1],"size":1},{"is_valid":true,"config":[0],"size":1}],"best_solutions":[[0],[1]]},{"label":"rule_sat06_unsat","instance":{"num_variables":2,"clauses":[{"literals":[{"negated":false,"variable":0},{"negated":false,"variable":1}]},{"literals":[{"negated":false,"variable":0},{"negated":true,"variable":1}]},{"literals":[{"negated":true,"variable":0},{"negated":false,"variable":1}]},{"literals":[{"negated":true,"variable":0},{"negated":true,"variable":1}]}]},"evaluations":[{"is_valid":true,"config":[0,0],"size":3},{"is_valid":true,"config":[1,1],"size":3},{"is_valid":true,"config":[1,0],"size":3},{"is_valid":true,"config":[0,1],"size":3}],"best_solutions":[[0,0],[1,0],[0,1],[1,1]]},{"label":"rule_sat07","instance":{"num_variables":2,"clauses":[{"literals":[{"negated":false,"variable":0},{"negated":false,"variable":1}]},{"literals":[{"negated":false,"variable":0},{"negated":true,"variable":1}]},{"literals":[{"negated":true,"variable":0},{"negated":false,"variable":1}]}]},"evaluations":[{"is_valid":true,"config":[0,0],"size":2},{"is_valid":true,"config":[1,1],"size":3},{"is_valid":true,"config":[1,0],"size":2},{"is_valid":true,"config":[0,1],"size":2}],"best_solutions":[[1,1]]},{"label":"rule_sat_coloring","instance":{"num_variables":2,"clauses":[{"literals":[{"negated":false,"variable":0},{"negated":false,"variable":1}]},{"literals":[{"negated":false,"variable":0},{"negated":true,"variable":1}]}]},"evaluations":[{"is_valid":true,"config":[0,0],"size":1},{"is_valid":true,"config":[1,1],"size":2},{"is_valid":true,"config":[1,0],"size":2},{"is_valid":true,"config":[0,1],"size":1}],"best_solutions":[[1,0],[1,1]]}],"problem_type":"Satisfiability"}
\ No newline at end of file
diff --git a/tests/data/jl/satisfiability_to_independentset.json b/tests/data/jl/satisfiability_to_independentset.json
index 57a35944..64d81eef 100644
--- a/tests/data/jl/satisfiability_to_independentset.json
+++ b/tests/data/jl/satisfiability_to_independentset.json
@@ -1 +1 @@
-{"target_type":"IndependentSet","cases":[{"label":"sat_is","extracted_single":[[1,1],[0,1]],"extracted_multiple":[[1,0],[1,1],[0,1]],"best_source":[[1,0],[0,1],[1,1]],"best_target":[[1,0],[0,1]]}],"source_type":"Satisfiability"}
\ No newline at end of file
+{"target_type":"IndependentSet","cases":[{"label":"sat_is","extracted_single":[[1,0],[1,1]],"extracted_multiple":[[1,0],[1,1],[0,1]],"best_source":[[1,0],[0,1],[1,1]],"best_target":[[1,0],[0,1]]}],"source_type":"Satisfiability"}
\ No newline at end of file
diff --git a/tests/data/jl/setcovering.json b/tests/data/jl/setcovering.json
index ca2c3b25..b5013457 100644
--- a/tests/data/jl/setcovering.json
+++ b/tests/data/jl/setcovering.json
@@ -1 +1 @@
-{"instances":[{"label":"doc_3subsets","instance":{"universe_size":4,"sets":[[0,1,2],[1,3],[0,3]],"weights":[1,2,3]},"evaluations":[{"is_valid":false,"config":[0,1,0],"size":2},{"is_valid":true,"config":[1,0,1],"size":4},{"is_valid":false,"config":[0,0,1],"size":3},{"is_valid":false,"config":[1,0,0],"size":1},{"is_valid":false,"config":[0,0,0],"size":0},{"is_valid":true,"config":[1,1,1],"size":6},{"is_valid":false,"config":[0,1,1],"size":5},{"is_valid":true,"config":[1,1,0],"size":3}],"best_solutions":[[1,1,0]]}],"problem_type":"SetCovering"}
\ No newline at end of file
+{"instances":[{"label":"doc_3subsets","instance":{"universe_size":4,"sets":[[0,1,2],[1,3],[0,3]],"weights":[1,2,3]},"evaluations":[{"is_valid":false,"config":[0,1,0],"size":2},{"is_valid":true,"config":[1,0,1],"size":4},{"is_valid":false,"config":[0,0,1],"size":3},{"is_valid":false,"config":[1,0,0],"size":1},{"is_valid":false,"config":[0,0,0],"size":0},{"is_valid":true,"config":[1,1,1],"size":6},{"is_valid":true,"config":[1,1,0],"size":3},{"is_valid":false,"config":[0,1,1],"size":5}],"best_solutions":[[1,1,0]]}],"problem_type":"SetCovering"}
\ No newline at end of file
diff --git a/tests/data/jl/setpacking.json b/tests/data/jl/setpacking.json
index 1d6e6579..d2c18cf4 100644
--- a/tests/data/jl/setpacking.json
+++ b/tests/data/jl/setpacking.json
@@ -1 +1 @@
-{"instances":[{"label":"five_sets","instance":{"sets":[[0,1,4],[0,2],[1,3],[2,5],[1,2,5]],"weights":[1,1,1,1,1]},"evaluations":[{"is_valid":true,"config":[0,0,0,0,0],"size":0},{"is_valid":false,"config":[1,1,1,0,0],"size":3},{"is_valid":true,"config":[0,1,1,0,0],"size":2},{"is_valid":false,"config":[1,1,0,0,0],"size":2},{"is_valid":false,"config":[1,1,1,1,1],"size":5},{"is_valid":true,"config":[0,0,0,1,0],"size":1},{"is_valid":false,"config":[1,1,1,1,0],"size":4},{"is_valid":false,"config":[0,1,0,1,1],"size":3},{"is_valid":false,"config":[0,0,1,0,1],"size":2},{"is_valid":true,"config":[1,0,0,0,0],"size":1}],"best_solutions":[[0,1,1,0,0],[1,0,0,1,0],[0,0,1,1,0]]}],"problem_type":"SetPacking"}
\ No newline at end of file
+{"instances":[{"label":"five_sets","instance":{"sets":[[0,1,4],[0,2],[1,3],[2,5],[1,2,5]],"weights":[1,1,1,1,1]},"evaluations":[{"is_valid":true,"config":[0,0,0,0,0],"size":0},{"is_valid":false,"config":[1,0,1,1,1],"size":4},{"is_valid":false,"config":[0,0,1,1,1],"size":3},{"is_valid":false,"config":[1,1,1,0,0],"size":3},{"is_valid":true,"config":[0,0,1,1,0],"size":2},{"is_valid":false,"config":[1,1,1,1,1],"size":5},{"is_valid":true,"config":[0,0,0,1,0],"size":1},{"is_valid":false,"config":[0,1,0,1,1],"size":3},{"is_valid":false,"config":[1,0,1,0,1],"size":3},{"is_valid":true,"config":[0,0,1,0,0],"size":1}],"best_solutions":[[0,1,1,0,0],[1,0,0,1,0],[0,0,1,1,0]]}],"problem_type":"SetPacking"}
\ No newline at end of file
diff --git a/tests/data/jl/spinglass.json b/tests/data/jl/spinglass.json
index 7a59c2bf..bd08b167 100644
--- a/tests/data/jl/spinglass.json
+++ b/tests/data/jl/spinglass.json
@@ -1 +1 @@
-{"instances":[{"label":"petersen","instance":{"J":[1,2,1,2,1,2,1,2,1,2,1,2,1,2,1],"num_vertices":10,"edges":[[0,1],[0,4],[0,5],[1,2],[1,6],[2,3],[2,7],[3,4],[3,8],[4,9],[5,7],[5,8],[6,8],[6,9],[7,9]],"h":[0,0,0,0,0,0,0,0,0,0]},"evaluations":[{"is_valid":true,"config":[0,1,0,1,0,1,1,0,1,1],"size":-2},{"is_valid":true,"config":[1,0,0,1,0,1,0,0,1,0],"size":4},{"is_valid":true,"config":[1,1,0,0,0,0,1,0,0,0],"size":6},{"is_valid":true,"config":[0,0,0,0,1,0,1,0,0,1],"size":8},{"is_valid":true,"config":[0,0,0,0,0,0,0,0,0,0],"size":22},{"is_valid":true,"config":[0,1,1,1,0,1,0,0,0,0],"size":2},{"is_valid":true,"config":[0,1,0,1,0,1,1,1,1,1],"size":0},{"is_valid":true,"config":[1,0,1,0,1,0,0,1,0,0],"size":-2},{"is_valid":true,"config":[1,1,1,1,1,1,1,1,1,1],"size":22},{"is_valid":true,"config":[0,1,1,1,0,1,1,1,0,0],"size":0}],"best_solutions":[[0,0,1,0,1,1,1,0,0,0],[1,1,0,1,0,0,0,1,1,1]]},{"label":"doc_4vertex","instance":{"J":[1,-1,1,-1],"num_vertices":4,"edges":[[0,1],[0,2],[1,2],[2,3]],"h":[1,-1,-1,1]},"evaluations":[{"is_valid":true,"config":[1,0,1,1],"size":-6},{"is_valid":true,"config":[1,1,1,0],"size":4},{"is_valid":true,"config":[0,1,0,1],"size":-2},{"is_valid":true,"config":[0,0,0,0],"size":0},{"is_valid":true,"config":[1,1,0,1],"size":0},{"is_valid":true,"config":[0,1,1,1],"size":2},{"is_valid":true,"config":[0,0,1,1],"size":0},{"is_valid":true,"config":[0,1,1,0],"size":6},{"is_valid":true,"config":[0,0,1,0],"size":4},{"is_valid":true,"config":[1,1,1,1],"size":0}],"best_solutions":[[1,0,1,1]]},{"label":"rule_4vertex","instance":{"J":[1,3,1,4],"num_vertices":4,"edges":[[0,1],[0,2],[1,2],[2,3]],"h":[0,0,0,0]},"evaluations":[{"is_valid":true,"config":[1,1,1,0],"size":1},{"is_valid":true,"config":[1,0,1,1],"size":5},{"is_valid":true,"config":[0,1,0,1],"size":-3},{"is_valid":true,"config":[1,0,1,0],"size":-3},{"is_valid":true,"config":[0,1,0,0],"size":5},{"is_valid":true,"config":[0,0,0,0],"size":9},{"is_valid":true,"config":[0,1,1,0],"size":-7},{"is_valid":true,"config":[1,1,0,0],"size":1},{"is_valid":true,"config":[0,0,1,1],"size":1},{"is_valid":true,"config":[1,1,1,1],"size":9}],"best_solutions":[[0,0,1,0],[0,1,1,0],[1,0,0,1],[1,1,0,1]]}],"problem_type":"SpinGlass"}
\ No newline at end of file
+{"instances":[{"label":"petersen","instance":{"J":[1,2,1,2,1,2,1,2,1,2,1,2,1,2,1],"num_vertices":10,"edges":[[0,1],[0,4],[0,5],[1,2],[1,6],[2,3],[2,7],[3,4],[3,8],[4,9],[5,7],[5,8],[6,8],[6,9],[7,9]],"h":[0,0,0,0,0,0,0,0,0,0]},"evaluations":[{"is_valid":true,"config":[1,0,0,1,1,0,0,0,0,1],"size":6},{"is_valid":true,"config":[0,1,1,1,0,0,1,1,1,1],"size":6},{"is_valid":true,"config":[0,0,1,0,0,0,1,0,0,0],"size":4},{"is_valid":true,"config":[0,0,0,1,1,0,1,1,1,1],"size":4},{"is_valid":true,"config":[1,1,0,0,1,1,1,0,0,0],"size":-2},{"is_valid":true,"config":[0,0,1,1,1,0,1,0,0,1],"size":4},{"is_valid":true,"config":[0,0,0,0,0,0,0,0,0,0],"size":22},{"is_valid":true,"config":[1,0,1,0,0,1,0,0,1,1],"size":-10},{"is_valid":true,"config":[0,0,1,0,1,1,1,1,1,1],"size":0},{"is_valid":true,"config":[1,1,1,1,1,1,1,1,1,1],"size":22}],"best_solutions":[[0,0,1,0,1,1,1,0,0,0],[1,1,0,1,0,0,0,1,1,1]]},{"label":"doc_4vertex","instance":{"J":[1,-1,1,-1],"num_vertices":4,"edges":[[0,1],[0,2],[1,2],[2,3]],"h":[1,-1,-1,1]},"evaluations":[{"is_valid":true,"config":[1,0,1,1],"size":-6},{"is_valid":true,"config":[1,1,1,0],"size":4},{"is_valid":true,"config":[0,1,0,1],"size":-2},{"is_valid":true,"config":[0,1,0,0],"size":-2},{"is_valid":true,"config":[0,0,0,1],"size":0},{"is_valid":true,"config":[0,0,0,0],"size":0},{"is_valid":true,"config":[1,1,0,1],"size":0},{"is_valid":true,"config":[1,1,0,0],"size":0},{"is_valid":true,"config":[0,0,1,0],"size":4},{"is_valid":true,"config":[1,1,1,1],"size":0}],"best_solutions":[[1,0,1,1]]},{"label":"rule_4vertex","instance":{"J":[1,3,1,4],"num_vertices":4,"edges":[[0,1],[0,2],[1,2],[2,3]],"h":[0,0,0,0]},"evaluations":[{"is_valid":true,"config":[1,1,1,0],"size":1},{"is_valid":true,"config":[0,1,0,1],"size":-3},{"is_valid":true,"config":[1,0,1,0],"size":-3},{"is_valid":true,"config":[0,1,0,0],"size":5},{"is_valid":true,"config":[0,0,0,1],"size":1},{"is_valid":true,"config":[0,0,0,0],"size":9},{"is_valid":true,"config":[0,1,1,1],"size":1},{"is_valid":true,"config":[0,0,1,1],"size":1},{"is_valid":true,"config":[1,1,0,0],"size":1},{"is_valid":true,"config":[1,1,1,1],"size":9}],"best_solutions":[[0,0,1,0],[0,1,1,0],[1,0,0,1],[1,1,0,1]]}],"problem_type":"SpinGlass"}
\ No newline at end of file
diff --git a/tests/data/jl/vertexcovering.json b/tests/data/jl/vertexcovering.json
index a46341eb..1fe4214e 100644
--- a/tests/data/jl/vertexcovering.json
+++ b/tests/data/jl/vertexcovering.json
@@ -1 +1 @@
-{"instances":[{"label":"petersen","instance":{"weights":[1,2,1,2,1,2,1,2,1,2],"num_vertices":10,"edges":[[0,1],[0,4],[0,5],[1,2],[1,6],[2,3],[2,7],[3,4],[3,8],[4,9],[5,7],[5,8],[6,8],[6,9],[7,9]]},"evaluations":[{"is_valid":false,"config":[1,0,0,0,1,0,1,1,1,0],"size":6},{"is_valid":false,"config":[0,0,1,0,1,1,0,1,0,0],"size":6},{"is_valid":true,"config":[1,1,0,1,0,1,1,1,1,1],"size":13},{"is_valid":false,"config":[1,0,1,0,0,1,0,0,0,0],"size":4},{"is_valid":false,"config":[0,0,1,0,1,0,1,0,1,0],"size":4},{"is_valid":false,"config":[0,0,1,0,1,1,0,1,1,1],"size":9},{"is_valid":false,"config":[0,0,0,0,0,0,0,0,0,0],"size":0},{"is_valid":false,"config":[1,0,1,0,0,1,1,0,0,0],"size":5},{"is_valid":false,"config":[0,1,1,1,0,0,0,0,0,0],"size":5},{"is_valid":true,"config":[1,1,1,1,1,1,1,1,1,1],"size":15}],"best_solutions":[[1,0,1,0,1,0,1,1,1,0]]},{"label":"doc_4vertex","instance":{"weights":[1,3,1,4],"num_vertices":4,"edges":[[0,1],[0,2],[0,3],[1,2],[2,3]]},"evaluations":[{"is_valid":true,"config":[1,0,1,1],"size":6},{"is_valid":true,"config":[1,1,1,0],"size":5},{"is_valid":false,"config":[0,1,0,1],"size":7},{"is_valid":false,"config":[0,0,0,0],"size":0},{"is_valid":true,"config":[0,1,1,1],"size":8},{"is_valid":true,"config":[1,1,0,1],"size":8},{"is_valid":false,"config":[0,0,1,1],"size":5},{"is_valid":false,"config":[1,0,0,1],"size":5},{"is_valid":false,"config":[1,0,0,0],"size":1},{"is_valid":true,"config":[1,1,1,1],"size":9}],"best_solutions":[[1,0,1,0]]},{"label":"rule_4vertex","instance":{"weights":[1,3,1,4],"num_vertices":4,"edges":[[0,1],[0,2],[1,2],[2,3]]},"evaluations":[{"is_valid":true,"config":[1,1,1,0],"size":5},{"is_valid":false,"config":[0,1,0,1],"size":7},{"is_valid":false,"config":[0,0,0,1],"size":4},{"is_valid":false,"config":[0,1,0,0],"size":3},{"is_valid":false,"config":[0,0,0,0],"size":0},{"is_valid":true,"config":[1,1,0,1],"size":8},{"is_valid":true,"config":[0,1,1,0],"size":4},{"is_valid":false,"config":[1,0,0,0],"size":1},{"is_valid":false,"config":[0,0,1,0],"size":1},{"is_valid":true,"config":[1,1,1,1],"size":9}],"best_solutions":[[1,0,1,0]]}],"problem_type":"VertexCovering"}
\ No newline at end of file
+{"instances":[{"label":"petersen","instance":{"weights":[1,2,1,2,1,2,1,2,1,2],"num_vertices":10,"edges":[[0,1],[0,4],[0,5],[1,2],[1,6],[2,3],[2,7],[3,4],[3,8],[4,9],[5,7],[5,8],[6,8],[6,9],[7,9]]},"evaluations":[{"is_valid":false,"config":[0,0,0,1,1,1,1,0,0,1],"size":8},{"is_valid":false,"config":[0,1,1,0,0,0,0,1,1,1],"size":8},{"is_valid":false,"config":[1,0,0,0,0,1,0,1,1,0],"size":6},{"is_valid":false,"config":[0,0,0,1,1,1,1,1,1,1],"size":11},{"is_valid":false,"config":[0,0,0,1,1,0,1,1,1,1],"size":9},{"is_valid":false,"config":[1,0,1,0,1,0,1,0,1,1],"size":7},{"is_valid":false,"config":[0,1,0,1,1,1,0,1,0,0],"size":9},{"is_valid":false,"config":[0,0,0,0,0,0,0,0,0,0],"size":0},{"is_valid":true,"config":[1,1,1,1,1,1,1,1,1,1],"size":15},{"is_valid":false,"config":[1,0,1,1,1,0,1,0,0,1],"size":8}],"best_solutions":[[1,0,1,0,1,0,1,1,1,0]]},{"label":"doc_4vertex","instance":{"weights":[1,3,1,4],"num_vertices":4,"edges":[[0,1],[0,2],[0,3],[1,2],[2,3]]},"evaluations":[{"is_valid":true,"config":[1,1,1,0],"size":5},{"is_valid":true,"config":[1,0,1,1],"size":6},{"is_valid":true,"config":[1,0,1,0],"size":2},{"is_valid":false,"config":[0,0,0,0],"size":0},{"is_valid":true,"config":[0,1,1,1],"size":8},{"is_valid":false,"config":[1,1,0,0],"size":4},{"is_valid":false,"config":[1,0,0,1],"size":5},{"is_valid":false,"config":[1,0,0,0],"size":1},{"is_valid":false,"config":[0,0,1,0],"size":1},{"is_valid":true,"config":[1,1,1,1],"size":9}],"best_solutions":[[1,0,1,0]]},{"label":"rule_4vertex","instance":{"weights":[1,3,1,4],"num_vertices":4,"edges":[[0,1],[0,2],[1,2],[2,3]]},"evaluations":[{"is_valid":true,"config":[1,1,1,0],"size":5},{"is_valid":true,"config":[1,0,1,1],"size":6},{"is_valid":false,"config":[0,1,0,1],"size":7},{"is_valid":false,"config":[0,0,0,0],"size":0},{"is_valid":true,"config":[0,1,1,1],"size":8},{"is_valid":false,"config":[1,1,0,0],"size":4},{"is_valid":true,"config":[0,1,1,0],"size":4},{"is_valid":false,"config":[0,0,1,0],"size":1},{"is_valid":false,"config":[0,0,1,1],"size":5},{"is_valid":true,"config":[1,1,1,1],"size":9}],"best_solutions":[[1,0,1,0]]}],"problem_type":"VertexCovering"}
\ No newline at end of file
diff --git a/tests/suites/examples.rs b/tests/suites/examples.rs
index 8e665488..6658d056 100644
--- a/tests/suites/examples.rs
+++ b/tests/suites/examples.rs
@@ -10,6 +10,7 @@ macro_rules! example_test {
};
}
+example_test!(chained_reduction_factoring_to_spinglass);
example_test!(chained_reduction_ksat_to_mis);
example_test!(reduction_circuitsat_to_spinglass);
example_test!(reduction_factoring_to_circuitsat);
@@ -52,6 +53,10 @@ macro_rules! example_fn {
};
}
+example_fn!(
+ test_chained_reduction_factoring_to_spinglass,
+ chained_reduction_factoring_to_spinglass
+);
example_fn!(
test_chained_reduction_ksat_to_mis,
chained_reduction_ksat_to_mis