BioJava:Tutorial:Changeability examples

Tutorial

By Matthew Pocock

We are going to play with the Changeability code using the example of a GUI for viewing the rolls on a roulette wheel. We will try to estimate the probability of the ball falling on any one of the 40 slots and of it falling on red or black.

The imports

We will need to import some standard graphical packages to make the GUI, and java.util as it gives us stuff like iterators. From BioJava, we will need all of the Changeability API. The other BioJava packages give us things like Symbol objects, alphabets, annotations and probability distributions.

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.util.*;
import javax.swing.*;

import org.biojava.utils.*;
import org.biojava.bio.*;
import org.biojava.bio.symbol.*;
import org.biojava.bio.dist.*;

Setting up the roulette data

Firstly, we need to declare the class as extending JApplet so that we can use it inside a web-page and also rely on Swing working properly.

public class Roulette extends JApplet {

Then we can declare the static variables that will define the game.

  public static final FiniteAlphabet rolls;
  public static final Symbol[] allRolls;

  public static final FiniteAlphabet redBlack;
  public static final Symbol red;
  public static final Symbol black;

  // probability distribution used to sample rolls of the wheel
  public static final Distribution wheelRoller;

Of course, all of these items must be initialized. We will use a static initialization block.

  // stuff to make the roulette wheel exist.
  static {
    final int numRolls = 40;

    // make the rolls alphabet
    rolls = new SimpleAlphabet("Rolls");
    allRolls = new Symbol[numRolls];

Having made the rolls alphabet, we now must populate it with each possible roulette wheel outcome - 1..40 - as a Symbol instance.

    for(int i = 1; i <= numRolls; i++) {
      Symbol s = allRolls[i-1] = AlphabetManager.createSymbol(i + "", Annotation.EMPTY_ANNOTATION);

      // attempt to add the symbol
      // this should work, but we still have to catch the exceptions. Since they
      // should be impossible to throw, we re-throw them as assertion-failures.
      try {
        rolls.addSymbol(s);
      } catch (ChangeVetoException cve) {
        throw new BioError("Assertion Failure: Can't add symbol to the rolls alphabet", cve);
      } catch (IllegalSymbolException ise) {
        throw new BioError("Assertion Failure: Can't add symbol to the rolls alphabet", ise);
      }
    }

Notice that we have to catch exceptions that should be impossible to generate, but are specified in the API. Under different circumstances, these exceptions may be legitimately thrown, and we would have caught them and done something more sensible to handle the error.

    rolls.addChangeListener(ChangeListener.ALWAYS_VETO, Alphabet.SYMBOLS);

This is an example of using ALWAYS_VETO to prevent things from changing. Here we lock the SYMBOLS property of rolls so that no more symbol instances can be added or removed from the alphabet. This ensures data-integrity and makes it harder to write syntactically correct bugs.

We must now make the red/black alphabet.

    // make the red/black alphabet
    redBlack = new SimpleAlphabet("Red/Black");
    red = AlphabetManager.createSymbol("red", Annotation.EMPTY_ANNOTATION);
    black = AlphabetManager.createSymbol("black", Annotation.EMPTY_ANNOTATION);
    // again, add them and throw any exceptions on as assertion-failures.
    try {
      redBlack.addSymbol(red);
      redBlack.addSymbol(black);
    } catch (ChangeVetoException cve) {
      throw new BioError("Assertion Failure: Can't add symbol to the red/black alphabet", cve);
    } catch (IllegalSymbolException ise) {
      throw new BioError("Assertion Failure: Can't add symbol to the red/black alphabet", ise);
    }
    // and again lock the alphabet
    redBlack.addChangeListener(ChangeListener.ALWAYS_VETO, Alphabet.SYMBOLS);

Notice that again while the symbols are added we must check that nothing goes wrong. Also, again, we lock the red/black alphabet so that it can’t be tampered with.

Now we will set up a probability distribution that can be sampled from to simulate the rolling of a roulette wheel. We will simply use an instance of UniformDistribution rather than generating a special distribution ourselves - casinos should have unbiased wheels.

    wheelRoller = new UniformDistribution(rolls);
  }

And there we close the static block. Everything is set up for a game of chance.

Applet for playing the game

Let us start by setting up the state of the applet that will be used for estimating how the game is played, and for rendering the current best-guess for the outcomes of multiple roles of the wheel.

