Skip to content

Equality constraint with right-hand side variable #336

@wjholden

Description

@wjholden

Good morning! I am using Pumpkin-Solver to model part 2 of Advent of Code 2025 Day 10. I got it to work, but I have a question. The constraints::equals function expects an i32 in its right-hand side. For variables a, b, c, d, e, f, and z, we cannot model a + b + c + d + e + f = z directly. Instead, I had to build up the partial sums as intermediate variables using the constraints::plus function. Was there a better way to do this?

Here is my program:

use pumpkin_solver::{
    ConstraintOperationError, DefaultBrancher, Solver, constraints,
    optimisation::{OptimisationDirection, linear_sat_unsat::LinearSatUnsat},
    results::{OptimisationResult, ProblemSolution, SolutionReference},
    termination::Indefinite,
};

/// This is the first example from Part 2 of
/// [Advent of Code 2025 Day 10](https://adventofcode.com/2025/day/10#part2).
///
/// This program minimizes the sum of non-negative integers a, b, c, d, e, and
/// f such that the matrix product A times a vector x={a,b,c,d,e,f} is equal
/// to vector y={3,5,4,7}. In the Wolfram language,
///
/// ```mathematica
/// In[1]:= A := {{0, 0, 0, 0, 1, 1}, {0, 1, 0, 0, 0, 1}, {0, 0, 1, 1, 1, 0}, {1, 1, 0, 1, 0, 0}}
///
/// In[2]:= x := {a,b,c,d,e,f}
///
/// In[3]:= y := {3,5,4,7}
///
/// In[4]:= Minimize[Total[x], A.x == y, x, NonNegativeIntegers]
///
/// Out[4]= {10, {a -> 1, b -> 3, c -> 0, d -> 3, e -> 1, f -> 2}}
/// ```
fn main() -> Result<(), ConstraintOperationError> {
    let mut solver = Solver::default();

    let a = solver.new_bounded_integer(0, 100);
    let b = solver.new_bounded_integer(0, 100);
    let c = solver.new_bounded_integer(0, 100);
    let d = solver.new_bounded_integer(0, 100);
    let e = solver.new_bounded_integer(0, 100);
    let f = solver.new_bounded_integer(0, 100);

    // 3 = e + f
    let tag = solver.new_constraint_tag();
    solver
        .add_constraint(constraints::equals(vec![e, f], 3, tag))
        .post()?;

    // 5 = b + f
    let tag = solver.new_constraint_tag();
    solver
        .add_constraint(constraints::equals(vec![b, f], 5, tag))
        .post()?;

    // 4 = c + d + e
    let tag = solver.new_constraint_tag();
    solver
        .add_constraint(constraints::equals(vec![c, d, e], 4, tag))
        .post()?;

    // 7 = a + b + d
    let tag = solver.new_constraint_tag();
    solver
        .add_constraint(constraints::equals(vec![a, b, d], 7, tag))
        .post()?;

    // Our goal is to minimize a + b + c + d + e + f, but the function
    // `constraints::equals(terms, rhs, constraint_tag)` expects an i32 for the
    // right-hand side. You cannot put another variable in the right-hand side.
    //
    // Instead, you can use `constraints::plus(a, b, c, constraint_tag)` to
    // model the dependencies between variables a, b, and c.
    //
    // We build up the intermediate sums of dependent variables as
    // a + b + c + d + e + f = ((((a + b) + c) + d) + e) + f.
    let ab = solver.new_bounded_integer(0, 100);
    let tag = solver.new_constraint_tag();
    solver
        .add_constraint(constraints::plus(a, b, ab, tag))
        .post()?;

    let abc = solver.new_bounded_integer(0, 100);
    let tag = solver.new_constraint_tag();
    solver
        .add_constraint(constraints::plus(ab, c, abc, tag))
        .post()?;

    let abcd = solver.new_bounded_integer(0, 100);
    let tag = solver.new_constraint_tag();
    solver
        .add_constraint(constraints::plus(ab, d, abcd, tag))
        .post()?;

    let abcde = solver.new_bounded_integer(0, 100);
    let tag = solver.new_constraint_tag();
    solver
        .add_constraint(constraints::plus(abcd, e, abcde, tag))
        .post()?;

    // This is our minimization objective, a + b + c + d + e + f.
    let abcdef = solver.new_bounded_integer(0, 100);
    let tag = solver.new_constraint_tag();
    solver
        .add_constraint(constraints::plus(abcde, f, abcdef, tag))
        .post()?;

    // Copied from https://docs.rs/pumpkin-solver/0.2.2/pumpkin_solver/index.html#using-pumpkin
    let mut termination = Indefinite;
    let mut brancher = solver.default_brancher();
    let callback: fn(&pumpkin_solver::Solver, SolutionReference, &DefaultBrancher) = |_, _, _| {};
    let result = solver.optimise(
        &mut brancher,
        &mut termination,
        LinearSatUnsat::new(OptimisationDirection::Minimise, abcdef, callback),
    );

    match result {
        OptimisationResult::Optimal(solution) => println!("{}", solution.get_integer_value(abcdef)),
        OptimisationResult::Satisfiable(_) => unreachable!(),
        OptimisationResult::Unsatisfiable => panic!("Not satisfiable"),
        OptimisationResult::Unknown => panic!("No result from solver"),
    }

    Ok(())
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentationenhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions