Skip to content

Commit 94063e7

Browse files
GiggleLiuclaude
andauthored
Fix #407: [Model] RootedTreeArrangement (#742)
* Add plan for #407: [Model] RootedTreeArrangement * Implement #407: [Model] RootedTreeArrangement * chore: remove plan file after implementation * Fix paper solve command to include --solver brute-force RootedTreeArrangement has no ILP reduction path, so the default pred solve fails. Add --solver brute-force as required. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 63b53e3 commit 94063e7

10 files changed

Lines changed: 456 additions & 8 deletions

File tree

docs/paper/reductions.typ

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@
123123
"DisjointConnectingPaths": [Disjoint Connecting Paths],
124124
"MinimumMultiwayCut": [Minimum Multiway Cut],
125125
"OptimalLinearArrangement": [Optimal Linear Arrangement],
126+
"RootedTreeArrangement": [Rooted Tree Arrangement],
126127
"RuralPostman": [Rural Postman],
127128
"MixedChinesePostman": [Mixed Chinese Postman],
128129
"StackerCrane": [Stacker Crane],
@@ -2026,6 +2027,30 @@ is feasible: each set induces a connected subgraph, the component weights are $2
20262027
]
20272028
]
20282029
}
2030+
#{
2031+
let x = load-model-example("RootedTreeArrangement")
2032+
let nv = graph-num-vertices(x.instance)
2033+
let ne = graph-num-edges(x.instance)
2034+
let edges = x.instance.graph.edges.map(e => (e.at(0), e.at(1)))
2035+
let K = x.instance.bound
2036+
[
2037+
#problem-def("RootedTreeArrangement")[
2038+
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$?
2039+
][
2040+
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.
2041+
2042+
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.
2043+
2044+
*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.
2045+
2046+
#pred-commands(
2047+
"pred create --example RootedTreeArrangement -o rooted-tree-arrangement.json",
2048+
"pred solve rooted-tree-arrangement.json --solver brute-force",
2049+
"pred evaluate rooted-tree-arrangement.json --config " + x.optimal_config.map(str).join(","),
2050+
)
2051+
]
2052+
]
2053+
}
20292054
#{
20302055
let x = load-model-example("KClique")
20312056
let nv = graph-num-vertices(x.instance)

docs/paper/references.bib

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,14 @@ @article{gareyJohnsonStockmeyer1976
277277
year = {1976}
278278
}
279279