  private Distribution rollDist;
  private Distribution redBlackDist;
  private boolean running = false;
  private Thread countAdder;

rollDist will be our estimate of the probability of any one of the rolls. redBlackDist is our estimate of getting one of red or black (even/odd). We will use the thread in countAdder to repeatedly sample the game, and when running is set to false, we will temporarily suspend sampling.

In the applet’s init method we will set up all the state and build the GUI.

  public void init() {
    super.init(); // can't hurt...

Firstly, lets create the rollDist and redBlackDist objects.

    try {
      rollDist = DistributionFactory.DEFAULT.createDistribution(rolls);
    } catch (IllegalAlphabetException iae) {
      throw new BioError("Could not create distribution", iae);
    }

    redBlackDist = new RedBlackDist(rollDist);

Now we must make an object to estimate the rollDist probabilities. This is done using a DistributionTrainerContext instance called dtc. dtc will collate counts for each of the forty outcomes so that rollDist can then represent these frequencies as a probability distribution.

    final DistributionTrainerContext dtc = new SimpleDistributionTrainerContext();
    dtc.registerDistribution(rollDist);

Now we will create the thread that samples rolls from the roulette wheel. It will synchronize upon itself so that we can suspend it as we wish.

    countAdder = new Thread(new Runnable() {
      public void run() {
        while(true) {

We will check the value of the running member variable to check if we should be sampling the wheel.

          boolean running;
          synchronized(countAdder) {
            running = Roulette.this.running;
          }
          if(running) {

Here we perform the sampling and inform the trainer of the roll. To force rollDist to reflect the new counts, we also call tdc.train, and catch all the resulting exceptions (which should be impossible if everything is set up correctly).

            Symbol s = Roulette.wheelRoller.sampleSymbol();
            try {
              dtc.addCount(rollDist, s, 1.0);
              dtc.train();
            } catch (IllegalSymbolException ise) {
              // should be impossible!
              throw new BioError("Assertion Failure: Sampled symbol not in alphabet", ise);
            } catch (ChangeVetoException cve) {
              cve.printStackTrace();
            }

Now we will synchronize on the thread and sleep for a half second.

            synchronized(countAdder) {
              try {
                countAdder.wait(500);
              } catch (InterruptedException ie) {
              }
            }

This code handles the case when the sampling thread has been asked to stop running temporarily. Again, we must synchronize on the sampling thread.

          } else {
            synchronized(countAdder) {
              try {
                countAdder.wait();
              } catch (InterruptedException ie) {
              } catch (IllegalMonitorStateException imse) {
                throw new Error("Ouch", imse);
              }
            }
          }
        }
      }
    });

That is the end of the sampling thread.

Now we can move onto the GUI. Let’s set up buttons to start and stop the sampler thread and to clear the counts so far.

    final JButton start = new JButton("Start");
    final JButton stop = new JButton("Stop");
    final JButton clear = new JButton("Clear");

The start button must start of enabled, and should cause sampling to start.

    start.setEnabled(true);
    start.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent ae) {
        synchronized(countAdder) {
          running = true;
          start.setEnabled(false);
          stop.setEnabled(true);
          countAdder.notify();
        }
      }
    });

The stop button should start off disabled, and should cause the sampling to stop.

    stop.setEnabled(false);
    stop.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent ae) {
        synchronized(countAdder) {
          running = false;
          start.setEnabled(true);
          stop.setEnabled(false);
          countAdder.notify();
        }
      }
    });

The clear button should be enabled, and should both clear the counts and suspend sampling.

    clear.setEnabled(true);
    clear.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent ae) {
        synchronized(countAdder) {
          running = false;
          start.setEnabled(true);
          stop.setEnabled(false);
          dtc.clearCounts();
          countAdder.notify();
        }
      }
    });

Now we should build the GUI components to render the probability distributions as pie-charts.

    Pie allPie;
    try {
      allPie = new Pie(rollDist, AlphabetManager.getAlphabetIndex(allRolls));
    } catch (IllegalSymbolException ise) {
      throw new BioError("Assertion Failure: Can't make indexer", ise);
    } catch (BioException be) {
      throw new BioError("Assertion Failure: Can't make indexer", be);
    }
    Pie redBlackPie = new Pie(redBlackDist);

