Skip to content
Open
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 2 additions & 2 deletions docs/paper/reductions.typ
Original file line number Diff line number Diff line change
Expand Up @@ -2228,7 +2228,7 @@ In all graph problems below, $G = (V, E)$ denotes an undirected graph with $|V|
import draw: *
let blue = graph-colors.at(0)
let gray = luma(185)
// Layout for issue #813: path 0–1–3–2–4–5 with clear bends so $v_0,v_1,v_3$ are not collinear
// Bend the path so $v_0,v_1,v_3$ are not collinear.
// (otherwise $(0,3)$ lies on the same geometric line as $0 arrow.r 1 arrow.r 3$).
let verts = (
(0, 0),
Expand Down Expand Up @@ -17146,7 +17146,7 @@ The following table shows concrete target-variable counts for example instances,
_Solution extraction._ First $n$ variables (sign variables) give the source configuration.
]

// === Non-ILP reduction rules (issue #974) ===
// === Non-ILP reduction rules ===

#reduction-rule("ILP", "ILP")[
ILP variants convert between binary and bounded integer variable domains and between exact-integer and floating-point coefficients. The independent bounds dimension defaults to general; the bounded integer variant requires finite lower and upper endpoints for every variable. Binary variables embed into bounded integer ILP, which embeds into general integer ILP while retaining all stored bounds. A bounded integer variable is encoded by binary variables with truncated positional weights. Integer coefficients are embedded only when every stored coefficient and right-hand side has an exact `f64` representation.
Expand Down
1 change: 0 additions & 1 deletion problemreductions-cli/src/bin/pred_sym.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,6 @@ fn main() {
})
.collect();

// Check for unbound variables
let expr_vars = parsed.variables();
let bound_vars: std::collections::HashSet<&str> =
bindings.iter().map(|(name, _)| name.as_str()).collect();
Expand Down
4 changes: 1 addition & 3 deletions problemreductions-cli/src/commands/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -353,9 +353,7 @@ pub fn create(args: &CreateArgs, out: &OutputConfig) -> Result<()> {
return create_registered_random(args, canonical, &resolved_variant, out);
}

