Graph & extensional constraints
Two families that share a shape: both describe a solution by reference to a structure given as data rather than by an arithmetic relation.
Graph constraints take a fixed graph as two parallel arrays, from and to, one entry per edge.
Two Boolean arrays then select a subgraph: nodes[v] says vertex v is in it, and edges[e] says
edge e is.
The flow constraints are the exception: they decide a quantity per arc rather than a selection, so
they take a flow array in place of the two selectors.
node_offset is the number the caller’s vertices start from, since the values in from and to
index nodes and a flat list cannot carry that numbering itself.
The two spanning-tree constraints carry no nodes array, every vertex being covered by definition,
and take a node_count in its place: a vertex in no edge is not recoverable from from and to,
and without one the constraint would hold on a graph it does not span.
Extensional constraints take the permitted assignments themselves: a table of tuples, or an automaton that accepts them.
The graph constraints share a graph_ prefix rather than a type prefix, because what they constrain
is the structure a subgraph takes and not the type of anything in it.
A constraint over directed edges takes a _directed suffix, so graph_tree and
graph_tree_directed sit together rather than being filed under t and d.
The extensional constraints are named for their element type, as everything else is.
bounded_path and its relatives are absent: bounding a path’s weight is graph_path alongside a
bool_lin_le over the edge selectors, which introduces nothing new.
The tree constraints do carry a weight array, because their propagators filter with it; what they
report is total_weight, an equality, so bounding a tree means bounding that.
Notation
Section titled “Notation”The definitions below are formulas rather than sentences, and use these throughout.
| the vertices of the given graph, where n is the length of nodes, or node_count where there is no such array | ||
| the number the caller’s vertices start from | ||
| the selected vertices | ||
| the selected edges | ||
| a selected edge joins them | ||
| a selected arc runs from one to the other | ||
| reachability: the reflexive-transitive closure of the adjacency above, taken over arcs in the directed constraints | ||
| its transitive closure, so a vertex reaching itself needs a genuine cycle | ||
| the degree, how many selected edges touch it | ||
| the in-degree, how many selected arcs end at it | ||
| the Iverson bracket |
26 constraints
bool_table
Section titled “bool_table”bool_table(list of var bool: xs, list of bool: tuples)also declarable as bool_table_reif and bool_table_imp
The Boolean assignment appears as a row of the given table.
tuples is the table flattened row-major; the rows are as long as xs.
graph_acyclic_directed
Section titled “graph_acyclic_directed”graph_acyclic_directed(list of int: from, list of int: to, int: node_offset, list of var bool: nodes, list of var bool: edges)also declarable as graph_acyclic_directed_reif and graph_acyclic_directed_imp
The selected directed subgraph contains no cycle, making it a DAG.
graph_connected
Section titled “graph_connected”graph_connected(list of int: from, list of int: to, int: node_offset, list of var bool: nodes, list of var bool: edges)also declarable as graph_connected_reif and graph_connected_imp
The selected subgraph is connected.
graph_connected_directed
Section titled “graph_connected_directed”graph_connected_directed(list of int: from, list of int: to, int: node_offset, list of var bool: nodes, list of var bool: edges)also declarable as graph_connected_directed_reif and graph_connected_directed_imp
The selected directed subgraph is strongly connected.
graph_network_flow
Section titled “graph_network_flow”graph_network_flow(list of int: from, list of int: to, int: node_offset, list of int: balance, list of var int: flow)also declarable as graph_network_flow_reif and graph_network_flow_imp
The flow on each arc satisfies the given supply and demand at every node.
The graph is given as from and to, one entry per arc, the same way every other constraint here takes one. balance is numbered from node_offset: a positive value is demand, a negative one supply. Unlike the rest of the family there are no selector arrays, because every arc carries a flow rather than being in or out.
graph_network_flow_cost
Section titled “graph_network_flow_cost”graph_network_flow_cost(list of int: from, list of int: to, int: node_offset, list of int: balance, list of int: weight, list of var int: flow, var int: total_cost)also declarable as graph_network_flow_cost_reif and graph_network_flow_cost_imp
As graph_network_flow, and the total cost is the weighted sum of the flows.
graph_path
Section titled “graph_path”graph_path(list of int: from, list of int: to, int: node_offset, var int: source, var int: sink, list of var bool: nodes, list of var bool: edges)also declarable as graph_path_reif and graph_path_imp
The selected subgraph is a simple path from the source to the sink.
graph_path_directed
Section titled “graph_path_directed”graph_path_directed(list of int: from, list of int: to, int: node_offset, var int: source, var int: sink, list of var bool: nodes, list of var bool: edges)also declarable as graph_path_directed_reif and graph_path_directed_imp
The selected subgraph is a simple directed path from the source to the sink.
graph_reachable
Section titled “graph_reachable”graph_reachable(list of int: from, list of int: to, int: node_offset, var int: root, list of var bool: nodes, list of var bool: edges)also declarable as graph_reachable_reif and graph_reachable_imp
Every selected node is reachable from the root in the selected subgraph.
graph_reachable_directed
Section titled “graph_reachable_directed”graph_reachable_directed(list of int: from, list of int: to, int: node_offset, var int: root, list of var bool: nodes, list of var bool: edges)also declarable as graph_reachable_directed_reif and graph_reachable_directed_imp
Every selected node is reachable from the root along the arcs’ direction.
graph_spanning_tree
Section titled “graph_spanning_tree”graph_spanning_tree(list of int: from, list of int: to, int: node_offset, int: node_count, list of int: weight, list of var bool: edges, var int: total_weight)also declarable as graph_spanning_tree_reif and graph_spanning_tree_imp
The selected edges span every node of the graph, reporting their total weight.
Unlike graph_steiner_tree, every node must be covered, so node_count stands in for the nodes array whose length gives the vertex count elsewhere.
graph_spanning_tree_directed
Section titled “graph_spanning_tree_directed”graph_spanning_tree_directed(list of int: from, list of int: to, int: node_offset, int: node_count, list of int: weight, var int: root, list of var bool: edges, var int: total_weight)also declarable as graph_spanning_tree_directed_reif and graph_spanning_tree_directed_imp
The selected arcs form a spanning arborescence from the root, reporting their total weight.
graph_steiner_tree
Section titled “graph_steiner_tree”graph_steiner_tree(list of int: from, list of int: to, int: node_offset, list of int: weight, list of var bool: nodes, list of var bool: edges, var int: total_weight)also declarable as graph_steiner_tree_reif and graph_steiner_tree_imp
The selected subgraph is a tree, reporting its total edge weight.
Which nodes to include is part of the decision, which is what makes it a Steiner tree rather than a spanning tree.
graph_steiner_tree_directed
Section titled “graph_steiner_tree_directed”graph_steiner_tree_directed(list of int: from, list of int: to, int: node_offset, list of int: weight, var int: root, list of var bool: nodes, list of var bool: edges, var int: total_weight)also declarable as graph_steiner_tree_directed_reif and graph_steiner_tree_directed_imp
The selected subgraph is a directed tree from the root, reporting its total arc weight.
graph_subgraph
Section titled “graph_subgraph”graph_subgraph(list of int: from, list of int: to, int: node_offset, list of var bool: nodes, list of var bool: edges)also declarable as graph_subgraph_reif and graph_subgraph_imp
The selected edges and nodes form a subgraph: every selected edge has both endpoints selected.
The well-formedness condition every other graph constraint here assumes.
graph_tree
Section titled “graph_tree”graph_tree(list of int: from, list of int: to, int: node_offset, var int: root, list of var bool: nodes, list of var bool: edges)also declarable as graph_tree_reif and graph_tree_imp
The selected subgraph is a tree rooted at the given node.
graph_tree_directed
Section titled “graph_tree_directed”graph_tree_directed(list of int: from, list of int: to, int: node_offset, var int: root, list of var bool: nodes, list of var bool: edges)also declarable as graph_tree_directed_reif and graph_tree_directed_imp
The selected subgraph is a directed tree with all arcs pointing away from the root.
int_circuit
Section titled “int_circuit”int_circuit(list of var int: xs, int: offset)also declarable as int_circuit_reif and int_circuit_imp
The successor array describes a single circuit visiting every node exactly once.
xs is numbered from offset, and its values name nodes in that same numbering. The Hamiltonian-circuit constraint behind most vehicle-routing models.
int_mdd
Section titled “int_mdd”int_mdd(list of var int: xs, int: nodes, list of int: level, int: edges, list of int: from, list of set of int: label, list of int: to)also declarable as int_mdd_reif and int_mdd_imp
The assignment corresponds to a root-to-leaf path through the given multi-valued decision diagram.
level gives each node’s layer, and from, label and to describe the edges. Nodes are numbered from 1, so no offset travels with them. A compact alternative to int_table when the relation factorises.
The diagram must be deterministic: no two edges leaving a node may share a label. int_mdd_nondeterministic is the form without that requirement.
int_mdd_cost
Section titled “int_mdd_cost”int_mdd_cost(list of var int: xs, int: nodes, list of int: level, int: edges, list of int: from, list of set of int: label, list of int: weight, list of int: to, var int: total_cost)also declarable as int_mdd_cost_reif and int_mdd_cost_imp
As int_mdd, and the total cost sums the weight of each edge on the chosen path.
int_mdd_nondeterministic
Section titled “int_mdd_nondeterministic”int_mdd_nondeterministic(list of var int: xs, int: nodes, list of int: level, int: edges, list of int: from, list of set of int: label, list of int: to)also declarable as int_mdd_nondeterministic_reif and int_mdd_nondeterministic_imp
The assignment corresponds to some root-to-leaf path through the given decision diagram.
As int_mdd, but edges leaving a node may share a label, so an assignment may be accepted along several paths. A separate name because a propagator written for the deterministic form is wrong here.
int_regular
Section titled “int_regular”int_regular(list of var int: xs, int: states, int: alphabet, int: alphabet_offset, list of int: transition, int: initial, set of int: accepting)also declarable as int_regular_reif and int_regular_imp
The array, read as a string, is accepted by the given deterministic automaton.
transition is the state-by-symbol table flattened row-major, states rows of alphabet entries, so no row length travels with it. A transition to 0 means the string is rejected. States are numbered from 1; symbols from alphabet_offset, so xs keeps the caller’s own alphabet.
int_regular_cost
Section titled “int_regular_cost”int_regular_cost(list of var int: xs, int: states, int: alphabet, int: alphabet_offset, list of int: transition, int: initial, set of int: accepting, list of int: weight, var int: total_cost)also declarable as int_regular_cost_reif and int_regular_cost_imp
As int_regular, and the total cost sums the weight of each transition taken.
int_regular_nondeterministic
Section titled “int_regular_nondeterministic”int_regular_nondeterministic(list of var int: xs, int: states, int: alphabet, int: alphabet_offset, list of set of int: transition, int: initial, set of int: accepting)also declarable as int_regular_nondeterministic_reif and int_regular_nondeterministic_imp
The array, read as a string, is accepted by the given non-deterministic automaton.
As int_regular, but each transition gives a set of possible next states.
int_subcircuit
Section titled “int_subcircuit”int_subcircuit(list of var int: xs, int: offset)also declarable as int_subcircuit_reif and int_subcircuit_imp
The successor array describes a single circuit over a subset of the nodes, with the rest as self-loops.
xs is numbered from offset. A node that maps to itself is unvisited; unlike int_circuit, nodes may be left out.
int_table
Section titled “int_table”int_table(list of var int: xs, list of int: tuples)also declarable as int_table_reif and int_table_imp
The assignment to the array appears as a row of the given table.
tuples is the table flattened row-major, one permitted assignment per row. No row length is needed: the rows are as long as xs. The most direct way to state a relation with no closed form.