Now, we add all of these components to the applet.

    JPanel top = new JPanel();
    top.setLayout(new FlowLayout());
    top.add(start);
    top.add(stop);
    top.add(clear);

    JPanel center = new JPanel();
    center.setLayout(new FlowLayout());
    center.add(redBlackPie);
    center.add(allPie);
    Dimension d = new Dimension(200, 200);
    redBlackPie.setPreferredSize(d);
    allPie.setPreferredSize(d);

    getContentPane().setLayout(new BorderLayout());
    getContentPane().add(top, BorderLayout.NORTH);
    getContentPane().add(center, BorderLayout.CENTER);
  }

This is the end of init. It has set up the state of the object, ready for it to render estimated probabilities of each wheel outcome being observed by repeatedly sampling the roulette wheel.

Starting the game off

The last bit of the applet is the command to set the sampler thread into motion. This really fits into the applet’s start method naturally.

  public void start() {
    super.start();
    countAdder.start();
  }
}

And that is the end of the Roulette class.

The pie-chart rendering component

To render a distribution as a pie-chart, we need a custom sub-class of JComponent. It will have to respond to changes in the distribution and consistently paint itself on the screen. Here is the state it will need.

class Pie extends JComponent {
  private Distribution dist;
  private AlphabetIndex indexer;
  protected ChangeListener repainter;

dist is the distribution that this pie-chart will render. indexer will be used to consistently order the states, and repainter is a ChangeListener instance that will repaint the pie whenever dist changes.

The first constructor just creates an alphabet indexer and chains onto the second one.

  public Pie(Distribution dist) {
    this(dist, AlphabetManager.getAlphabetIndex((FiniteAlphabet) dist.getAlphabet()));
  }

The second constructor builds a couple of ChangeListener instances

  public Pie(Distribution dist, AlphabetIndex indexer) {
    this.dist = dist;
    this.indexer = indexer;

    dist.addChangeListener(repainter = new ChangeAdapter() {
      public void postChange(ChangeEvent ce) {
        repaint();
      }
    }, Distribution.WEIGHTS);
  }

We must provide a way to render the pie-chart. JComponent likes us to override the paintComponent method, so this is what we shall do. The first job for the paint method is to work out some basic geometric points around which to render.

  protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2 = (Graphics2D) g;

    double pad = 5.0;
    Rectangle2D boundingBox = new Rectangle2D.Double(pad, pad, getWidth() - 2.0 * pad, getHeight() - 2.0 * pad);
    double midx = getWidth() * 0.5;
    double midy = getHeight() * 0.5;

Now we can render each slice of the pie-chart, using a width proportional to the probability of each symbol, skipping each zero probability.

    double angle = 0.0;
    for(int i = 0; i < indexer.getAlphabet().size(); i++) {
      try {
        Symbol s = indexer.symbolForIndex(i);
        double p = dist.getWeight(s);
        if(p != 0.0) {
          double extent = p * 365.0;
          Arc2D slice = new Arc2D.Double(boundingBox, angle, extent, Arc2D.PIE);
          angle += extent;

          g2.setPaint((s == Roulette.red) ? Color.red : (s == Roulette.black) ? Color.black :
              (((char) (Integer.parseInt(s.getName()) - '0') % 2) == 0) ? Color.red : Color.black);

          g2.fill(slice);
          g2.setPaint(Color.blue);
          g2.draw(slice);
        }
      } catch (IllegalSymbolException ise) {
        ise.printStackTrace();
      }
    }

The last task is to render on some labels so that we know what each slice represents.

    angle = 0.0;
    g2.setPaint(Color.yellow);
    for(int i = 0; i < indexer.getAlphabet().size(); i++) {
      try {
        Symbol s = indexer.symbolForIndex(i);
        double p = dist.getWeight(s);
        if(p != 0.0) {
          double extent = p * 365.0;
          double a2 = Math.toRadians(angle + 0.5 * extent);
          angle += extent;

          g2.drawString(s.getName(),
              (float) (midx + Math.cos(a2) * midx * 0.8), (float) (midy - Math.sin(a2) * midy * 0.8));
        }
      } catch (IllegalSymbolException ise) {
        ise.printStackTrace();
      }
    }
  }
}

That is the end of the pie-chart class.

RedBlackDist as a view onto the rollDist distribution

The RedBlackDist class will implement Distribution, but will need to map the 40-symbol alphabet of the entire roulette wheel into the 2-symbol alphabet of red/black. It must remain synchronized with the main wheel, updating its state whenever its parent does.

