// (c) 2026-2026 Fair Isaac Corporation
using System;
using Optimizer;
using Optimizer.Maps;
using Optimizer.Objects;
using static Optimizer.Objects.Utils;
namespace XpressExamples {
/// 9x9 Sudoku puzzle to illustrate solution enumeration.
///
/// This is a pure feasibility problem, i.e., the objective function is
/// all-zero.
/// We first solve with prefixed values that allow exactly one solution.
/// Then we add clear one of the fixings and enumerate all solutions the
/// new board allows.
///
class Sudoku {
/// The available row indices (first row has index zero).
static readonly int[] ROWS = {0, 1, 2, 3, 4, 5, 6, 7, 8};
/// The available column indices (first column has index zero).
static readonly int[] COLS = {0, 1, 2, 3, 4, 5, 6, 7, 8};
/// The available values (1 through 9).
static readonly int[] VALS = {1, 2, 3, 4, 5, 6, 7, 8, 9};
/// Values that are fixed in the beginning.
///
/// These fixings allow exactly one feasible solution.
/// Data set from "The Guardian", 15 July, 2005. http://www.guardian.co.uk/sudoku
///
static int[,] PRESET = {
{ 0, 0, 0, 7, 0, 5, 6, 9, 0 },
{ 0, 0, 8, 0, 4, 0, 0, 0, 7 },
{ 0, 2, 0, 0, 0, 3, 0, 0, 0 },
{ 0, 0, 2, 0, 0, 0, 9, 0, 0 },
{ 0, 0, 0, 9, 0, 0, 0, 8, 0 },
{ 0, 0, 0, 0, 1, 0, 0, 0, 5 },
{ 0, 0, 0, 0, 0, 4, 0, 0, 0 },
{ 0, 0, 6, 0, 0, 0, 3, 0, 4 },
{ 0, 0, 0, 8, 2, 1, 0, 5, 0 },
};
/// Print a board to the console.
/// A 9x9 array that specifies the numbers that are
/// set in board. A zero means "not set".
static void PrintBoard(int[,] board) {
foreach (int r in ROWS) {
string fill = (r == 3 || r == 6) ? "===+" : "---+";
Console.Write("+");
foreach (int c in COLS) {
Console.Write(fill);
if (c == 2 || c == 5)
Console.Write('+');
}
Console.WriteLine();
Console.Write("|");
foreach (int c in COLS) {
Console.WriteLine($" {((board[r,c] == 0) ? ' ' : (char)(board[r,c] + '0'))} |");
if (c == 2 || c == 5)
Console.Write("|");
}
Console.WriteLine();
}
Console.Write("+");
foreach (int c in COLS) {
Console.Write("---+");
if (c == 2 || c == 5)
Console.Write('+');
}
Console.WriteLine();
}
/// Print a soolution.
///
/// Constructs a board from a solution and then prints it.
///
/// The binary variables.
/// The solution values for b.
static void PrintSolution(HashMap3 b, double[] sol) {
int[,] result = new int[9,9];
foreach (int r in ROWS) {
foreach (int c in COLS) {
foreach (int v in VALS) {
if (b[r, c, v].GetValue(sol) > 0.5) {
result[r,c] = v;
}
}
}
}
PrintBoard(result);
}
/// Solve the 9x9 Sudoku puzzle with the given fixed values.
/// The values that are fixed on the board.
/// If true then we enumerate all solutions,
/// otherwise we stop on the first solution. Note that
/// enumeration can become expensive the more solutions
/// there are.
static void Solve(int[,] preset, bool enumerate) {
using (XpressProblem prob = new XpressProblem()) {
// Create the variables. One binary variable for each combination of
// row, column, value.
// We store the variables in a map rather than a 3D array since rows and
// columns index from 0, but values index from 1.
HashMap3 b = prob.AddVariables(ROWS, COLS, VALS)
.WithName("b({0},{1},{2})")
.WithType(ColumnType.Binary)
.ToMap();
// Fix the variables that should be fixed on the initial board.
foreach (int r in ROWS) {
foreach (int c in COLS) {
if (preset[r,c] > 0) {
b[r, c, preset[r, c]].Fix(1.0);
}
}
}
// Each cell must have a number.
prob.AddConstraints(ROWS, COLS, (r, c) => Sum(VALS, v => b[r, c, v]) == 1.0);
// Each number must appear exactly once in a row.
prob.AddConstraints(ROWS, VALS, (r, v) => Sum(COLS, c => b[r, c, v]) == 1.0);
// Each number must appear exactly once in a column.
prob.AddConstraints(COLS, VALS, (c, v) => Sum(ROWS, r => b[r, c, v]) == 1.0);
// In 3x3 squares all values must be different.
for (int k = 0; k < 3; ++k) {
int[] colidx = {3 * k, 1 + 3 * k, 2 + 3 * k};
foreach (int v in VALS) {
prob.AddConstraint(Sum(new int[]{0, 1, 2}, colidx,
(x, y) => b[x, y, v]) == 1.0);
prob.AddConstraint(Sum(new int[]{3, 4, 5}, colidx,
(x, y) => b[x, y, v]) == 1.0);
prob.AddConstraint(Sum(new int[]{6, 7, 8}, colidx,
(x, y) => b[x, y, v]) == 1.0);
}
}
// Enable log output and prepare for numeration (if requested).
prob.callbacks.AddMessageCallback(DefaultMessageListener.Console);
if (enumerate) {
prob.PoolEnumStrategy = PoolEnumStrategy.Full;
prob.PoolLimit = Int32.MaxValue; // Find all solutions.
}
// Find all solutions.
prob.Optimize();
// Print solutions.
// Note that we only print the first 10 solutions to not flood the screen.
for (int s = 0; s < Math.Min(10, prob.PoolSize); ++s) {
Console.WriteLine($"Solution {s}:");
PrintSolution(b, prob.GetPoolSolution(s));
}
}
}
public static void Main(string[] args) {
// First solve with the preset board. This should allow exactly one solution.
Solve(PRESET, false);
// Now remove some fixed values and enumerate all solutions.
PRESET[0, 3] = 0;
Solve(PRESET, true);
}
}
}