Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
600 changes: 366 additions & 234 deletions docs/paper/reductions.typ

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion problemreductions-cli/tests/cli_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2821,7 +2821,7 @@ fn test_create_model_example_steiner_tree() {
#[test]
fn test_create_missing_model_example() {
let output = pred()
.args(["create", "--example", "GraphPartitioning/SimpleGraph"])
.args(["create", "--example", "MaximumIndependentSet/KingsSubgraph/One"])
.output()
.unwrap();
assert!(!output.status.success());
Expand Down
25 changes: 25 additions & 0 deletions src/models/graph/graph_partitioning.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,31 @@ crate::declare_variants! {
default opt GraphPartitioning<SimpleGraph> => "2^num_vertices",
}

#[cfg(feature = "example-db")]
pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
use crate::topology::SimpleGraph;
// Two triangles connected by 3 edges; balanced cut = 3
vec![crate::example_db::specs::ModelExampleSpec {
id: "graph_partitioning",
instance: Box::new(GraphPartitioning::new(SimpleGraph::new(
6,
vec![
(0, 1),
(0, 2),
(1, 2),
(1, 3),
(2, 3),
(2, 4),
(3, 4),
(3, 5),
(4, 5),
],
))),
optimal_config: vec![0, 0, 0, 1, 1, 1],
optimal_value: serde_json::json!({"Valid": 3}),
}]
}

#[cfg(test)]
#[path = "../../unit_tests/models/graph/graph_partitioning.rs"]
mod tests;
15 changes: 15 additions & 0 deletions src/models/graph/minimum_feedback_arc_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,21 @@ crate::declare_variants! {
default opt MinimumFeedbackArcSet<i32> => "2^num_vertices",
}

#[cfg(feature = "example-db")]
pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
use crate::topology::DirectedGraph;
// 3-node cycle, unit weights; remove one arc to break cycle, cost = 1
vec![crate::example_db::specs::ModelExampleSpec {
id: "minimum_feedback_arc_set",
instance: Box::new(MinimumFeedbackArcSet::new(
DirectedGraph::new(3, vec![(0, 1), (1, 2), (2, 0)]),
vec![1i32, 1, 1],
)),
optimal_config: vec![0, 0, 1],
optimal_value: serde_json::json!({"Valid": 1}),
}]
}

#[cfg(test)]
#[path = "../../unit_tests/models/graph/minimum_feedback_arc_set.rs"]
mod tests;
5 changes: 5 additions & 0 deletions src/models/graph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,5 +141,10 @@ pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::M
specs.extend(directed_two_commodity_integral_flow::canonical_model_example_specs());
specs.extend(undirected_two_commodity_integral_flow::canonical_model_example_specs());
specs.extend(strong_connectivity_augmentation::canonical_model_example_specs());
specs.extend(rural_postman::canonical_model_example_specs());
specs.extend(graph_partitioning::canonical_model_example_specs());
specs.extend(minimum_feedback_arc_set::canonical_model_example_specs());
specs.extend(optimal_linear_arrangement::canonical_model_example_specs());
specs.extend(subgraph_isomorphism::canonical_model_example_specs());
specs
}
19 changes: 19 additions & 0 deletions src/models/graph/optimal_linear_arrangement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,25 @@ crate::declare_variants! {
default sat OptimalLinearArrangement<SimpleGraph> => "2^num_vertices",
}

#[cfg(feature = "example-db")]
pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
use crate::topology::SimpleGraph;
// 6 vertices, 7 edges (path + two long chords), bound K=11
// Identity permutation [0,1,2,3,4,5] gives cost 1+1+1+1+1+3+3 = 11
vec![crate::example_db::specs::ModelExampleSpec {
id: "optimal_linear_arrangement",
instance: Box::new(OptimalLinearArrangement::new(
SimpleGraph::new(
6,
vec![(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (0, 3), (2, 5)],
),
11,
)),
optimal_config: vec![0, 1, 2, 3, 4, 5],
optimal_value: serde_json::json!(true),
}]
}