class RedBlackDist extends AbstractDistribution {
  private Distribution parent;
  private Distribution nullModel;
  private double red;
  private double black;

  protected ChangeListener parentListener;
  protected ChangeListener propUpdater;

parent is the distribution being viewed. nullModel represents a view of the parent’s null model. red and black will store the probabilities of coming up red or black in the parent. parentListener will listen to the parent for when it changes and notify all interested parties that this distribution is changing in response. propUpdater will do the job of actually calculating red and black from the parent.

Let’s set up our distribution.

  public RedBlackDist(final Distribution parent) {
    this.parent = parent;
    generateChangeSupport();
    parent.addChangeListener(parentListener =
      new ChangeForwarder(this, getChangeSupport(Distribution.WEIGHTS)) {

This listener will forward changes to the parent weights as changes to this distribution. It extends ChangeForwarder that is a special instance that passes on changes to one object as knock-on events to another. By using the ChangeEvent constructor that includes a ChangeEvent, we can pass on the complete chain-of-evidence that allows listeners to work out why we are claiming to alter.

      protected ChangeEvent generateEvent(ChangeEvent ce) {
        return new ChangeEvent(getSource(), Distribution.WEIGHTS, null, null, ce);
      }
    }, Distribution.WEIGHTS);

We must also add a listener to ourselves to trap successful attempts to change (those that are not vetoed), and to update the values of red and black.

    addChangeListener(propUpdater = new ChangeAdapter() {
      public void postChange(ChangeEvent ce) {
        red = black = 0.0;
        for(Iterator<Symbol> i = ((FiniteAlphabet) (parent.getAlphabet())).iterator(); i.hasNext(); ) {
          Symbol s = i.next();
          try {
            if(((char) (Integer.parseInt(s.getName()) - '0') % 2) == 0) // even - red
              red += parent.getWeight(s);
            else // odd - black
              black += parent.getWeight(s);
          } catch (IllegalSymbolException ise) {
            throw new BioError("Assertion Failure: Can't find symbol", ise);
          }
        }
      }
    }, Distribution.WEIGHTS);
  }

And that is the end of the constructor.

Now we must provide the missing methods in AbstractDistribution. These are fairly boring. Our alphabet is the same as the roulette redBlack object, and getWeightImpl will return the value of red for the red symbol and the value of black for the black symbol.

  public Alphabet getAlphabet() {
    return Roulette.redBlack;
  }

  protected double getWeightImpl(AtomicSymbol sym) throws IllegalSymbolException {
    if(sym == Roulette.red)
      return red;
    else if(sym == Roulette.black)
      return black;
    throw new IllegalSymbolException("No symbol known for " + sym);
  }

All of these methods are just stubs. Notice that they throw ChangeVetoExceptions to indicate that they are not implemented. ChangeVetoException can either mean that the change is disallowed because some listener explicitly stops it, or that the method is not supported. Either way, the state of the object will not be updated.

  protected void setWeightImpl(AtomicSymbol as, double weight)
  throws ChangeVetoException, IllegalSymbolException {
    throw new ChangeVetoException("RedBlackDist is immutable");
  }

  protected void setNullModelImpl(Distribution nullModel)
  throws ChangeVetoException, IllegalAlphabetException {
    throw new ChangeVetoException("RedBlackDist is immutable");
  }

  public Distribution getNullModel() {
    if(nullModel == null)
      nullModel = new RedBlackDist(parent.getNullModel());
    return nullModel;
  }
}

What you should see

When you run this applet, you should see a GUI with start, stop, and clear buttons. If you click on start, the applet will start sampling the table every 1/2 second. You will notice that the two pie-charts reflect these rolls by repainting. If you click stop, the sampling thread will stop getting new rolls. If you click start again, then more counts will be collected. If you click clear, then the sampling will stop. Pressing start again will start the process off from the initial point of just one count collected. This applet looks crisp with a width of 450 pixels and a height of 250. Unfortunately, the applet page appears to have disappeared.

By the end of this, you should feel comfortable with listening for events and writing custom ChangeListener implementations. You should be able to prevent a property from altering by adding an ALWAYS_VETO listener. You should have an understanding of how when one object changes, it may cause the state of another object to change, and off how to write a ChangeAdapter instance that will wire this together. I hope it was fun.