001    package edu.nrao.sss.model.resource.evla;
002    
003    import java.util.ArrayList;
004    import java.util.List;
005    import java.util.Map;
006    import java.util.Set;
007    import java.util.SortedSet;
008    import java.util.TreeSet;
009    
010    import javax.xml.bind.annotation.XmlAttribute;
011    import javax.xml.bind.annotation.XmlElement;
012    import javax.xml.bind.annotation.XmlRootElement;
013    import javax.xml.bind.annotation.XmlTransient;
014    
015    import ca.nrc.widar.jaxb.vci.AutoCorrSubset;
016    import ca.nrc.widar.jaxb.vci.BlbPair;
017    import ca.nrc.widar.jaxb.vci.BlbSingle;
018    import ca.nrc.widar.jaxb.vci.CorrelationType;
019    import ca.nrc.widar.jaxb.vci.MaxMinPackType;
020    import ca.nrc.widar.jaxb.vci.PolProducts;
021    import ca.nrc.widar.jaxb.vci.Pp;
022    import ca.nrc.widar.jaxb.vci.ProductPacking;
023    import ca.nrc.widar.jaxb.vci.StationPacking;
024    
025    import static edu.nrao.sss.astronomy.StokesParameter.LR;
026    import static edu.nrao.sss.astronomy.StokesParameter.RL;
027    
028    import edu.nrao.sss.astronomy.PolarizationType;
029    import edu.nrao.sss.astronomy.StokesParameter;
030    import edu.nrao.sss.measure.TimeDuration;
031    import edu.nrao.sss.model.resource.CorrelationProductGroupAbs;
032    import edu.nrao.sss.model.resource.CorrelatorBaseband;
033    import edu.nrao.sss.model.resource.CorrelatorSubband;
034    import edu.nrao.sss.util.LookupTable;
035    
036    /**
037     * A group of WIDAR correlation products that share certain features.
038     * <p>
039     * The VCI element to which this class is mapped is
040     * {@link ca.nrc.widar.jaxb.vci.PolProducts}.</p>
041     * <p>
042     * <b>Version Info:</b>
043     * <table style="margin-left:2em">
044     *   <tr><td>$Revision: 2298 $</td></tr>
045     *   <tr><td>$Date: 2009-05-13 16:26:11 -0600 (Wed, 13 May 2009) $</td></tr>
046     *   <tr><td>$Author: dharland $ (last person to modify)</td></tr>
047     * </table></p>
048     * 
049     * @author David M. Harland
050     * @since 2008-11-19
051     */
052    @XmlRootElement(name="group")
053    public class WidarCorrelationProductGroup
054      extends CorrelationProductGroupAbs
055    {
056      private WidarIntegrationTime integrationTime;
057      private int                  recircFactor;
058      
059      //Cached so that we don't always have to navigate the containment
060      //hierarchy, but not persisted.  The only method that should use
061      //this variable directly is getBlbpPool().
062      transient private BlbpPool blbpPool;
063      
064      /**
065       * Creates a new correlation product group for the given WIDAR subband.
066       * 
067       * @param container
068       *   the subband that contains this correlation product group.
069       *   
070       * @throws IllegalArgumentException
071       *   if {@code container} is <i>null</i>.
072       */
073      WidarCorrelationProductGroup(WidarSubband container)
074      {
075        super();
076        
077        if (container == null)
078          throw new IllegalArgumentException(
079            "WidarCorrelationProductGroup may not be created for NULL subband.");
080        
081        init(container);
082      }
083      
084      @SuppressWarnings("unused")  //Used by frameworks such as jaxb & hibernate
085      private WidarCorrelationProductGroup()
086      {
087        super();
088        init(null);
089      }
090      
091      private void init(WidarSubband container)
092      {
093        setContainer(container);
094        
095        integrationTime = new WidarIntegrationTime(this);
096        recircFactor    = 1;
097        
098        setVciDefaults();
099      }
100      
101      WidarSubband getSubband()
102      {
103        return (WidarSubband)getContainer();
104      }
105    
106      void setSubband(WidarSubband container)
107      {
108        setContainer(container);
109      }
110    
111      //============================================================================
112      // RECIRCULATION
113      //============================================================================
114    
115      private static final LookupTable<Integer, Integer> VALID_RF_7_BIT_RQ =
116        new LookupTable<Integer, Integer>();
117      static
118      {
119        for (int i=1; i <= 128; i*=2)
120          VALID_RF_7_BIT_RQ.put(i, i);
121      }
122      private static final LookupTable<Integer, Integer> VALID_RF_4_BIT_RQ =
123        new LookupTable<Integer, Integer>();
124      static
125      {
126        for (int i=1; i <= 256; i*=2)
127          VALID_RF_4_BIT_RQ.put(i, i);
128      }
129    
130      /**
131       * Returns the largest allowable recirculation factor for this group of
132       * correlation products.
133       * The returned value depends on the requantization and bandwidth of the
134       * containing subband.
135       * 
136       * @return
137       *   the largest allowable recirculation factor for this group of
138       *   correlation products.
139       */
140      public int getMaximumRecirculationFactor()
141      {
142        int normalMax  = getRecircFactorTable().get(Integer.MAX_VALUE);
143        int specialMax = normalMax;
144    
145        CorrelatorSubband sb = getContainer();
146        
147        if (sb != null)
148          specialMax = sb.getMaximumBandwidth().dividedBy(sb.getBandwidth()).intValue();
149        
150        return Math.min(normalMax, Math.max(1, specialMax));
151      }
152    
153      /**
154       * Returns the collection of valid recirculation factor values for
155       * this group of correlation products.  The values in this collection
156       * depend on the requantization and bandwidth of the containing subband.
157       * 
158       * @return
159       *   the valid recirculation factors for this group of
160       *   correlation products.
161       */
162      public SortedSet<Integer> getAllowableRecirculationFactors()
163      {
164        SortedSet<Integer> factors = new TreeSet<Integer>();
165    
166        int maxRF = getMaximumRecirculationFactor();
167    
168        LookupTable<Integer, Integer> factorTable = getRecircFactorTable();
169    
170        for (int rf : factorTable.getKeySet())
171        {
172          if (rf <= maxRF)  factors.add(rf);
173          else              break;
174        }
175    
176        return factors;
177      }
178    
179      /**
180       * Sets the recirculation factor for this group of correlation products.
181       * A value of <tt>1</tt> (one) represents <i>no recirculation</i>.
182       * <p>
183       * Recirculation is a means of producing more spectral channels
184       * per polarization product.</p>
185       * 
186       * @param newFactor
187       *   the new recirculation factor for this group of correlation products.
188       *   The minimum value is <tt>1</tt> and the maximum value is
189       *   give by {@link #getMaximumRecirculationFactor()}.
190       *   All valid values in between the limits are integral powers
191       *   of two.  If {@code newFactor} is not a valid value, this
192       *   method will set the recirculation factor to the largest valid
193       *   value that is less than {@code newFactor} (except for the case
194       *   where {@code newFactor} is less than the minimum, in which case
195       *   the minimum valid value will be used).
196       */
197      @XmlAttribute(required=false)
198      public void setRecirculationFactor(int newFactor)
199      {
200        int recirculation = 1;
201    
202        if (newFactor > 1)
203        {
204          //We don't rely solely on table for max because there is a specialMax
205          //calc that can override the table values.
206          int max = getMaximumRecirculationFactor();
207          
208          recirculation =
209            (newFactor >= max) ? max : getRecircFactorTable().get(newFactor);
210        }
211        
212        if (recircFactor != recirculation)
213        {
214          recircFactor = recirculation;
215          needToRecalcChannels = true;
216        }
217      }
218    
219      /**
220       * Returns the recirculation factor for this group of correlation products.
221       * @return the recirculation factor for this group of correlation products.
222       * @see #setRecirculationFactor(int)
223       */
224      public int getRecirculationFactor()
225      {
226        return recircFactor;
227      }
228    
229      private LookupTable<Integer, Integer> getRecircFactorTable()
230      {
231        CorrelatorSubband sb = getContainer();
232        
233        int rq = (sb == null) ? 4 : sb.getRequantization();
234        
235        return rq == 4 ? VALID_RF_4_BIT_RQ : VALID_RF_7_BIT_RQ;
236      }
237    
238      //============================================================================
239      // INTEGRATION TIME
240      //============================================================================
241      
242      /**
243       * Sets the total integration time for the products of this group.
244       * <p>
245       * Note that this object will not hold a reference to
246       * <tt>totalDuration</tt>, so clients may continue to use it without
247       * affecting this object.</p>
248       * 
249       * @param totalDuration
250       *   the total integration duration for the products of this group.
251       */
252      @XmlTransient
253      public void setTotalIntegrationTime(TimeDuration totalDuration)
254      {
255        integrationTime.setTotalIntegrationTime(totalDuration);
256      }
257      
258      /**
259       * Returns the total integration time for the products of this group.
260       * <p>
261       * The returned duration is not referenced internally, so clients may
262       * alter it without affecting this object.</p>
263       * 
264       * @return the total integration time for the products of this group.
265       */
266      public TimeDuration getTotalIntegrationTime()
267      {
268        return integrationTime.getTotalIntegrationTime();
269      }
270      
271      /**
272       * Returns the minimum total integration time for this product.
273       * <p>
274       * The returned duration is not referenced internally, so clients may
275       * alter it without affecting this object.</p>
276       * 
277       * @return the minimum total integration time for this product.
278       */
279      public TimeDuration getMinimumTotalIntegrationTime()
280      {
281        return integrationTime.getSmallestMinimumHardwareIntegrationTime();
282      }
283      
284      /**
285       * Returns the integration time for this group.
286       * This method can be used when a client wants fine grained control
287       * over the components of the total integration time.  Many clients
288       * will prefer to use the convenience methods
289       * <ul>
290       *   <li>{@link #getTotalIntegrationTime()}</li>
291       *   <li>{@link #setTotalIntegrationTime(TimeDuration)}</li>
292       *   <li>{@link #getMinimumTotalIntegrationTime()}</li>
293       * </ul>
294       * <p>
295       * The object returned is the one held internally by this group,
296       * so changes made to it <i>will</i> be reflected herein.</p>
297       * 
298       * @return
299       *   the integration time object held internally by this group.
300       */
301      public WidarIntegrationTime getIntegrationTime()
302      {
303        return integrationTime;
304      }
305      
306      @XmlElement
307      @SuppressWarnings("unused") //For JAXB & Hibernate
308      private void setIntegrationTime(WidarIntegrationTime newTime)
309      {
310        if (newTime != null)
311          integrationTime = newTime;
312      }
313      
314      //============================================================================
315      // CORRELATOR RESOURCES (BLBPs)
316      //============================================================================
317      
318      //Fetches and caches the BLBP pool
319      private BlbpPool getBlbpPool()
320      {
321        WidarSubband sb = getSubband();
322        
323        if (blbpPool == null && sb != null)
324        {
325          WidarBaseband bb = sb.getBaseband();
326          
327          if (bb != null)
328          {
329            EvlaWidarConfiguration bbContainer = bb.getEvlaWidarConfiguration();
330            
331            if (bbContainer != null)
332              blbpPool = bbContainer.getBaselineBoardPool();
333          }
334        }
335        
336        return blbpPool;
337      }
338      
339      /**
340       * Returns a list of the number of BLBPs that may be owned by this group.
341       * Often the returned list will contain the integers zero through the number
342       * of unowned BLBPs.  However, depending on the properties of this group,
343       * not all integral values in that range may be valid, and the maximum number
344       * might be something less than the total available.
345       * <p>
346       * The returned list is sorted from lowest to highest.</p>
347       *   
348       * @return
349       *   a list containing the valid numbers of BLBPs for the this group.
350       */
351      public List<Integer> getValidBlbpCounts()
352      {
353        List<Integer> valid;
354        
355        BlbpPool pool = getBlbpPool();
356        
357        if (pool == null)
358        {
359          valid = new ArrayList<Integer>();
360          valid.add(0);
361        }
362        else
363        {
364          valid = pool.getValidBlbpCounts(this);
365        }
366        
367        return valid; 
368      }
369      
370      /**
371       * Attempts to allocate the given number of BLBPs to this group.
372       * Note that <tt>newTotalCount</tt> is the <i>total</i> number of BLBPs
373       * to be allocated to this group, not an <i>additional</i> number.
374       * 
375       * @param newTotalCount
376       *   the desired number of baseline board pairs to assign to this group.
377       *   
378       * @return
379       *   the total number of BLBPs actually allocated to this group.
380       *   This number will be less than or equal to <tt>newTotalCount</tt>,
381       *   unless <tt>newCount</tt> is less than zero, in which case the
382       *   returned value will be zero.
383       */
384      public int setBlbpCount(int newTotalCount)
385      {
386        int actualNewTotalCount = 0;
387        
388        BlbpPool pool = getBlbpPool();
389        
390        if (pool != null)
391        {
392          int oldTotalCount = pool.getBlbpsOwnedBy(this);
393          
394          if (newTotalCount > oldTotalCount)
395          {
396            int added =
397              pool.allocateAdditionalPairsTo(this, newTotalCount-oldTotalCount);
398            
399            actualNewTotalCount = oldTotalCount + added;
400          }
401          else if (newTotalCount < oldTotalCount)
402          {
403            int removed =
404              pool.reclaimPairsFrom(this, oldTotalCount-newTotalCount);
405            
406            actualNewTotalCount = oldTotalCount - removed;
407          }
408          else //old == new
409            actualNewTotalCount = oldTotalCount;
410          
411          if (actualNewTotalCount != oldTotalCount)
412            needToRecalcChannels = true;
413        }
414        
415        return actualNewTotalCount;
416      }
417      
418      /**
419       * Returns the current number of baseline board pairs allocated to this group.
420       * Note that it is possible for the returned value to be an illegal value.
421       * This can happen because the underlying pool of BLBPs may not be aware
422       * of recent changes to this group or its containing subband and baseband.
423       * Use {@link #fixBlbpCount()} to simultaneously adjust the allocated number
424       * to a legal value and obtain that number.
425       * 
426       * @return
427       *   the current number of BLBPs allocated to this group.
428       */
429      @XmlTransient //The BlbpPool will be persisted elsewhere and hold these allocations
430      public int getBlbpCount()
431      {
432        BlbpPool pool = getBlbpPool();
433        
434        return pool == null ? 0 : pool.getBlbpsOwnedBy(this);
435      }
436      
437      /**
438       * Adjusts, if necessary, the number of BLBPs allocated to this group
439       * and returns the new value.
440       * 
441       * @return
442       *   the number of BLBPs allocated to this group after ensuring the
443       *   current number was legal for this group.
444       */
445      public int fixBlbpCount()
446      {
447        return setBlbpCount(getBlbpCount());
448      }
449      
450      //============================================================================
451      // CORRELATION PRODUCTS
452      //============================================================================
453    
454      //TODO Think about what will happen in the case where someone set up 4 products
455      //     but then later split the containing baseband into singlets.  We need
456      //     to ensure we don't have impossible products (eg, RxL for a BB w/ only R)
457      
458      private static final int CHANNELS_PER_BLBP_BUNCH   = 256;
459      private static final int SUBBAND_CHANNEL_MAX       =  64 * 1024;
460      private static final int SUBBAND_CHANNEL_SUPER_MAX = 256 * 1024;
461      
462      @Override
463      protected void recalculateChannels()
464      {
465        BlbpPool pool = getBlbpPool();
466    
467        //Make sure we have only legal correlations in channels map
468        removeDisallowedProducts();
469        
470        int productCount = channels.size();
471        
472        //Do the calculations only if we have polarization products to
473        //which we can distribute the channels.
474        if (productCount > 0 && pool != null)
475        {
476          //Make sure our # of BLBPs is valid
477          int blbps = fixBlbpCount();
478        
479          //Make sure recirculation and bandwidth are compatible.
480          //Keep bandwidth and adjust recirc if necessary.
481          setRecirculationFactor(recircFactor);
482        
483          //Atomic unit of BLBPs (1 or 4).  Integer divide.
484          int blbpBunches = blbps / pool.getBlbpIncrement(this);
485    
486          //Total number of channels for this group, to be spread over products
487          int totalChannels = blbpBunches * CHANNELS_PER_BLBP_BUNCH * recircFactor;
488        
489          //Make sure we didn't violate maximum
490          //TODO need to refresh knowledge re: max channels for 7-bit RQ
491          int max = (productCount > blbpBunches) ? SUBBAND_CHANNEL_MAX
492                                                 : SUBBAND_CHANNEL_SUPER_MAX;
493          if (totalChannels > max)
494            totalChannels = max;
495          
496          //Spread channels over existing products.
497          //For 1, 2, or 4 products, spread evenly.
498          if (productCount == 1 || productCount == 2 || productCount == 4)
499          {
500            //Since we already know totalChannels is a multiple of 256,
501            //this division by 1, 2, or 4 will produce an integer. 
502            int channelsPerProduct = totalChannels / productCount;
503            
504            for (StokesParameter sp : channels.keySet())
505              channels.put(sp, channelsPerProduct);
506          }
507          //For 3 products divide in ratio 1:1:2, where the products receiving
508          //fewer channels are either both parallel or both cross products.
509          else if (productCount == 3)
510          {
511            int chX1 = totalChannels / 4;
512            int chX2 = 2 * chX1;
513            
514            Set<StokesParameter> products = channels.keySet();
515            
516            boolean chX2ForParallel = products.contains(RL) && products.contains(LR);
517            
518            for (StokesParameter sp : channels.keySet())
519            {
520              switch (sp)
521              {
522                case LR: //intentional fall-through
523                case RL:
524                  channels.put(sp, chX2ForParallel ? chX1 : chX2);
525                  break;
526    
527                case LL: //intentional fall-through
528                case RR:
529                  channels.put(sp, chX2ForParallel ? chX2 : chX1);
530                  break;
531                  
532                default:
533                  throw new RuntimeException("PROGRAMMER ERROR: Unexpected polarization product "+sp);
534              }
535            }
536          }
537        }
538        
539        //Even if we did not do the calc, we clear this flag
540        needToRecalcChannels = false;
541      }
542      
543      //============================================================================
544      // VCI ELEMENTS
545      //============================================================================
546    
547      //VCI element PolProducts
548      private AutoCorrSubset  autoCorrSubset;
549      private List<BlbPair>   blbPair;
550      //                      blbProdIntegration - part of this class already
551      private List<BlbSingle> blbSingle;
552      //                      pp                 - part of this class already
553      private ProductPacking  productPacking;
554      private StationPacking  stationPacking;
555      
556      
557      //TODO need to handle blbPair and blbSingle differently.
558      //     Need to derive from getBlbpCount.
559      
560      
561      private void setVciDefaults()
562      {
563        //VCI element PolProducts
564        overrideAutoCorr = false;
565        autoCorrSubset   = null;
566        autoCorrCache    = VciJaxbUtil.makeAutoCorrSubset();
567        
568        blbPair          = new ArrayList<BlbPair>();
569        blbSingle        = new ArrayList<BlbSingle>();
570        productPacking   = new ProductPacking();
571        stationPacking   = new StationPacking();
572        
573        productPacking.setAlgorithm(MaxMinPackType.MAX_PACK);
574        stationPacking.setAlgorithm(MaxMinPackType.MAX_PACK);
575      }
576      
577      /**
578       * Returns a Virtual Correlator Interface (VCI) representation of this object.
579       * 
580       * @return a VCI representation of this object.
581       */
582      public PolProducts toVci()
583      {
584        PolProducts vciObj = new PolProducts();
585    
586        //Modeled as VCI elements
587        vciObj.setAutoCorrSubset(VciJaxbUtil.clone(autoCorrSubset));
588        vciObj.setProductPacking(VciJaxbUtil.clone(productPacking));
589        vciObj.setStationPacking(VciJaxbUtil.clone(stationPacking));
590    
591        VciJaxbUtil.copyBlbPairs  (blbPair,   vciObj.getBlbPair());
592        VciJaxbUtil.copyBlbSingles(blbSingle, vciObj.getBlbSingle());
593    
594        //Mapped from SSS objects
595        vciObj.setBlbProdIntegration(integrationTime.toVci());
596    
597        int p = 0;
598        List<Pp> vciPps = vciObj.getPp();
599        vciPps.clear();
600        for (Map.Entry<StokesParameter, Integer> corrProd : channels.entrySet())
601        {
602          Pp vciPp = new Pp();
603    
604          //TODO below gives unique w/in group; need unique w/in subband?
605          vciPp.setId(++p);
606          vciPp.setCorrelation(getVciCorrType(corrProd.getKey()));
607          vciPp.setSpectralChannels(corrProd.getValue());
608    
609          vciPps.add(vciPp);
610        }
611        
612        return vciObj;
613      }
614      
615      //----------------------------------------------------------------------------
616      // Direct access to some VCI elements
617      //----------------------------------------------------------------------------
618      
619      private boolean        overrideAutoCorr;
620      private AutoCorrSubset autoCorrCache;
621    
622      /**
623       * Tells this group to override, or restore, auto correlation subset defaults.
624       * 
625       * @param override
626       *   instruction to override, or restore, auto correlation subset defaults.
627       *   
628       * @return
629       *   <i>null</i> if {@code override} is <i>false</i>.
630       *   If {@code override} is <i>true</i>, returns the <tt>AutoCorrSubset</tt>
631       *   held internally by this group.  This means clients can operate
632       *   directly on the returned object and impact this group.
633       */
634      public AutoCorrSubset overrideDefaultAutoCorrSubset(boolean override)
635      {
636        //Changing state?
637        if (override != overrideAutoCorr)
638        {
639          overrideAutoCorr = override;
640          
641          if (override) //user-specified
642          {
643            autoCorrSubset = autoCorrCache;
644          }
645          else //default setting
646          {
647            autoCorrCache  = VciJaxbUtil.clone(autoCorrSubset);
648            autoCorrSubset = null;
649          }
650        }
651        
652        return autoCorrSubset;
653      }
654    
655      /**
656       * Returns the auto correlation configuration, if any.
657       * If this group is not using this feature, <i>null</i> is returned.
658       * <p>
659       * This is a VCI property.
660       * The returned object was generated from VCI XML schema elements and
661       * is the actual instance held internally by this baseband.</p>
662       * 
663       * @return the auto correlation configuration, if any.
664       * 
665       * @see #overrideDefaultAutoCorrSubset(boolean)
666       */
667      @XmlElement(namespace="http://www.nrc.ca/namespaces/widar")
668      public AutoCorrSubset getAutoCorrSubset()  { return autoCorrSubset; }
669    
670      //This method is for persistence frameworks such as JAXB & Hibernate
671      @SuppressWarnings("unused")
672      private void setAutoCorrSubset(AutoCorrSubset newSubset)
673      {
674        autoCorrSubset = newSubset;
675        overrideAutoCorr = (newSubset != null);
676      }
677      
678      @XmlElement(namespace="http://www.nrc.ca/namespaces/widar")
679      public void setProductPacking(ProductPacking newPacking)
680      {
681        if (newPacking != null)
682          productPacking = newPacking;
683      }
684      public ProductPacking getProductPacking()  { return productPacking; }
685      
686      @XmlTransient
687      public void setProductPackingAlgorithm(MaxMinPackType newAlg)
688      {
689        if (newAlg != null)
690          productPacking.setAlgorithm(newAlg);
691      }
692      public MaxMinPackType getProductPackingAlgorithm()
693      {
694        return productPacking.getAlgorithm();
695      }
696      
697      @XmlElement(namespace="http://www.nrc.ca/namespaces/widar")
698      public void setStationPacking(StationPacking newPacking)
699      {
700        if (newPacking != null)
701          stationPacking = newPacking;
702      }
703      public StationPacking getStationPacking()  { return stationPacking; }
704      
705      @XmlTransient
706      public void setStationPackingAlgorithm(MaxMinPackType newAlg)
707      {
708        if (newAlg != null)
709          stationPacking.setAlgorithm(newAlg);
710      }
711      public MaxMinPackType getStationPackingAlgorithm()
712      {
713        return stationPacking.getAlgorithm();
714      }
715      
716      /** Returns the VCI version of correlation product type. */
717      private CorrelationType getVciCorrType(StokesParameter stokes)
718      {
719        CorrelatorBaseband bb = null;
720        
721        WidarSubband sb = getSubband();
722        if (sb != null)
723          bb = sb.getBaseband();
724        
725        boolean aIsR = true;
726        
727        //We allow construction of subbands outside of basebands, so it is possible
728        //for bb to be null.  In common usage, though, this will not be the case.
729        if (bb != null)
730        {
731          List<PolarizationType> polarizations = bb.getPolarizations();
732        
733          PolarizationType ptA = polarizations.get(0);
734          PolarizationType ptB = polarizations.size() < 2 ? null : polarizations.get(1);
735    
736          aIsR = ptA.equals(PolarizationType.R);
737          
738          if (ptB != null && !ptB.getOpposite().equals(ptA))
739            throw new IllegalStateException("Expected ptA (" + ptA + ") and ptB ("+ ptB +
740                                            ") to be opposite polarizations.");
741        }
742        
743        switch (stokes)
744        {
745          case RR:  return aIsR ? CorrelationType.A_A : CorrelationType.B_B;
746          case RL:  return aIsR ? CorrelationType.A_B : CorrelationType.B_A;
747          case LR:  return aIsR ? CorrelationType.B_A : CorrelationType.A_B;
748          case LL:  return aIsR ? CorrelationType.B_B : CorrelationType.A_A;
749    
750          default:
751            throw new RuntimeException(
752              "PROGRAMMER ERROR: Unexpected StokesParameter found = "+ stokes);
753        }
754      }
755    
756      //============================================================================
757      // 
758      //============================================================================
759    
760      @Override
761      protected void copyInto(CorrelationProductGroupAbs other)
762      {
763        if (other instanceof WidarCorrelationProductGroup)
764          copyInto((WidarCorrelationProductGroup)other);
765      }
766      
767      private void copyInto(WidarCorrelationProductGroup other)
768      {
769        super.copyInto(other);
770        
771        other.integrationTime = this.integrationTime.makeCopyFor(other);
772        other.recircFactor    = this.recircFactor;
773    
774        other.overrideAutoCorr = this.overrideAutoCorr;
775        other.autoCorrSubset   = VciJaxbUtil.clone(this.autoCorrSubset);
776        other.autoCorrCache    = VciJaxbUtil.clone(this.autoCorrCache);
777    
778        other.productPacking = VciJaxbUtil.clone(this.productPacking);
779        other.stationPacking = VciJaxbUtil.clone(this.stationPacking);
780        
781        other.blbPair.clear();
782        for (BlbPair blbp : this.blbPair)
783          other.blbPair.add(VciJaxbUtil.clone(blbp));
784        
785        other.blbSingle.clear();
786        for (BlbSingle blb : this.blbSingle)
787          other.blbSingle.add(VciJaxbUtil.clone(blb));
788      }
789    
790      /**
791       * Creates a copy of this product group.
792       * The new copy is identical to this one, except:
793       * <ol>
794       *   <li>Its subband is {@code otherSubband}.</li>
795       *   <li>Its ID has been cleared.</li>
796       * </ol>
797       * Note that this method does <i>not</i> tell {@code otherSubband} about
798       * the new group.  The link forged in this method is one-way; clients
799       * are responsible for the link in the other direction.
800       */
801      WidarCorrelationProductGroup makeCopyFor(WidarSubband otherSubband)
802      {
803        WidarCorrelationProductGroup newGroup =
804          new WidarCorrelationProductGroup(otherSubband);
805        
806        this.copyInto(newGroup);
807        
808        return newGroup;
809      }
810      
811      @Override
812      public boolean equals(Object o)
813      {
814        //Quick exit if o is this object
815        if (o == this)
816          return true;
817        
818        //Not equal if super class says not equal
819        if (!super.equals(o))
820          return false;
821        
822        //Super class tested for Class equality, so cast is safe
823        WidarCorrelationProductGroup other = (WidarCorrelationProductGroup)o;
824        
825        //Intentionally NOT comparing: subband, blbpPool
826        
827        //SSS properties
828        if (other.recircFactor != this.recircFactor)
829          return false;
830        
831        if (!other.integrationTime.equals(this.integrationTime))
832          return false;
833        
834        //VCI properties
835        if (other.overrideAutoCorr != this.overrideAutoCorr)
836          return false;
837    
838        if (overrideAutoCorr &&
839            !VciJaxbUtil.testEquality(other.autoCorrSubset, this.autoCorrSubset))
840          return false;
841        
842        if (!VciJaxbUtil.testEquality(other.productPacking, this.productPacking) ||
843            !VciJaxbUtil.testEquality(other.stationPacking, this.stationPacking))
844          return false;
845    
846        return true;
847      }
848    
849      @Override
850      public int hashCode()
851      {
852        //Taken from the Effective Java book by Joshua Bloch.
853        //The constant 37 is arbitrary & carries no meaning.
854        int result = 37 * super.hashCode();
855        
856        //SSS properties
857        result = 37 * result + recircFactor;
858        result = 37 * result + integrationTime.hashCode();
859    
860        //VCI properties
861        if (overrideAutoCorr)
862          result = 37 * result + VciJaxbUtil.makeHashCode(autoCorrSubset);
863      
864        result = 37 * result + VciJaxbUtil.makeHashCode(productPacking);
865        result = 37 * result + VciJaxbUtil.makeHashCode(stationPacking);
866    
867        return result;
868      }
869    
870      //============================================================================
871      // 
872      //============================================================================
873      /*
874      public static void main(String... args) throws Exception
875      {
876      }
877      */
878    }