#[cfg(test)]
#[path = "../../unit_tests/models/graph/optimal_linear_arrangement.rs"]
mod tests;
31 changes: 31 additions & 0 deletions src/models/graph/rural_postman.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,37 @@ crate::declare_variants! {
default sat RuralPostman<SimpleGraph, i32> => "2^num_vertices * num_vertices^2",
}

#[cfg(feature = "example-db")]
pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
use crate::topology::SimpleGraph;
// Issue #248 instance 1: hexagonal graph, 8 edges, E'={e0,e2,e4}, B=6
// Solution: hexagon cycle with all 6 unit-cost edges, config [1,1,1,1,1,1,0,0]
let graph = SimpleGraph::new(
6,
vec![
(0, 1),
(1, 2),
(2, 3),
(3, 4),
(4, 5),
(5, 0),
(0, 3),
(1, 4),
],
);
vec![crate::example_db::specs::ModelExampleSpec {
id: "rural_postman",
instance: Box::new(RuralPostman::new(
graph,
vec![1, 1, 1, 1, 1, 1, 2, 2],
vec![0, 2, 4],
6,
)),
optimal_config: vec![1, 1, 1, 1, 1, 1, 0, 0],
optimal_value: serde_json::json!(true),
}]
}

#[cfg(test)]
#[path = "../../unit_tests/models/graph/rural_postman.rs"]
mod tests;
15 changes: 15 additions & 0 deletions src/models/graph/subgraph_isomorphism.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,21 @@ crate::declare_variants! {
default sat SubgraphIsomorphism => "num_host_vertices ^ num_pattern_vertices",
}

#[cfg(feature = "example-db")]
pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
use crate::topology::SimpleGraph;
// Host: K4, Pattern: K3 → map [0,1,2] preserves all edges
vec![crate::example_db::specs::ModelExampleSpec {
id: "subgraph_isomorphism",
instance: Box::new(SubgraphIsomorphism::new(
SimpleGraph::new(4, vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]),
SimpleGraph::new(3, vec![(0, 1), (0, 2), (1, 2)]),
)),
optimal_config: vec![0, 1, 2],
optimal_value: serde_json::json!(true),
}]
}

#[cfg(test)]
#[path = "../../unit_tests/models/graph/subgraph_isomorphism.rs"]
mod tests;
11 changes: 11 additions & 0 deletions src/models/misc/bin_packing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,17 @@ crate::declare_variants! {
opt BinPacking<f64> => "2^num_items",
}

#[cfg(feature = "example-db")]
pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
vec![crate::example_db::specs::ModelExampleSpec {
id: "bin_packing",
// 3 items of sizes [3,3,4], capacity 7 → optimal 2 bins
instance: Box::new(BinPacking::<i32>::new(vec![3, 3, 4], 7)),
optimal_config: vec![0, 1, 0],
optimal_value: serde_json::json!({"Valid": 2}),
}]
}

#[cfg(test)]
#[path = "../../unit_tests/models/misc/bin_packing.rs"]
mod tests;
21 changes: 21 additions & 0 deletions src/models/misc/flow_shop_scheduling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,27 @@ crate::declare_variants! {
default sat FlowShopScheduling => "factorial(num_jobs)",
}

#[cfg(feature = "example-db")]
pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
vec![crate::example_db::specs::ModelExampleSpec {
id: "flow_shop_scheduling",
instance: Box::new(FlowShopScheduling::new(
3,
vec![
vec![3, 4, 2],
vec![2, 3, 5],
vec![4, 1, 3],
vec![1, 5, 4],
vec![3, 2, 3],
],
25,
)),
// Job order [3,0,4,2,1] = Lehmer code [3,0,2,1,0], makespan 23
optimal_config: vec![3, 0, 2, 1, 0],
optimal_value: serde_json::json!(true),
}]
}

#[cfg(test)]
#[path = "../../unit_tests/models/misc/flow_shop_scheduling.rs"]
mod tests;
12 changes: 12 additions & 0 deletions src/models/misc/knapsack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,18 @@ mod nonnegative_i64_vec {
}
}

