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
25 changes: 25 additions & 0 deletions docs/paper/reductions.typ
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@
"DisjointConnectingPaths": [Disjoint Connecting Paths],
"MinimumMultiwayCut": [Minimum Multiway Cut],
"OptimalLinearArrangement": [Optimal Linear Arrangement],
"RootedTreeArrangement": [Rooted Tree Arrangement],
"RuralPostman": [Rural Postman],
"MixedChinesePostman": [Mixed Chinese Postman],
"StackerCrane": [Stacker Crane],
Expand Down Expand Up @@ -2026,6 +2027,30 @@ is feasible: each set induces a connected subgraph, the component weights are $2
]
]
}
#{
let x = load-model-example("RootedTreeArrangement")
let nv = graph-num-vertices(x.instance)
let ne = graph-num-edges(x.instance)
let edges = x.instance.graph.edges.map(e => (e.at(0), e.at(1)))
let K = x.instance.bound
[
#problem-def("RootedTreeArrangement")[
Given an undirected graph $G = (V, E)$ and a non-negative integer $K$, is there a rooted tree $T = (U, F)$ with $|U| = |V|$ and a bijection $f: V -> U$ such that every edge $\{u, v\} in E$ maps to two nodes lying on a common root-to-leaf path in $T$, and $sum_(\{u, v\} in E) d_T(f(u), f(v)) <= K$?
][
Rooted Tree Arrangement is GT45 in Garey and Johnson @garey1979. It generalizes Optimal Linear Arrangement by allowing the host layout to be any rooted tree rather than a single path. Garey and Johnson cite Gavril's NP-completeness proof via reduction from Optimal Linear Arrangement @gavril1977.

The connection to Optimal Linear Arrangement is immediate: if the rooted tree is restricted to a chain, the stretch objective becomes the linear-arrangement objective. This explains why the two problems live in the same arrangement family. For tree-oriented ordering problems, Adolphson and Hu give a polynomial-time algorithm for optimal linear ordering on trees @adolphsonHu1973, showing that the difficulty here comes from simultaneously choosing both the rooted-tree topology and the vertex-to-node bijection.

*Example.* Consider the graph with $n = #nv$ vertices, $|E| = #ne$ edges, and edge set ${#edges.map(((u, v)) => $(v_#u, v_#v)$).join(", ")}$. With bound $K = #K$, the chain tree encoded by parent array $(0, 0, 1, 2)$ and identity mapping $(0, 1, 2, 3)$ is a valid witness: every listed edge lies on the unique root-to-leaf chain, and the total stretch is $1 + 2 + 1 + 1 = 5 <= #K$. Therefore this canonical instance is a YES instance.

#pred-commands(
"pred create --example RootedTreeArrangement -o rooted-tree-arrangement.json",
"pred solve rooted-tree-arrangement.json --solver brute-force",
"pred evaluate rooted-tree-arrangement.json --config " + x.optimal_config.map(str).join(","),
)
]
]
}
#{
let x = load-model-example("KClique")
let nv = graph-num-vertices(x.instance)
Expand Down
19 changes: 19 additions & 0 deletions docs/paper/references.bib
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,14 @@ @article{gareyJohnsonStockmeyer1976
year = {1976}
}

@inproceedings{gavril1977,
author = {F. Gavril},
title = {Some {NP}-Complete Problems on Graphs},
booktitle = {Proceedings of the 11th Conference on Information Sciences and Systems},
pages = {91--95},
year = {1977}
}

