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.dp; 024 025import java.io.Serializable; 026import java.util.HashMap; 027import java.util.Iterator; 028import java.util.Map; 029 030import org.biojava.bio.BioError; 031import org.biojava.bio.dist.Distribution; 032import org.biojava.bio.dist.SimpleDistribution; 033import org.biojava.bio.symbol.Alphabet; 034import org.biojava.bio.symbol.FiniteAlphabet; 035import org.biojava.bio.symbol.IllegalAlphabetException; 036import org.biojava.bio.symbol.IllegalSymbolException; 037import org.biojava.bio.symbol.SimpleAlphabet; 038import org.biojava.utils.AbstractChangeable; 039import org.biojava.utils.ChangeEvent; 040import org.biojava.utils.ChangeForwarder; 041import org.biojava.utils.ChangeListener; 042import org.biojava.utils.ChangeSupport; 043import org.biojava.utils.ChangeType; 044import org.biojava.utils.ChangeVetoException; 045 046/** 047 * @author Matthew Pocock 048 * @author Thomas Down 049 * @author Samiul Hasan 050 * @author Keith James 051 */ 052public class SimpleMarkovModel 053 extends 054 AbstractChangeable 055 implements 056 MarkovModel, 057 Serializable { 058 public static final long serialVersionUID = -3043028839927615753l; 059 private final Alphabet emissionAlpha; 060 private final FiniteAlphabet stateAlpha; 061 private final MagicalState magicalState; 062 063 private final Map transFrom; 064 private final Map transTo; 065 private final Map transWeights; 066 private transient ChangeForwarder distForwarder; 067 068 { 069 transFrom = new HashMap(); 070 transTo = new HashMap(); 071 transWeights = new HashMap(); 072 } 073 074 protected ChangeSupport getChangeSupport(ChangeType ct) { 075 ChangeSupport changeSupport = super.getChangeSupport(ct); 076 077 if ( 078 (MarkovModel.PARAMETER.isMatchingType(ct) || 079 ct.isMatchingType(MarkovModel.PARAMETER)) && 080 distForwarder == null 081 ) { 082 distForwarder = new ChangeForwarder.Retyper( 083 this, changeSupport, MarkovModel.PARAMETER); 084 for (Iterator si = stateAlpha.iterator(); si.hasNext();) { 085 State s = (State) si.next(); 086 if (s instanceof EmissionState) { 087 EmissionState es = (EmissionState) s; 088 es.addChangeListener(distForwarder, EmissionState.DISTRIBUTION); 089 es.addChangeListener(ChangeListener.ALWAYS_VETO, EmissionState.ADVANCE); 090 } 091 } 092 } 093 094 return changeSupport; 095 } 096 097 public Alphabet emissionAlphabet() { 098 return emissionAlpha; 099 } 100 101 public FiniteAlphabet stateAlphabet() { 102 return stateAlpha; 103 } 104 105 public int heads() { 106 return magicalState().getAdvance().length; 107 } 108 109 public MagicalState magicalState() { 110 return magicalState; 111 } 112 113 public int[] advance() { 114 int[] advances = new int[heads()]; 115 116 for (int i = 0; i < advances.length; i++) { 117 advances[i] = 0; 118 } 119 120 for (Iterator si = stateAlphabet().iterator(); si.hasNext();) { 121 State s = (State) si.next(); 122 if (s instanceof EmissionState) { 123 int[] adv = ((EmissionState) s).getAdvance(); 124 for (int i = 0; i < advances.length; i++) { 125 advances[i] = Math.max(advances[i], adv[i]); 126 } 127 } 128 } 129 130 return advances; 131 } 132 133 public Distribution getWeights(State source) 134 throws IllegalSymbolException { 135 stateAlphabet().validate(source); 136 137 Distribution dist = (Distribution) transWeights.get(source); 138 if (dist == null) { 139 throw new BioError( 140 "Model does contain " + source.getName() + 141 " but the associated transition distribution is missing." 142 ); 143 } 144 return dist; 145 } 146 147 /** 148 * Use this methods to customize the transition probabilities. 149 * <p> 150 * By default, the distribution P(destination | source) is a totally free 151 * distribution. This allows the different probabilities to vary. If you 152 * wish to change this behaviour (for example, to make one set of transition 153 * probabilities equal to another), then use this method to replace the 154 * Distribution with one of your own. 155 * 156 * @param source source State 157 * @param dist the new Distribution over the transition probabilites from source 158 * @throws IllegalSymbolException if source is not a member of this model 159 * @throws IllegalAlphabetException if dist is not a distribution over the 160 * states returned by model.transitionsFrom(source) 161 * @throws ChangeVetoException if a listener vetoed this change 162 */ 163 public void setWeights(State source, Distribution dist) 164 throws IllegalSymbolException, IllegalAlphabetException, ChangeVetoException { 165 FiniteAlphabet ta = transitionsFrom(source); 166 if (!dist.getAlphabet().equals(ta)) { 167 throw new IllegalAlphabetException( 168 "Can't set distribution from state " + source.getName() + 169 " as the distribution alphabet is not the alphabet of transitions: " + 170 ta.getName() + " and " + dist.getAlphabet().getName() 171 ); 172 } 173 174 if (!hasListeners()) { 175 transWeights.put(source, dist); 176 } else { 177 ChangeSupport cs = getChangeSupport(MarkovModel.PARAMETER); 178 synchronized (cs) { 179 ChangeEvent ce = new ChangeEvent(this, 180 MarkovModel.PARAMETER, 181 dist, 182 transWeights.get(source)); 183 cs.firePreChangeEvent(ce); 184 transWeights.put(source, dist); 185 cs.firePostChangeEvent(ce); 186 } 187 } 188 } 189 190 public void createTransition(State from, State to) 191 throws IllegalSymbolException, ChangeVetoException { 192 stateAlphabet().validate(from); 193 stateAlphabet().validate(to); 194 195 ChangeEvent ce = new ChangeEvent( 196 this, MarkovModel.ARCHITECTURE, 197 new Object[]{from, to}, 198 null 199 ); 200 201 FiniteAlphabet f = transitionsFrom(from); 202 FiniteAlphabet t = transitionsTo(to); 203 204 if (f.contains(to)) { 205 throw new ChangeVetoException( 206 ce, 207 "Transition already exists: " + from.getName() + " -> " + to.getName() 208 ); 209 } 210 211 if (!hasListeners()) { 212 f.addSymbol(to); 213 t.addSymbol(from); 214 } else { 215 ChangeSupport changeSupport = getChangeSupport(MarkovModel.ARCHITECTURE); 216 synchronized (changeSupport) { 217 changeSupport.firePreChangeEvent(ce); 218 219 f.addSymbol(to); 220 t.addSymbol(from); 221 222 changeSupport.firePostChangeEvent(ce); 223 } 224 } 225 } 226 227 public void destroyTransition(State from, State to) 228 throws IllegalSymbolException, ChangeVetoException { 229 stateAlphabet().validate(from); 230 stateAlphabet().validate(to); 231 232 FiniteAlphabet f = transitionsFrom(from); 233 234 ChangeEvent ce = new ChangeEvent( 235 this, MarkovModel.ARCHITECTURE, 236 null, 237 new Object[]{from, to} 238 ); 239 240 if (!f.contains(to)) { 241 throw new ChangeVetoException( 242 ce, 243 "Transition does not exists: " + from.getName() + " -> " + to.getName() 244 ); 245 } 246 247 Distribution dist = getWeights(from); 248 double w = dist.getWeight(to); 249 if (w != 0.0 && !Double.isNaN(w)) { 250 throw new ChangeVetoException( 251 ce, 252 "Can't remove transition as its weight is not zero or NaN: " + 253 from.getName() + " -> " + to.getName() + " = " + w 254 ); 255 } 256 257 if (!hasListeners()) { 258 transitionsFrom(from).removeSymbol(to); 259 transitionsTo(to).removeSymbol(from); 260 } else { 261 ChangeSupport changeSupport = getChangeSupport(MarkovModel.ARCHITECTURE); 262 synchronized (changeSupport) { 263 changeSupport.firePreChangeEvent(ce); 264 265 transitionsFrom(from).removeSymbol(to); 266 transitionsTo(to).removeSymbol(from); 267 268 changeSupport.firePostChangeEvent(ce); 269 } 270 } 271 } 272 273 public boolean containsTransition(State from, State to) 274 throws IllegalSymbolException { 275 stateAlphabet().validate(to); 276 return transitionsFrom(from).contains(to); 277 } 278 279 public FiniteAlphabet transitionsFrom(State from) 280 throws IllegalSymbolException { 281 stateAlphabet().validate(from); 282 283 FiniteAlphabet s = (FiniteAlphabet) transFrom.get(from); 284 if (s == null) { 285 throw new BioError( 286 "State " + from.getName() + 287 " is known in states " + 288 stateAlphabet().getName() + 289 " but is not listed in the transFrom table" 290 ); 291 } 292 return s; 293 } 294 295 public FiniteAlphabet transitionsTo(State to) 296 throws IllegalSymbolException { 297 stateAlphabet().validate(to); 298 299 FiniteAlphabet s = (FiniteAlphabet) transTo.get(to); 300 if (s == null) { 301 throw new BioError( 302 "State " + to + 303 " is known in states " + 304 stateAlphabet().getName() + 305 " but is not listed in the transTo table" 306 ); 307 } 308 return s; 309 } 310 311 public void addState(State toAdd) 312 throws IllegalSymbolException, ChangeVetoException { 313 if (toAdd instanceof MagicalState && toAdd != magicalState) { 314 throw new IllegalSymbolException("Can not add a MagicalState"); 315 } 316 317 if (stateAlphabet().contains(toAdd)) { 318 throw new IllegalSymbolException("We already contain " + toAdd.getName()); 319 } 320 321 if (toAdd instanceof EmissionState) { 322 int esh = ((EmissionState) toAdd).getAdvance().length; 323 if (esh != heads()) { 324 throw new IllegalSymbolException( 325 "This model " + stateAlphabet().getName() + 326 " has " + heads() + " heads, but the state " + 327 toAdd.getName() + " has " + esh + " heads" 328 ); 329 } 330 } 331 332 if (toAdd instanceof ModelInState) { 333 int esh = ((ModelInState) toAdd).getModel().advance().length; 334 if (esh != heads()) { 335 throw new IllegalSymbolException( 336 "This model " + stateAlphabet().getName() + 337 " has " + heads() + " heads, but the model-in-state " + 338 toAdd.getName() + " has " + esh + " heads" 339 ); 340 } 341 } 342 343 if (!hasListeners()) { 344 doAddState(toAdd); 345 } else { 346 ChangeSupport changeSupport = getChangeSupport(MarkovModel.ARCHITECTURE); 347 synchronized (changeSupport) { 348 ChangeEvent ce = new ChangeEvent( 349 this, MarkovModel.ARCHITECTURE, 350 toAdd, 351 null 352 ); 353 changeSupport.firePreChangeEvent(ce); 354 if (toAdd instanceof EmissionState) { 355 EmissionState eState = (EmissionState) toAdd; 356 eState.addChangeListener(distForwarder, EmissionState.DISTRIBUTION); 357 eState.addChangeListener(ChangeListener.ALWAYS_VETO, EmissionState.ADVANCE); 358 } 359 doAddState(toAdd); 360 changeSupport.firePostChangeEvent(ce); 361 } 362 } 363 } 364 365 private void doAddState(State toAdd) 366 throws IllegalSymbolException, ChangeVetoException { 367 stateAlphabet().addSymbol(toAdd); 368 FiniteAlphabet fa = new SimpleAlphabet("Transitions from " + toAdd.getName()); 369 transFrom.put(toAdd, fa); 370 transTo.put(toAdd, new SimpleAlphabet("Transitions to " + toAdd.getName())); 371 transWeights.put(toAdd, new SimpleDistribution(fa)); 372 stateAlphabet().addSymbol(toAdd); 373 } 374 375 public void removeState(State toGo) 376 throws IllegalSymbolException, IllegalTransitionException, ChangeVetoException { 377 stateAlphabet().validate(toGo); 378 if (toGo instanceof MagicalState) { 379 throw new IllegalSymbolException("You can not remove the MagicalState"); 380 } 381 FiniteAlphabet t; 382 if ((t = transitionsFrom(toGo)).size() != 0) { 383 throw new IllegalTransitionException( 384 toGo, (State) t.iterator().next(), 385 "You can not remove a state untill all transitions to and from it " + 386 "have been destroyed" 387 ); 388 } 389 390 if ((t = transitionsTo(toGo)).size() != 0) { 391 throw new IllegalTransitionException( 392 (State) t.iterator().next(), toGo, 393 "You can not remove a state untill all transitions to and from it " + 394 "have been destroyed" 395 ); 396 } 397 398 if (!hasListeners()) { 399 doRemoveState(toGo); 400 } else { 401 ChangeSupport changeSupport = getChangeSupport(MarkovModel.ARCHITECTURE); 402 synchronized (changeSupport) { 403 ChangeEvent ce = new ChangeEvent( 404 this, MarkovModel.ARCHITECTURE, 405 null, 406 toGo 407 ); 408 changeSupport.firePreChangeEvent(ce); 409 if (toGo instanceof EmissionState) { 410 EmissionState eState = (EmissionState) toGo; 411 eState.removeChangeListener(distForwarder, EmissionState.DISTRIBUTION); 412 eState.removeChangeListener(ChangeListener.ALWAYS_VETO, EmissionState.ADVANCE); 413 } 414 doRemoveState(toGo); 415 changeSupport.firePostChangeEvent(ce); 416 } 417 } 418 } 419 420 private void doRemoveState(State toGo) 421 throws IllegalSymbolException, ChangeVetoException 422 { 423 stateAlphabet().removeSymbol(toGo); 424 transFrom.remove(toGo); 425 transTo.remove(toGo); 426 } 427 428 public SimpleMarkovModel(int heads, Alphabet emissionAlpha, String name) { 429 this(heads, emissionAlpha); 430 ((SimpleAlphabet) stateAlpha).setName(name); 431 } 432 433 /** 434 * @deprecated 435 */ 436 public SimpleMarkovModel(int heads, Alphabet emissionAlpha) { 437 this.emissionAlpha = emissionAlpha; 438 this.stateAlpha = new SimpleAlphabet(); 439 this.magicalState = MagicalState.getMagicalState(emissionAlpha, heads); 440 441 try { 442 addState(magicalState); 443 } catch (IllegalSymbolException ise) { 444 throw new BioError("Assertion failure: Couldn't add magical state", ise); 445 } catch (ChangeVetoException cve) { 446 throw new BioError("Assertion failure: Couldn't add magical state", cve); 447 } 448 } 449 450 public String toString() { 451 return this.getClass().getName() + ":" + stateAlpha.getName(); 452 } 453}