#[cfg(feature = "example-db")]
pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
// 4 items: weights [2,3,4,5], values [3,4,5,7], capacity 7
// Optimal: items 0,3 → weight=7, value=10
vec![crate::example_db::specs::ModelExampleSpec {
id: "knapsack",
instance: Box::new(Knapsack::new(vec![2, 3, 4, 5], vec![3, 4, 5, 7], 7)),
optimal_config: vec![1, 0, 0, 1],
optimal_value: serde_json::json!({"Valid": 10}),
}]
}

#[cfg(test)]
#[path = "../../unit_tests/models/misc/knapsack.rs"]
mod tests;
4 changes: 4 additions & 0 deletions src/models/misc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,5 +86,9 @@ pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::M
specs.extend(sum_of_squares_partition::canonical_model_example_specs());
specs.extend(precedence_constrained_scheduling::canonical_model_example_specs());
specs.extend(sequencing_with_release_times_and_deadlines::canonical_model_example_specs());
specs.extend(flow_shop_scheduling::canonical_model_example_specs());
specs.extend(bin_packing::canonical_model_example_specs());
specs.extend(knapsack::canonical_model_example_specs());
specs.extend(subset_sum::canonical_model_example_specs());
specs
}
8 changes: 4 additions & 4 deletions src/models/misc/sequencing_within_intervals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,11 +180,11 @@ pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::M
vec![crate::example_db::specs::ModelExampleSpec {
id: "sequencing_within_intervals",
instance: Box::new(SequencingWithinIntervals::new(
vec![0, 0, 0, 0, 5],
vec![11, 11, 11, 11, 6],
vec![3, 1, 2, 4, 1],
vec![0, 1, 3, 6, 0],
vec![5, 8, 9, 12, 12],
vec![2, 2, 2, 3, 2],
)),
optimal_config: vec![0, 6, 3, 7, 0],
optimal_config: vec![0, 1, 1, 0, 9],
optimal_value: serde_json::json!(true),
}]
}
Expand Down
6 changes: 3 additions & 3 deletions src/models/misc/shortest_common_supersequence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,10 @@ pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::M
id: "shortest_common_supersequence",
instance: Box::new(ShortestCommonSupersequence::new(
3,
vec![vec![0, 1, 2], vec![1, 0, 2]],
4,
vec![vec![0, 1, 2, 1], vec![1, 2, 0, 1], vec![0, 2, 1, 0]],
7,
)),
optimal_config: vec![0, 1, 0, 2],
optimal_config: vec![0, 1, 2, 0, 2, 1, 0],
optimal_value: serde_json::json!(true),
}]
}
Expand Down
11 changes: 11 additions & 0 deletions src/models/misc/subset_sum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,17 @@ mod decimal_biguint_vec {
}
}

#[cfg(feature = "example-db")]
pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
// 6 elements [3,7,1,8,2,4], target 11 → select {3,8}
vec![crate::example_db::specs::ModelExampleSpec {
id: "subset_sum",
instance: Box::new(SubsetSum::new(vec![3u32, 7, 1, 8, 2, 4], 11u32)),
optimal_config: vec![1, 0, 0, 1, 0, 0],
optimal_value: serde_json::json!(true),
}]
}

#[cfg(test)]
#[path = "../../unit_tests/models/misc/subset_sum.rs"]
mod tests;
33 changes: 33 additions & 0 deletions src/unit_tests/models/graph/rural_postman.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,39 @@ fn test_rural_postman_brute_force_no_solution() {
assert!(result.is_none());
}

#[test]
fn test_rural_postman_find_all_satisfying() {
// Issue #248 instance 1: hexagonal graph, 6 vertices, 8 edges
// Required edges E'={{0,1},{2,3},{4,5}}, B=6
// Search space = 3^8 = 6561
let problem = hexagon_rpp();
let solver = BruteForce::new();
let solutions = solver.find_all_satisfying(&problem);
for sol in &solutions {
assert!(problem.evaluate(sol));
}
// The issue witness (hexagon cycle, all multiplicity 1) must be among solutions
assert!(solutions.contains(&vec![1, 1, 1, 1, 1, 1, 0, 0]));
// Only the hexagon cycle (cost 6 = B) satisfies; diagonals cost 2 each
assert_eq!(solutions.len(), 1);
}

