Skip to content
Draft
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
40 changes: 31 additions & 9 deletions docs/paper/reductions.typ

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions docs/paper/references.bib
Original file line number Diff line number Diff line change
Expand Up @@ -2321,3 +2321,14 @@ @techreport{mandersAdleman1976
month = {November},
url = {https://digicoll.lib.berkeley.edu/record/134974/files/ERL-m-615.pdf}
}

@article{axler2019,
author = {Axler, Christian},
title = {New Estimates for the nth Prime Number},
journal = {Journal of Integer Sequences},
volume = {22},
number = {4},
pages = {Article 19.4.2},
year = {2019},
url = {https://www.maths.tcd.ie/EMIS/journals/JIS/VOL22/Axler/axler17.pdf}
}
6 changes: 6 additions & 0 deletions docs/src/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,12 @@ magnitudes. New parameters must describe intrinsic source data independently of
any reduction, and their propagation must be audited on incoming rules. Keep
model-specific definitions and rule-specific formulas beside their implementations.

Avoid registering synonymous aliases. Arithmetic dependence alone does not make a
parameter redundant: keep a meaningful derived quantity when its name makes
formulas clearer or enables useful, sound predictions. Substitute existing
parameters when doing so preserves clarity. Lack of a current formula consumer
alone is not a reason to remove a parameter.

`ReductionParameterDeclarations::fields` stores `(name, relation, expression)` triples.
Use `ParameterTransform::relation(field)` to inspect a formula's accuracy and
`unavailable(field)` for a composition failure and its upstream cause. The uniform
Expand Down
31 changes: 24 additions & 7 deletions problemreductions-cli/tests/cli_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5536,12 +5536,31 @@ fn test_path_set_has_explicit_parameter_information() {
#[test]
fn test_path_overall_unavailable_is_reported_per_field_without_internal_modes() {
let output = pred()
.args(["path", "Factoring", "SpinGlass", "--json"])
.args([
"path",
"ThreePartition",
"QUBO/i64",
"--limit",
"2",
"--json",
])
.output()
.unwrap();
assert!(output.status.success());
let envelope: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap();
let overall = &envelope["paths"][0]["overall_parameters"];
let path = envelope["paths"]
.as_array()
.unwrap()
.iter()
.find(|path| {
path["path"]
.as_array()
.unwrap()
.iter()
.any(|step| step["from"]["name"] == "SequencingWithReleaseTimesAndDeadlines")
})
.expect("time-indexed scheduling path exists");
let overall = &path["overall_parameters"];
let fields = overall["fields"].as_array().unwrap();
assert!(!fields.is_empty());
assert!(fields.iter().all(|field| {
Expand Down Expand Up @@ -5601,7 +5620,7 @@ fn test_path_preserves_exact_variables_and_bounded_quadratic_terms() {
#[test]
fn test_path_overall_preserves_unavailable_fields_alongside_exact_fields() {
let output = pred()
.args(["path", "BMF", "BicliqueCover", "--limit", "1", "--json"])
.args(["path", "Partition", "Knapsack", "--limit", "1", "--json"])
.output()
.unwrap();
assert!(output.status.success());
Expand All @@ -5618,10 +5637,8 @@ fn test_path_overall_preserves_unavailable_fields_alongside_exact_fields() {
)
})
.collect::<std::collections::BTreeMap<_, _>>();
for field in ["num_vertices", "left_size", "right_size", "rank"] {
assert_eq!(relations[field], "exact");
}
assert_eq!(relations["num_edges"], "unavailable");
assert_eq!(relations["num_items"], "exact");
assert_eq!(relations["capacity"], "unavailable");
let unavailable = fields
.iter()
.find(|field| field["relation"] == "unavailable")
Expand Down
8 changes: 8 additions & 0 deletions src/models/algebraic/closest_vector_problem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ impl ClosestVectorProblem {
self.target.len()
}

/// Maximum bit length of an absolute basis or target entry, at least one.
pub fn max_numeric_magnitude_bits(&self) -> u64 {
crate::types::max_numeric_magnitude_bits(
self.basis.iter().flatten().chain(&self.target).copied(),
)
}

/// Integer basis columns.
pub fn basis(&self) -> &[Vec<i64>] {
&self.basis
Expand Down Expand Up @@ -167,6 +174,7 @@ impl Problem for ClosestVectorProblem {
crate::problem_parameters![
("ambient_dimension", ambient_dimension),
("num_basis_vectors", num_basis_vectors),
("max_numeric_magnitude_bits", max_numeric_magnitude_bits),
];

fn evaluate(&self, solution: &Self::Solution) -> Result<Min<i64>, EvaluationError> {
Expand Down
17 changes: 11 additions & 6 deletions src/models/misc/knapsack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,11 @@ impl Knapsack {
self.capacity
}

/// Binary digit count of the capacity, with a minimum of one for zero.
pub fn capacity_bits(&self) -> u64 {
crate::types::max_numeric_magnitude_bits([self.capacity])
}

/// Returns the number of items.
pub fn num_items(&self) -> usize {
self.weights.len()
Expand All @@ -142,11 +147,7 @@ impl Knapsack {
/// For positive capacity this is `floor(log2(C)) + 1`; for zero capacity we
/// keep one slack bit so the encoding shape remains uniform.
pub fn num_slack_bits(&self) -> usize {
if self.capacity == 0 {
1
} else {
self.capacity.ilog2() as usize + 1
}
usize::try_from(self.capacity_bits()).expect("capacity bit length fits usize")
}
}

Expand All @@ -155,7 +156,11 @@ impl Problem for Knapsack {
type Solution = Vec<bool>;
type Value = Max<i64>;

crate::problem_parameters![("capacity", capacity), ("num_items", num_items),];
crate::problem_parameters![
("capacity", capacity),
("capacity_bits", capacity_bits),
("num_items", num_items),
];

fn variant() -> Vec<(&'static str, &'static str)> {
crate::variant_params![]
Expand Down
6 changes: 6 additions & 0 deletions src/models/misc/open_shop_scheduling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,11 @@ impl OpenShopScheduling {
.expect("processing times must fit the brute-force schedule horizon")
}

/// Binary digit count of the schedule horizon, with a minimum of one for zero.
pub fn schedule_horizon_bits(&self) -> u64 {
crate::types::max_numeric_magnitude_bits([self.schedule_horizon()])
}

fn finish_time(
&self,
config: &[usize],
Expand Down Expand Up @@ -238,6 +243,7 @@ impl Problem for OpenShopScheduling {
("num_jobs", num_jobs),
("num_machines", num_machines),
("schedule_horizon", schedule_horizon),
("schedule_horizon_bits", schedule_horizon_bits),
];

fn variant() -> Vec<(&'static str, &'static str)> {
Expand Down
1 change: 0 additions & 1 deletion src/models/set/exact_cover_by_3_sets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,6 @@ impl Problem for ExactCoverBy3Sets {
type Value = crate::types::Or;

crate::problem_parameters![
("num_sets", num_sets),
("num_subsets", num_subsets),
("universe_size", universe_size),
];
Expand Down
11 changes: 10 additions & 1 deletion src/models/set/integer_knapsack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ impl IntegerKnapsack {
self.capacity
}

/// Binary digit count of the capacity, with a minimum of one for zero.
pub fn capacity_bits(&self) -> u64 {
crate::types::max_numeric_magnitude_bits([self.capacity])
}

/// Returns the number of items.
pub fn num_items(&self) -> usize {
self.sizes.len()
Expand All @@ -99,7 +104,11 @@ impl Problem for IntegerKnapsack {
type Solution = Vec<usize>;
type Value = Max<i64>;

crate::problem_parameters![("capacity", capacity), ("num_items", num_items),];
crate::problem_parameters![
("capacity", capacity),
("capacity_bits", capacity_bits),
("num_items", num_items),
];

fn variant() -> Vec<(&'static str, &'static str)> {
crate::variant_params![]
Expand Down
12 changes: 6 additions & 6 deletions src/rules/bmf_bicliquecover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,17 +92,17 @@ impl ReductionResult for ReductionBMFToBicliqueCover {
}
}

#[reduction(
transform = exact {
#[reduction(transform = {
exact {
num_vertices = "rows + cols",
left_size = "rows",
right_size = "cols",
rank = "rank",
},
unavailable = {
num_edges = "the number of true matrix entries is not a registered BMF parameter",
}
)]
upper_bound {
num_edges = "rows * cols",
},
})]
impl ReduceTo<BicliqueCover> for BMF {
type Result = ReductionBMFToBicliqueCover;

Expand Down
12 changes: 5 additions & 7 deletions src/rules/circuit_sat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,13 +307,11 @@ impl ReductionResult for ReductionCircuitSATToSAT {
#[crate::aggregate_reduction(identity)]
impl crate::rules::AggregateReductionResult for ReductionCircuitSATToSAT {}

#[reduction(
transform = unavailable {
num_vars = "the exact Tseitin variable count is specific to this reduction and is not a CircuitSAT parameter",
num_clauses = "the exact Tseitin clause count is specific to this reduction and is not a CircuitSAT parameter",
num_literals = "the exact target parameter is not represented by this reduction's symbolic transform",
}
)]
#[reduction(transform = upper_bound {
num_vars = "num_variables + 2 * num_expression_nodes",
num_clauses = "8 * num_expression_nodes + 2 * num_assignment_outputs",
num_literals = "24 * num_expression_nodes + 4 * num_assignment_outputs",
})]
impl ReduceTo<Satisfiability> for CircuitSAT {
type Result = ReductionCircuitSATToSAT;

Expand Down
8 changes: 5 additions & 3 deletions src/rules/closestvectorproblem_qubo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,9 +231,11 @@ fn dot(left: &[i64], right: &[i64], operation: &str) -> Result<i64, crate::rules
})
}

#[reduction(transform = unavailable {
num_vars = "the exact encoding size depends on the concrete basis and target values",
num_quadratic_terms = "the number of nonzero products depends on the concrete basis coefficients",
// Cofactor bounds give at most r^2 + d + r*h + 3 bits per coefficient,
// where r is the rank, d the ambient dimension, and h the input magnitude bits.
#[reduction(transform = upper_bound {
num_vars = "num_basis_vectors * (num_basis_vectors^2 + ambient_dimension + num_basis_vectors * max_numeric_magnitude_bits + 3)",
num_quadratic_terms = "(num_basis_vectors * (num_basis_vectors^2 + ambient_dimension + num_basis_vectors * max_numeric_magnitude_bits + 3))^2",
})]
impl ReduceTo<QUBO<i64>> for ClosestVectorProblem {
type Result = ReductionCVPToQUBO;
Expand Down
10 changes: 4 additions & 6 deletions src/rules/decisionminimumvertexcover_hamiltoniancircuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,12 +300,10 @@ impl crate::rules::AggregateReductionResult
{
}

#[reduction(
transform = unavailable {
num_vertices = "the construction size depends on the decision threshold, which is not a problem parameter",
num_edges = "the construction size depends on the decision threshold, which is not a problem parameter",
}
)]
#[reduction(transform = upper_bound {
num_vertices = "num_vertices + 12 * num_edges + 3",
num_edges = "(num_vertices + 12 * num_edges + 3)^2",
})]
impl ReduceTo<HamiltonianCircuit<SimpleGraph>> for Decision<MinimumVertexCover<SimpleGraph, One>> {
type Result = ReductionDecisionMinimumVertexCoverToHamiltonianCircuit;

Expand Down
4 changes: 2 additions & 2 deletions src/rules/exactcoverby3sets_algebraicequationsovergf2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ impl crate::rules::AggregateReductionResult for ReductionX3CToAlgebraicEquations

#[reduction(
transform = upper_bound {
num_variables = "num_sets",
num_equations = "universe_size + 9 * num_sets^2",
num_variables = "num_subsets",
num_equations = "universe_size + 9 * num_subsets^2",
})]
impl ReduceTo<AlgebraicEquationsOverGF2> for ExactCoverBy3Sets {
type Result = ReductionX3CToAlgebraicEquationsOverGF2;
Expand Down
12 changes: 6 additions & 6 deletions src/rules/exactcoverby3sets_maximumsetpacking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,14 @@ impl crate::rules::AggregateReductionResult for ReductionXC3SToMaximumSetPacking
}
}

