diff --git a/.gitignore b/.gitignore index 678eb006..220a2ab0 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,9 @@ quickcheck-tests.json /dist/ /build/ +# Julia +**/Manifest.toml + # Python __pycache__/ *.py[cod] diff --git a/Makefile b/Makefile index dd392f36..1d250a09 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # Makefile for problemreductions -.PHONY: help build test fmt clippy doc mdbook paper examples clean coverage rust-export compare qubo-testdata export-schemas release run-plan diagrams +.PHONY: help build test fmt clippy doc mdbook paper examples clean coverage rust-export compare qubo-testdata export-schemas release run-plan diagrams jl-testdata # Default target help: @@ -22,6 +22,7 @@ help: @echo " examples - Generate example JSON for paper" @echo " export-schemas - Export problem schemas to JSON" @echo " qubo-testdata - Regenerate QUBO test data (requires uv)" + @echo " jl-testdata - Regenerate Julia parity test data (requires julia)" @echo " release V=x.y.z - Tag and push a new release (triggers CI publish)" @echo " run-plan - Execute a plan with Claude autorun (latest plan in docs/plans/)" @@ -114,6 +115,9 @@ check: fmt-check clippy test qubo-testdata: cd scripts && uv run python generate_qubo_tests.py +jl-testdata: ## Regenerate Julia parity test data + cd scripts/jl && julia --project=. generate_testdata.jl + # Release a new version: make release V=0.2.0 release: ifndef V diff --git a/scripts/jl/Project.toml b/scripts/jl/Project.toml new file mode 100644 index 00000000..2648dfd7 --- /dev/null +++ b/scripts/jl/Project.toml @@ -0,0 +1,4 @@ +[deps] +ProblemReductions = "899c297d-f7d2-4ebf-8815-a35996def416" +Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" +JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" diff --git a/scripts/jl/generate_testdata.jl b/scripts/jl/generate_testdata.jl new file mode 100644 index 00000000..d66e24e3 --- /dev/null +++ b/scripts/jl/generate_testdata.jl @@ -0,0 +1,628 @@ +#!/usr/bin/env julia +# Generate JSON test fixtures from ProblemReductions.jl for Rust parity testing. +# Run: cd scripts/jl && julia --project=. generate_testdata.jl + +using ProblemReductions, Graphs, JSON + +const OUTDIR = joinpath(@__DIR__, "..", "..", "tests", "data", "jl") +mkpath(OUTDIR) + +# ── helpers ────────────────────────────────────────────────────────── + +"""Convert a SimpleGraph to a sorted list of 0-based edges [[u,v], ...].""" +function graph_to_edges(g::SimpleGraph) + return [[src(e) - 1, dst(e) - 1] for e in edges(g)] +end + +"""Convert HyperGraph hyperedges to 0-based lists.""" +function hypergraph_to_hyperedges(g::HyperGraph) + return [sort([v - 1 for v in he]) for he in g.edges] +end + +"""Write a JSON dict to tests/data/.""" +function write_fixture(filename, data) + path = joinpath(OUTDIR, filename) + open(path, "w") do f + JSON.print(f, data) + end + println(" wrote $path") +end + +"""Evaluate several configs on a problem and return evaluation dicts.""" +function evaluate_configs(problem, configs) + results = [] + for config in configs + ss = solution_size(problem, config) + d = Dict( + "config" => config, + "is_valid" => ss.is_valid, + "size" => ss.size, + ) + push!(results, d) + end + return results +end + +"""Generate random binary configs for a problem.""" +function sample_configs(problem; n=8) + nv = num_variables(problem) + nf = num_flavors(problem) + total_configs = nf^nv + n = min(n, total_configs) # cap at total possible configs + configs = Set{Vector{Int}}() + # always include all-zeros and all-ones + push!(configs, zeros(Int, nv)) + if nf == 2 && nv > 0 + push!(configs, ones(Int, nv)) + end + # random samples + while length(configs) < n + push!(configs, [rand(0:nf-1) for _ in 1:nv]) + end + return collect(configs) +end + +# ── model serializers ──────────────────────────────────────────────── + +function serialize_graph_problem(problem, graph::SimpleGraph; weight_field=:weights) + d = Dict( + "num_vertices" => nv(graph), + "edges" => graph_to_edges(graph), + ) + w = getfield(problem, weight_field) + if w isa UnitWeight + d["weights"] = ones(Int, w.n) + else + d["weights"] = collect(w) + end + return d +end + +function model_fixture(problem_type::String, instances) + return Dict( + "problem_type" => problem_type, + "instances" => instances, + ) +end + +function make_instance(label, instance_data, problem; extra=Dict()) + configs = sample_configs(problem; n=10) + evals = evaluate_configs(problem, configs) + best = findbest(problem, BruteForce()) + d = Dict( + "label" => label, + "instance" => instance_data, + "evaluations" => evals, + "best_solutions" => best, + ) + merge!(d, extra) + return d +end + +# ── model exports ──────────────────────────────────────────────────── + +function export_independentset(graph, label, weights=nothing) + if weights === nothing + is = IndependentSet(graph) + else + is = IndependentSet(graph, weights) + end + inst = serialize_graph_problem(is, graph) + return make_instance(label, inst, is) +end + +function export_spinglass(sg, graph, label) + inst = Dict( + "num_vertices" => nv(graph), + "edges" => graph_to_edges(graph), + "J" => collect(sg.J), + "h" => collect(sg.h), + ) + return make_instance(label, inst, sg) +end + +function export_maxcut(mc, graph, label) + inst = Dict( + "num_vertices" => nv(graph), + "edges" => graph_to_edges(graph), + ) + w = mc.weights + if w isa UnitWeight + inst["weights"] = ones(Int, w.n) + else + inst["weights"] = collect(w) + end + return make_instance(label, inst, mc) +end + +function export_qubo(q, label) + inst = Dict( + "matrix" => [collect(q.matrix[i, :]) for i in 1:size(q.matrix, 1)], + ) + return make_instance(label, inst, q) +end + +function export_sat(sat, label) + # Map symbols to 0-based integer indices + syms = sat.symbols + sym_to_idx = Dict(s => i - 1 for (i, s) in enumerate(syms)) + clauses = [] + for clause in sat.cnf.clauses + lits = [] + for bv in clause.vars + push!(lits, Dict( + "variable" => sym_to_idx[bv.name], + "negated" => bv.neg, + )) + end + push!(clauses, Dict("literals" => lits)) + end + inst = Dict( + "num_variables" => length(syms), + "clauses" => clauses, + ) + return make_instance(label, inst, sat) +end + +function export_ksat(ksat, k, label) + syms = ksat.symbols + sym_to_idx = Dict(s => i - 1 for (i, s) in enumerate(syms)) + clauses = [] + for clause in ksat.cnf.clauses + lits = [] + for bv in clause.vars + push!(lits, Dict( + "variable" => sym_to_idx[bv.name], + "negated" => bv.neg, + )) + end + push!(clauses, Dict("literals" => lits)) + end + inst = Dict( + "num_variables" => length(syms), + "clauses" => clauses, + "k" => k, + ) + return make_instance(label, inst, ksat) +end + +function export_vertexcovering(vc, graph, label) + inst = serialize_graph_problem(vc, graph) + return make_instance(label, inst, vc) +end + +function export_setpacking(sp, label) + # convert sets to 0-based + sets_0 = [sort([e - 1 for e in s]) for s in sp.sets] + w = sp.weights + if w isa UnitWeight + wts = ones(Int, w.n) + else + wts = collect(w) + end + inst = Dict( + "sets" => sets_0, + "weights" => wts, + ) + return make_instance(label, inst, sp) +end + +function export_matching(m, graph, label) + inst = Dict( + "num_vertices" => nv(graph), + "edges" => graph_to_edges(graph), + ) + w = m.weights + if w isa UnitWeight + inst["weights"] = ones(Int, w.n) + else + inst["weights"] = collect(w) + end + return make_instance(label, inst, m) +end + +function export_factoring(f, label) + inst = Dict( + "m" => f.m, + "n" => f.n, + "input" => f.input, + ) + return make_instance(label, inst, f) +end + +function export_dominatingset(ds, graph, label) + inst = serialize_graph_problem(ds, graph) + return make_instance(label, inst, ds) +end + +function export_maximalis(mis, graph, label) + inst = serialize_graph_problem(mis, graph) + return make_instance(label, inst, mis) +end + +function export_paintshop(ps, label) + inst = Dict( + "sequence" => ps.sequence, + "num_cars" => length(unique(ps.sequence)), + ) + return make_instance(label, inst, ps) +end + +function export_coloring(col, graph, k, label) + inst = Dict( + "num_vertices" => nv(graph), + "edges" => graph_to_edges(graph), + "k" => k, + ) + return make_instance(label, inst, col) +end + +function export_setcovering(sc, label) + # convert sets to 0-based + sets_0 = [sort([e - 1 for e in s]) for s in sc.sets] + w = sc.weights + if w isa UnitWeight + wts = ones(Int, w.n) + else + wts = collect(w) + end + inst = Dict( + "universe_size" => length(sc.elements), + "sets" => sets_0, + "weights" => wts, + ) + return make_instance(label, inst, sc) +end + +# ── reduction exports ──────────────────────────────────────────────── + +function export_reduction(source, target_type, source_label) + println(" reducing $(typeof(source)) => $target_type [$source_label]") + # direct solve source + best_source = findbest(source, BruteForce()) + + # reduce + result = reduceto(target_type, source) + target = target_problem(result) + + # solve target + best_target = findbest(target, BruteForce()) + + # extract solutions (convert booleans to 0/1 integers for JSON consistency) + bool_to_int(x::Bool) = Int(x) + bool_to_int(x) = x + extracted_single = unique([bool_to_int.(sol) for sol in extract_solution.(Ref(result), best_target)]) + extracted_multiple = [bool_to_int.(sol) for sol in extract_multiple_solutions(result, best_target)] + + return Dict( + "label" => source_label, + "best_source" => best_source, + "best_target" => best_target, + "extracted_single" => extracted_single, + "extracted_multiple" => extracted_multiple, + ) +end + +# ── main ───────────────────────────────────────────────────────────── + +function main() + println("Generating Julia parity test data...") + + # ── Build test instances (matching Julia test/rules/rules.jl) ── + graph = smallgraph(:petersen) + circuit = CircuitSAT(@circuit begin + x = a ∨ ¬b + y = ¬c ∨ b + z = x ∧ y ∧ a + end) + maxcut = MaxCut(graph) + spinglass = SpinGlass(graph, [1,2,1,2,1,2,1,2,1,2,1,2,1,2,1], zeros(Int, nv(graph))) + vertexcovering = VertexCovering(graph, [1,2,1,2,1,2,1,2,1,2]) + sat = Satisfiability(CNF([CNFClause([BoolVar(:a), BoolVar(:b)])])) + ksat = KSatisfiability{3}(CNF([CNFClause([BoolVar(:a), BoolVar(:b), BoolVar(:c)])])) + graph2 = HyperGraph(3, [[1, 2], [1], [2,3], [2]]) + qubo = QUBO([0 1 -2; 1 0 -2; -2 -2 6]) + is = IndependentSet(graph) + is2 = IndependentSet(graph2) + setpacking = SetPacking([[1, 2, 5], [1, 3], [2, 4], [3, 6], [2, 3, 6]]) + matching = Matching(graph) + + # ── Doc example instances ── + # IndependentSet docstring: 4-vertex graph + doc_is_graph = SimpleGraph(Graphs.SimpleEdge.([(1, 2), (1, 3), (3, 4), (2, 3)])) + doc_is = IndependentSet(doc_is_graph) + # Tutorial: diamond graph + doc_diamond = smallgraph(:diamond) + doc_is_diamond = IndependentSet(doc_diamond) + + # SpinGlass docstring: 4-vertex graph + doc_sg_graph = SimpleGraph(Graphs.SimpleEdge.([(1, 2), (1, 3), (3, 4), (2, 3)])) + doc_sg = SpinGlass(doc_sg_graph, [1, -1, 1, -1], [1, -1, -1, 1]) + + # MaxCut docstring: complete_graph(3) with weights + doc_mc_graph = complete_graph(3) + doc_mc = MaxCut(doc_mc_graph, [1, 2, 3]) + + # QUBO docstring: identity matrix + doc_qubo = QUBO([1. 0 0; 0 1 0; 0 0 1]) + + # VertexCovering docstring: 4-vertex 5-edge graph with weights + doc_vc_graph = SimpleGraph(Graphs.SimpleEdge.([(1,2), (1,3), (3,4), (2,3), (1,4)])) + doc_vc = VertexCovering(doc_vc_graph, [1, 3, 1, 4]) + + # Factoring docstring + Ising example: Factoring(2,2,6) + doc_fact = Factoring(2, 2, 6) + + # DominatingSet docstring: path_graph(5) + doc_ds_graph = path_graph(5) + doc_ds = DominatingSet(doc_ds_graph) + + # MaximalIS docstring: 4-vertex 5-edge graph + doc_mis_graph = SimpleGraph(Graphs.SimpleEdge.([(1, 2), (1, 3), (3, 4), (2, 3), (1, 4)])) + doc_mis = MaximalIS(doc_mis_graph) + + # PaintShop docstring + doc_ps = PaintShop(["a", "b", "a", "c", "c", "b"]) + + # Coloring docstring: petersen graph, 3 colors + doc_col = Coloring{3}(graph) + + # SetCovering docstring + doc_sc = SetCovering([[1, 2, 3], [2, 4], [1, 4]], [1, 2, 3]) + + # ── Individual rule test instances (from test/rules/*.jl) ── + rule_graph4 = SimpleGraph(Graphs.SimpleEdge.([(1, 2), (1, 3), (3, 4), (2, 3)])) + + # spinglass_maxcut.jl: MaxCut with specific weights + rule_mc = MaxCut(rule_graph4, [1, 3, 1, 4]) + # spinglass_maxcut.jl: SpinGlass with same weights + rule_sg = SpinGlass(rule_graph4, [1, 3, 1, 4], zeros(Int, 4)) + + # spinglass_qubo.jl: different QUBO matrix + rule_qubo = QUBO([2 1 -2; 1 2 -2; -2 -2 2]) + + # vertexcovering_setcovering.jl: VC with specific weights + rule_vc = VertexCovering(rule_graph4, [1, 3, 1, 4]) + + # independentset_setpacking.jl: g02 variant (different edge insertion order, same graph) + rule_is_g02 = IndependentSet(SimpleGraph(Graphs.SimpleEdge.([(1, 3), (1, 2), (2, 3), (3, 4)]))) + + # matching_setpacking.jl: 4-vertex matching (unweighted + weighted) + rule_match_uw = Matching(rule_graph4) + rule_match_w = Matching(rule_graph4, [1, 2, 3, 4]) + + # sat_3sat.jl: multi-clause SAT + rule_sat_3sat = Satisfiability(CNF([ + CNFClause([BoolVar(:x)]), + CNFClause([BoolVar(:y, true), BoolVar(:z)]), + CNFClause([BoolVar(:x), BoolVar(:y, true), BoolVar(:z), BoolVar(:w)]), + ])) + + # sat_independentset.jl / sat_dominatingset.jl / circuit_sat.jl: 3-variable SAT instances + x1, nx1 = BoolVar(:x1), BoolVar(:x1, true) + x2, nx2 = BoolVar(:x2), BoolVar(:x2, true) + x3, nx3 = BoolVar(:x3), BoolVar(:x3, true) + + rule_sat01 = Satisfiability(CNF([ + CNFClause([x1, nx2, x3]), + CNFClause([nx1, x2, nx3]), + CNFClause([x1, nx2, nx3]), + CNFClause([nx1, x2, x3]), + ])) + rule_sat02 = Satisfiability(CNF([ + CNFClause([nx1, x2, x3]), + CNFClause([x1, nx2, x3]), + CNFClause([x1, x2, nx3]), + ])) + rule_sat03 = Satisfiability(CNF([ + CNFClause([x1, x2, x3]), + CNFClause([nx1, nx2, nx3]), + ])) + # Unsatisfiable instances + rule_sat04 = Satisfiability(CNF([ + CNFClause([x1, x1, x1]), + CNFClause([nx1, nx1, nx1]), + ])) + rule_sat05 = Satisfiability(CNF([CNFClause([x1]), CNFClause([nx1])])) + rule_sat06 = Satisfiability(CNF([ + CNFClause([x1, x2]), + CNFClause([x1, nx2]), + CNFClause([nx1, x2]), + CNFClause([nx1, nx2]), + ])) + rule_sat07 = Satisfiability(CNF([ + CNFClause([x1, x2]), + CNFClause([x1, nx2]), + CNFClause([nx1, x2]), + ])) + + # sat_coloring.jl + rule_sat_col = Satisfiability(CNF([ + CNFClause([BoolVar(:X), BoolVar(:Y)]), + CNFClause([BoolVar(:X), BoolVar(:Y, true)]), + ])) + + # ── Export model fixtures ── + println("Exporting model fixtures...") + + # IndependentSet (SimpleGraph) + write_fixture("independentset.json", model_fixture("IndependentSet", [ + export_independentset(graph, "petersen"), + export_independentset(doc_is_graph, "doc_4vertex"), + export_independentset(doc_diamond, "doc_diamond"), + ])) + + # SpinGlass + write_fixture("spinglass.json", model_fixture("SpinGlass", [ + export_spinglass(spinglass, graph, "petersen"), + export_spinglass(doc_sg, doc_sg_graph, "doc_4vertex"), + export_spinglass(rule_sg, rule_graph4, "rule_4vertex"), + ])) + + # MaxCut + write_fixture("maxcut.json", model_fixture("MaxCut", [ + export_maxcut(maxcut, graph, "petersen"), + export_maxcut(doc_mc, doc_mc_graph, "doc_k3"), + export_maxcut(rule_mc, rule_graph4, "rule_4vertex"), + ])) + + # QUBO + write_fixture("qubo.json", model_fixture("QUBO", [ + export_qubo(qubo, "3x3_matrix"), + export_qubo(doc_qubo, "doc_identity"), + export_qubo(rule_qubo, "rule_3x3"), + ])) + + # Satisfiability + write_fixture("satisfiability.json", model_fixture("Satisfiability", [ + export_sat(sat, "simple_clause"), + export_sat(rule_sat_3sat, "rule_3sat_multi"), + export_sat(rule_sat01, "rule_sat01"), + export_sat(rule_sat02, "rule_sat02"), + export_sat(rule_sat03, "rule_sat03"), + export_sat(rule_sat04, "rule_sat04_unsat"), + export_sat(rule_sat05, "rule_sat05_unsat"), + export_sat(rule_sat06, "rule_sat06_unsat"), + export_sat(rule_sat07, "rule_sat07"), + export_sat(rule_sat_col, "rule_sat_coloring"), + ])) + + # KSatisfiability + write_fixture("ksatisfiability.json", model_fixture("KSatisfiability", [ + export_ksat(ksat, 3, "simple_3sat"), + ])) + + # VertexCovering + write_fixture("vertexcovering.json", model_fixture("VertexCovering", [ + export_vertexcovering(vertexcovering, graph, "petersen"), + export_vertexcovering(doc_vc, doc_vc_graph, "doc_4vertex"), + export_vertexcovering(rule_vc, rule_graph4, "rule_4vertex"), + ])) + + # SetPacking + write_fixture("setpacking.json", model_fixture("SetPacking", [ + export_setpacking(setpacking, "five_sets"), + ])) + + # Matching + write_fixture("matching.json", model_fixture("Matching", [ + export_matching(matching, graph, "petersen"), + export_matching(rule_match_uw, rule_graph4, "rule_4vertex"), + export_matching(rule_match_w, rule_graph4, "rule_4vertex_weighted"), + ])) + + # Factoring + fact1 = Factoring(1, 1, 1) + fact2 = Factoring(2, 1, 2) + fact3 = Factoring(2, 1, 3) + write_fixture("factoring.json", model_fixture("Factoring", [ + export_factoring(fact1, "1x1_factor_1"), + export_factoring(fact2, "2x1_factor_2"), + export_factoring(fact3, "2x1_factor_3"), + export_factoring(doc_fact, "doc_factor6"), + ])) + + # DominatingSet (doc example) + write_fixture("dominatingset.json", model_fixture("DominatingSet", [ + export_dominatingset(doc_ds, doc_ds_graph, "doc_path5"), + ])) + + # MaximalIS (doc example) + write_fixture("maximalis.json", model_fixture("MaximalIS", [ + export_maximalis(doc_mis, doc_mis_graph, "doc_4vertex"), + ])) + + # PaintShop (doc example) + write_fixture("paintshop.json", model_fixture("PaintShop", [ + export_paintshop(doc_ps, "doc_abaccb"), + ])) + + # Coloring (doc example) + write_fixture("coloring.json", model_fixture("Coloring", [ + export_coloring(doc_col, graph, 3, "doc_petersen_3color"), + ])) + + # SetCovering (doc example) + write_fixture("setcovering.json", model_fixture("SetCovering", [ + export_setcovering(doc_sc, "doc_3subsets"), + ])) + + # ── Export reduction fixtures ── + println("Exporting reduction fixtures...") + + # ── Reduction pairs: rules.jl round-trip + doc examples ── + reduction_pairs = Any[ + (doc_is, SetPacking, "doc_is", "doc_IndependentSet", "SetPacking"), + (circuit, SpinGlass{<:SimpleGraph}, "circuit", "CircuitSAT", "SpinGlass"), + (maxcut, SpinGlass{<:SimpleGraph}, "maxcut", "MaxCut", "SpinGlass"), + (spinglass, MaxCut, "spinglass", "SpinGlass", "MaxCut"), + (vertexcovering, SetCovering, "vertexcovering", "VertexCovering", "SetCovering"), + (sat, Coloring{3}, "sat_col", "Satisfiability", "Coloring3"), + (qubo, SpinGlass{<:SimpleGraph}, "qubo", "QUBO", "SpinGlass"), + (spinglass, QUBO, "spinglass_qubo", "SpinGlass", "QUBO"), + (sat, KSatisfiability{3}, "sat_ksat", "Satisfiability", "KSatisfiability3"), + (ksat, Satisfiability, "ksat_sat", "KSatisfiability", "Satisfiability"), + (sat, IndependentSet{<:SimpleGraph}, "sat_is", "Satisfiability", "IndependentSet"), + (sat, DominatingSet{<:SimpleGraph}, "sat_ds", "Satisfiability", "DominatingSet"), + (is, SetPacking, "is", "IndependentSet", "SetPacking"), + (is2, SetPacking, "is2_hyper", "IndependentSet_HyperGraph", "SetPacking"), + (setpacking, IndependentSet{<:SimpleGraph}, "sp", "SetPacking", "IndependentSet"), + (is, VertexCovering, "is_vc", "IndependentSet", "VertexCovering"), + (matching, SetPacking, "matching", "Matching", "SetPacking"), + (fact1, CircuitSAT, "factoring", "Factoring", "CircuitSAT"), + ] + + # ── Reduction pairs: individual rule test instances (test/rules/*.jl) ── + rule_reduction_pairs = Any[ + # spinglass_maxcut.jl + (rule_mc, SpinGlass{<:SimpleGraph}, "rule_mc", "rule_MaxCut", "SpinGlass"), + (rule_sg, MaxCut, "rule_sg", "rule_SpinGlass", "MaxCut"), + # spinglass_qubo.jl + (rule_qubo, SpinGlass{<:SimpleGraph}, "rule_qubo", "rule_QUBO", "SpinGlass"), + # vertexcovering_setcovering.jl + (rule_vc, SetCovering, "rule_vc", "rule_VertexCovering", "SetCovering"), + # independentset_setpacking.jl + (rule_is_g02, SetPacking, "rule_is_g02", "rule_IndependentSet", "SetPacking"), + # vertexcovering_independentset.jl + (doc_is, VertexCovering, "rule_is_vc", "rule2_IndependentSet", "VertexCovering"), + # matching_setpacking.jl (unweighted + weighted) + (rule_match_uw, SetPacking, "rule_match_uw", "rule_Matching", "SetPacking"), + (rule_match_w, SetPacking, "rule_match_w", "rule_MatchingW", "SetPacking"), + # sat_3sat.jl + (rule_sat_3sat, KSatisfiability{3}, "rule_sat_3sat", "rule_Satisfiability", "KSatisfiability3"), + # circuit_sat.jl (SAT → CircuitSAT) + (rule_sat01, CircuitSAT, "rule_sat01", "rule_SAT01", "CircuitSAT"), + (rule_sat02, CircuitSAT, "rule_sat02", "rule_SAT02", "CircuitSAT"), + (rule_sat03, CircuitSAT, "rule_sat03", "rule_SAT03", "CircuitSAT"), + # sat_coloring.jl + (rule_sat_col, Coloring{3}, "rule_sat_col", "rule_Satisfiability2", "Coloring3"), + # sat_independentset.jl + (rule_sat01, IndependentSet{<:SimpleGraph}, "rule_sat01", "rule_SAT01", "IndependentSet"), + (rule_sat02, IndependentSet{<:SimpleGraph}, "rule_sat02", "rule_SAT02", "IndependentSet"), + (rule_sat03, IndependentSet{<:SimpleGraph}, "rule_sat03", "rule_SAT03", "IndependentSet"), + (rule_sat04, IndependentSet{<:SimpleGraph}, "rule_sat04", "rule_SAT04_unsat", "IndependentSet"), + (rule_sat07, IndependentSet{<:SimpleGraph}, "rule_sat07", "rule_SAT07", "IndependentSet"), + # sat_dominatingset.jl + (rule_sat01, DominatingSet{<:SimpleGraph}, "rule_sat01", "rule_SAT01", "DominatingSet"), + (rule_sat02, DominatingSet{<:SimpleGraph}, "rule_sat02", "rule_SAT02", "DominatingSet"), + (rule_sat03, DominatingSet{<:SimpleGraph}, "rule_sat03", "rule_SAT03", "DominatingSet"), + (rule_sat04, DominatingSet{<:SimpleGraph}, "rule_sat04", "rule_SAT04_unsat", "DominatingSet"), + (rule_sat07, DominatingSet{<:SimpleGraph}, "rule_sat07", "rule_SAT07", "DominatingSet"), + ] + append!(reduction_pairs, rule_reduction_pairs) + + for (source, target_type, source_label, src_name, tgt_name) in reduction_pairs + filename = "$(lowercase(src_name))_to_$(lowercase(tgt_name)).json" + case = export_reduction(source, target_type, source_label) + data = Dict( + "source_type" => src_name, + "target_type" => tgt_name, + "cases" => [case], + ) + write_fixture(filename, data) + end + + println("Done! Generated fixtures in $OUTDIR") +end + +main() diff --git a/src/unit_tests/jl_helpers.rs b/src/unit_tests/jl_helpers.rs new file mode 100644 index 00000000..d45c68a8 --- /dev/null +++ b/src/unit_tests/jl_helpers.rs @@ -0,0 +1,136 @@ +// Shared JSON parsing helpers for Julia parity tests. +// Include this file with `include!("../jl_helpers.rs")` from rule tests +// or `include!("../../jl_helpers.rs")` from model tests. + +use std::collections::HashSet; + +#[allow(dead_code)] +fn jl_parse_edges(instance: &serde_json::Value) -> Vec<(usize, usize)> { + instance["edges"] + .as_array() + .expect("edges should be an array") + .iter() + .map(|e| { + let arr = e.as_array().expect("edge should be a [u, v] array"); + ( + arr[0].as_u64().expect("edge vertex should be u64") as usize, + arr[1].as_u64().expect("edge vertex should be u64") as usize, + ) + }) + .collect() +} + +#[allow(dead_code)] +fn jl_parse_weighted_edges(instance: &serde_json::Value) -> Vec<(usize, usize, i32)> { + let edges = jl_parse_edges(instance); + let weights: Vec = instance["weights"] + .as_array() + .expect("weights should be an array") + .iter() + .map(|w| w.as_i64().expect("weight should be i64") as i32) + .collect(); + edges + .into_iter() + .zip(weights) + .map(|((u, v), w)| (u, v, w)) + .collect() +} + +#[allow(dead_code)] +fn jl_parse_config(val: &serde_json::Value) -> Vec { + val.as_array() + .expect("config should be an array") + .iter() + .map(|v| v.as_u64().expect("config element should be u64") as usize) + .collect() +} + +#[allow(dead_code)] +fn jl_parse_configs_set(val: &serde_json::Value) -> HashSet> { + val.as_array() + .expect("configs set should be an array") + .iter() + .map(jl_parse_config) + .collect() +} + +#[allow(dead_code)] +fn jl_parse_i32_vec(val: &serde_json::Value) -> Vec { + val.as_array() + .expect("should be an array of integers") + .iter() + .map(|v| v.as_i64().expect("element should be i64") as i32) + .collect() +} + +#[allow(dead_code)] +fn jl_parse_sets(val: &serde_json::Value) -> Vec> { + val.as_array() + .expect("sets should be an array") + .iter() + .map(|s| { + s.as_array() + .expect("set should be an array") + .iter() + .map(|v| v.as_u64().expect("set element should be u64") as usize) + .collect() + }) + .collect() +} + +#[allow(dead_code)] +fn jl_parse_sat_clauses( + instance: &serde_json::Value, +) -> (usize, Vec) { + let num_vars = instance["num_variables"] + .as_u64() + .expect("num_variables should be a u64") as usize; + let clauses = instance["clauses"] + .as_array() + .expect("clauses should be an array") + .iter() + .map(|clause| { + let literals: Vec = clause["literals"] + .as_array() + .expect("clause.literals should be an array") + .iter() + .map(|lit| { + let var = lit["variable"] + .as_u64() + .expect("literal.variable should be a u64") as i32 + + 1; + let negated = lit["negated"] + .as_bool() + .expect("literal.negated should be a bool"); + if negated { -var } else { var } + }) + .collect(); + crate::models::satisfiability::CNFClause::new(literals) + }) + .collect(); + (num_vars, clauses) +} + +/// Flip a binary config: 0<->1 for SpinGlass spin convention mapping. +#[allow(dead_code)] +fn jl_flip_config(config: &[usize]) -> Vec { + config.iter().map(|&x| 1 - x).collect() +} + +#[allow(dead_code)] +fn jl_flip_configs_set(configs: &HashSet>) -> HashSet> { + configs.iter().map(|c| jl_flip_config(c)).collect() +} + +#[allow(dead_code)] +fn jl_find_instance_by_label<'a>( + data: &'a serde_json::Value, + label: &str, +) -> &'a serde_json::Value { + data["instances"] + .as_array() + .expect("instances should be an array") + .iter() + .find(|inst| inst["label"].as_str().expect("instance label should be a string") == label) + .unwrap_or_else(|| panic!("Instance '{label}' not found")) +} diff --git a/src/unit_tests/models/graph/kcoloring.rs b/src/unit_tests/models/graph/kcoloring.rs index feae1b2b..0b57fbf6 100644 --- a/src/unit_tests/models/graph/kcoloring.rs +++ b/src/unit_tests/models/graph/kcoloring.rs @@ -1,5 +1,6 @@ use super::*; use crate::solvers::BruteForce; +include!("../../jl_helpers.rs"); #[test] fn test_kcoloring_creation() { @@ -12,61 +13,6 @@ fn test_kcoloring_creation() { assert_eq!(problem.dims(), vec![3, 3, 3, 3]); } -#[test] -fn test_evaluate_valid() { - use crate::traits::Problem; - - let problem = KColoring::<3, SimpleGraph>::new(3, vec![(0, 1), (1, 2)]); - - // Valid: different colors on adjacent vertices - assert!(problem.evaluate(&[0, 1, 0])); - assert!(problem.evaluate(&[0, 1, 2])); -} - -#[test] -fn test_evaluate_invalid() { - use crate::traits::Problem; - - let problem = KColoring::<3, SimpleGraph>::new(3, vec![(0, 1), (1, 2)]); - - // Invalid: adjacent vertices have same color - assert!(!problem.evaluate(&[0, 0, 1])); - assert!(!problem.evaluate(&[0, 0, 0])); -} - -#[test] -fn test_brute_force_path() { - use crate::traits::Problem; - - // Path graph can be 2-colored - let problem = KColoring::<2, SimpleGraph>::new(4, vec![(0, 1), (1, 2), (2, 3)]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_satisfying(&problem); - // All solutions should be valid - for sol in &solutions { - assert!(problem.evaluate(sol)); - } -} - -#[test] -fn test_brute_force_triangle() { - use crate::traits::Problem; - - // Triangle needs 3 colors - let problem = KColoring::<3, SimpleGraph>::new(3, vec![(0, 1), (1, 2), (0, 2)]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_satisfying(&problem); - for sol in &solutions { - assert!(problem.evaluate(sol)); - // All three vertices have different colors - assert_ne!(sol[0], sol[1]); - assert_ne!(sol[1], sol[2]); - assert_ne!(sol[0], sol[2]); - } -} - #[test] fn test_triangle_2_colors() { // Triangle cannot be 2-colored @@ -105,21 +51,6 @@ fn test_empty_graph() { } } -#[test] -fn test_complete_graph_k4() { - use crate::traits::Problem; - - // K4 needs 4 colors - let problem = - KColoring::<4, SimpleGraph>::new(4, vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_satisfying(&problem); - for sol in &solutions { - assert!(problem.evaluate(sol)); - } -} - #[test] fn test_from_graph() { let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2)]); @@ -129,14 +60,23 @@ fn test_from_graph() { } #[test] -fn test_kcoloring_problem() { - use crate::traits::Problem; - - // Triangle graph with 3 colors - let p = KColoring::<3, SimpleGraph>::new(3, vec![(0, 1), (1, 2), (0, 2)]); - assert_eq!(p.dims(), vec![3, 3, 3]); - // Valid: each vertex different color - assert!(p.evaluate(&[0, 1, 2])); - // Invalid: vertices 0 and 1 same color - assert!(!p.evaluate(&[0, 0, 1])); +fn test_jl_parity_evaluation() { + let data: serde_json::Value = + serde_json::from_str(include_str!("../../../../tests/data/jl/coloring.json")).unwrap(); + for instance in data["instances"].as_array().unwrap() { + let nv = instance["instance"]["num_vertices"].as_u64().unwrap() as usize; + let edges = jl_parse_edges(&instance["instance"]); + let num_edges = edges.len(); + let problem = KColoring::<3, SimpleGraph>::new(nv, edges); + for eval in instance["evaluations"].as_array().unwrap() { + let config = jl_parse_config(&eval["config"]); + let result: bool = problem.evaluate(&config); + let jl_size = eval["size"].as_i64().unwrap() as usize; + assert_eq!(result, jl_size == num_edges, "KColoring mismatch for config {:?}", config); + } + let all_sat = BruteForce::new().find_all_satisfying(&problem); + let jl_best = jl_parse_configs_set(&instance["best_solutions"]); + let rust_sat: HashSet> = all_sat.into_iter().collect(); + assert_eq!(rust_sat, jl_best, "KColoring satisfying solutions mismatch"); + } } diff --git a/src/unit_tests/models/graph/max_cut.rs b/src/unit_tests/models/graph/max_cut.rs index 4de09c43..59ebe065 100644 --- a/src/unit_tests/models/graph/max_cut.rs +++ b/src/unit_tests/models/graph/max_cut.rs @@ -1,6 +1,6 @@ use super::*; use crate::solvers::BruteForce; -use crate::types::SolutionSize; +include!("../../jl_helpers.rs"); #[test] fn test_maxcut_creation() { @@ -18,68 +18,6 @@ fn test_maxcut_unweighted() { assert_eq!(problem.num_edges(), 2); } -#[test] -fn test_evaluate() { - use crate::traits::Problem; - - let problem = MaxCut::::new(3, vec![(0, 1, 1), (1, 2, 2), (0, 2, 3)]); - - // All same partition: no cut - assert_eq!(problem.evaluate(&[0, 0, 0]), SolutionSize::Valid(0)); - - // 0 vs {1,2}: cuts edges 0-1 (1) and 0-2 (3) = 4 - assert_eq!(problem.evaluate(&[0, 1, 1]), SolutionSize::Valid(4)); - - // {0,2} vs {1}: cuts edges 0-1 (1) and 1-2 (2) = 3 - assert_eq!(problem.evaluate(&[0, 1, 0]), SolutionSize::Valid(3)); -} - -#[test] -fn test_brute_force_triangle() { - use crate::traits::Problem; - - // Triangle with unit weights: max cut is 2 - let problem = MaxCut::::unweighted(3, vec![(0, 1), (1, 2), (0, 2)]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - for sol in &solutions { - let size = problem.evaluate(sol); - assert_eq!(size, SolutionSize::Valid(2)); - } -} - -#[test] -fn test_brute_force_path() { - use crate::traits::Problem; - - // Path 0-1-2: max cut is 2 (partition {0,2} vs {1}) - let problem = MaxCut::::unweighted(3, vec![(0, 1), (1, 2)]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - for sol in &solutions { - let size = problem.evaluate(sol); - assert_eq!(size, SolutionSize::Valid(2)); - } -} - -#[test] -fn test_brute_force_weighted() { - use crate::traits::Problem; - - // Edge with weight 10 should always be cut - let problem = MaxCut::::new(3, vec![(0, 1, 10), (1, 2, 1)]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - // Max is 11 (cut both edges) with partition like [0,1,0] or [1,0,1] - for sol in &solutions { - let size = problem.evaluate(sol); - assert_eq!(size, SolutionSize::Valid(11)); - } -} - #[test] fn test_cut_size_function() { use crate::topology::SimpleGraph; @@ -120,81 +58,6 @@ fn test_direction() { assert_eq!(problem.direction(), Direction::Maximize); } -#[test] -fn test_empty_graph() { - use crate::traits::Problem; - - let problem = MaxCut::::unweighted(3, vec![]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - // Any partition gives cut size 0 - assert!(!solutions.is_empty()); - for sol in &solutions { - assert_eq!(problem.evaluate(sol), SolutionSize::Valid(0)); - } -} - -#[test] -fn test_single_edge() { - use crate::traits::Problem; - - let problem = MaxCut::::new(2, vec![(0, 1, 5)]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - // Putting vertices in different sets maximizes cut - assert_eq!(solutions.len(), 2); // [0,1] and [1,0] - for sol in &solutions { - assert_eq!(problem.evaluate(sol), SolutionSize::Valid(5)); - } -} - -#[test] -fn test_complete_graph_k4() { - use crate::traits::Problem; - - // K4: every partition cuts exactly 4 edges (balanced) or less - let problem = MaxCut::::unweighted( - 4, - vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)], - ); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - // Max cut in K4 is 4 (2-2 partition) - for sol in &solutions { - assert_eq!(problem.evaluate(sol), SolutionSize::Valid(4)); - } -} - -#[test] -fn test_bipartite_graph() { - use crate::traits::Problem; - - // Complete bipartite K_{2,2}: max cut is all 4 edges - let problem = MaxCut::::unweighted(4, vec![(0, 2), (0, 3), (1, 2), (1, 3)]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - // Bipartite graph can achieve max cut = all edges - for sol in &solutions { - assert_eq!(problem.evaluate(sol), SolutionSize::Valid(4)); - } -} - -#[test] -fn test_symmetry() { - use crate::traits::Problem; - - // Complementary partitions should give same cut - let problem = MaxCut::::unweighted(3, vec![(0, 1), (1, 2), (0, 2)]); - - let sol1 = problem.evaluate(&[0, 1, 1]); - let sol2 = problem.evaluate(&[1, 0, 0]); // complement - assert_eq!(sol1, sol2); -} - #[test] fn test_from_graph() { let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2)]); @@ -236,16 +99,23 @@ fn test_edge_weight_by_index() { } #[test] -fn test_maxcut_problem() { - use crate::traits::{OptimizationProblem, Problem}; - use crate::types::Direction; - - // Triangle with unit edge weights - let p = MaxCut::::unweighted(3, vec![(0, 1), (1, 2), (0, 2)]); - assert_eq!(p.dims(), vec![2, 2, 2]); - // Partition {0} vs {1,2}: cuts edges (0,1) and (0,2), weight = 2 - assert_eq!(p.evaluate(&[1, 0, 0]), SolutionSize::Valid(2)); - // All same partition: no cut, weight = 0 - assert_eq!(p.evaluate(&[0, 0, 0]), SolutionSize::Valid(0)); - assert_eq!(p.direction(), Direction::Maximize); +fn test_jl_parity_evaluation() { + let data: serde_json::Value = + serde_json::from_str(include_str!("../../../../tests/data/jl/maxcut.json")).unwrap(); + for instance in data["instances"].as_array().unwrap() { + let nv = instance["instance"]["num_vertices"].as_u64().unwrap() as usize; + let weighted_edges = jl_parse_weighted_edges(&instance["instance"]); + let problem = MaxCut::::new(nv, weighted_edges); + 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; + assert!(result.is_valid(), "MaxCut should always be valid"); + assert_eq!(result.unwrap(), jl_size, "MaxCut 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, "MaxCut best solutions mismatch"); + } } diff --git a/src/unit_tests/models/graph/maximal_is.rs b/src/unit_tests/models/graph/maximal_is.rs index 417b21cd..4c5bd1d5 100644 --- a/src/unit_tests/models/graph/maximal_is.rs +++ b/src/unit_tests/models/graph/maximal_is.rs @@ -1,6 +1,6 @@ use super::*; use crate::solvers::BruteForce; -use crate::types::SolutionSize; +include!("../../jl_helpers.rs"); #[test] fn test_maximal_is_creation() { @@ -58,50 +58,6 @@ fn test_is_maximal() { assert!(!problem.is_maximal(&[0, 0, 0])); } -#[test] -fn test_evaluate() { - use crate::traits::Problem; - - let problem = MaximalIS::::new(3, vec![(0, 1), (1, 2)]); - - // Maximal: {0, 2} - assert_eq!(problem.evaluate(&[1, 0, 1]), SolutionSize::Valid(2)); - - // Maximal: {1} - assert_eq!(problem.evaluate(&[0, 1, 0]), SolutionSize::Valid(1)); - - // Not maximal: {0} - returns Invalid - assert_eq!(problem.evaluate(&[1, 0, 0]), SolutionSize::Invalid); -} - -#[test] -fn test_brute_force_path() { - let problem = MaximalIS::::new(3, vec![(0, 1), (1, 2)]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - // Largest maximal IS is {0, 2} with size 2 - assert_eq!(solutions.len(), 1); - assert_eq!(solutions[0], vec![1, 0, 1]); -} - -#[test] -fn test_brute_force_triangle() { - use crate::traits::Problem; - - let problem = MaximalIS::::new(3, vec![(0, 1), (1, 2), (0, 2)]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - // All maximal IS have size 1 (any single vertex) - assert_eq!(solutions.len(), 3); - for sol in &solutions { - assert_eq!(sol.iter().sum::(), 1); - // Maximal IS should evaluate to Valid(1) - assert_eq!(problem.evaluate(sol), SolutionSize::Valid(1)); - } -} - #[test] fn test_is_maximal_independent_set_function() { let edges = vec![(0, 1), (1, 2)]; @@ -125,17 +81,6 @@ fn test_direction() { assert_eq!(problem.direction(), Direction::Maximize); } -#[test] -fn test_empty_graph() { - let problem = MaximalIS::::new(3, vec![]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - // Only maximal IS is all vertices - assert_eq!(solutions.len(), 1); - assert_eq!(solutions[0], vec![1, 1, 1]); -} - #[test] fn test_weights() { let problem = MaximalIS::::new(3, vec![(0, 1)]); @@ -198,29 +143,26 @@ fn test_weights_ref() { } #[test] -fn test_weighted_solution() { - let problem = - MaximalIS::::with_weights(3, vec![(0, 1), (1, 2)], vec![10, 100, 10]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - // Should prefer {1} with weight 100 over {0, 2} with weight 20 - // With LargerSizeIsBetter, {1} with 100 > {0, 2} with 20 - assert_eq!(solutions.len(), 1); - assert_eq!(solutions[0], vec![0, 1, 0]); -} - -#[test] -fn test_maximal_is_problem() { - use crate::traits::{OptimizationProblem, Problem}; - use crate::types::Direction; - - // Path graph 0-1-2 - let p = MaximalIS::::new(3, vec![(0, 1), (1, 2)]); - assert_eq!(p.dims(), vec![2, 2, 2]); - // Valid maximal IS: {0, 2} - independent and maximal - assert_eq!(p.evaluate(&[1, 0, 1]), SolutionSize::Valid(2)); - // Not maximal: {0} alone - vertex 2 could be added - assert_eq!(p.evaluate(&[1, 0, 0]), SolutionSize::Invalid); - assert_eq!(p.direction(), Direction::Maximize); +fn test_jl_parity_evaluation() { + let data: serde_json::Value = + serde_json::from_str(include_str!("../../../../tests/data/jl/maximalis.json")).unwrap(); + for instance in data["instances"].as_array().unwrap() { + let nv = instance["instance"]["num_vertices"].as_u64().unwrap() as usize; + let edges = jl_parse_edges(&instance["instance"]); + let problem = MaximalIS::::new(nv, edges); + 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(); + assert_eq!(result.is_valid(), jl_valid, "MaximalIS validity mismatch for config {:?}", config); + if jl_valid { + let jl_size = eval["size"].as_i64().unwrap() as i32; + assert_eq!(result.unwrap(), jl_size, "MaximalIS 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, "MaximalIS best solutions mismatch"); + } } diff --git a/src/unit_tests/models/graph/maximum_independent_set.rs b/src/unit_tests/models/graph/maximum_independent_set.rs index a841d7e5..dd98bb0a 100644 --- a/src/unit_tests/models/graph/maximum_independent_set.rs +++ b/src/unit_tests/models/graph/maximum_independent_set.rs @@ -1,7 +1,8 @@ use super::*; use crate::solvers::BruteForce; use crate::traits::{OptimizationProblem, Problem}; -use crate::types::{Direction, SolutionSize}; +use crate::types::Direction; +include!("../../jl_helpers.rs"); #[test] fn test_independent_set_creation() { @@ -34,107 +35,6 @@ fn test_has_edge() { assert!(!problem.has_edge(0, 2)); } -#[test] -fn test_evaluate_valid() { - let problem = MaximumIndependentSet::::new(4, vec![(0, 1), (2, 3)]); - - // Valid: select 0 and 2 (not adjacent) - assert_eq!(problem.evaluate(&[1, 0, 1, 0]), SolutionSize::Valid(2)); - - // Valid: select 1 and 3 (not adjacent) - assert_eq!(problem.evaluate(&[0, 1, 0, 1]), SolutionSize::Valid(2)); -} - -#[test] -fn test_evaluate_invalid() { - let problem = MaximumIndependentSet::::new(4, vec![(0, 1), (2, 3)]); - - // Invalid: 0 and 1 are adjacent -> returns Invalid - assert_eq!(problem.evaluate(&[1, 1, 0, 0]), SolutionSize::Invalid); - - // Invalid: 2 and 3 are adjacent -> returns Invalid - assert_eq!(problem.evaluate(&[0, 0, 1, 1]), SolutionSize::Invalid); -} - -#[test] -fn test_evaluate_empty() { - let problem = MaximumIndependentSet::::new(3, vec![(0, 1), (1, 2)]); - assert_eq!(problem.evaluate(&[0, 0, 0]), SolutionSize::Valid(0)); -} - -#[test] -fn test_weighted_evaluate() { - let problem = - MaximumIndependentSet::::with_weights(3, vec![(0, 1)], vec![10, 20, 30]); - - // Select vertex 2 (weight 30) - assert_eq!(problem.evaluate(&[0, 0, 1]), SolutionSize::Valid(30)); - - // Select vertices 0 and 2 (weights 10 + 30 = 40) - assert_eq!(problem.evaluate(&[1, 0, 1]), SolutionSize::Valid(40)); -} - -#[test] -fn test_brute_force_triangle() { - // Triangle graph: maximum IS has size 1 - let problem = MaximumIndependentSet::::new(3, vec![(0, 1), (1, 2), (0, 2)]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - // All solutions should have exactly 1 vertex selected - assert_eq!(solutions.len(), 3); // Three equivalent solutions - for sol in &solutions { - assert_eq!(sol.iter().sum::(), 1); - } -} - -#[test] -fn test_brute_force_path() { - // Path graph 0-1-2-3: maximum IS = {0,2} or {1,3} or {0,3} - let problem = MaximumIndependentSet::::new(4, vec![(0, 1), (1, 2), (2, 3)]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - // Maximum size is 2 - for sol in &solutions { - let size: usize = sol.iter().sum(); - assert_eq!(size, 2); - // Verify it's valid (evaluate returns Valid) - let eval = problem.evaluate(sol); - assert!(eval.is_valid()); - } -} - -#[test] -fn test_brute_force_weighted() { - // Graph with weights: vertex 1 has high weight but is connected to both 0 and 2 - let problem = MaximumIndependentSet::::with_weights( - 3, - vec![(0, 1), (1, 2)], - vec![1, 100, 1], - ); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - assert_eq!(solutions.len(), 1); - // Should select vertex 1 (weight 100) over vertices 0+2 (weight 2) - assert_eq!(solutions[0], vec![0, 1, 0]); -} - -#[test] -fn test_brute_force_weighted_f64() { - let problem = MaximumIndependentSet::::with_weights( - 3, - vec![(0, 1), (1, 2)], - vec![0.5, 2.0, 0.75], - ); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - assert_eq!(solutions, vec![vec![0, 1, 0]]); - assert_eq!(problem.evaluate(&solutions[0]), SolutionSize::Valid(2.0)); -} - #[test] fn test_is_independent_set_function() { assert!(is_independent_set(3, &[(0, 1)], &[true, false, true])); @@ -174,17 +74,6 @@ fn test_set_weights() { assert_eq!(problem.weights(), vec![5, 10, 15]); } -#[test] -fn test_empty_graph() { - let problem = MaximumIndependentSet::::new(3, vec![]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - assert_eq!(solutions.len(), 1); - // All vertices can be selected - assert_eq!(solutions[0], vec![1, 1, 1]); -} - #[test] fn test_from_graph() { let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2)]); @@ -217,31 +106,6 @@ fn test_weights_ref() { assert_eq!(problem.weights_ref(), &vec![5, 10, 15]); } -#[test] -fn test_mis_problem_trait() { - // Triangle graph with explicit weights - let p = MaximumIndependentSet::::with_weights( - 3, - vec![(0, 1), (1, 2), (0, 2)], - vec![1, 1, 1], - ); - assert_eq!(p.dims(), vec![2, 2, 2]); - // Valid IS: select vertex 0 only - assert_eq!(p.evaluate(&[1, 0, 0]), SolutionSize::Valid(1)); - // Invalid IS: select adjacent 0,1 -> should return Invalid - assert_eq!(p.evaluate(&[1, 1, 0]), SolutionSize::Invalid); - assert_eq!(p.direction(), Direction::Maximize); -} - -#[test] -fn test_mis_unweighted() { - // Unweighted MIS uses i32 weight type with unit weights - let p = MaximumIndependentSet::::new(3, vec![(0, 1), (1, 2), (0, 2)]); - assert_eq!(p.dims(), vec![2, 2, 2]); - assert_eq!(p.evaluate(&[1, 0, 0]), SolutionSize::Valid(1)); - assert_eq!(p.evaluate(&[0, 0, 0]), SolutionSize::Valid(0)); -} - #[test] fn test_problem_name() { assert_eq!( @@ -249,3 +113,33 @@ fn test_problem_name() { "MaximumIndependentSet" ); } + +#[test] +fn test_jl_parity_evaluation() { + let data: serde_json::Value = + serde_json::from_str(include_str!("../../../../tests/data/jl/independentset.json")).unwrap(); + for instance in data["instances"].as_array().unwrap() { + let nv = instance["instance"]["num_vertices"].as_u64().unwrap() as usize; + let edges = jl_parse_edges(&instance["instance"]); + let weights = jl_parse_i32_vec(&instance["instance"]["weights"]); + let problem = if weights.iter().all(|&w| w == 1) { + MaximumIndependentSet::::new(nv, edges) + } else { + MaximumIndependentSet::with_weights(nv, edges, weights) + }; + 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(); + assert_eq!(result.is_valid(), jl_valid, "IS validity mismatch for config {:?}", config); + if jl_valid { + let jl_size = eval["size"].as_i64().unwrap() as i32; + assert_eq!(result.unwrap(), jl_size, "IS 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, "IS best solutions mismatch"); + } +} diff --git a/src/unit_tests/models/graph/maximum_matching.rs b/src/unit_tests/models/graph/maximum_matching.rs index 1b2ee56e..6e6c754f 100644 --- a/src/unit_tests/models/graph/maximum_matching.rs +++ b/src/unit_tests/models/graph/maximum_matching.rs @@ -2,6 +2,7 @@ use super::*; use crate::solvers::BruteForce; use crate::traits::{OptimizationProblem, Problem}; use crate::types::{Direction, SolutionSize}; +include!("../../jl_helpers.rs"); #[test] fn test_matching_creation() { @@ -41,64 +42,6 @@ fn test_is_valid_matching() { assert!(!problem.is_valid_matching(&[1, 1, 0])); } -#[test] -fn test_evaluate() { - let problem = - MaximumMatching::::new(4, vec![(0, 1, 5), (1, 2, 10), (2, 3, 3)]); - - // Valid matching: edges 0 and 2 (disjoint) - assert_eq!( - Problem::evaluate(&problem, &[1, 0, 1]), - SolutionSize::Valid(8) - ); // 5 + 3 - - // Valid matching: edge 1 only - assert_eq!( - Problem::evaluate(&problem, &[0, 1, 0]), - SolutionSize::Valid(10) - ); -} - -#[test] -fn test_brute_force_path() { - // Path 0-1-2-3 with unit weights - let problem = MaximumMatching::::unweighted(4, vec![(0, 1), (1, 2), (2, 3)]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - // Maximum matching has 2 edges: {0-1, 2-3} - assert!(solutions.contains(&vec![1, 0, 1])); - for sol in &solutions { - assert_eq!(Problem::evaluate(&problem, sol), SolutionSize::Valid(2)); - } -} - -#[test] -fn test_brute_force_triangle() { - let problem = MaximumMatching::::unweighted(3, vec![(0, 1), (1, 2), (0, 2)]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - // Maximum matching has 1 edge (any of the 3) - for sol in &solutions { - assert_eq!(sol.iter().sum::(), 1); - // Verify it's a valid matching - assert!(Problem::evaluate(&problem, sol).is_valid()); - } -} - -#[test] -fn test_brute_force_weighted() { - // Prefer heavy edge even if it excludes more edges - let problem = - MaximumMatching::::new(4, vec![(0, 1, 100), (0, 2, 1), (1, 3, 1)]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - // Edge 0-1 (weight 100) alone beats edges 0-2 + 1-3 (weight 2) - assert!(solutions.contains(&vec![1, 0, 0])); -} - #[test] fn test_is_matching_function() { let edges = vec![(0, 1), (1, 2), (2, 3)]; @@ -129,33 +72,6 @@ fn test_edges() { assert_eq!(edges.len(), 2); } -#[test] -fn test_perfect_matching() { - // K4: can have perfect matching (2 edges covering all 4 vertices) - let problem = MaximumMatching::::unweighted( - 4, - vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)], - ); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - // Perfect matching has 2 edges - for sol in &solutions { - assert_eq!(Problem::evaluate(&problem, sol), SolutionSize::Valid(2)); - // Check it's a valid matching using 4 vertices - let mut used = [false; 4]; - for (idx, &sel) in sol.iter().enumerate() { - if sel == 1 { - if let Some((u, v)) = problem.edge_endpoints(idx) { - used[u] = true; - used[v] = true; - } - } - } - assert!(used.iter().all(|&u| u)); // All vertices matched - } -} - #[test] fn test_empty_sets() { let problem = MaximumMatching::::unweighted(2, vec![]); @@ -202,13 +118,26 @@ fn test_graph_accessor() { } #[test] -fn test_matching_problem_v2() { - // Path graph 0-1-2 with edges (0,1) and (1,2) - let p = MaximumMatching::::unweighted(3, vec![(0, 1), (1, 2)]); - assert_eq!(p.dims(), vec![2, 2]); - // Valid matching: select edge 0 only - assert_eq!(Problem::evaluate(&p, &[1, 0]), SolutionSize::Valid(1)); - // Invalid matching: select both edges (vertex 1 shared) - assert_eq!(Problem::evaluate(&p, &[1, 1]), SolutionSize::Invalid); - assert_eq!(p.direction(), Direction::Maximize); +fn test_jl_parity_evaluation() { + let data: serde_json::Value = + serde_json::from_str(include_str!("../../../../tests/data/jl/matching.json")).unwrap(); + for instance in data["instances"].as_array().unwrap() { + let nv = instance["instance"]["num_vertices"].as_u64().unwrap() as usize; + let weighted_edges = jl_parse_weighted_edges(&instance["instance"]); + let problem = MaximumMatching::::new(nv, weighted_edges); + 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(); + assert_eq!(result.is_valid(), jl_valid, "Matching validity mismatch for config {:?}", config); + if jl_valid { + let jl_size = eval["size"].as_i64().unwrap() as i32; + assert_eq!(result.unwrap(), jl_size, "Matching 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, "Matching best solutions mismatch"); + } } diff --git a/src/unit_tests/models/graph/minimum_dominating_set.rs b/src/unit_tests/models/graph/minimum_dominating_set.rs index ad0a92b9..d0b7605f 100644 --- a/src/unit_tests/models/graph/minimum_dominating_set.rs +++ b/src/unit_tests/models/graph/minimum_dominating_set.rs @@ -1,7 +1,8 @@ use super::*; use crate::solvers::BruteForce; use crate::traits::{OptimizationProblem, Problem}; -use crate::types::{Direction, SolutionSize}; +use crate::types::Direction; +include!("../../jl_helpers.rs"); #[test] fn test_dominating_set_creation() { @@ -36,86 +37,6 @@ fn test_closed_neighborhood() { assert!(!cn.contains(&3)); } -#[test] -fn test_evaluate_valid() { - // Star graph: center dominates all - let problem = MinimumDominatingSet::::new(4, vec![(0, 1), (0, 2), (0, 3)]); - - // Select center - assert_eq!( - Problem::evaluate(&problem, &[1, 0, 0, 0]), - SolutionSize::Valid(1) - ); - - // Select all leaves - assert_eq!( - Problem::evaluate(&problem, &[0, 1, 1, 1]), - SolutionSize::Valid(3) - ); -} - -#[test] -fn test_evaluate_invalid() { - let problem = MinimumDominatingSet::::new(4, vec![(0, 1), (2, 3)]); - - // Select none - returns Invalid for minimization - assert_eq!( - Problem::evaluate(&problem, &[0, 0, 0, 0]), - SolutionSize::Invalid - ); - - // Select only vertex 0 (doesn't dominate 2, 3) - assert_eq!( - Problem::evaluate(&problem, &[1, 0, 0, 0]), - SolutionSize::Invalid - ); -} - -#[test] -fn test_brute_force_star() { - // Star graph: minimum dominating set is the center - let problem = MinimumDominatingSet::::new(4, vec![(0, 1), (0, 2), (0, 3)]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - assert!(solutions.contains(&vec![1, 0, 0, 0])); - for sol in &solutions { - assert_eq!(Problem::evaluate(&problem, sol), SolutionSize::Valid(1)); - } -} - -#[test] -fn test_brute_force_path() { - // Path 0-1-2-3-4: need to dominate all 5 vertices - let problem = - MinimumDominatingSet::::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - // Minimum is 2 (e.g., vertices 1 and 3) - for sol in &solutions { - assert_eq!(Problem::evaluate(&problem, sol), SolutionSize::Valid(2)); - // Verify it's a valid dominating set - assert!(Problem::evaluate(&problem, sol).is_valid()); - } -} - -#[test] -fn test_brute_force_weighted() { - // Star with heavy center - let problem = MinimumDominatingSet::::with_weights( - 4, - vec![(0, 1), (0, 2), (0, 3)], - vec![100, 1, 1, 1], - ); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - // Prefer selecting all leaves (3) over center (100) - assert_eq!(solutions.len(), 1); - assert_eq!(solutions[0], vec![0, 1, 1, 1]); -} - #[test] fn test_is_dominating_set_function() { let edges = vec![(0, 1), (0, 2), (0, 3)]; @@ -201,13 +122,26 @@ fn test_has_edge() { } #[test] -fn test_mds_problem_v2() { - // Path graph 0-1-2 - let p = MinimumDominatingSet::::new(3, vec![(0, 1), (1, 2)]); - assert_eq!(p.dims(), vec![2, 2, 2]); - // Valid DS: select vertex 1 (dominates all) - assert_eq!(Problem::evaluate(&p, &[0, 1, 0]), SolutionSize::Valid(1)); - // Invalid DS: select no vertices - assert_eq!(Problem::evaluate(&p, &[0, 0, 0]), SolutionSize::Invalid); - assert_eq!(p.direction(), Direction::Minimize); +fn test_jl_parity_evaluation() { + let data: serde_json::Value = + serde_json::from_str(include_str!("../../../../tests/data/jl/dominatingset.json")).unwrap(); + for instance in data["instances"].as_array().unwrap() { + let nv = instance["instance"]["num_vertices"].as_u64().unwrap() as usize; + let edges = jl_parse_edges(&instance["instance"]); + let problem = MinimumDominatingSet::::new(nv, edges); + 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(); + assert_eq!(result.is_valid(), jl_valid, "DS validity mismatch for config {:?}", config); + if jl_valid { + let jl_size = eval["size"].as_i64().unwrap() as i32; + assert_eq!(result.unwrap(), jl_size, "DS 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, "DS best solutions mismatch"); + } } diff --git a/src/unit_tests/models/graph/minimum_vertex_cover.rs b/src/unit_tests/models/graph/minimum_vertex_cover.rs index 899bcca6..03881f5c 100644 --- a/src/unit_tests/models/graph/minimum_vertex_cover.rs +++ b/src/unit_tests/models/graph/minimum_vertex_cover.rs @@ -1,7 +1,8 @@ use super::*; use crate::solvers::BruteForce; use crate::traits::{OptimizationProblem, Problem}; -use crate::types::{Direction, SolutionSize}; +use crate::types::Direction; +include!("../../jl_helpers.rs"); #[test] fn test_vertex_cover_creation() { @@ -18,83 +19,6 @@ fn test_vertex_cover_with_weights() { assert_eq!(problem.weights(), vec![1, 2, 3]); } -#[test] -fn test_evaluate_valid() { - let problem = MinimumVertexCover::::new(3, vec![(0, 1), (1, 2)]); - - // Valid: select vertex 1 (covers both edges) - assert_eq!( - Problem::evaluate(&problem, &[0, 1, 0]), - SolutionSize::Valid(1) - ); - - // Valid: select all vertices - assert_eq!( - Problem::evaluate(&problem, &[1, 1, 1]), - SolutionSize::Valid(3) - ); -} - -#[test] -fn test_evaluate_invalid() { - let problem = MinimumVertexCover::::new(3, vec![(0, 1), (1, 2)]); - - // Invalid: no vertex selected - returns Invalid for minimization - assert_eq!( - Problem::evaluate(&problem, &[0, 0, 0]), - SolutionSize::Invalid - ); - - // Invalid: only vertex 0 selected (edge 1-2 not covered) - assert_eq!( - Problem::evaluate(&problem, &[1, 0, 0]), - SolutionSize::Invalid - ); -} - -#[test] -fn test_brute_force_path() { - // Path graph 0-1-2: minimum vertex cover is {1} - let problem = MinimumVertexCover::::new(3, vec![(0, 1), (1, 2)]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - assert_eq!(solutions.len(), 1); - assert_eq!(solutions[0], vec![0, 1, 0]); -} - -#[test] -fn test_brute_force_triangle() { - // Triangle: minimum vertex cover has size 2 - let problem = MinimumVertexCover::::new(3, vec![(0, 1), (1, 2), (0, 2)]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - // There are 3 minimum covers of size 2 - assert_eq!(solutions.len(), 3); - for sol in &solutions { - assert_eq!(sol.iter().sum::(), 2); - // Verify it's a valid cover by checking evaluate returns Valid - assert!(Problem::evaluate(&problem, sol).is_valid()); - } -} - -#[test] -fn test_brute_force_weighted() { - // Weighted: prefer selecting low-weight vertices - let problem = MinimumVertexCover::::with_weights( - 3, - vec![(0, 1), (1, 2)], - vec![100, 1, 100], - ); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - assert_eq!(solutions.len(), 1); - // Should select vertex 1 (weight 1) instead of 0 and 2 (total 200) - assert_eq!(solutions[0], vec![0, 1, 0]); -} - #[test] fn test_is_vertex_cover_function() { assert!(is_vertex_cover(3, &[(0, 1), (1, 2)], &[false, true, false])); @@ -117,27 +41,6 @@ fn test_direction() { assert_eq!(problem.direction(), Direction::Minimize); } -#[test] -fn test_empty_graph() { - let problem = MinimumVertexCover::::new(3, vec![]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - // No edges means empty cover is valid and optimal - assert_eq!(solutions.len(), 1); - assert_eq!(solutions[0], vec![0, 0, 0]); -} - -#[test] -fn test_single_edge() { - let problem = MinimumVertexCover::::new(2, vec![(0, 1)]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - // Either vertex covers the single edge - assert_eq!(solutions.len(), 2); -} - #[test] fn test_complement_relationship() { // For a graph, if S is an independent set, then V\S is a vertex cover @@ -197,18 +100,31 @@ fn test_has_edge() { } #[test] -fn test_mvc_problem_v2() { - let p = MinimumVertexCover::::with_weights( - 3, - vec![(0, 1), (1, 2), (0, 2)], - vec![1, 1, 1], - ); - assert_eq!(p.dims(), vec![2, 2, 2]); - // Valid VC: select all vertices - assert_eq!(Problem::evaluate(&p, &[1, 1, 1]), SolutionSize::Valid(3)); - // Valid VC: select vertices 0 and 1 (covers all edges in triangle) - assert_eq!(Problem::evaluate(&p, &[1, 1, 0]), SolutionSize::Valid(2)); - // Invalid VC: select only vertex 0 (edge (1,2) not covered) - assert_eq!(Problem::evaluate(&p, &[1, 0, 0]), SolutionSize::Invalid); - assert_eq!(p.direction(), Direction::Minimize); +fn test_jl_parity_evaluation() { + let data: serde_json::Value = + serde_json::from_str(include_str!("../../../../tests/data/jl/vertexcovering.json")).unwrap(); + for instance in data["instances"].as_array().unwrap() { + let nv = instance["instance"]["num_vertices"].as_u64().unwrap() as usize; + let edges = jl_parse_edges(&instance["instance"]); + let weights = jl_parse_i32_vec(&instance["instance"]["weights"]); + let problem = if weights.iter().all(|&w| w == 1) { + MinimumVertexCover::::new(nv, edges) + } else { + MinimumVertexCover::with_weights(nv, edges, weights) + }; + 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(); + assert_eq!(result.is_valid(), jl_valid, "VC validity mismatch for config {:?}", config); + if jl_valid { + let jl_size = eval["size"].as_i64().unwrap() as i32; + assert_eq!(result.unwrap(), jl_size, "VC 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, "VC best solutions mismatch"); + } } diff --git a/src/unit_tests/models/optimization/qubo.rs b/src/unit_tests/models/optimization/qubo.rs index a587614e..86becd57 100644 --- a/src/unit_tests/models/optimization/qubo.rs +++ b/src/unit_tests/models/optimization/qubo.rs @@ -2,6 +2,7 @@ use super::*; use crate::solvers::BruteForce; use crate::traits::{OptimizationProblem, Problem}; use crate::types::{Direction, SolutionSize}; +include!("../../jl_helpers.rs"); #[test] fn test_qubo_from_matrix() { @@ -20,63 +21,6 @@ fn test_qubo_new() { assert_eq!(problem.get(0, 1), Some(&3.0)); } -#[test] -fn test_evaluate() { - // Q = [[1, 2], [0, 3]] - // f(x) = x0 + 3*x1 + 2*x0*x1 - let problem = QUBO::from_matrix(vec![vec![1.0, 2.0], vec![0.0, 3.0]]); - - assert_eq!( - Problem::evaluate(&problem, &[0, 0]), - SolutionSize::Valid(0.0) - ); - assert_eq!( - Problem::evaluate(&problem, &[1, 0]), - SolutionSize::Valid(1.0) - ); - assert_eq!( - Problem::evaluate(&problem, &[0, 1]), - SolutionSize::Valid(3.0) - ); - assert_eq!( - Problem::evaluate(&problem, &[1, 1]), - SolutionSize::Valid(6.0) - ); // 1 + 3 + 2 = 6 -} - -#[test] -fn test_brute_force_minimize() { - // Q = [[1, 0], [0, -2]] - // f(x) = x0 - 2*x1 - // Minimum at x = [0, 1] with value -2 - let problem = QUBO::from_matrix(vec![vec![1.0, 0.0], vec![0.0, -2.0]]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - assert_eq!(solutions.len(), 1); - assert_eq!(solutions[0], vec![0, 1]); - assert_eq!( - Problem::evaluate(&problem, &solutions[0]), - SolutionSize::Valid(-2.0) - ); -} - -#[test] -fn test_brute_force_with_interaction() { - // Q = [[-1, 2], [0, -1]] - // f(x) = -x0 - x1 + 2*x0*x1 - // x=[0,0] -> 0, x=[1,0] -> -1, x=[0,1] -> -1, x=[1,1] -> 0 - let problem = QUBO::from_matrix(vec![vec![-1.0, 2.0], vec![0.0, -1.0]]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - // Minimum is -1 at [1,0] or [0,1] - assert_eq!(solutions.len(), 2); - for sol in &solutions { - assert_eq!(Problem::evaluate(&problem, sol), SolutionSize::Valid(-1.0)); - } -} - #[test] fn test_direction() { let problem = QUBO::::from_matrix(vec![vec![1.0]]); @@ -108,16 +52,6 @@ fn test_empty_qubo() { assert_eq!(Problem::evaluate(&problem, &[]), SolutionSize::Valid(0.0)); } -#[test] -fn test_single_variable() { - let problem = QUBO::from_matrix(vec![vec![-5.0]]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - assert_eq!(solutions.len(), 1); - assert_eq!(solutions[0], vec![1]); // x=1 gives -5, x=0 gives 0 -} - #[test] fn test_qubo_new_reverse_indices() { // Test the case where (j, i) is provided with i < j @@ -133,17 +67,33 @@ fn test_get_out_of_bounds() { } #[test] -fn test_qubo_problem() { - // Simple 2-variable QUBO: Q = [[1, -2], [0, 1]] - // f(x) = x0 - 2*x0*x1 + x1 - let q = vec![vec![1.0, -2.0], vec![0.0, 1.0]]; - let p = QUBO::::from_matrix(q); - assert_eq!(p.dims(), vec![2, 2]); - // x = [0, 0]: f = 0 - assert_eq!(Problem::evaluate(&p, &[0, 0]), SolutionSize::Valid(0.0)); - // x = [1, 1]: f = 1 - 2 + 1 = 0 - assert_eq!(Problem::evaluate(&p, &[1, 1]), SolutionSize::Valid(0.0)); - // x = [1, 0]: f = 1 - assert_eq!(Problem::evaluate(&p, &[1, 0]), SolutionSize::Valid(1.0)); - assert_eq!(p.direction(), Direction::Minimize); +fn test_jl_parity_evaluation() { + let data: serde_json::Value = + serde_json::from_str(include_str!("../../../../tests/data/jl/qubo.json")).unwrap(); + for instance in data["instances"].as_array().unwrap() { + let jl_matrix: Vec> = instance["instance"]["matrix"] + .as_array().unwrap().iter() + .map(|row| row.as_array().unwrap().iter().map(|v| v.as_f64().unwrap()).collect()) + .collect(); + let n = jl_matrix.len(); + let mut rust_matrix = vec![vec![0.0f64; n]; n]; + for i in 0..n { + rust_matrix[i][i] = jl_matrix[i][i]; + for j in (i + 1)..n { + rust_matrix[i][j] = jl_matrix[i][j] + jl_matrix[j][i]; + } + } + let problem = QUBO::from_matrix(rust_matrix); + for eval in instance["evaluations"].as_array().unwrap() { + let config = jl_parse_config(&eval["config"]); + let result: SolutionSize = Problem::evaluate(&problem, &config); + let jl_size = eval["size"].as_f64().unwrap(); + assert!(result.is_valid(), "QUBO should always be valid"); + assert!((result.unwrap() - jl_size).abs() < 1e-10, "QUBO value 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, "QUBO best solutions mismatch"); + } } diff --git a/src/unit_tests/models/optimization/spin_glass.rs b/src/unit_tests/models/optimization/spin_glass.rs index cec4fb56..b61c14e2 100644 --- a/src/unit_tests/models/optimization/spin_glass.rs +++ b/src/unit_tests/models/optimization/spin_glass.rs @@ -1,7 +1,8 @@ use super::*; use crate::solvers::BruteForce; use crate::traits::{OptimizationProblem, Problem}; -use crate::types::{Direction, SolutionSize}; +use crate::types::Direction; +include!("../../jl_helpers.rs"); #[test] fn test_spin_glass_creation() { @@ -66,54 +67,6 @@ fn test_compute_energy_with_fields() { assert_eq!(problem.compute_energy(&[-1, 1]), -2.0); // -1 - 1 = -2 } -#[test] -fn test_evaluate() { - let problem = SpinGlass::::new(2, vec![((0, 1), 1.0)], vec![0.0, 0.0]); - - // config [0,0] -> spins [-1,-1] -> energy = 1 - assert_eq!( - Problem::evaluate(&problem, &[0, 0]), - SolutionSize::Valid(1.0) - ); - - // config [0,1] -> spins [-1,1] -> energy = -1 - assert_eq!( - Problem::evaluate(&problem, &[0, 1]), - SolutionSize::Valid(-1.0) - ); -} - -#[test] -fn test_brute_force_ferromagnetic() { - // Ferromagnetic: J > 0 prefers aligned spins to minimize energy - // But wait, energy = J*s1*s2, so J>0 with aligned gives positive energy - // For minimization, we want anti-aligned for J>0 - let problem = SpinGlass::::new(2, vec![((0, 1), 1.0)], vec![0.0, 0.0]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - // Minimum energy is -1 (anti-aligned) - for sol in &solutions { - assert_ne!(sol[0], sol[1]); - assert_eq!(Problem::evaluate(&problem, sol), SolutionSize::Valid(-1.0)); - } -} - -#[test] -fn test_brute_force_antiferromagnetic() { - // Antiferromagnetic: J < 0, energy = J*s1*s2 - // J<0 with aligned spins gives negative energy (good for minimization) - let problem = SpinGlass::::new(2, vec![((0, 1), -1.0)], vec![0.0, 0.0]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - // Minimum energy is -1 (aligned) - for sol in &solutions { - assert_eq!(sol[0], sol[1]); - assert_eq!(Problem::evaluate(&problem, sol), SolutionSize::Valid(-1.0)); - } -} - #[test] fn test_direction() { let problem = SpinGlass::::without_fields(2, vec![]); @@ -126,24 +79,6 @@ fn test_num_variables() { assert_eq!(problem.num_variables(), 5); } -#[test] -fn test_triangle_frustration() { - // Triangle with all antiferromagnetic couplings - frustrated system - let problem = SpinGlass::::new( - 3, - vec![((0, 1), 1.0), ((1, 2), 1.0), ((0, 2), 1.0)], - vec![0.0, 0.0, 0.0], - ); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - // Best we can do is satisfy 2 out of 3 interactions - // Energy = -1 -1 + 1 = -1 (one frustrated) - for sol in &solutions { - assert_eq!(Problem::evaluate(&problem, sol), SolutionSize::Valid(-1.0)); - } -} - #[test] fn test_from_graph() { let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2)]); @@ -172,19 +107,27 @@ fn test_graph_accessor() { } #[test] -fn test_spin_glass_problem() { - // Two spins with antiferromagnetic coupling J_01 = 1 - let p = SpinGlass::::new(2, vec![((0, 1), 1.0)], vec![0.0, 0.0]); - assert_eq!(p.dims(), vec![2, 2]); - - // config [0, 0] => spins [-1, -1]: H = 1 * (-1)*(-1) = 1 - assert_eq!(Problem::evaluate(&p, &[0, 0]), SolutionSize::Valid(1.0)); - // config [1, 1] => spins [+1, +1]: H = 1 * 1*1 = 1 - assert_eq!(Problem::evaluate(&p, &[1, 1]), SolutionSize::Valid(1.0)); - // config [0, 1] => spins [-1, +1]: H = 1 * (-1)*(1) = -1 - assert_eq!(Problem::evaluate(&p, &[0, 1]), SolutionSize::Valid(-1.0)); - // config [1, 0] => spins [+1, -1]: H = 1 * (1)*(-1) = -1 - assert_eq!(Problem::evaluate(&p, &[1, 0]), SolutionSize::Valid(-1.0)); - - assert_eq!(p.direction(), Direction::Minimize); +fn test_jl_parity_evaluation() { + let data: serde_json::Value = + serde_json::from_str(include_str!("../../../../tests/data/jl/spinglass.json")).unwrap(); + for instance in data["instances"].as_array().unwrap() { + let nv = instance["instance"]["num_vertices"].as_u64().unwrap() as usize; + let edges = jl_parse_edges(&instance["instance"]); + let j_values = jl_parse_i32_vec(&instance["instance"]["J"]); + let h_values = jl_parse_i32_vec(&instance["instance"]["h"]); + let interactions: Vec<((usize, usize), i32)> = edges.into_iter().zip(j_values).collect(); + let problem = SpinGlass::::new(nv, interactions, h_values); + for eval in instance["evaluations"].as_array().unwrap() { + let jl_config = jl_parse_config(&eval["config"]); + let config = jl_flip_config(&jl_config); + let result = problem.evaluate(&config); + let jl_size = eval["size"].as_i64().unwrap() as i32; + assert!(result.is_valid(), "SpinGlass should always be valid"); + assert_eq!(result.unwrap(), jl_size, "SpinGlass energy mismatch for config {:?}", config); + } + let best = BruteForce::new().find_all_best(&problem); + let jl_best = jl_flip_configs_set(&jl_parse_configs_set(&instance["best_solutions"])); + let rust_best: HashSet> = best.into_iter().collect(); + assert_eq!(rust_best, jl_best, "SpinGlass best solutions mismatch"); + } } diff --git a/src/unit_tests/models/satisfiability/ksat.rs b/src/unit_tests/models/satisfiability/ksat.rs index 47a56c98..ec8b2960 100644 --- a/src/unit_tests/models/satisfiability/ksat.rs +++ b/src/unit_tests/models/satisfiability/ksat.rs @@ -1,6 +1,7 @@ use super::*; use crate::solvers::BruteForce; use crate::traits::Problem; +include!("../../jl_helpers.rs"); #[test] fn test_3sat_creation() { @@ -48,24 +49,6 @@ fn test_3sat_is_satisfying() { assert!(!problem.is_satisfying(&[true, true, true])); } -#[test] -fn test_3sat_brute_force() { - let problem = KSatisfiability::<3>::new( - 3, - vec![ - CNFClause::new(vec![1, 2, 3]), - CNFClause::new(vec![-1, -2, 3]), - ], - ); - let solver = BruteForce::new(); - let solutions = solver.find_all_satisfying(&problem); - - assert!(!solutions.is_empty()); - for sol in &solutions { - assert!(problem.evaluate(sol)); - } -} - #[test] fn test_ksat_allow_less() { // This should work - clause has 2 literals which is <= 3 @@ -102,50 +85,22 @@ fn test_ksat_count_satisfied() { } #[test] -fn test_ksat_evaluate() { - let problem = KSatisfiability::<3>::new( - 3, - vec![ - CNFClause::new(vec![1, 2, 3]), - CNFClause::new(vec![-1, -2, -3]), - ], - ); - assert!(problem.evaluate(&[1, 0, 0])); // x1=T, x2=F, x3=F - assert!(!problem.evaluate(&[1, 1, 1])); // x1=T, x2=T, x3=T -} - -#[test] -fn test_ksat_problem_v2() { - use crate::traits::Problem; - - let p = KSatisfiability::<3>::new( - 3, - vec![ - CNFClause::new(vec![1, 2, 3]), - CNFClause::new(vec![-1, -2, -3]), - ], - ); - - assert_eq!(p.dims(), vec![2, 2, 2]); - assert!(p.evaluate(&[1, 0, 0])); - assert!(!p.evaluate(&[1, 1, 1])); - assert!(!p.evaluate(&[0, 0, 0])); - assert!(p.evaluate(&[1, 0, 1])); - assert_eq!( as Problem>::NAME, "KSatisfiability"); -} - -#[test] -fn test_ksat_problem_v2_2sat() { - use crate::traits::Problem; - - let p = KSatisfiability::<2>::new( - 2, - vec![CNFClause::new(vec![1, 2]), CNFClause::new(vec![-1, -2])], - ); - - assert_eq!(p.dims(), vec![2, 2]); - assert!(p.evaluate(&[1, 0])); - assert!(p.evaluate(&[0, 1])); - assert!(!p.evaluate(&[1, 1])); - assert!(!p.evaluate(&[0, 0])); +fn test_jl_parity_evaluation() { + let data: serde_json::Value = + serde_json::from_str(include_str!("../../../../tests/data/jl/ksatisfiability.json")).unwrap(); + for instance in data["instances"].as_array().unwrap() { + let (num_vars, clauses) = jl_parse_sat_clauses(&instance["instance"]); + let num_clauses = instance["instance"]["clauses"].as_array().unwrap().len(); + let problem = KSatisfiability::<3>::new(num_vars, clauses); + for eval in instance["evaluations"].as_array().unwrap() { + let config = jl_parse_config(&eval["config"]); + let rust_result = problem.evaluate(&config); + let jl_size = eval["size"].as_u64().unwrap() as usize; + assert_eq!(rust_result, jl_size == num_clauses, "KSat eval mismatch for config {:?}", config); + } + let rust_best = BruteForce::new().find_all_satisfying(&problem); + let jl_best = jl_parse_configs_set(&instance["best_solutions"]); + let rust_best_set: HashSet> = rust_best.into_iter().collect(); + assert_eq!(rust_best_set, jl_best, "KSat best solutions mismatch"); + } } diff --git a/src/unit_tests/models/satisfiability/sat.rs b/src/unit_tests/models/satisfiability/sat.rs index 04e74e23..f0d0fdfc 100644 --- a/src/unit_tests/models/satisfiability/sat.rs +++ b/src/unit_tests/models/satisfiability/sat.rs @@ -1,6 +1,7 @@ use super::*; use crate::solvers::{BruteForce, Solver}; use crate::traits::Problem; +include!("../../jl_helpers.rs"); #[test] fn test_cnf_clause_creation() { @@ -71,52 +72,6 @@ fn test_count_satisfied() { assert_eq!(problem.count_satisfied(&[true, false]), 2); // x1 and last } -#[test] -fn test_evaluate() { - let problem = Satisfiability::new( - 2, - vec![CNFClause::new(vec![1, 2]), CNFClause::new(vec![-1, -2])], - ); - - // true, false - satisfies both clauses - assert!(problem.evaluate(&[1, 0])); - - // true, true - fails second clause - assert!(!problem.evaluate(&[1, 1])); -} - -#[test] -fn test_brute_force_satisfiable() { - // (x1) AND (x2) AND (NOT x1 OR NOT x2) - UNSAT - let problem = Satisfiability::new( - 2, - vec![ - CNFClause::new(vec![1]), - CNFClause::new(vec![2]), - CNFClause::new(vec![-1, -2]), - ], - ); - let solver = BruteForce::new(); - - // This is unsatisfiable, so find_satisfying returns None - let solution = solver.find_satisfying(&problem); - assert!(solution.is_none()); -} - -#[test] -fn test_brute_force_simple_sat() { - // (x1 OR x2) - many solutions - let problem = Satisfiability::new(2, vec![CNFClause::new(vec![1, 2])]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_satisfying(&problem); - // 3 satisfying assignments - assert_eq!(solutions.len(), 3); - for sol in &solutions { - assert!(problem.evaluate(sol)); - } -} - #[test] fn test_is_satisfying_assignment() { let clauses = vec![vec![1, 2], vec![-1, 3]]; @@ -179,38 +134,6 @@ fn test_get_clause() { assert_eq!(problem.get_clause(2), None); } -#[test] -fn test_three_sat_example() { - // (x1 OR x2 OR x3) AND (NOT x1 OR NOT x2 OR x3) AND (x1 OR NOT x2 OR NOT x3) - let problem = Satisfiability::new( - 3, - vec![ - CNFClause::new(vec![1, 2, 3]), - CNFClause::new(vec![-1, -2, 3]), - CNFClause::new(vec![1, -2, -3]), - ], - ); - let solver = BruteForce::new(); - - let solutions = solver.find_all_satisfying(&problem); - for sol in &solutions { - assert!(problem.evaluate(sol)); - } -} - -#[test] -fn test_evaluate_csp() { - let problem = Satisfiability::new( - 2, - vec![CNFClause::new(vec![1, 2]), CNFClause::new(vec![-1, -2])], - ); - - assert!(problem.evaluate(&[1, 0])); - assert!(problem.evaluate(&[0, 1])); - assert!(!problem.evaluate(&[1, 1])); - assert!(!problem.evaluate(&[0, 0])); -} - #[test] fn test_is_satisfying_assignment_defaults() { // When assignment is shorter than needed, missing vars default to false @@ -243,40 +166,26 @@ fn test_clause_debug() { } #[test] -fn test_sat_problem() { - use crate::traits::Problem; - - let p = Satisfiability::new( - 2, - vec![CNFClause::new(vec![1, 2]), CNFClause::new(vec![-1, 2])], - ); - - assert_eq!(p.dims(), vec![2, 2]); - assert!(!p.evaluate(&[0, 0])); - assert!(!p.evaluate(&[1, 0])); - assert!(p.evaluate(&[0, 1])); - assert!(p.evaluate(&[1, 1])); - assert_eq!(::NAME, "Satisfiability"); -} - -#[test] -fn test_sat_problem_empty_formula() { - use crate::traits::Problem; - - let p = Satisfiability::new(2, vec![]); - assert_eq!(p.dims(), vec![2, 2]); - assert!(p.evaluate(&[0, 0])); - assert!(p.evaluate(&[1, 1])); +fn test_jl_parity_evaluation() { + let data: serde_json::Value = + serde_json::from_str(include_str!("../../../../tests/data/jl/satisfiability.json")).unwrap(); + for instance in data["instances"].as_array().unwrap() { + let (num_vars, clauses) = jl_parse_sat_clauses(&instance["instance"]); + let problem = Satisfiability::new(num_vars, clauses); + let num_clauses = instance["instance"]["clauses"].as_array().unwrap().len(); + for eval in instance["evaluations"].as_array().unwrap() { + let config = jl_parse_config(&eval["config"]); + let rust_result = problem.evaluate(&config); + let jl_size = eval["size"].as_u64().unwrap() as usize; + let jl_all_satisfied = jl_size == num_clauses; + assert_eq!(rust_result, jl_all_satisfied, "SAT eval mismatch for config {:?}", config); + } + let rust_best = BruteForce::new().find_all_satisfying(&problem); + let rust_best_set: HashSet> = rust_best.into_iter().collect(); + if !rust_best_set.is_empty() { + let jl_best = jl_parse_configs_set(&instance["best_solutions"]); + assert_eq!(rust_best_set, jl_best, "SAT best solutions mismatch"); + } + } } -#[test] -fn test_sat_problem_single_literal() { - use crate::traits::Problem; - - let p = Satisfiability::new(2, vec![CNFClause::new(vec![1]), CNFClause::new(vec![-2])]); - assert_eq!(p.dims(), vec![2, 2]); - assert!(p.evaluate(&[1, 0])); - assert!(!p.evaluate(&[0, 0])); - assert!(!p.evaluate(&[1, 1])); - assert!(!p.evaluate(&[0, 1])); -} diff --git a/src/unit_tests/models/set/maximum_set_packing.rs b/src/unit_tests/models/set/maximum_set_packing.rs index 06ce8e7a..1aab88c9 100644 --- a/src/unit_tests/models/set/maximum_set_packing.rs +++ b/src/unit_tests/models/set/maximum_set_packing.rs @@ -2,6 +2,7 @@ use super::*; use crate::solvers::BruteForce; use crate::traits::{OptimizationProblem, Problem}; use crate::types::{Direction, SolutionSize}; +include!("../../jl_helpers.rs"); #[test] fn test_set_packing_creation() { @@ -35,64 +36,6 @@ fn test_overlapping_pairs() { assert!(pairs.contains(&(1, 2))); } -#[test] -fn test_evaluate_valid() { - let problem = MaximumSetPacking::::new(vec![vec![0, 1], vec![2, 3], vec![4, 5]]); - - // All disjoint, can select all - assert_eq!( - Problem::evaluate(&problem, &[1, 1, 1]), - SolutionSize::Valid(3) - ); - - // Select none - valid with size 0 - assert_eq!( - Problem::evaluate(&problem, &[0, 0, 0]), - SolutionSize::Valid(0) - ); -} - -#[test] -fn test_evaluate_invalid() { - let problem = MaximumSetPacking::::new(vec![vec![0, 1], vec![1, 2], vec![3, 4]]); - - // Sets 0 and 1 overlap - returns Invalid - assert_eq!( - Problem::evaluate(&problem, &[1, 1, 0]), - SolutionSize::Invalid - ); -} - -#[test] -fn test_brute_force_chain() { - // Chain: {0,1}, {1,2}, {2,3} - can select at most 2 non-adjacent sets - let problem = MaximumSetPacking::::new(vec![vec![0, 1], vec![1, 2], vec![2, 3]]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - // Max is 2: select {0,1} and {2,3} - for sol in &solutions { - assert_eq!(sol.iter().sum::(), 2); - // Verify it's a valid packing - assert!(Problem::evaluate(&problem, sol).is_valid()); - } -} - -#[test] -fn test_brute_force_weighted() { - // Weighted: single heavy set vs multiple light sets - let problem = MaximumSetPacking::with_weights( - vec![vec![0, 1, 2, 3], vec![0, 1], vec![2, 3]], - vec![5, 3, 3], - ); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - // Should select sets 1 and 2 (total 6) over set 0 (total 5) - assert_eq!(solutions.len(), 1); - assert_eq!(solutions[0], vec![0, 1, 1]); -} - #[test] fn test_is_set_packing_function() { let sets = vec![vec![0, 1], vec![1, 2], vec![3, 4]]; @@ -109,30 +52,6 @@ fn test_direction() { assert_eq!(problem.direction(), Direction::Maximize); } -#[test] -fn test_disjoint_sets() { - let problem = MaximumSetPacking::::new(vec![vec![0], vec![1], vec![2], vec![3]]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - // All sets are disjoint, so select all - assert_eq!(solutions.len(), 1); - assert_eq!(solutions[0], vec![1, 1, 1, 1]); -} - -#[test] -fn test_all_overlapping() { - // All sets share element 0 - let problem = MaximumSetPacking::::new(vec![vec![0, 1], vec![0, 2], vec![0, 3]]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - // Can only select one set - for sol in &solutions { - assert_eq!(sol.iter().sum::(), 1); - } -} - #[test] fn test_empty_sets() { let problem = MaximumSetPacking::::new(vec![]); @@ -179,17 +98,30 @@ fn test_is_set_packing_wrong_len() { } #[test] -fn test_set_packing_problem() { - // S0={0,1}, S1={1,2}, S2={3,4} -- S0 and S1 overlap, S2 is disjoint from both - let p = MaximumSetPacking::::new(vec![vec![0, 1], vec![1, 2], vec![3, 4]]); - assert_eq!(p.dims(), vec![2, 2, 2]); - - // Select S0 and S2 (disjoint) -> valid, weight=2 - assert_eq!(Problem::evaluate(&p, &[1, 0, 1]), SolutionSize::Valid(2)); - // Select S0 and S1 (overlap) -> invalid - assert_eq!(Problem::evaluate(&p, &[1, 1, 0]), SolutionSize::Invalid); - // Select none -> valid, weight=0 - assert_eq!(Problem::evaluate(&p, &[0, 0, 0]), SolutionSize::Valid(0)); - - assert_eq!(p.direction(), Direction::Maximize); +fn test_jl_parity_evaluation() { + let data: serde_json::Value = + serde_json::from_str(include_str!("../../../../tests/data/jl/setpacking.json")).unwrap(); + for instance in data["instances"].as_array().unwrap() { + let sets = jl_parse_sets(&instance["instance"]["sets"]); + let weights = jl_parse_i32_vec(&instance["instance"]["weights"]); + let problem = if weights.iter().all(|&w| w == 1) { + MaximumSetPacking::::new(sets) + } else { + MaximumSetPacking::with_weights(sets, weights) + }; + 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(); + assert_eq!(result.is_valid(), jl_valid, "SetPacking validity mismatch for config {:?}", config); + if jl_valid { + let jl_size = eval["size"].as_i64().unwrap() as i32; + assert_eq!(result.unwrap(), jl_size, "SetPacking 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, "SetPacking best solutions mismatch"); + } } diff --git a/src/unit_tests/models/set/minimum_set_covering.rs b/src/unit_tests/models/set/minimum_set_covering.rs index 3746d72a..65ef74e8 100644 --- a/src/unit_tests/models/set/minimum_set_covering.rs +++ b/src/unit_tests/models/set/minimum_set_covering.rs @@ -2,6 +2,7 @@ use super::*; use crate::solvers::BruteForce; use crate::traits::{OptimizationProblem, Problem}; use crate::types::{Direction, SolutionSize}; +include!("../../jl_helpers.rs"); #[test] fn test_set_covering_creation() { @@ -33,71 +34,6 @@ fn test_covered_elements() { assert!(covered.contains(&3)); } -#[test] -fn test_evaluate_valid() { - let problem = MinimumSetCovering::::new(4, vec![vec![0, 1], vec![1, 2], vec![2, 3]]); - - // Select first and third sets: covers {0,1} + {2,3} = {0,1,2,3} - assert_eq!( - Problem::evaluate(&problem, &[1, 0, 1]), - SolutionSize::Valid(2) - ); - - // Select all sets - assert_eq!( - Problem::evaluate(&problem, &[1, 1, 1]), - SolutionSize::Valid(3) - ); -} - -#[test] -fn test_evaluate_invalid() { - let problem = MinimumSetCovering::::new(4, vec![vec![0, 1], vec![1, 2], vec![2, 3]]); - - // Select only first set: missing 2, 3 - returns Invalid - assert_eq!( - Problem::evaluate(&problem, &[1, 0, 0]), - SolutionSize::Invalid - ); - - // Select none - assert_eq!( - Problem::evaluate(&problem, &[0, 0, 0]), - SolutionSize::Invalid - ); -} - -#[test] -fn test_brute_force_simple() { - // Universe {0,1,2}, sets: {0,1}, {1,2}, {0,2} - // Minimum cover: any 2 sets work - let problem = MinimumSetCovering::::new(3, vec![vec![0, 1], vec![1, 2], vec![0, 2]]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - for sol in &solutions { - assert_eq!(sol.iter().sum::(), 2); - // Verify it's a valid cover - assert!(Problem::evaluate(&problem, sol).is_valid()); - } -} - -#[test] -fn test_brute_force_weighted() { - // Prefer lighter sets - let problem = MinimumSetCovering::with_weights( - 3, - vec![vec![0, 1, 2], vec![0, 1], vec![2]], - vec![10, 3, 3], - ); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - // Should select sets 1 and 2 (total 6) instead of set 0 (total 10) - assert_eq!(solutions.len(), 1); - assert_eq!(solutions[0], vec![0, 1, 1]); -} - #[test] fn test_is_set_cover_function() { let sets = vec![vec![0, 1], vec![1, 2], vec![2, 3]]; @@ -122,30 +58,6 @@ fn test_direction() { assert_eq!(problem.direction(), Direction::Minimize); } -#[test] -fn test_single_set_covers_all() { - let problem = MinimumSetCovering::::new(3, vec![vec![0, 1, 2], vec![0], vec![1], vec![2]]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - // First set alone covers everything - assert_eq!(solutions.len(), 1); - assert_eq!(solutions[0], vec![1, 0, 0, 0]); -} - -#[test] -fn test_overlapping_sets() { - // All sets overlap on element 1 - let problem = MinimumSetCovering::::new(3, vec![vec![0, 1], vec![1, 2], vec![1]]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - // Minimum is selecting first two sets - for sol in &solutions { - assert_eq!(sol.iter().sum::(), 2); - } -} - #[test] fn test_empty_universe() { let problem = MinimumSetCovering::::new(0, vec![]); @@ -160,17 +72,27 @@ fn test_is_set_cover_wrong_len() { } #[test] -fn test_set_covering_problem() { - // Universe {0,1,2,3}, S0={0,1}, S1={2,3} - let p = MinimumSetCovering::::new(4, vec![vec![0, 1], vec![2, 3]]); - assert_eq!(p.dims(), vec![2, 2]); - - // Select both -> covers all, weight=2 - assert_eq!(Problem::evaluate(&p, &[1, 1]), SolutionSize::Valid(2)); - // Select only S0 -> doesn't cover {2,3}, invalid - assert_eq!(Problem::evaluate(&p, &[1, 0]), SolutionSize::Invalid); - // Select none -> doesn't cover anything -> invalid - assert_eq!(Problem::evaluate(&p, &[0, 0]), SolutionSize::Invalid); - - assert_eq!(p.direction(), Direction::Minimize); +fn test_jl_parity_evaluation() { + let data: serde_json::Value = + serde_json::from_str(include_str!("../../../../tests/data/jl/setcovering.json")).unwrap(); + for instance in data["instances"].as_array().unwrap() { + let universe_size = instance["instance"]["universe_size"].as_u64().unwrap() as usize; + let sets = jl_parse_sets(&instance["instance"]["sets"]); + let weights = jl_parse_i32_vec(&instance["instance"]["weights"]); + let problem = MinimumSetCovering::::with_weights(universe_size, sets, weights); + 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(); + assert_eq!(result.is_valid(), jl_valid, "SetCovering validity mismatch for config {:?}", config); + if jl_valid { + let jl_size = eval["size"].as_i64().unwrap() as i32; + assert_eq!(result.unwrap(), jl_size, "SetCovering 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, "SetCovering best solutions mismatch"); + } } diff --git a/src/unit_tests/models/specialized/factoring.rs b/src/unit_tests/models/specialized/factoring.rs index 6ad18e87..cfe62ee5 100644 --- a/src/unit_tests/models/specialized/factoring.rs +++ b/src/unit_tests/models/specialized/factoring.rs @@ -1,7 +1,8 @@ use super::*; use crate::solvers::BruteForce; use crate::traits::{OptimizationProblem, Problem}; -use crate::types::{Direction, SolutionSize}; +use crate::types::Direction; +include!("../../jl_helpers.rs"); #[test] fn test_factoring_creation() { @@ -41,78 +42,6 @@ fn test_read_factors() { assert_eq!(b, 3); } -#[test] -fn test_evaluate_valid() { - let problem = Factoring::new(2, 2, 6); - // 2 * 3 = 6 -> distance 0 - assert_eq!( - Problem::evaluate(&problem, &[0, 1, 1, 1]), - SolutionSize::Valid(0) - ); - - // 3 * 2 = 6 -> distance 0 - assert_eq!( - Problem::evaluate(&problem, &[1, 1, 0, 1]), - SolutionSize::Valid(0) - ); -} - -#[test] -fn test_evaluate_invalid() { - let problem = Factoring::new(2, 2, 6); - // 2 * 2 = 4 != 6 -> distance 2 - assert_eq!( - Problem::evaluate(&problem, &[0, 1, 0, 1]), - SolutionSize::Valid(2) - ); - - // 1 * 1 = 1 != 6 -> distance 5 - assert_eq!( - Problem::evaluate(&problem, &[1, 0, 1, 0]), - SolutionSize::Valid(5) - ); -} - -#[test] -fn test_brute_force_factor_6() { - let problem = Factoring::new(2, 2, 6); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - // Should find 2*3 and 3*2 - assert!(!solutions.is_empty()); - for sol in &solutions { - let (a, b) = problem.read_factors(sol); - assert_eq!(a * b, 6); - } -} - -#[test] -fn test_brute_force_factor_15() { - let problem = Factoring::new(3, 3, 15); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - // Should find 3*5, 5*3, 1*15, 15*1 - for sol in &solutions { - let (a, b) = problem.read_factors(sol); - assert_eq!(a * b, 15); - } -} - -#[test] -fn test_brute_force_prime() { - // 7 is prime, only 1*7 and 7*1 work - let problem = Factoring::new(3, 3, 7); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - let factor_pairs: Vec<_> = solutions.iter().map(|s| problem.read_factors(s)).collect(); - - // Should find at least one of (1,7) or (7,1) - assert!(factor_pairs.contains(&(1, 7)) || factor_pairs.contains(&(7, 1))); -} - #[test] fn test_is_factoring_function() { assert!(is_factoring(6, 2, 3)); @@ -135,33 +64,27 @@ fn test_is_valid_factorization() { } #[test] -fn test_factor_one() { - // Factor 1: only 1*1 works - let problem = Factoring::new(2, 2, 1); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - for sol in &solutions { - let (a, b) = problem.read_factors(sol); - assert_eq!(a * b, 1); +fn test_jl_parity_evaluation() { + let data: serde_json::Value = + serde_json::from_str(include_str!("../../../../tests/data/jl/factoring.json")).unwrap(); + for instance in data["instances"].as_array().unwrap() { + let m = instance["instance"]["m"].as_u64().unwrap() as usize; + let n = instance["instance"]["n"].as_u64().unwrap() as usize; + let input = instance["instance"]["input"].as_u64().unwrap(); + let problem = Factoring::new(m, n, input); + 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(); + if jl_valid { + assert_eq!(result.unwrap(), 0, "Factoring: valid config should have distance 0"); + } else { + assert_ne!(result.unwrap(), 0, "Factoring: invalid config should have nonzero distance"); + } + } + 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, "Factoring best solutions mismatch"); } } - -#[test] -fn test_factoring_problem() { - use crate::traits::{OptimizationProblem, Problem}; - use crate::types::Direction; - - // Factor 6 with 2-bit factors - let p = Factoring::new(2, 2, 6); - assert_eq!(p.dims(), vec![2, 2, 2, 2]); - - // Bits [0,1, 1,1] = a=2, b=3, product=6, distance=0 - assert_eq!(Problem::evaluate(&p, &[0, 1, 1, 1]), SolutionSize::Valid(0)); - // Bits [1,1, 0,1] = a=3, b=2, product=6, distance=0 - assert_eq!(Problem::evaluate(&p, &[1, 1, 0, 1]), SolutionSize::Valid(0)); - // Bits [0,0, 0,0] = a=0, b=0, product=0, distance=6 - assert_eq!(Problem::evaluate(&p, &[0, 0, 0, 0]), SolutionSize::Valid(6)); - - assert_eq!(p.direction(), Direction::Minimize); -} diff --git a/src/unit_tests/models/specialized/paintshop.rs b/src/unit_tests/models/specialized/paintshop.rs index bab8b635..040f0994 100644 --- a/src/unit_tests/models/specialized/paintshop.rs +++ b/src/unit_tests/models/specialized/paintshop.rs @@ -1,7 +1,8 @@ use super::*; use crate::solvers::BruteForce; use crate::traits::{OptimizationProblem, Problem}; -use crate::types::{Direction, SolutionSize}; +use crate::types::Direction; +include!("../../jl_helpers.rs"); #[test] fn test_paintshop_creation() { @@ -46,43 +47,6 @@ fn test_count_switches() { assert_eq!(problem.count_switches(&[1, 1]), 1); } -#[test] -fn test_evaluate() { - let problem = PaintShop::new(vec!["a", "b", "a", "b"]); - - // Config [0, 0] -> coloring [0, 0, 1, 1] -> 1 switch - assert_eq!(Problem::evaluate(&problem, &[0, 0]), SolutionSize::Valid(1)); - - // Config [0, 1] -> coloring [0, 1, 1, 0] -> 2 switches - assert_eq!(Problem::evaluate(&problem, &[0, 1]), SolutionSize::Valid(2)); -} - -#[test] -fn test_brute_force_simple() { - let problem = PaintShop::new(vec!["a", "b", "a", "b"]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - // Optimal has 1 switch: [0,0] or [1,1] - for sol in &solutions { - assert_eq!(problem.count_switches(sol), 1); - } -} - -#[test] -fn test_brute_force_longer() { - // Sequence: a, b, a, c, c, b - let problem = PaintShop::new(vec!["a", "b", "a", "c", "c", "b"]); - let solver = BruteForce::new(); - - let solutions = solver.find_all_best(&problem); - // Find the minimum number of switches - let min_switches = problem.count_switches(&solutions[0]); - for sol in &solutions { - assert_eq!(problem.count_switches(sol), min_switches); - } -} - #[test] fn test_count_paint_switches_function() { assert_eq!(count_paint_switches(&[0, 0, 0]), 0); @@ -138,24 +102,23 @@ fn test_car_labels() { } #[test] -fn test_paintshop_problem() { - use crate::traits::{OptimizationProblem, Problem}; - use crate::types::Direction; - - let problem = PaintShop::new(vec!["a", "b", "a", "b"]); - - // dims: one binary variable per car - assert_eq!(problem.dims(), vec![2, 2]); - - // Config [0, 0] -> coloring [0, 0, 1, 1] -> 1 switch - assert_eq!(Problem::evaluate(&problem, &[0, 0]), SolutionSize::Valid(1)); - - // Config [0, 1] -> coloring [0, 1, 1, 0] -> 2 switches - assert_eq!(Problem::evaluate(&problem, &[0, 1]), SolutionSize::Valid(2)); - - // Config [1, 1] -> coloring [1, 1, 0, 0] -> 1 switch - assert_eq!(Problem::evaluate(&problem, &[1, 1]), SolutionSize::Valid(1)); - - // Direction is minimize - assert_eq!(problem.direction(), Direction::Minimize); +fn test_jl_parity_evaluation() { + let data: serde_json::Value = + serde_json::from_str(include_str!("../../../../tests/data/jl/paintshop.json")).unwrap(); + for instance in data["instances"].as_array().unwrap() { + let sequence: Vec = instance["instance"]["sequence"] + .as_array().unwrap().iter() + .map(|v| v.as_str().unwrap().to_string()).collect(); + let problem = PaintShop::new(sequence); + 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; + assert_eq!(result.unwrap(), jl_size, "PaintShop switches 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, "PaintShop best solutions mismatch"); + } } diff --git a/src/unit_tests/rules/circuit_spinglass.rs b/src/unit_tests/rules/circuit_spinglass.rs index 7a655304..a362ffec 100644 --- a/src/unit_tests/rules/circuit_spinglass.rs +++ b/src/unit_tests/rules/circuit_spinglass.rs @@ -3,6 +3,7 @@ use crate::models::specialized::Circuit; use crate::solvers::BruteForce; use crate::types::{NumericSize, WeightElement}; use num_traits::Num; +include!("../jl_helpers.rs"); /// Verify a gadget has the correct ground states. fn verify_gadget_truth_table(gadget: &LogicGadget, expected: &[(Vec, Vec)]) @@ -137,154 +138,6 @@ fn test_set1_gadget() { assert!(!solutions.contains(&vec![0])); } -#[test] -fn test_simple_and_circuit() { - // c = x AND y - let circuit = Circuit::new(vec![Assignment::new( - vec!["c".to_string()], - BooleanExpr::and(vec![BooleanExpr::var("x"), BooleanExpr::var("y")]), - )]); - let problem = CircuitSAT::new(circuit); - let reduction = problem.reduce_to(); - let sg = reduction.target_problem(); - - let solver = BruteForce::new(); - let solutions = solver.find_all_best(sg); - - // Extract and verify solutions - let extracted: Vec> = solutions - .iter() - .map(|s| reduction.extract_solution(s)) - .collect(); - - // Should have valid AND configurations - // Variables are sorted: c, x, y - let valid_configs = vec![ - vec![0, 0, 0], // c=0, x=0, y=0: 0 AND 0 = 0 OK - vec![0, 0, 1], // c=0, x=0, y=1: 0 AND 1 = 0 OK - vec![0, 1, 0], // c=0, x=1, y=0: 1 AND 0 = 0 OK - vec![1, 1, 1], // c=1, x=1, y=1: 1 AND 1 = 1 OK - ]; - - for config in &valid_configs { - assert!( - extracted.contains(config), - "Expected valid config {:?} not found in {:?}", - config, - extracted - ); - } -} - -#[test] -fn test_simple_or_circuit() { - // c = x OR y - let circuit = Circuit::new(vec![Assignment::new( - vec!["c".to_string()], - BooleanExpr::or(vec![BooleanExpr::var("x"), BooleanExpr::var("y")]), - )]); - let problem = CircuitSAT::new(circuit); - let reduction = problem.reduce_to(); - let sg = reduction.target_problem(); - - let solver = BruteForce::new(); - let solutions = solver.find_all_best(sg); - - let extracted: Vec> = solutions - .iter() - .map(|s| reduction.extract_solution(s)) - .collect(); - - // Variables sorted: c, x, y - let valid_configs = vec![ - vec![0, 0, 0], // c=0, x=0, y=0: 0 OR 0 = 0 OK - vec![1, 0, 1], // c=1, x=0, y=1: 0 OR 1 = 1 OK - vec![1, 1, 0], // c=1, x=1, y=0: 1 OR 0 = 1 OK - vec![1, 1, 1], // c=1, x=1, y=1: 1 OR 1 = 1 OK - ]; - - for config in &valid_configs { - assert!( - extracted.contains(config), - "Expected valid config {:?} not found in {:?}", - config, - extracted - ); - } -} - -#[test] -fn test_not_circuit() { - // c = NOT x - let circuit = Circuit::new(vec![Assignment::new( - vec!["c".to_string()], - BooleanExpr::not(BooleanExpr::var("x")), - )]); - let problem = CircuitSAT::new(circuit); - let reduction = problem.reduce_to(); - let sg = reduction.target_problem(); - - let solver = BruteForce::new(); - let solutions = solver.find_all_best(sg); - - let extracted: Vec> = solutions - .iter() - .map(|s| reduction.extract_solution(s)) - .collect(); - - // Variables sorted: c, x - let valid_configs = vec![ - vec![1, 0], // c=1, x=0: NOT 0 = 1 OK - vec![0, 1], // c=0, x=1: NOT 1 = 0 OK - ]; - - for config in &valid_configs { - assert!( - extracted.contains(config), - "Expected valid config {:?} not found in {:?}", - config, - extracted - ); - } -} - -#[test] -fn test_xor_circuit() { - // c = x XOR y - let circuit = Circuit::new(vec![Assignment::new( - vec!["c".to_string()], - BooleanExpr::xor(vec![BooleanExpr::var("x"), BooleanExpr::var("y")]), - )]); - let problem = CircuitSAT::new(circuit); - let reduction = problem.reduce_to(); - let sg = reduction.target_problem(); - - let solver = BruteForce::new(); - let solutions = solver.find_all_best(sg); - - let extracted: Vec> = solutions - .iter() - .map(|s| reduction.extract_solution(s)) - .collect(); - - // Variables sorted: c, x, y - let valid_configs = vec![ - vec![0, 0, 0], // c=0, x=0, y=0: 0 XOR 0 = 0 OK - vec![1, 0, 1], // c=1, x=0, y=1: 0 XOR 1 = 1 OK - vec![1, 1, 0], // c=1, x=1, y=0: 1 XOR 0 = 1 OK - vec![0, 1, 1], // c=0, x=1, y=1: 1 XOR 1 = 0 OK - ]; - - for config in &valid_configs { - assert!( - extracted.contains(config), - "Expected valid config {:?} not found in {:?}", - config, - extracted - ); - } -} - #[test] fn test_constant_true() { // c = true @@ -377,105 +230,6 @@ fn test_multi_input_and() { ); } -#[test] -fn test_chained_circuit() { - // c = x AND y - // d = c OR z - let circuit = Circuit::new(vec![ - Assignment::new( - vec!["c".to_string()], - BooleanExpr::and(vec![BooleanExpr::var("x"), BooleanExpr::var("y")]), - ), - Assignment::new( - vec!["d".to_string()], - BooleanExpr::or(vec![BooleanExpr::var("c"), BooleanExpr::var("z")]), - ), - ]); - let problem = CircuitSAT::new(circuit); - let reduction = problem.reduce_to(); - let sg = reduction.target_problem(); - - let solver = BruteForce::new(); - let solutions = solver.find_all_best(sg); - - let extracted: Vec> = solutions - .iter() - .map(|s| reduction.extract_solution(s)) - .collect(); - - // Verify some valid configurations - // Variables sorted: c, d, x, y, z - // c = x AND y, d = c OR z - - // x=1, y=1 -> c=1, z=0 -> d=1 - assert!( - extracted.contains(&vec![1, 1, 1, 1, 0]), - "Expected (1,1,1,1,0) in {:?}", - extracted - ); - - // x=0, y=0 -> c=0, z=1 -> d=1 - assert!( - extracted.contains(&vec![0, 1, 0, 0, 1]), - "Expected (0,1,0,0,1) in {:?}", - extracted - ); - - // x=0, y=0 -> c=0, z=0 -> d=0 - assert!( - extracted.contains(&vec![0, 0, 0, 0, 0]), - "Expected (0,0,0,0,0) in {:?}", - extracted - ); -} - -#[test] -fn test_nested_expression() { - // c = (x AND y) OR z - let circuit = Circuit::new(vec![Assignment::new( - vec!["c".to_string()], - BooleanExpr::or(vec![ - BooleanExpr::and(vec![BooleanExpr::var("x"), BooleanExpr::var("y")]), - BooleanExpr::var("z"), - ]), - )]); - let problem = CircuitSAT::new(circuit); - let reduction = problem.reduce_to(); - let sg = reduction.target_problem(); - - let solver = BruteForce::new(); - let solutions = solver.find_all_best(sg); - - let extracted: Vec> = solutions - .iter() - .map(|s| reduction.extract_solution(s)) - .collect(); - - // Variables sorted: c, x, y, z - // c = (x AND y) OR z - - // x=1, y=1, z=0 -> c=1 - assert!( - extracted.contains(&vec![1, 1, 1, 0]), - "Expected (1,1,1,0) in {:?}", - extracted - ); - - // x=0, y=0, z=1 -> c=1 - assert!( - extracted.contains(&vec![1, 0, 0, 1]), - "Expected (1,0,0,1) in {:?}", - extracted - ); - - // x=0, y=0, z=0 -> c=0 - assert!( - extracted.contains(&vec![0, 0, 0, 0]), - "Expected (0,0,0,0) in {:?}", - extracted - ); -} - #[test] fn test_reduction_result_methods() { let circuit = Circuit::new(vec![Assignment::new( @@ -518,3 +272,27 @@ fn test_solution_extraction() { let sg = reduction.target_problem(); assert!(sg.num_spins() >= 3); // At least c, x, y } + +#[test] +fn test_jl_parity_circuitsat_to_spinglass() { + use crate::models::specialized::{Assignment, BooleanExpr, Circuit}; + let a = BooleanExpr::var("a"); + let b = BooleanExpr::var("b"); + let c = BooleanExpr::var("c"); + let x_expr = BooleanExpr::or(vec![a.clone(), BooleanExpr::not(b.clone())]); + let y_expr = BooleanExpr::or(vec![BooleanExpr::not(c.clone()), b.clone()]); + let z_expr = BooleanExpr::and(vec![BooleanExpr::var("x"), BooleanExpr::var("y"), a.clone()]); + let circuit = Circuit::new(vec![ + Assignment::new(vec!["x".to_string()], x_expr), + Assignment::new(vec!["y".to_string()], y_expr), + Assignment::new(vec!["z".to_string()], z_expr), + ]); + let source = CircuitSAT::new(circuit); + let result = ReduceTo::>::reduce_to(&source); + let solver = BruteForce::new(); + let best_target = solver.find_all_best(result.target_problem()); + let best_source = solver.find_all_satisfying(&source); + let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + let best_source_set: HashSet> = best_source.into_iter().collect(); + assert!(extracted.is_subset(&best_source_set), "CircuitSAT->SpinGlass: extracted not satisfying"); +} diff --git a/src/unit_tests/rules/factoring_circuit.rs b/src/unit_tests/rules/factoring_circuit.rs index 39abea60..38375b76 100644 --- a/src/unit_tests/rules/factoring_circuit.rs +++ b/src/unit_tests/rules/factoring_circuit.rs @@ -1,6 +1,8 @@ use super::*; +use crate::solvers::BruteForce; use crate::traits::Problem; use std::collections::HashMap; +include!("../jl_helpers.rs"); #[test] fn test_read_bit() { @@ -293,3 +295,20 @@ fn test_factorization_1_trivial() { "2 * 1 = 2 != 1 should not satisfy" ); } + +#[test] +fn test_jl_parity_factoring_to_circuitsat() { + let source = Factoring::new(1, 1, 1); + let result = ReduceTo::::reduce_to(&source); + let solver = BruteForce::new(); + let best_target = solver.find_all_satisfying(result.target_problem()); + for t in &best_target { + let sol = result.extract_solution(t); + assert_eq!(source.evaluate(&sol).unwrap(), 0, "Factoring extracted solution should be valid"); + } + let data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/factoring_to_circuitsat.json")).unwrap(); + let jl_best_source = jl_parse_configs_set(&data["cases"][0]["best_source"]); + let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect(); + assert_eq!(best_source, jl_best_source, "Factoring best source mismatch"); +} diff --git a/src/unit_tests/rules/maximumindependentset_maximumsetpacking.rs b/src/unit_tests/rules/maximumindependentset_maximumsetpacking.rs index 9c47e01e..dbaae629 100644 --- a/src/unit_tests/rules/maximumindependentset_maximumsetpacking.rs +++ b/src/unit_tests/rules/maximumindependentset_maximumsetpacking.rs @@ -1,73 +1,6 @@ use super::*; use crate::solvers::BruteForce; - -#[test] -fn test_is_to_setpacking() { - // Triangle graph - let is_problem = - MaximumIndependentSet::::new(3, vec![(0, 1), (1, 2), (0, 2)]); - let reduction = ReduceTo::>::reduce_to(&is_problem); - let sp_problem = reduction.target_problem(); - - let solver = BruteForce::new(); - let sp_solutions = solver.find_all_best(sp_problem); - - // Extract back - let is_solutions: Vec<_> = sp_solutions - .iter() - .map(|s| reduction.extract_solution(s)) - .collect(); - - // Max IS in triangle = 1 - for sol in &is_solutions { - let size: usize = sol.iter().sum(); - assert_eq!(size, 1); - } -} - -#[test] -fn test_setpacking_to_is() { - // Two disjoint sets and one overlapping - let sets = vec![ - vec![0, 1], - vec![2, 3], - vec![1, 2], // overlaps with both - ]; - let sp_problem = MaximumSetPacking::::new(sets); - let reduction: ReductionSPToIS = - ReduceTo::>::reduce_to(&sp_problem); - let is_problem = reduction.target_problem(); - - let solver = BruteForce::new(); - let is_solutions = solver.find_all_best(is_problem); - - // Max packing = 2 (sets 0 and 1) - for sol in &is_solutions { - let size: usize = sol.iter().sum(); - assert_eq!(size, 2); - } -} - -#[test] -fn test_roundtrip_is_sp_is() { - let original = MaximumIndependentSet::::new(4, vec![(0, 1), (1, 2), (2, 3)]); - let solver = BruteForce::new(); - let original_solutions = solver.find_all_best(&original); - - // IS -> SP -> IS - let reduction1 = ReduceTo::>::reduce_to(&original); - let sp = reduction1.target_problem().clone(); - let reduction2: ReductionSPToIS = - ReduceTo::>::reduce_to(&sp); - let roundtrip = reduction2.target_problem(); - - let roundtrip_solutions = solver.find_all_best(roundtrip); - - // Solutions should have same objective value - let orig_size: usize = original_solutions[0].iter().sum(); - let rt_size: usize = roundtrip_solutions[0].iter().sum(); - assert_eq!(orig_size, rt_size); -} +include!("../jl_helpers.rs"); #[test] fn test_weighted_reduction() { @@ -129,3 +62,83 @@ fn test_reduction_structure() { // IS should have same number of vertices as sets in SP assert_eq!(is.num_vertices(), 2); } + +#[test] +fn test_jl_parity_is_to_setpacking() { + let data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/independentset_to_setpacking.json")).unwrap(); + let is_data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/independentset.json")).unwrap(); + let inst = &is_data["instances"][0]["instance"]; + let source = MaximumIndependentSet::::new( + inst["num_vertices"].as_u64().unwrap() as usize, jl_parse_edges(inst)); + let result = ReduceTo::>::reduce_to(&source); + let solver = BruteForce::new(); + let best_target = solver.find_all_best(result.target_problem()); + let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect(); + let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + assert!(extracted.is_subset(&best_source)); + for case in data["cases"].as_array().unwrap() { + assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); + } +} + +#[test] +fn test_jl_parity_setpacking_to_is() { + let data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/setpacking_to_independentset.json")).unwrap(); + let sp_data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/setpacking.json")).unwrap(); + let inst = &sp_data["instances"][0]["instance"]; + let source = MaximumSetPacking::::new(jl_parse_sets(&inst["sets"])); + let result = ReduceTo::>::reduce_to(&source); + let solver = BruteForce::new(); + let best_target = solver.find_all_best(result.target_problem()); + let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect(); + let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + assert!(extracted.is_subset(&best_source)); + for case in data["cases"].as_array().unwrap() { + assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); + } +} + +#[test] +fn test_jl_parity_rule_is_to_setpacking() { + let data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/rule_independentset_to_setpacking.json")).unwrap(); + let is_data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/independentset.json")).unwrap(); + let inst = &jl_find_instance_by_label(&is_data, "doc_4vertex")["instance"]; + let source = MaximumIndependentSet::::new( + inst["num_vertices"].as_u64().unwrap() as usize, jl_parse_edges(inst)); + let result = ReduceTo::>::reduce_to(&source); + let solver = BruteForce::new(); + let best_target = solver.find_all_best(result.target_problem()); + let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect(); + let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + assert!(extracted.is_subset(&best_source)); + for case in data["cases"].as_array().unwrap() { + assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); + } +} + +#[test] +fn test_jl_parity_doc_is_to_setpacking() { + let data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/doc_independentset_to_setpacking.json")).unwrap(); + let is_data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/independentset.json")).unwrap(); + let is_instance = jl_find_instance_by_label(&is_data, "doc_4vertex"); + let inst = &is_instance["instance"]; + let source = MaximumIndependentSet::::new( + inst["num_vertices"].as_u64().unwrap() as usize, jl_parse_edges(inst)); + let result = ReduceTo::>::reduce_to(&source); + let solver = BruteForce::new(); + let best_target = solver.find_all_best(result.target_problem()); + let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect(); + let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + assert!(extracted.is_subset(&best_source)); + for case in data["cases"].as_array().unwrap() { + assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); + } +} diff --git a/src/unit_tests/rules/maximummatching_maximumsetpacking.rs b/src/unit_tests/rules/maximummatching_maximumsetpacking.rs index 173a02c5..ad5cc1a4 100644 --- a/src/unit_tests/rules/maximummatching_maximumsetpacking.rs +++ b/src/unit_tests/rules/maximummatching_maximumsetpacking.rs @@ -3,6 +3,7 @@ use crate::solvers::BruteForce; use crate::topology::SimpleGraph; use crate::traits::Problem; use crate::types::SolutionSize; +include!("../jl_helpers.rs"); #[test] fn test_matching_to_setpacking_structure() { @@ -20,51 +21,6 @@ fn test_matching_to_setpacking_structure() { assert_eq!(sets[1], vec![1, 2]); } -#[test] -fn test_matching_to_setpacking_path() { - // Path 0-1-2-3 with unit weights - let matching = MaximumMatching::::unweighted(4, vec![(0, 1), (1, 2), (2, 3)]); - let reduction = ReduceTo::>::reduce_to(&matching); - let sp = reduction.target_problem(); - - let solver = BruteForce::new(); - let sp_solutions = solver.find_all_best(sp); - - // Extract back to MaximumMatching solutions - let _matching_solutions: Vec<_> = sp_solutions - .iter() - .map(|s| reduction.extract_solution(s)) - .collect(); - - // Verify against direct MaximumMatching solution - let direct_solutions = solver.find_all_best(&matching); - - // Solutions should have same objective value - let sp_size: usize = sp_solutions[0].iter().sum(); - let direct_size: usize = direct_solutions[0].iter().sum(); - assert_eq!(sp_size, direct_size); - assert_eq!(sp_size, 2); // Max matching in path graph has 2 edges -} - -#[test] -fn test_matching_to_setpacking_triangle() { - // Triangle graph - let matching = MaximumMatching::::unweighted(3, vec![(0, 1), (1, 2), (0, 2)]); - let reduction = ReduceTo::>::reduce_to(&matching); - let sp = reduction.target_problem(); - - let solver = BruteForce::new(); - let sp_solutions = solver.find_all_best(sp); - - // Max matching in triangle = 1 (any single edge) - for sol in &sp_solutions { - assert_eq!(sol.iter().sum::(), 1); - } - - // Should have 3 optimal solutions (one for each edge) - assert_eq!(sp_solutions.len(), 3); -} - #[test] fn test_matching_to_setpacking_weighted() { // Weighted edges: heavy edge should win over multiple light edges @@ -108,27 +64,6 @@ fn test_matching_to_setpacking_solution_extraction() { assert!(matching.evaluate(&matching_solution).is_valid()); } -#[test] -fn test_matching_to_setpacking_k4() { - // Complete graph K4: can have perfect matching (2 edges covering all 4 vertices) - let matching = MaximumMatching::::unweighted( - 4, - vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)], - ); - let reduction = ReduceTo::>::reduce_to(&matching); - let sp = reduction.target_problem(); - - let solver = BruteForce::new(); - let sp_solutions = solver.find_all_best(sp); - let direct_solutions = solver.find_all_best(&matching); - - // Both should find matchings of size 2 - let sp_size: usize = sp_solutions[0].iter().sum(); - let direct_size: usize = direct_solutions[0].iter().sum(); - assert_eq!(sp_size, 2); - assert_eq!(direct_size, 2); -} - #[test] fn test_matching_to_setpacking_empty() { // Graph with no edges @@ -196,3 +131,30 @@ fn test_matching_to_setpacking_star() { // Should have 3 optimal solutions assert_eq!(sp_solutions.len(), 3); } + +#[test] +fn test_jl_parity_matching_to_setpacking() { + let match_data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/matching.json")).unwrap(); + let fixtures: &[(&str, &str)] = &[ + (include_str!("../../../tests/data/jl/matching_to_setpacking.json"), "petersen"), + (include_str!("../../../tests/data/jl/rule_matching_to_setpacking.json"), "rule_4vertex"), + (include_str!("../../../tests/data/jl/rule_matchingw_to_setpacking.json"), "rule_4vertex_weighted"), + ]; + for (fixture_str, label) in fixtures { + let data: serde_json::Value = serde_json::from_str(fixture_str).unwrap(); + let inst = &jl_find_instance_by_label(&match_data, label)["instance"]; + let source = MaximumMatching::::new( + inst["num_vertices"].as_u64().unwrap() as usize, jl_parse_weighted_edges(inst)); + let result = ReduceTo::>::reduce_to(&source); + let solver = BruteForce::new(); + let best_target = solver.find_all_best(result.target_problem()); + let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect(); + let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + assert!(extracted.is_subset(&best_source), "Matching->SP [{label}]: extracted not subset"); + for case in data["cases"].as_array().unwrap() { + assert_eq!(best_source, jl_parse_configs_set(&case["best_source"]), + "Matching->SP [{label}]: best source mismatch"); + } + } +} diff --git a/src/unit_tests/rules/minimumvertexcover_maximumindependentset.rs b/src/unit_tests/rules/minimumvertexcover_maximumindependentset.rs index 39658724..e5121865 100644 --- a/src/unit_tests/rules/minimumvertexcover_maximumindependentset.rs +++ b/src/unit_tests/rules/minimumvertexcover_maximumindependentset.rs @@ -1,72 +1,6 @@ use super::*; use crate::solvers::BruteForce; - -#[test] -fn test_is_to_vc_reduction() { - // Triangle graph: max IS = 1, min VC = 2 - let is_problem = - MaximumIndependentSet::::new(3, vec![(0, 1), (1, 2), (0, 2)]); - let reduction = ReduceTo::>::reduce_to(&is_problem); - let vc_problem = reduction.target_problem(); - - // Solve the VC problem - let solver = BruteForce::new(); - let vc_solutions = solver.find_all_best(vc_problem); - - // Extract back to IS solutions - let is_solutions: Vec<_> = vc_solutions - .iter() - .map(|s| reduction.extract_solution(s)) - .collect(); - - // Verify IS solutions are valid and optimal - for sol in &is_solutions { - let size: usize = sol.iter().sum(); - assert_eq!(size, 1, "Max IS in triangle should be 1"); - } -} - -#[test] -fn test_vc_to_is_reduction() { - // Path graph 0-1-2: min VC = 1 (just vertex 1), max IS = 2 (vertices 0 and 2) - let vc_problem = MinimumVertexCover::::new(3, vec![(0, 1), (1, 2)]); - let reduction = ReduceTo::>::reduce_to(&vc_problem); - let is_problem = reduction.target_problem(); - - let solver = BruteForce::new(); - let is_solutions = solver.find_all_best(is_problem); - - let vc_solutions: Vec<_> = is_solutions - .iter() - .map(|s| reduction.extract_solution(s)) - .collect(); - - // Verify VC solutions - for sol in &vc_solutions { - let size: usize = sol.iter().sum(); - assert_eq!(size, 1, "Min VC in path should be 1"); - } -} - -#[test] -fn test_roundtrip_is_vc_is() { - let original = MaximumIndependentSet::::new(4, vec![(0, 1), (1, 2), (2, 3)]); - let solver = BruteForce::new(); - let original_solutions = solver.find_all_best(&original); - - // IS -> VC -> IS - let reduction1 = ReduceTo::>::reduce_to(&original); - let vc = reduction1.target_problem().clone(); - let reduction2 = ReduceTo::>::reduce_to(&vc); - let roundtrip = reduction2.target_problem(); - - let roundtrip_solutions = solver.find_all_best(roundtrip); - - // Solutions should have same objective value - let orig_size: usize = original_solutions[0].iter().sum(); - let rt_size: usize = roundtrip_solutions[0].iter().sum(); - assert_eq!(orig_size, rt_size); -} +include!("../jl_helpers.rs"); #[test] fn test_weighted_reduction() { @@ -89,3 +23,43 @@ fn test_reduction_structure() { // Same number of vertices in both problems assert_eq!(vc.num_vertices(), 5); } + +#[test] +fn test_jl_parity_is_to_vertexcovering() { + let data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/independentset_to_vertexcovering.json")).unwrap(); + let is_data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/independentset.json")).unwrap(); + let inst = &is_data["instances"][0]["instance"]; + let source = MaximumIndependentSet::::new( + inst["num_vertices"].as_u64().unwrap() as usize, jl_parse_edges(inst)); + let result = ReduceTo::>::reduce_to(&source); + let solver = BruteForce::new(); + let best_target = solver.find_all_best(result.target_problem()); + let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect(); + let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + assert!(extracted.is_subset(&best_source)); + for case in data["cases"].as_array().unwrap() { + assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); + } +} + +#[test] +fn test_jl_parity_rule_is_to_vertexcovering() { + let data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/rule2_independentset_to_vertexcovering.json")).unwrap(); + let is_data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/independentset.json")).unwrap(); + let inst = &jl_find_instance_by_label(&is_data, "doc_4vertex")["instance"]; + let source = MaximumIndependentSet::::new( + inst["num_vertices"].as_u64().unwrap() as usize, jl_parse_edges(inst)); + let result = ReduceTo::>::reduce_to(&source); + let solver = BruteForce::new(); + let best_target = solver.find_all_best(result.target_problem()); + let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect(); + let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + assert!(extracted.is_subset(&best_source)); + for case in data["cases"].as_array().unwrap() { + assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); + } +} diff --git a/src/unit_tests/rules/minimumvertexcover_minimumsetcovering.rs b/src/unit_tests/rules/minimumvertexcover_minimumsetcovering.rs index 4af15ba5..0a8d5143 100644 --- a/src/unit_tests/rules/minimumvertexcover_minimumsetcovering.rs +++ b/src/unit_tests/rules/minimumvertexcover_minimumsetcovering.rs @@ -1,5 +1,6 @@ use super::*; use crate::solvers::BruteForce; +include!("../jl_helpers.rs"); #[test] fn test_vc_to_sc_basic() { @@ -41,59 +42,6 @@ fn test_vc_to_sc_triangle() { } } -#[test] -fn test_vc_to_sc_solution_extraction() { - use crate::traits::Problem; - - let vc_problem = MinimumVertexCover::::new(3, vec![(0, 1), (1, 2)]); - let reduction = ReduceTo::>::reduce_to(&vc_problem); - let sc_problem = reduction.target_problem(); - - // Solve the MinimumSetCovering problem - let solver = BruteForce::new(); - let sc_solutions = solver.find_all_best(sc_problem); - - // Extract solutions back to MinimumVertexCover - let vc_solutions: Vec<_> = sc_solutions - .iter() - .map(|s| reduction.extract_solution(s)) - .collect(); - - // Verify extracted solutions are valid vertex covers - for sol in &vc_solutions { - // Check that the solution evaluates to a valid value (not i32::MAX for invalid) - let eval = vc_problem.evaluate(sol); - assert!(eval.is_valid()); - } - - // The minimum should be selecting just vertex 1 (covers both edges) - let min_size: usize = vc_solutions[0].iter().sum(); - assert_eq!(min_size, 1); -} - -#[test] -fn test_vc_to_sc_optimality_preservation() { - // Test that optimal solutions are preserved through reduction - let vc_problem = MinimumVertexCover::::new(4, vec![(0, 1), (1, 2), (2, 3)]); - let solver = BruteForce::new(); - - // Solve VC directly - let direct_solutions = solver.find_all_best(&vc_problem); - let direct_size = direct_solutions[0].iter().sum::(); - - // Solve via reduction - let reduction = ReduceTo::>::reduce_to(&vc_problem); - let sc_solutions = solver.find_all_best(reduction.target_problem()); - let reduced_solutions: Vec<_> = sc_solutions - .iter() - .map(|s| reduction.extract_solution(s)) - .collect(); - let reduced_size = reduced_solutions[0].iter().sum::(); - - // Optimal sizes should match - assert_eq!(direct_size, reduced_size); -} - #[test] fn test_vc_to_sc_weighted() { // Weighted problem: weights should be preserved @@ -152,25 +100,41 @@ fn test_vc_to_sc_star_graph() { } #[test] -fn test_vc_to_sc_all_solutions_valid() { - use crate::traits::Problem; - - // Ensure all solutions extracted from SC are valid VC solutions - let vc_problem = - MinimumVertexCover::::new(4, vec![(0, 1), (1, 2), (0, 2), (2, 3)]); - let reduction = ReduceTo::>::reduce_to(&vc_problem); - let sc_problem = reduction.target_problem(); - +fn test_jl_parity_vc_to_setcovering() { + let data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/vertexcovering_to_setcovering.json")).unwrap(); + let vc_data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/vertexcovering.json")).unwrap(); + let inst = &vc_data["instances"][0]["instance"]; + let source = MinimumVertexCover::with_weights( + inst["num_vertices"].as_u64().unwrap() as usize, jl_parse_edges(inst), jl_parse_i32_vec(&inst["weights"])); + let result = ReduceTo::>::reduce_to(&source); let solver = BruteForce::new(); - let sc_solutions = solver.find_all_best(sc_problem); + let best_target = solver.find_all_best(result.target_problem()); + let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect(); + let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + assert!(extracted.is_subset(&best_source)); + for case in data["cases"].as_array().unwrap() { + assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); + } +} - for sc_sol in &sc_solutions { - let vc_sol = reduction.extract_solution(sc_sol); - let eval = vc_problem.evaluate(&vc_sol); - assert!( - eval.is_valid(), - "Extracted solution {:?} should be valid", - vc_sol - ); +#[test] +fn test_jl_parity_rule_vc_to_setcovering() { + let data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/rule_vertexcovering_to_setcovering.json")).unwrap(); + let vc_data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/vertexcovering.json")).unwrap(); + let inst = &jl_find_instance_by_label(&vc_data, "rule_4vertex")["instance"]; + let source = MinimumVertexCover::with_weights( + inst["num_vertices"].as_u64().unwrap() as usize, jl_parse_edges(inst), jl_parse_i32_vec(&inst["weights"])); + let result = ReduceTo::>::reduce_to(&source); + let solver = BruteForce::new(); + let best_target = solver.find_all_best(result.target_problem()); + let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect(); + let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + assert!(extracted.is_subset(&best_source)); + for case in data["cases"].as_array().unwrap() { + assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); } } diff --git a/src/unit_tests/rules/sat_coloring.rs b/src/unit_tests/rules/sat_coloring.rs index 87972423..6b2d7319 100644 --- a/src/unit_tests/rules/sat_coloring.rs +++ b/src/unit_tests/rules/sat_coloring.rs @@ -1,6 +1,7 @@ use super::*; use crate::models::satisfiability::CNFClause; use crate::solvers::BruteForce; +include!("../jl_helpers.rs"); #[test] fn test_constructor_basic_structure() { @@ -61,43 +62,6 @@ fn test_reduction_structure() { assert_eq!(reduction.neg_vertices().len(), 2); } -#[test] -fn test_unsatisfiable_formula() { - // Unsatisfiable: (x1) AND (NOT x1) - let sat = Satisfiability::new(1, vec![CNFClause::new(vec![1]), CNFClause::new(vec![-1])]); - - let reduction = ReduceTo::>::reduce_to(&sat); - let coloring = reduction.target_problem(); - - // Solve the coloring problem - use find_all_satisfying since KColoring is a satisfaction problem - let solver = BruteForce::new(); - let solutions = solver.find_all_satisfying(coloring); - - // For an unsatisfiable formula, the coloring should have no valid solutions - // OR no valid coloring exists that extracts to a satisfying SAT assignment - let mut found_satisfying = false; - for sol in &solutions { - let sat_sol = reduction.extract_solution(sol); - let assignment: Vec = sat_sol.iter().map(|&v| v == 1).collect(); - if sat.is_satisfying(&assignment) { - found_satisfying = true; - break; - } - } - - // The coloring should not yield a satisfying SAT assignment - // because the formula is unsatisfiable - // Note: The coloring graph itself may still be colorable, - // but the constraints should make it impossible for both - // x1 and NOT x1 to be TRUE color simultaneously - // Actually, let's check if ANY coloring solution produces a valid SAT solution - // If the formula is unsat, no valid coloring should extract to a satisfying assignment - assert!( - !found_satisfying, - "Unsatisfiable formula should not produce satisfying assignment" - ); -} - #[test] fn test_three_literal_clause_structure() { // (x1 OR x2 OR x3) @@ -177,32 +141,6 @@ fn test_complex_formula_structure() { assert_eq!(reduction.num_clauses(), 3); } -#[test] -fn test_single_literal_clauses() { - // (x1) AND (x2) - both must be true - let sat = Satisfiability::new(2, vec![CNFClause::new(vec![1]), CNFClause::new(vec![2])]); - - let reduction = ReduceTo::>::reduce_to(&sat); - let coloring = reduction.target_problem(); - - let solver = BruteForce::new(); - let solutions = solver.find_all_satisfying(coloring); - - let mut found_correct = false; - for sol in &solutions { - let sat_sol = reduction.extract_solution(sol); - if sat_sol == vec![1, 1] { - found_correct = true; - break; - } - } - - assert!( - found_correct, - "Should find solution where both x1 and x2 are true" - ); -} - #[test] fn test_empty_sat() { // Empty SAT (trivially satisfiable) @@ -293,3 +231,31 @@ fn test_extraction_with_different_color_assignment() { let extracted2 = reduction.extract_solution(&coloring_permuted2); assert_eq!(extracted2, vec![1]); } + +#[test] +fn test_jl_parity_sat_to_coloring() { + let sat_data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/satisfiability.json")).unwrap(); + let fixtures: &[(&str, &str)] = &[ + (include_str!("../../../tests/data/jl/satisfiability_to_coloring3.json"), "simple_clause"), + (include_str!("../../../tests/data/jl/rule_satisfiability2_to_coloring3.json"), "rule_sat_coloring"), + ]; + for (fixture_str, label) in fixtures { + let data: serde_json::Value = serde_json::from_str(fixture_str).unwrap(); + let inst = &jl_find_instance_by_label(&sat_data, label)["instance"]; + let (num_vars, clauses) = jl_parse_sat_clauses(inst); + let source = Satisfiability::new(num_vars, clauses); + let result = ReduceTo::>::reduce_to(&source); + let ilp_solver = crate::solvers::ILPSolver::new(); + let target = result.target_problem(); + let target_sol = ilp_solver.solve_reduced(target).expect("ILP should find a coloring"); + let extracted = result.extract_solution(&target_sol); + let best_source: HashSet> = BruteForce::new() + .find_all_satisfying(&source).into_iter().collect(); + assert!(best_source.contains(&extracted), "SAT->Coloring [{label}]: extracted not satisfying"); + for case in data["cases"].as_array().unwrap() { + assert_eq!(best_source, jl_parse_configs_set(&case["best_source"]), + "SAT->Coloring [{label}]: best source mismatch"); + } + } +} diff --git a/src/unit_tests/rules/sat_ksat.rs b/src/unit_tests/rules/sat_ksat.rs index 137919a6..e08621c8 100644 --- a/src/unit_tests/rules/sat_ksat.rs +++ b/src/unit_tests/rules/sat_ksat.rs @@ -1,6 +1,6 @@ use super::*; use crate::solvers::BruteForce; -use crate::traits::Problem; +include!("../jl_helpers.rs"); #[test] fn test_sat_to_3sat_exact_size() { @@ -99,63 +99,6 @@ fn test_sat_to_3sat_single_literal() { } } -#[test] -fn test_sat_to_3sat_preserves_satisfiability() { - // Create a SAT formula and verify the 3-SAT version is equisatisfiable - let sat = Satisfiability::new( - 3, - vec![ - CNFClause::new(vec![1, 2]), // Needs padding - CNFClause::new(vec![-1, 2, 3]), // Already 3 literals - CNFClause::new(vec![1, -2, 3, -3]), // Needs splitting (tautology for testing) - ], - ); - - let reduction = ReduceTo::>::reduce_to(&sat); - let ksat = reduction.target_problem(); - - // Solve both problems - use find_all_satisfying for satisfaction problems - let solver = BruteForce::new(); - - let sat_solutions = solver.find_all_satisfying(&sat); - let ksat_solutions = solver.find_all_satisfying(ksat); - - // If SAT is satisfiable, K-SAT should be too - let sat_satisfiable = !sat_solutions.is_empty(); - let ksat_satisfiable = !ksat_solutions.is_empty(); - - assert_eq!(sat_satisfiable, ksat_satisfiable); - - // Extract solutions should map back correctly - if ksat_satisfiable { - for ksat_sol in &ksat_solutions { - let sat_sol = reduction.extract_solution(ksat_sol); - assert_eq!(sat_sol.len(), 3); // Original variable count - } - } -} - -#[test] -fn test_sat_to_3sat_solution_extraction() { - let sat = Satisfiability::new(2, vec![CNFClause::new(vec![1, 2])]); - - let reduction = ReduceTo::>::reduce_to(&sat); - let ksat = reduction.target_problem(); - - // Solve K-SAT - use find_all_satisfying for satisfaction problems - let solver = BruteForce::new(); - let ksat_solutions = solver.find_all_satisfying(ksat); - - // Extract and verify solutions - for ksat_sol in &ksat_solutions { - let sat_sol = reduction.extract_solution(ksat_sol); - // Should only have original 2 variables - assert_eq!(sat_sol.len(), 2); - // Should satisfy original problem - assert!(sat.evaluate(&sat_sol)); - } -} - #[test] fn test_3sat_to_sat() { let ksat = KSatisfiability::<3>::new( @@ -188,35 +131,6 @@ fn test_3sat_to_sat_solution_extraction() { assert_eq!(extracted, vec![1, 0, 1]); } -#[test] -fn test_roundtrip_sat_3sat_sat() { - // SAT -> 3-SAT -> SAT roundtrip - let original_sat = Satisfiability::new( - 3, - vec![CNFClause::new(vec![1, -2]), CNFClause::new(vec![2, 3])], - ); - - // SAT -> 3-SAT - let to_ksat = ReduceTo::>::reduce_to(&original_sat); - let ksat = to_ksat.target_problem(); - - // 3-SAT -> SAT - let to_sat = ReduceTo::::reduce_to(ksat); - let final_sat = to_sat.target_problem(); - - // Solve all three - use find_all_satisfying for satisfaction problems - let solver = BruteForce::new(); - - let orig_solutions = solver.find_all_satisfying(&original_sat); - let ksat_solutions = solver.find_all_satisfying(ksat); - let final_solutions = solver.find_all_satisfying(final_sat); - - // All should be satisfiable (have at least one solution) - assert!(!orig_solutions.is_empty()); - assert!(!ksat_solutions.is_empty()); - assert!(!final_solutions.is_empty()); -} - #[test] fn test_sat_to_3sat_mixed_clause_types() { // Test padding, exact-size, and splitting all at once @@ -262,53 +176,61 @@ fn test_empty_sat_to_3sat() { } #[test] -fn test_mixed_clause_sizes() { - let sat = Satisfiability::new( - 5, - vec![ - CNFClause::new(vec![1]), // 1 literal - CNFClause::new(vec![2, 3]), // 2 literals - CNFClause::new(vec![1, 2, 3]), // 3 literals - CNFClause::new(vec![1, 2, 3, 4]), // 4 literals - CNFClause::new(vec![1, 2, 3, 4, 5]), // 5 literals - ], - ); - - let reduction = ReduceTo::>::reduce_to(&sat); - let ksat = reduction.target_problem(); - - // All clauses should have exactly 3 literals - for clause in ksat.clauses() { - assert_eq!(clause.len(), 3); +fn test_jl_parity_sat_to_ksat() { + let data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/satisfiability_to_ksatisfiability3.json")).unwrap(); + let sat_data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/satisfiability.json")).unwrap(); + let inst = &sat_data["instances"][0]["instance"]; + let (num_vars, clauses) = jl_parse_sat_clauses(inst); + let source = Satisfiability::new(num_vars, clauses); + let result = ReduceTo::>::reduce_to(&source); + let solver = BruteForce::new(); + let best_target = solver.find_all_satisfying(result.target_problem()); + let best_source: HashSet> = solver.find_all_satisfying(&source).into_iter().collect(); + let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + assert!(extracted.is_subset(&best_source)); + for case in data["cases"].as_array().unwrap() { + assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); } +} - // Verify satisfiability is preserved - use find_all_satisfying for satisfaction problems +#[test] +fn test_jl_parity_ksat_to_sat() { + let data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/ksatisfiability_to_satisfiability.json")).unwrap(); + let ksat_data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/ksatisfiability.json")).unwrap(); + let inst = &ksat_data["instances"][0]["instance"]; + let (num_vars, clauses) = jl_parse_sat_clauses(inst); + let source = KSatisfiability::<3>::new(num_vars, clauses); + let result = ReduceTo::::reduce_to(&source); let solver = BruteForce::new(); - let sat_solutions = solver.find_all_satisfying(&sat); - let ksat_solutions = solver.find_all_satisfying(ksat); - - let sat_satisfiable = !sat_solutions.is_empty(); - let ksat_satisfiable = !ksat_solutions.is_empty(); - assert_eq!(sat_satisfiable, ksat_satisfiable); + let best_target = solver.find_all_satisfying(result.target_problem()); + let best_source: HashSet> = solver.find_all_satisfying(&source).into_iter().collect(); + let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + assert!(extracted.is_subset(&best_source)); + for case in data["cases"].as_array().unwrap() { + assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); + } } #[test] -fn test_unsatisfiable_formula() { - // (x) AND (-x) is unsatisfiable - let sat = Satisfiability::new(1, vec![CNFClause::new(vec![1]), CNFClause::new(vec![-1])]); - - let reduction = ReduceTo::>::reduce_to(&sat); - let ksat = reduction.target_problem(); - +fn test_jl_parity_rule_sat_to_ksat() { + let data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/rule_satisfiability_to_ksatisfiability3.json")).unwrap(); + let sat_data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/satisfiability.json")).unwrap(); + let inst = &jl_find_instance_by_label(&sat_data, "rule_3sat_multi")["instance"]; + let (num_vars, clauses) = jl_parse_sat_clauses(inst); + let source = Satisfiability::new(num_vars, clauses); + let result = ReduceTo::>::reduce_to(&source); let solver = BruteForce::new(); - - // Both should be unsatisfiable - use find_all_satisfying for satisfaction problems - let sat_solutions = solver.find_all_satisfying(&sat); - let ksat_solutions = solver.find_all_satisfying(ksat); - - let sat_satisfiable = !sat_solutions.is_empty(); - let ksat_satisfiable = !ksat_solutions.is_empty(); - - assert!(!sat_satisfiable); - assert!(!ksat_satisfiable); + let best_target = solver.find_all_satisfying(result.target_problem()); + let best_source: HashSet> = solver.find_all_satisfying(&source).into_iter().collect(); + let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + assert!(extracted.is_subset(&best_source)); + for case in data["cases"].as_array().unwrap() { + assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); + } } diff --git a/src/unit_tests/rules/sat_maximumindependentset.rs b/src/unit_tests/rules/sat_maximumindependentset.rs index aa6e6f24..3d32ce6d 100644 --- a/src/unit_tests/rules/sat_maximumindependentset.rs +++ b/src/unit_tests/rules/sat_maximumindependentset.rs @@ -1,6 +1,8 @@ use super::*; use crate::models::satisfiability::CNFClause; use crate::solvers::BruteForce; +use crate::traits::Problem; +include!("../jl_helpers.rs"); #[test] fn test_boolvar_creation() { @@ -72,107 +74,6 @@ fn test_two_clause_sat_to_is() { } } -#[test] -fn test_satisfiable_formula() { - // SAT: (x1 OR x2) AND (NOT x1 OR x2) AND (x1 OR NOT x2) - // Satisfiable with x1=true, x2=true or x1=false, x2=true - let sat = Satisfiability::new( - 2, - vec![ - CNFClause::new(vec![1, 2]), // x1 OR x2 - CNFClause::new(vec![-1, 2]), // NOT x1 OR x2 - CNFClause::new(vec![1, -2]), // x1 OR NOT x2 - ], - ); - let reduction = ReduceTo::>::reduce_to(&sat); - let is_problem = reduction.target_problem(); - - // Should have 6 vertices (2 literals per clause, 3 clauses) - assert_eq!(is_problem.num_vertices(), 6); - - // Count edges: - // - 3 edges within clauses (one per clause, since each clause has 2 literals) - // - Edges between complementary literals across clauses: - // - x1 (clause 0, vertex 0) and NOT x1 (clause 1, vertex 2) - // - x2 (clause 0, vertex 1) and NOT x2 (clause 2, vertex 5) - // - x2 (clause 1, vertex 3) and NOT x2 (clause 2, vertex 5) - // - x1 (clause 2, vertex 4) and NOT x1 (clause 1, vertex 2) - // Total: 3 (clique) + 4 (complement) = 7 edges - - // Solve the IS problem - let solver = BruteForce::new(); - let is_solutions = solver.find_all_best(is_problem); - - // Max IS should be 3 (one literal per clause) - for sol in &is_solutions { - assert_eq!(sol.iter().sum::(), 3); - } - - // Extract SAT solutions and verify they satisfy the original formula - for sol in &is_solutions { - let sat_sol = reduction.extract_solution(sol); - let assignment: Vec = sat_sol.iter().map(|&v| v == 1).collect(); - assert!( - sat.is_satisfying(&assignment), - "Extracted solution {:?} should satisfy the SAT formula", - assignment - ); - } -} - -#[test] -fn test_unsatisfiable_formula() { - // SAT: (x1) AND (NOT x1) - unsatisfiable - let sat = Satisfiability::new(1, vec![CNFClause::new(vec![1]), CNFClause::new(vec![-1])]); - let reduction = ReduceTo::>::reduce_to(&sat); - let is_problem = reduction.target_problem(); - - let solver = BruteForce::new(); - let is_solutions = solver.find_all_best(is_problem); - - // Max IS can only be 1 (not 2 = num_clauses) - // This indicates the formula is unsatisfiable - for sol in &is_solutions { - assert!( - sol.iter().sum::() < reduction.num_clauses(), - "For unsatisfiable formula, IS size should be less than num_clauses" - ); - } -} - -#[test] -fn test_three_sat_example() { - // 3-SAT: (x1 OR x2 OR x3) AND (NOT x1 OR NOT x2 OR x3) AND (x1 OR NOT x2 OR NOT x3) - let sat = Satisfiability::new( - 3, - vec![ - CNFClause::new(vec![1, 2, 3]), // x1 OR x2 OR x3 - CNFClause::new(vec![-1, -2, 3]), // NOT x1 OR NOT x2 OR x3 - CNFClause::new(vec![1, -2, -3]), // x1 OR NOT x2 OR NOT x3 - ], - ); - - let reduction = ReduceTo::>::reduce_to(&sat); - let is_problem = reduction.target_problem(); - - // Should have 9 vertices (3 literals per clause, 3 clauses) - assert_eq!(is_problem.num_vertices(), 9); - - let solver = BruteForce::new(); - let is_solutions = solver.find_all_best(is_problem); - - // Check that max IS has size 3 (satisfiable) - let max_size = is_solutions[0].iter().sum::(); - assert_eq!(max_size, 3, "3-SAT should be satisfiable with IS size = 3"); - - // Verify extracted solutions - for sol in &is_solutions { - let sat_sol = reduction.extract_solution(sol); - let assignment: Vec = sat_sol.iter().map(|&v| v == 1).collect(); - assert!(sat.is_satisfying(&assignment)); - } -} - #[test] fn test_extract_solution_basic() { // Simple case: (x1 OR x2) @@ -258,43 +159,6 @@ fn test_empty_sat() { assert_eq!(reduction.num_clauses(), 0); } -#[test] -fn test_sat_is_solution_correspondence() { - // Comprehensive test: solve both SAT and IS, compare solutions - let sat = Satisfiability::new( - 2, - vec![CNFClause::new(vec![1, 2]), CNFClause::new(vec![-1, -2])], - ); - - // Solve SAT directly - use find_all_satisfying for satisfaction problems - let sat_solver = BruteForce::new(); - let direct_sat_solutions = sat_solver.find_all_satisfying(&sat); - - // Solve via reduction (IS is an optimization problem, so use find_best) - let reduction = ReduceTo::>::reduce_to(&sat); - let is_problem = reduction.target_problem(); - let is_solutions = sat_solver.find_all_best(is_problem); - - // Extract SAT solutions from IS - let extracted_sat_solutions: Vec<_> = is_solutions - .iter() - .map(|s| reduction.extract_solution(s)) - .collect(); - - // All extracted solutions should be valid SAT solutions - for sol in &extracted_sat_solutions { - let assignment: Vec = sol.iter().map(|&v| v == 1).collect(); - assert!(sat.is_satisfying(&assignment)); - } - - // Direct SAT solutions and extracted solutions should be compatible - // (same satisfying assignments, though representation might differ) - for sol in &direct_sat_solutions { - let assignment: Vec = sol.iter().map(|&v| v == 1).collect(); - assert!(sat.is_satisfying(&assignment)); - } -} - #[test] fn test_literals_accessor() { let sat = Satisfiability::new(2, vec![CNFClause::new(vec![1, -2])]); @@ -305,3 +169,39 @@ fn test_literals_accessor() { assert_eq!(literals[0], BoolVar::new(0, false)); // x1 assert_eq!(literals[1], BoolVar::new(1, true)); // NOT x2 } + +#[test] +fn test_jl_parity_sat_to_independentset() { + let sat_data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/satisfiability.json")).unwrap(); + let fixtures: &[(&str, &str)] = &[ + (include_str!("../../../tests/data/jl/satisfiability_to_independentset.json"), "simple_clause"), + (include_str!("../../../tests/data/jl/rule_sat01_to_independentset.json"), "rule_sat01"), + (include_str!("../../../tests/data/jl/rule_sat02_to_independentset.json"), "rule_sat02"), + (include_str!("../../../tests/data/jl/rule_sat03_to_independentset.json"), "rule_sat03"), + (include_str!("../../../tests/data/jl/rule_sat04_unsat_to_independentset.json"), "rule_sat04_unsat"), + (include_str!("../../../tests/data/jl/rule_sat07_to_independentset.json"), "rule_sat07"), + ]; + for (fixture_str, label) in fixtures { + let data: serde_json::Value = serde_json::from_str(fixture_str).unwrap(); + let inst = &jl_find_instance_by_label(&sat_data, label)["instance"]; + let (num_vars, clauses) = jl_parse_sat_clauses(inst); + let source = Satisfiability::new(num_vars, clauses); + let result = ReduceTo::>::reduce_to(&source); + let solver = BruteForce::new(); + let best_target = solver.find_all_best(result.target_problem()); + let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + let sat_solutions: HashSet> = solver.find_all_satisfying(&source).into_iter().collect(); + for case in data["cases"].as_array().unwrap() { + if sat_solutions.is_empty() { + for sol in &extracted { + assert!(!source.evaluate(sol), "SAT->IS [{label}]: unsatisfiable but extracted satisfies"); + } + } else { + assert!(extracted.is_subset(&sat_solutions), "SAT->IS [{label}]: extracted not subset"); + assert_eq!(sat_solutions, jl_parse_configs_set(&case["best_source"]), + "SAT->IS [{label}]: best source mismatch"); + } + } + } +} diff --git a/src/unit_tests/rules/sat_minimumdominatingset.rs b/src/unit_tests/rules/sat_minimumdominatingset.rs index b7824a3d..aa58799c 100644 --- a/src/unit_tests/rules/sat_minimumdominatingset.rs +++ b/src/unit_tests/rules/sat_minimumdominatingset.rs @@ -1,6 +1,8 @@ use super::*; use crate::models::satisfiability::CNFClause; use crate::solvers::BruteForce; +use crate::traits::Problem; +include!("../jl_helpers.rs"); #[test] fn test_simple_sat_to_ds() { @@ -35,119 +37,6 @@ fn test_two_variable_sat_to_ds() { assert_eq!(ds_problem.num_edges(), 8); } -#[test] -fn test_satisfiable_formula() { - // SAT: (x1 OR x2) AND (NOT x1 OR x2) - // Satisfiable with x2 = true - let sat = Satisfiability::new( - 2, - vec![ - CNFClause::new(vec![1, 2]), // x1 OR x2 - CNFClause::new(vec![-1, 2]), // NOT x1 OR x2 - ], - ); - let reduction = ReduceTo::>::reduce_to(&sat); - let ds_problem = reduction.target_problem(); - - // Solve the dominating set problem - let solver = BruteForce::new(); - let solutions = solver.find_all_best(ds_problem); - - // Minimum dominating set should be of size 2 (one per variable) - let min_size = solutions[0].iter().sum::(); - assert_eq!(min_size, 2, "Minimum dominating set should have 2 vertices"); - - // Extract and verify at least one solution satisfies SAT - let mut found_satisfying = false; - for sol in &solutions { - let sat_sol = reduction.extract_solution(sol); - let assignment: Vec = sat_sol.iter().map(|&v| v == 1).collect(); - if sat.is_satisfying(&assignment) { - found_satisfying = true; - break; - } - } - assert!(found_satisfying, "Should find a satisfying assignment"); -} - -#[test] -fn test_unsatisfiable_formula() { - // SAT: (x1) AND (NOT x1) - unsatisfiable - let sat = Satisfiability::new(1, vec![CNFClause::new(vec![1]), CNFClause::new(vec![-1])]); - let reduction = ReduceTo::>::reduce_to(&sat); - let ds_problem = reduction.target_problem(); - - // Vertices: 3 (gadget) + 2 (clauses) = 5 - assert_eq!(ds_problem.num_vertices(), 5); - - let solver = BruteForce::new(); - let solutions = solver.find_all_best(ds_problem); - - // For unsatisfiable formula, the minimum dominating set will need - // more than num_variables vertices OR won't produce a valid assignment - // Actually, in this case we can still dominate with just selecting - // one literal vertex (it dominates its gadget AND one clause), - // but then the other clause isn't dominated. - // So we need at least 2 vertices: one for each clause's requirement. - - // The key insight is that both clauses share the same variable gadget - // but require opposite literals. To dominate both clause vertices, - // we need to select BOTH literal vertices (0 and 1) or the dummy + - // something else. - - // Verify no extracted solution satisfies the formula - for sol in &solutions { - let sat_sol = reduction.extract_solution(sol); - let assignment: Vec = sat_sol.iter().map(|&v| v == 1).collect(); - // This unsatisfiable formula should not have a satisfying assignment - assert!( - !sat.is_satisfying(&assignment), - "Unsatisfiable formula should not be satisfied" - ); - } -} - -#[test] -fn test_three_sat_example() { - // 3-SAT: (x1 OR x2 OR x3) AND (NOT x1 OR NOT x2 OR x3) AND (x1 OR NOT x2 OR NOT x3) - let sat = Satisfiability::new( - 3, - vec![ - CNFClause::new(vec![1, 2, 3]), // x1 OR x2 OR x3 - CNFClause::new(vec![-1, -2, 3]), // NOT x1 OR NOT x2 OR x3 - CNFClause::new(vec![1, -2, -3]), // x1 OR NOT x2 OR NOT x3 - ], - ); - - let reduction = ReduceTo::>::reduce_to(&sat); - let ds_problem = reduction.target_problem(); - - // 3 variables * 3 = 9 gadget vertices + 3 clauses = 12 - assert_eq!(ds_problem.num_vertices(), 12); - - let solver = BruteForce::new(); - let solutions = solver.find_all_best(ds_problem); - - // Minimum should be 3 (one per variable) - let min_size = solutions[0].iter().sum::(); - assert_eq!(min_size, 3, "Minimum dominating set should have 3 vertices"); - - // Verify extracted solutions - let mut found_satisfying = false; - for sol in &solutions { - let sat_sol = reduction.extract_solution(sol); - let assignment: Vec = sat_sol.iter().map(|&v| v == 1).collect(); - if sat.is_satisfying(&assignment) { - found_satisfying = true; - break; - } - } - assert!( - found_satisfying, - "Should find a satisfying assignment for 3-SAT" - ); -} - #[test] fn test_extract_solution_positive_literal() { // (x1) - select positive literal @@ -230,48 +119,6 @@ fn test_multiple_literals_same_variable() { assert_eq!(ds_problem.num_edges(), 5); } -#[test] -fn test_sat_ds_solution_correspondence() { - // Comprehensive test: verify that solutions extracted from DS satisfy SAT - let sat = Satisfiability::new( - 2, - vec![CNFClause::new(vec![1, 2]), CNFClause::new(vec![-1, -2])], - ); - - // Solve SAT directly - use find_all_satisfying for satisfaction problems - let sat_solver = BruteForce::new(); - let direct_sat_solutions = sat_solver.find_all_satisfying(&sat); - - // Solve via reduction (DS is an optimization problem, so use find_best) - let reduction = ReduceTo::>::reduce_to(&sat); - let ds_problem = reduction.target_problem(); - let ds_solutions = sat_solver.find_all_best(ds_problem); - - // Direct SAT solutions should all be valid (they're from find_all_satisfying, so they all satisfy) - assert!(!direct_sat_solutions.is_empty()); - - // DS solutions with minimum size should correspond to valid SAT solutions - let min_size = ds_solutions[0].iter().sum::(); - if min_size == 2 { - // Only if min dominating set = num_vars - let mut found_satisfying = false; - for sol in &ds_solutions { - if sol.iter().sum::() == 2 { - let sat_sol = reduction.extract_solution(sol); - let assignment: Vec = sat_sol.iter().map(|&v| v == 1).collect(); - if sat.is_satisfying(&assignment) { - found_satisfying = true; - break; - } - } - } - assert!( - found_satisfying, - "At least one DS solution should give a SAT solution" - ); - } -} - #[test] fn test_accessors() { let sat = Satisfiability::new(2, vec![CNFClause::new(vec![1, -2])]); @@ -310,3 +157,39 @@ fn test_negated_variable_connection() { // - 2 from negated literals to clause: (1,6), (4,6) assert_eq!(ds_problem.num_edges(), 8); } + +#[test] +fn test_jl_parity_sat_to_dominatingset() { + let sat_data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/satisfiability.json")).unwrap(); + let fixtures: &[(&str, &str)] = &[ + (include_str!("../../../tests/data/jl/satisfiability_to_dominatingset.json"), "simple_clause"), + (include_str!("../../../tests/data/jl/rule_sat01_to_dominatingset.json"), "rule_sat01"), + (include_str!("../../../tests/data/jl/rule_sat02_to_dominatingset.json"), "rule_sat02"), + (include_str!("../../../tests/data/jl/rule_sat03_to_dominatingset.json"), "rule_sat03"), + (include_str!("../../../tests/data/jl/rule_sat04_unsat_to_dominatingset.json"), "rule_sat04_unsat"), + (include_str!("../../../tests/data/jl/rule_sat07_to_dominatingset.json"), "rule_sat07"), + ]; + for (fixture_str, label) in fixtures { + let data: serde_json::Value = serde_json::from_str(fixture_str).unwrap(); + let inst = &jl_find_instance_by_label(&sat_data, label)["instance"]; + let (num_vars, clauses) = jl_parse_sat_clauses(inst); + let source = Satisfiability::new(num_vars, clauses); + let result = ReduceTo::>::reduce_to(&source); + let solver = BruteForce::new(); + let best_target = solver.find_all_best(result.target_problem()); + let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + let sat_solutions: HashSet> = solver.find_all_satisfying(&source).into_iter().collect(); + for case in data["cases"].as_array().unwrap() { + if sat_solutions.is_empty() { + for sol in &extracted { + assert!(!source.evaluate(sol), "SAT->DS [{label}]: unsatisfiable but extracted satisfies"); + } + } else { + assert!(extracted.is_subset(&sat_solutions), "SAT->DS [{label}]: extracted not subset"); + assert_eq!(sat_solutions, jl_parse_configs_set(&case["best_source"]), + "SAT->DS [{label}]: best source mismatch"); + } + } + } +} diff --git a/src/unit_tests/rules/spinglass_maxcut.rs b/src/unit_tests/rules/spinglass_maxcut.rs index 656ba6a0..9171a21e 100644 --- a/src/unit_tests/rules/spinglass_maxcut.rs +++ b/src/unit_tests/rules/spinglass_maxcut.rs @@ -1,18 +1,6 @@ use super::*; use crate::solvers::BruteForce; - -#[test] -fn test_maxcut_to_spinglass() { - // Simple triangle MaxCut - let mc = MaxCut::::unweighted(3, vec![(0, 1), (1, 2), (0, 2)]); - let reduction = ReduceTo::>::reduce_to(&mc); - let sg = reduction.target_problem(); - - let solver = BruteForce::new(); - let solutions = solver.find_all_best(sg); - - assert!(!solutions.is_empty()); -} +include!("../jl_helpers.rs"); #[test] fn test_spinglass_to_maxcut_no_onsite() { @@ -90,3 +78,92 @@ fn test_reduction_structure() { assert_eq!(mc2.num_vertices(), 3); } + +#[test] +fn test_jl_parity_spinglass_to_maxcut() { + let data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/spinglass_to_maxcut.json")).unwrap(); + let sg_data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/spinglass.json")).unwrap(); + let inst = &sg_data["instances"][0]["instance"]; + let nv = inst["num_vertices"].as_u64().unwrap() as usize; + let edges = jl_parse_edges(inst); + let j_values = jl_parse_i32_vec(&inst["J"]); + let h_values = jl_parse_i32_vec(&inst["h"]); + let interactions: Vec<((usize, usize), i32)> = edges.into_iter().zip(j_values).collect(); + let source = SpinGlass::::new(nv, interactions, h_values); + let result = ReduceTo::>::reduce_to(&source); + let solver = BruteForce::new(); + let best_target = solver.find_all_best(result.target_problem()); + let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect(); + let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + assert!(extracted.is_subset(&best_source)); + for case in data["cases"].as_array().unwrap() { + assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); + } +} + +#[test] +fn test_jl_parity_maxcut_to_spinglass() { + let data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/maxcut_to_spinglass.json")).unwrap(); + let mc_data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/maxcut.json")).unwrap(); + let inst = &mc_data["instances"][0]["instance"]; + let nv = inst["num_vertices"].as_u64().unwrap() as usize; + let weighted_edges = jl_parse_weighted_edges(inst); + let source = MaxCut::::new(nv, weighted_edges); + let result = ReduceTo::>::reduce_to(&source); + let solver = BruteForce::new(); + let best_target = solver.find_all_best(result.target_problem()); + let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect(); + let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + assert!(extracted.is_subset(&best_source)); + for case in data["cases"].as_array().unwrap() { + assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); + } +} + +#[test] +fn test_jl_parity_rule_maxcut_to_spinglass() { + let data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/rule_maxcut_to_spinglass.json")).unwrap(); + let mc_data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/maxcut.json")).unwrap(); + let inst = &jl_find_instance_by_label(&mc_data, "rule_4vertex")["instance"]; + let source = MaxCut::::new( + inst["num_vertices"].as_u64().unwrap() as usize, jl_parse_weighted_edges(inst)); + let result = ReduceTo::>::reduce_to(&source); + let solver = BruteForce::new(); + let best_target = solver.find_all_best(result.target_problem()); + let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect(); + let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + assert!(extracted.is_subset(&best_source)); + for case in data["cases"].as_array().unwrap() { + assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); + } +} + +#[test] +fn test_jl_parity_rule_spinglass_to_maxcut() { + let data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/rule_spinglass_to_maxcut.json")).unwrap(); + let sg_data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/spinglass.json")).unwrap(); + let inst = &jl_find_instance_by_label(&sg_data, "rule_4vertex")["instance"]; + let nv = inst["num_vertices"].as_u64().unwrap() as usize; + let edges = jl_parse_edges(inst); + let j_values = jl_parse_i32_vec(&inst["J"]); + let h_values = jl_parse_i32_vec(&inst["h"]); + let interactions: Vec<((usize, usize), i32)> = edges.into_iter().zip(j_values).collect(); + let source = SpinGlass::::new(nv, interactions, h_values); + let result = ReduceTo::>::reduce_to(&source); + let solver = BruteForce::new(); + let best_target = solver.find_all_best(result.target_problem()); + let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect(); + let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + assert!(extracted.is_subset(&best_source)); + for case in data["cases"].as_array().unwrap() { + assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); + } +} diff --git a/src/unit_tests/rules/spinglass_qubo.rs b/src/unit_tests/rules/spinglass_qubo.rs index 0f73e042..235cc48f 100644 --- a/src/unit_tests/rules/spinglass_qubo.rs +++ b/src/unit_tests/rules/spinglass_qubo.rs @@ -1,81 +1,7 @@ use super::*; use crate::solvers::BruteForce; use crate::traits::Problem; - -#[test] -fn test_qubo_to_spinglass() { - // Simple 2-variable QUBO: minimize x0 + x1 - 2*x0*x1 - // Optimal at x = [0, 0] (value 0) or x = [1, 1] (value 0) - let qubo = QUBO::from_matrix(vec![vec![1.0, -2.0], vec![0.0, 1.0]]); - let reduction = ReduceTo::>::reduce_to(&qubo); - let sg = reduction.target_problem(); - - let solver = BruteForce::new(); - let sg_solutions = solver.find_all_best(sg); - let qubo_solutions: Vec<_> = sg_solutions - .iter() - .map(|s| reduction.extract_solution(s)) - .collect(); - - // Verify solutions are valid - assert!(!qubo_solutions.is_empty()); - - // Original QUBO at [0,0]: 0, at [1,1]: 1 + 1 - 2 = 0, at [0,1]: 1, at [1,0]: 1 - // So [0,0] and [1,1] are optimal with value 0 - for sol in &qubo_solutions { - let val = qubo.evaluate(sol); - assert!( - val <= 0.0 + 1e-6, - "Expected optimal value near 0, got {}", - val - ); - } -} - -#[test] -fn test_spinglass_to_qubo() { - // Simple SpinGlass: J_01 = -1 (ferromagnetic: prefers aligned spins) - // Energy: J_01 * s0 * s1 = -s0 * s1 - // Aligned spins give -1, anti-aligned give +1 - // Minimum is -1 at [0,0] or [1,1] (both give s=-1,-1 or s=+1,+1) - let sg = SpinGlass::::new(2, vec![((0, 1), -1.0)], vec![0.0, 0.0]); - let reduction = ReduceTo::>::reduce_to(&sg); - let qubo = reduction.target_problem(); - - let solver = BruteForce::new(); - let qubo_solutions = solver.find_all_best(qubo); - - // Ferromagnetic: aligned spins are optimal - for sol in &qubo_solutions { - assert_eq!(sol[0], sol[1], "Ferromagnetic should have aligned spins"); - } -} - -#[test] -fn test_roundtrip_qubo_sg_qubo() { - let original = QUBO::from_matrix(vec![vec![-1.0, 2.0], vec![0.0, -1.0]]); - let solver = BruteForce::new(); - let original_solutions = solver.find_all_best(&original); - let _original_val = original.evaluate(&original_solutions[0]); - - // QUBO -> SG -> QUBO - let reduction1 = ReduceTo::>::reduce_to(&original); - let sg = reduction1.target_problem().clone(); - let reduction2 = ReduceTo::>::reduce_to(&sg); - let roundtrip = reduction2.target_problem(); - - let roundtrip_solutions = solver.find_all_best(roundtrip); - let _roundtrip_val = roundtrip.evaluate(&roundtrip_solutions[0]); - - // The solutions should have the same configuration - // (optimal configs should match) - let orig_configs: std::collections::HashSet<_> = original_solutions.iter().collect(); - let rt_configs: std::collections::HashSet<_> = roundtrip_solutions.iter().collect(); - assert!( - orig_configs.intersection(&rt_configs).count() > 0, - "At least one optimal solution should match" - ); -} +include!("../jl_helpers.rs"); #[test] fn test_antiferromagnetic() { @@ -129,3 +55,83 @@ fn test_reduction_structure() { assert_eq!(qubo2.num_variables(), 3); } + +#[test] +fn test_jl_parity_spinglass_to_qubo() { + let data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/spinglass_to_qubo.json")).unwrap(); + let sg_data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/spinglass.json")).unwrap(); + let inst = &sg_data["instances"][0]["instance"]; + let nv = inst["num_vertices"].as_u64().unwrap() as usize; + let edges = jl_parse_edges(inst); + let j_values: Vec = inst["J"].as_array().unwrap().iter().map(|v| v.as_i64().unwrap() as f64).collect(); + let h_values: Vec = inst["h"].as_array().unwrap().iter().map(|v| v.as_i64().unwrap() as f64).collect(); + let interactions: Vec<((usize, usize), f64)> = edges.into_iter().zip(j_values).collect(); + let source = SpinGlass::::new(nv, interactions, h_values); + let result = ReduceTo::>::reduce_to(&source); + let solver = BruteForce::new(); + let best_target = solver.find_all_best(result.target_problem()); + let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect(); + let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + assert!(extracted.is_subset(&best_source)); + for case in data["cases"].as_array().unwrap() { + assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); + } +} + +#[test] +fn test_jl_parity_qubo_to_spinglass() { + let data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/qubo_to_spinglass.json")).unwrap(); + let q_data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/qubo.json")).unwrap(); + let jl_matrix: Vec> = q_data["instances"][0]["instance"]["matrix"] + .as_array().unwrap().iter() + .map(|row| row.as_array().unwrap().iter().map(|v| v.as_i64().unwrap() as f64).collect()) + .collect(); + let n = jl_matrix.len(); + let mut rust_matrix = vec![vec![0.0f64; n]; n]; + for i in 0..n { + rust_matrix[i][i] = jl_matrix[i][i]; + for j in (i + 1)..n { rust_matrix[i][j] = jl_matrix[i][j] + jl_matrix[j][i]; } + } + let source = QUBO::from_matrix(rust_matrix); + let result = ReduceTo::>::reduce_to(&source); + let solver = BruteForce::new(); + let best_target = solver.find_all_best(result.target_problem()); + let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect(); + let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + assert!(extracted.is_subset(&best_source)); + for case in data["cases"].as_array().unwrap() { + assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); + } +} + +#[test] +fn test_jl_parity_rule_qubo_to_spinglass() { + let data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/rule_qubo_to_spinglass.json")).unwrap(); + let q_data: serde_json::Value = + serde_json::from_str(include_str!("../../../tests/data/jl/qubo.json")).unwrap(); + let jl_matrix: Vec> = jl_find_instance_by_label(&q_data, "rule_3x3")["instance"]["matrix"] + .as_array().unwrap().iter() + .map(|row| row.as_array().unwrap().iter().map(|v| v.as_f64().unwrap()).collect()) + .collect(); + let n = jl_matrix.len(); + let mut rust_matrix = vec![vec![0.0f64; n]; n]; + for i in 0..n { + rust_matrix[i][i] = jl_matrix[i][i]; + for j in (i + 1)..n { rust_matrix[i][j] = jl_matrix[i][j] + jl_matrix[j][i]; } + } + let source = QUBO::from_matrix(rust_matrix); + let result = ReduceTo::>::reduce_to(&source); + let solver = BruteForce::new(); + let best_target = solver.find_all_best(result.target_problem()); + let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect(); + let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + assert!(extracted.is_subset(&best_source)); + for case in data["cases"].as_array().unwrap() { + assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); + } +} diff --git a/tests/data/jl/circuitsat_to_spinglass.json b/tests/data/jl/circuitsat_to_spinglass.json new file mode 100644 index 00000000..683014a6 --- /dev/null +++ b/tests/data/jl/circuitsat_to_spinglass.json @@ -0,0 +1 @@ +{"target_type":"SpinGlass","cases":[{"label":"circuit","extracted_single":[[1,0,1,0,0,1,0,0,0],[1,0,1,1,0,1,0,0,0],[0,1,0,0,0,1,1,0,0],[0,1,0,0,1,0,1,0,0],[1,0,1,0,1,0,1,1,0],[0,1,1,1,0,1,1,1,1],[0,1,1,1,1,0,1,1,1],[1,0,1,1,1,0,1,1,1]],"extracted_multiple":[[1,0,1,0,0,1,0,0,0],[1,0,1,1,0,1,0,0,0],[0,1,0,0,0,1,1,0,0],[0,1,0,0,1,0,1,0,0],[1,0,1,0,1,0,1,1,0],[0,1,1,1,0,1,1,1,1],[0,1,1,1,1,0,1,1,1],[1,0,1,1,1,0,1,1,1]],"best_source":[[1,0,1,0,0,1,0,0,0],[1,0,1,1,0,1,0,0,0],[0,1,0,0,1,0,1,0,0],[0,1,0,0,0,1,1,0,0],[1,0,1,0,1,0,1,1,0],[1,0,1,1,1,0,1,1,1],[0,1,1,1,1,0,1,1,1],[0,1,1,1,0,1,1,1,1]],"best_target":[[0,1,0,1,1,0,0,0,0],[0,1,1,1,1,0,0,0,0],[1,0,0,0,1,0,1,0,0],[1,0,0,0,0,1,1,0,0],[0,1,0,1,0,1,1,1,0],[1,0,1,1,1,0,1,1,1],[1,0,1,1,0,1,1,1,1],[0,1,1,1,0,1,1,1,1]]}],"source_type":"CircuitSAT"} \ No newline at end of file diff --git a/tests/data/jl/coloring.json b/tests/data/jl/coloring.json new file mode 100644 index 00000000..fb46f99a --- /dev/null +++ b/tests/data/jl/coloring.json @@ -0,0 +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 diff --git a/tests/data/jl/doc_independentset_to_setpacking.json b/tests/data/jl/doc_independentset_to_setpacking.json new file mode 100644 index 00000000..d0b61ec2 --- /dev/null +++ b/tests/data/jl/doc_independentset_to_setpacking.json @@ -0,0 +1 @@ +{"target_type":"SetPacking","cases":[{"label":"doc_is","extracted_single":[[1,0,0,1],[0,1,0,1]],"extracted_multiple":[[1,0,0,1],[0,1,0,1]],"best_source":[[1,0,0,1],[0,1,0,1]],"best_target":[[1,0,0,1],[0,1,0,1]]}],"source_type":"doc_IndependentSet"} \ No newline at end of file diff --git a/tests/data/jl/dominatingset.json b/tests/data/jl/dominatingset.json new file mode 100644 index 00000000..d9735f67 --- /dev/null +++ b/tests/data/jl/dominatingset.json @@ -0,0 +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 diff --git a/tests/data/jl/factoring.json b/tests/data/jl/factoring.json new file mode 100644 index 00000000..03606810 --- /dev/null +++ b/tests/data/jl/factoring.json @@ -0,0 +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 diff --git a/tests/data/jl/factoring_to_circuitsat.json b/tests/data/jl/factoring_to_circuitsat.json new file mode 100644 index 00000000..d516ac2f --- /dev/null +++ b/tests/data/jl/factoring_to_circuitsat.json @@ -0,0 +1 @@ +{"target_type":"CircuitSAT","cases":[{"label":"factoring","extracted_single":[[1,1]],"extracted_multiple":[[1,1]],"best_source":[[1,1]],"best_target":[[1,1,1,1,1,0,0,0]]}],"source_type":"Factoring"} \ No newline at end of file diff --git a/tests/data/jl/independentset.json b/tests/data/jl/independentset.json new file mode 100644 index 00000000..95789eac --- /dev/null +++ b/tests/data/jl/independentset.json @@ -0,0 +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 diff --git a/tests/data/jl/independentset_hypergraph_to_setpacking.json b/tests/data/jl/independentset_hypergraph_to_setpacking.json new file mode 100644 index 00000000..79fd1361 --- /dev/null +++ b/tests/data/jl/independentset_hypergraph_to_setpacking.json @@ -0,0 +1 @@ +{"target_type":"SetPacking","cases":[{"label":"is2_hyper","extracted_single":[[1,0,1]],"extracted_multiple":[[1,0,1]],"best_source":[[1,0,1]],"best_target":[[1,0,1]]}],"source_type":"IndependentSet_HyperGraph"} \ No newline at end of file diff --git a/tests/data/jl/independentset_to_setpacking.json b/tests/data/jl/independentset_to_setpacking.json new file mode 100644 index 00000000..31672aa1 --- /dev/null +++ b/tests/data/jl/independentset_to_setpacking.json @@ -0,0 +1 @@ +{"target_type":"SetPacking","cases":[{"label":"is","extracted_single":[[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]],"extracted_multiple":[[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]],"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,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]],"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,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]]}],"source_type":"IndependentSet"} \ No newline at end of file diff --git a/tests/data/jl/independentset_to_vertexcovering.json b/tests/data/jl/independentset_to_vertexcovering.json new file mode 100644 index 00000000..da283cf1 --- /dev/null +++ b/tests/data/jl/independentset_to_vertexcovering.json @@ -0,0 +1 @@ +{"target_type":"VertexCovering","cases":[{"label":"is_vc","extracted_single":[[1,0,1,0,0,0,0,0,1,1],[0,1,0,1,0,1,0,0,0,1],[0,1,0,0,1,0,0,1,1,0],[1,0,0,1,0,0,1,1,0,0],[0,0,1,0,1,1,1,0,0,0]],"extracted_multiple":[[1,0,1,0,0,0,0,0,1,1],[0,1,0,1,0,1,0,0,0,1],[0,1,0,0,1,0,0,1,1,0],[1,0,0,1,0,0,1,1,0,0],[0,0,1,0,1,1,1,0,0,0]],"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,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]],"best_target":[[0,1,0,1,1,1,1,1,0,0],[1,0,1,0,1,0,1,1,1,0],[1,0,1,1,0,1,1,0,0,1],[0,1,1,0,1,1,0,0,1,1],[1,1,0,1,0,0,0,1,1,1]]}],"source_type":"IndependentSet"} \ No newline at end of file diff --git a/tests/data/jl/ksatisfiability.json b/tests/data/jl/ksatisfiability.json new file mode 100644 index 00000000..99900bad --- /dev/null +++ b/tests/data/jl/ksatisfiability.json @@ -0,0 +1 @@ +{"instances":[{"label":"simple_3sat","instance":{"num_variables":3,"k":3,"clauses":[{"literals":[{"negated":false,"variable":0},{"negated":false,"variable":1},{"negated":false,"variable":2}]}]},"evaluations":[{"is_valid":true,"config":[0,1,0],"size":1},{"is_valid":true,"config":[1,0,1],"size":1},{"is_valid":true,"config":[0,0,1],"size":1},{"is_valid":true,"config":[1,0,0],"size":1},{"is_valid":true,"config":[0,0,0],"size":0},{"is_valid":true,"config":[1,1,1],"size":1},{"is_valid":true,"config":[1,1,0],"size":1},{"is_valid":true,"config":[0,1,1],"size":1}],"best_solutions":[[1,0,0],[0,1,0],[1,1,0],[0,0,1],[1,0,1],[0,1,1],[1,1,1]]}],"problem_type":"KSatisfiability"} \ No newline at end of file diff --git a/tests/data/jl/ksatisfiability_to_satisfiability.json b/tests/data/jl/ksatisfiability_to_satisfiability.json new file mode 100644 index 00000000..2614c745 --- /dev/null +++ b/tests/data/jl/ksatisfiability_to_satisfiability.json @@ -0,0 +1 @@ +{"target_type":"Satisfiability","cases":[{"label":"ksat_sat","extracted_single":[[1,0,0],[0,1,0],[1,1,0],[0,0,1],[1,0,1],[0,1,1],[1,1,1]],"extracted_multiple":[[1,0,0],[0,1,0],[1,1,0],[0,0,1],[1,0,1],[0,1,1],[1,1,1]],"best_source":[[1,0,0],[0,1,0],[1,1,0],[0,0,1],[1,0,1],[0,1,1],[1,1,1]],"best_target":[[1,0,0],[0,1,0],[1,1,0],[0,0,1],[1,0,1],[0,1,1],[1,1,1]]}],"source_type":"KSatisfiability"} \ No newline at end of file diff --git a/tests/data/jl/matching.json b/tests/data/jl/matching.json new file mode 100644 index 00000000..f4dc3d65 --- /dev/null +++ b/tests/data/jl/matching.json @@ -0,0 +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 diff --git a/tests/data/jl/matching_to_setpacking.json b/tests/data/jl/matching_to_setpacking.json new file mode 100644 index 00000000..f1b1636e --- /dev/null +++ b/tests/data/jl/matching_to_setpacking.json @@ -0,0 +1 @@ +{"target_type":"SetPacking","cases":[{"label":"matching","extracted_single":[[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]],"extracted_multiple":[[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]],"best_source":[[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]],"best_target":[[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]]}],"source_type":"Matching"} \ No newline at end of file diff --git a/tests/data/jl/maxcut.json b/tests/data/jl/maxcut.json new file mode 100644 index 00000000..91c12428 --- /dev/null +++ b/tests/data/jl/maxcut.json @@ -0,0 +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 diff --git a/tests/data/jl/maxcut_to_spinglass.json b/tests/data/jl/maxcut_to_spinglass.json new file mode 100644 index 00000000..e3fe47fd --- /dev/null +++ b/tests/data/jl/maxcut_to_spinglass.json @@ -0,0 +1 @@ +{"target_type":"SpinGlass","cases":[{"label":"maxcut","extracted_single":[[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]],"extracted_multiple":[[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]],"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]],"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]]}],"source_type":"MaxCut"} \ No newline at end of file diff --git a/tests/data/jl/maximalis.json b/tests/data/jl/maximalis.json new file mode 100644 index 00000000..52a13471 --- /dev/null +++ b/tests/data/jl/maximalis.json @@ -0,0 +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 diff --git a/tests/data/jl/paintshop.json b/tests/data/jl/paintshop.json new file mode 100644 index 00000000..4c7637bc --- /dev/null +++ b/tests/data/jl/paintshop.json @@ -0,0 +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 diff --git a/tests/data/jl/qubo.json b/tests/data/jl/qubo.json new file mode 100644 index 00000000..097dd733 --- /dev/null +++ b/tests/data/jl/qubo.json @@ -0,0 +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 diff --git a/tests/data/jl/qubo_to_spinglass.json b/tests/data/jl/qubo_to_spinglass.json new file mode 100644 index 00000000..5cfbccb3 --- /dev/null +++ b/tests/data/jl/qubo_to_spinglass.json @@ -0,0 +1 @@ +{"target_type":"SpinGlass","cases":[{"label":"qubo","extracted_single":[[0,0,0],[1,0,0],[0,1,0],[1,1,1]],"extracted_multiple":[[0,0,0],[1,0,0],[0,1,0],[1,1,1]],"best_source":[[0,0,0],[1,0,0],[0,1,0],[1,1,1]],"best_target":[[0,0,0],[1,0,0],[0,1,0],[1,1,1]]}],"source_type":"QUBO"} \ No newline at end of file diff --git a/tests/data/jl/rule2_independentset_to_vertexcovering.json b/tests/data/jl/rule2_independentset_to_vertexcovering.json new file mode 100644 index 00000000..74bba1b3 --- /dev/null +++ b/tests/data/jl/rule2_independentset_to_vertexcovering.json @@ -0,0 +1 @@ +{"target_type":"VertexCovering","cases":[{"label":"rule_is_vc","extracted_single":[[0,1,0,1],[1,0,0,1]],"extracted_multiple":[[0,1,0,1],[1,0,0,1]],"best_source":[[1,0,0,1],[0,1,0,1]],"best_target":[[1,0,1,0],[0,1,1,0]]}],"source_type":"rule2_IndependentSet"} \ No newline at end of file diff --git a/tests/data/jl/rule_independentset_to_setpacking.json b/tests/data/jl/rule_independentset_to_setpacking.json new file mode 100644 index 00000000..64130862 --- /dev/null +++ b/tests/data/jl/rule_independentset_to_setpacking.json @@ -0,0 +1 @@ +{"target_type":"SetPacking","cases":[{"label":"rule_is_g02","extracted_single":[[1,0,0,1],[0,1,0,1]],"extracted_multiple":[[1,0,0,1],[0,1,0,1]],"best_source":[[1,0,0,1],[0,1,0,1]],"best_target":[[1,0,0,1],[0,1,0,1]]}],"source_type":"rule_IndependentSet"} \ No newline at end of file diff --git a/tests/data/jl/rule_matching_to_setpacking.json b/tests/data/jl/rule_matching_to_setpacking.json new file mode 100644 index 00000000..c4e4b6be --- /dev/null +++ b/tests/data/jl/rule_matching_to_setpacking.json @@ -0,0 +1 @@ +{"target_type":"SetPacking","cases":[{"label":"rule_match_uw","extracted_single":[[1,0,0,1]],"extracted_multiple":[[1,0,0,1]],"best_source":[[1,0,0,1]],"best_target":[[1,0,0,1]]}],"source_type":"rule_Matching"} \ No newline at end of file diff --git a/tests/data/jl/rule_matchingw_to_setpacking.json b/tests/data/jl/rule_matchingw_to_setpacking.json new file mode 100644 index 00000000..2fb44f26 --- /dev/null +++ b/tests/data/jl/rule_matchingw_to_setpacking.json @@ -0,0 +1 @@ +{"target_type":"SetPacking","cases":[{"label":"rule_match_w","extracted_single":[[1,0,0,1]],"extracted_multiple":[[1,0,0,1]],"best_source":[[1,0,0,1]],"best_target":[[1,0,0,1]]}],"source_type":"rule_MatchingW"} \ No newline at end of file diff --git a/tests/data/jl/rule_maxcut_to_spinglass.json b/tests/data/jl/rule_maxcut_to_spinglass.json new file mode 100644 index 00000000..2b3c6bd8 --- /dev/null +++ b/tests/data/jl/rule_maxcut_to_spinglass.json @@ -0,0 +1 @@ +{"target_type":"SpinGlass","cases":[{"label":"rule_mc","extracted_single":[[0,0,1,0],[0,1,1,0],[1,0,0,1],[1,1,0,1]],"extracted_multiple":[[0,0,1,0],[0,1,1,0],[1,0,0,1],[1,1,0,1]],"best_source":[[0,0,1,0],[0,1,1,0],[1,0,0,1],[1,1,0,1]],"best_target":[[0,0,1,0],[0,1,1,0],[1,0,0,1],[1,1,0,1]]}],"source_type":"rule_MaxCut"} \ No newline at end of file diff --git a/tests/data/jl/rule_qubo_to_spinglass.json b/tests/data/jl/rule_qubo_to_spinglass.json new file mode 100644 index 00000000..43530555 --- /dev/null +++ b/tests/data/jl/rule_qubo_to_spinglass.json @@ -0,0 +1 @@ +{"target_type":"SpinGlass","cases":[{"label":"rule_qubo","extracted_single":[[0,0,0],[1,0,1],[0,1,1],[1,1,1]],"extracted_multiple":[[0,0,0],[1,0,1],[0,1,1],[1,1,1]],"best_source":[[0,0,0],[1,0,1],[0,1,1],[1,1,1]],"best_target":[[0,0,0],[1,0,1],[0,1,1],[1,1,1]]}],"source_type":"rule_QUBO"} \ No newline at end of file diff --git a/tests/data/jl/rule_sat01_to_circuitsat.json b/tests/data/jl/rule_sat01_to_circuitsat.json new file mode 100644 index 00000000..cb40962f --- /dev/null +++ b/tests/data/jl/rule_sat01_to_circuitsat.json @@ -0,0 +1 @@ +{"target_type":"CircuitSAT","cases":[{"label":"rule_sat01","extracted_single":[null,[1,1,1],[1,1,0],[0,0,1],[0,0,0]],"extracted_multiple":[[1,1,1],[1,1,0],[0,0,1],[0,0,0]],"best_source":[[0,0,0],[1,1,0],[0,0,1],[1,1,1]],"best_target":[[1,0,1,1,0,0,1,1,1,1,1,0,0,0],[1,0,1,1,1,0,0,0,1,0,1,0,1,0],[0,1,1,0,1,1,0,1,0,0,0,1,1,0],[0,1,0,0,0,1,1,1,0,1,1,1,1,0],[0,1,1,1,1,0,0,1,0,0,1,0,1,1],[0,1,1,1,0,0,1,1,0,1,1,0,1,1],[1,0,1,0,1,1,0,1,1,0,1,1,1,1],[1,0,1,0,0,1,1,1,1,1,1,1,1,1]]}],"source_type":"rule_SAT01"} \ No newline at end of file diff --git a/tests/data/jl/rule_sat01_to_dominatingset.json b/tests/data/jl/rule_sat01_to_dominatingset.json new file mode 100644 index 00000000..9892f3b3 --- /dev/null +++ b/tests/data/jl/rule_sat01_to_dominatingset.json @@ -0,0 +1 @@ +{"target_type":"DominatingSet","cases":[{"label":"rule_sat01","extracted_single":[[1,1,1],[0,0,1],[1,1,0],[0,0,0]],"extracted_multiple":[[1,1,1],[0,0,1],[1,1,0],[0,0,0]],"best_source":[[0,0,0],[1,1,0],[0,0,1],[1,1,1]],"best_target":[[1,0,0,1,0,0,1,0,0,0,0,0,0],[0,1,0,0,1,0,1,0,0,0,0,0,0],[1,0,0,1,0,0,0,1,0,0,0,0,0],[0,1,0,0,1,0,0,1,0,0,0,0,0],[1,0,0,1,0,0,0,0,1,0,0,0,0],[0,1,0,0,1,0,0,0,1,0,0,0,0]]}],"source_type":"rule_SAT01"} \ 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 new file mode 100644 index 00000000..ee4a6f3e --- /dev/null +++ b/tests/data/jl/rule_sat01_to_independentset.json @@ -0,0 +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 diff --git a/tests/data/jl/rule_sat02_to_circuitsat.json b/tests/data/jl/rule_sat02_to_circuitsat.json new file mode 100644 index 00000000..dde4972a --- /dev/null +++ b/tests/data/jl/rule_sat02_to_circuitsat.json @@ -0,0 +1 @@ +{"target_type":"CircuitSAT","cases":[{"label":"rule_sat02","extracted_single":[null,[0,1,1],[1,1,1],[1,0,1],[1,1,0],[0,0,0]],"extracted_multiple":[[0,1,1],[1,1,1],[1,0,1],[1,1,0],[0,0,0]],"best_source":[[0,0,0],[1,1,0],[1,0,1],[0,1,1],[1,1,1]],"best_target":[[1,0,1,0,1,1,1,0,0,0],[1,0,1,1,0,0,0,1,1,0],[0,1,0,0,0,1,1,1,1,0],[1,0,1,1,1,0,1,0,1,1],[0,1,1,1,1,0,1,0,1,1],[0,1,1,0,1,1,1,0,1,1],[0,1,1,1,0,0,1,1,1,1],[1,0,1,0,0,1,1,1,1,1]]}],"source_type":"rule_SAT02"} \ No newline at end of file diff --git a/tests/data/jl/rule_sat02_to_dominatingset.json b/tests/data/jl/rule_sat02_to_dominatingset.json new file mode 100644 index 00000000..0d699f4b --- /dev/null +++ b/tests/data/jl/rule_sat02_to_dominatingset.json @@ -0,0 +1 @@ +{"target_type":"DominatingSet","cases":[{"label":"rule_sat02","extracted_single":[[1,1,1],[0,1,1],[1,0,1],[1,1,0],[0,0,0]],"extracted_multiple":[[1,1,1],[0,1,1],[1,0,1],[1,1,0],[0,0,0]],"best_source":[[0,0,0],[1,1,0],[1,0,1],[0,1,1],[1,1,1]],"best_target":[[1,0,0,1,0,0,1,0,0,0,0,0],[0,1,0,1,0,0,1,0,0,0,0,0],[0,0,1,1,0,0,1,0,0,0,0,0],[1,0,0,0,1,0,1,0,0,0,0,0],[1,0,0,0,0,1,1,0,0,0,0,0],[1,0,0,1,0,0,0,1,0,0,0,0],[0,1,0,0,1,0,0,1,0,0,0,0],[1,0,0,1,0,0,0,0,1,0,0,0]]}],"source_type":"rule_SAT02"} \ 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 new file mode 100644 index 00000000..2a41c9a8 --- /dev/null +++ b/tests/data/jl/rule_sat02_to_independentset.json @@ -0,0 +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 diff --git a/tests/data/jl/rule_sat03_to_circuitsat.json b/tests/data/jl/rule_sat03_to_circuitsat.json new file mode 100644 index 00000000..57662ff4 --- /dev/null +++ b/tests/data/jl/rule_sat03_to_circuitsat.json @@ -0,0 +1 @@ +{"target_type":"CircuitSAT","cases":[{"label":"rule_sat03","extracted_single":[null,[0,1,1],[1,0,1],[0,0,1],[1,1,0],[0,1,0],[1,0,0]],"extracted_multiple":[[0,1,1],[1,0,1],[0,0,1],[1,1,0],[0,1,0],[1,0,0]],"best_source":[[1,0,0],[0,1,0],[1,1,0],[0,0,1],[1,0,1],[0,1,1]],"best_target":[[1,1,1,1,0,0,0,0,0],[0,0,0,0,1,1,1,1,0],[1,0,1,1,1,0,0,1,1],[1,1,0,1,0,1,0,1,1],[1,0,0,1,1,1,0,1,1],[1,1,1,0,0,0,1,1,1],[1,0,1,0,1,0,1,1,1],[1,1,0,0,0,1,1,1,1]]}],"source_type":"rule_SAT03"} \ No newline at end of file diff --git a/tests/data/jl/rule_sat03_to_dominatingset.json b/tests/data/jl/rule_sat03_to_dominatingset.json new file mode 100644 index 00000000..21de64eb --- /dev/null +++ b/tests/data/jl/rule_sat03_to_dominatingset.json @@ -0,0 +1 @@ +{"target_type":"DominatingSet","cases":[{"label":"rule_sat03","extracted_single":[[0,1,1],[1,0,1],[0,0,1],[1,1,0],[0,1,0],[1,0,0]],"extracted_multiple":[[0,1,1],[1,0,1],[0,0,1],[1,1,0],[0,1,0],[1,0,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,1,0,0,0,0],[1,0,0,0,1,0,1,0,0,0,0],[0,1,0,0,1,0,1,0,0,0,0],[0,0,1,0,1,0,1,0,0,0,0],[0,1,0,0,0,1,1,0,0,0,0],[1,0,0,1,0,0,0,1,0,0,0],[0,1,0,1,0,0,0,1,0,0,0],[0,0,1,1,0,0,0,1,0,0,0],[1,0,0,0,1,0,0,1,0,0,0],[1,0,0,0,0,1,0,1,0,0,0],[0,1,0,1,0,0,0,0,1,0,0],[1,0,0,0,1,0,0,0,1,0,0]]}],"source_type":"rule_SAT03"} \ 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 new file mode 100644 index 00000000..668a179d --- /dev/null +++ b/tests/data/jl/rule_sat03_to_independentset.json @@ -0,0 +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 diff --git a/tests/data/jl/rule_sat04_unsat_to_dominatingset.json b/tests/data/jl/rule_sat04_unsat_to_dominatingset.json new file mode 100644 index 00000000..0d8a41f0 --- /dev/null +++ b/tests/data/jl/rule_sat04_unsat_to_dominatingset.json @@ -0,0 +1 @@ +{"target_type":"DominatingSet","cases":[{"label":"rule_sat04","extracted_single":[null],"extracted_multiple":[],"best_source":[[0],[1]],"best_target":[[1,1,0,0,0],[0,1,0,1,0],[1,0,0,0,1]]}],"source_type":"rule_SAT04_unsat"} \ No newline at end of file diff --git a/tests/data/jl/rule_sat04_unsat_to_independentset.json b/tests/data/jl/rule_sat04_unsat_to_independentset.json new file mode 100644 index 00000000..706ed5b8 --- /dev/null +++ b/tests/data/jl/rule_sat04_unsat_to_independentset.json @@ -0,0 +1 @@ +{"target_type":"IndependentSet","cases":[{"label":"rule_sat04","extracted_single":[[1],[0]],"extracted_multiple":[[1],[0]],"best_source":[[0],[1]],"best_target":[[1,0,0,0,0,0],[0,1,0,0,0,0],[0,0,1,0,0,0],[0,0,0,1,0,0],[0,0,0,0,1,0],[0,0,0,0,0,1]]}],"source_type":"rule_SAT04_unsat"} \ No newline at end of file diff --git a/tests/data/jl/rule_sat07_to_dominatingset.json b/tests/data/jl/rule_sat07_to_dominatingset.json new file mode 100644 index 00000000..17889094 --- /dev/null +++ b/tests/data/jl/rule_sat07_to_dominatingset.json @@ -0,0 +1 @@ +{"target_type":"DominatingSet","cases":[{"label":"rule_sat07","extracted_single":[[1,1]],"extracted_multiple":[[1,1]],"best_source":[[1,1]],"best_target":[[1,0,0,1,0,0,0,0,0]]}],"source_type":"rule_SAT07"} \ No newline at end of file diff --git a/tests/data/jl/rule_sat07_to_independentset.json b/tests/data/jl/rule_sat07_to_independentset.json new file mode 100644 index 00000000..133322da --- /dev/null +++ b/tests/data/jl/rule_sat07_to_independentset.json @@ -0,0 +1 @@ +{"target_type":"IndependentSet","cases":[{"label":"rule_sat07","extracted_single":[[1,1]],"extracted_multiple":[[1,1]],"best_source":[[1,1]],"best_target":[[1,0,1,0,0,1],[0,1,1,0,0,1]]}],"source_type":"rule_SAT07"} \ No newline at end of file diff --git a/tests/data/jl/rule_satisfiability2_to_coloring3.json b/tests/data/jl/rule_satisfiability2_to_coloring3.json new file mode 100644 index 00000000..fa3461ed --- /dev/null +++ b/tests/data/jl/rule_satisfiability2_to_coloring3.json @@ -0,0 +1 @@ +{"target_type":"Coloring3","cases":[{"label":"rule_sat_col","extracted_single":[[1,0],[1,1]],"extracted_multiple":[[1,0],[1,1]],"best_source":[[1,0],[1,1]],"best_target":[[0,1,2,0,1,1,0,1,2,2,1,0,2,1,2,1,0],[0,2,1,0,2,2,0,2,1,1,2,0,2,1,2,1,0],[0,1,2,0,0,1,1,2,1,2,1,0,1,2,2,1,0],[0,1,2,0,1,1,0,1,2,2,1,0,1,2,2,1,0],[0,1,2,0,0,1,1,1,2,2,1,0,1,2,2,1,0],[0,2,1,0,2,2,0,2,1,1,2,0,1,2,2,1,0],[0,1,2,0,0,1,1,2,1,1,2,0,1,2,2,1,0],[0,1,2,0,0,1,1,1,2,1,2,0,1,2,2,1,0],[0,2,1,0,0,2,2,2,1,2,1,0,2,1,1,2,0],[0,1,2,0,1,1,0,1,2,2,1,0,2,1,1,2,0],[0,2,1,0,0,2,2,1,2,2,1,0,2,1,1,2,0],[0,2,1,0,2,2,0,2,1,1,2,0,2,1,1,2,0],[0,2,1,0,0,2,2,2,1,1,2,0,2,1,1,2,0],[0,2,1,0,0,2,2,1,2,1,2,0,2,1,1,2,0],[0,1,2,0,1,1,0,1,2,2,1,0,1,2,1,2,0],[0,2,1,0,2,2,0,2,1,1,2,0,1,2,1,2,0],[1,0,2,1,0,0,1,0,2,2,0,1,2,0,2,0,1],[1,2,0,1,2,2,1,2,0,0,2,1,2,0,2,0,1],[1,0,2,1,1,0,0,2,0,2,0,1,0,2,2,0,1],[1,0,2,1,1,0,0,0,2,2,0,1,0,2,2,0,1],[1,0,2,1,0,0,1,0,2,2,0,1,0,2,2,0,1],[1,0,2,1,1,0,0,2,0,0,2,1,0,2,2,0,1],[1,2,0,1,2,2,1,2,0,0,2,1,0,2,2,0,1],[1,0,2,1,1,0,0,0,2,0,2,1,0,2,2,0,1],[1,2,0,1,1,2,2,2,0,2,0,1,2,0,0,2,1],[1,0,2,1,0,0,1,0,2,2,0,1,2,0,0,2,1],[1,2,0,1,1,2,2,0,2,2,0,1,2,0,0,2,1],[1,2,0,1,2,2,1,2,0,0,2,1,2,0,0,2,1],[1,2,0,1,1,2,2,2,0,0,2,1,2,0,0,2,1],[1,2,0,1,1,2,2,0,2,0,2,1,2,0,0,2,1],[1,0,2,1,0,0,1,0,2,2,0,1,0,2,0,2,1],[1,2,0,1,2,2,1,2,0,0,2,1,0,2,0,2,1],[2,0,1,2,0,0,2,0,1,1,0,2,1,0,1,0,2],[2,1,0,2,1,1,2,1,0,0,1,2,1,0,1,0,2],[2,0,1,2,2,0,0,1,0,1,0,2,0,1,1,0,2],[2,0,1,2,2,0,0,0,1,1,0,2,0,1,1,0,2],[2,0,1,2,0,0,2,0,1,1,0,2,0,1,1,0,2],[2,0,1,2,2,0,0,1,0,0,1,2,0,1,1,0,2],[2,1,0,2,1,1,2,1,0,0,1,2,0,1,1,0,2],[2,0,1,2,2,0,0,0,1,0,1,2,0,1,1,0,2],[2,1,0,2,2,1,1,1,0,1,0,2,1,0,0,1,2],[2,1,0,2,2,1,1,0,1,1,0,2,1,0,0,1,2],[2,0,1,2,0,0,2,0,1,1,0,2,1,0,0,1,2],[2,1,0,2,2,1,1,1,0,0,1,2,1,0,0,1,2],[2,1,0,2,1,1,2,1,0,0,1,2,1,0,0,1,2],[2,1,0,2,2,1,1,0,1,0,1,2,1,0,0,1,2],[2,0,1,2,0,0,2,0,1,1,0,2,0,1,0,1,2],[2,1,0,2,1,1,2,1,0,0,1,2,0,1,0,1,2]]}],"source_type":"rule_Satisfiability2"} \ No newline at end of file diff --git a/tests/data/jl/rule_satisfiability_to_ksatisfiability3.json b/tests/data/jl/rule_satisfiability_to_ksatisfiability3.json new file mode 100644 index 00000000..f9ef95a5 --- /dev/null +++ b/tests/data/jl/rule_satisfiability_to_ksatisfiability3.json @@ -0,0 +1 @@ +{"target_type":"KSatisfiability3","cases":[{"label":"rule_sat_3sat","extracted_single":[[1,0,0,0],[1,0,1,0],[1,1,1,0],[1,0,0,1],[1,0,1,1],[1,1,1,1]],"extracted_multiple":[[1,0,0,0],[1,0,1,0],[1,1,1,0],[1,0,0,1],[1,0,1,1],[1,1,1,1]],"best_source":[[1,0,0,0],[1,0,1,0],[1,1,1,0],[1,0,0,1],[1,0,1,1],[1,1,1,1]],"best_target":[[1,0,0,0,0,0,0,0,0],[1,1,0,0,0,0,0,0,0],[1,0,1,0,0,0,0,0,0],[1,1,1,0,0,0,0,0,0],[1,0,0,1,0,0,0,0,0],[1,1,0,1,0,0,0,0,0],[1,0,1,1,0,0,0,0,0],[1,1,1,1,0,0,0,0,0],[1,0,0,0,0,1,0,0,0],[1,1,0,0,0,1,0,0,0],[1,0,1,0,0,1,0,0,0],[1,1,1,0,0,1,0,0,0],[1,0,0,1,0,1,0,0,0],[1,1,0,1,0,1,0,0,0],[1,0,1,1,0,1,0,0,0],[1,1,1,1,0,1,0,0,0],[1,0,0,0,1,1,0,0,0],[1,1,0,0,1,1,0,0,0],[1,0,1,0,1,1,0,0,0],[1,1,1,0,1,1,0,0,0],[1,0,0,1,1,1,0,0,0],[1,1,0,1,1,1,0,0,0],[1,0,1,1,1,1,0,0,0],[1,1,1,1,1,1,0,0,0],[1,0,0,0,0,0,1,0,0],[1,1,0,0,0,0,1,0,0],[1,0,1,0,0,0,1,0,0],[1,1,1,0,0,0,1,0,0],[1,0,0,1,0,0,1,0,0],[1,1,0,1,0,0,1,0,0],[1,0,1,1,0,0,1,0,0],[1,1,1,1,0,0,1,0,0],[1,0,0,0,0,1,1,0,0],[1,1,0,0,0,1,1,0,0],[1,0,1,0,0,1,1,0,0],[1,1,1,0,0,1,1,0,0],[1,0,0,1,0,1,1,0,0],[1,1,0,1,0,1,1,0,0],[1,0,1,1,0,1,1,0,0],[1,1,1,1,0,1,1,0,0],[1,0,0,0,1,1,1,0,0],[1,1,0,0,1,1,1,0,0],[1,0,1,0,1,1,1,0,0],[1,1,1,0,1,1,1,0,0],[1,0,0,1,1,1,1,0,0],[1,1,0,1,1,1,1,0,0],[1,0,1,1,1,1,1,0,0],[1,1,1,1,1,1,1,0,0],[1,0,0,0,0,1,0,1,0],[1,1,0,0,0,1,0,1,0],[1,0,1,0,0,1,0,1,0],[1,1,1,0,0,1,0,1,0],[1,0,0,1,0,1,0,1,0],[1,1,0,1,0,1,0,1,0],[1,0,1,1,0,1,0,1,0],[1,1,1,1,0,1,0,1,0],[1,0,0,0,1,1,0,1,0],[1,1,0,0,1,1,0,1,0],[1,0,1,0,1,1,0,1,0],[1,1,1,0,1,1,0,1,0],[1,0,0,1,1,1,0,1,0],[1,1,0,1,1,1,0,1,0],[1,0,1,1,1,1,0,1,0],[1,1,1,1,1,1,0,1,0],[1,0,0,0,0,1,1,1,0],[1,1,0,0,0,1,1,1,0],[1,0,1,0,0,1,1,1,0],[1,1,1,0,0,1,1,1,0],[1,0,0,1,0,1,1,1,0],[1,1,0,1,0,1,1,1,0],[1,0,1,1,0,1,1,1,0],[1,1,1,1,0,1,1,1,0],[1,0,0,0,1,1,1,1,0],[1,1,0,0,1,1,1,1,0],[1,0,1,0,1,1,1,1,0],[1,1,1,0,1,1,1,1,0],[1,0,0,1,1,1,1,1,0],[1,1,0,1,1,1,1,1,0],[1,0,1,1,1,1,1,1,0],[1,1,1,1,1,1,1,1,0],[1,0,0,0,0,0,0,0,1],[1,1,0,0,0,0,0,0,1],[1,0,1,0,0,0,0,0,1],[1,1,1,0,0,0,0,0,1],[1,0,0,1,0,0,0,0,1],[1,1,0,1,0,0,0,0,1],[1,0,1,1,0,0,0,0,1],[1,1,1,1,0,0,0,0,1],[1,0,0,0,0,1,0,0,1],[1,1,0,0,0,1,0,0,1],[1,0,1,0,0,1,0,0,1],[1,1,1,0,0,1,0,0,1],[1,0,0,1,0,1,0,0,1],[1,1,0,1,0,1,0,0,1],[1,0,1,1,0,1,0,0,1],[1,1,1,1,0,1,0,0,1],[1,0,0,0,1,1,0,0,1],[1,1,0,0,1,1,0,0,1],[1,0,1,0,1,1,0,0,1],[1,1,1,0,1,1,0,0,1],[1,0,0,1,1,1,0,0,1],[1,1,0,1,1,1,0,0,1],[1,0,1,1,1,1,0,0,1],[1,1,1,1,1,1,0,0,1],[1,0,0,0,0,0,1,0,1],[1,1,0,0,0,0,1,0,1],[1,0,1,0,0,0,1,0,1],[1,1,1,0,0,0,1,0,1],[1,0,0,1,0,0,1,0,1],[1,1,0,1,0,0,1,0,1],[1,0,1,1,0,0,1,0,1],[1,1,1,1,0,0,1,0,1],[1,0,0,0,0,1,1,0,1],[1,1,0,0,0,1,1,0,1],[1,0,1,0,0,1,1,0,1],[1,1,1,0,0,1,1,0,1],[1,0,0,1,0,1,1,0,1],[1,1,0,1,0,1,1,0,1],[1,0,1,1,0,1,1,0,1],[1,1,1,1,0,1,1,0,1],[1,0,0,0,1,1,1,0,1],[1,1,0,0,1,1,1,0,1],[1,0,1,0,1,1,1,0,1],[1,1,1,0,1,1,1,0,1],[1,0,0,1,1,1,1,0,1],[1,1,0,1,1,1,1,0,1],[1,0,1,1,1,1,1,0,1],[1,1,1,1,1,1,1,0,1],[1,0,0,0,0,0,0,1,1],[1,1,0,0,0,0,0,1,1],[1,0,1,0,0,0,0,1,1],[1,1,1,0,0,0,0,1,1],[1,0,0,1,0,0,0,1,1],[1,1,0,1,0,0,0,1,1],[1,0,1,1,0,0,0,1,1],[1,1,1,1,0,0,0,1,1],[1,0,0,0,0,1,0,1,1],[1,1,0,0,0,1,0,1,1],[1,0,1,0,0,1,0,1,1],[1,1,1,0,0,1,0,1,1],[1,0,0,1,0,1,0,1,1],[1,1,0,1,0,1,0,1,1],[1,0,1,1,0,1,0,1,1],[1,1,1,1,0,1,0,1,1],[1,0,0,0,1,1,0,1,1],[1,1,0,0,1,1,0,1,1],[1,0,1,0,1,1,0,1,1],[1,1,1,0,1,1,0,1,1],[1,0,0,1,1,1,0,1,1],[1,1,0,1,1,1,0,1,1],[1,0,1,1,1,1,0,1,1],[1,1,1,1,1,1,0,1,1],[1,0,0,0,0,0,1,1,1],[1,1,0,0,0,0,1,1,1],[1,0,1,0,0,0,1,1,1],[1,1,1,0,0,0,1,1,1],[1,0,0,1,0,0,1,1,1],[1,1,0,1,0,0,1,1,1],[1,0,1,1,0,0,1,1,1],[1,1,1,1,0,0,1,1,1],[1,0,0,0,0,1,1,1,1],[1,1,0,0,0,1,1,1,1],[1,0,1,0,0,1,1,1,1],[1,1,1,0,0,1,1,1,1],[1,0,0,1,0,1,1,1,1],[1,1,0,1,0,1,1,1,1],[1,0,1,1,0,1,1,1,1],[1,1,1,1,0,1,1,1,1],[1,0,0,0,1,1,1,1,1],[1,1,0,0,1,1,1,1,1],[1,0,1,0,1,1,1,1,1],[1,1,1,0,1,1,1,1,1],[1,0,0,1,1,1,1,1,1],[1,1,0,1,1,1,1,1,1],[1,0,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1]]}],"source_type":"rule_Satisfiability"} \ No newline at end of file diff --git a/tests/data/jl/rule_spinglass_to_maxcut.json b/tests/data/jl/rule_spinglass_to_maxcut.json new file mode 100644 index 00000000..3dff82f9 --- /dev/null +++ b/tests/data/jl/rule_spinglass_to_maxcut.json @@ -0,0 +1 @@ +{"target_type":"MaxCut","cases":[{"label":"rule_sg","extracted_single":[[0,0,1,0],[0,1,1,0],[1,0,0,1],[1,1,0,1]],"extracted_multiple":[[0,0,1,0],[0,1,1,0],[1,0,0,1],[1,1,0,1]],"best_source":[[0,0,1,0],[0,1,1,0],[1,0,0,1],[1,1,0,1]],"best_target":[[0,0,1,0],[0,1,1,0],[1,0,0,1],[1,1,0,1]]}],"source_type":"rule_SpinGlass"} \ No newline at end of file diff --git a/tests/data/jl/rule_vertexcovering_to_setcovering.json b/tests/data/jl/rule_vertexcovering_to_setcovering.json new file mode 100644 index 00000000..d4c20b91 --- /dev/null +++ b/tests/data/jl/rule_vertexcovering_to_setcovering.json @@ -0,0 +1 @@ +{"target_type":"SetCovering","cases":[{"label":"rule_vc","extracted_single":[[1,0,1,0]],"extracted_multiple":[[1,0,1,0]],"best_source":[[1,0,1,0]],"best_target":[[1,0,1,0]]}],"source_type":"rule_VertexCovering"} \ No newline at end of file diff --git a/tests/data/jl/satisfiability.json b/tests/data/jl/satisfiability.json new file mode 100644 index 00000000..0e47af4e --- /dev/null +++ b/tests/data/jl/satisfiability.json @@ -0,0 +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 diff --git a/tests/data/jl/satisfiability_to_coloring3.json b/tests/data/jl/satisfiability_to_coloring3.json new file mode 100644 index 00000000..9034b83d --- /dev/null +++ b/tests/data/jl/satisfiability_to_coloring3.json @@ -0,0 +1 @@ +{"target_type":"Coloring3","cases":[{"label":"sat_col","extracted_single":[[1,1],[0,1],[1,0]],"extracted_multiple":[[1,1],[0,1],[1,0]],"best_source":[[1,0],[0,1],[1,1]],"best_target":[[0,1,2,0,0,1,1,2,1,2,1,0],[0,2,1,2,0,0,2,2,1,2,1,0],[0,2,1,0,0,2,2,2,1,2,1,0],[0,1,2,0,1,1,0,1,2,2,1,0],[0,1,2,0,0,1,1,1,2,2,1,0],[0,2,1,0,0,2,2,1,2,2,1,0],[0,2,1,0,2,2,0,2,1,1,2,0],[0,1,2,0,0,1,1,2,1,1,2,0],[0,2,1,0,0,2,2,2,1,1,2,0],[0,1,2,1,0,0,1,1,2,1,2,0],[0,1,2,0,0,1,1,1,2,1,2,0],[0,2,1,0,0,2,2,1,2,1,2,0],[1,0,2,1,1,0,0,2,0,2,0,1],[1,2,0,2,1,1,2,2,0,2,0,1],[1,2,0,1,1,2,2,2,0,2,0,1],[1,0,2,1,1,0,0,0,2,2,0,1],[1,0,2,1,0,0,1,0,2,2,0,1],[1,2,0,1,1,2,2,0,2,2,0,1],[1,0,2,1,1,0,0,2,0,0,2,1],[1,2,0,1,2,2,1,2,0,0,2,1],[1,2,0,1,1,2,2,2,0,0,2,1],[1,0,2,1,1,0,0,0,2,0,2,1],[1,0,2,0,1,1,0,0,2,0,2,1],[1,2,0,1,1,2,2,0,2,0,2,1],[2,0,1,2,2,0,0,1,0,1,0,2],[2,1,0,2,2,1,1,1,0,1,0,2],[2,1,0,1,2,2,1,1,0,1,0,2],[2,0,1,2,2,0,0,0,1,1,0,2],[2,1,0,2,2,1,1,0,1,1,0,2],[2,0,1,2,0,0,2,0,1,1,0,2],[2,0,1,2,2,0,0,1,0,0,1,2],[2,1,0,2,2,1,1,1,0,0,1,2],[2,1,0,2,1,1,2,1,0,0,1,2],[2,0,1,2,2,0,0,0,1,0,1,2],[2,0,1,0,2,2,0,0,1,0,1,2],[2,1,0,2,2,1,1,0,1,0,1,2]]}],"source_type":"Satisfiability"} \ No newline at end of file diff --git a/tests/data/jl/satisfiability_to_dominatingset.json b/tests/data/jl/satisfiability_to_dominatingset.json new file mode 100644 index 00000000..4b47c9f0 --- /dev/null +++ b/tests/data/jl/satisfiability_to_dominatingset.json @@ -0,0 +1 @@ +{"target_type":"DominatingSet","cases":[{"label":"sat_ds","extracted_single":[[1,1],[0,1],[1,0]],"extracted_multiple":[[1,1],[0,1],[1,0]],"best_source":[[1,0],[0,1],[1,1]],"best_target":[[1,0,0,1,0,0,0],[0,1,0,1,0,0,0],[0,0,1,1,0,0,0],[1,0,0,0,1,0,0],[1,0,0,0,0,1,0]]}],"source_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 new file mode 100644 index 00000000..57a35944 --- /dev/null +++ b/tests/data/jl/satisfiability_to_independentset.json @@ -0,0 +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 diff --git a/tests/data/jl/satisfiability_to_ksatisfiability3.json b/tests/data/jl/satisfiability_to_ksatisfiability3.json new file mode 100644 index 00000000..0196b632 --- /dev/null +++ b/tests/data/jl/satisfiability_to_ksatisfiability3.json @@ -0,0 +1 @@ +{"target_type":"KSatisfiability3","cases":[{"label":"sat_ksat","extracted_single":[[1,0],[0,1],[1,1]],"extracted_multiple":[[1,0],[0,1],[1,1]],"best_source":[[1,0],[0,1],[1,1]],"best_target":[[1,0,0],[0,1,0],[1,1,0],[1,0,1],[0,1,1],[1,1,1]]}],"source_type":"Satisfiability"} \ No newline at end of file diff --git a/tests/data/jl/setcovering.json b/tests/data/jl/setcovering.json new file mode 100644 index 00000000..ca2c3b25 --- /dev/null +++ b/tests/data/jl/setcovering.json @@ -0,0 +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 diff --git a/tests/data/jl/setpacking.json b/tests/data/jl/setpacking.json new file mode 100644 index 00000000..1d6e6579 --- /dev/null +++ b/tests/data/jl/setpacking.json @@ -0,0 +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 diff --git a/tests/data/jl/setpacking_to_independentset.json b/tests/data/jl/setpacking_to_independentset.json new file mode 100644 index 00000000..9ac387f5 --- /dev/null +++ b/tests/data/jl/setpacking_to_independentset.json @@ -0,0 +1 @@ +{"target_type":"IndependentSet","cases":[{"label":"sp","extracted_single":[[0,1,1,0,0],[1,0,0,1,0],[0,0,1,1,0]],"extracted_multiple":[[0,1,1,0,0],[1,0,0,1,0],[0,0,1,1,0]],"best_source":[[0,1,1,0,0],[1,0,0,1,0],[0,0,1,1,0]],"best_target":[[0,1,1,0,0],[1,0,0,1,0],[0,0,1,1,0]]}],"source_type":"SetPacking"} \ No newline at end of file diff --git a/tests/data/jl/spinglass.json b/tests/data/jl/spinglass.json new file mode 100644 index 00000000..7a59c2bf --- /dev/null +++ b/tests/data/jl/spinglass.json @@ -0,0 +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 diff --git a/tests/data/jl/spinglass_to_maxcut.json b/tests/data/jl/spinglass_to_maxcut.json new file mode 100644 index 00000000..9f28d48d --- /dev/null +++ b/tests/data/jl/spinglass_to_maxcut.json @@ -0,0 +1 @@ +{"target_type":"MaxCut","cases":[{"label":"spinglass","extracted_single":[[0,0,1,0,1,1,1,0,0,0],[1,1,0,1,0,0,0,1,1,1]],"extracted_multiple":[[0,0,1,0,1,1,1,0,0,0],[1,1,0,1,0,0,0,1,1,1]],"best_source":[[0,0,1,0,1,1,1,0,0,0],[1,1,0,1,0,0,0,1,1,1]],"best_target":[[0,0,1,0,1,1,1,0,0,0],[1,1,0,1,0,0,0,1,1,1]]}],"source_type":"SpinGlass"} \ No newline at end of file diff --git a/tests/data/jl/spinglass_to_qubo.json b/tests/data/jl/spinglass_to_qubo.json new file mode 100644 index 00000000..6d5a8f01 --- /dev/null +++ b/tests/data/jl/spinglass_to_qubo.json @@ -0,0 +1 @@ +{"target_type":"QUBO","cases":[{"label":"spinglass_qubo","extracted_single":[[0,0,1,0,1,1,1,0,0,0],[1,1,0,1,0,0,0,1,1,1]],"extracted_multiple":[[0,0,1,0,1,1,1,0,0,0],[1,1,0,1,0,0,0,1,1,1]],"best_source":[[0,0,1,0,1,1,1,0,0,0],[1,1,0,1,0,0,0,1,1,1]],"best_target":[[0,0,1,0,1,1,1,0,0,0],[1,1,0,1,0,0,0,1,1,1]]}],"source_type":"SpinGlass"} \ No newline at end of file diff --git a/tests/data/jl/vertexcovering.json b/tests/data/jl/vertexcovering.json new file mode 100644 index 00000000..a46341eb --- /dev/null +++ b/tests/data/jl/vertexcovering.json @@ -0,0 +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 diff --git a/tests/data/jl/vertexcovering_to_setcovering.json b/tests/data/jl/vertexcovering_to_setcovering.json new file mode 100644 index 00000000..b4e8e161 --- /dev/null +++ b/tests/data/jl/vertexcovering_to_setcovering.json @@ -0,0 +1 @@ +{"target_type":"SetCovering","cases":[{"label":"vertexcovering","extracted_single":[[1,0,1,0,1,0,1,1,1,0]],"extracted_multiple":[[1,0,1,0,1,0,1,1,1,0]],"best_source":[[1,0,1,0,1,0,1,1,1,0]],"best_target":[[1,0,1,0,1,0,1,1,1,0]]}],"source_type":"VertexCovering"} \ No newline at end of file diff --git a/tests/main.rs b/tests/main.rs index 50f9b04a..4c93d3f9 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -2,5 +2,7 @@ mod examples; #[path = "suites/integration.rs"] mod integration; +#[path = "suites/jl_parity.rs"] +mod jl_parity; #[path = "suites/reductions.rs"] mod reductions; diff --git a/tests/suites/jl_parity.rs b/tests/suites/jl_parity.rs new file mode 100644 index 00000000..9aae3137 --- /dev/null +++ b/tests/suites/jl_parity.rs @@ -0,0 +1,5 @@ +// Julia parity tests have been moved to unit tests in src/unit_tests/ +// (models and rules files). Each unit test file now includes JL fixture-based +// tests via `include!("../jl_helpers.rs")` and `include_str!` for JSON data. +// +// To run all JL parity tests: cargo test test_jl_parity