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 */
021package org.biojava.bio.dist;
022
023import java.io.Serializable;
024import java.util.Iterator;
025import java.util.List;
026
027import org.biojava.bio.symbol.Alphabet;
028import org.biojava.bio.symbol.AlphabetManager;
029import org.biojava.bio.symbol.AtomicSymbol;
030import org.biojava.bio.symbol.BasisSymbol;
031import org.biojava.bio.symbol.FiniteAlphabet;
032import org.biojava.bio.symbol.IllegalAlphabetException;
033import org.biojava.bio.symbol.IllegalSymbolException;
034import org.biojava.bio.symbol.Symbol;
035import org.biojava.utils.ChangeForwarder;
036import org.biojava.utils.ChangeSupport;
037import org.biojava.utils.ChangeType;
038import org.biojava.utils.ChangeVetoException;
039
040/**
041 * Simple base class for OrderNDistributions.
042 *
043 * @author Samiul Hasan
044 * @author Matthew Pocock
045 * @author Thomas Down
046 * @since 1.2
047 */
048public abstract class AbstractOrderNDistribution extends AbstractDistribution
049    implements OrderNDistribution, Serializable
050{
051    static final long serialVersionUID = 1406135308618188893L;
052    
053  private Alphabet alphabet;
054  private Alphabet firstA;
055  private Alphabet lastA;
056  private Distribution nullModel;
057
058  /**
059   * The listener that will forward events from the underlying distributions to
060   * listeners for this distribution.
061   */
062  protected transient ChangeForwarder weightForwarder = null;
063  
064  protected ChangeSupport getChangeSupport(ChangeType ct) {
065    ChangeSupport changeSupport = super.getChangeSupport(ct);
066    
067    if(
068      ( (Distribution.WEIGHTS.isMatchingType(ct)) || (ct.isMatchingType(Distribution.WEIGHTS)) ) &&
069      weightForwarder == null
070    ) {
071      weightForwarder =
072              new ChangeForwarder.Retyper(this, changeSupport, Distribution.WEIGHTS);
073      for(Iterator i = conditionedDistributions().iterator(); i.hasNext(); ) {
074        Distribution dist = (Distribution) i.next();
075        dist.addChangeListener(weightForwarder, Distribution.WEIGHTS);
076      }
077    }
078    
079    return changeSupport;
080  }
081  
082    /**
083     * Construct a new NthOrderDistribution.
084     *
085     * @param alpha  the Alpahbet this is over
086     */
087
088  protected AbstractOrderNDistribution(Alphabet alpha)
089  throws IllegalAlphabetException  {
090    this.alphabet = alpha;
091    List aList = alpha.getAlphabets();
092    int lb1 = aList.size() - 1;
093    if(aList.size() == 2) {
094      this.firstA = (Alphabet) aList.get(0);
095    } else {
096      this.firstA = AlphabetManager.getCrossProductAlphabet(aList.subList(0, lb1));
097    }
098    this.lastA = (Alphabet) aList.get(lb1); 
099    this.nullModel = new UniformNullModel();
100  }
101  
102    /**
103     * Get the conditioning alphabet of this distribution.  If the `overall'
104     * alphabet is a cross-product of two alphabets, this will be the first 
105     * of those alphabets.  If it is a cross-product of more than two alphabets,
106     * the conditioning alphabet is the cross-product of all but the last
107     * alphabet.
108     *
109     * @return the conditioning Alphabet
110     */
111
112    public Alphabet getConditioningAlphabet() {
113        return firstA;
114    }
115
116    /**
117     * Get the conditioned alphabet.  This is the last alphabet in the
118     * distribution's overall cross-product.  It will be the alphabet of
119     * all the sub-distributions contained within this OrderNDistribution.
120     */
121
122    public Alphabet getConditionedAlphabet() {
123        return lastA;
124    }
125   
126  public Alphabet getAlphabet() {
127    return alphabet;
128  }
129  
130    /**
131     * Get a weight from one of the sub-distributions, conditioned
132     * on the first part of the symbol.
133     *
134     * @param sym  the symbol to look up
135     * @return the weight
136     * @throws IllegalSymbolException  if sym is not recognised
137     */
138
139  protected double getWeightImpl(AtomicSymbol sym) throws IllegalSymbolException {
140    List symL = sym.getSymbols();
141    int lb1 = symL.size() - 1;
142    BasisSymbol firstS;
143    if(symL.size() == 2) {
144      firstS = (AtomicSymbol) symL.get(0);
145    } else {
146      firstS = (AtomicSymbol) firstA.getSymbol(symL.subList(0, lb1));
147    }
148    Distribution dist = getDistribution(firstS);
149    return dist.getWeight((AtomicSymbol) symL.get(lb1));
150  }
151
152    /**
153     * Set a weight in one of the conditioned distributions.  It is the callers
154     * responsibility to ensure that all the conditioned distributions have total
155     * weights which sum to 1.0.
156     *
157     * @param sym   the symbol to set the weight for
158     * @param w     the new weight
159     */
160
161    public void setWeightImpl(AtomicSymbol sym, double w) 
162    throws IllegalSymbolException, ChangeVetoException {
163      List symL = sym.getSymbols();
164      int lb1 = symL.size() - 1;
165      Symbol firstS;
166      if(symL.size() == 2) {
167        firstS = (Symbol) symL.get(0);
168      } else {
169        firstS = firstA.getSymbol(symL.subList(0, lb1));
170      }
171      Distribution dist = getDistribution(firstS);
172      dist.setWeight((Symbol) symL.get(lb1), w);
173    }
174  
175  public void setNullModelImpl(Distribution nullModel) {
176        this.nullModel = nullModel;   
177  }
178  
179  public Distribution getNullModel()  {
180        return this.nullModel;
181  }
182  
183  public void registerWithTrainer(DistributionTrainerContext dtc) {
184    for(Iterator i = conditionedDistributions().iterator(); i.hasNext(); ) {
185      dtc.registerDistribution((Distribution) i.next());
186    }
187    dtc.registerTrainer(this, new IgnoreCountsTrainer() {
188      public void addCount(
189        DistributionTrainerContext dtc,
190        AtomicSymbol sym,
191        double count
192      ) throws IllegalSymbolException {
193        List symL = sym.getSymbols();
194        int lb1 = symL.size() - 1;
195        Symbol firstS;
196        if(lb1 == 1) {
197          firstS = (Symbol) symL.get(0);
198        } else {
199          firstS = firstA.getSymbol(symL.subList(0, lb1));
200        }
201        Distribution dist = getDistribution(firstS);
202        dtc.addCount(dist, (Symbol) symL.get(lb1), count);
203      }
204      
205      public double getCount(
206        DistributionTrainerContext dtc,
207        AtomicSymbol sym
208      ) throws IllegalSymbolException {
209        List symL = sym.getSymbols();
210        int lb1 = symL.size() - 1;
211        Symbol firstS;
212        if(lb1 == 1) {
213          firstS = (Symbol) symL.get(0);
214        } else {
215          firstS = firstA.getSymbol(symL.subList(0, lb1));
216        }
217        Distribution dist = getDistribution(firstS);
218        return dtc.getCount(dist, (AtomicSymbol) symL.get(lb1));
219      }
220    });
221  }
222  
223  private class UniformNullModel
224  extends AbstractDistribution implements Serializable 
225  {
226    private static final long serialVersionUID = -3357793043843515032L;
227    private Distribution nullModel = new UniformDistribution(
228      (FiniteAlphabet) lastA
229    );
230    
231    public Alphabet getAlphabet() {
232      return AbstractOrderNDistribution.this.getAlphabet();
233    }
234    
235    protected double getWeightImpl(AtomicSymbol sym)
236    throws IllegalSymbolException {
237      List symL = sym.getSymbols();
238      int lb1 = symL.size() - 1;
239      return nullModel.getWeight((AtomicSymbol) symL.get(lb1));
240    }
241    
242    protected void setWeightImpl(AtomicSymbol sym, double weight)
243    throws ChangeVetoException {
244      throw new ChangeVetoException(
245        "Can't change the weight of this null model"
246      );
247    }
248    
249    public Distribution getNullModel() {
250      return this;
251    }
252    
253    protected void setNullModelImpl(Distribution nullModel)
254    throws IllegalAlphabetException, ChangeVetoException {
255      throw new ChangeVetoException(
256        "Can't set the null model for NthOrderDistribution.UniformNullModel"
257      );
258    }
259  }
260}