#[test]
fn test_rural_postman_find_all_satisfying_empty() {
// Issue #248 instance 2: required edges {0,1} and {4,5} are far apart
// Minimum circuit cost ≥ 8 > B=4
let graph = SimpleGraph::new(
6,
vec![(0, 1), (1, 2), (2, 3), (3, 0), (3, 4), (4, 5), (5, 3)],
);
let edge_lengths = vec![1, 1, 1, 1, 3, 1, 3];
let required_edges = vec![0, 5];
let bound = 4;
let problem = RuralPostman::new(graph, edge_lengths, required_edges, bound);
let solver = BruteForce::new();
assert!(solver.find_all_satisfying(&problem).is_empty());
}

#[test]
fn test_rural_postman_serialization() {
let problem = chinese_postman_rpp();
Expand Down
36 changes: 36 additions & 0 deletions src/unit_tests/models/misc/flow_shop_scheduling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,42 @@ fn test_flow_shop_scheduling_empty() {
assert!(problem.evaluate(&[]));
}

#[test]
fn test_flow_shop_scheduling_find_all_satisfying() {
// Issue #507 example: 3 machines, 5 jobs, D=25
// Search space = 5! = 120 permutations
let problem = FlowShopScheduling::new(
3,
vec![
vec![3, 4, 2],
vec![2, 3, 5],
vec![4, 1, 3],
vec![1, 5, 4],
vec![3, 2, 3],
],
25,
);
let solver = BruteForce::new();
let solutions = solver.find_all_satisfying(&problem);
for sol in &solutions {
assert!(problem.evaluate(sol));
}
// The issue witness sequence [3,0,4,2,1] = Lehmer code [3,0,2,1,0]
// gives makespan 23 ≤ 25
assert!(solutions.contains(&vec![3, 0, 2, 1, 0]));
// 99 out of 120 permutations have makespan ≤ 25
assert_eq!(solutions.len(), 99);
}

#[test]
fn test_flow_shop_scheduling_find_all_satisfying_empty() {
// 2 machines, 2 symmetric jobs [5,5], deadline 10
// Both orderings give makespan 15 > 10
let problem = FlowShopScheduling::new(2, vec![vec![5, 5], vec![5, 5]], 10);
let solver = BruteForce::new();
assert!(solver.find_all_satisfying(&problem).is_empty());
}

#[test]
fn test_flow_shop_scheduling_single_job() {
// 3 machines, 1 job: [2, 3, 4]
Expand Down
25 changes: 25 additions & 0 deletions src/unit_tests/models/misc/multiprocessor_scheduling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,31 @@ fn test_multiprocessor_scheduling_brute_force_infeasible() {
assert!(solution.is_none());
}

#[test]
fn test_multiprocessor_scheduling_find_all_satisfying() {
// Issue #212 example: 5 tasks [4,5,3,2,6], m=2, D=10
// Search space = 2^5 = 32
let problem = MultiprocessorScheduling::new(vec![4, 5, 3, 2, 6], 2, 10);
let solver = BruteForce::new();
let solutions = solver.find_all_satisfying(&problem);
for sol in &solutions {
assert!(problem.evaluate(sol));
}
// The issue witness {t1,t5} on P0 and {t2,t3,t4} on P1 must be among solutions
assert!(solutions.contains(&vec![0, 1, 1, 1, 0]));
// Only 2 feasible partitions: {t1,t5}/{t2,t3,t4} and its mirror
assert_eq!(solutions.len(), 2);
}

#[test]
fn test_multiprocessor_scheduling_find_all_satisfying_empty() {
// Same instance but deadline 9: total=20, need each processor ≤ 9,
// but 20 > 2*9 = 18, so impossible
let problem = MultiprocessorScheduling::new(vec![4, 5, 3, 2, 6], 2, 9);
let solver = BruteForce::new();
assert!(solver.find_all_satisfying(&problem).is_empty());
}

#[test]
fn test_multiprocessor_scheduling_serialization() {
let problem = MultiprocessorScheduling::new(vec![4, 5, 3, 2, 6], 2, 10);
Expand Down
Loading
Loading