/* * BucketTree.java * @author Fabio G. Cozman * Copyright 1996 - 1999, Fabio G. Cozman, * Carnergie Mellon University, Universidade de Sao Paulo * fgcozman@usp.br, http://www.cs.cmu.edu/~fgcozman/home.html * * The JavaBayes distribution is free software; you can * redistribute it and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation (either * version 2 of the License or, at your option, any later version), * provided that this notice and the name of the author appear in all * copies. Upon request to the author, some of the packages in the * JavaBayes distribution can be licensed under the GNU Lesser General * Public License as published by the Free Software Foundation (either * version 2 of the License, or (at your option) any later version). * If you're using the software, please notify fgcozman@usp.br so * that you can receive updates and patches. JavaBayes is distributed * "as is", in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with the JavaBayes distribution. If not, write to the Free * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package BayesianInferences; import BayesianNetworks.*; import java.io.*; import java.util.Vector; import java.util.Enumeration; /*******************************************************************/ public class BucketTree { Bucket bucket_tree[]; // Array of Bucket objects. BayesNet bn; // BayesNet that contains the variables. int backward_pointers[]; // Array that stores the index of variables for minimization. DiscreteFunction unnormalized_result; Ordering ordering; int explanation_status; boolean do_produce_clusters; private int active_bucket; final static int MAX_OUT = 2; final static int SUM_OUT = 1; /** * Simple constructor for BucketTree. */ public BucketTree(Ordering ord) { this(ord, false); } /** * Constructor for BucketTree. * Does the whole initialization; it should * be the only method that deals with symbolic names * for variables. */ public BucketTree(Ordering ord, boolean dpc) { int i, j, markers[]; ProbabilityFunction pf; ProbabilityVariable pv; DiscreteVariable aux_pv; DiscreteFunction ut; String order[]; do_produce_clusters = dpc; ordering = ord; // Collect information from the Ordering object. bn = ord.bn; explanation_status = ord.explanation_status; order = ord.order; // Indicate the first bucket to process active_bucket = 0; // Check the possibility that the query has an observed variable i = bn.index_of_variable(order[order.length-1]); pv = bn.get_probability_variable(i); if (pv.is_observed() == true) { pf = transform_to_probability_function(bn, pv); bucket_tree = new Bucket[1]; bucket_tree[0] = new Bucket(this, pv, do_produce_clusters); insert(pf); } else { // Initialize the bucket objects bucket_tree = new Bucket[order.length]; for (i=0; i<order.length; i++) { j = bn.index_of_variable(order[i]); bucket_tree[i] = new Bucket(this, bn.get_probability_variable(j), do_produce_clusters); } // Insert the probability functions into the bucket_tree; // first mark all functions that are actually going // into the bucket_tree. markers = new int[bn.number_variables()]; for (i=0; i<order.length; i++) markers[ bn.index_of_variable(order[i]) ] = 1; // Now insert functions that are marked and non-null. for (i=0; i<bn.number_probability_functions(); i++) { if (markers[ bn.get_probability_function(i).get_index(0) ] == 1) { pf = check_evidence( bn.get_probability_function(i) ); if (pf != null) { aux_pv = (bn.get_probability_function(i)).get_variable(0); insert(pf, ! pf.memberOf(aux_pv.get_index())); } } } // Insert the utility_function. ut = bn.get_utility_function(); if (ut != null) insert(ut); } } /* * Transform an observed ProbabilityVariable into a ProbabilityFunction * to handle the case where the query involves an observed variable. */ private ProbabilityFunction transform_to_probability_function(BayesNet bn, ProbabilityVariable pv) { ProbabilityFunction pf = new ProbabilityFunction(bn, 1, pv.number_values(), null); pf.set_variable(0, pv); int index_of_value = pv.get_observed_index(); pf.set_value(index_of_value, 1.0); return(pf); } /* * Eliminates all variables defined as evidence. * The order of the variables that are not eliminated is * the same order in the original function. */ private ProbabilityFunction check_evidence(ProbabilityFunction pf) { int i, j, k, v, aux_i; boolean markers[] = new boolean[bn.number_variables()]; int n = build_evidence_markers(pf, markers); // Handle special cases if (n == 0) return(null); // No variable remains if (n == pf.number_variables()) return(pf); // No relevant evidence // Calculate necessary quantities in such a // way that the order of variables in the original // function is not altered. int joined_indexes[] = new int[n]; for (i=0, j=0, v=1; i<pf.number_variables(); i++) { aux_i = pf.get_variable(i).get_index(); if (markers[aux_i] == true) { joined_indexes[j] = aux_i; j++; v *= bn.get_probability_variable(aux_i).number_values(); } } // Create new function to be filled with joined variables ProbabilityFunction new_pf = new ProbabilityFunction(bn, n, v, null); for (i=0; i<n; i++) new_pf.set_variable(i, bn.get_probability_variable( joined_indexes[i] )); // Loop through the values check_evidence_loop(new_pf, pf); return(new_pf); } /* * Build an array of markers. The marker for a * variable is true only if the variable is present in the * input ProbabilityFunction pf and is not observed. * Even explanatory variables can be observed and taken as * evidence. */ private int build_evidence_markers(ProbabilityFunction pf, boolean markers[]) { int i, n; // Initialize the markers for (i=0; i<markers.length; i++) markers[i] = false; // Insert the variables of the ProbabilityFunction for (i=0; i<pf.number_variables(); i++) markers[ pf.get_index(i) ] = true; // Take the evidence out for (i=0; i<bn.number_variables(); i++) { if (bn.get_probability_variable(i).is_observed()) markers[i] = false; } // Count how many variables remain n = 0; for (i=0; i<markers.length; i++) { if (markers[i] == true) n++; } return(n); } /* * Obtain the values for the evidence plus function. */ private void check_evidence_loop(ProbabilityFunction new_pf, ProbabilityFunction pf) { int i, j, k, l, m, p, last, current; int indexes[] = new int[bn.number_variables()]; int value_lengths[] = new int[bn.number_variables()]; for (i=0; i<bn.number_variables(); i++) { indexes[i] = 0; value_lengths[i] = bn.get_probability_variable(i).number_values(); } for (i=0; i<bn.number_variables(); i++) { if (bn.get_probability_variable(i).is_observed()) { indexes[i] = bn.get_probability_variable(i).get_observed_index(); } } last = new_pf.number_variables() - 1; for (i=0; i<new_pf.number_values(); i++) { p = new_pf.get_position_from_indexes(indexes); new_pf.set_value(p, pf.evaluate(indexes)); indexes[ new_pf.get_index(last) ]++; for (j=last; j>0; j--) { current = new_pf.get_index(j); if (indexes[current] >= value_lengths[current]) { indexes[current] = 0; indexes[ new_pf.get_index(j - 1) ]++; } else break; } } } /** * Variable elimination in the BucketTree. */ public void reduce() { int i; // Reduce all Bucket objects. for (i=0;i<(bucket_tree.length-1); i++) { active_bucket = i; bucket_tree[i].reduce(); insert(bucket_tree[i]); } // Now reduce the last Bucket. unnormalized_result = bucket_tree[i].combine(); // Mark the last Bucket as DISTRIBUTED. bucket_tree[i].bucket_status = Bucket.DISTRIBUTED; // Generate the backward_pointers if necessary. backward_pointers = backward_maximization(); } /** * Distribute evidence in the BucketTree. * @return true if successful; false if not. */ public boolean distribute() { int i, j; boolean mark_non_conditioning[] = new boolean[bn.number_variables()]; // First make sure the BucketTree has been reduced. if (unnormalized_result == null) reduce(); // Second make sure there is more than one Bucket in the BucketTree. int last = bucket_tree.length - 1; if (last < 1) return(true); // Third, this method is used only if do_produce_clusters is true. if (do_produce_clusters == false) return(false); // Fourth, this method is use only if no explanatory variable was max'ed out. if (backward_pointers != null) return(false); // Go through the Bucket objects, from bottom to top, // to compute the new separator and cluster for each bucket. for (i=(last-1); i>=0; i--) { // Start from (last-1); last does not have child. // Check whether the Bucket has any valid content. if (bucket_tree[i].cluster == null) break; // Take the non-conditioning variables in a boolean array. for (j=0; j<mark_non_conditioning.length; j++) mark_non_conditioning[j] = true; // OBS: The following piece of code will actually be less efficient than // necessary. It will count as "conditioning" any variable in the cluster // except the bucket variable. This will imply that some variables in the // separator will be normalized over without need, and the separator will // be larger than necessary. // OBS: this code was contributed by Wei Zhou (wei@cs.ualberta.ca), // who also detected the problem with the original code. // if (bucket_tree[i].cluster.number_variables() > bucket_tree[i].non_conditioning_variables.size()) for (j=1; j<bucket_tree[i].cluster.number_variables(); j++) { mark_non_conditioning[ (bucket_tree[i].cluster.get_variables())[j].get_index() ] = false; } // The following piece of code does the right thing (compared to the // piece of code above): it selects the // minimum number of non-conditioning variables. To use this piece // of code, it will be necessary to create a "normalize" method that // normalizes with respect to a number of variables at at time. /* for (j=0; j<bucket_tree[i].cluster.number_variables(); j++) { mark_non_conditioning[ (bucket_tree[i].cluster.get_variables())[j].get_index() ] = false; } for (Enumeration e = bucket_tree[i].non_conditioning_variables.elements(); e.hasMoreElements(); ) { ProbabilityVariable pv = (ProbabilityVariable)(e.nextElement()); mark_non_conditioning[pv.get_index() ] = true; } */ // Update the separator. bucket_tree[i].separator = bucket_tree[i].child.cluster.sum_out(bn.get_probability_variables(), mark_non_conditioning); // Compute cluster using new separator (note that if separator // is null, the cluster had all variables already processed). if (bucket_tree[i].separator != null) { // OBS: the method here should normalize with respect to more // than one variable, to allow this algorithm to be more efficient! bucket_tree[i].cluster.normalize_first(); // Now combine the cluster and the separator. bucket_tree[i].cluster = bucket_tree[i].cluster.multiply(bn.get_probability_variables(), bucket_tree[i].separator); } // Mark the Bucket as DISTRIBUTED. bucket_tree[i].bucket_status = Bucket.DISTRIBUTED; } // Indicate success. return(true); } /* * Recover the maximizing variables going back through the * maximizing bucket_tree; the variables are returned as an array * of markers (non-explanation variables get INVALID_INDEX). */ private int[] backward_maximization() { int i, j; int bi = bucket_tree.length - 1; DiscreteFunction back_df; Bucket b = bucket_tree[bi]; // If there are no explanation variables in the BayesNet, return null if (b.backward_pointers == null) return(null); // Initialize the markers for backward pointers with INVALID_INDEX int backward_markers[] = new int[bn.number_variables()]; for (i=0; i<backward_markers.length; i++) backward_markers[i] = BayesNet.INVALID_INDEX; // Initialize the marker for the last bucket backward_markers[b.variable.get_index()] = (int)(b.backward_pointers.get_value(0) + 0.5); // Go backwards through the bucket_tree for (i=(bi-1); i>=0; i--) { if (!bucket_tree[i].is_explanation()) break; back_df = bucket_tree[i].backward_pointers; // Skip null pointers (caused by evidence) if (back_df == null) continue; // Special treatment for bucket with only one value, // since it can be a bucket with only the bucket variable left if (back_df.number_values() == 1) { backward_markers[bucket_tree[i].variable.get_index()] = (int)(back_df.get_value(0) + 0.5); continue; } // Process the bucket j = back_df.get_position_from_indexes(bn.get_probability_variables(), backward_markers); backward_markers[bucket_tree[i].variable.get_index()] = (int)(back_df.get_value(j) + 0.5); } return(backward_markers); } /* * Put the separator function of a Bucket buck * into the BucketTree beyond the current * active_bucket. */ private void insert(Bucket buck) { int i, index; Bucket b; if (buck.separator == null) return; for (i=active_bucket; i<bucket_tree.length; i++) { // Get the index for current Bucket's variable. index = bucket_tree[i].variable.get_index(); // If separator contains a variable in the current Bucket, then join buckets. if (buck.separator.memberOf(index)) { // Add separator to bucket. bucket_tree[i].discrete_functions.addElement(buck.separator); // Update the non_conditioning variables. // Go through the non-conditioning variables in the inserted Bucket. for (Enumeration e = buck.non_conditioning_variables.elements(); e.hasMoreElements(); ) bucket_tree[i].non_conditioning_variables.addElement(e.nextElement()); // Take the inserted Bucket variable out by making it CONDITIONING: // Must take the variable out as it has been eliminated already. bucket_tree[i].non_conditioning_variables.removeElement(buck.variable); // Mark parent/child relationship. buck.child = bucket_tree[i]; bucket_tree[i].parents.addElement(buck); return; } } } /* * Put a DiscreteFunction into the BucketTree beyond the current * active_bucket. */ private void insert(DiscreteFunction df) { insert(df, false); } /* * Put a DiscreteFunction into the BucketTree beyond the current * active_bucket. If was_first_variable_cancelled_by_evidence is true, * then mark the bucket accordingly. */ private void insert(DiscreteFunction df, boolean was_first_variable_cancelled_by_evidence) { int i, index; Bucket b; for (i=active_bucket; i<bucket_tree.length; i++) { index = bucket_tree[i].variable.get_index(); if (df.memberOf(index)) { bucket_tree[i].discrete_functions.addElement(df); // If the function is a ProbabilityFunction, store its // first variable appropriately (assuming for now that // the first variable is the only possible non-conditioning variable). if ((df instanceof ProbabilityFunction) && (!was_first_variable_cancelled_by_evidence)) { bucket_tree[i].non_conditioning_variables.addElement(df.get_variable(0)); } return; } } } /** * Print method for BucketTree. */ public void print() { print(System.out); } /** * Print method for BucketTree. */ public void print(PrintStream out) { out.println("BucketTree:" + "\n\tActive Bucket is " + active_bucket + "."); for (int i=0; i<bucket_tree.length; i++) bucket_tree[i].print(out); out.println("Bucket result: "); unnormalized_result.print(out); } /** * Get the normalized result for the BucketTree. */ public ProbabilityFunction get_normalized_result() { ProbabilityFunction aux_pf = new ProbabilityFunction(unnormalized_result, bn); aux_pf.normalize(); return( aux_pf ); } /* *************************************************************** */ /* Methods that allow basic manipulation of non-public variables */ /* *************************************************************** */ /** * Get the unnormalized_result for the BucketTree. */ public DiscreteFunction get_unnormalized_result() { return(unnormalized_result); } }