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
64 changes: 64 additions & 0 deletions docs/paper/reductions.typ
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@
"FlowShopScheduling": [Flow Shop Scheduling],
"JobShopScheduling": [Job-Shop Scheduling],
"GroupingBySwapping": [Grouping by Swapping],
"IntegerExpressionMembership": [Integer Expression Membership],
"MinimumCutIntoBoundedSets": [Minimum Cut Into Bounded Sets],
"MinimumDummyActivitiesPert": [Minimum Dummy Activities in PERT Networks],
"MinimumSumMulticenter": [Minimum Sum Multicenter],
Expand Down Expand Up @@ -6810,6 +6811,69 @@ A classical NP-complete problem from Garey and Johnson @garey1979[Ch.~3, p.~76],
]
}

#{
let x = load-model-example("IntegerExpressionMembership")
[
#problem-def("IntegerExpressionMembership")[
Given a recursive integer expression $e$ over union ($union$) and Minkowski sum ($+$) operations on singleton positive integers, and a target $K in NN^+$, determine whether $K in "eval"(e)$, where $"eval"("Atom"(n)) = \{n\}$, $"eval"(F union G) = "eval"(F) union "eval"(G)$, and $"eval"(F + G) = \{m + n : m in "eval"(F), n in "eval"(G)\}$.
][
Integer Expression Membership asks whether a specific integer is reachable by selecting one branch at each union node in a recursive expression tree. Each configuration assigns a binary choice (left or right) at every union node in depth-first order; with all unions resolved, the expression reduces to a chain of sums and atoms that evaluates to a single integer.#footnote[No algorithm improving on brute-force enumeration of all $2^u$ union-branch combinations (where $u$ is the number of union nodes) is known for the general case.]

*Example.* Consider $e = (1 union 4) + (3 union 6) + (2 union 5)$ with $K = 12$. There are $u = 3$ union nodes, giving $2^3 = 8$ configurations. The reachable set is $\{6, 9, 12, 15\}$. Since $12 in \{6, 9, 12, 15\}$, the answer is YES. One witness: choose right ($4$), right ($6$), left ($2$) at the three union nodes, giving $4 + 6 + 2 = 12 = K$.

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

#figure(
canvas(length: 0.7cm, {
import draw: *
// Draw expression tree: Sum( Sum( Union(1,4), Union(3,6) ), Union(2,5) )
let node-r = 0.45
let level-gap = 1.5
let spread = 2.0
// Root: Sum (level 0)
circle((0, 0), radius: node-r, name: "root", fill: rgb("#e8f0fe"))
content("root", text(8pt, $+$))
// Left child: Sum (level 1)
circle((-spread, -level-gap), radius: node-r, name: "lsum", fill: rgb("#e8f0fe"))
content("lsum", text(8pt, $+$))
line("root.south-west", "lsum.north", mark: (end: "straight"))
// Right child: Union (level 1)
circle((spread, -level-gap), radius: node-r, name: "ru", fill: rgb("#fce8e6"))
content("ru", text(8pt, $union$))
line("root.south-east", "ru.north", mark: (end: "straight"))
// Left-Left: Union (level 2)
circle((-spread - 1.2, -2 * level-gap), radius: node-r, name: "llu", fill: rgb("#fce8e6"))
content("llu", text(8pt, $union$))
line("lsum.south-west", "llu.north", mark: (end: "straight"))
// Left-Right: Union (level 2)
circle((-spread + 1.2, -2 * level-gap), radius: node-r, name: "lru", fill: rgb("#fce8e6"))
content("lru", text(8pt, $union$))
line("lsum.south-east", "lru.north", mark: (end: "straight"))
// Leaves (level 3)
let leaf-spread = 0.7
for (parent, vals, xoff) in (
("llu", (1, 4), -spread - 1.2),
("lru", (3, 6), -spread + 1.2),
("ru", (2, 5), spread),
) {
circle((xoff - leaf-spread, -3 * level-gap), radius: node-r, name: parent + "l", fill: rgb("#e6f4ea"))
content(parent + "l", text(8pt, str(vals.at(0))))
line(parent + ".south-west", (parent + "l") + ".north", mark: (end: "straight"))
circle((xoff + leaf-spread, -3 * level-gap), radius: node-r, name: parent + "r", fill: rgb("#e6f4ea"))
content(parent + "r", text(8pt, str(vals.at(1))))
line(parent + ".south-east", (parent + "r") + ".north", mark: (end: "straight"))
}
}),
caption: [Expression tree $e = (1 union 4) + (3 union 6) + (2 union 5)$ with target $K = 12$. Blue nodes are sums ($+$), red nodes are unions ($union$), green leaves are atoms. Choosing right at the first two unions and left at the third yields $4 + 6 + 2 = 12$.],
) <fig:integer-expression-membership>
]
]
}

#{
let x = load-model-example("FeasibleBasisExtension")
let A = x.instance.matrix
Expand Down
4 changes: 4 additions & 0 deletions problemreductions-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ Flags by problem type:
D2CIF --arcs, --capacities, --source-1, --sink-1, --source-2, --sink-2, --requirement-1, --requirement-2
MinimumDummyActivitiesPert --arcs [--num-vertices]
CBQ --domain-size, --relations, --conjuncts-spec
IntegerExpressionMembership --expression (JSON), --target
ILP, CircuitSAT (via reduction only)

