Sometimes you want to access all pairs of array elements in an expression. For example, suppose you wanted to define a two-dimensional array outerProduct where outerProduct[i,j] = y[i]*y[j]. How do you declare ranges i and j? You might try the following:
Range i = new Range(3); Range j = new Range(3); VariableArray<double> y = Variable.Array<double>(i); y.ObservedValue = new double[] { 1, 2, 3 }; VariableArray2D<double> outerProduct = Variable.Array<double>(i, j); outerProduct[i, j] = y[i]*y[j];
Range i = new Range(3);
Range j = new Range(3);
VariableArray<double> y = Variable.Array<double>(i);
y.ObservedValue = new double[] { 1, 2, 3 };
VariableArray2D<double> outerProduct = Variable.Array<double>(i, j);
outerProduct[i, j] = y[i]*y[j];
But this is not accepted by Infer.NET, because y was declared with range i and cannot be indexed by range j, even though j has the same size. In order to tell Infer.NET that range j is compatible with range i, you create it by cloning range i:
Range i = new Range(3); Range j = i.Clone(); VariableArray<double> y = Variable.Array<double>(i); y.ObservedValue = new double[] { 1, 2, 3 }; VariableArray2D<double> outerProduct = Variable.Array<double>(i, j); outerProduct[i, j] = y[i]*y[j];
Range j = i.Clone();
This version works as desired.
Another good example of where you need to clone a Range is in the 'Mixed Membership Stochastic Blockmodel' of Airoldi et al. which attempts to model relational information among nodes in a network(for example individuals in an social network). Given N nodes, K blocks and a binary matrix of known relationships between nodes (YObs in the code below), the aim is to learn the block membership mixture pi for each node at the same time as learning the pairwise relationship B between different blocks. Because this is a model of relationships, nodes are processed in pairs (initiator and receiver) and these nodes both index the same array variable pi. However the nested Variable.ForEach statements require different ranges; the solution is to clone the range as highlighted below. Similarly the link matrix B requires different ranges for the nested Variable.Switch statements.
Variable.ForEach
Variable.Switch
// Observed interaction matrixvar YObs = new bool[5][];YObs[0] = new bool[] { false, true, true, false, false };YObs[1] = new bool[] { true, false, true, false, false };YObs[2] = new bool[] { true, true, false, false, false };YObs[3] = new bool[] { false, false, false, false, true };YObs[4] = new bool[] { false, false, false, true, false };int K = 2; // Number of blocksint N = YObs.Length; // Number of nodes// RangesRange p = new Range(N).Named("p"); // Range for initiatorRange q = p.Clone().Named("q"); // Range for receiverRange kp = new Range(K).Named("kp"); // Range for initiator block membershipRange kq = kp.Clone().Named("kq"); // Range for receiver block membership// The modelvar Y = Variable.Array(Variable.Array<bool>(q), p); // Interaction matrixvar pi = Variable.Array<Vector>(p).Named("pi"); // Block-membership probability vectorpi[p] = Variable.DirichletUniform(kp).ForEach(p);var B = Variable.Array<double>(kp, kq).Named("B"); // Link probability matrixB[kp, kq] = Variable.Beta(1, 1).ForEach(kp, kq);using (Variable.ForEach(p)) { using (Variable.ForEach(q)) { var z1 = Variable.Discrete(pi[p]).Named("z1"); // Draw initiator membership indicator var z2 = Variable.Discrete(pi[q]).Named("z2"); // Draw receiver membership indicator z2.SetValueRange(kq); using (Variable.Switch(z1)) using (Variable.Switch(z2)) Y[p][q] = Variable.Bernoulli(B[z1, z2]); // Sample interaction value }}// Initialise to break symmetryvar piInit = new Dirichlet[N];for (int i = 0; i < N; i++) { Vector v = Vector.Zero(K); for (int j = 0; j < K; j++) v[j] = 10 * Rand.Double(); piInit[i] = new Dirichlet(v);}// Hook up the dataY.ObservedValue = YObs;// Infervar engine = new InferenceEngine(new VariationalMessagePassing());pi.InitialiseTo(Distribution<Vector>.Array(piInit));var posteriorPi = engine.Infer<Dirichlet[]>(pi);var posteriorB = engine.Infer<Beta[,]>(B);