// ILP and CircuitSAT have complex input structures
// not suited for CLI flags. Check before the empty-flags help so they get a
// clear message.
// ILP and CircuitSAT have complex input structures not suited for CLI flags.
if canonical == "ILP" || canonical == "CircuitSAT" {
bail!(
"CLI creation is not yet supported for {canonical}.\n\n\
Expand Down
1 change: 0 additions & 1 deletion problemreductions-cli/src/commands/create/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -778,7 +778,6 @@ fn test_create_staff_scheduling_reports_invalid_schedule_without_panic() {
let result = std::panic::catch_unwind(|| create(&args, &out));
assert!(result.is_ok(), "create should return an error, not panic");
let err = result.unwrap().unwrap_err().to_string();
// parse_bool_rows catches ragged rows before validate_staff_scheduling_args
assert!(
err.contains("All rows") || err.contains("schedule 1 has 6 periods, expected 7"),
"expected row-length validation error, got: {err}"
Expand Down
1 change: 0 additions & 1 deletion problemreductions-cli/src/commands/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1259,7 +1259,6 @@ pub fn neighbors(
TraversalFlow::Both => "both directions",
};

// Build tree structure via BFS with parent tracking
let tree = graph.k_neighbor_tree(&spec_name, &variant, max_hops, direction);

out.emit(
Expand Down
1 change: 0 additions & 1 deletion problemreductions-cli/src/commands/inspect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ pub fn inspect(input: &Path, out: &OutputConfig) -> Result<()> {
let content = read_input(input)?;
let json: serde_json::Value = serde_json::from_str(&content)?;

// Detect if it's a bundle or a problem
if json.get("source").is_some() && json.get("target").is_some() && json.get("path").is_some() {
let bundle: ReductionBundle = serde_json::from_value(json)?;
inspect_bundle(&bundle, out)
Expand Down
1 change: 0 additions & 1 deletion problemreductions-cli/src/mcp/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,6 @@ fn deterministic_solver_dispatch_mcp_output_is_repeatable_for_each_solver_class(
fn test_solve_bundle() {
let server = McpServer::new();
let problem_json = create_test_mis(&server);
// Reduce first, then solve the bundle
let bundle_json = server
.reduce_inner(
&problem_json,
Expand Down
3 changes: 0 additions & 3 deletions problemreductions-cli/src/mcp/tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,6 @@ impl McpServer {
)
})?;

// Check for random generation
let is_random = params
.get("random")
.and_then(|v| v.as_bool())
Expand Down Expand Up @@ -363,7 +362,6 @@ impl McpServer {
pub fn inspect_problem_inner(&self, problem_json: &str) -> anyhow::Result<String> {
let json: serde_json::Value = serde_json::from_str(problem_json)?;

// Detect if it's a bundle or a problem
if json.get("source").is_some()
&& json.get("target").is_some()
&& json.get("path").is_some()
Expand Down Expand Up @@ -442,7 +440,6 @@ impl McpServer {
let timeout_secs = u64::try_from(timeout.unwrap_or(0))
.map_err(|_| anyhow::anyhow!("timeout must be a nonnegative i64"))?;

// Detect if it's a bundle or a problem
let is_bundle = json.get("source").is_some()
&& json.get("target").is_some()
&& json.get("path").is_some();
Expand Down
4 changes: 0 additions & 4 deletions problemreductions-cli/src/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ pub fn format_table(
rows: &[Vec<String>],
color_fns: &[Option<CellFormatter>],
) -> String {
// Compute actual column widths (max of header width, specified width, and data width)
let widths: Vec<usize> = columns
.iter()
.enumerate()
Expand All @@ -127,7 +126,6 @@ pub fn format_table(

let mut text = String::new();

// Header
text.push_str(" ");
for (i, (header, align, _)) in columns.iter().enumerate() {
if i > 0 {
Expand All @@ -140,7 +138,6 @@ pub fn format_table(
}
text.push('\n');

// Separator
text.push_str(" ");
for (i, _) in columns.iter().enumerate() {
if i > 0 {
Expand All @@ -150,7 +147,6 @@ pub fn format_table(
}
text.push('\n');

// Data rows
for row in rows {
text.push_str(" ");
for (i, (_, align, _)) in columns.iter().enumerate() {
Expand Down
21 changes: 0 additions & 21 deletions problemreductions-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,15 +206,6 @@ fn option_inner_type(ty: &Type) -> Option<&Type> {
/// - `transform = { exact { ... }, upper_bound { ... }, unavailable { ... } }` — per-field relations
/// - `transform = unavailable { field = "reason", ... }` — no symbolic parameter transform
/// - `unavailable = { field = "reason", ... }` — fields that cannot be propagated
///
/// ## Syntax
/// ```ignore
/// #[reduction(transform = exact {
/// num_vars = "num_vertices^2",
/// num_constraints = num_edges,
/// })]
/// ```
///
#[proc_macro_attribute]
pub fn reduction(attr: TokenStream, item: TokenStream) -> TokenStream {
let attrs = parse_macro_input!(attr as ReductionAttrs);
Expand Down Expand Up @@ -598,28 +589,22 @@ fn generate_reduction_entry(
attrs: &ReductionAttrs,
impl_block: &ItemImpl,
) -> syn::Result<TokenStream2> {
// Extract the trait path (should be ReduceTo<Target>)
let trait_path = impl_block
.trait_
.as_ref()
.map(|(path, _)| path)
.ok_or_else(|| syn::Error::new_spanned(impl_block, "Expected impl ReduceTo<T> for S"))?;

// Extract target type from ReduceTo<Target>
let target_type = extract_target_from_trait(trait_path)?;

// Extract source type (Self type)
let source_type = &impl_block.self_ty;

// Get type names
let source_name = extract_type_name(source_type)
.ok_or_else(|| syn::Error::new_spanned(source_type, "Cannot extract source type name"))?;
let target_name = extract_type_name(&target_type)
.ok_or_else(|| syn::Error::new_spanned(&target_type, "Cannot extract target type name"))?;
// Collect generic parameter info from the impl block
let type_generics = collect_type_generic_names(&impl_block.generics);

// Generate variant fn bodies
let source_variant_body = make_variant_fn_body(source_type, &type_generics)?;
let target_variant_body = make_variant_fn_body(&target_type, &type_generics)?;

Expand Down Expand Up @@ -652,7 +637,6 @@ fn generate_reduction_entry(
.iter()
.map(|(field, reason)| quote! { crate::rules::registry::UnavailableParameterField { field: #field, reason: #reason } });

// Generate the combined output
let output = quote! {
#impl_block

Expand Down Expand Up @@ -926,8 +910,6 @@ pub fn register_brute_force(input: TokenStream) -> TokenStream {

/// Generate code for all `declare_variants!` entries.
fn generate_declare_variants(input: &DeclareVariantsInput) -> syn::Result<TokenStream2> {
// Validate default markers per problem name.
// Group entries by their base type name (e.g., "MaximumIndependentSet").
let mut defaults_per_problem: HashMap<String, Vec<usize>> = HashMap::new();
let mut problem_names = HashSet::new();
for (i, entry) in input.entries.iter().enumerate() {
Expand All @@ -938,7 +920,6 @@ fn generate_declare_variants(input: &DeclareVariantsInput) -> syn::Result<TokenS
}
}

// Check for multiple defaults for the same problem
for (name, indices) in &defaults_per_problem {
if indices.len() > 1 {
return Err(syn::Error::new(
Expand Down Expand Up @@ -973,15 +954,13 @@ fn generate_declare_variants(input: &DeclareVariantsInput) -> syn::Result<TokenS
let is_default = entry.is_default;
let alias_lits: Vec<_> = entry.aliases.iter().map(|s| s.value()).collect();

// Parse the complexity expression to validate syntax
let parsed = problemreductions_expr::Expr::try_parse(&complexity_str).map_err(|e| {
syn::Error::new(
entry.complexity.span(),
format!("invalid complexity expression \"{complexity_str}\": {e}"),
)
})?;

// Generate a compiled complexity evaluator over the problem-owned parameters.
let complexity_eval_fn = generate_complexity_eval_fn(&parsed, ty)?;

let construction_fields = if let Some(create_spec) = create_spec {
Expand Down
1 change: 0 additions & 1 deletion scripts/generate_reduction_graph_layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ function buildElkGraph(model) {
"elk.algorithm": "stress",
"elk.stress.desiredEdgeLength": String(STRESS_PARAMS.desiredEdgeLength),
"elk.spacing.nodeNode": String(STRESS_PARAMS.nodeNodeSpacing),
// cytoscape-elk accepts this shorter key; keep it for parity with the old browser config.
"nodeNode.spacing": String(STRESS_PARAMS.nodeNodeSpacing),
},
children: nodeElements.map((element) => {
Expand Down
1 change: 0 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ pub mod prelude {
};
}

// Re-export commonly used items at crate root
pub use big_o::big_o_normal_form;
pub use error::{ProblemError, Result};
pub use expr::{
Expand Down
6 changes: 0 additions & 6 deletions src/models/algebraic/algebraic_equations_over_gf2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ impl AlgebraicEquationsOverGF2 {
) -> Result<(), crate::registry::ConstructionError> {
for (eq_idx, equation) in equations.iter().enumerate() {
for (mono_idx, monomial) in equation.iter().enumerate() {
// Check variable indices are in range
for &var in monomial {
if var >= num_variables {
return Err(format!(
Expand All @@ -84,7 +83,6 @@ impl AlgebraicEquationsOverGF2 {
.into());
}
}
// Check monomial is sorted and has no duplicates
for w in monomial.windows(2) {
if w[0] >= w[1] {
return Err(format!(
Expand Down Expand Up @@ -225,17 +223,13 @@ pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::M
AlgebraicEquationsOverGF2::new(
3,
vec![
// x0*x1 + x2 = 0
vec![vec![0, 1], vec![2]],
// x1*x2 + x0 + 1 = 0
vec![vec![1, 2], vec![0], vec![]],
// x0 + x1 + x2 + 1 = 0
vec![vec![0], vec![1], vec![2], vec![]],
],
)
.unwrap(),
),
// config [1,0,0]: eq1: 0*0+0=0 ✓, eq2: 0*0+1+1=0 ✓, eq3: 1+0+0+1=0 ✓
optimal_config: serde_json::json!(vec![true, false, false]),
optimal_value: serde_json::json!(true),
}]
Expand Down
2 changes: 0 additions & 2 deletions src/models/algebraic/bmf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,8 +298,6 @@ pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::M
],
2,
)),
// B = [[1,0],[1,1],[0,1]], C = [[1,1,0],[0,1,1]].
// Total 1s: 4 in B + 4 in C = 8, and B * C = A exactly.
optimal_config: serde_json::json!((
vec![vec![true, false], vec![true, true], vec![false, true]],
vec![vec![true, true, false], vec![false, true, true]]
Expand Down
2 changes: 0 additions & 2 deletions src/models/algebraic/consecutive_block_minimization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,8 +275,6 @@ fn validate_matrix_dimensions(

#[cfg(feature = "example-db")]
pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
// Adjacency matrix of path graph P_6, K=6 (one block per row).
// Issue #420 Instance 2.
vec![crate::example_db::specs::ModelExampleSpec {
id: "consecutive_block_minimization",
instance: Box::new(ConsecutiveBlockMinimization::new(
Expand Down
4 changes: 1 addition & 3 deletions src/models/algebraic/consecutive_ones_submatrix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,6 @@ impl Problem for ConsecutiveOnesSubmatrix {
"column-selection length does not match the matrix".into(),
));
}
// Collect selected column indices
let selected: Vec<usize> = config
.iter()
.enumerate()
Expand Down Expand Up @@ -249,8 +248,7 @@ crate::register_brute_force! {
pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
vec![crate::example_db::specs::ModelExampleSpec {
id: "consecutive_ones_submatrix",
// Tucker matrix (3×4): full matrix lacks C1P, but K=3 works
// Select columns {0,1,3} (config [1,1,0,1])
// A Tucker obstruction forces the witness to omit a column.
instance: Box::new(ConsecutiveOnesSubmatrix::new(
vec![
vec![true, true, false, true],
Expand Down
11 changes: 0 additions & 11 deletions src/models/algebraic/equilibrium_point.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,6 @@ impl Problem for EquilibriumPoint {
if alt == solution[i] {
continue;
}
// Build alternative assignment with player i using alt.
let mut alt_assignment = solution.clone();
alt_assignment[i] = alt;
let alt_payoff = self.eval_payoff(i, &alt_assignment)?;
Expand Down Expand Up @@ -262,16 +261,6 @@ crate::register_brute_force! {

#[cfg(feature = "example-db")]
pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
// 3 players, M_i = {0, 1} for all i.
// F1 = x1*x2*x3: factors [[0,1,0,0],[0,0,1,0],[0,0,0,1]]
// F2 = (1-x1)*x2: factors [[1,-1,0,0],[0,0,1,0]]
// F3 = x1*(1-x3): factors [[0,1,0,0],[1,0,0,-1]]
//
// config [0,1,0] → assignment (0,1,0).
// F1(0,1,0) = 0*1*0 = 0. Deviations for player 1: y'=1 → F1(1,1,0)=0. No improvement.
// F2(0,1,0) = (1-0)*1 = 1. Deviations for player 2: y'=0 → F2(0,0,0)=0. No improvement.
// F3(0,1,0) = 0*(1-0) = 0. Deviations for player 3: y'=1 → F3(0,1,1)=0. No improvement.
// → (0,1,0) is a Nash equilibrium.
let polynomials = vec![
vec![vec![0, 1, 0, 0], vec![0, 0, 1, 0], vec![0, 0, 0, 1]],
vec![vec![1, -1, 0, 0], vec![0, 0, 1, 0]],
Expand Down
4 changes: 1 addition & 3 deletions src/models/algebraic/feasible_basis_extension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,6 @@ impl Problem for FeasibleBasisExtension {
let s = self.num_required();
let needed = m - s;

// Count selected free columns
let selected_free: Vec<usize> = config
.iter()
.enumerate()
Expand Down Expand Up @@ -450,7 +449,6 @@ crate::register_brute_force! {
pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
vec![crate::example_db::specs::ModelExampleSpec {
id: "feasible_basis_extension",
// 3x6 matrix, rhs=[7,5,3], required={0,1}, select col 2 -> B={0,1,2}, x=(4,5,3)>=0
instance: Box::new(FeasibleBasisExtension::new(
vec![
vec![1, 0, 1, 2, -1, 0],
Expand All @@ -460,7 +458,7 @@ pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::M
vec![7, 5, 3],
vec![0, 1],
)),
optimal_config: serde_json::json!(vec![true, false, false, false]), // select col 2 (first free column)
optimal_config: serde_json::json!(vec![true, false, false, false]),
optimal_value: serde_json::json!(true),
}]
}
Expand Down
2 changes: 0 additions & 2 deletions src/models/algebraic/minimum_matrix_cover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,6 @@ crate::register_brute_force! {

#[cfg(feature = "example-db")]
pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
// 4×4 symmetric matrix with zero diagonal
// Config [0,1,1,0] → f=(-1,+1,+1,-1) → value = -20
vec![crate::example_db::specs::ModelExampleSpec {
id: "minimum_matrix_cover",
instance: Box::new(MinimumMatrixCover::new(vec![
Expand Down
4 changes: 0 additions & 4 deletions src/models/algebraic/minimum_matrix_domination.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,6 @@ impl Problem for MinimumMatrixDomination {
"selected-entry vector length does not match the matrix".into(),
));
}
// Collect the set of selected 1-entry indices
let selected: Vec<usize> = config
.iter()
.enumerate()
Expand Down Expand Up @@ -207,9 +206,6 @@ crate::register_brute_force! {

#[cfg(feature = "example-db")]
pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
// P6 adjacency matrix (6×6, 10 ones)
// 1-entries: (0,1),(1,0),(1,2),(2,1),(2,3),(3,2),(3,4),(4,3),(4,5),(5,4)
// Optimal: select indices 0,1,7,6 -> C = {(0,1),(1,0),(4,3),(3,4)}, value = 4
let matrix = vec![
vec![false, true, false, false, false, false],
vec![true, false, true, false, false, false],
Expand Down
2 changes: 0 additions & 2 deletions src/models/algebraic/minimum_weight_decoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,6 @@ crate::register_brute_force! {

#[cfg(feature = "example-db")]
pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
// H (3×4): [[1,0,1,1],[0,1,1,0],[1,1,0,1]], s = [1,1,0]
// Config [0,0,1,0] → weight 1, Hx = [1,1,0] ≡ s → Min(1)
let matrix = vec![
vec![true, false, true, true],
vec![false, true, true, false],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,9 +266,6 @@ crate::register_brute_force! {

#[cfg(feature = "example-db")]
pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
// A = [[1,2,3,1],[2,1,1,3]], b = [5,4], m=4, n=2
// Config [1,1,0,0]: select columns 0,1. Submatrix [[1,2],[2,1]].
// Solve [1,2;2,1]y=[5,4] → y=(1,2). Consistent. Min(2).
let matrix = vec![vec![1, 2, 3, 1], vec![2, 1, 1, 3]];
let rhs = vec![5, 4];
vec![crate::example_db::specs::ModelExampleSpec {
Expand Down
Loading
Loading