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}