diff options
Diffstat (limited to 'sourcecodes/bnt-master/graph')
61 files changed, 2993 insertions, 0 deletions
diff --git a/sourcecodes/bnt-master/graph/CVS/Entries b/sourcecodes/bnt-master/graph/CVS/Entries new file mode 100644 index 00000000..cf3baf2c --- /dev/null +++ b/sourcecodes/bnt-master/graph/CVS/Entries @@ -0,0 +1,49 @@ +/README.txt/1.1.1.1/Mon Aug 29 17:44:02 2005// +/acyclic.m/1.1.1.1/Mon Aug 29 17:44:04 2005// +/assignEdgeNums.m/1.1.1.1/Mon Aug 29 17:44:04 2005// +/best_first_elim_order.m/1.1.1.1/Mon Aug 29 17:44:04 2005// +/check_jtree_property.m/1.1.1.1/Mon Aug 29 17:44:04 2005// +/check_triangulated.m/1.1.1.1/Mon Aug 29 17:44:04 2005// +/children.m/1.1.1.1/Mon Aug 29 17:44:04 2005// +/cliques_to_jtree.m/1.1.1.1/Mon Aug 29 17:44:04 2005// +/cliques_to_strong_jtree.m/1.1.1.1/Mon Aug 29 17:44:04 2005// +/connected_graph.m/1.1.1.1/Mon Aug 29 17:44:04 2005// +/dag_to_essential_graph.m/1.1.1.1/Mon Aug 29 17:44:04 2005// +/dfs.m/1.1.1.1/Mon Aug 29 17:44:04 2005// +/dfs_test.m/1.1.1.1/Mon Aug 29 17:44:04 2005// +/dijkstra.m/1.1.1.1/Mon Aug 29 17:44:04 2005// +/family.m/1.1.1.1/Mon Aug 29 17:44:04 2005// +/graph_separated.m/1.1.1.1/Mon Aug 29 17:44:04 2005// +/graph_to_jtree.m/1.1.1.1/Mon Aug 29 17:44:04 2005// +/min_subtree_con_nodes.m/1.1.1.1/Mon Aug 29 17:44:04 2005// +/minimum_spanning_tree.m/1.1.1.1/Mon Aug 29 17:44:04 2005// +/minspan.m/1.1.1.1/Mon Aug 29 17:44:06 2005// +/mk_2D_lattice.m/1.1.1.1/Mon Aug 29 17:44:06 2005// +/mk_2D_lattice_slow.m/1.1.1.1/Mon Aug 29 17:44:06 2005// +/mk_adj_mat.m/1.1.1.1/Mon Aug 29 17:44:06 2005// +/mk_adjmat_chain.m/1.1.1.1/Mon Aug 29 17:44:06 2005// +/mk_all_dags.m/1.1.1.1/Mon Aug 29 17:44:06 2005// +/mk_nbrs_of_dag.m/1.1.1.1/Mon Aug 29 17:44:06 2005// +/mk_nbrs_of_digraph.m/1.1.1.1/Mon Aug 29 17:44:06 2005// +/mk_nbrs_of_digraph_broken.m/1.1.1.1/Mon Aug 29 17:44:06 2005// +/mk_nbrs_of_digraph_not_vectorized.m/1.1.1.1/Mon Aug 29 17:44:06 2005// +/mk_rnd_dag.m/1.1.1.1/Mon Aug 29 17:44:06 2005// +/mk_rnd_dag_given_edge_prob.m/1.1.1.1/Mon Aug 29 17:44:06 2005// +/mk_rooted_tree.m/1.1.1.1/Mon Aug 29 17:44:06 2005// +/mk_undirected.m/1.1.1.1/Mon Aug 29 17:44:06 2005// +/moralize.m/1.1.1.1/Mon Aug 29 17:44:06 2005// +/neighbors.m/1.1.1.1/Mon Aug 29 17:44:06 2005// +/parents.m/1.1.1.1/Mon Aug 29 17:44:08 2005// +/pred2path.m/1.1.1.1/Mon Aug 29 17:44:08 2005// +/reachability_graph.m/1.1.1.1/Mon Aug 29 17:44:08 2005// +/scc.m/1.1.1.1/Mon Aug 29 17:44:08 2005// +/strong_elim_order.m/1.1.1.1/Mon Aug 29 17:44:08 2005// +/test.m/1.1.1.1/Mon Aug 29 17:44:08 2005// +/test_strong_root.m/1.1.1.1/Mon Aug 29 17:44:08 2005// +/topological_sort.m/1.1.1.1/Mon Aug 29 17:44:08 2005// +/trees.txt/1.1.1.1/Mon Aug 29 17:44:08 2005// +/triangulate.c/1.1.1.1/Mon Aug 29 17:44:08 2005// +/triangulate.m/1.1.1.1/Mon Aug 29 17:44:08 2005// +/triangulate_2Dlattice_demo.m/1.1.1.1/Mon Aug 29 17:44:08 2005// +/triangulate_test.m/1.1.1.1/Mon Aug 29 17:44:08 2005// +D diff --git a/sourcecodes/bnt-master/graph/CVS/Entries.Log b/sourcecodes/bnt-master/graph/CVS/Entries.Log new file mode 100644 index 00000000..24f16336 --- /dev/null +++ b/sourcecodes/bnt-master/graph/CVS/Entries.Log @@ -0,0 +1 @@ +A D/Old//// diff --git a/sourcecodes/bnt-master/graph/CVS/Repository b/sourcecodes/bnt-master/graph/CVS/Repository new file mode 100644 index 00000000..a18677ae --- /dev/null +++ b/sourcecodes/bnt-master/graph/CVS/Repository @@ -0,0 +1 @@ +FullBNT/graph diff --git a/sourcecodes/bnt-master/graph/CVS/Root b/sourcecodes/bnt-master/graph/CVS/Root new file mode 100644 index 00000000..f3bd14a6 --- /dev/null +++ b/sourcecodes/bnt-master/graph/CVS/Root @@ -0,0 +1 @@ +:ext:nsaunier@bnt.cvs.sourceforge.net:/cvsroot/bnt diff --git a/sourcecodes/bnt-master/graph/Old/CVS/Entries b/sourcecodes/bnt-master/graph/Old/CVS/Entries new file mode 100644 index 00000000..c45011ba --- /dev/null +++ b/sourcecodes/bnt-master/graph/Old/CVS/Entries @@ -0,0 +1,6 @@ +/best_first_elim_order.m/1.1.1.1/Mon Aug 29 17:44:02 2005// +/dag_to_jtree.m/1.1.1.1/Mon Aug 29 17:44:02 2005// +/dfs.m/1.1.1.1/Mon Aug 29 17:44:02 2005// +/dsep_test.m/1.1.1.1/Mon Aug 29 17:44:02 2005// +/mk_2D_lattice_slow.m/1.1.1.1/Mon Aug 29 17:44:02 2005// +D diff --git a/sourcecodes/bnt-master/graph/Old/CVS/Repository b/sourcecodes/bnt-master/graph/Old/CVS/Repository new file mode 100644 index 00000000..f3b2d81f --- /dev/null +++ b/sourcecodes/bnt-master/graph/Old/CVS/Repository @@ -0,0 +1 @@ +FullBNT/graph/Old diff --git a/sourcecodes/bnt-master/graph/Old/CVS/Root b/sourcecodes/bnt-master/graph/Old/CVS/Root new file mode 100644 index 00000000..f3bd14a6 --- /dev/null +++ b/sourcecodes/bnt-master/graph/Old/CVS/Root @@ -0,0 +1 @@ +:ext:nsaunier@bnt.cvs.sourceforge.net:/cvsroot/bnt diff --git a/sourcecodes/bnt-master/graph/Old/best_first_elim_order.m b/sourcecodes/bnt-master/graph/Old/best_first_elim_order.m new file mode 100644 index 00000000..82f1236b --- /dev/null +++ b/sourcecodes/bnt-master/graph/Old/best_first_elim_order.m @@ -0,0 +1,64 @@ +function order = best_first_elim_order(G, node_sizes, stage) +% BEST_FIRST_ELIM_ORDER Greedily search for an optimal elimination order. +% order = best_first_elim_order(moral_graph, node_sizes) +% +% Find an order in which to eliminate nodes from the graph in such a way as to try and minimize the +% weight of the resulting triangulated graph. The weight of a graph is the sum of the weights of each +% of its cliques; the weight of a clique is the product of the weights of each of its members; the +% weight of a node is the number of values it can take on. +% +% Since this is an NP-hard problem, we use the following greedy heuristic: +% at each step, eliminate that node which will result in the addition of the least +% number of fill-in edges, breaking ties by choosing the node that induces the lighest clique. +% For details, see +% - Kjaerulff, "Triangulation of graphs -- algorithms giving small total state space", +% Univ. Aalborg tech report, 1990 (www.cs.auc.dk/~uk) +% - C. Huang and A. Darwiche, "Inference in Belief Networks: A procedural guide", +% Intl. J. Approx. Reasoning, 11, 1994 +% + +% Warning: This code is pretty old and could probably be made faster. + +n = length(G); +if nargin < 3, stage = { 1:n }; end % no constraints + +% For long DBNs, it may be useful to eliminate all the nodes in slice t before slice t+1. +% This will ensure that the jtree has a repeating structure (at least away from both edges). +% This is why we have stages. +% See the discussion of splicing jtrees on p68 of +% Geoff Zweig's PhD thesis, Dept. Comp. Sci., UC Berkeley, 1998. +% This constraint can increase the clique size significantly. + +MG = G; % copy the original graph +uneliminated = ones(1,n); +order = zeros(1,n); +t = 1; % Counts which time slice we are on +for i=1:n + U = find(uneliminated); + valid = myintersect(U, stage{t}); + % Choose the best node from the set of valid candidates + score1 = zeros(1,length(valid)); + score2 = zeros(1,length(valid)); + for j=1:length(valid) + k = valid(j); + ns = myintersect(neighbors(G, k), U); + l = length(ns); + M = MG(ns,ns); + score1(j) = l^2 - sum(M(:)); % num. added edges + score2(j) = prod(node_sizes([k ns])); % weight of clique + end + j1s = find(score1==min(score1)); + j = j1s(argmin(score2(j1s))); + k = valid(j); + uneliminated(k) = 0; + order(i) = k; + ns = myintersect(neighbors(G, k), U); + if ~isempty(ns) + G(ns,ns) = 1; + G = setdiag(G,0); + end + if ~any(logical(uneliminated(stage{t}))) % are we allowed to the next slice? + t = t + 1; + end +end + diff --git a/sourcecodes/bnt-master/graph/Old/dag_to_jtree.m b/sourcecodes/bnt-master/graph/Old/dag_to_jtree.m new file mode 100644 index 00000000..23c58430 --- /dev/null +++ b/sourcecodes/bnt-master/graph/Old/dag_to_jtree.m @@ -0,0 +1,55 @@ +function [jtree, root, cliques, B, w, elim_order, moral_edges, fill_in_edges, strong] = ... + dag_to_jtree(dag, node_sizes, partial_order, stages, clusters) +% DAG_TO_JTREE Moralize and triangulate a DAG, and make a junction tree from its cliques. +% [jtree, root, cliques, B, w, elim_order, moral_edges, fill_in_edges, strong] = ... +% dag_to_jtree(dag, node_sizes, partial_order, stages, clusters) +% +% Input: +% dag(i,j) +% jtree(i,j) = 1 iff there is an arc between clique i and clique j +% root = the root clique +% cliques{i} = the nodes in clique i +% B(i,j) = 1 iff node j occurs in clique i +% w(i) = weight of clique i + +N = length(bnet.dag); +if nargin < 2, obs_nodes = []; end +if nargin < 3, stages = { 1:N }; end +if nargin < 4, clusters = {}; end + +[MG, moral_edges] = moralize(bnet.dag); + +% Add extra arcs between nodes in each cluster to ensure they occur in the same clique +for i=1:length(clusters) + c = clusters{i}; + MG(c,c) = 1; +end +MG = setdiag(MG, 0); + +% Find an optimal elimination ordering (NP-hard problem!) +ns = bnet.node_sizes(:); +ns(obs_nodes) = 1; % observed nodes have only 1 possible value +partial_order = determine_elim_constraints(bnet, obs_nodes); + +if isempty(partial_order) + strong = 0; + elim_order = best_first_elim_order(MG, ns, stages); +else + strong = 1; + elim_order = strong_elim_order(MG, ns, partial_order); +end + +[MTG, cliques, fill_in_edges] = triangulate(MG, elim_order); + +% Connect the cliques up into a jtree, +[jtree, root, B, w] = cliques_to_jtree(cliques, ns); + +if 0 + disp('testing dag to jtree'); + % Find the cliques containing each node, and check they form a connected subtree + clqs_con_node = cell(1,N); + for i=1:N + clqs_con_node{i} = find(B(:,i))'; + end + check_jtree_property(clqs_con_node, jtree); +end diff --git a/sourcecodes/bnt-master/graph/Old/dfs.m b/sourcecodes/bnt-master/graph/Old/dfs.m new file mode 100644 index 00000000..0c7f46ab --- /dev/null +++ b/sourcecodes/bnt-master/graph/Old/dfs.m @@ -0,0 +1,84 @@ +function [d, pre, post, height, cycle, pred] = dfs(adj_mat, start, directed) +% DFS Perform a depth-first search of the graph starting from 'start'. +% [d, pre, post, height, cycle, pred] = dfs(adj_mat, start, directed) +% +% d(i) is the time at which node i is first discovered. +% pre is a listing of the nodes in the order in which they are first encountered (opened). +% post is a listing of the nodes in the order in which they are last encountered (closed). +% A node is last encountered once we have explored all of its neighbors. +% If the graph is directed, i's neighbors are its children. +% If the graph is a tree, preorder is parents before children, and +% postorder is children before parents. +% For a DAG, topological order = reverse(postorder). +% height(i) is the height (distance) of node i from the start. +% 'cycle' is true iff a (directed) cycle is found. +% pred(i) is the parent of i in the dfs tree rooted at start. +% See Cormen, Leiserson and Rivest, "An intro. to algorithms" 1994, p478. + +% We can detect undirected cycles by checking if we are about to visit a node n which we have +% already visited. To detect *directed* cycles, we need to know if n has been closed or is still open. +% For example (where arcs are directed down) +% 1 2 +% \ / +% 3 +% Assume we visit 1, 3 and then 2 in order. The fact that a child of 2 (namely, 3) has +% already been visited is okay, because 3 has been closed. +% The algorithms in Aho, Hopcroft and Ullman, and Sedgewick, do not detect directed cycles. + +n = length(adj_mat); + +global white gray black +white = 0; gray = 1; black = 2; + +color = white*ones(1,n); +d = zeros(1,n); +height = zeros(1,n); +pred = zeros(1,n); +pre = []; +post = []; +cycle = 0; +global count +count = 0; +h = 0; +[d, pre, post, height, cycle, color, pred] = ... + dfs2(adj_mat, start, directed, h, d, pre, post, height, cycle, color, pred); + + + +%%%%%%%%%% + +function [d, pre, post, height, cycle, color, pred] = ... + dfs2(adj_mat, i, directed, h, d, pre, post, height, cycle, color, pred) + +global count +global white gray black + +color(i) = gray; +count = count + 1; +d(i) = count; +pre = [pre i]; +height(i) = h; +if directed + ns = children(adj_mat, i); +else + ns = neighbors(adj_mat, i); +end +for j=1:length(ns) + n=ns(j); + if ~directed & n==pred(i) % don't go back up the edge you just came down + % continue + else + if color(n) == gray % going back to a non-closed vertex via a new edge + %fprintf(1, 'cycle from %d to %d\n', i, n); + cycle = 1; + end + if color(n) == white % not visited n before + pred(n)=i; + [d, pre, post, height, cycle, color, pred] = ... + dfs2(adj_mat, n, directed, h+1, d, pre, post, height, cycle, color, pred); + end + end +end +color(i) = black; +post = [post i]; + diff --git a/sourcecodes/bnt-master/graph/Old/dsep_test.m b/sourcecodes/bnt-master/graph/Old/dsep_test.m new file mode 100644 index 00000000..f0f14638 --- /dev/null +++ b/sourcecodes/bnt-master/graph/Old/dsep_test.m @@ -0,0 +1,15 @@ + +% Cowell et al p72 +G = zeros(10); +G(1,2)=1; +G(2,3)=1; +G(3,7)=1; +G(4,[5 8])=1; +G(5,6)=1; +G(6,7)=1; +G(7,[9 10])=1; +G(8,9)=1; + +dsep(1, 4, [5 7], G) +dsep(1, 4, [7], G) +dsep(1, 4, [10 5], G) diff --git a/sourcecodes/bnt-master/graph/Old/mk_2D_lattice_slow.m b/sourcecodes/bnt-master/graph/Old/mk_2D_lattice_slow.m new file mode 100644 index 00000000..e5f595b7 --- /dev/null +++ b/sourcecodes/bnt-master/graph/Old/mk_2D_lattice_slow.m @@ -0,0 +1,121 @@ +function G = mk_2D_lattice(nrows, ncols, wrap_around) +% MK_2D_LATTICE Return adjacency matrix for 4-nearest neighbor connected 2D lattice +% G = mk_2D_lattice(nrows, ncols, wrap_around) +% G(k1, k2) = 1 iff k1=(i1,j1) is connected to k2=(i2,j2) +% +% If wrap_around = 1, we use toroidal boundary conditions (default = 0) +% +% Nodes are assumed numbered as in the following 3x3 lattice +% 1 4 7 +% 2 5 8 +% 3 6 9 +% +% e.g., G = mk_2D_lattice(3, 3, 0) returns +% 0 1 0 1 0 0 0 0 0 +% 1 0 1 0 1 0 0 0 0 +% 0 1 0 0 0 1 0 0 0 +% 1 0 0 0 1 0 1 0 0 +% 0 1 0 1 0 1 0 1 0 +% 0 0 1 0 1 0 0 0 1 +% 0 0 0 1 0 0 0 1 0 +% 0 0 0 0 1 0 1 0 1 +% 0 0 0 0 0 1 0 1 0 +% so find(G(5,:)) = [2 4 6 8] +% but find(G(1,:)) = [2 4] +% +% Using wrap around, G = mk_2D_lattice(3, 3, 1), we get +% 0 1 1 1 0 0 1 0 0 +% 1 0 1 0 1 0 0 1 0 +% 1 1 0 0 0 1 0 0 1 +% 1 0 0 0 1 1 1 0 0 +% 0 1 0 1 0 1 0 1 0 +% 0 0 1 1 1 0 0 0 1 +% 1 0 0 1 0 0 0 1 1 +% 0 1 0 0 1 0 1 0 1 +% 0 0 1 0 0 1 1 1 0 +% so find(G(5,:)) = [2 4 6 8] +% and find(G(1,:)) = [2 3 4 7] + +if nargin < 3, wrap_around = 0; end + +% M contains the number of each cell e.g. +% 1 4 7 +% 2 5 8 +% 3 6 9 +% North neighbors (assuming wrap around) are +% 3 6 9 +% 1 4 7 +% 2 5 8 +% Without wrap around, they are +% 1 4 7 +% 1 4 7 +% 2 5 8 +% The first row is arbitrary, since pixels at the top have no north neighbor. + +npixels = nrows*ncols; + +N = 1; E = 2; S = 3; W = 4; +if wrap_around + rows{N} = [nrows 1:nrows-1]; cols{N} = 1:ncols; + rows{E} = 1:nrows; cols{E} = [2:ncols 1]; + rows{S} = [2:nrows 1]; cols{S} = 1:ncols; + rows{W} = 1:nrows; cols{W} = [ncols 1:ncols-1]; +else + rows{N} = [1 1:nrows-1]; cols{N} = 1:ncols; + rows{E} = 1:nrows; cols{E} = [2:ncols 2]; + rows{S} = [2:nrows 2]; cols{S} = 1:ncols; + rows{W} = 1:nrows; cols{W} = [1 1:ncols-1]; +end + +M = reshape(1:npixels, [nrows ncols]); +nbrs = cell(1, 4); +for i=1:4 + nbrs{i} = M(rows{i}, cols{i}); +end + + +G = zeros(npixels, npixels); +if wrap_around + for i=1:4 + if 0 + % naive + for p=1:npixels + G(p, nbrs{i}(p)) = 1; + end + else + % vectorized + ndx2 = sub2ind([npixels npixels], 1:npixels, nbrs{i}(:)'); + G(ndx2) = 1; + end + end +else + i = N; + mask = ones(nrows, ncols); + mask(1,:) = 0; % pixels in row 1 have no nbr to the north + ndx = find(mask); + ndx2 = sub2ind([npixels npixels], ndx, nbrs{i}(ndx)); + G(ndx2) = 1; + + i = E; + mask = ones(nrows, ncols); + mask(:,ncols) = 0; + ndx = find(mask); + ndx2 = sub2ind([npixels npixels], ndx, nbrs{i}(ndx)); + G(ndx2) = 1; + + i = S; + mask = ones(nrows, ncols); + mask(nrows,:)=0; + ndx = find(mask); + ndx2 = sub2ind([npixels npixels], ndx, nbrs{i}(ndx)); + G(ndx2) = 1; + + i = W; + mask = ones(nrows, ncols); + mask(:,1)=0; + ndx = find(mask); + ndx2 = sub2ind([npixels npixels], ndx, nbrs{i}(ndx)); + G(ndx2) = 1; +end + +G = setdiag(G, 0); diff --git a/sourcecodes/bnt-master/graph/README.txt b/sourcecodes/bnt-master/graph/README.txt new file mode 100644 index 00000000..917cd9ae --- /dev/null +++ b/sourcecodes/bnt-master/graph/README.txt @@ -0,0 +1,3 @@ +Graph theory toolbox. +All functions written by Kevin Murphy (1998) unless otherwise noted. +The latest version is available from http://www.ai.mit.edu/~murphyk/Software/index.html diff --git a/sourcecodes/bnt-master/graph/acyclic.m b/sourcecodes/bnt-master/graph/acyclic.m new file mode 100644 index 00000000..0b497d73 --- /dev/null +++ b/sourcecodes/bnt-master/graph/acyclic.m @@ -0,0 +1,23 @@ +function b = acyclic(adj_mat, directed) +% ACYCLIC Returns true iff the graph has no (directed) cycles. +% b = acyclic(adj_mat, directed) + +adj_mat = double(adj_mat); +if nargin < 2, directed = 1; end + +% e.g., G = +% 1 -> 3 +% | +% v +% 2 <- 4 +% In this case, 1->2 in the transitive closure, but 1 cannot get to itself. +% If G was undirected, 1 could get to itself, but this graph is not cyclic. +% So we cannot use the closure test in the undirected case. + +if directed + R = reachability_graph(adj_mat); + b = ~any(diag(R)==1); +else + [d, pre, post, cycle] = dfs(adj_mat,[],directed); + b = ~cycle; +end diff --git a/sourcecodes/bnt-master/graph/assignEdgeNums.m b/sourcecodes/bnt-master/graph/assignEdgeNums.m new file mode 100644 index 00000000..5d9a2a0d --- /dev/null +++ b/sourcecodes/bnt-master/graph/assignEdgeNums.m @@ -0,0 +1,32 @@ +function [edge_id, nedges] = assignEdgeNums(adj_mat) +% give each edge a unique number +% we number (i,j) for j>i first, in row, column order. +% Then we number the reverse links + +nnodes = length(adj_mat); +edge_id = zeros(nnodes); +e = 1; +for i=1:nnodes + for j=i+1:nnodes + if adj_mat(i,j) + edge_id(i,j) = e; + e = e+1; + end + end +end + +nedges = e-1; +tmp = edge_id; +ndx = find(tmp); +tmp(ndx) = tmp(ndx)+nedges; +edge_id = edge_id + triu(tmp)'; + + +if 0 +ndx = find(adj_mat); +nedges = length(ndx); +nnodes = length(adj_mat); +edge_id = zeros(1, nnodes*nnodes); +edge_id(ndx) = 1:nedges; +edge_id = reshape(edge_id, nnodes, nnodes); +end diff --git a/sourcecodes/bnt-master/graph/best_first_elim_order.m b/sourcecodes/bnt-master/graph/best_first_elim_order.m new file mode 100644 index 00000000..2a808c16 --- /dev/null +++ b/sourcecodes/bnt-master/graph/best_first_elim_order.m @@ -0,0 +1,68 @@ +function order = best_first_elim_order(G, node_sizes, stage) +% BEST_FIRST_ELIM_ORDER Greedily search for an optimal elimination order. +% order = best_first_elim_order(moral_graph, node_sizes) +% +% Find an order in which to eliminate nodes from the graph in such a way as to try and minimize the +% weight of the resulting triangulated graph. The weight of a graph is the sum of the weights of each +% of its cliques; the weight of a clique is the product of the weights of each of its members; the +% weight of a node is the number of values it can take on. +% +% Since this is an NP-hard problem, we use the following greedy heuristic: +% at each step, eliminate that node which will result in the addition of the least +% number of fill-in edges, breaking ties by choosing the node that induces the lighest clique. +% For details, see +% - Kjaerulff, "Triangulation of graphs -- algorithms giving small total state space", +% Univ. Aalborg tech report, 1990 (www.cs.auc.dk/~uk) +% - C. Huang and A. Darwiche, "Inference in Belief Networks: A procedural guide", +% Intl. J. Approx. Reasoning, 11, 1994 +% + +% Warning: This code is pretty old and could probably be made faster. + +n = length(G); +if nargin < 3, stage = { 1:n }; end % no constraints + +% For long DBNs, it may be useful to eliminate all the nodes in slice t before slice t+1. +% This will ensure that the jtree has a repeating structure (at least away from both edges). +% This is why we have stages. +% See the discussion of splicing jtrees on p68 of +% Geoff Zweig's PhD thesis, Dept. Comp. Sci., UC Berkeley, 1998. +% This constraint can increase the clique size significantly. + +MG = G; % copy the original graph +uneliminated = ones(1,n); +order = zeros(1,n); +t = 1; % Counts which time slice we are on +for i=1:n + U = find(uneliminated); + valid = myintersect(U, stage{t}); + % Choose the best node from the set of valid candidates + min_fill = zeros(1,length(valid)); + min_weight = zeros(1,length(valid)); + for j=1:length(valid) + k = valid(j); + nbrs = myintersect(neighbors(G, k), U); + l = length(nbrs); + M = MG(nbrs,nbrs); + min_fill(j) = l^2 - sum(M(:)); % num. added edges + min_weight(j) = prod(node_sizes([k nbrs])); % weight of clique + end + lightest_nbrs = find(min_weight==min(min_weight)); + % break ties using min-fill heuristic + best_nbr_ndx = argmin(min_fill(lightest_nbrs)); + j = lightest_nbrs(best_nbr_ndx); % we will eliminate the j'th element of valid + %j1s = find(score1==min(score1)); + %j = j1s(argmin(score2(j1s))); + k = valid(j); + uneliminated(k) = 0; + order(i) = k; + ns = myintersect(neighbors(G, k), U); + if ~isempty(ns) + G(ns,ns) = 1; + G = setdiag(G,0); + end + if ~any(logical(uneliminated(stage{t}))) % are we allowed to the next slice? + t = t + 1; + end +end + diff --git a/sourcecodes/bnt-master/graph/check_jtree_property.m b/sourcecodes/bnt-master/graph/check_jtree_property.m new file mode 100644 index 00000000..3986c05e --- /dev/null +++ b/sourcecodes/bnt-master/graph/check_jtree_property.m @@ -0,0 +1,18 @@ +function check_jtree_property(cliques_containing_node, jtree) +% CHECK_JTREE_PROPERTY Raise an error if the graph does not satisfy the join tree property. +% check_jtree_property(cliques_containing_node, jtree_adj_mat) +% +% The join tree property says: +% For each node n in the dag, compute the node-induced subgraph G by looking at all the cliques +% that contain n, and make sure G forms a connected graph. +% This ensures that local propagation leads to global consistency. + +num_bn_nodes = length(cliques_containing_node); +directed = 0; +for i=1:num_bn_nodes + cs = cliques_containing_node{i}; + G = jtree(cs,cs); + if ~connected_graph(G, directed) + error(['node ' num2str(i) ' violates jtree property']); + end +end diff --git a/sourcecodes/bnt-master/graph/check_triangulated.m b/sourcecodes/bnt-master/graph/check_triangulated.m new file mode 100644 index 00000000..99bcc9f1 --- /dev/null +++ b/sourcecodes/bnt-master/graph/check_triangulated.m @@ -0,0 +1,34 @@ +function [triangulated, order] = check_triangulated(G) +% CHECK_TRIANGULATED Return 1 if G is a triangulated (chordal) graph, 0 otherwise. +% [triangulated, order] = check_triangulated(G) +% +% A numbering alpha is perfect if Nbrs(alpha(i)) intersect {alpha(1)...alpha(i-1)} is complete. +% A graph is triangulated iff it has a perfect numbering. +% The Maximum Cardinality Search algorithm will create such a perfect numbering if possible. +% See Golumbic, "Algorithmic Graph Theory and Perfect Graphs", Cambridge Univ. Press, 1985, p85. +% or Castillo, Gutierrez and Hadi, "Expert systems and probabilistic network models", Springer 1997, p134. + + +G = setdiag(G, 1); +n = length(G); +order = zeros(1,n); +triangulated = 1; +numbered = [1]; +order(1) = 1; +for i=2:n + U = mysetdiff(1:n, numbered); % unnumbered nodes + score = zeros(1, length(U)); + for ui=1:length(U) + u = U(ui); + score(ui) = length(myintersect(neighbors(G, u), numbered)); + end + u = U(argmax(score)); + numbered = [numbered u]; + order(i) = u; + nns = myintersect(neighbors(G,u), order(1:i-1)); % numbered neighbors + if ~isequal(G(nns,nns), ones(length(nns))) % ~complete(G(nns,nns)) + triangulated = 0; + break; + end +end + diff --git a/sourcecodes/bnt-master/graph/children.m b/sourcecodes/bnt-master/graph/children.m new file mode 100644 index 00000000..d79701b7 --- /dev/null +++ b/sourcecodes/bnt-master/graph/children.m @@ -0,0 +1,17 @@ +function cs = children(adj_mat, i, t) +% CHILDREN Return the indices of a node's children in sorted order +% c = children(adj_mat, i, t) +% +% t is an optional argument: if present, dag is assumed to be a 2-slice DBN + +if nargin < 3 + cs = find(adj_mat(i,:)); +else + if t==1 + cs = find(adj_mat(i,:)); + else + ss = length(adj_mat)/2; + j = i+ss; + cs = find(adj_mat(j,:)) + (t-2)*ss; + end +end diff --git a/sourcecodes/bnt-master/graph/cliques_to_jtree.m b/sourcecodes/bnt-master/graph/cliques_to_jtree.m new file mode 100644 index 00000000..921ced0b --- /dev/null +++ b/sourcecodes/bnt-master/graph/cliques_to_jtree.m @@ -0,0 +1,57 @@ +function [jtree, root, B, w] = cliques_to_jtree(cliques, ns) +% MK_JTREE Make an optimal junction tree. +% [jtree, root, B, w] = mk_jtree(cliques, ns) +% +% A junction tree is a tree that satisfies the jtree property, which says: +% for each pair of cliques U,V with intersection S, all cliques on the path between U and V +% contain S. (This ensures that local propagation leads to global consistency.) +% +% We can create a junction tree by computing the maximal spanning tree of the junction graph. +% (The junction graph connects all cliques, and the weight of an edge (i,j) is +% |C(i) intersect C(j)|, where C(i) is the i'th clique.) +% +% The best jtree is the maximal spanning tree which minimizes the sum of the costs on each edge, +% where cost(i,j) = w(C(i)) + w(C(j)), and w(C) is the weight of clique C, +% which is the total number of values C can take on. +% +% For details, see +% - Jensen and Jensen, "Optimal Junction Trees", UAI 94. +% +% Input: +% cliques{i} = nodes in clique i +% ns(i) = number of values node i can take on +% Output: +% jtree(i,j) = 1 iff cliques i and j aer connected +% root = the clique that should be used as root +% B(i,j) = 1 iff node j occurs in clique i +% w(i) = weight of clique i + + + +num_cliques = length(cliques); +w = zeros(num_cliques, 1); +B = sparse(num_cliques, 1); +for i=1:num_cliques + B(i, cliques{i}) = 1; + w(i) = prod(ns(cliques{i})); +end + + +% C1(i,j) = length(intersect(cliques{i}, cliques{j})); +% The length of the intersection of two sets is the dot product of their bit vector representation. +C1 = B*B'; +C1 = setdiag(C1, 0); + +% C2(i,j) = w(i) + w(j) +num_cliques = length(w); +W = repmat(w, 1, num_cliques); +C2 = W + W'; +C2 = setdiag(C2, 0); + +jtree = sparse(minimum_spanning_tree(-C1, C2)); % Using -C1 gives *maximum* spanning tree + +% The root is arbitrary, but since the first pass is towards the root, +% we would like this to correspond to going forward in time in a DBN. +root = num_cliques; + + diff --git a/sourcecodes/bnt-master/graph/cliques_to_strong_jtree.m b/sourcecodes/bnt-master/graph/cliques_to_strong_jtree.m new file mode 100644 index 00000000..551894f2 --- /dev/null +++ b/sourcecodes/bnt-master/graph/cliques_to_strong_jtree.m @@ -0,0 +1,114 @@ +function [jtree, root, cliques, B, w] = mk_strong_jtree(cliques, ns, elim_order, MTG) +% MK_SRONG_JTREE Make a strong junction tree. +% [jtree, root, cliques, B, w] = mk_strong_jtree(cliques, ns, elim_order, MTG) +% +% Here is a definition of a strong jtree from Jensen et al. 1994: +% "A junction tree is said to be strong if it has at least one distinguished clique R, +% called a strong root, s.t. for each pair (C1,C2) of adjacent cliques in the tree, +% with C1 closer to R than C2, there exists and ordering of [the nodes below] C2 +% that respects [the partial order] and with the vertices of the separator C1 intersect C2 +% preceeding the vertices [below C2] of C2 \ C1." +% +% For details, see +% - Jensen, Jensen and Dittmer, "From influence diagrams to junction trees", UAI 94. +% +% MTG is the moralized, triangulated graph. +% elim_order is the elimination ordering used to compute MTG. + + +% Warning: this is a very naive implementation of the algorithm in Jensen et al. + +n = length(elim_order); +alpha(elim_order) = n:-1:1; +% alpha(u) = i if we eliminate u at step n-i+1 +% i.e., vertices with higher alpha numbers are eliminated before vertices with lower numbers. +% e.g., from the Jensen et al paper +% node a=1 eliminated at step 6, so alpha(a)=16-6+1=11. +% alpha = [11 1 2 10 9 3 4 7 5 8 13 12 6 16 15 14] + + +% We sort the cliques in order of increasing index. The index of a clique C is defined as follows. +% Let lower = {u | alpha(u) < alpha(v)}, and +% let v in C be the highest-numbered vertex s.t. the vertices in W = lower intersect C +% have a common neighbor u in U, where U = lower \ C. +% If such a v exists, define index(C) = alpha(v), otherwise, index(C) = 1. +% Intuitively, index(C) is the step in the elimination process at which C disappears. + +num_cliques = length(cliques); +index = zeros(1, num_cliques); +for c = 1:num_cliques + C = cliques{c}; + highest_num = -inf; + for vi = 1:length(C) + v = C(vi); + lower = find(alpha < alpha(v)); + W = myintersect(lower, C); + U = mysetdiff(lower, C); + found = 0; + for ui=1:length(U) + u = U(ui); + if mysubset(W, neighbors(MTG, u)) + found = 1; + break; + end + end + if found + if alpha(v) > highest_num + highest_num = alpha(v); + end + end + end + if highest_num == -inf + index(c) = 1; + else + index(c) = highest_num; + end +end + + +% Permute the cliques so that they are ordered according to index +[dummy, clique_order] = sort(index); +cliques = cliques(clique_order); + +w = zeros(num_cliques, 1); +B = sparse(num_cliques, 1); +for i=1:num_cliques + B(i, cliques{i}) = 1; + w(i) = prod(ns(cliques{i})); +end + +% Pearl p113 suggests ordering the cliques by rank of the highest vertex in each clique. +% However, this will only work if we use maximum cardinality search. + + +% Join up the cliques so that they satisfy the Running Intersection Property. +% This states that, for all k > 1, S(k) subseteq C(j) for some j < k, where +% S(k) = C(k) intersect (union_{i=1}^{k-1} C(i)) +jtree = sparse(num_cliques, num_cliques); +for k=2:num_cliques + S = []; + for i=1:k-1 + S = myunion(S, cliques{i}); + end + S = myintersect(S, cliques{k}); + found = 0; + for j=1:k-1 + if mysubset(S, cliques{j}) + found = 1; + break; + end + end + if ~found + disp(['RIP is violated for clique ' num2str(k)]); + end + jtree(k,j)=1; + jtree(j,k)=1; +end + +% Pearl p113 suggests connecting Ci to a predecessor Cj (j < i) sharing +% the highest number of vertices with Ci (i.e., the heaviest i-j edge +% in the jgraph). However, this will only work if we use maximum cardinality search. + +root = 1; + + diff --git a/sourcecodes/bnt-master/graph/connected_graph.m b/sourcecodes/bnt-master/graph/connected_graph.m new file mode 100644 index 00000000..612393ea --- /dev/null +++ b/sourcecodes/bnt-master/graph/connected_graph.m @@ -0,0 +1,9 @@ +function b = connected(adj_mat, directed) +% +% b = connected(adj_mat). +% Returns true iff the graph is connected. + +n = length(adj_mat); +start = 1; +[d, pre] = dfs(adj_mat, start, directed); +b = (length(pre) == n); diff --git a/sourcecodes/bnt-master/graph/dag_to_essential_graph.m b/sourcecodes/bnt-master/graph/dag_to_essential_graph.m new file mode 100644 index 00000000..84dd8ee5 --- /dev/null +++ b/sourcecodes/bnt-master/graph/dag_to_essential_graph.m @@ -0,0 +1,112 @@ + +function [eg] = dag_to_essential_graph(dag) +cpdag = dag_to_cpdag(dag); +eg = dag + dag .* (cpdag + cpdag'); + +return; + + + + +% Coverts a DAG into Essential Graph where edges are coded by 2 and 3, 2 is +% directed edge and 3 is bidirected edge and is at one (the same as the original DAG) of the two +% symetrical places. + +% Is implemented by the algorithm of Max Chickering in D.M.Chickering (1995). +% A transformational characterization of equivalent Bayesian network structures. +% In Proceedings of Eleventh Conference on Uncertainty in Artificial Intelligence, Montreal, QU, +% pages 87-98. Morgan Kaufmann +% http://research.microsoft.com/~dmax/publications/uai95.pdf + +% Implemented by Tomas Kocka, AAU. + +function [eg] = dag_to_essential_graph(dagx) + +%print_dag(dagx); % Just checking input + +order = topological_sort(dagx); % get the topological order of nodes and their number + +% fprintf('the topological order is: %d',order); +% fprintf('\n'); + +[nx,ny] = size(dagx); % gets the number of nodes, note that nx == ny +[I,J] = find(dagx); % finds all nonzero elements in the adjacency matrix, i.e. arcs in the DAG - however we will overwrite it in a special order +% we will sort the arcs from lowest possible y and highest possible x, arcs are x->y +e = 1; +for y = 1:ny + for x = nx:-1:1 + %fprintf('x %d ',order(x)); fprintf('y %d ',order(y)); + if dagx(order(x),order(y)) == 1 + I(e) = order(x); + J(e) = order(y); + e = e + 1; + %fprintf('x order %d',x); + %fprintf('y order %d',y); + %fprintf('\n'); + end + end +end + + +% fprintf('the arcs are: %d',I); +% fprintf('\n'); +% fprintf('the arcs are: %d',J); +% fprintf('\n'); + + +% Now we have to decide which arcs are part of the essential graph and +% which are undirected edges in the essential graph. +% Undecided arc in the DAG are 1, directed in EG are 2 and undirected in EG +% are 3. + + +for e = 1:length(I) + if dagx(I(e),J(e)) == 1 + cont = true; + for w = 1:nx + if dagx(w,I(e)) == 2 + if dagx(w,J(e)) ~= 0 + dagx(w,J(e)) = 2; + else + for ww = 1:nx + if dagx(ww,J(e)) ~= 0 + dagx(ww,J(e)) = 2; + end + end % and now skip the rest and start with another arc from the list + w = nx; + cont = false; + end + end + end + if cont + exists = false; + for z = 1:nx + %fprintf('test %d',dagx(z,J(e))); + if dagx(z,J(e)) ~= 0 & z ~= I(e) & dagx(z,I(e)) == 0 + exists = true; + for ww = 1:nx + if dagx(ww,J(e)) == 1 + dagx(ww,J(e)) = 2; + end + end + end + end + if ~ exists + for ww = 1:nx + if dagx(ww,J(e)) == 1 + dagx(ww,J(e)) = 3; + end + end + end + end + end +end + +%print_dag(dagx); % Just checking output + + + + + + + diff --git a/sourcecodes/bnt-master/graph/dfs.m b/sourcecodes/bnt-master/graph/dfs.m new file mode 100644 index 00000000..cd13c358 --- /dev/null +++ b/sourcecodes/bnt-master/graph/dfs.m @@ -0,0 +1,91 @@ +function [d, pre, post, cycle, f, pred] = dfs(adj_mat, start, directed) +% DFS Perform a depth-first search of the graph starting from 'start'. +% [d, pre, post, cycle, f, pred] = dfs(adj_mat, start, directed) +% +% Input: +% adj_mat(i,j)=1 iff i is connected to j. +% start is the root vertex of the dfs tree; if [], all nodes are searched +% directed = 1 if the graph is directed +% +% Output: +% d(i) is the time at which node i is first discovered. +% pre is a list of the nodes in the order in which they are first encountered (opened). +% post is a list of the nodes in the order in which they are last encountered (closed). +% 'cycle' is true iff a (directed) cycle is found. +% f(i) is the time at which node i is finished. +% pred(i) is the predecessor of i in the dfs tree. +% +% If the graph is a tree, preorder is parents before children, +% and postorder is children before parents. +% For a DAG, topological order = reverse(postorder). +% +% See Cormen, Leiserson and Rivest, "An intro. to algorithms" 1994, p478. + +n = length(adj_mat); + +global white gray black color +white = 0; gray = 1; black = 2; +color = white*ones(1,n); + +global time_stamp +time_stamp = 0; + +global d f +d = zeros(1,n); +f = zeros(1,n); + +global pred +pred = zeros(1,n); + +global cycle +cycle = 0; + +global pre post +pre = []; +post = []; + +if ~isempty(start) + dfs_visit(start, adj_mat, directed); +end +for u=1:n + if color(u)==white + dfs_visit(u, adj_mat, directed); + end +end + + +%%%%%%%%%% + +function dfs_visit(u, adj_mat, directed) + +global white gray black color time_stamp d f pred cycle pre post + +pre = [pre u]; +color(u) = gray; +time_stamp = time_stamp + 1; +d(u) = time_stamp; +if directed + ns = children(adj_mat, u); +else + ns = neighbors(adj_mat, u); + ns = setdiff(ns, pred(u)); % don't go back to visit the guy who called you! +end +for v=ns(:)' + %fprintf('u=%d, v=%d, color(v)=%d\n', u, v, color(v)) + switch color(v) + case white, % not visited v before (tree edge) + pred(v)=u; + dfs_visit(v, adj_mat, directed); + case gray, % back edge - v has been visited, but is still open + cycle = 1; + %fprintf('cycle: back edge from v=%d to u=%d\n', v, u); + case black, % v has been visited, but is closed + % no-op + end +end +color(u) = black; +post = [post u]; +time_stamp = time_stamp + 1; +f(u) = time_stamp; + + diff --git a/sourcecodes/bnt-master/graph/dfs_test.m b/sourcecodes/bnt-master/graph/dfs_test.m new file mode 100644 index 00000000..e3e4ae12 --- /dev/null +++ b/sourcecodes/bnt-master/graph/dfs_test.m @@ -0,0 +1,30 @@ +% Do the example in fig 23.4 p479 of Cormen, Leiserson and Rivest (1994) + +u = 1; v = 2; w = 3; x = 4; y = 5; z = 6; +n = 6; +dag=zeros(n,n); +dag(u,[v x])=1; +dag(v,y)=1; +dag(w,[y z])=1; +dag(x,v)=1; +dag(y,x)=1; +dag(z,z)=1; + +[d, pre, post, cycle, f, pred] = dfs(dag, [], 1); +assert(isequal(d, [1 2 9 4 3 10])) +assert(isequal(f, [8 7 12 5 6 11]) +assert(cycle) + +% Now give it an undirected cyclic graph +G = mk_2D_lattice(2,2,0); +% 1 - 3 +% | | +% 2 - 4 +[d, pre, post, cycle, f, pred] = dfs(G, [], 0); +% d = [1 2 4 3] +assert(cycle) + +% Now break the cycle +G(1,2)=0; G(2,1)=0; +[d, pre, post, cycle, f, pred] = dfs(G, [], 0); +assert(~cycle) diff --git a/sourcecodes/bnt-master/graph/dijkstra.m b/sourcecodes/bnt-master/graph/dijkstra.m new file mode 100644 index 00000000..2e444d4c --- /dev/null +++ b/sourcecodes/bnt-master/graph/dijkstra.m @@ -0,0 +1,112 @@ +function [D,P] = dijk(A,s,t) +%DIJK Shortest paths from nodes 's' to nodes 't' using Dijkstra algorithm. +% [D,p] = dijk(A,s,t) +% A = n x n node-node weighted adjacency matrix of arc lengths +% (Note: A(i,j) = 0 => Arc (i,j) does not exist; +% A(i,j) = NaN => Arc (i,j) exists with 0 weight) +% s = FROM node indices +% = [] (default), paths from all nodes +% t = TO node indices +% = [] (default), paths to all nodes +% D = |s| x |t| matrix of shortest path distances from 's' to 't' +% = [D(i,j)], where D(i,j) = distance from node 'i' to node 'j' +% P = |s| x n matrix of predecessor indices, where P(i,j) is the +% index of the predecessor to node 'j' on the path from 's(i)' to 'j' +% (use PRED2PATH to convert P to paths) +% = path from 's' to 't', if |s| = |t| = 1 +% +% (If A is a triangular matrix, then computationally intensive node +% selection step not needed since graph is acyclic (triangularity is a +% sufficient, but not a necessary, condition for a graph to be acyclic) +% and A can have non-negative elements) +% +% (If |s| >> |t|, then DIJK is faster if DIJK(A',t,s) used, where D is now +% transposed and P now represents successor indices) +% +% (Based on Fig. 4.6 in Ahuja, Magnanti, and Orlin, Network Flows, +% Prentice-Hall, 1993, p. 109.) + +% Copyright (c) 1998-2001 by Michael G. Kay +% Matlog Version 5 22-Aug-2001 + +% Input Error Checking ****************************************************** +error(nargchk(1,3,nargin)); + +[n,cA] = size(A); + +if nargin < 2 | isempty(s), s = (1:n)'; else s = s(:); end +if nargin < 3 | isempty(t), t = (1:n)'; else t = t(:); end + +if ~any(any(tril(A) ~= 0)) % A is upper triangular + isAcyclic = 1; +elseif ~any(any(triu(A) ~= 0)) % A is lower triangular + isAcyclic = 2; +else % Graph may not be acyclic + isAcyclic = 0; +end + +if n ~= cA + error('A must be a square matrix'); +elseif ~isAcyclic & any(any(A < 0)) + error('A must be non-negative'); +elseif any(s < 1 | s > n) + error(['''s'' must be an integer between 1 and ',num2str(n)]); +elseif any(t < 1 | t > n) + error(['''t'' must be an integer between 1 and ',num2str(n)]); +end +% End (Input Error Checking) ************************************************ + +A = A'; % Use transpose to speed-up FIND for sparse A + +D = zeros(length(s),length(t)); +if nargout > 1, P = zeros(length(s),n); end + +for i = 1:length(s) + j = s(i); + + Di = Inf*ones(n,1); Di(j) = 0; + + isLab = logical(zeros(length(t),1)); + if isAcyclic == 1 + nLab = j - 1; + elseif isAcyclic == 2 + nLab = n - j; + else + nLab = 0; + UnLab = 1:n; + isUnLab = logical(ones(n,1)); + end + + while nLab < n & ~all(isLab) + if isAcyclic + Dj = Di(j); + else % Node selection + [Dj,jj] = min(Di(isUnLab)); + j = UnLab(jj); + UnLab(jj) = []; + isUnLab(j) = 0; + end + + nLab = nLab + 1; + if length(t) < n, isLab = isLab | (j == t); end + + [jA,kA,Aj] = find(A(:,j)); + Aj(isnan(Aj)) = 0; + + if isempty(Aj), Dk = Inf; else Dk = Dj + Aj; end + + if nargout > 1, P(i,jA(Dk < Di(jA))) = j; end + Di(jA) = min(Di(jA),Dk); + + if isAcyclic == 1 % Increment node index for upper triangular A + j = j + 1; + elseif isAcyclic == 2 % Decrement node index for lower triangular A + j = j - 1; + end + end + D(i,:) = Di(t)'; +end + +if nargout > 1 & length(s) == 1 & length(t) == 1 + P = pred2path(P,s,t); +end diff --git a/sourcecodes/bnt-master/graph/family.m b/sourcecodes/bnt-master/graph/family.m new file mode 100644 index 00000000..0a6494d6 --- /dev/null +++ b/sourcecodes/bnt-master/graph/family.m @@ -0,0 +1,17 @@ +function f = family(A,i,t) +% FAMILY Return the indices of parents and self in sorted order +% f = family(dag,i,t) +% +% t is an optional argument: if present, dag is assumed to be a 2-slice DBN + +if nargin < 3 + f = [parents(A,i) i]; +else + if t == 1 + f = [parents(A,i) i]; + else + ss = length(A)/2; + j = i+ss; + f = [parents(A,j) j] + (t-2)*ss; + end +end diff --git a/sourcecodes/bnt-master/graph/findroot.m b/sourcecodes/bnt-master/graph/findroot.m new file mode 100644 index 00000000..d242a3a3 --- /dev/null +++ b/sourcecodes/bnt-master/graph/findroot.m @@ -0,0 +1,24 @@ +function root = findroot(bnet, cliques) + +%% findroot is to find the strong root in a clique tree assume it has one +%% in the tree. For a clique tree constructed from a strongly triangulated +%% graph, an interface clique that contains all discrete parents +%% and at least one continuous node from a connected continuous component +%% is for sure to be available as a guaranteed strong root. +%% -By Wei Sun, George Mason University, 4/17/2010. + +%% We choose the interface clique that contains the max number +%% of interface nodes to be the strong root. +n0 = 0 ; +for i=1:length(cliques) + % check hybrid cliques + hc = intersect(cliques{i}, bnet.cnodes) ; + hd = intersect(cliques{i}, bnet.dnodes) ; + if ~isempty(hd) & ~isempty(hc) + nd = length(hd) ; + if nd > n0 + root = i ; + n0 = nd ; + end + end +end diff --git a/sourcecodes/bnt-master/graph/graph_separated.m b/sourcecodes/bnt-master/graph/graph_separated.m new file mode 100644 index 00000000..32a11841 --- /dev/null +++ b/sourcecodes/bnt-master/graph/graph_separated.m @@ -0,0 +1,9 @@ +function sep = graph_separated(G, X, Y, S) + +G2 = G; +G2(S,:) = 0; +G2(:,S) = 0; +conn = reachability_graph(G2); +conn2 = conn(X,Y); +sep = all(conn2(:)==0); + diff --git a/sourcecodes/bnt-master/graph/graph_to_jtree.m b/sourcecodes/bnt-master/graph/graph_to_jtree.m new file mode 100644 index 00000000..952bbca7 --- /dev/null +++ b/sourcecodes/bnt-master/graph/graph_to_jtree.m @@ -0,0 +1,59 @@ +function [jtree, root, cliques, B, w, elim_order] = graph_to_jtree(MG, ns, partial_order, stages, clusters) +% GRAPH_TO_JTREE Triangulate a graph and make a junction tree from its cliques. +% [jtree, root, cliques, B, w, elim_order] = ... +% graph_to_jtree(graph, node_sizes, partial_order, stages, clusters) +% +% INPUT: +% graph(i,j) = 1 iff there is an edge between i,j +% node_weights(i) = num discrete values node i can take on [1 if observed] +% partial_order = {} if no constraints on elimination ordering +% stages{i} = nodes that must be eliminated at i'th stage (if porder is empty) +% clusters{i} = list of nodes that must get connected together in the moral graph +% +% OUTPUT: +% jtree(i,j) = 1 iff there is an arc between clique i and clique j +% root = the root clique +% cliques{i} = the nodes in clique i +% B(i,j) = 1 iff node j occurs in clique i +% w(i) = weight of clique i + +N = length(MG); + +if nargin >= 5 + % Add extra arcs between nodes in each cluster to ensure they occur in the same clique + for i=1:length(clusters) + c = clusters{i}; + MG(c,c) = 1; + end +end +MG = setdiag(MG, 0); + +% Find an optimal elimination ordering (NP-hard problem!) +if nargin < 4 + stages = {1:N}; +end +if nargin < 3 + partial_order = {}; +end +if isempty(partial_order) + strong = 0; + elim_order = best_first_elim_order(MG, ns, stages); +else + strong = 1; + elim_order = strong_elim_order(MG, ns, partial_order); +end + +[MTG, cliques, fill_in_edges] = triangulate(MG, elim_order); + +% Connect the cliques up into a jtree, +[jtree, root, B, w] = cliques_to_jtree(cliques, ns); + +if 0 + disp('testing dag to jtree'); + % Find the cliques containing each node, and check they form a connected subtree + clqs_con_node = cell(1,N); + for i=1:N + clqs_con_node{i} = find(B(:,i))'; + end + check_jtree_property(clqs_con_node, jtree); +end diff --git a/sourcecodes/bnt-master/graph/min_subtree_con_nodes.m b/sourcecodes/bnt-master/graph/min_subtree_con_nodes.m new file mode 100644 index 00000000..0bc3c2d3 --- /dev/null +++ b/sourcecodes/bnt-master/graph/min_subtree_con_nodes.m @@ -0,0 +1,60 @@ +function [subtree, nroot_node] = min_subtree_con_nodes(jtree, root, nodes) +%min_subtree_con_nodes get the minimum subtree of tree which contains the nodes + +if isempty(jtree) | isempty(nodes) + subtree = []; + nroot_node = []; + return; +end + +rnodes = min_subtree_nodes(jtree, nodes); +nea_node = nearest_node(jtree, root, nodes); +node_num = length(jtree); +subtree = zeros(node_num); +subtree(rnodes, rnodes) = jtree(rnodes, rnodes); +nroot_node = nea_node; + + +function rnodes = min_subtree_nodes(tree, nodes) +rnodes = []; +if isempty(tree) | isempty(nodes) + return +end + +rnodes = nodes(1); +newnodes = neighbors(tree, nodes(1)); +while ~mysubset(nodes, rnodes) + swapnodes = newnodes; + newnodes = []; + added = 0; + for i=1:length(swapnodes) + inode = swapnodes(i); + tnodes = myunion(inode, rnodes); + if mysubset(nodes, tnodes) + added = 1; + break; + end + nns = neighbors(tree, inode); + add_nodes = mysetdiff(nns, tnodes); + newnodes = myunion(newnodes, add_nodes); + end + if added + rnodes = tnodes; + else + rnodes = myunion(rnodes, newnodes); + end +end + +function nea_node = nearest_node(tree, inode, nodes) +if myismember(inode, nodes) + nea_node = inode; + return; +end +cs = children(tree, inode); +for i = 1:length(cs) + n = cs(i); + nea_node = nearest_node(tree, n, nodes); +end + + + diff --git a/sourcecodes/bnt-master/graph/minimum_spanning_tree.m b/sourcecodes/bnt-master/graph/minimum_spanning_tree.m new file mode 100644 index 00000000..ca208985 --- /dev/null +++ b/sourcecodes/bnt-master/graph/minimum_spanning_tree.m @@ -0,0 +1,51 @@ +function A = minimum_spanning_tree(C1, C2) +% +% Find the minimum spanning tree using Prim's algorithm. +% C1(i,j) is the primary cost of connecting i to j. +% C2(i,j) is the (optional) secondary cost of connecting i to j, used to break ties. +% We assume that absent edges have 0 cost. +% To find the maximum spanning tree, used -1*C. +% See Aho, Hopcroft & Ullman 1983, "Data structures and algorithms", p 237. + +% Prim's is O(V^2). Kruskal's algorithm is O(E log E) and hence is more efficient +% for sparse graphs, but is implemented in terms of a priority queue. + +% We partition the nodes into those in U and those not in U. +% closest(i) is the vertex in U that is closest to i in V-U. +% lowcost(i) is the cost of the edge (i, closest(i)), or infinity is i has been used. +% In Aho, they say C(i,j) should be "some appropriate large value" if the edge is missing. +% We set it to infinity. +% However, since lowcost is initialized from C, we must distinguish absent edges from used nodes. + +n = length(C1); +if nargin==1, C2 = zeros(n); end +A = zeros(n); + +closest = ones(1,n); +used = zeros(1,n); % contains the members of U +used(1) = 1; % start with node 1 +C1(find(C1==0))=inf; +C2(find(C2==0))=inf; +lowcost1 = C1(1,:); +lowcost2 = C2(1,:); + +for i=2:n + ks = find(lowcost1==min(lowcost1)); + k = ks(argmin(lowcost2(ks))); + A(k, closest(k)) = 1; + A(closest(k), k) = 1; + lowcost1(k) = inf; + lowcost2(k) = inf; + used(k) = 1; + NU = find(used==0); + for ji=1:length(NU) + for j=NU(ji) + if C1(k,j) < lowcost1(j) + lowcost1(j) = C1(k,j); + lowcost2(j) = C2(k,j); + closest(j) = k; + end + end + end +end + diff --git a/sourcecodes/bnt-master/graph/minspan.m b/sourcecodes/bnt-master/graph/minspan.m new file mode 100644 index 00000000..23d396fb --- /dev/null +++ b/sourcecodes/bnt-master/graph/minspan.m @@ -0,0 +1,56 @@ +function [t,nk] = minspan(IJC) +%MINSPAN Minimum weight spanning tree using Kruskal algorithm. +%[t,nk] = minspan(IJC) +% IJC = n x 3 matrix arc list [i j c] of arc heads, tails, and costs +% t = n-element logical vector, where +% t(i) = 1, if IJC(i,:) arc in spanning tree +% t(i) = k, if IJC(i,:) arc in component k of forest +% nk = number of components + +% Copyright (c) 1998-2001 by Michael G. Kay +% Matlog Version 5 22-Aug-2001 + +% Input Error Checking ****************************************************** +[n,cIJC] = size(IJC); +if cIJC ~= 3 + error('''IJC'' must be a 3-column matrix.') +elseif n < 1 + error('''IJC'' must have at least one row.') +elseif any(IJC(:,1) < 1) | any(any(~isint(IJC(:,[1 2])))) + error('Invalid arc index in IJC.') +end +% End (Input Error Checking) ************************************************ + +i = IJC(:,1); j = abs(IJC(:,2)); +m = max(max([i j])); + +sidxIJ = argsort(IJC(:,3)); +i = i(sidxIJ); j = j(sidxIJ); + +t = logical(zeros(n,1)); +k = 1; % Current arc +nt = 0; % Number of arcs in spanning tree +v = (1:m)'; % Arc labels + +while nt < m - 1 & k <= n + if (v(i(k)) ~= v(j(k))) + v(v==v(j(k))) = v(i(k)); + t(k) = 1; + nt = nt + 1; + end + k = k + 1; +end + +idxIJ = invperm(sidxIJ); +t = t(idxIJ); i = i(idxIJ); j = j(idxIJ); + +c = unique(v(unique([i; j]))); % Unique labels of arc vertices +nk = length(c); +if ~any(t), nk = 0; end % Self-loop not a component + +if nk > 1 + for k = 1:nk + t(t~=0 & v(i)==c(k)) = k; % Relabel to consecutive component numbers + end +end + diff --git a/sourcecodes/bnt-master/graph/mk_2D_lattice.m b/sourcecodes/bnt-master/graph/mk_2D_lattice.m new file mode 100644 index 00000000..a9873260 --- /dev/null +++ b/sourcecodes/bnt-master/graph/mk_2D_lattice.m @@ -0,0 +1,59 @@ +function G = mk_2D_lattice(nrows, ncols, con) +% MK_2D_LATTICE Return adjacency matrix for nearest neighbor connected 2D lattice +% G = mk_2D_lattice(nrows, ncols, con) +% G(k1, k2) = 1 iff k1=(i1,j1) is a neighbor of k2=(i2,j2) +% (Two pixels are neighbors if their Euclidean distance is less than r.) +% Default connectivity = 4. +% +% WE ASSUME NO WRAP AROUND. +% +% This is the neighborhood as a function of con: +% +% con=4,r=1 con=8,r=sqrt(2) con=12,r=2 con=24,r=sqrt(8) +% nn 2nd order 4th order +% x x x x x x +% x x x x x x x x x x x x +% x o x x o x x x o x x x x o x x +% x x x x x x x x x x x x +% x x x x x x +% +% Examples: +% Consider a 3x4 grid +% 1 4 7 10 +% 2 5 8 11 +% 3 6 9 12 +% +% 4-connected: +% G=mk_2D_lattice(3,4,4); +% find(G(1,:)) = [2 4] +% find(G(5,:)) = [2 4 6 8] +% +% 8-connected: +% G=mk_2D_lattice(3,4,8); +% find(G(1,:)) = [2 4 5] +% find(G(5,:)) = [1 2 3 4 6 7 8 9] + +% meshgrid trick due to Temu Gautama (temu@neuro.kuleuven.ac.be) + +if nargin < 3, con = 4; end + +switch con, + case 4, r = 1; + case 8, r = sqrt(2); + case 12, r = 2; + case 24, r = sqrt(8); + otherwise, error(['unrecognized connectivity ' num2str(con)]) +end + + +npixels = nrows*ncols; + +[x y]=meshgrid(1:ncols, 1:nrows); +M = [x(:) y(:)]; +M1 = repmat(reshape(M',[1 2 npixels]),[npixels 1 1]); +M2 = repmat(M,[1 1 npixels]); +%D = squeeze(sum(abs(M1-M2),2)); % Manhattan distance +M3 = M1-M2; +D = sqrt(squeeze(M3(:,1,:)) .^2 + squeeze(M3(:,2,:)) .^2); % Euclidean distance +G = reshape(D <= r,npixels,npixels); +G = setdiag(G, 0); diff --git a/sourcecodes/bnt-master/graph/mk_2D_lattice_slow.m b/sourcecodes/bnt-master/graph/mk_2D_lattice_slow.m new file mode 100644 index 00000000..0c646d80 --- /dev/null +++ b/sourcecodes/bnt-master/graph/mk_2D_lattice_slow.m @@ -0,0 +1,135 @@ +function G = mk_2D_lattice_slow(nrows, ncols, wrap_around) +% MK_2D_LATTICE Return adjacency matrix for 4-nearest neighbor connected 2D lattice +% G = mk_2D_lattice(nrows, ncols, wrap_around) +% G(k1, k2) = 1 iff k1=(i1,j1) is connected to k2=(i2,j2) +% +% If wrap_around = 1, we use toroidal boundary conditions (default = 0) +% +% Nodes are assumed numbered as in the following 3x3 lattice +% 1 4 7 +% 2 5 8 +% 3 6 9 +% +% e.g., G = mk_2D_lattice(3, 3, 0) returns +% 0 1 0 1 0 0 0 0 0 +% 1 0 1 0 1 0 0 0 0 +% 0 1 0 0 0 1 0 0 0 +% 1 0 0 0 1 0 1 0 0 +% 0 1 0 1 0 1 0 1 0 +% 0 0 1 0 1 0 0 0 1 +% 0 0 0 1 0 0 0 1 0 +% 0 0 0 0 1 0 1 0 1 +% 0 0 0 0 0 1 0 1 0 +% so find(G(5,:)) = [2 4 6 8] +% but find(G(1,:)) = [2 4] +% +% Using wrap around, G = mk_2D_lattice(3, 3, 1), we get +% 0 1 1 1 0 0 1 0 0 +% 1 0 1 0 1 0 0 1 0 +% 1 1 0 0 0 1 0 0 1 +% 1 0 0 0 1 1 1 0 0 +% 0 1 0 1 0 1 0 1 0 +% 0 0 1 1 1 0 0 0 1 +% 1 0 0 1 0 0 0 1 1 +% 0 1 0 0 1 0 1 0 1 +% 0 0 1 0 0 1 1 1 0 +% so find(G(5,:)) = [2 4 6 8] +% and find(G(1,:)) = [2 3 4 7] + +if nargin < 3, wrap_around = 0; end + +% M contains the number of each cell e.g. +% 1 4 7 +% 2 5 8 +% 3 6 9 +% North neighbors (assuming wrap around) are +% 3 6 9 +% 1 4 7 +% 2 5 8 +% Without wrap around, they are +% 1 4 7 +% 1 4 7 +% 2 5 8 +% The first row is arbitrary, since pixels at the top have no north neighbor. + +if nrows==1 + G = zeros(1, ncols); + for i=1:ncols-1 + G(i,i+1) = 1; + G(i+1,i) = 1; + end + if wrap_around + G(1,ncols) = 1; + G(ncols,1) = 1; + end + return; +end + + +npixels = nrows*ncols; + +N = 1; E = 2; S = 3; W = 4; +if wrap_around + rows{N} = [nrows 1:nrows-1]; cols{N} = 1:ncols; + rows{E} = 1:nrows; cols{E} = [2:ncols 1]; + rows{S} = [2:nrows 1]; cols{S} = 1:ncols; + rows{W} = 1:nrows; cols{W} = [ncols 1:ncols-1]; +else + rows{N} = [1 1:nrows-1]; cols{N} = 1:ncols; + rows{E} = 1:nrows; cols{E} = [1 1:ncols-1]; + rows{S} = [2:nrows nrows]; cols{S} = 1:ncols; + rows{W} = 1:nrows; cols{W} = [2:ncols ncols]; +end + +M = reshape(1:npixels, [nrows ncols]); +nbrs = cell(1, 4); +for i=1:4 + nbrs{i} = M(rows{i}, cols{i}); +end + + +G = zeros(npixels, npixels); +if wrap_around + for i=1:4 + if 0 + % naive + for p=1:npixels + G(p, nbrs{i}(p)) = 1; + end + else + % vectorized + ndx2 = sub2ind([npixels npixels], 1:npixels, nbrs{i}(:)'); + G(ndx2) = 1; + end + end +else + i = N; + mask = ones(nrows, ncols); + mask(1,:) = 0; % pixels in row 1 have no nbr to the north + ndx = find(mask); + ndx2 = sub2ind([npixels npixels], ndx, nbrs{i}(ndx)); + G(ndx2) = 1; + + i = E; + mask = ones(nrows, ncols); + mask(:,ncols) = 0; + ndx = find(mask); + ndx2 = sub2ind([npixels npixels], ndx, nbrs{i}(ndx)); + G(ndx2) = 1; + + i = S; + mask = ones(nrows, ncols); + mask(nrows,:)=0; + ndx = find(mask); + ndx2 = sub2ind([npixels npixels], ndx, nbrs{i}(ndx)); + G(ndx2) = 1; + + i = W; + mask = ones(nrows, ncols); + mask(:,1)=0; + ndx = find(mask); + ndx2 = sub2ind([npixels npixels], ndx, nbrs{i}(ndx)); + G(ndx2) = 1; +end + +G = setdiag(G, 0); diff --git a/sourcecodes/bnt-master/graph/mk_adj_mat.m b/sourcecodes/bnt-master/graph/mk_adj_mat.m new file mode 100644 index 00000000..09f73d4a --- /dev/null +++ b/sourcecodes/bnt-master/graph/mk_adj_mat.m @@ -0,0 +1,36 @@ +function [A, names] = mk_adj_mat(connections, names, topological) +% MK_ADJ_MAT Make a directed adjacency matrix from a list of connections between named nodes. +% +% A = mk_adj_mat(connections, name) +% This is best explaine by an example: +% names = {'WetGrass', 'Sprinkler', 'Cloudy', 'Rain'}; +% connections = {'Cloudy', 'Sprinkler'; 'Cloudy', 'Rain'; 'Sprinkler', 'WetGrass'; 'Rain', 'WetGrass'}; +% adds the arcs C -> S, C -> R, S -> W, R -> W. Node 1 is W, 2 is S, 3 is C, 4 is R. +% +% [A, names] = mk_adj_mat(connections, name, 1) +% The last argument of 1 indicates that we should topologically sort the nodes (parents before children). +% In the example, the numbering becomes: node 1 is C, 2 is R, 3 is S, 4 is W +% and the return value of names gets permuted to {'Cloudy', 'Rain', 'Sprinkler', 'WetGrass'}. +% Note that topological sorting the graph is only possible if it has no directed cycles. + +if nargin < 3, topological = 0; end + +n=length(names); +A=zeros(n); +[nr nc] = size(connections); +for r=1:nr + from = strmatch(connections{r,1}, names, 'exact'); + assert(~isempty(from)); + to = strmatch(connections{r,2}, names, 'exact'); + assert(~isempty(to)); + %fprintf(1, 'from %s %d to %s %d\n', connections{r,1}, from, connections{r,2}, to); + A(from,to) = 1; +end + +if topological + order = topological_sort(A); + A = A(order, order); + names = names(order); +end + + diff --git a/sourcecodes/bnt-master/graph/mk_adjmat_chain.m b/sourcecodes/bnt-master/graph/mk_adjmat_chain.m new file mode 100644 index 00000000..d206b272 --- /dev/null +++ b/sourcecodes/bnt-master/graph/mk_adjmat_chain.m @@ -0,0 +1,8 @@ +function G = mk_adjmat_chain(T) +% MK_DAG_CHAIN Make adjacency matrix for bi-directional Markov chain of T nodes +% function G = mk_dag_chain(T) +% +% G(t,t+1) = 1 for all t<T + +G = diag(ones(1,T-1),1) + diag(ones(1,T-1),-1); + diff --git a/sourcecodes/bnt-master/graph/mk_all_dags.m b/sourcecodes/bnt-master/graph/mk_all_dags.m new file mode 100644 index 00000000..7335fd0b --- /dev/null +++ b/sourcecodes/bnt-master/graph/mk_all_dags.m @@ -0,0 +1,52 @@ +function Gs = mk_all_dags(N, order) +% MK_ALL_DAGS generate all DAGs on N variables +% G = mk_all_dags(N) +% +% G = mk_all_dags(N, order) only generates DAGs in which node i has parents from +% nodes in order(1:i-1). Default: order=[] (no constraints). +% +% G{i} is the i'th dag +% +% Note: the number of DAGs is super-exponential in N, so don't call this with N > 4. + +if nargin < 2, order = []; end + +use_file = 0; + +global BNT_HOME +fname = sprintf('%s/DAGS%d.mat', BNT_HOME, N); +if use_file & exist(fname, 'file') + S = load(fname, '-mat'); + fprintf('loading %s\n', fname); + Gs = S.Gs; + return; +end + +m = 2^(N*N); +ind = ind2subv(2*ones(1,N^2), 1:m); +Gs = {}; +j = 1; +directed = 1; +for i=1:m + dag = reshape(ind(i,:)-1, N, N); + if acyclic(dag, directed) + out_of_order = 0; + if ~isempty(order) + for k=1:N-1 + if any(dag(order(k+1:end), k)) + out_of_order = 1; + break; + end + end + end + if ~out_of_order + Gs{j} = dag; + j = j + 1; + end + end +end + +if use_file + disp(['mk_all_dags: saving to ' fname '!']); + save(fname, 'Gs'); +end diff --git a/sourcecodes/bnt-master/graph/mk_nbrs_of_dag.m b/sourcecodes/bnt-master/graph/mk_nbrs_of_dag.m new file mode 100644 index 00000000..04742774 --- /dev/null +++ b/sourcecodes/bnt-master/graph/mk_nbrs_of_dag.m @@ -0,0 +1,64 @@ +function [Gs, op, nodes] = mk_nbrs_of_dag(G0) +% MK_NBRS_OF_DAG Make all DAGs that differ from G0 by a single edge deletion, addition or reversal +% [Gs, op, nodes] = mk_nbrs_of_dag(G0) +% +% Gs{i} is the i'th neighbor. +% op{i} = 'add', 'del', or 'rev' is the operation used to create the i'th neighbor. +% nodes(i,1:2) are the head and tail of the operated-on arc. + +Gs = {}; +op = {}; +nodes = []; + +[I,J] = find(G0); +nnbrs = 1; +% all single edge deletions +for e=1:length(I) + i = I(e); j = J(e); + G = G0; + G(i,j) = 0; + Gs{nnbrs} = G; + op{nnbrs} = 'del'; + nodes(nnbrs, :) = [i j]; + nnbrs = nnbrs + 1; +end + +% all single edge reversals +for e=1:length(I) + i = I(e); j = J(e); + G = G0; + G(i,j) = 0; + G(j,i) = 1; + if acyclic(G) + Gs{nnbrs} = G; + op{nnbrs} = 'rev'; + nodes(nnbrs, :) = [i j]; + nnbrs = nnbrs + 1; + end +end + +[I,J] = find(~G0); +% all single edge additions +for e=1:length(I) + i = I(e); j = J(e); + if i ~= j % don't add self arcs + G = G0; + G(i,j) = 1; + if G(j,i)==0 % don't add i->j if j->i exists already + if acyclic(G) + Gs{nnbrs} = G; + op{nnbrs} = 'add'; + nodes(nnbrs, :) = [i j]; + nnbrs = nnbrs + 1; + end + end + end +end + + + + + + + + diff --git a/sourcecodes/bnt-master/graph/mk_nbrs_of_digraph.m b/sourcecodes/bnt-master/graph/mk_nbrs_of_digraph.m new file mode 100644 index 00000000..f71c2b54 --- /dev/null +++ b/sourcecodes/bnt-master/graph/mk_nbrs_of_digraph.m @@ -0,0 +1,132 @@ +function [Gs, op, nodes, A] = my_mk_nbs_of_digraph(G0,A) +% MY_MK_NBRS_OF_DIGRAPH Make all digraphs that differ from G0 by a single edge deletion, addition or reversal, subject to acyclicity +% [Gs, op, nodes, A] = my_mk_nbrs_of_digraph(G0,<A>) +% +% G0 is an adj matrix s.t. G0(i,j)=1 iff i->j in graph +% A is the ancestor matrix for G0 (opt, creates if necessary) +% +% Gs(:,:,i) is the i'th neighbor +% op{i} = 'add', 'del', or 'rev' is the operation used to create the i'th neighbor. +% nodes(i,1:2) are the head and tail of the operated-on arc. +% Modified from mk_nbrs_of_digraph by Sonia Leach +% +% Modified by Sonia Leach Feb 02 + +if nargin ==1, A = reachability_graph(G0');, end + +n = length(G0); +[I,J] = find(G0); % I(k), J(k) is the k'th edge +E = length(I); % num edges present in G0 + + +% SINGLE EDGE DELETIONS +% all deletions are valid wrt acyclity + +Grep = repmat(G0(:), 1, E); % each column is a copy of G0 +% edge_ndx(k) is the scalar location of the k'th edge +edge_ndx = find(G0); + +% edge_ndx = subv2ind([n n], [I J]); % equivalent +% We set (ndx(k), k) to 0 for k=1:E in Grep +ndx = subv2ind(size(Grep), [edge_ndx(:) (1:E)']); +G1 = Grep; +G1(ndx) = 0; +Gdel = reshape(G1, [n n E]); + + +% SINGLE EDGE REVERSALS + +% SML: previously Kevin had that legal structure was if +% A(P,i)=1 for any P = { p | p in parents(j), p~=i} +% specifically he said +% "if any(A(ps,i)) then there is a path i -> parent of j -> j +% so reversing i->j would create a cycle" +% Thus put in another way: +% for each i,j if sum(G0(:,j)' * A(:,i)) > 0, reversing i->j +% is not legal. +% +% Ex. Suppose we want to check if 2->4 can be reversed in the +% following graph: +% G0 = A = +% 0 0 1 0 0 0 0 0 +% 0 0 1 1 0 0 0 0 +% 0 0 0 1 1 1 0 0 +% 0 0 0 0 1 1 1 0 +% +% Then parents(4) = G0(:,4) = [0 1 1 0]' +% and A(:,2) = [0 0 1 1]. Thus G0(:,4)'*A(:,2) = 1 b/c 3 is +% an ancestor of 4 and a child of 2. Note that this works b/c +% matrix multiplication has the effect of ANDing the two vectors +% and summing up the result (equiv. to the any(A(ps,i)) in kevin's code) +% +% So, we vectorize and check for all i,j pairs by looking for +% 1's in L = (G0'*A)' which has L(i,j)=1 if rev(i,j) not legal +% Note that this will give 1's where there are none in the G0 +% so we do a L=max(0, G0-L) to cancel out only the existing edges that +% aren't legal (subtracting where both are 1 and setting where +% G0=0 and A=1 back to 0). + +L = max(0, G0-(G0'*A)'); +[IL, JL] = find(L); % I(k), J(k) is the k'th legal edge to rev. +EL = length(IL); + + +% SML: First we have to DELETE THE EDGES WE ARE REVERSING +% We can't use G1 w/ reversed edges already deleted (as +% Kevin did) b/c the space of possible deletions are different +% now (some reverses aren't legal) + +Grep = repmat(G0(:), 1, EL); % each column is a copy of G0 +% edge_ndx(k) is the scalar location of the k'th edge +edge_ndx = subv2ind([n n], [IL JL]); +% We set (ndx(k), k) to 0 for k=1:E in Grep +ndx = subv2ind(size(Grep), [edge_ndx(:) (1:EL)']); +G1 = Grep; +G1(ndx) = 0; + +% SML: Now we add in our REVERSED EDGES +% rev_edge_ndx(k) is the scalar location of the k'th legal reversed edge +rev_edge_ndx = subv2ind([n n], [JL IL]); + +% We set (rev_edge_ndx(k), k) to 1 for k=1:EL in G1 +% We have already deleted i->j in the previous step +ndx = subv2ind(size(Grep), [rev_edge_ndx(:) (1:EL)']); +G1(ndx) = 1; +Grev = reshape(G1, [n n EL]); + +% SINGLE EDGE ADDITIONS + +% SML: previously Kevin had that any addition was legal if A(i,j)=0 +% however, you can not add i->j if j is a descendent of i. +% Thus, we create all possible additions in Gbar and then +% subtract the descendants of each edge as possible parents +% This means the potential parents of i (i.e. Gbar(:,i)) +% can not also be descendants if i i.e. (A(:,i)) which is accomplished +% by subtracting (Gbar-A == 1 iff Gbar=1 & A=0) + +Gbar = ~G0; % Gbar(i,j)=1 iff there is no i->j edge in G0 +Gbar = setdiag(Gbar, 0); % turn off self loops + +GbarL = Gbar-A; +[IbarL, JbarL] = find(GbarL); % I(k), J(k) is the k'th legal edge to add +EbarL = length(IbarL); + +bar_edge_ndx = find(GbarL); + +Grep = repmat(G0(:), 1, EbarL); % each column is a copy of G0 +ndx = subv2ind(size(Grep), [bar_edge_ndx(:) (1:EbarL)']); +Grep(ndx) = 1; +Gadd = reshape(Grep, [n n EbarL]); + + +Gs = cat(3, Gdel, Grev, Gadd); + +nodes = [I J; + IL JL; + IbarL JbarL]; + +op = cell(1, E+EL+EbarL); +op(1:E) = {'del'}; +op(E+(1:EL)) = {'rev'}; +op((E+EL+1):end) = {'add'}; + diff --git a/sourcecodes/bnt-master/graph/mk_nbrs_of_digraph_broken.m b/sourcecodes/bnt-master/graph/mk_nbrs_of_digraph_broken.m new file mode 100644 index 00000000..8d43afcb --- /dev/null +++ b/sourcecodes/bnt-master/graph/mk_nbrs_of_digraph_broken.m @@ -0,0 +1,111 @@ +function [Gs, op, nodes] = mk_nbrs_of_digraph(G0) +% MK_NBRS_OF_DIGRAPH Make all digraphs that differ from G0 by a single edge deletion, addition or reversal +% [Gs, op, nodes] = mk_nbrs_of_digraph(G0) +% +% Gs(:,:,i) is the i'th neighbor +% op{i} = 'add', 'del', or 'rev' is the operation used to create the i'th neighbor. +% nodes(i,1:2) are the head and tail of the operated-on arc. + +debug = 0; % the vectorized version is about 3 to 10 times faster + +n = length(G0); +[I,J] = find(G0); % I(k), J(k) is the k'th edge +E = length(I); % num edges present in G0 + +% SINGLE EDGE DELETIONS + +Grep = repmat(G0(:), 1, E); % each column is a copy of G0 +% edge_ndx(k) is the scalar location of the k'th edge +edge_ndx = find(G0); +% edge_ndx = subv2ind([n n], [I J]); % equivalent +% We set (ndx(k), k) to 0 for k=1:E in Grep +ndx = subv2ind(size(Grep), [edge_ndx(:) (1:E)']); +G1 = Grep; +G1(ndx) = 0; +Gdel = reshape(G1, [n n E]); + + +% if debug +% % Non-vectorized version +% ctr = 1; +% for e=1:E +% i = I(e); j = J(e); +% Gdel2(:,:,ctr) = G0; +% Gdel2(i,j,ctr) = 0; +% ctr = ctr + 1; +% end +% assert(isequal(Gdel, Gdel2)); +% end + + +% SINGLE EDGE REVERSALS + +% rev_edge_ndx(k) is the scalar location of the k'th reversed edge +%rev_edge_ndx = find(G0'); % different order to edge_ndx, which is bad +rev_edge_ndx = subv2ind([n n], [J I]); +% We set (rev_edge_ndx(k), k) to 1 for k=1:E in G1 +% We have already deleted i->j in the previous step +ndx = subv2ind(size(Grep), [rev_edge_ndx(:) (1:E)']); +G1(ndx) = 1; +Grev = reshape(G1, [n n E]); + +% if debug +% % Non-vectorized version +% ctr = 1; +% for e=1:E +% i = I(e); j = J(e); +% Grev2(:,:,ctr) = G0; +% Grev2(i,j,ctr) = 0; +% Grev2(j,i,ctr) = 1; +% ctr = ctr + 1; +% end +% assert(isequal(Grev, Grev2)); +% end + + +% SINGLE EDGE ADDITIONS + +Gbar = ~G0; % Gbar(i,j)=1 iff there is no i->j edge in G0 +Gbar = setdiag(Gbar, 0); % turn off self loops +[Ibar,Jbar] = find(Gbar); + +bar_edge_ndx = find(Gbar); +Ebar = length(Ibar); % num edges present in Gbar +Grep = repmat(G0(:), 1, Ebar); % each column is a copy of G0 +ndx = subv2ind(size(Grep), [bar_edge_ndx(:) (1:Ebar)']); +Grep(ndx) = 1; +Gadd = reshape(Grep, [n n Ebar]); + +% if debug +% % Non-vectorized version +% ctr = 1; +% for e=1:length(Ibar) +% i = Ibar(e); j = Jbar(e); +% Gadd2(:,:,ctr) = G0; +% Gadd2(i,j,ctr) = 1; +% ctr = ctr + 1; +% end +% assert(isequal(Gadd, Gadd2)); +% end + + +Gs = cat(3, Gdel, Grev, Gadd); + +nodes = [I J; + I J; + Ibar Jbar]; + +op = cell(1, E+E+Ebar); +op(1:E) = {'del'}; +op(E+1:2*E) = {'rev'}; +op(2*E+1:end) = {'add'}; + + +% numeric output: +% op(i) = 1, 2, or 3, if the i'th neighbor was created by adding, deleting or reversing an arc. + +ADD = 1; +DEL = 2; +REV = 3; + +%op = [repmat(DEL, 1, E) repmat(REV, 1, E) repmat(ADD, 1, Ebar)]; diff --git a/sourcecodes/bnt-master/graph/mk_nbrs_of_digraph_not_vectorized.m b/sourcecodes/bnt-master/graph/mk_nbrs_of_digraph_not_vectorized.m new file mode 100644 index 00000000..d622943d --- /dev/null +++ b/sourcecodes/bnt-master/graph/mk_nbrs_of_digraph_not_vectorized.m @@ -0,0 +1,53 @@ +function [Gs, op, nodes] = mk_nbrs_of_digraph2(G0) +% MK_NBRS_OF_DIGRAPH Make all digraphs that differ from G0 by a single edge deletion, addition or reversal +% [Gs, op, nodes] = mk_nbrs_of_digraph(G0) +% op{i} = 'add', 'del', or 'rev' is the operation used to create the i'th neighbor. +% nodes(i,1:2) are the head and tail of the operated-on arc. + +[I,J] = find(G0); +G0bar = setdiag(~G0, 0); % exclude self loops in graph complement +[Ibar,Jbar] = find(G0bar); +nnbrs = 2*length(I) + length(Ibar); +Gs = cell(1, nnbrs); +op = cell(1, nnbrs); +nodes = zeros(nnbrs, 2); + +nbr = 1; +% all single edge deletions +for e=1:length(I) + i = I(e); j = J(e); + G = G0; + G(i,j) = 0; + Gs{nbr} = G; + op{nbr} = 'del'; + nodes(nbr, :) = [i j]; + nbr = nbr + 1; +end + +% all single edge reversals +for e=1:length(I) + i = I(e); j = J(e); + G = G0; + G(i,j) = 0; + G(j,i) = 1; + Gs{nbr} = G; + op{nbr} = 'rev'; + nodes(nbr, :) = [i j]; + nbr = nbr + 1; +end + +[I,J] = find(~G0); +% all single edge additions +for e=1:length(I) + i = I(e); j = J(e); + G = G0; + if i ~= j % don't add self loops + G(i,j) = 1; + Gs{nbr} = G; + op{nbr} = 'add'; + nodes(nbr, :) = [i j]; + nbr = nbr + 1; + end +end + +assert(nnbrs == nbr-1); diff --git a/sourcecodes/bnt-master/graph/mk_rnd_dag.m b/sourcecodes/bnt-master/graph/mk_rnd_dag.m new file mode 100644 index 00000000..4bf59c34 --- /dev/null +++ b/sourcecodes/bnt-master/graph/mk_rnd_dag.m @@ -0,0 +1,27 @@ +function [dag, order] = mk_rnd_dag(N, max_fan_in) +% MY_MK_RND_DAG Create a random directed acyclic graph +% +% [dag, order] = my_mk_rnd_dag(N, max_fan_in) +% max_fan_in defaults to N. +% order is the random topological order that was chosen + +% Modified by Sonia Leach 2/25/02 + +if nargin < 2, max_fan_in = N; end + +order = randperm(N); +dag = zeros(N,N); +for i=2:N + j = order(i); + %k = sample_discrete(normalise(ones(1, min(i-1, max_fan_in)))); + k = sample_discrete(normalise(ones(1, min(i-1, max_fan_in)+1))) - 1; % min = 0 (bug fix due to + % Pedrito, 7/28/04) + SS = order(1:i-1); % get Set of possible parentS + p = randperm(length(SS)); % permute order of set + dag(SS(p(1:k)),j) = 1; % take first k in permuted order + + % Kevin had: + %SS = subsets(order(1:i-1), k, k); + %p = sample_discrete(normalise(ones(1, length(SS)))); + %dag(SS{p}, j) = 1; +end diff --git a/sourcecodes/bnt-master/graph/mk_rnd_dag_given_edge_prob.m b/sourcecodes/bnt-master/graph/mk_rnd_dag_given_edge_prob.m new file mode 100644 index 00000000..205e5b51 --- /dev/null +++ b/sourcecodes/bnt-master/graph/mk_rnd_dag_given_edge_prob.m @@ -0,0 +1,15 @@ +function dag = sample_dag(P) +% SAMPLE_DAG Create a random directed acyclic graph with edge probabilities P(i,j) +% dag = sample_dag(P) +% +% This uses rejection sampling to reject graphs with directed cycles. + +done = 0; +directed = 1; +iter = 1; +while ~done + dag = binornd(1, P); % each edge is an indep Bernoulli (0/1) random variable + dag = setdiag(dag, 0); + done = acyclic(dag, directed); + iter = iter + 1 +end diff --git a/sourcecodes/bnt-master/graph/mk_rooted_tree.m b/sourcecodes/bnt-master/graph/mk_rooted_tree.m new file mode 100644 index 00000000..396043eb --- /dev/null +++ b/sourcecodes/bnt-master/graph/mk_rooted_tree.m @@ -0,0 +1,19 @@ +function [T, pre, post, cycle] = mk_rooted_tree(G, root) +% MK_ROOTED_TREE Make a directed tree pointing away from root +% [T, pre, post, cycle] = mk_rooted_tree(G, root) + +n = length(G); +T = sparse(n,n); % not the same as T = sparse(n) ! +directed = 0; +[d, pre, post, cycle, f, pred] = dfs(G, root, directed); +[junk, pre2] = sort(d); +assert(isequal(pre, pre2)) +[junk, post2] = sort(f); +assert(isequal(post, post2)); +%[d, pre, post, cycle, f, pred] = dfs(G, [], directed); +for i=1:length(pred) + if pred(i)>0 + T(pred(i),i)=1; + end +end + diff --git a/sourcecodes/bnt-master/graph/mk_undirected.m b/sourcecodes/bnt-master/graph/mk_undirected.m new file mode 100644 index 00000000..0a1614aa --- /dev/null +++ b/sourcecodes/bnt-master/graph/mk_undirected.m @@ -0,0 +1,11 @@ +function U = mk_undirected(G) + +[nr nc] = size(G); +U = G; +for i=1:nr + for j=1:nc + if U(i,j)==1 + U(j,i) = 1; + end + end +end diff --git a/sourcecodes/bnt-master/graph/moralize.m b/sourcecodes/bnt-master/graph/moralize.m new file mode 100644 index 00000000..59beff97 --- /dev/null +++ b/sourcecodes/bnt-master/graph/moralize.m @@ -0,0 +1,12 @@ +function [M, moral_edges] = moralize(G) +% MORALIZE Ensure that for every child, all its parents are married, and drop directionality of edges. +% [M, moral_edges] = moralize(G) + +M = G; +n = length(M); +for i=1:n + fam = family(G,i); + M(fam,fam)=1; +end +M = setdiag(M,0); +moral_edges = sparse(triu(max(0,M-G),1)); diff --git a/sourcecodes/bnt-master/graph/neighbors.m b/sourcecodes/bnt-master/graph/neighbors.m new file mode 100644 index 00000000..7275c4e0 --- /dev/null +++ b/sourcecodes/bnt-master/graph/neighbors.m @@ -0,0 +1,7 @@ +function ns = neighbors(adj_mat, i) +% NEIGHBORS Find the parents and children of a node in a graph. +% ns = neighbors(adj_mat, i) + +%ns = myunion(children(adj_mat, i), parents(adj_mat, i)); +ns = unique([find(adj_mat(i,:)) find(adj_mat(:,i))']); + diff --git a/sourcecodes/bnt-master/graph/parents.m b/sourcecodes/bnt-master/graph/parents.m new file mode 100644 index 00000000..24354a74 --- /dev/null +++ b/sourcecodes/bnt-master/graph/parents.m @@ -0,0 +1,5 @@ +function ps = parents(adj_mat, i) +% PARENTS Return the list of parents of node i +% ps = parents(adj_mat, i) + +ps = find(adj_mat(:,i))'; diff --git a/sourcecodes/bnt-master/graph/pred2path.m b/sourcecodes/bnt-master/graph/pred2path.m new file mode 100644 index 00000000..4763d12c --- /dev/null +++ b/sourcecodes/bnt-master/graph/pred2path.m @@ -0,0 +1,78 @@ +function rte = pred2path(P,s,t) +%PRED2PATH Convert predecessor indices to shortest paths from node 's' to 't'. +% rte = pred2path(P,s,t) +% P = |s| x n matrix of predecessor indices (from DIJK) +% s = FROM node indices +% = [] (default), paths from all nodes +% t = TO node indices +% = [] (default), paths to all nodes +% rte = |s| x |t| cell array of paths (or routes) from 's' to 't', where +% rte{i,j} = path from s(i) to t(j) +% = [], if no path exists from s(i) to t(j) +% +% (Used with output of DIJK) + +% Copyright (c) 1998-2001 by Michael G. Kay +% Matlog Version 5 22-Aug-2001 + +% Input Error Checking ****************************************************** +error(nargchk(1,3,nargin)); + +[rP,n] = size(P); + +if nargin < 2 | isempty(s), s = (1:n)'; else s = s(:); end +if nargin < 3 | isempty(t), t = (1:n)'; else t = t(:); end + +if any(P < 0 | P > n) + error(['Elements of P must be integers between 1 and ',num2str(n)]); +elseif any(s < 1 | s > n) + error(['''s'' must be an integer between 1 and ',num2str(n)]); +elseif any(t < 1 | t > n) + error(['''t'' must be an integer between 1 and ',num2str(n)]); +end +% End (Input Error Checking) ************************************************ + +rte = cell(length(s),length(t)); + +for i = 1:length(s) + if rP == 1 + si = 1; + else + si = s(i); + if si < 1 | si > rP + error('Invalid P matrix.') + end + end + for j = 1:length(t) + tj = t(j); + if tj == s(i) + r = tj; + elseif P(si,tj) == 0 + r = []; + else + r = tj; + while tj ~= s(i) + if tj < 1 | tj > n + error('Invalid element of P matrix found.') + end + r = [P(si,tj) r]; + tj = P(si,tj); + end + end + rte{i,j} = r; + end +end + +if length(s) == 1 & length(t) == 1 + rte = rte{:}; +end + +%rte = t; +while 0%t ~= s + if t < 1 | t > n | round(t) ~= t + error('Invalid ''pred'' element found prior to reaching ''s'''); + end + rte = [P(t) rte]; + t = P(t); +end + diff --git a/sourcecodes/bnt-master/graph/reachability_graph.m b/sourcecodes/bnt-master/graph/reachability_graph.m new file mode 100644 index 00000000..0f3c4f58 --- /dev/null +++ b/sourcecodes/bnt-master/graph/reachability_graph.m @@ -0,0 +1,19 @@ +function C = reachability_graph(G) +% REACHABILITY_GRAPH C(i,j) = 1 iff there is a path from i to j in DAG G +% C = reachability_graph(G) + +if 1 + % expm(G) = I + G + G^2 / 2! + G^3 / 3! + ... + M = expm(double(full(G))) - eye(length(G)); + C = (M>0); +else + % This computes C = G + G^2 + ... + G^{n-1} + n = length(G); + A = G; + C = zeros(n); + for i=1:n-1 + C = C + A; + A = A * G; + end + C = (C > 0); +end diff --git a/sourcecodes/bnt-master/graph/scc.m b/sourcecodes/bnt-master/graph/scc.m new file mode 100644 index 00000000..9a2e8b9d --- /dev/null +++ b/sourcecodes/bnt-master/graph/scc.m @@ -0,0 +1,64 @@ +function [c,v] = scc(a,tol) + +% Finds the strongly connected sets of vertices +% in the DI-rected G-raph of A +% c = 0-1 matrix displaying accessibility +% v = displays the equivalent classes +% +% v(i,j) is the j'th member of the i'th equiv class (0 padded) +% +% http://www.math.wsu.edu/math/faculty/tsat/matlab.html + +[m,n] = size(a); +if m~=n 'Not a Square Matrix', break, end +b=abs(a); o=ones(size(a)); x=zeros(1,n); +msg='The Matrix is Irreducible !'; v='Connected Directed Graph !'; +if (nargin==1) tol=n*eps*norm(a,'inf'); end + +% Create a companion matrix +c = b>tol*o; +if (c==o) + % msg, break + v = 1:length(a); + return +end + + +% Compute accessibility in at most n-step paths +for k=1:n + for j=1:n + for i=1:n + % If index i accesses j, where can you go ? + if c(i,j) > 0 c(i,:) = c(i,:)+c(j,:); end + end + end +end +% Create a 0-1 matrix with the above information +c>zeros(size(a)); c=ans; if (c==o) msg, break, end + +% Identify equivalence classes +d=c.*c'+eye(size(a)); d>zeros(size(a)); d=ans; +v=zeros(size(a)); +for i=1:n find(d(i,:)); ans(n)=0; v(i,:)=ans; end + +% Eliminate displaying of identical rows +i=1; +while(i<n) + for k=i+1:n + if v(k,1) == v(i,1) + v(k,:)=x; + end + end + i=i+1; +end +j=1; +for i=1:n + if v(i,1)>0 + h(j,:)=v(i,:); + j=j+1; + end +end +v=h; + + + diff --git a/sourcecodes/bnt-master/graph/strong_elim_order.m b/sourcecodes/bnt-master/graph/strong_elim_order.m new file mode 100644 index 00000000..3fd995d7 --- /dev/null +++ b/sourcecodes/bnt-master/graph/strong_elim_order.m @@ -0,0 +1,75 @@ +function order = strong_elim_order(G, node_sizes, partial_order) +% STRONG_ELIM_ORDER Find an elimination order to produce a strongly triangulated graph. +% order = strong_elim_order(moral_graph, node_sizes, partial_order) +% +% partial_order(i,j)=1 if we must marginalize i *after* j +% (so i will be nearer the strong root). +% e.g., if j is a decision node and i is its information set: +% we cannot maximize j if we have marginalized out some of i +% e.g., if j is a continuous child and i is its discrete parent: +% we want to integrate out the cts nodes before the discrete ones, +% so that the marginal is strong. +% +% For details, see +% - Jensen, Jensen and Dittmer, "From influence diagrams to junction trees", UAI 94. +% - Lauritzen, "Propgation of probabilities, means, and variances in mixed graphical +% association models", JASA 87(420):1098--1108, 1992. +% +% On p369 of the Jensen paper, they state "the reverse of the elimination order must be some +% extension of [the partial order] to a total order". +% We make no attempt to find the best such total ordering, in the sense of minimizing the weight +% of the resulting cliques. + +% Example from the Jensen paper: +% Let us number the nodes in Fig 1 from top to bottom, left to right, +% so a=1,b=2,D1=3,c=4,...,l=14,j=15,k=16. +% The elimination ordering they propose on p370 is [14 15 16 11 12 1 4 5 10 8 13 9 7 6 3 2]; + +if 0 + total_order = topological_sort(partial_order); + order = total_order(end:-1:1); % no attempt to find an optimal constrained ordering! + return; +end + +% The following implementation is due to Ilya Shpitser and seems to give wrong +% results on cg1 + +n = length(G); +MG = G; % copy the original graph +uneliminated = ones(1,n); +order = zeros(1,n); + +for i=1:n + roots = []; + k = 1; + for j=1:n + if sum(partial_order(j,:)) == 0 + roots(k) = j; + k = k + 1; + end + end + U = find(uneliminated); + valid = myintersect(U, roots); + % Choose the best node from the set of valid candidates + score1 = zeros(1,length(valid)); + score2 = zeros(1,length(valid)); + for j=1:length(valid) + k = valid(j); + ns = myintersect(neighbors(G, k), U); + l = length(ns); + M = MG(ns,ns); + score1(j) = l^2 - sum(M(:)); % num. added edges + score2(j) = prod(node_sizes([k ns])); % weight of clique + end + j1s = find(score1==min(score1)); + j = j1s(argmin(score2(j1s))); + k = valid(j); + uneliminated(k) = 0; + order(i) = k; + ns = myintersect(neighbors(G, k), U); + if ~isempty(ns) + G(ns,ns) = 1; + G = setdiag(G,0); + end + partial_order(:,k) = 0; +end diff --git a/sourcecodes/bnt-master/graph/test.m b/sourcecodes/bnt-master/graph/test.m new file mode 100644 index 00000000..6ffe3d2e --- /dev/null +++ b/sourcecodes/bnt-master/graph/test.m @@ -0,0 +1,16 @@ +% A - B +% | +% D - C - E + +A=1;B=2;C=3;D=4;E=5; +dag = zeros(5,5); +dag(A,B)=1; +%dag(A,D)=1; +dag(B,C)=1; +dag(C,D)=1; +dag(E,C)=1; +[d, pre, post, cycle, f, pred] = dfs(dag, A, 0) + +[T, pre, post, cycle] = mk_rooted_tree(dag, A) + +%[T, pre, post, cycle] = mkRootedTree(dag, A) diff --git a/sourcecodes/bnt-master/graph/test_strong_root.m b/sourcecodes/bnt-master/graph/test_strong_root.m new file mode 100644 index 00000000..86e1117e --- /dev/null +++ b/sourcecodes/bnt-master/graph/test_strong_root.m @@ -0,0 +1,34 @@ +function strong = test_strong_root(jtree,cliques,dnodes,root) +% This function tests, whether root is a strong root of jtree. +% The following parameters are used +% Input: +% jtree An matrix with two colums. jtree(i,j) == jtree(j,i) is 1 if node +% i is connected with node j +% cliques Cells which contain the nodes in each clique +% dnodes An array with the discrete nodes of the juntion tree. +% root It is tested whether root is the strong root of the junction tree +% Output: +% strong The output is 1 if root is the strong root of the junction tree jtree. +% Please note, that the running intersection property is not tested. +if isempty(dnodes) + strong = 1; + return; +end + +children = find(jtree(root,:)==1); +i = 1; +strong = 1; +while (i <= length(children)) & (strong==1) + child = children(i); + jtree(child,root) = 0; + jtree(root,child) = 0; + sep = myintersect(cliques{child},cliques{root}); + diff = mysetdiff(cliques{child},cliques{root}); + if (mysubset(sep,dnodes) | isempty(myintersect(diff,dnodes))) + strong = test_strong_root(jtree,cliques,dnodes,child); + else + strong = 0; + end; + i = i+1; +end + diff --git a/sourcecodes/bnt-master/graph/topological_sort.m b/sourcecodes/bnt-master/graph/topological_sort.m new file mode 100644 index 00000000..cd8b8325 --- /dev/null +++ b/sourcecodes/bnt-master/graph/topological_sort.m @@ -0,0 +1,30 @@ +function order = topological_sort(A) +% TOPOLOGICAL_SORT Return the nodes in topological order (parents before children). +% order = topological_sort(adj_mat) + +n = length(A); +indeg = zeros(1,n); +zero_indeg = []; % a stack of nodes with no parents +for i=1:n + indeg(i) = length(parents(A,i)); + if indeg(i)==0 + zero_indeg = [i zero_indeg]; + end +end + +t=1; +order = zeros(1,n); +while ~isempty(zero_indeg) + v = zero_indeg(1); % pop v + zero_indeg = zero_indeg(2:end); + order(t) = v; + t = t + 1; + cs = children(A, v); + for j=1:length(cs) + c = cs(j); + indeg(c) = indeg(c) - 1; + if indeg(c) == 0 + zero_indeg = [c zero_indeg]; % push c + end + end +end diff --git a/sourcecodes/bnt-master/graph/trees.txt b/sourcecodes/bnt-master/graph/trees.txt new file mode 100644 index 00000000..f5475aac --- /dev/null +++ b/sourcecodes/bnt-master/graph/trees.txt @@ -0,0 +1,168 @@ + +% make undirected adjacency matrix of graph/tree +% e.g., +% 1 +% / \ +% 2 3 +T = zeros(3,3); +T(1,2) = 1; T(2,1)=1; +T(1,3)=1; T(3,1) = 1; + +root = 1; +[T, preorder, postorder] = mk_rooted_tree(T, root); + +% bottom up message passing leaves to root +for n=postorder(:)' + for p = parents(T, n) + % p is parent of n + end +end + +% top down, root to leaves +for n=preorder(:)' + for c= children(T,n) + % c is child of n + end +end + + +%%%%%%%%%%%%% + +function ps = parents(adj_mat, i) +% PARENTS Return the list of parents of node i +% ps = parents(adj_mat, i) + +ps = find(adj_mat(:,i))'; + + +%%%%%%%%%%%% + +function cs = children(adj_mat, i, t) +% CHILDREN Return the indices of a node's children in sorted order +% c = children(adj_mat, i, t) +% +% t is an optional argument: if present, dag is assumed to be a 2-slice DBN + +if nargin < 3 + cs = find(adj_mat(i,:)); +else + if t==1 + cs = find(adj_mat(i,:)); + else + ss = length(adj_mat)/2; + j = i+ss; + cs = find(adj_mat(j,:)) + (t-2)*ss; + end +end + +%%%%%%%%%%% + +function [T, pre, post, cycle] = mk_rooted_tree(G, root) +% MK_ROOTED_TREE Make a directed, rooted tree out of an undirected tree. +% [T, pre, post, cycle] = mk_rooted_tree(G, root) + +n = length(G); +T = sparse(n,n); % not the same as T = sparse(n) ! +directed = 0; +[d, pre, post, cycle, f, pred] = dfs(G, root, directed); +for i=1:length(pred) + if pred(i)>0 + T(pred(i),i)=1; + end +end + + +%%%%%%%%%%% + +function [d, pre, post, cycle, f, pred] = dfs(adj_mat, start, directed) +% DFS Perform a depth-first search of the graph starting from 'start'. +% [d, pre, post, cycle, f, pred] = dfs(adj_mat, start, directed) +% +% Input: +% adj_mat(i,j)=1 iff i is connected to j. +% start is the root vertex of the dfs tree; if [], all nodes are searched +% directed = 1 if the graph is directed +% +% Output: +% d(i) is the time at which node i is first discovered. +% pre is a list of the nodes in the order in which they are first encountered (opened). +% post is a list of the nodes in the order in which they are last encountered (closed). +% 'cycle' is true iff a (directed) cycle is found. +% f(i) is the time at which node i is finished. +% pred(i) is the predecessor of i in the dfs tree. +% +% If the graph is a tree, preorder is parents before children, +% and postorder is children before parents. +% For a DAG, topological order = reverse(postorder). +% +% See Cormen, Leiserson and Rivest, "An intro. to algorithms" 1994, p478. + +n = length(adj_mat); + +global white gray black color +white = 0; gray = 1; black = 2; +color = white*ones(1,n); + +global time_stamp +time_stamp = 0; + +global d f +d = zeros(1,n); +f = zeros(1,n); + +global pred +pred = zeros(1,n); + +global cycle +cycle = 0; + +global pre post +pre = []; +post = []; + +if ~isempty(start) + dfs_visit(start, adj_mat, directed); +else + for u=1:n + if color(u)==white + dfs_visit(u, adj_mat, directed); + end + end +end + + +%%%%%%%%%% + +function dfs_visit(u, adj_mat, directed) + +global white gray black color time_stamp d f pred cycle pre post + +pre = [pre u]; +color(u) = gray; +time_stamp = time_stamp + 1; +d(u) = time_stamp; +if directed + ns = children(adj_mat, u); +else + ns = neighbors(adj_mat, u); + ns = mysetdiff(ns, pred(u)); % don't go back to visit the guy who called you! +end +for v=ns(:)' + %fprintf('u=%d, v=%d, color(v)=%d\n', u, v, color(v)) + switch color(v) + case white, % not visited v before (tree edge) + pred(v)=u; + dfs_visit(v, adj_mat, directed); + case gray, % back edge - v has been visited, but is still open + cycle = 1; + %fprintf('cycle: back edge from v=%d to u=%d\n', v, u); + case black, % v has been visited, but is closed + % no-op + end +end +color(u) = black; +post = [post u]; +time_stamp = time_stamp + 1; +f(u) = time_stamp; + + diff --git a/sourcecodes/bnt-master/graph/triangulate.c b/sourcecodes/bnt-master/graph/triangulate.c new file mode 100644 index 00000000..cda13533 --- /dev/null +++ b/sourcecodes/bnt-master/graph/triangulate.c @@ -0,0 +1,159 @@ +/* triangulate.c written by Ilya Shpitser */ + +#include <stdlib.h> + +#ifdef UNIX +#include "matlab.h" +#endif + +#include "matrix.h" +#include "mex.h" + +#include "elim.h" +#include "map.h" +#include "misc.h" + +void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]){ + + int dims [2]; + int i, j, k, m, n; + long index; + double * G_pr; + double * stage_pr; + double * answer_G_pr, * fill_ins_pr; + double * matlab_clique_pr; + mxArray * matlab_clique; + Elimination e; + float ** adj_mat; + int ** order = (int **) NULL; + Iterator iter, iter2; + word w, w2; + int ** fill_ins; + Map cliques; + Map clique; + mxArray * fill_ins_mat; + int * nodes; + mxArray * full; + +// (original) full = mlfFull((mxArray *) prhs[0]); + full = (mxArray *) mlfFull((mxArray *) prhs[0]); // added typecasting + /* Obtain graph matrix information. */ + m = mxGetM(full); + n = mxGetN(full); + G_pr = mxGetPr(full); + + if(n < 1 || m < 1){ + return; + } + + /* Allocate and populate the log weight adjacency matrix corresponding + to the input graph. */ + adj_mat = (float **) malloc(sizeof(float *) * m); + adj_mat[0] = (float *) malloc(sizeof(float) * m * n); + for(i = 1; i < m; i++){ + adj_mat[i] = adj_mat[i - 1] + n; + } + /* We no longer have log weight info, but we have a (total) ordering on + the nodes already, so we do not need this information. */ + for(i = 0; i < m; i++){ + for(j = 0; j < n; j++){ + index = j * m + i; + if(G_pr[index] > 0){ + adj_mat[i][j] = 1; + } else { + adj_mat[i][j] = 0; + } + } + } + + /* Convert the total elimination ordering into a partial order argument + for the elimination routine. The elimination routine's purpose in this + mode of operation is to return cliques and fill-in edges. */ + if(nrhs > 1){ + order = (int **) malloc(sizeof(int *) * m); + order[0] = (int *) malloc(sizeof(int) * m * n); + for(i = 1; i < m; i++){ + order[i] = order[i - 1] + n; + } + for(i = 0; i < m; i++){ + for(j = 0; j < n; j++){ + order[i][j] = 0; + } + } + stage_pr = mxGetPr(prhs[1]); + for(i = 0; i < mxGetN(prhs[1]) - 1; i++){ + order[(int) stage_pr[i] - 1][(int) stage_pr[i + 1] - 1] = 1; + } + } + + /* Find the elimination ordering. */ + e = find_elim(n, adj_mat, order, -1); + + /* Allocate memory for the answer, and set the answer. */ + plhs[0] = mxCreateDoubleMatrix(m, n, mxREAL); + answer_G_pr = mxGetPr(plhs[0]); + cliques = get_cliques(e); +/* + dims[0] = 1; + dims[1] = get_size_Map(cliques); + plhs[1] = mxCreateCellArray(2, (const int *) dims);*/ + plhs[1] = mxCreateCellMatrix(get_size_Map(cliques), 1); + fill_ins = get_fill_ins(e); + fill_ins_mat = mxCreateDoubleMatrix(m, n, mxREAL); + fill_ins_pr = mxGetPr(fill_ins_mat); + + for(i = 0; i < n; i++){ + for(j = 0; j < m; j++){ + index = j * m + i; + answer_G_pr[index] = G_pr[index]; + if(fill_ins[i][j] > 0){ + answer_G_pr[index] = 1; + fill_ins_pr[index] = 1; + } + } + } + mxDestroyArray(full); +// (original) plhs[2] = mlfSparse(fill_ins_mat, NULL, NULL, NULL, NULL, NULL); + plhs[2] = (mxArray *) mlfSparse(fill_ins_mat, NULL, NULL, NULL, NULL, NULL); // added typecasting + mxDestroyArray(fill_ins_mat); + nodes = (int *) malloc(sizeof(int) * n); + k = 0; + iter = get_Iterator(cliques); + while(!is_empty(iter)){ + w = next_key(iter); + clique = (Map) w.v; + matlab_clique = mxCreateDoubleMatrix(1, get_size_Map(clique), mxREAL); + matlab_clique_pr = mxGetPr(matlab_clique); + for(i = 0; i < n; i++){ + nodes[i] = 0; + } + iter2 = get_Iterator(clique); + while(!is_empty(iter2)){ + w2 = next_key(iter2); + nodes[w2.i] = w2.i + 1; + } + j = 0; + for(i = 0; i < n; i++){ + if(nodes[i] > 0){ + matlab_clique_pr[j++] = nodes[i]; + } + } + mxSetCell(plhs[1], k++, matlab_clique); + } + free(nodes); + + /* Finally, free the allocated memory. */ + destroy_Elimination(e); + if(adj_mat){ + if(adj_mat[0]){ + free(adj_mat[0]); + } + free(adj_mat); + } + if(order){ + if(order[0]){ + free(order[0]); + } + free(order); + } +} diff --git a/sourcecodes/bnt-master/graph/triangulate.m b/sourcecodes/bnt-master/graph/triangulate.m new file mode 100644 index 00000000..8725c62e --- /dev/null +++ b/sourcecodes/bnt-master/graph/triangulate.m @@ -0,0 +1,42 @@ +function [G, cliques, fill_ins] = triangulate(G, order) +% TRIANGULATE Ensure G is triangulated (chordal), i.e., every cycle of length > 3 has a chord. +% [G, cliques, fill_ins, cliques_containing_node] = triangulate(G, order) +% +% cliques{i} is the i'th maximal complete subgraph of the triangulated graph. +% fill_ins(i,j) = 1 iff we add a fill-in arc between i and j. +% +% To find the maximal cliques, we save each induced cluster (created by adding connecting +% neighbors) that is not a subset of any previously saved cluster. (A cluster is a complete, +% but not necessarily maximal, set of nodes.) + +MG = G; +n = length(G); +eliminated = zeros(1,n); +cliques = {}; +for i=1:n + u = order(i); + U = find(~eliminated); % uneliminated + nodes = myintersect(neighbors(G,u), U); % look up neighbors in the partially filled-in graph + nodes = myunion(nodes, u); % the clique will always contain at least u + G(nodes,nodes) = 1; % make them all connected to each other + G = setdiag(G,0); + eliminated(u) = 1; + + exclude = 0; + for c=1:length(cliques) + if mysubset(nodes,cliques{c}) % not maximal + exclude = 1; + break; + end + end + if ~exclude + cnum = length(cliques)+1; + cliques{cnum} = nodes; + end +end + +fill_ins = sparse(triu(max(0, G - MG), 1)); + +%assert(check_triangulated(G)); % takes 72% of the time! + + diff --git a/sourcecodes/bnt-master/graph/triangulate_2Dlattice_demo.m b/sourcecodes/bnt-master/graph/triangulate_2Dlattice_demo.m new file mode 100644 index 00000000..12ad1312 --- /dev/null +++ b/sourcecodes/bnt-master/graph/triangulate_2Dlattice_demo.m @@ -0,0 +1,131 @@ +% Consider a 3x3 lattice with 4-nearest neighbor connectivity + +% 1 - 2 - 3 +% | | | +% 4 - 5 - 6 +% | | | +% 7 - 8 - 9 + +N = 3; +G = mk_2D_lattice(N,N,4); +G0 = G; + +% Now add in the diagonal edges + +if 0 +% 1 - 2 - 3 +% | x | x | +% 4 - 5 - 6 +% | x | x | +% 7 - 8 - 9 + +G(1,5)=1; G(5,1)=1; +G(2,6)=1; G(6,2)=1; +G(4,2)=1; G(2,4)=1; +G(5,3)=1; G(3,5)=1; + +G(4,8)=1; G(8,4)=1; +G(5,9)=1; G(9,5)=1; +G(7,5)=1; G(5,7)=1; +G(8,6)=1; G(6,8)=1; +end + +% 1 - 2 - 3 +% | / | \ | +% 4 - 5 - 6 +% | \ | / | +% 7 - 8 - 9 + +G(2,6)=1; G(6,2)=1; +G(4,2)=1; G(2,4)=1; +G(4,8)=1; G(8,4)=1; +G(8,6)=1; G(6,8)=1; + +% Is this a chordal (triangulated) graph? No! + +assert(~check_triangulated(G)) + +% The reason is that there is a chordless cycle around the outside nodes. +% To see this, imagine "picking up" node 5, leaving the rest on the plane +% (like a hoop skirt, or a tent), as shown below + +% 1 - 2 - 3 +% | / \ | +% 4 6 +% | \ / | +% 7 - 8 - 9 + + +% However, if we add in the 4-6 arc, it will be chordal. + +G2 = G; +G2(4,6)=1; G2(6,4)=1; +assert(check_triangulated(G2)) + +% Or we can add in the 2-8 arc +G2 = G; +G2(2,8)=1; G2(8,2)=1; +assert(check_triangulated(G2)) + + +if 0 +% 4x4 lattice with cross arcs +N=4;G0 = mk_2D_lattice(N,N,4); +vs = [1 6; 2 5; 2 7; 3 6; 3 8; 4 7; ... + 5 10; 6 9; 6 11; 7 10; 7 12; 8 11;... + 9 14; 10 13; 10 15; 11 14; 11 16; 12 15]; +for i=1:size(vs,1) + u = vs(i,1); v= vs(i,2); + G0(u,v) = 1; G0(v,u) = 1; +end +end + +% Here is how we can discover which edges to fill in automatically +% (although possibly sub-optimally) +weights = 2*ones(1,N*N); % all nodes are binar + +% fill-ins = 2-4, 2-6, 4-8, 6-8 and 4-6 +% cliques = 124, etc and 2456 4568 +greedy_order = best_first_elim_order(G0, weights); +[GT, cliques, fill_ins] = triangulate(G0, greedy_order) +assert(check_triangulated(GT)) + + + +greedy_order = best_first_elim_order(G, weights); +[GT, cliques, fill_ins] = triangulate(G, greedy_order) +assert(check_triangulated(GT)) + +% fill-ins = [4 6] + +% Cliques are the overlapping squares [1,2,4,5], [2 3 5 6], [4 5 7 8], [5 6 8 9] +% and the following caused by the fill-in: [2 4 5 6], [4 5 6 8] + +% Connect the maximal cliques of the triangulate graph into a junction tree +[jtree, root, B, clq_weights] = cliques_to_jtree(cliques, weights); + +% In this case, all cliques have weight 2^4 = 16 + + +% Now consider size of max clique as a function of grid size +% Note: this is not necessarily the optimal triangulation + +% N 5 10 15 16 17 18 +% m 6 15 23 25 28 28 +Ns = [5 10 15 16 17 18]; +for i=1:length(Ns) + N = Ns(i) + G = mk_2D_lattice(N,N,4); + weights = 2*ones(1,N*N); % all nodes are binary + greedy_order = best_first_elim_order(G, weights); % slow! + [GT, cliques, fill_ins] = triangulate(G, greedy_order); + %assert(check_triangulated(GT)) + [jtree, root, B, clq_weights] = cliques_to_jtree(cliques, weights); + m(i) = log2(max(clq_weights)); +end + +% plot distribution of clique sizes for fixed N +for c=1:length(cliques) + l(c) = length(cliques{c}); +end +hist(l) diff --git a/sourcecodes/bnt-master/graph/triangulate_test.m b/sourcecodes/bnt-master/graph/triangulate_test.m new file mode 100644 index 00000000..70083d1c --- /dev/null +++ b/sourcecodes/bnt-master/graph/triangulate_test.m @@ -0,0 +1,36 @@ +% Test the code using the dag in Fig 1 of Jensen, Jensen, Dittmer, +% "From influence diagrams to junction trees", UAI 94 + +% By reverse enginering Fig 2, we infer that the following arcs should +% be absent from the original dag: b->d1, e->d2, f->d2, g->d4 +a=1; b=2; d1=3; c=4; d=5; e=6; f=7; g=8; d2=9; d4=10; i=11; h=12; d3=13; l=14; j=15; k=16; +dag=zeros(16); +dag(a,c)=1; +%dag(b,[c d d1])=1; +dag(b,[c d])=1; +dag(d1,d)=1; +dag(c,e)=1; +dag(d,[e f])=1; +%dag(e,[g d2])=1; +dag(e,[g])=1; +%dag(f,[d2 h])=1; +dag(f,[h])=1; +%dag(g,[d4 i])=1; +dag(g,[i])=1; +dag(d2,i)=1; +dag(d4,l)=1; +dag(i,l)=1; +dag(h,[j k])=1; +dag(d3,k)=1; + + +[MG, moral_edges] = moralize(dag); +MG(j,k)=1; MG(k,j)=1; % simulate having a common utility child +% MG now equals fig 2 +order = [l j k i h a c d d4 g d3 d2 f e d1 b]; +[MTG, cliques, fill_ins] = triangulate(MG, order); +% MTG equals fig 3 +ns = 2*ones(1,16); +[jtree, root, cliques2] = mk_strong_jtree(cliques, ns, order, MTG); +jtree2 = mk_rooted_tree(jtree, root); +% jtree2 equals fig 4, with their arrows reversed |
