From d84d25dc21cefbf8ac165b243db8e45243b666d9 Mon Sep 17 00:00:00 2001 From: zazabap Date: Thu, 12 Mar 2026 11:52:30 +0000 Subject: [PATCH 1/3] Add plan for #97: BinPacking to ILP reduction --- docs/plans/2026-03-12-binpacking-to-ilp.md | 48 ++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 docs/plans/2026-03-12-binpacking-to-ilp.md diff --git a/docs/plans/2026-03-12-binpacking-to-ilp.md b/docs/plans/2026-03-12-binpacking-to-ilp.md new file mode 100644 index 000000000..8507bbdf1 --- /dev/null +++ b/docs/plans/2026-03-12-binpacking-to-ilp.md @@ -0,0 +1,48 @@ +# Plan: BinPacking to ILP Reduction (Issue #97) + +## Overview +Add a reduction rule from BinPacking to ILP (Integer Linear Programming) using the standard assignment-based formulation from Martello & Toth (1990). + +## Reduction Algorithm +- **Variables:** `x_{ij}` (item i assigned to bin j) + `y_j` (bin j is used), all binary. Total: `n^2 + n` variables. +- **Variable ordering:** `x_{00}, x_{01}, ..., x_{0,n-1}, x_{10}, ..., x_{n-1,n-1}, y_0, ..., y_{n-1}` +- **Constraints:** + 1. Assignment: for each item i, `sum_j x_{ij} = 1` (n constraints) + 2. Capacity+linking: for each bin j, `sum_i w_i * x_{ij} <= C * y_j` (n constraints) +- **Objective:** minimize `sum_j y_j` +- **Solution extraction:** For each item i, find unique j with `x_{ij} = 1`; return bin assignment vector. + +## Overhead +- `num_vars = num_items * num_items + num_items` +- `num_constraints = 2 * num_items` + +## Implementation Steps + +### Step 1: Create reduction rule file +File: `src/rules/binpacking_ilp.rs` +- Implement `ReduceTo> for BinPacking` +- Create `ReductionBPToILP` result struct with `target` and `n` fields +- Use `#[reduction(overhead = { num_vars = "num_items * num_items + num_items", num_constraints = "2 * num_items" })]` +- Solution extraction: for each item i (0..n), scan x_{ij} variables to find which bin j has value 1 + +### Step 2: Register module in `src/rules/mod.rs` +Add `mod binpacking_ilp;` under the `#[cfg(feature = "ilp-solver")]` block. + +### Step 3: Create unit tests +File: `src/unit_tests/rules/binpacking_ilp.rs` +- `test_binpacking_to_ilp_closed_loop`: solve with BruteForce and ILP solver, compare objectives +- `test_reduction_creates_valid_ilp`: verify ILP structure (num_vars, num_constraints) +- `test_single_item`: edge case with 1 item +- `test_same_weight_items`: all items have same weight +- `test_exact_fill`: items that exactly fill bins +- `test_solution_extraction`: verify extract_solution returns valid packing + +### Step 4: Create example file +File: `examples/reduction_binpacking_to_ilp.rs` +- Use the issue's example: 5 items with weights [6, 5, 5, 4, 3], capacity 10 +- Export JSON to `docs/paper/examples/binpacking_to_ilp.json` + +### Step 5: Register example in tests +Add `example_test!` and `example_fn!` entries to `tests/suites/examples.rs`. + +### Step 6: Run `make check` to verify everything compiles and passes. From 7705e9d30a9bffdf30c3677eeba5c499801db6bf Mon Sep 17 00:00:00 2001 From: zazabap Date: Thu, 12 Mar 2026 11:59:57 +0000 Subject: [PATCH 2/3] Implement #97: BinPacking to ILP reduction Add the standard assignment-based formulation (Martello & Toth, 1990) for reducing Bin Packing to Integer Linear Programming. Co-Authored-By: Claude Opus 4.6 --- docs/paper/reductions.typ | 16 +++ examples/reduction_binpacking_to_ilp.rs | 115 +++++++++++++++++++ src/rules/binpacking_ilp.rs | 101 +++++++++++++++++ src/rules/mod.rs | 2 + src/unit_tests/rules/binpacking_ilp.rs | 143 ++++++++++++++++++++++++ tests/suites/examples.rs | 2 + 6 files changed, 379 insertions(+) create mode 100644 examples/reduction_binpacking_to_ilp.rs create mode 100644 src/rules/binpacking_ilp.rs create mode 100644 src/unit_tests/rules/binpacking_ilp.rs diff --git a/docs/paper/reductions.typ b/docs/paper/reductions.typ index 915878827..6dd70db63 100644 --- a/docs/paper/reductions.typ +++ b/docs/paper/reductions.typ @@ -1524,6 +1524,22 @@ The following reductions to Integer Linear Programming are straightforward formu _Solution extraction._ $K = {v : x_v = 1}$. ] +#reduction-rule("BinPacking", "ILP")[ + The assignment-based formulation introduces a binary indicator for each item--bin pair and a binary variable for each bin being open. Assignment constraints ensure each item is placed in exactly one bin; capacity constraints link bin usage to item weights. +][ + _Construction._ Given $n$ items with sizes $s_1, dots, s_n$ and bin capacity $C$: + + _Variables:_ $x_(i j) in {0, 1}$ for $i, j in {0, dots, n-1}$: item $i$ is assigned to bin $j$. $y_j in {0, 1}$: bin $j$ is used. Total: $n^2 + n$ variables. + + _Constraints:_ (1) Assignment: $sum_(j=0)^(n-1) x_(i j) = 1$ for each item $i$ (each item in exactly one bin). (2) Capacity + linking: $sum_(i=0)^(n-1) s_i dot x_(i j) lt.eq C dot y_j$ for each bin $j$ (bin capacity respected; $y_j$ forced to 1 if bin $j$ is used). + + _Objective:_ Minimize $sum_(j=0)^(n-1) y_j$. + + _Correctness._ ($arrow.r.double$) A valid packing assigns each item to exactly one bin (satisfying (1)); each bin's load is at most $C$ and $y_j = 1$ for any used bin (satisfying (2)). ($arrow.l.double$) Any feasible solution assigns each item to one bin by (1), respects capacity by (2), and the objective counts the number of open bins. + + _Solution extraction._ For each item $i$, find the unique $j$ with $x_(i j) = 1$; assign item $i$ to bin $j$. +] + #reduction-rule("TravelingSalesman", "ILP", example: true, example-caption: [Weighted $K_4$: the optimal tour $0 arrow 1 arrow 3 arrow 2 arrow 0$ with cost 80 is found by position-based ILP.], diff --git a/examples/reduction_binpacking_to_ilp.rs b/examples/reduction_binpacking_to_ilp.rs new file mode 100644 index 000000000..104d707a4 --- /dev/null +++ b/examples/reduction_binpacking_to_ilp.rs @@ -0,0 +1,115 @@ +// # Bin Packing to ILP Reduction +// +// ## Mathematical Formulation +// Variables: x_{ij} in {0,1} (item i in bin j), y_j in {0,1} (bin j used). +// Constraints: +// Assignment: sum_j x_{ij} = 1 for each item i. +// Capacity: sum_i w_i * x_{ij} <= C * y_j for each bin j. +// Objective: minimize sum_j y_j. +// +// ## This Example +// - Instance: 5 items with weights [6, 5, 5, 4, 3], bin capacity 10 +// - Optimal: 3 bins (e.g., {6,4}, {5,5}, {3}) +// - Target ILP: 30 binary variables (25 assignment + 5 bin-open), 10 constraints +// +// ## Output +// Exports `docs/paper/examples/binpacking_to_ilp.json` and `binpacking_to_ilp.result.json`. + +use problemreductions::export::*; +use problemreductions::models::algebraic::ILP; +use problemreductions::prelude::*; +use problemreductions::solvers::ILPSolver; +use problemreductions::types::SolutionSize; + +pub fn run() { + // 1. Create BinPacking instance: 5 items, capacity 10 + let weights = vec![6, 5, 5, 4, 3]; + let capacity = 10; + let bp = BinPacking::new(weights.clone(), capacity); + + // 2. Reduce to ILP + let reduction = ReduceTo::>::reduce_to(&bp); + let ilp = reduction.target_problem(); + + // 3. Print transformation + println!("\n=== Problem Transformation ==="); + println!( + "Source: BinPacking with {} items, weights {:?}, capacity {}", + bp.num_items(), + bp.sizes(), + bp.capacity() + ); + println!( + "Target: ILP with {} variables, {} constraints", + ilp.num_vars, + ilp.constraints.len() + ); + + // 4. Solve target ILP using ILP solver (BruteForce would be too slow: 2^30 configs) + let ilp_solver = ILPSolver::new(); + let ilp_solution = ilp_solver.solve(ilp).expect("ILP should be solvable"); + + println!("\n=== Solution ==="); + + // 5. Extract source solution + let bp_solution = reduction.extract_solution(&ilp_solution); + println!("Source BinPacking solution (bin assignments): {:?}", bp_solution); + + // 6. Verify + let size = bp.evaluate(&bp_solution); + println!("Number of bins used: {:?}", size); + assert!(size.is_valid()); + assert_eq!(size, SolutionSize::Valid(3)); + println!("\nReduction verified successfully"); + + // 7. Collect solution and export JSON + let mut solutions = Vec::new(); + { + let source_sol = reduction.extract_solution(&ilp_solution); + let s = bp.evaluate(&source_sol); + assert!(s.is_valid()); + solutions.push(SolutionPair { + source_config: source_sol, + target_config: ilp_solution.clone(), + }); + } + + let source_variant = variant_to_map(BinPacking::::variant()); + let target_variant = variant_to_map(ILP::::variant()); + let overhead = lookup_overhead( + "BinPacking", + &source_variant, + "ILP", + &target_variant, + ) + .unwrap_or_default(); + + let data = ReductionData { + source: ProblemSide { + problem: BinPacking::::NAME.to_string(), + variant: source_variant, + instance: serde_json::json!({ + "num_items": bp.num_items(), + "sizes": bp.sizes(), + "capacity": bp.capacity(), + }), + }, + target: ProblemSide { + problem: ILP::::NAME.to_string(), + variant: target_variant, + instance: serde_json::json!({ + "num_vars": ilp.num_vars, + "num_constraints": ilp.constraints.len(), + }), + }, + overhead: overhead_to_json(&overhead), + }; + + let results = ResultData { solutions }; + let name = "binpacking_to_ilp"; + write_example(name, &data, &results); +} + +fn main() { + run() +} diff --git a/src/rules/binpacking_ilp.rs b/src/rules/binpacking_ilp.rs new file mode 100644 index 000000000..b44e29da3 --- /dev/null +++ b/src/rules/binpacking_ilp.rs @@ -0,0 +1,101 @@ +//! Reduction from BinPacking to ILP (Integer Linear Programming). +//! +//! The Bin Packing problem can be formulated as a binary ILP using +//! the standard assignment formulation (Martello & Toth, 1990): +//! - Variables: `x_{ij}` (item i assigned to bin j) + `y_j` (bin j used), all binary +//! - Constraints: assignment (each item in exactly one bin) + capacity/linking +//! - Objective: minimize number of bins used + +use crate::models::algebraic::{LinearConstraint, ObjectiveSense, ILP}; +use crate::models::misc::BinPacking; +use crate::reduction; +use crate::rules::traits::{ReduceTo, ReductionResult}; + +/// Result of reducing BinPacking to ILP. +/// +/// Variable layout (all binary): +/// - `x_{ij}` for i=0..n-1, j=0..n-1: item i assigned to bin j (index: i*n + j) +/// - `y_j` for j=0..n-1: bin j is used (index: n*n + j) +/// +/// Total: n^2 + n variables. +#[derive(Debug, Clone)] +pub struct ReductionBPToILP { + target: ILP, + /// Number of items in the source problem. + n: usize, +} + +impl ReductionResult for ReductionBPToILP { + type Source = BinPacking; + type Target = ILP; + + fn target_problem(&self) -> &ILP { + &self.target + } + + /// Extract solution from ILP back to BinPacking. + /// + /// For each item i, find the unique bin j where x_{ij} = 1. + fn extract_solution(&self, target_solution: &[usize]) -> Vec { + let n = self.n; + let mut assignment = vec![0usize; n]; + for i in 0..n { + for j in 0..n { + if target_solution[i * n + j] == 1 { + assignment[i] = j; + break; + } + } + } + assignment + } +} + +#[reduction( + overhead = { + num_vars = "num_items * num_items + num_items", + num_constraints = "2 * num_items", + } +)] +impl ReduceTo> for BinPacking { + type Result = ReductionBPToILP; + + fn reduce_to(&self) -> Self::Result { + let n = self.num_items(); + let num_vars = n * n + n; + + let mut constraints = Vec::with_capacity(2 * n); + + // Assignment constraints: for each item i, sum_j x_{ij} = 1 + for i in 0..n { + let terms: Vec<(usize, f64)> = (0..n).map(|j| (i * n + j, 1.0)).collect(); + constraints.push(LinearConstraint::eq(terms, 1.0)); + } + + // Capacity + linking constraints: for each bin j, + // sum_i w_i * x_{ij} - C * y_j <= 0 + let cap = *self.capacity() as f64; + for j in 0..n { + let mut terms: Vec<(usize, f64)> = self + .sizes() + .iter() + .enumerate() + .map(|(i, w)| (i * n + j, *w as f64)) + .collect(); + // Subtract C * y_j + terms.push((n * n + j, -cap)); + constraints.push(LinearConstraint::le(terms, 0.0)); + } + + // Objective: minimize sum_j y_j + let objective: Vec<(usize, f64)> = (0..n).map(|j| (n * n + j, 1.0)).collect(); + + let target = ILP::new(num_vars, constraints, objective, ObjectiveSense::Minimize); + + ReductionBPToILP { target, n } + } +} + +#[cfg(test)] +#[path = "../unit_tests/rules/binpacking_ilp.rs"] +mod tests; diff --git a/src/rules/mod.rs b/src/rules/mod.rs index 80bdb735b..f9c9c2038 100644 --- a/src/rules/mod.rs +++ b/src/rules/mod.rs @@ -34,6 +34,8 @@ mod traits; pub mod unitdiskmapping; +#[cfg(feature = "ilp-solver")] +mod binpacking_ilp; #[cfg(feature = "ilp-solver")] mod circuit_ilp; #[cfg(feature = "ilp-solver")] diff --git a/src/unit_tests/rules/binpacking_ilp.rs b/src/unit_tests/rules/binpacking_ilp.rs new file mode 100644 index 000000000..87633735f --- /dev/null +++ b/src/unit_tests/rules/binpacking_ilp.rs @@ -0,0 +1,143 @@ +use super::*; +use crate::solvers::{BruteForce, ILPSolver}; +use crate::traits::Problem; +use crate::types::SolutionSize; + +#[test] +fn test_reduction_creates_valid_ilp() { + // 3 items with weights [3, 3, 2], capacity 5 + let problem = BinPacking::new(vec![3, 3, 2], 5); + let reduction: ReductionBPToILP = ReduceTo::>::reduce_to(&problem); + let ilp = reduction.target_problem(); + + // n=3: 9 assignment vars + 3 bin vars = 12 + assert_eq!(ilp.num_vars, 12, "Should have n^2 + n variables"); + // 3 assignment + 3 capacity = 6 + assert_eq!(ilp.constraints.len(), 6, "Should have 2n constraints"); + assert_eq!(ilp.sense, ObjectiveSense::Minimize, "Should minimize"); +} + +#[test] +fn test_binpacking_to_ilp_closed_loop() { + // 4 items with weights [3, 3, 2, 2], capacity 5 + // Optimal: 2 bins, e.g. {3,2} and {3,2} + let problem = BinPacking::new(vec![3, 3, 2, 2], 5); + let reduction: ReductionBPToILP = ReduceTo::>::reduce_to(&problem); + let ilp = reduction.target_problem(); + + let bf = BruteForce::new(); + let ilp_solver = ILPSolver::new(); + + // Solve original with brute force + let bf_solutions = bf.find_all_best(&problem); + let bf_obj = problem.evaluate(&bf_solutions[0]); + + // Solve via ILP + let ilp_solution = ilp_solver.solve(ilp).expect("ILP should be solvable"); + let extracted = reduction.extract_solution(&ilp_solution); + let ilp_obj = problem.evaluate(&extracted); + + assert_eq!(bf_obj, SolutionSize::Valid(2)); + assert_eq!(ilp_obj, SolutionSize::Valid(2)); +} + +#[test] +fn test_single_item() { + let problem = BinPacking::new(vec![5], 10); + let reduction: ReductionBPToILP = ReduceTo::>::reduce_to(&problem); + let ilp = reduction.target_problem(); + + assert_eq!(ilp.num_vars, 2); // 1 assignment + 1 bin var + assert_eq!(ilp.constraints.len(), 2); // 1 assignment + 1 capacity + + let ilp_solver = ILPSolver::new(); + let ilp_solution = ilp_solver.solve(ilp).expect("ILP should be solvable"); + let extracted = reduction.extract_solution(&ilp_solution); + + assert!(problem.evaluate(&extracted).is_valid()); + assert_eq!(problem.evaluate(&extracted), SolutionSize::Valid(1)); +} + +#[test] +fn test_same_weight_items() { + // 4 items all weight 3, capacity 6 -> 2 items per bin -> 2 bins needed + let problem = BinPacking::new(vec![3, 3, 3, 3], 6); + let reduction: ReductionBPToILP = ReduceTo::>::reduce_to(&problem); + let ilp = reduction.target_problem(); + + let ilp_solver = ILPSolver::new(); + let ilp_solution = ilp_solver.solve(ilp).expect("ILP should be solvable"); + let extracted = reduction.extract_solution(&ilp_solution); + + assert!(problem.evaluate(&extracted).is_valid()); + assert_eq!(problem.evaluate(&extracted), SolutionSize::Valid(2)); +} + +#[test] +fn test_exact_fill() { + // 2 items, weights [5, 5], capacity 10 -> fit in 1 bin + let problem = BinPacking::new(vec![5, 5], 10); + let reduction: ReductionBPToILP = ReduceTo::>::reduce_to(&problem); + let ilp = reduction.target_problem(); + + let ilp_solver = ILPSolver::new(); + let ilp_solution = ilp_solver.solve(ilp).expect("ILP should be solvable"); + let extracted = reduction.extract_solution(&ilp_solution); + + assert!(problem.evaluate(&extracted).is_valid()); + assert_eq!(problem.evaluate(&extracted), SolutionSize::Valid(1)); +} + +#[test] +fn test_solution_extraction() { + let problem = BinPacking::new(vec![3, 3, 2], 5); + let reduction: ReductionBPToILP = ReduceTo::>::reduce_to(&problem); + + // Manually construct an ILP solution: + // n=3, x_{00}=1 (item 0 in bin 0), x_{11}=1 (item 1 in bin 1), x_{20}=1 (item 2 in bin 0) + // y_0=1, y_1=1, y_2=0 + let mut ilp_solution = vec![0usize; 12]; + ilp_solution[0] = 1; // x_{0,0} = 1 + ilp_solution[4] = 1; // x_{1,1} = 1 + ilp_solution[6] = 1; // x_{2,0} = 1 + ilp_solution[9] = 1; // y_0 = 1 + ilp_solution[10] = 1; // y_1 = 1 + + let extracted = reduction.extract_solution(&ilp_solution); + assert_eq!(extracted, vec![0, 1, 0]); + assert!(problem.evaluate(&extracted).is_valid()); +} + +#[test] +fn test_ilp_structure_constraints() { + // 2 items, weights [3, 4], capacity 5 + let problem = BinPacking::new(vec![3, 4], 5); + let reduction: ReductionBPToILP = ReduceTo::>::reduce_to(&problem); + let ilp = reduction.target_problem(); + + // 4 assignment vars + 2 bin vars = 6 + assert_eq!(ilp.num_vars, 6); + // 2 assignment + 2 capacity = 4 + assert_eq!(ilp.constraints.len(), 4); + + // Check objective: minimize y_0 + y_1 (vars at indices 4 and 5) + let obj_vars: Vec = ilp.objective.iter().map(|&(v, _)| v).collect(); + assert!(obj_vars.contains(&4)); + assert!(obj_vars.contains(&5)); + for &(_, coef) in &ilp.objective { + assert!((coef - 1.0).abs() < 1e-9); + } +} + +#[test] +fn test_solve_reduced() { + let problem = BinPacking::new(vec![6, 5, 5, 4, 3], 10); + + let ilp_solver = ILPSolver::new(); + let solution = ilp_solver + .solve_reduced(&problem) + .expect("solve_reduced should work"); + + assert!(problem.evaluate(&solution).is_valid()); + assert_eq!(problem.evaluate(&solution), SolutionSize::Valid(3)); +} diff --git a/tests/suites/examples.rs b/tests/suites/examples.rs index 3c9ad8033..7a3d1aa61 100644 --- a/tests/suites/examples.rs +++ b/tests/suites/examples.rs @@ -12,6 +12,7 @@ macro_rules! example_test { example_test!(chained_reduction_factoring_to_spinglass); example_test!(chained_reduction_ksat_to_mis); +example_test!(reduction_binpacking_to_ilp); example_test!(reduction_circuitsat_to_ilp); example_test!(reduction_circuitsat_to_spinglass); example_test!(reduction_factoring_to_circuitsat); @@ -66,6 +67,7 @@ example_fn!( test_chained_reduction_ksat_to_mis, chained_reduction_ksat_to_mis ); +example_fn!(test_binpacking_to_ilp, reduction_binpacking_to_ilp); example_fn!(test_circuitsat_to_ilp, reduction_circuitsat_to_ilp); example_fn!( test_circuitsat_to_spinglass, From e936fd70ad51024977abdeb1d4cefa7b57fa5d44 Mon Sep 17 00:00:00 2001 From: zazabap Date: Thu, 12 Mar 2026 12:00:08 +0000 Subject: [PATCH 3/3] chore: remove plan file after implementation --- docs/plans/2026-03-12-binpacking-to-ilp.md | 48 ---------------------- 1 file changed, 48 deletions(-) delete mode 100644 docs/plans/2026-03-12-binpacking-to-ilp.md diff --git a/docs/plans/2026-03-12-binpacking-to-ilp.md b/docs/plans/2026-03-12-binpacking-to-ilp.md deleted file mode 100644 index 8507bbdf1..000000000 --- a/docs/plans/2026-03-12-binpacking-to-ilp.md +++ /dev/null @@ -1,48 +0,0 @@ -# Plan: BinPacking to ILP Reduction (Issue #97) - -## Overview -Add a reduction rule from BinPacking to ILP (Integer Linear Programming) using the standard assignment-based formulation from Martello & Toth (1990). - -## Reduction Algorithm -- **Variables:** `x_{ij}` (item i assigned to bin j) + `y_j` (bin j is used), all binary. Total: `n^2 + n` variables. -- **Variable ordering:** `x_{00}, x_{01}, ..., x_{0,n-1}, x_{10}, ..., x_{n-1,n-1}, y_0, ..., y_{n-1}` -- **Constraints:** - 1. Assignment: for each item i, `sum_j x_{ij} = 1` (n constraints) - 2. Capacity+linking: for each bin j, `sum_i w_i * x_{ij} <= C * y_j` (n constraints) -- **Objective:** minimize `sum_j y_j` -- **Solution extraction:** For each item i, find unique j with `x_{ij} = 1`; return bin assignment vector. - -## Overhead -- `num_vars = num_items * num_items + num_items` -- `num_constraints = 2 * num_items` - -## Implementation Steps - -### Step 1: Create reduction rule file -File: `src/rules/binpacking_ilp.rs` -- Implement `ReduceTo> for BinPacking` -- Create `ReductionBPToILP` result struct with `target` and `n` fields -- Use `#[reduction(overhead = { num_vars = "num_items * num_items + num_items", num_constraints = "2 * num_items" })]` -- Solution extraction: for each item i (0..n), scan x_{ij} variables to find which bin j has value 1 - -### Step 2: Register module in `src/rules/mod.rs` -Add `mod binpacking_ilp;` under the `#[cfg(feature = "ilp-solver")]` block. - -### Step 3: Create unit tests -File: `src/unit_tests/rules/binpacking_ilp.rs` -- `test_binpacking_to_ilp_closed_loop`: solve with BruteForce and ILP solver, compare objectives -- `test_reduction_creates_valid_ilp`: verify ILP structure (num_vars, num_constraints) -- `test_single_item`: edge case with 1 item -- `test_same_weight_items`: all items have same weight -- `test_exact_fill`: items that exactly fill bins -- `test_solution_extraction`: verify extract_solution returns valid packing - -### Step 4: Create example file -File: `examples/reduction_binpacking_to_ilp.rs` -- Use the issue's example: 5 items with weights [6, 5, 5, 4, 3], capacity 10 -- Export JSON to `docs/paper/examples/binpacking_to_ilp.json` - -### Step 5: Register example in tests -Add `example_test!` and `example_fn!` entries to `tests/suites/examples.rs`. - -### Step 6: Run `make check` to verify everything compiles and passes.