001/*
002 *                    BioJava development code
003 *
004 * This code may be freely distributed and modified under the
005 * terms of the GNU Lesser General Public Licence.  This should
006 * be distributed with the code.  If you do not have a copy,
007 * see:
008 *
009 *      http://www.gnu.org/copyleft/lesser.html
010 *
011 * Copyright for this code is held jointly by the individual
012 * authors.  These should be listed in @author doc comments.
013 *
014 * For more information on the BioJava project and its aims,
015 * or to join the biojava-l mailing list, visit the home page
016 * at:
017 *
018 *      http://www.biojava.org/
019 *
020 */
021
022
023package org.biojava.bio.dist;
024
025import java.util.Iterator;
026
027import org.biojava.bio.BioError;
028import org.biojava.bio.symbol.Alphabet;
029import org.biojava.bio.symbol.AtomicSymbol;
030import org.biojava.bio.symbol.FiniteAlphabet;
031import org.biojava.bio.symbol.IllegalAlphabetException;
032import org.biojava.bio.symbol.IllegalSymbolException;
033import org.biojava.bio.symbol.Symbol;
034import org.biojava.utils.AbstractChangeable;
035import org.biojava.utils.ChangeEvent;
036import org.biojava.utils.ChangeForwarder;
037import org.biojava.utils.ChangeSupport;
038import org.biojava.utils.ChangeType;
039import org.biojava.utils.ChangeVetoException;
040
041/**
042 * An abstract implementation of Distribution.
043 * <p>
044 * You will need to override <code>getWeight()</code> for a simple
045 * implementation. You may also wish to override the other methods if the
046 * default implementation is not suitable.
047 * </p>
048 *
049 * <p>
050 * The <code>registerWithTrainer</code> method registers
051 * an <code>IgnoreCountsTrainer</code>.  To make an <code>AbstractDistribution</code>
052 * subclass trainable, this method must be overridden.
053 * </p>
054 *
055 * @author Matthew Pocock
056 * @author Thomas Down
057 * @author Mark Schreiber (serialization support)
058 * @author Greg Cox
059 * @since 1.0
060 */
061
062public abstract class AbstractDistribution
063  extends
064    AbstractChangeable
065  implements
066    Distribution
067{
068  /**
069   * Forwarder for modifications to the null model.
070   */
071  protected transient ChangeForwarder nullModelForwarder = null;
072
073  protected ChangeSupport getChangeSupport(ChangeType ct) {
074      ChangeSupport changeSupport = super.getChangeSupport(ct);
075
076    if(
077      ((Distribution.NULL_MODEL.isMatchingType(ct)) || (ct.isMatchingType(Distribution.NULL_MODEL))) &&
078      nullModelForwarder == null
079    ) {
080      nullModelForwarder =
081              new ChangeForwarder.Retyper(this, changeSupport, Distribution.NULL_MODEL);
082      getNullModel().addChangeListener(nullModelForwarder, Distribution.WEIGHTS);
083    }
084
085    return changeSupport;
086  }
087
088  /**
089   * Implement this to actually set the weight.
090   *
091   * <p>
092   * Do not inform any listeners. This has already been done for you. Just
093   * update state.
094   * </p>
095   *
096   * @param sym     the AtomicSymbol to update for
097   * @param weight  the new weight for that symbol
098   * @throws IllegalSymbolException if the symbol is not known
099   * @throws ChangeVetoException    if the change is to be prevented
100   */
101  abstract protected void setWeightImpl(AtomicSymbol sym, double weight)
102  throws IllegalSymbolException, ChangeVetoException;
103
104  /**
105   * Set the weight of a given symbol in this distribution.
106   * <P>
107   * This implementation informs all listeners of the change, and then calls
108   * setWeightImpl to make the actual change. Sub-classes should over-ride
109   * setWeightImpl to implement the actual storage of the weights.
110   *
111   * @param sym  the Symbol to set the weight for
112   * @param weight  it's new weight
113   * @throws IllegalSymbolException if sym is not known
114   * @throws ChangeVetoException    if the update was prevented
115   */
116  final public void setWeight(Symbol sym, double weight)
117  throws IllegalSymbolException, ChangeVetoException {
118    if(!hasListeners()) {
119      doSetWeight(sym, weight);
120    } else {
121      ChangeEvent ce = new ChangeEvent(
122        this,
123        Distribution.WEIGHTS,
124        new Object[] {sym, new Double(weight)},
125        new Object[] {sym, new Double(getWeight(sym))}
126      );
127      ChangeSupport changeSupport = super.getChangeSupport(Distribution.WEIGHTS);
128      synchronized(changeSupport) {
129        changeSupport.firePreChangeEvent(ce);
130        doSetWeight(sym, weight);
131        changeSupport.firePostChangeEvent(ce);
132      }
133    }
134  }
135
136  private void doSetWeight(Symbol sym, double weight)
137  throws IllegalSymbolException, ChangeVetoException {
138    if(sym instanceof AtomicSymbol) {
139      setWeightImpl((AtomicSymbol) sym, weight);
140    } else {
141      //need to divide the weight up amongst the atomic symbols according
142      //to the null model
143      FiniteAlphabet fa = (FiniteAlphabet) sym.getMatches();
144      double totalNullWeight = this.getNullModel().getWeight(sym);
145      for(Iterator si = fa.iterator(); si.hasNext(); ) {
146        AtomicSymbol as = (AtomicSymbol) si.next();
147        double symNullWeight = this.getNullModel().getWeight(as);
148        setWeightImpl(as, weight * symNullWeight / totalNullWeight);
149      }
150    }
151  }
152
153  /**
154   * Implement this to set the null model.
155   *
156   * <p>
157   * You should not inform any change listeners in this method. All of that
158   * work has been done for you.
159   * </p>
160   *
161   * @param nullModel  the new null model Distribution
162   * @throws IllegalAlphabetException if the null model is for the wrong alphabet
163   * @throws ChangeVetoException  if your implementation wishes to block this
164   *    opperation
165   */
166  abstract protected void setNullModelImpl(Distribution nullModel)
167  throws IllegalAlphabetException, ChangeVetoException;
168
169  final public void setNullModel(Distribution nullModel)
170  throws IllegalAlphabetException, ChangeVetoException {
171    if(nullModel.getAlphabet() != getAlphabet()) {
172      throw new IllegalAlphabetException(
173        "Could not use distribution " + nullModel +
174        " as its alphabet is " + nullModel.getAlphabet().getName() +
175        " and this distribution's alphabet is " + getAlphabet().getName()
176      );
177    }
178    Distribution oldModel = getNullModel();
179    if(nullModelForwarder != null) {
180      if(oldModel != null) {
181        oldModel.removeChangeListener(nullModelForwarder);
182      }
183      nullModel.addChangeListener(nullModelForwarder);
184    }
185    if(!hasListeners()) {
186      // if there are no listeners yet, don't go through the overhead of
187      // synchronized regions or of trying to inform them.
188      setNullModelImpl(nullModel);
189    } else {
190      // OK - so somebody is intereted in me. Do it properly this time.
191      ChangeEvent ce = new ChangeEvent(
192        this,
193        Distribution.NULL_MODEL,
194        nullModel,
195        oldModel
196      );
197      ChangeSupport changeSupport = super.getChangeSupport(Distribution.NULL_MODEL);
198      synchronized(changeSupport) {
199        changeSupport.firePreChangeEvent(ce);
200        setNullModelImpl(nullModel);
201        changeSupport.firePostChangeEvent(ce);
202      }
203    }
204  }
205
206  /**
207   * Retrieve the weight for this distribution.
208   * <P>
209   * Performs the standard munge to handle ambiguity symbols. The actual weights
210   * for each atomic symbol should be calculated by the getWeightImpl
211   * functions.
212   *
213   * @param sym the Symbol to find the probability of
214   * @return the probability that one of the symbols matching amb was emitted
215   * @throws IllegalSymbolException if for any reason the symbols within amb
216   *         are not recognized by this state
217   */
218  public final double getWeight(Symbol sym)
219  throws IllegalSymbolException {
220    if(sym instanceof AtomicSymbol) {
221      return getWeightImpl((AtomicSymbol) sym);
222    } else {
223      Alphabet ambA = sym.getMatches();
224      if(((FiniteAlphabet) ambA).size() == 0) { // a gap
225        getAlphabet().validate(sym);
226
227        double totalWeight = 0.0;
228        for (Iterator i = ((FiniteAlphabet)getAlphabet()).iterator();
229                          i.hasNext(); ) {
230
231          Symbol s = (Symbol)i.next();
232          totalWeight += getWeight(s);
233        }
234        return 1.0 - totalWeight;
235      }
236      if(ambA instanceof FiniteAlphabet) {
237        FiniteAlphabet fa = (FiniteAlphabet) ambA;
238        double sum = 0.0;
239        for(Iterator i = fa.iterator(); i.hasNext(); ) {
240          Object obj = i.next();
241          if(!(obj instanceof AtomicSymbol)) {
242            throw new BioError(
243              "Assertion Failure: Not an instance of AtomicSymbol: " +
244              obj
245            );
246          }
247          AtomicSymbol as = (AtomicSymbol) obj;
248          sum += getWeightImpl(as);
249        }
250        return sum;
251      } else {
252        throw new IllegalSymbolException(
253           "Can't find weight for infinite set of symbols matched by " +
254           sym.getName()
255        );
256      }
257    }
258  }
259
260  /**
261   * Override this method to implement getting the weight for an atomic
262   * symbol. You should just do what is necessary to fetch state. All the work
263   * with exceptions and listeners will have been handled for you.
264   *
265   * @param sym   the AtomicSymbol to get the weight for
266   * @return      the weight
267   * @throws IllegalSymbolException if sym is not known
268   */
269  protected abstract double getWeightImpl(AtomicSymbol sym)
270  throws IllegalSymbolException;
271
272  public Symbol sampleSymbol() {
273    double p = Math.random();
274    try {
275      for(Iterator i = ((FiniteAlphabet) getAlphabet()).iterator(); i.hasNext(); ) {
276        AtomicSymbol s = (AtomicSymbol) i.next();
277        p -= getWeight(s);
278        if( p <= 0) {
279          return s;
280        }
281      }
282      return getAlphabet().getGapSymbol();
283
284//      StringBuffer sb = new StringBuffer();
285//      for(Iterator i = ((FiniteAlphabet) this.getAlphabet()).iterator(); i.hasNext(); ) {
286//        AtomicSymbol s = (AtomicSymbol) i.next();
287//        double w = getWeight(s);
288//        sb.append("\t" + s.getName() + " -> " + w + "\n");
289//      }
290//      throw new BioError(
291//        "Could not find a symbol to emit from alphabet " + getAlphabet() +
292//        ". Do the probabilities sum to 1?" + "\np=" + p + "\n" + sb.substring(0)
293//      );
294    } catch (IllegalSymbolException ire) {
295      throw new BioError(
296        "Unable to iterate over all symbols in alphabet - " +
297        "things changed beneath me!", ire
298      );
299    }
300  }
301
302  /**
303   * Register an IgnoreCountsTrainer instance as the trainer for this
304   * distribution.  Override this if you wish to implement a trainable
305   * distribution.
306   *
307   * @param dtc  the context to register with
308   */
309  public void registerWithTrainer(DistributionTrainerContext dtc) {
310    dtc.registerTrainer(this, IgnoreCountsTrainer.getInstance());
311  }
312  
313  public int hashCode() {
314      int hc = 0;
315      try {
316          for (Iterator i = ((FiniteAlphabet) getAlphabet()).iterator(); i.hasNext(); ) {
317              Symbol s = (Symbol) i.next();
318              hc = hc ^ (int) (getWeight(s) * (1 << 30));
319          }
320      } catch (IllegalSymbolException ex) {
321          throw new BioError("Assertion failed", ex);
322      }
323      return hc;
324  }
325  
326  public boolean equals(Object o) {
327      if (o instanceof Distribution) {
328          Distribution d = (Distribution) o;
329          if (d.getAlphabet() != this.getAlphabet()) {
330              return false;
331          }
332          try {
333              for (Iterator i = ((FiniteAlphabet) getAlphabet()).iterator(); i.hasNext(); ) {
334                  Symbol s = (Symbol) i.next();
335                  if (this.getWeight(s) != d.getWeight(s)) {
336                      return false;
337                  }
338              }
339              return true;
340          } catch (IllegalSymbolException ex) {
341              throw new BioError(ex);
342          }
343      }
344      return false;
345  }
346}