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
65 changes: 65 additions & 0 deletions docs/paper/reductions.typ
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@
"QuantifiedBooleanFormulas": [Quantified Boolean Formulas (QBF)],
"RectilinearPictureCompression": [Rectilinear Picture Compression],
"ResourceConstrainedScheduling": [Resource Constrained Scheduling],
"RootedTreeStorageAssignment": [Rooted Tree Storage Assignment],
"SchedulingWithIndividualDeadlines": [Scheduling With Individual Deadlines],
"SequencingToMinimizeMaximumCumulativeCost": [Sequencing to Minimize Maximum Cumulative Cost],
"SequencingToMinimizeWeightedCompletionTime": [Sequencing to Minimize Weighted Completion Time],
Expand Down Expand Up @@ -2912,6 +2913,70 @@ A classical NP-complete problem from Garey and Johnson @garey1979[Ch.~3, p.~76],
]
}

#{
let x = load-model-example("RootedTreeStorageAssignment")
let n = x.instance.universe_size
let subsets = x.instance.subsets
let m = subsets.len()
let K = x.instance.bound
let config = x.optimal_config
let edges = config.enumerate().filter(((v, p)) => v != p).map(((v, p)) => (p, v))
let fmt-set(s) = "${" + s.map(e => str(e)).join(", ") + "}$"
let highlight-nodes = (0, 2, 4)
let highlight-edges = ((0, 2), (2, 4))
[
#problem-def("RootedTreeStorageAssignment")[
Given a finite set $X = {0, 1, dots, #(n - 1)}$, a collection $cal(C) = {X_1, dots, X_m}$ of subsets of $X$, and a nonnegative integer $K$, find a directed rooted tree $T = (X, A)$ and supersets $X_i' supset.eq X_i$ such that every $X_i'$ forms a directed path in $T$ and $sum_(i = 1)^m |X_i' backslash X_i| <= K$.
][
Rooted Tree Storage Assignment is the storage-and-retrieval problem SR5 in Garey and Johnson @garey1979. Their catalog credits a reduction from Rooted Tree Arrangement, framing the problem as hierarchical file organization: pick a rooted tree on the records so every request set can be completed to a single root-to-leaf path using only a limited number of extra records. The implementation here uses one parent variable per element of $X$, so the direct exhaustive bound is $|X|^(|X|)$ candidate parent arrays, filtered down to valid rooted trees#footnote[No exact algorithm improving on the direct parent-array search bound is claimed here for the general formulation.].

*Example.* Let $X = {0, 1, dots, #(n - 1)}$, $K = #K$, and $cal(C) = {#range(m).map(i => $X_#(i + 1)$).join(", ")}$ with #subsets.enumerate().map(((i, s)) => $X_#(i + 1) = #fmt-set(s)$).join(", "). The satisfying parent array $p = (#config.map(str).join(", "))$ encodes the rooted tree with arcs #edges.map(((u, v)) => $(#u, #v)$).join(", "). In this tree, $X_1 = {0, 2}$, $X_2 = {1, 3}$, and $X_4 = {2, 4}$ are already directed paths. The only extension is $X_3 = {0, 4}$, which becomes $X_3' = {0, 2, 4}$ along the path $0 -> 2 -> 4$, so the total extension cost is exactly $1 = K$.

#pred-commands(
"pred create --example " + problem-spec(x) + " -o rooted-tree-storage-assignment.json",
"pred solve rooted-tree-storage-assignment.json --solver brute-force",
"pred evaluate rooted-tree-storage-assignment.json --config " + x.optimal_config.map(str).join(","),
)

#figure(
canvas(length: 1cm, {
import draw: *

let positions = (
(1.5, 1.8),
(0.6, 0.9),
(2.4, 0.9),
(0.6, 0.0),
(2.4, 0.0),
)

for (u, v) in edges {
let highlighted = highlight-edges.contains((u, v))
line(
positions.at(u),
positions.at(v),
stroke: if highlighted { 1.2pt + graph-colors.at(0) } else { 0.8pt + luma(140) },
mark: (end: "straight", scale: 0.45),
)
}

for (vertex, pos) in positions.enumerate() {
let highlighted = highlight-nodes.contains(vertex)
circle(
pos,
radius: 0.2,
fill: if highlighted { graph-colors.at(0) } else { white },
stroke: 0.6pt + black,
)
content(pos, if highlighted { text(fill: white)[$#vertex$] } else { [$#vertex$] })
}
}),
caption: [Rooted Tree Storage Assignment example. The rooted tree encoded by $p = (#config.map(str).join(", "))$ is shown; the blue path $0 -> 2 -> 4$ is the unique extension needed to realize $X_3 = {0, 4}$ within total cost $K = #K$.],
) <fig:rooted-tree-storage-assignment>
]
]
}

#{
let x = load-model-example("TwoDimensionalConsecutiveSets")
let n = x.instance.alphabet_size
Expand Down
1 change: 1 addition & 0 deletions problemreductions-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ Flags by problem type:
SetBasis --universe, --sets, --k
MinimumCardinalityKey --num-attributes, --dependencies, --k
PrimeAttributeName --universe, --deps, --query
RootedTreeStorageAssignment --universe, --sets, --bound
TwoDimensionalConsecutiveSets --alphabet-size, --sets
BicliqueCover --left, --right, --biedges, --k
BalancedCompleteBipartiteSubgraph --left, --right, --biedges, --k
Expand Down
58 changes: 58 additions & 0 deletions problemreductions-cli/src/commands/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,7 @@ fn example_for(canonical: &str, graph_type: Option<&str>) -> &'static str {
"KColoring" => "--graph 0-1,1-2,2-0 --k 3",
"HamiltonianCircuit" => "--graph 0-1,1-2,2-3,3-0",
"EnsembleComputation" => "--universe 4 --sets \"0,1,2;0,1,3\" --budget 4",
"RootedTreeStorageAssignment" => "--universe 5 --sets \"0,2;1,3;0,4;2,4\" --bound 1",
"MinMaxMulticenter" => {
"--graph 0-1,1-2,2-3 --weights 1,1,1,1 --edge-weights 1,1,1 --k 2 --bound 2"
}
Expand Down Expand Up @@ -2668,6 +2669,31 @@ pub fn create(args: &CreateArgs, out: &OutputConfig) -> Result<()> {
)
}

// RootedTreeStorageAssignment
"RootedTreeStorageAssignment" => {
let usage =
"Usage: pred create RootedTreeStorageAssignment --universe 5 --sets \"0,2;1,3;0,4;2,4\" --bound 1";
let universe_size = args.universe.ok_or_else(|| {
anyhow::anyhow!("RootedTreeStorageAssignment requires --universe\n\n{usage}")
})?;
let subsets = parse_sets(args)?;
let bound = args.bound.ok_or_else(|| {
anyhow::anyhow!("RootedTreeStorageAssignment requires --bound\n\n{usage}")
})?;
let bound = parse_nonnegative_usize_bound(bound, "RootedTreeStorageAssignment", usage)?;
(
ser(
problemreductions::models::set::RootedTreeStorageAssignment::try_new(
universe_size,
subsets,
bound,
)
.map_err(anyhow::Error::msg)?,
)?,
resolved_variant.clone(),
)
}

// BicliqueCover
"BicliqueCover" => {
let usage = "pred create BicliqueCover --left 2 --right 2 --biedges 0-0,0-1,1-1 --k 2";
Expand Down Expand Up @@ -7495,6 +7521,38 @@ mod tests {
assert!(err.contains("ExpectedRetrievalCost requires --latency-bound"));
}

#[test]
fn test_create_rooted_tree_storage_assignment_json() {
let mut args = empty_args();
args.problem = Some("RootedTreeStorageAssignment".to_string());
args.universe = Some(5);
args.sets = Some("0,2;1,3;0,4;2,4".to_string());
args.bound = Some(1);

let output_path =
std::env::temp_dir().join("pred_test_create_rooted_tree_storage_assignment.json");
let out = OutputConfig {
output: Some(output_path.clone()),
quiet: true,
json: false,
auto_json: false,
};

create(&args, &out).unwrap();

let content = std::fs::read_to_string(&output_path).unwrap();
let json: serde_json::Value = serde_json::from_str(&content).unwrap();
assert_eq!(json["type"], "RootedTreeStorageAssignment");
assert_eq!(json["data"]["universe_size"], 5);
assert_eq!(
json["data"]["subsets"],
serde_json::json!([[0, 2], [1, 3], [0, 4], [2, 4]])
);
assert_eq!(json["data"]["bound"], 1);

std::fs::remove_file(output_path).ok();
}

#[test]
fn test_create_stacker_crane_json() {
let mut args = empty_args();
Expand Down
3 changes: 2 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ pub mod prelude {
};
pub use crate::models::set::{
ComparativeContainment, ConsecutiveSets, ExactCoverBy3Sets, MaximumSetPacking,
MinimumCardinalityKey, MinimumHittingSet, MinimumSetCovering, PrimeAttributeName, SetBasis,
MinimumCardinalityKey, MinimumHittingSet, MinimumSetCovering, PrimeAttributeName,
RootedTreeStorageAssignment, SetBasis,
};

// Core traits
Expand Down
4 changes: 2 additions & 2 deletions src/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,6 @@ pub use misc::{
};
pub use set::{
ComparativeContainment, ConsecutiveSets, ExactCoverBy3Sets, MaximumSetPacking,
MinimumCardinalityKey, MinimumHittingSet, MinimumSetCovering, PrimeAttributeName, SetBasis,
TwoDimensionalConsecutiveSets,
MinimumCardinalityKey, MinimumHittingSet, MinimumSetCovering, PrimeAttributeName,
RootedTreeStorageAssignment, SetBasis, TwoDimensionalConsecutiveSets,
};
4 changes: 4 additions & 0 deletions src/models/set/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
//! - [`MinimumHittingSet`]: Minimum-size universe subset hitting every set
//! - [`MinimumSetCovering`]: Minimum weight set cover
//! - [`PrimeAttributeName`]: Determine if an attribute belongs to any candidate key
//! - [`RootedTreeStorageAssignment`]: Extend subsets to directed tree paths within a total-cost bound

pub(crate) mod comparative_containment;
pub(crate) mod consecutive_sets;
Expand All @@ -17,6 +18,7 @@ pub(crate) mod minimum_cardinality_key;
pub(crate) mod minimum_hitting_set;
pub(crate) mod minimum_set_covering;
pub(crate) mod prime_attribute_name;
pub(crate) mod rooted_tree_storage_assignment;
pub(crate) mod set_basis;
pub(crate) mod two_dimensional_consecutive_sets;

Expand All @@ -28,6 +30,7 @@ pub use minimum_cardinality_key::MinimumCardinalityKey;
pub use minimum_hitting_set::MinimumHittingSet;
pub use minimum_set_covering::MinimumSetCovering;
pub use prime_attribute_name::PrimeAttributeName;
pub use rooted_tree_storage_assignment::RootedTreeStorageAssignment;
pub use set_basis::SetBasis;
pub use two_dimensional_consecutive_sets::TwoDimensionalConsecutiveSets;

Expand All @@ -42,6 +45,7 @@ pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::M
specs.extend(minimum_hitting_set::canonical_model_example_specs());
specs.extend(minimum_set_covering::canonical_model_example_specs());
specs.extend(prime_attribute_name::canonical_model_example_specs());
specs.extend(rooted_tree_storage_assignment::canonical_model_example_specs());
specs.extend(set_basis::canonical_model_example_specs());
specs.extend(two_dimensional_consecutive_sets::canonical_model_example_specs());
specs
Expand Down
Loading
Loading