///|
/// Constraint recipes for common operational rules.
pub struct RecipeResult {
mut posted : Int
mut rejected : Int
}
///|
/// Create an empty recipe result.
pub fn recipe_result() -> RecipeResult {
{ posted: 0, rejected: 0 }
}
///|
pub fn RecipeResult::posted(self : RecipeResult) -> Int {
self.posted
}
///|
pub fn RecipeResult::rejected(self : RecipeResult) -> Int {
self.rejected
}
///|
pub fn RecipeResult::ok(self : RecipeResult) -> Bool {
self.rejected == 0
}
///|
pub fn RecipeResult::describe(self : RecipeResult) -> String {
"posted=\{self.posted}, rejected=\{self.rejected}"
}
///|
/// A reusable recipe object.
pub struct ConstraintRecipe {
builder : ConstraintBuilder
result : RecipeResult
}
///|
/// Create a recipe around a builder.
pub fn constraint_recipe(builder : ConstraintBuilder) -> ConstraintRecipe {
{ builder, result: recipe_result() }
}
///|
pub fn ConstraintRecipe::builder(self : ConstraintRecipe) -> ConstraintBuilder {
self.builder
}
///|
pub fn ConstraintRecipe::result(self : ConstraintRecipe) -> RecipeResult {
self.result
}
///|
/// Post a constraint and update recipe accounting.
fn ConstraintRecipe::recipe_post(
self : ConstraintRecipe,
accepted : Bool,
) -> Bool {
if accepted {
self.result.posted += 1
} else {
self.result.rejected += 1
}
accepted
}
///|
/// Post a row of all-different variables.
pub fn ConstraintRecipe::post_all_different(
self : ConstraintRecipe,
variables : Array[Int],
) -> Bool {
self.recipe_post(self.builder.all_different(variables))
}
///|
pub fn ConstraintRecipe::post_exact_sum(
self : ConstraintRecipe,
variables : Array[Int],
target : Int,
) -> Bool {
self.recipe_post(self.builder.exact_sum(variables, target))
}
///|
pub fn ConstraintRecipe::post_exact_count(
self : ConstraintRecipe,
variables : Array[Int],
value : Int,
count : Int,
) -> Bool {
self.recipe_post(self.builder.exact_count(variables, value, count))
}
///|
pub fn ConstraintRecipe::post_at_most(
self : ConstraintRecipe,
variables : Array[Int],
value : Int,
count : Int,
) -> Bool {
self.recipe_post(self.builder.at_most_count(variables, value, count))
}
///|
pub fn ConstraintRecipe::post_at_least(
self : ConstraintRecipe,
variables : Array[Int],
value : Int,
count : Int,
) -> Bool {
self.recipe_post(self.builder.at_least_count(variables, value, count))
}
///|
pub fn ConstraintRecipe::post_chain(
self : ConstraintRecipe,
variables : Array[Int],
) -> Bool {
self.recipe_post(self.builder.strict_chain(variables))
}
///|
pub fn ConstraintRecipe::post_non_decreasing(
self : ConstraintRecipe,
variables : Array[Int],
) -> Bool {
self.recipe_post(self.builder.nondecreasing_chain(variables))
}
///|
pub fn ConstraintRecipe::post_adjacent_change(
self : ConstraintRecipe,
variables : Array[Int],
) -> Bool {
self.recipe_post(self.builder.adjacent_different(variables))
}
///|
pub fn ConstraintRecipe::post_minimum_distance(
self : ConstraintRecipe,
variables : Array[Int],
distance : Int,
) -> Bool {
self.recipe_post(self.builder.minimum_distance(variables, distance))
}
///|
pub fn ConstraintRecipe::post_transition(
self : ConstraintRecipe,
variables : Array[Int],
transitions : Array[Array[Int]],
) -> Bool {
self.recipe_post(self.builder.transition_table(variables, transitions))
}
///|
/// Post a grid's row and column rules.
pub fn ConstraintRecipe::post_grid_rules(
self : ConstraintRecipe,
grid : Array[Int],
rows : Int,
columns : Int,
) -> Bool {
let rows_ok = self.builder.grid_rows(grid, rows, columns)
let columns_ok = self.builder.grid_columns(grid, rows, columns)
self.recipe_post(rows_ok && columns_ok)
}
///|
/// Post bounded weighted rules.
pub fn ConstraintRecipe::post_upper(
self : ConstraintRecipe,
terms : Array[(Int, Int)],
target : Int,
) -> Bool {
self.recipe_post(self.builder.weighted_at_most(terms, target))
}
///|
pub fn ConstraintRecipe::post_lower(
self : ConstraintRecipe,
terms : Array[(Int, Int)],
target : Int,
) -> Bool {
self.recipe_post(self.builder.weighted_at_least(terms, target))
}
///|
pub fn ConstraintRecipe::post_no_overlap(
self : ConstraintRecipe,
starts : Array[Int],
durations : Array[Int],
) -> Bool {
self.recipe_post(self.builder.no_overlap(starts, durations))
}
///|
pub fn ConstraintRecipe::post_cumulative(
self : ConstraintRecipe,
starts : Array[Int],
durations : Array[Int],
heights : Array[Int],
capacity : Int,
) -> Bool {
self.recipe_post(
self.builder.cumulative(starts, durations, heights, capacity),
)
}
///|
/// Post a fixed assignment.
pub fn ConstraintRecipe::post_fix(
self : ConstraintRecipe,
variable_id : Int,
value : Int,
) -> Bool {
self.recipe_post(self.builder.fix(variable_id, value))
}
///|
/// Build a binary selection vector.
pub fn ConstraintRecipe::recipe_binary_vector(
self : ConstraintRecipe,
variables : Array[Int],
) -> Bool {
self.recipe_post(post_binary_vector(self.builder, variables))
}
///|
pub fn ConstraintRecipe::recipe_one_hot(
self : ConstraintRecipe,
variables : Array[Int],
) -> Bool {
self.recipe_post(post_one_hot(self.builder, variables))
}
///|
/// Return whether all posted recipes succeeded.
pub fn ConstraintRecipe::valid(self : ConstraintRecipe) -> Bool {
self.result.ok()
}
///|
/// Return a recipe model summary.
pub fn ConstraintRecipe::describe(self : ConstraintRecipe) -> String {
"\{self.result.describe()}, \{self.builder.describe()}"
}
///|
/// Add a bounded sequence with adjacent changes.
pub fn recipe_sequence(
recipe : ConstraintRecipe,
name : String,
length : Int,
lower : Int,
upper : Int,
) -> Array[Int] {
let variables = recipe.builder.grid(name, 1, length, lower, upper)
ignore(recipe.post_adjacent_change(variables))
variables
}
///|
/// Add a balanced binary vector.
pub fn recipe_balanced_binary(
recipe : ConstraintRecipe,
name : String,
length : Int,
ones : Int,
) -> Array[Int] {
let variables : Array[Int] = []
for index in 0.. Array[Int] {
let variables = recipe.builder.grid(name, 1, size, 0, size - 1)
ignore(recipe.post_all_different(variables))
variables
}
///|
/// Add a Latin square.
pub fn recipe_latin(
recipe : ConstraintRecipe,
name : String,
size : Int,
) -> Array[Int] {
let grid = recipe.builder.grid(name, size, size, 0, size - 1)
ignore(recipe.post_grid_rules(grid, size, size))
grid
}
///|
/// Add a magic-square sum model.
pub fn recipe_magic_square(
recipe : ConstraintRecipe,
name : String,
size : Int,
) -> Array[Int] {
let grid = recipe_latin(recipe, name, size)
let target = size * (size * size - 1) / 2
for row in 0.. Array[Int] {
let starts : Array[Int] = []
let durations : Array[Int] = []
for index in 0.. Array[Int] {
let starts : Array[Int] = []
let durations : Array[Int] = []
let heights : Array[Int] = []
for index in 0.. Array[Int] {
let assignments = recipe.builder.grid(name, 1, slots, 0, workers - 1)
for worker in 0.. Array[Int] {
let assignments = recipe.builder.grid(name, 1, slots, 0, workers - 1)
ignore(recipe.post_adjacent_change(assignments))
assignments
}
///|
/// Add a bounded knapsack selection vector.
pub fn recipe_knapsack(
recipe : ConstraintRecipe,
name : String,
weights : Array[Int],
capacity : Int,
) -> Array[Int] {
let variables : Array[Int] = []
let terms : Array[(Int, Int)] = []
for index, weight in weights {
let variable_id = recipe.builder.add_bool("\{name}_\{index}")
variables.push(variable_id)
terms.push((variable_id, weight))
}
ignore(recipe.post_upper(terms, capacity))
variables
}
///|
/// Add a graph coloring vector.
pub fn recipe_coloring(
recipe : ConstraintRecipe,
name : String,
vertices : Int,
colors : Int,
edges : Array[(Int, Int)],
) -> Array[Int] {
let variables = recipe.builder.grid(name, 1, vertices, 0, colors - 1)
for edge in edges {
if edge.0 >= 0 && edge.1 >= 0 && edge.0 < vertices && edge.1 < vertices {
recipe.builder.solver.add_constraint(
not_equal(variables[edge.0], variables[edge.1]),
)
}
}
variables
}
///|
/// Solve a recipe.
pub fn ConstraintRecipe::solve(self : ConstraintRecipe) -> Solution? {
self.builder.solve()
}
///|
pub fn ConstraintRecipe::solve_all(
self : ConstraintRecipe,
limit : Int,
) -> Array[Solution] {
self.builder.solve_all(limit)
}
///|
pub fn ConstraintRecipe::signature(self : ConstraintRecipe) -> Int {
self.builder.signature() * 37 + self.result.posted * 7 + self.result.rejected
}