2

I'm trying to determine that shape of a directed graph by solving constraints on presence of nodes and arcs, e.g., binary variable V1V2 is 1 if there is an arc from node V1 to V2. I would like to express the reachability constraint (or, alternatively, find a transitive closure), e.g., to require that the graph is connected, or that there is a path from one given node to another given node.

I've seen that SICStus Prolog has fd_closure for this purpose, but I could not find anything similar in SWI Prolog. Should I use CHR? I've been looking at the arc/path consistency examples but I'm not sure whether I'm looking in the right direction.

false
  • 10,264
  • 13
  • 101
  • 209
Alexander Serebrenik
  • 3,567
  • 2
  • 16
  • 32

2 Answers2

3

SICStus' fd_closure/2 is very similar to SWI-Prolog's term_attvars/2. It gives you all variables that can be transitively reached via attributes (and SWI's CLP(FD) uses attributes to store constraints).

Note that you typically do not need these predicates. They are used for example on the toplevel to obtain residual goals.

You can express a graph with finite domain constraints without using any of these predicates. For example, use a finite domain variable B_i_j which is 1 iff there is an arc from node i to node j.

If you require stronger properties, you will likely need stronger constraints. For example, circuit/1 is available in SICStus and SWI and describes a Hamiltonian circuit. For other properties, you can implement dedicated filtering algorithms.

mat
  • 40,498
  • 3
  • 51
  • 78
  • ".. on the toplevel to obtain residual goals": But, ideally, would'nt it be better to use copy_term/3 for this purpose? After all the identity of internal variables is irrelevant in that context. So `copy_term/3` cannot be used simply because there are older constraint solvers around that do not take the interface into account. – false Nov 16 '13 at 21:22
  • Well to be more precise, the toplevel uses `copy_term/3`, and `copy_term/3` uses `term_attvars/2`. You need to see all constraints that a variable is involved in when projecting residual goals, and hence the transitive closure of variables that are reachable via any attributes. To be even more precise, the toplevel in SWI-Prolog *also* uses `term_attvars/2` directly, but only to quickly check whether any variable in a set of bindings is involved in any constraint at all, in which case constraint projection has to be performed. – mat Nov 16 '13 at 21:27
  • So that second use is actually a bug and should rather be replaced by `call_residue_vars/2` – false Nov 16 '13 at 21:29
  • The whole query execution should be wrapped in `call_residue_vars/2`, and projection should then use the *residual* variables for the same reasoning it currently only performs on the *solution* variables. The current mechanism for constraint projection works OK as is - it's only that the mechanism is applied to fewer variables than it should. Unfortunately, `call_residue_vars/2` does not work reliably in SWI. Try for example several calls of `?- call_residue_vars((all_different([A]),A in 1..2, indomain(A)), Vs).` and see that it yields different results depending on the number of invocations. – mat Nov 16 '13 at 21:36
  • ... I get one variable for `Vs`. Intended or bug? When I `copy_term(Vs,Vs,Goals)`, I get `Goals = []`. What is happening there? – false Nov 16 '13 at 22:54
  • As you suggested I use the encoding "`B_i_j` which is 1 iff there is an arc from node i to node j" but how can I define a new variable, say `A_i_j`, as being true if there is a path between i and j, consisting of the `B_i_j` arcs? – Alexander Serebrenik Nov 17 '13 at 10:29
  • You can express this with reified constraints, for example there is a path `P_1_3` between vertices `V1` and `V3` if there is an arc between `V1` and `V2`, and an arc between `V2` and `V3`: `B_1_2 #/\ B_2_3 #==> P_1_3`. This can obviously quickly get out of hand due to too many constraints. It depends on the size of your problem and the number of arcs whether it is worth to try this formulation. You can try it and see how it works. If it does not, you can implement a custom filtering algorithm that works on all variables and dynamically checks whether there is still such a path. – mat Nov 17 '13 at 10:47
  • What matters is how many you actually have. Obviously, if you know beforehand that there cannot be an arc between two vertices, you need not post such a constraint at all, because it will always evaluate to 0. You need to think about what specific invariants hold in your concrete problem, and may be able to significantly reduce the number of necessary constraints. As I said, you can implement a custom global constraint if there are too many reified constraints. – mat Nov 17 '13 at 10:55
  • @false: `call_residue_vars/2` is greatly improved in more recent SWI versions. In particular, it now yields the same result on subsequent invocations. In addition, you can now use `?- set_prolog_flag(toplevel_residue_vars, true).` to implicitly wrap the whole query in `call_residue_vars/2` and see pending residual goals. The `all_different/1` case you mention is justified in the sense that there is a remaining variable that had attributes attached during the execution. Use `get_attrs/2` on the remaining variable to see them. Its projection is suppressed though, so this is pretty harmless. – mat Jul 14 '15 at 13:01
  • @mat: Using `get_attrs/2` is pretty overkill and highly SWI specific. – false Jul 14 '15 at 14:36
  • This is *only* in case you are ever wondering why there is an apparently *not* attributed variable in `Vs` when using `call_residue_vars(Goal, Vs)`: It *is* attributed, but the attribute is not projected to any residual goal because it is an internal attribute. Example: `?- call_residue_vars(all_different([]), [V]), get_attrs(V, Attrs)`. However, `?- call_residue_vars(all_different([]), Vs).` shows no remaining CLP(FD) constraints, because there is nothing more to say. Still, an attribute was introduced during execution of this query, and that is recognized by `call_residue_vars/2`. – mat Jul 14 '15 at 16:51
1

With copy_term/3 and term_variables/2 you should be able to get (edit: almost) the same effect as fd_closure/2.

Please note that fd_closure in SICStus only works for FD-constraints. Other connections between variables are not taken into account.

Edit: This way you will get only copies of internal variables - this should be good enough to determine the shape. However, you might want to refer to those variables later on in which case @mat's solution is it.

false
  • 10,264
  • 13
  • 101
  • 209