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}