Geometry graph variants (use slash notation, e.g., MIS/KingsSubgraph):
Expand Down Expand Up @@ -758,6 +759,9 @@ pub struct CreateArgs {
/// Target string for StringToStringCorrection (comma-separated symbol indices, e.g., "0,1,3,2")
#[arg(long)]
pub target_string: Option<String>,
/// Expression tree for IntegerExpressionMembership (JSON, e.g., '{"Sum":[{"Atom":1},{"Atom":2}]}')
#[arg(long)]
pub expression: Option<String>,
/// Coefficient a for QuadraticDiophantineEquations (coefficient of x²)
#[arg(long)]
pub coeff_a: Option<u64>,
Expand Down
51 changes: 42 additions & 9 deletions problemreductions-cli/src/commands/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ use problemreductions::models::graph::{
use problemreductions::models::misc::{
AdditionalKey, BinPacking, BoyceCoddNormalFormViolation, CapacityAssignment, CbqRelation,
ConjunctiveBooleanQuery, ConsistencyOfDatabaseFrequencyTables, EnsembleComputation,
ExpectedRetrievalCost, FlowShopScheduling, FrequencyTable, GroupingBySwapping,
JobShopScheduling, KnownValue, KthLargestMTuple, LongestCommonSubsequence,
MinimumTardinessSequencing, MultiprocessorScheduling, PaintShop, PartiallyOrderedKnapsack,
ProductionPlanning, QueryArg, RectilinearPictureCompression, ResourceConstrainedScheduling,
SchedulingToMinimizeWeightedCompletionTime, SchedulingWithIndividualDeadlines,
SequencingToMinimizeMaximumCumulativeCost, SequencingToMinimizeWeightedCompletionTime,
SequencingToMinimizeWeightedTardiness, SequencingWithReleaseTimesAndDeadlines,
SequencingWithinIntervals, ShortestCommonSupersequence, StringToStringCorrection, SubsetSum,
SumOfSquaresPartition, ThreePartition, TimetableDesign,
ExpectedRetrievalCost, FlowShopScheduling, FrequencyTable, GroupingBySwapping, IntExpr,
IntegerExpressionMembership, JobShopScheduling, KnownValue, KthLargestMTuple,
LongestCommonSubsequence, MinimumTardinessSequencing, MultiprocessorScheduling, PaintShop,
PartiallyOrderedKnapsack, ProductionPlanning, QueryArg, RectilinearPictureCompression,
ResourceConstrainedScheduling, SchedulingToMinimizeWeightedCompletionTime,
SchedulingWithIndividualDeadlines, SequencingToMinimizeMaximumCumulativeCost,
SequencingToMinimizeWeightedCompletionTime, SequencingToMinimizeWeightedTardiness,
SequencingWithReleaseTimesAndDeadlines, SequencingWithinIntervals, ShortestCommonSupersequence,
StringToStringCorrection, SubsetSum, SumOfSquaresPartition, ThreePartition, TimetableDesign,
};
use problemreductions::models::BiconnectivityAugmentation;
use problemreductions::prelude::*;
Expand Down Expand Up @@ -193,6 +193,7 @@ fn all_data_flags_empty(args: &CreateArgs) -> bool {
&& args.domain_size.is_none()
&& args.relations.is_none()
&& args.conjuncts_spec.is_none()
&& args.expression.is_none()
&& args.deps.is_none()
&& args.query.is_none()
&& args.coeff_a.is_none()
Expand Down Expand Up @@ -735,6 +736,9 @@ fn example_for(canonical: &str, graph_type: Option<&str>) -> &'static str {
}
"IntegerKnapsack" => "--sizes 3,4,5,2,7 --values 4,5,7,3,9 --capacity 15",
"SubsetSum" => "--sizes 3,7,1,8,2,4 --target 11",
"IntegerExpressionMembership" => {
"--expression '{\"Sum\":[{\"Sum\":[{\"Union\":[{\"Atom\":1},{\"Atom\":4}]},{\"Union\":[{\"Atom\":3},{\"Atom\":6}]}]},{\"Union\":[{\"Atom\":2},{\"Atom\":5}]}]}' --target 12"
}
"ThreePartition" => "--sizes 4,5,6,4,6,5 --bound 15",
"KthLargestMTuple" => "--sets \"2,5,8;3,6;1,4,7\" --k 14 --bound 12",
"QuadraticDiophantineEquations" => "--coeff-a 3 --coeff-b 5 --coeff-c 53",
Expand Down Expand Up @@ -2401,6 +2405,34 @@ pub fn create(args: &CreateArgs, out: &OutputConfig) -> Result<()> {
)
}

// IntegerExpressionMembership
"IntegerExpressionMembership" => {
let usage = "Usage: pred create IntegerExpressionMembership --expression '{\"Sum\":[{\"Atom\":1},{\"Atom\":2}]}' --target 3";
let expr_str = args.expression.as_deref().ok_or_else(|| {
anyhow::anyhow!(
"IntegerExpressionMembership requires --expression and --target\n\n{usage}"
)
})?;
let target = args.target.as_deref().ok_or_else(|| {
anyhow::anyhow!("IntegerExpressionMembership requires --target\n\n{usage}")
})?;
let target: u64 = target
.parse()
.context("IntegerExpressionMembership --target must be a positive integer")?;
if target == 0 {
anyhow::bail!("IntegerExpressionMembership --target must be > 0");
}
let expr: IntExpr = serde_json::from_str(expr_str)
.context("IntegerExpressionMembership --expression must be valid JSON representing an IntExpr tree")?;
if !expr.all_atoms_positive() {
anyhow::bail!("IntegerExpressionMembership --expression must contain only positive integers (all Atom values > 0)");
}
(
ser(IntegerExpressionMembership::new(expr, target))?,
resolved_variant.clone(),
)
}

// ThreePartition
"ThreePartition" => {
let sizes_str = args.sizes.as_deref().ok_or_else(|| {
Expand Down Expand Up @@ -7804,6 +7836,7 @@ mod tests {
storage: None,
quantifiers: None,
homologous_pairs: None,
expression: None,
coeff_a: None,
coeff_b: None,
rhs: None,
Expand Down
Loading
Loading