#[reduction(
transform = exact {
#[reduction(transform = {
exact {
num_sets = "num_subsets",
},
unavailable = {
universe_size = "the exact target parameter is not represented by this reduction's symbolic transform",
}
)]
upper_bound {
universe_size = "universe_size",
},
})]
impl ReduceTo<MaximumSetPacking<One>> for ExactCoverBy3Sets {
type Result = ReductionXC3SToMaximumSetPacking;

Expand Down
2 changes: 1 addition & 1 deletion src/rules/exactcoverby3sets_subsetproduct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ impl crate::rules::AggregateReductionResult for ReductionX3CToSubsetProduct {}

#[reduction(
transform = exact {
num_elements = "num_sets",
num_elements = "num_subsets",
})]
impl ReduceTo<SubsetProduct> for ExactCoverBy3Sets {
type Result = ReductionX3CToSubsetProduct;
Expand Down
16 changes: 6 additions & 10 deletions src/rules/factoring_circuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,16 +214,12 @@ fn build_multiplier_cell(
#[crate::aggregate_reduction(identity)]
impl crate::rules::AggregateReductionResult for ReductionFactoringToCircuit {}

#[reduction(
transform = upper_bound {
num_variables = "6 * num_bits_first * num_bits_second + 2 * (num_bits_first + num_bits_second) + 1",
num_assignments = "6 * num_bits_first * num_bits_second + 2 * (num_bits_first + num_bits_second) + 2",
},
unavailable = {
num_assignment_outputs = "the exact target parameter is not represented by this reduction's symbolic transform",
num_expression_nodes = "the exact target parameter is not represented by this reduction's symbolic transform",
}
)]
#[reduction(transform = upper_bound {
num_variables = "6 * num_bits_first * num_bits_second + 2 * (num_bits_first + num_bits_second) + 1",
num_assignments = "6 * num_bits_first * num_bits_second + 2 * (num_bits_first + num_bits_second) + 2",
num_assignment_outputs = "6 * num_bits_first * num_bits_second + 2 * (num_bits_first + num_bits_second) + 2",
num_expression_nodes = "5 * (6 * num_bits_first * num_bits_second + 2 * (num_bits_first + num_bits_second) + 2)",
})]
impl ReduceTo<CircuitSAT> for Factoring {
type Result = ReductionFactoringToCircuit;

Expand Down
2 changes: 1 addition & 1 deletion src/rules/integerknapsack_ilp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ impl ReductionResult for ReductionIntegerKnapsackToILP {
num_constraints = "num_items + 1",
},
upper_bound {
max_constraint_magnitude_bits = "capacity + 1",
max_constraint_magnitude_bits = "capacity_bits",
num_nonzeros = "2 * num_items",
},
})]
Expand Down
16 changes: 6 additions & 10 deletions src/rules/kclique_balancedcompletebipartitesubgraph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,12 @@ impl ReductionResult for ReductionKCliqueToBCBS {
#[crate::aggregate_reduction(identity)]
impl crate::rules::AggregateReductionResult for ReductionKCliqueToBCBS {}

#[reduction(
transform = upper_bound {
left_size = "num_vertices + k * (k - 1) / 2",
right_size = "num_edges + num_vertices - k",
k = "num_vertices + k * (k - 1) / 2 - k",
},
unavailable = {
num_vertices = "the exact target parameter is not represented by this reduction's symbolic transform",
}
)]
#[reduction(transform = upper_bound {
left_size = "num_vertices + k * (k - 1) / 2",
right_size = "num_edges + num_vertices - k",
k = "num_vertices + k * (k - 1) / 2 - k",
num_vertices = "2 * num_vertices + num_edges + k * (k - 1) / 2 - k",
})]
impl ReduceTo<BalancedCompleteBipartiteSubgraph> for KClique<SimpleGraph> {
type Result = ReductionKCliqueToBCBS;

Expand Down
2 changes: 1 addition & 1 deletion src/rules/knapsack_ilp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ impl ReductionResult for ReductionKnapsackToILP {
num_vars = "num_items",
},
upper_bound {
max_constraint_magnitude_bits = "capacity + 1",
max_constraint_magnitude_bits = "capacity_bits",
num_constraints = "num_items + 1",
num_nonzeros = "num_items * 1",
},
Expand Down
10 changes: 7 additions & 3 deletions src/rules/knapsack_qubo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,13 @@ impl ReductionResult for ReductionKnapsackToQUBO {
}
}

#[reduction(transform = unavailable {
num_vars = "the exact piecewise slack-bit count is not representable in the parameter-expression language",
num_quadratic_terms = "the nonzero products depend on item sizes and values",
#[reduction(transform = {
exact {
num_vars = "num_items + capacity_bits",
},
upper_bound {
num_quadratic_terms = "(num_items + capacity_bits)^2",
},
})]
impl ReduceTo<QUBO<i64>> for Knapsack {
type Result = ReductionKnapsackToQUBO;
Expand Down
Loading
Loading