280+
@inproceedings{gavril1977,
281+
author = {F. Gavril},
282+
title = {Some {NP}-Complete Problems on Graphs},
283+
booktitle = {Proceedings of the 11th Conference on Information Sciences and Systems},
284+
pages = {91--95},
285+
year = {1977}
286+
}
287+
280288
@article{evenItaiShamir1976,
281289
author = {Shimon Even and Alon Itai and Adi Shamir},
282290
title = {On the Complexity of Timetable and Multicommodity Flow Problems},
@@ -1206,6 +1214,17 @@ @article{hu1961
12061214
doi = {10.1287/opre.9.6.841}
12071215
}
12081216

1217+
@article{adolphsonHu1973,
1218+
author = {Donald Adolphson and Te Chiang Hu},
1219+
title = {Optimal Linear Ordering},
1220+
journal = {SIAM Journal on Applied Mathematics},
1221+
volume = {25},
1222+
number = {3},
1223+
pages = {403--423},
1224+
year = {1973},
1225+
doi = {10.1137/0125040}
1226+
}
1227+
12091228
@inproceedings{kolaitis1998,
12101229
author = {Phokion G. Kolaitis and Moshe Y. Vardi},
12111230
title = {Conjunctive-Query Containment and Constraint Satisfaction},

problemreductions-cli/src/cli.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ Flags by problem type:
272272
MultiprocessorScheduling --lengths, --num-processors, --deadline
273273
SequencingWithinIntervals --release-times, --deadlines, --lengths
274274
OptimalLinearArrangement --graph, --bound
275+
RootedTreeArrangement --graph, --bound
275276
MinMaxMulticenter (pCenter) --graph, --weights, --edge-weights, --k, --bound
276277
MixedChinesePostman (MCPP) --graph, --arcs, --edge-weights, --arc-costs, --bound [--num-vertices]
277278
RuralPostman (RPP) --graph, --edge-weights, --required-edges, --bound
@@ -534,7 +535,7 @@ pub struct CreateArgs {
534535
/// Required edge indices for RuralPostman (comma-separated, e.g., "0,2,4")
535536
#[arg(long)]
536537
pub required_edges: Option<String>,
537-
/// Bound parameter (lower bound for LongestCircuit; upper or length bound for BoundedComponentSpanningForest, LengthBoundedDisjointPaths, LongestCommonSubsequence, MultipleCopyFileAllocation, MultipleChoiceBranching, OptimalLinearArrangement, RuralPostman, ShortestCommonSupersequence, or StringToStringCorrection)
538+
/// Bound parameter (lower bound for LongestCircuit; upper or length bound for BoundedComponentSpanningForest, LengthBoundedDisjointPaths, LongestCommonSubsequence, MultipleCopyFileAllocation, MultipleChoiceBranching, OptimalLinearArrangement, RootedTreeArrangement, RuralPostman, ShortestCommonSupersequence, or StringToStringCorrection)
538539
#[arg(long, allow_hyphen_values = true)]
539540
pub bound: Option<i64>,
540541
/// Upper bound on total path length

problemreductions-cli/src/commands/create.rs

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ use problemreductions::models::graph::{
1515
DisjointConnectingPaths, GeneralizedHex, GraphPartitioning, HamiltonianCircuit,
1616
HamiltonianPath, IntegralFlowBundles, LengthBoundedDisjointPaths, LongestCircuit, LongestPath,
1717
MinimumCutIntoBoundedSets, MinimumDummyActivitiesPert, MinimumMultiwayCut, MixedChinesePostman,
18-
MultipleChoiceBranching, PathConstrainedNetworkFlow, SteinerTree, SteinerTreeInGraphs,
19-
StrongConnectivityAugmentation,
18+
MultipleChoiceBranching, PathConstrainedNetworkFlow, RootedTreeArrangement, SteinerTree,
19+
SteinerTreeInGraphs, StrongConnectivityAugmentation,
2020
};
2121
use problemreductions::models::misc::{
2222
AdditionalKey, BinPacking, BoyceCoddNormalFormViolation, CbqRelation, ConjunctiveBooleanQuery,
@@ -614,6 +614,7 @@ fn example_for(canonical: &str, graph_type: Option<&str>) -> &'static str {
614614
"--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"
615615
}
616616
"OptimalLinearArrangement" => "--graph 0-1,1-2,2-3 --bound 5",
617+
"RootedTreeArrangement" => "--graph 0-1,0-2,1-2,2-3,3-4 --bound 7",
617618
"DirectedTwoCommodityIntegralFlow" => {
618619
"--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"
619620
}
@@ -3185,6 +3186,23 @@ pub fn create(args: &CreateArgs, out: &OutputConfig) -> Result<()> {
31853186
)
31863187
}
31873188

3189+
// RootedTreeArrangement — graph + bound
3190+
"RootedTreeArrangement" => {
3191+
let usage =
3192+
"Usage: pred create RootedTreeArrangement --graph 0-1,0-2,1-2,2-3,3-4 --bound 7";
3193+
let (graph, _) = parse_graph(args).map_err(|e| anyhow::anyhow!("{e}\n\n{usage}"))?;
3194+
let bound_raw = args.bound.ok_or_else(|| {
3195+
anyhow::anyhow!(
3196+
"RootedTreeArrangement requires --bound (upper bound K on total tree stretch)\n\n{usage}"
3197+
)
3198+
})?;
3199+
let bound = parse_nonnegative_usize_bound(bound_raw, "RootedTreeArrangement", usage)?;
3200+
(
3201+
ser(RootedTreeArrangement::new(graph, bound))?,
3202+
resolved_variant.clone(),
3203+
)
3204+
}
3205+
31883206
// FlowShopScheduling
31893207
"FlowShopScheduling" => {
31903208
let task_str = args.task_lengths.as_deref().ok_or_else(|| {
@@ -5963,12 +5981,30 @@ fn create_random(
59635981
(ser(OptimalLinearArrangement::new(graph, bound))?, variant)
59645982
}
59655983

5984+
// RootedTreeArrangement — graph + bound
5985+
"RootedTreeArrangement" => {
5986+
let edge_prob = args.edge_prob.unwrap_or(0.5);
5987+
if !(0.0..=1.0).contains(&edge_prob) {
5988+
bail!("--edge-prob must be between 0.0 and 1.0");
5989+
}
5990+
let graph = util::create_random_graph(num_vertices, edge_prob, args.seed);
5991+
let n = graph.num_vertices();
5992+
let usage = "Usage: pred create RootedTreeArrangement --random --num-vertices 5 [--edge-prob 0.5] [--seed 42] [--bound 10]";
5993+
let bound = args
5994+
.bound
5995+
.map(|b| parse_nonnegative_usize_bound(b, "RootedTreeArrangement", usage))
5996+
.transpose()?
5997+
.unwrap_or((n.saturating_sub(1)) * graph.num_edges());
5998+
let variant = variant_map(&[("graph", "SimpleGraph")]);
5999+
(ser(RootedTreeArrangement::new(graph, bound))?, variant)
6000+
}
6001+
59666002
_ => bail!(
59676003
"Random generation is not supported for {canonical}. \
59686004
Supported: graph-based problems (MIS, MVC, MaxCut, MaxClique, \
59696005
MaximumMatching, MinimumDominatingSet, SpinGlass, KColoring, KClique, TravelingSalesman, \
59706006
BottleneckTravelingSalesman, SteinerTreeInGraphs, HamiltonianCircuit, SteinerTree, \
5971-
OptimalLinearArrangement, HamiltonianPath, LongestCircuit, GeneralizedHex)"
6007+
OptimalLinearArrangement, RootedTreeArrangement, HamiltonianPath, LongestCircuit, GeneralizedHex)"
59726008
),
59736009
};
59746010

problemreductions-cli/tests/cli_tests.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3412,6 +3412,34 @@ fn test_create_ola_rejects_negative_bound() {
34123412
assert!(stderr.contains("nonnegative --bound"), "stderr: {stderr}");
34133413
}
34143414

3415+
#[test]
3416+
fn test_create_rooted_tree_arrangement() {
3417+
let output_file = std::env::temp_dir().join("pred_test_create_rooted_tree_arrangement.json");
3418+
let output = pred()
3419+
.args([
3420+
"-o",
3421+
output_file.to_str().unwrap(),
3422+
"create",
3423+
"RootedTreeArrangement",
3424+
"--graph",
3425+
"0-1,0-2,1-2,2-3,3-4",
3426+
"--bound",
3427+
"7",
3428+
])
3429+
.output()
3430+
.unwrap();
3431+
assert!(
3432+
output.status.success(),
3433+
"stderr: {}",
3434+
String::from_utf8_lossy(&output.stderr)
3435+
);
3436+
let content = std::fs::read_to_string(&output_file).unwrap();
3437+
let json: serde_json::Value = serde_json::from_str(&content).unwrap();
3438+
assert_eq!(json["type"], "RootedTreeArrangement");
3439+
assert_eq!(json["data"]["bound"], 7);
3440+
std::fs::remove_file(&output_file).ok();
3441+
}
3442+
34153443
#[test]
34163444
fn test_create_scs_rejects_negative_bound() {
34173445
let output = pred()

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ pub mod prelude {
6262
MinimumDummyActivitiesPert, MinimumFeedbackArcSet, MinimumFeedbackVertexSet,
6363
MinimumMultiwayCut, MinimumSumMulticenter, MinimumVertexCover, MultipleChoiceBranching,
6464
MultipleCopyFileAllocation, OptimalLinearArrangement, PartitionIntoPathsOfLength2,
65-
PartitionIntoTriangles, PathConstrainedNetworkFlow, RuralPostman,
65+
PartitionIntoTriangles, PathConstrainedNetworkFlow, RootedTreeArrangement, RuralPostman,
6666
ShortestWeightConstrainedPath, SteinerTreeInGraphs, TravelingSalesman,
6767
UndirectedFlowLowerBounds, UndirectedTwoCommodityIntegralFlow,
6868
};

src/models/graph/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
//! - [`BottleneckTravelingSalesman`]: Hamiltonian cycle minimizing the maximum selected edge weight
3535
//! - [`MultipleCopyFileAllocation`]: File-copy placement under storage and access costs
3636
//! - [`OptimalLinearArrangement`]: Optimal linear arrangement (total edge length at most K)
37+
//! - [`RootedTreeArrangement`]: Rooted-tree embedding with bounded total edge stretch
3738
//! - [`MinimumFeedbackArcSet`]: Minimum feedback arc set on directed graphs
3839
//! - [`MinMaxMulticenter`]: Min-max multicenter (vertex p-center, satisfaction)
3940
//! - [`MinimumSumMulticenter`]: Min-sum multicenter (p-median)
@@ -96,6 +97,7 @@ pub(crate) mod optimal_linear_arrangement;
9697
pub(crate) mod partition_into_paths_of_length_2;
9798
pub(crate) mod partition_into_triangles;
9899
pub(crate) mod path_constrained_network_flow;
100+
pub(crate) mod rooted_tree_arrangement;
99101
pub(crate) mod rural_postman;
100102
pub(crate) mod shortest_weight_constrained_path;
101103
pub(crate) mod spin_glass;
@@ -150,6 +152,7 @@ pub use optimal_linear_arrangement::OptimalLinearArrangement;
150152
pub use partition_into_paths_of_length_2::PartitionIntoPathsOfLength2;
151153
pub use partition_into_triangles::PartitionIntoTriangles;
152154
pub use path_constrained_network_flow::PathConstrainedNetworkFlow;
155+
pub use rooted_tree_arrangement::RootedTreeArrangement;
153156
pub use rural_postman::RuralPostman;
154157
pub use shortest_weight_constrained_path::ShortestWeightConstrainedPath;
155158
pub use spin_glass::SpinGlass;
@@ -203,6 +206,7 @@ pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::M
203206
specs.extend(partition_into_triangles::canonical_model_example_specs());
204207
specs.extend(partition_into_paths_of_length_2::canonical_model_example_specs());
205208
specs.extend(path_constrained_network_flow::canonical_model_example_specs());
209+
specs.extend(rooted_tree_arrangement::canonical_model_example_specs());
206210
specs.extend(steiner_tree::canonical_model_example_specs());
207211
specs.extend(steiner_tree_in_graphs::canonical_model_example_specs());
208212
specs.extend(directed_two_commodity_integral_flow::canonical_model_example_specs());

0 commit comments

Comments
 (0)