@article{evenItaiShamir1976,
author = {Shimon Even and Alon Itai and Adi Shamir},
title = {On the Complexity of Timetable and Multicommodity Flow Problems},
Expand Down Expand Up @@ -1206,6 +1214,17 @@ @article{hu1961
doi = {10.1287/opre.9.6.841}
}

@article{adolphsonHu1973,
author = {Donald Adolphson and Te Chiang Hu},
title = {Optimal Linear Ordering},
journal = {SIAM Journal on Applied Mathematics},
volume = {25},
number = {3},
pages = {403--423},
year = {1973},
doi = {10.1137/0125040}
}

@inproceedings{kolaitis1998,
author = {Phokion G. Kolaitis and Moshe Y. Vardi},
title = {Conjunctive-Query Containment and Constraint Satisfaction},
Expand Down
3 changes: 2 additions & 1 deletion problemreductions-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ Flags by problem type:
MultiprocessorScheduling --lengths, --num-processors, --deadline
SequencingWithinIntervals --release-times, --deadlines, --lengths
OptimalLinearArrangement --graph, --bound
RootedTreeArrangement --graph, --bound
MinMaxMulticenter (pCenter) --graph, --weights, --edge-weights, --k, --bound
MixedChinesePostman (MCPP) --graph, --arcs, --edge-weights, --arc-costs, --bound [--num-vertices]
RuralPostman (RPP) --graph, --edge-weights, --required-edges, --bound
Expand Down Expand Up @@ -534,7 +535,7 @@ pub struct CreateArgs {
/// Required edge indices for RuralPostman (comma-separated, e.g., "0,2,4")
#[arg(long)]
pub required_edges: Option<String>,
/// Bound parameter (lower bound for LongestCircuit; upper or length bound for BoundedComponentSpanningForest, LengthBoundedDisjointPaths, LongestCommonSubsequence, MultipleCopyFileAllocation, MultipleChoiceBranching, OptimalLinearArrangement, RuralPostman, ShortestCommonSupersequence, or StringToStringCorrection)
/// Bound parameter (lower bound for LongestCircuit; upper or length bound for BoundedComponentSpanningForest, LengthBoundedDisjointPaths, LongestCommonSubsequence, MultipleCopyFileAllocation, MultipleChoiceBranching, OptimalLinearArrangement, RootedTreeArrangement, RuralPostman, ShortestCommonSupersequence, or StringToStringCorrection)
#[arg(long, allow_hyphen_values = true)]
pub bound: Option<i64>,
/// Upper bound on total path length
Expand Down
42 changes: 39 additions & 3 deletions problemreductions-cli/src/commands/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ use problemreductions::models::graph::{
DisjointConnectingPaths, GeneralizedHex, GraphPartitioning, HamiltonianCircuit,
HamiltonianPath, IntegralFlowBundles, LengthBoundedDisjointPaths, LongestCircuit, LongestPath,
MinimumCutIntoBoundedSets, MinimumDummyActivitiesPert, MinimumMultiwayCut, MixedChinesePostman,
MultipleChoiceBranching, PathConstrainedNetworkFlow, SteinerTree, SteinerTreeInGraphs,
StrongConnectivityAugmentation,
MultipleChoiceBranching, PathConstrainedNetworkFlow, RootedTreeArrangement, SteinerTree,
SteinerTreeInGraphs, StrongConnectivityAugmentation,
};
use problemreductions::models::misc::{
AdditionalKey, BinPacking, BoyceCoddNormalFormViolation, CbqRelation, ConjunctiveBooleanQuery,
Expand Down Expand Up @@ -614,6 +614,7 @@ fn example_for(canonical: &str, graph_type: Option<&str>) -> &'static str {
"--arcs \"0>1,0>2,1>3,1>4,2>4,2>5,3>5,4>5\" --weights 2,3,2,1,3,1 --arc-costs 1,1,1,1,1,1,1,1 --weight-bound 5 --cost-bound 5"
}
"OptimalLinearArrangement" => "--graph 0-1,1-2,2-3 --bound 5",
"RootedTreeArrangement" => "--graph 0-1,0-2,1-2,2-3,3-4 --bound 7",
"DirectedTwoCommodityIntegralFlow" => {
"--arcs \"0>2,0>3,1>2,1>3,2>4,2>5,3>4,3>5\" --capacities 1,1,1,1,1,1,1,1 --source-1 0 --sink-1 4 --source-2 1 --sink-2 5 --requirement-1 1 --requirement-2 1"
}
Expand Down Expand Up @@ -3185,6 +3186,23 @@ pub fn create(args: &CreateArgs, out: &OutputConfig) -> Result<()> {
)
}

// RootedTreeArrangement — graph + bound
"RootedTreeArrangement" => {
let usage =
"Usage: pred create RootedTreeArrangement --graph 0-1,0-2,1-2,2-3,3-4 --bound 7";
let (graph, _) = parse_graph(args).map_err(|e| anyhow::anyhow!("{e}\n\n{usage}"))?;
let bound_raw = args.bound.ok_or_else(|| {
anyhow::anyhow!(
"RootedTreeArrangement requires --bound (upper bound K on total tree stretch)\n\n{usage}"
)
})?;
let bound = parse_nonnegative_usize_bound(bound_raw, "RootedTreeArrangement", usage)?;
(
ser(RootedTreeArrangement::new(graph, bound))?,
resolved_variant.clone(),
)
}

// FlowShopScheduling
"FlowShopScheduling" => {
let task_str = args.task_lengths.as_deref().ok_or_else(|| {
Expand Down Expand Up @@ -5963,12 +5981,30 @@ fn create_random(
(ser(OptimalLinearArrangement::new(graph, bound))?, variant)
}

// RootedTreeArrangement — graph + bound
"RootedTreeArrangement" => {
let edge_prob = args.edge_prob.unwrap_or(0.5);
if !(0.0..=1.0).contains(&edge_prob) {
bail!("--edge-prob must be between 0.0 and 1.0");
}
let graph = util::create_random_graph(num_vertices, edge_prob, args.seed);
let n = graph.num_vertices();
let usage = "Usage: pred create RootedTreeArrangement --random --num-vertices 5 [--edge-prob 0.5] [--seed 42] [--bound 10]";
let bound = args
.bound
.map(|b| parse_nonnegative_usize_bound(b, "RootedTreeArrangement", usage))
.transpose()?
.unwrap_or((n.saturating_sub(1)) * graph.num_edges());
let variant = variant_map(&[("graph", "SimpleGraph")]);
(ser(RootedTreeArrangement::new(graph, bound))?, variant)
}

_ => bail!(
"Random generation is not supported for {canonical}. \
Supported: graph-based problems (MIS, MVC, MaxCut, MaxClique, \
MaximumMatching, MinimumDominatingSet, SpinGlass, KColoring, KClique, TravelingSalesman, \
BottleneckTravelingSalesman, SteinerTreeInGraphs, HamiltonianCircuit, SteinerTree, \
OptimalLinearArrangement, HamiltonianPath, LongestCircuit, GeneralizedHex)"
OptimalLinearArrangement, RootedTreeArrangement, HamiltonianPath, LongestCircuit, GeneralizedHex)"
),
};

Expand Down
28 changes: 28 additions & 0 deletions problemreductions-cli/tests/cli_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3412,6 +3412,34 @@ fn test_create_ola_rejects_negative_bound() {
assert!(stderr.contains("nonnegative --bound"), "stderr: {stderr}");
}

#[test]
fn test_create_rooted_tree_arrangement() {
let output_file = std::env::temp_dir().join("pred_test_create_rooted_tree_arrangement.json");
let output = pred()
.args([
"-o",
output_file.to_str().unwrap(),
"create",
"RootedTreeArrangement",
"--graph",
"0-1,0-2,1-2,2-3,3-4",
"--bound",
"7",
])
.output()
.unwrap();
assert!(
output.status.success(),
"stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let content = std::fs::read_to_string(&output_file).unwrap();
let json: serde_json::Value = serde_json::from_str(&content).unwrap();
assert_eq!(json["type"], "RootedTreeArrangement");
assert_eq!(json["data"]["bound"], 7);
std::fs::remove_file(&output_file).ok();
}

#[test]
fn test_create_scs_rejects_negative_bound() {
let output = pred()
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ pub mod prelude {
MinimumDummyActivitiesPert, MinimumFeedbackArcSet, MinimumFeedbackVertexSet,
MinimumMultiwayCut, MinimumSumMulticenter, MinimumVertexCover, MultipleChoiceBranching,
MultipleCopyFileAllocation, OptimalLinearArrangement, PartitionIntoPathsOfLength2,
PartitionIntoTriangles, PathConstrainedNetworkFlow, RuralPostman,
PartitionIntoTriangles, PathConstrainedNetworkFlow, RootedTreeArrangement, RuralPostman,
ShortestWeightConstrainedPath, SteinerTreeInGraphs, TravelingSalesman,
UndirectedFlowLowerBounds, UndirectedTwoCommodityIntegralFlow,
};
Expand Down
4 changes: 4 additions & 0 deletions src/models/graph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
//! - [`BottleneckTravelingSalesman`]: Hamiltonian cycle minimizing the maximum selected edge weight
//! - [`MultipleCopyFileAllocation`]: File-copy placement under storage and access costs
//! - [`OptimalLinearArrangement`]: Optimal linear arrangement (total edge length at most K)
//! - [`RootedTreeArrangement`]: Rooted-tree embedding with bounded total edge stretch
//! - [`MinimumFeedbackArcSet`]: Minimum feedback arc set on directed graphs
//! - [`MinMaxMulticenter`]: Min-max multicenter (vertex p-center, satisfaction)
//! - [`MinimumSumMulticenter`]: Min-sum multicenter (p-median)
Expand Down Expand Up @@ -96,6 +97,7 @@ pub(crate) mod optimal_linear_arrangement;
pub(crate) mod partition_into_paths_of_length_2;
pub(crate) mod partition_into_triangles;
pub(crate) mod path_constrained_network_flow;
pub(crate) mod rooted_tree_arrangement;
pub(crate) mod rural_postman;
pub(crate) mod shortest_weight_constrained_path;
pub(crate) mod spin_glass;
Expand Down Expand Up @@ -150,6 +152,7 @@ pub use optimal_linear_arrangement::OptimalLinearArrangement;
pub use partition_into_paths_of_length_2::PartitionIntoPathsOfLength2;
pub use partition_into_triangles::PartitionIntoTriangles;
pub use path_constrained_network_flow::PathConstrainedNetworkFlow;
pub use rooted_tree_arrangement::RootedTreeArrangement;
pub use rural_postman::RuralPostman;
pub use shortest_weight_constrained_path::ShortestWeightConstrainedPath;
pub use spin_glass::SpinGlass;
Expand Down Expand Up @@ -203,6 +206,7 @@ pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::M
specs.extend(partition_into_triangles::canonical_model_example_specs());
specs.extend(partition_into_paths_of_length_2::canonical_model_example_specs());
specs.extend(path_constrained_network_flow::canonical_model_example_specs());
specs.extend(rooted_tree_arrangement::canonical_model_example_specs());
specs.extend(steiner_tree::canonical_model_example_specs());
specs.extend(steiner_tree_in_graphs::canonical_model_example_specs());
specs.extend(directed_two_commodity_integral_flow::canonical_model_example_specs());
Expand Down
Loading
Loading