The goal of this assignment is to give you more practice with loops, arrays, and functions and to reinforce the simulation concepts learned in the first programming assignment.
We are interested in looking at the dynamics of triads (aka, trios) in the network and how changes in their relationships affect the structure of the network as a whole. Social network theory suggests that some combinations of friend/enemy relationships among three people are stable (or balanced) and others are not. There are four possible configurations or types of triads (note: this figure is in color in the on-line version of the assignment):
We identify the type of a triad based on the number of enemy edges. A Type 0 triad represents three mutual friends, which is a stable configuration. Type 2 triads are also stable under the theory that the enemy of my friend is my enemy. In contrast, Type 1 triads are not stable. If person A is friends with persons B and C, it is likely that persons B and C will eventually become friends or at least acquaintances, which yields Type 0 triad. (A property known as triadic closure.) Alternatively, it might be the case that persons B and C cannot stand each other, in which case, it is likely that person A will eventually flip his opinion of one of person B and person C, which yields a Type 2 triad. Finally, the theory that the enemy of my enemy is my friend suggests that Type 3 triads are also not stable. Eventually, two of the three persons in the triad will gang up on the third.
The field of social network theory uses the term balanced to describe a triad that is in a stable configuration. A network is said to be in structural balance if every possible triad is balanced. The question of interest to sociologists is: given a neighborhood where the relationships among the people in the neighborhood are chosen at random and a set of rules for how to update relationships to resolve instabilities, how likely is it that we will eventually reach a state of structural balance?
We will answer this question by simulating the evolution of the relationships in the network. To guarantee that our simulations will terminate, we are going to limit the updates that can be done during a single trial simulation. Instead of asking how likely is it that a neighborhood with certain characteristics will reach structural balance, we ask how likely is it that such neighborhoods will reach update-limited structural-balance, i.e., how likely is it that such neighborhoods will reach structural balance within a specified number of updates. Your task is to write a program to answer this question.
We have provided a function genNeighborhood(int N, double p) that takes the number of people in the neighborhood, N, and the friendship probability, p, and returns a randomly generated neighborhood (where each pair of neighbors are friends with probability p).
For testing purposes, it can be useful to start from a known neighborhood. The distribution contains some simple neighborhoods for this purpose:
We have also provided functions that read the neighborhood files (boolean[][] TableUtil.loadBooleanTable(String filename)) and print the current state of a neighborhood (TableUtil.printBooleanTable(boolean[][] table)).
We have provided the code for your program to accept all the necessary parameters as command-line arguments. Our code validates the parameters and uses them to invoke estimateBalanceLikelihood.
You should not implement your entire solution in that one function! This exercise requires decomposing the problem into multiple functions; instead of telling you what that decomposition is, it is up to you to decide what functions you will need to solve the problem cleanly. You should think carefully about this decomposition and how you will test your functions before you start writing code.
Balance.java includes code to parse the command-line arguments, call the function estimateBalanceLikelihood, and output the result.
Unlike the previous two assignments, we are providing you with only a few tests. Furthermore, these tests only test your complete solution in a few scenarios. So, just because your code passes the tests we have provided is no guarantee that your solution is correct.
In this assignment, it is up to you to write tests that will convince you (and us) that your code is correct. These tests are more properly called unit tests because they focus on a single function (a single "unit" of your program) to determine whether it will function properly when used in a larger context. A good set of unit tests covers all the different types of input the function might receive. For example, if you have a function for determining whether a network is in structural balance, you would want to test it on at least one balanced network and one unbalanced network.
In the previous assignment, we encouraged you to lump all such tests into a TestAuxiliary.java file. Java programmers traditionally write a separate test class, named TestX.java, for each function X. In this assignment, you must follow this convention and provide test code for all your major functions. To get you started, we have provided sample test code for the function estimateBalanceLikelihood (see TestestimateBalanceLikelihood.java) to give you an idea of how to structure your testing code.
When writing your own test classes, we suggest you do the following:
Your grade will depend, in part, on the quality of your test code, that is, whether your tests are comprehensive.
This part of the assignment may sound tedious, but testing each function as you write it can often significantly reduce the amount of time you spend debugging. One of my test functions turned up a bug---I divided by 30 in a place where I had intended to divide by 3.0---that I had overlooked and might never have found in the absence of careful unit testing.
Because testing code that uses random numbers can be tricky, our LocalRandom class allows you dictate the sequence of numbers to be generated by calling the function LocalRandom.initRandomFixed() with an array of doubles. Subsequent calls to the random number generator will hand out the values you specified in the order they occur in the array. Note: LocalRandom.randBoolean() generates a random value (using the specified values, if provided) and returns true if the specified value is strictly less than 0.5 and false, otherwise.
For example, if we wish to the test first update rule for type 1
triads, we can set q to be .6 and use the (1,2,3) triad
the sample-neighborhood-3.txt as a test case. Here's the
setup code for the test:
boolean[][] network = TableUtil.loadBooleanTable("tests/sample-neighborhood-3.txt");
double[] vals = {.4}; // yield a value less than q.
LocalRandom.initRandomFixed(vals);
// do test on network w/ trial (1,2,3)
boolean[][] network1 = TableUtil.loadBooleanTable("tests/sample-neighborhood-3.txt");
double[] vals1 = {.7, .3}; // yield a value greater q, and then yield T.
LocalRandom.initRandomFixed(vals1);
// do test on network1 w/ trial (1,2,3)
boolean[][] network2 = TableUtil.loadBooleanTable("tests/sample-neighborhood-3.txt");
double[] vals2 = {.7, .6}; // yield a value greater q, and then yield F.
LocalRandom.initRandomFixed(vals2);
// do test on network2 w/ trial (1,2,3)
You will need to add your test code to your PhoenixForge repository using the command svn add <filename> from within your pa3 directory, where <filename> is replaced by the name of the file you wish to add. For example, svn add TestestimateBalanceLikelihood.java will add the file TestestimateBalanceLikelihood.java to the repository.
To submit your assignment, use the command svn status to make sure you have added all your files to your repository and then check your code into PhoenixForge using the command:
from within your pa3 directory. We will grade the last version you check in. We recommend checking in your code early and often.svn ci -m"final version ready for submission"