001    package edu.nrao.sss.model.resource.vla;
002    
003    import java.awt.event.ActionEvent;
004    import java.math.BigDecimal;
005    import java.math.MathContext;
006    import java.util.ArrayList;
007    import java.util.Collection;
008    import java.util.Date;
009    import java.util.HashSet;
010    import java.util.List;
011    import java.util.Set;
012    
013    import javax.xml.bind.annotation.XmlAccessType;
014    import javax.xml.bind.annotation.XmlAccessorType;
015    import javax.xml.bind.annotation.XmlList;
016    import javax.xml.bind.annotation.XmlRootElement;
017    
018    import static edu.nrao.sss.measure.FrequencyUnits.MEGAHERTZ;
019    import static edu.nrao.sss.model.resource.vla.VlaConfigurationValidator.MIN_AC_LOW_FREQ;
020    
021    import edu.nrao.sss.astronomy.CoordinateConversionException;
022    import edu.nrao.sss.astronomy.DopplerTracker;
023    import edu.nrao.sss.electronics.DigitalSignal;
024    import edu.nrao.sss.measure.Frequency;
025    import edu.nrao.sss.measure.FrequencyRange;
026    import edu.nrao.sss.measure.FrequencyUnits;
027    import edu.nrao.sss.model.resource.AntennaElectronics;
028    import edu.nrao.sss.model.resource.CorrelatorConfiguration;
029    import edu.nrao.sss.model.resource.CorrelatorName;
030    import edu.nrao.sss.model.resource.ResourceSpecification;
031    import edu.nrao.sss.model.resource.ReceiverBand;
032    import edu.nrao.sss.model.resource.TelescopeType;
033    import edu.nrao.sss.model.source.Source;
034    import edu.nrao.sss.util.StringUtil;
035    
036    /**
037     * The configuration of the VLA correlator.
038     * <p>
039     * This class was created without a lot of care.  It is meant to be used
040     * for a short period of time, primarily to support Ka observing on the
041     * "interim" system.</p>
042     * <p>
043     * <b>UPDATE</b>: the code in here has become progressively worse.
044     * It is a dangerous class to use unless you have insider knowledge.
045     * This class is planned for destruction in 2010.</p>
046     * <p>
047     * <b>Another UPDATE</b>: Yup, this is the worst crap i've written in
048     * a decade.  Recently tried to effect a simple change in behavior, and
049     * found what appeared to be the right place to change.  Took a few attempts
050     * before doing it right.  This class cannot be killed soon enough.</p>
051     * <p>
052     * <b>Version Info:</b>
053     * <table style="margin-left:2em">
054     *   <tr><td>$Revision: 2271 $</td></tr>
055     *   <tr><td>$Date: 2009-04-28 12:02:17 -0600 (Tue, 28 Apr 2009) $</td></tr>
056     *   <tr><td>$Author: dharland $ (last person to modify)</td></tr>
057     * </table></p>
058     * 
059     * @author David M. Harland
060     * @since 2008-03-14
061     */
062    @XmlRootElement
063    @XmlAccessorType(XmlAccessType.FIELD)
064    public class VlaConfiguration
065      extends CorrelatorConfiguration
066    {
067      private static final StringUtil sUtil = StringUtil.getInstance();
068    
069      //============================================================================
070      // NEWER MODEL
071      //============================================================================
072      
073      public VlaConfiguration(AntennaElectronics signalSrc)
074      {
075        super(signalSrc);
076        initOldStyleVars();
077      }
078      
079      //This is here only for persistence mechanisms
080      @SuppressWarnings("unused")
081      private VlaConfiguration()
082      {
083        initOldStyleVars();
084      }
085    
086      public CorrelatorName getName()  { return CorrelatorName.VLA; }
087    
088      /* (non-Javadoc)
089       * @see CorrelatorConfiguration#makeBasebandFrom(DigitalSignal)
090       */
091      protected VlaBasebandPair makeBasebandFrom(DigitalSignal ds)
092      {
093        return null;
094      }
095    
096      /* (non-Javadoc)
097       * @see CorrelatorConfiguration#makeBasebandFrom(DigitalSignal)
098       */
099      protected VlaBasebandPair makeBasebandFrom(DigitalSignal ds1,
100                                                 DigitalSignal ds2)
101      {
102        VlaBasebandPair newBbp =
103          (ds1 == null || ds2 == null) ? null : new VlaBasebandPair(ds1, ds2);
104        
105        if (newBbp != null)
106          newBbp.setContainer(this);
107        
108        return newBbp;
109      }
110      
111      //This is called when the antenna electronics' execute method is called.
112      public void actionPerformed(ActionEvent event)
113      {
114        //TODO code me
115      }
116      
117      /**
118       * <i>This method is not yet supported.</i>
119       */
120      public void configureFrom(ResourceSpecification scienceView)
121      {
122        throw new UnsupportedOperationException("not yet programmed"); //TODO code me
123      }
124    
125      //============================================================================
126      // COMPATIBILITY WITH OLDER MODEL
127      //============================================================================
128      
129      private Frequency      centralFrequencyAC;
130      private Frequency      centralFrequencyBD;
131    
132      private boolean        centralFrequencyACIsSkyFrequency;
133      private boolean        centralFrequencyBDIsSkyFrequency;
134      
135      private BigDecimal     integrationTimeInSeconds;
136    
137      private CorrelatorMode correlatorMode;
138    
139      private BandwidthCode  bwCodeIfA;
140      private BandwidthCode  bwCodeIfB;
141      private BandwidthCode  bwCodeIfC;
142      private BandwidthCode  bwCodeIfD;
143    
144      @XmlList
145      private Set<ProcessingType> spectralProcessing = new HashSet<ProcessingType>();
146    
147      private void initOldStyleVars()
148      {
149        AntennaElectronics src = getSignalSource();
150        if (src != null)
151        {
152          Set<ReceiverBand> bands = src.getActiveReceivers();
153    
154          if (!bands.isEmpty())
155          {
156            Frequency center = bands.iterator().next().getNominalRange().getCenterFrequency();
157            centralFrequencyAC = center;
158          }
159        }
160    
161        if (centralFrequencyAC == null)
162          centralFrequencyAC = new Frequency();
163    
164        centralFrequencyBD = centralFrequencyAC.clone();
165    
166        centralFrequencyACIsSkyFrequency = true;
167        centralFrequencyBDIsSkyFrequency = true;
168    
169        correlatorMode = CorrelatorMode.CONTINUUM;
170    
171        bwCodeIfA = BandwidthCode.ZERO;
172        bwCodeIfB = BandwidthCode.ZERO;
173        bwCodeIfC = BandwidthCode.ZERO;
174        bwCodeIfD = BandwidthCode.ZERO;
175    
176        spectralProcessing = new HashSet<ProcessingType>();
177    
178        // Using the set method to set the default to 3.3s because the set method
179        // will automatically choose the closest valid integration time and I don't
180        // know the exact value out to the n'th decimal point of the 3.3s setting.
181        setIntegrationTime(new BigDecimal("3.3"));
182      }
183      
184      /**
185       * A convenience method for setting all the old-style parameters at once.
186       * See the individual <tt>set<i>Xxx</i></tt> methods for details.  NOTE: This
187       * assumes your center frequencies are SKY frequencies!
188       */
189      public void setAll(Frequency           centFreqAC,
190                         Frequency           centFreqBD,
191                         BandwidthCode       bwAC,
192                         BandwidthCode       bwBD,
193                         CorrelatorMode      corrMode,
194                         Set<ProcessingType> processingOptions,
195                         BigDecimal          integrationSeconds)
196      {
197        setCentralFrequency(IFPair.AC, centFreqAC);
198        setCentralFrequency(IFPair.BD, centFreqBD);
199        setBandwidthCodes(bwAC, bwBD);
200        setCorrelatorMode(corrMode);
201        setSpectralProcessing(processingOptions);
202        setIntegrationTime(integrationSeconds);
203      }
204      
205      //----------------------------------------------------------------------------
206      // Frequency
207      //----------------------------------------------------------------------------
208    
209      /**
210       * Sets one of the two central frequencies that can be held by this
211       * configuration. TODO: This method assumes that there is only 1 receiver
212       * band selected in the AntennaElectronics' active receiver set.
213       * 
214       * @param ifPair
215       *   the IF pair for which the central frequency is being set.
216       * @param central
217       *   the central frequency.  To use only one central frequency,
218       *   set the other to a value of zero.
219       * @param isSkyFrequency
220       *   True if central is a sky frequency, false if it is a
221       *   rest frequency.
222       * @throws IllegalArgumentException
223       *   if either parameter is <i>null</i> or if {@code central} is not within
224       *   the range of the selected receiver.
225       */
226      public void setCentralFrequency(IFPair ifPair, Frequency central, boolean isSkyFrequency)
227      {
228        if (central == null)
229          throw new IllegalArgumentException("Cannot set NULL central frequency.");
230    
231        if (ifPair == null)
232          throw new IllegalArgumentException("Cannot use NULL ifPair to set central frequency.");
233    
234        if (ifPair.equals(IFPair.AC))
235        {
236          centralFrequencyAC = central.clone();
237          centralFrequencyACIsSkyFrequency = isSkyFrequency;
238        }
239    
240        else if (ifPair.equals(IFPair.BD))
241        {
242          centralFrequencyBD = central.clone();
243          centralFrequencyBDIsSkyFrequency = isSkyFrequency;
244        }
245    
246        else
247          throw new RuntimeException("PROGRAMMER ERROR: did not expect ifPair = " + ifPair);
248      }
249    
250      /**
251       * calls setCentralFrequency(ifPair, central, true);
252       */
253      public void setCentralFrequency(IFPair ifPair, Frequency central)
254      {
255        setCentralFrequency(ifPair, central, true);
256      }
257      
258      /**
259       * Returns true if the central frequency for the specified IFPair was
260       * specified as a Sky Frequency, false if it was specified as a Rest
261       * Frequency.
262       */
263      public boolean isSkyFrequency(IFPair ifPair)
264      {
265        if      (ifPair.equals(IFPair.AC)) return centralFrequencyACIsSkyFrequency;
266        else if (ifPair.equals(IFPair.BD)) return centralFrequencyBDIsSkyFrequency;
267        else throw new RuntimeException("PROGRAMMER ERROR: did not expect ifPair = " + ifPair);
268      }
269    
270      /**
271       * Returns a copy of one of the two central frequencies
272       * that can be held by this configuration.
273       * The returned frequency will never be <i>null</i> but could be zero.
274       * <p>
275       * This central frequency may be either a rest or sky frequency;
276       * use {@link #isSkyFrequency(IFPair)} to determine which it is.
277       * See also {@link #getCentralSkyFrequency(IFPair, Source, Date)}.</p>
278       * 
279       * @param ifPair
280       *   the IF pair for which the central frequency is being sought.
281       * @return
282       *   the central frequency for the given IF pair.
283       * @throws IllegalArgumentException
284       *   if {@code ifPair} is <i>null</i>.
285       */
286      public Frequency getCentralFrequency(IFPair ifPair)
287      {
288        if (ifPair == null)
289          throw new IllegalArgumentException("Cannot use NULL ifPair to fetch central frequency.");
290    
291        if      (ifPair.equals(IFPair.AC))  return centralFrequencyAC.clone();
292        else if (ifPair.equals(IFPair.BD))  return centralFrequencyBD.clone();
293        else
294          throw new RuntimeException("PROGRAMMER ERROR: did not expect ifPair = " + ifPair);
295      }
296      
297      /**
298       * @deprecated do not use; see {@link #getCentralSkyFrequency(IFPair, DopplerTracker, Date)}
299       */
300      public Frequency getCentralSkyFrequency(IFPair ifPair,
301                                              Source source, Date dateTime)
302        throws CoordinateConversionException
303      {
304        Frequency centerFreq = getCentralFrequency(ifPair);
305        
306        if (!isSkyFrequency(ifPair))
307        {
308          centerFreq =
309            source.calcShiftedFrequency(centerFreq,
310                                        TelescopeType.EVLA.getLocation(), dateTime);
311        }
312        
313        return centerFreq;
314      }
315      
316      /**
317       * Returns a central sky frequency for the given IF pair.
318       * The {@code source} and {@code dateTime} are used only if the stored
319       * frequency is a rest frequency.
320       * 
321       * @param ifPair
322       *   the IF pair for which the central sky frequency is being sought.
323       * 
324       * @param dt
325       *   a Doppler tracker that will provide information on source position,
326       *   source velocity, and earth velocity. 
327       *   If this value is <i>null</i> Doppler tracking will <i>not</i>
328       *   be performed.
329       *   
330       * @param dateTime
331       *   the date and time of the observation.
332       *   This parameter is used if this configuration holds a rest frequency.
333       *   The velocity of the source relative to the EVLA is calculated using
334       *   this point in time.
335       *   If this value is <i>null</i> the current system time will be used.
336       *   
337       * @return
338       *   the central sky frequency for the given IF pair.
339       *   
340       * @throws IllegalArgumentException
341       *   if {@code ifPair} is <i>null</i>.
342       *   
343       * @throws CoordinateConversionException
344       *   if the position of the source cannot be converted to
345       *   an equatorial RA / Dec position.
346       *   
347       * @since 2008-07-29
348       */
349      public Frequency getCentralSkyFrequency(IFPair ifPair,
350                                              DopplerTracker dt, Date dateTime)
351        throws CoordinateConversionException
352      {
353        Frequency centerFreq = getCentralFrequency(ifPair);
354        
355        if (dt != null && !isSkyFrequency(ifPair))
356        {
357          if (dateTime == null)
358            dateTime = new Date();
359          
360          centerFreq = dt.calculateShiftedFrequency(centerFreq, dateTime);
361        }
362        
363        return centerFreq;
364      }
365    
366      /**
367       * Returns the bandwidth used for this configuration for the given IF pair.
368       * @param ifPair
369       *   the IF pair (A/C or B/D) for which the bandwidth is requested.
370       * @return
371       *   the bandwidth used for this configuration for the given IF pair.
372       */
373      public Frequency getBandwidth(IFPair ifPair)
374      {
375        BandwidthCode bwCode = getBandwidthCode(ifPair.getIfCodes().get(0));
376    
377        IFMode ifMode = correlatorMode.getCorrespondingIFMode();
378    
379        return (ifMode == null) ? bwCode.getBandwidthForContinuum()
380                                : bwCode.getBandwidthForSpectralLine(ifMode, spectralProcessing);
381      }
382      
383      public Frequency getBandwidthNominal(IFPair ifPair)
384      {
385        BandwidthCode bwCode = getBandwidthCode(ifPair.getIfCodes().get(0));
386    
387        return bwCode.getBandwidthNominal();
388      }
389      
390      /**
391       * Returns the sky frequency range for this configuration for the given
392       * IF pair.
393       * @param ifPair
394       *   the IF pair (A/C or B/D) for which the bandwidth is requested.
395       * @return
396       *   the frequency range used for this configuration for the given IF pair.
397       */
398      public FrequencyRange getFrequencyRange(IFPair ifPair)
399      {
400        Frequency c = getCentralFrequency(ifPair).normalize();
401        Frequency w = getBandwidth(ifPair);
402        
403        return new FrequencyRange().setCenterAndWidth(c, w);
404      }
405      
406      //List of receiver/IF-pairs that have SSLO = centerFreq PLUS bw/2
407      //DANGER:
408      //  This list ASSUMES:
409      //    1. 8-bit sampling
410      //    2. Use of LO-2 path for B/D of K, Ka, & Q
411      //    3. Use of LO-1 and LO-2 paths for Ku
412      private static final List<String> RCVR_IF_PAIR_ADD = new ArrayList<String>();
413      static
414      {
415        RCVR_IF_PAIR_ADD.add(ReceiverBand.EVLA_X.name()  + "." + IFPair.AC.name());
416        RCVR_IF_PAIR_ADD.add(ReceiverBand.EVLA_X.name()  + "." + IFPair.BD.name());
417        RCVR_IF_PAIR_ADD.add(ReceiverBand.EVLA_K.name()  + "." + IFPair.BD.name());
418        RCVR_IF_PAIR_ADD.add(ReceiverBand.EVLA_Ka.name() + "." + IFPair.BD.name());
419        RCVR_IF_PAIR_ADD.add(ReceiverBand.EVLA_Q.name()  + "." + IFPair.BD.name());
420      }
421      
422      /**
423       * Returns an array of text that can be used in the LoIfSetup constructor.
424       * 
425       * @param acTracker
426       *   a doppler tracker for the A/C IF pair.
427       *   
428       * @param bdTracker
429       *   a doppler tracker for the B/D IF pair.
430       *   
431       * @param when
432       *   the point in time to use for calculating doppler shifts.
433       *   
434       * @return
435       *   the parameters to use in the LoIfConstructor plus one other value that
436       *   tells clients whether or not this method swapped the A/C and B/D pairs.
437       *   The returned array has a length of five, and the parameters are in this
438       *   order:
439       *   <ol start="0">
440       *     <li>Frequency name of the band, e.g. "33GHz".</li>
441       *     <li>SSLO A/C in MHz.</li>
442       *     <li>SSLO B/D in MHz.</li>
443       *     <li>The T303 path variable.  Value = "0", "1", or "2".</li>
444       *     <li>Value = "swapped" or "not swapped".</li>
445       *   </ol> 
446       *   
447       * @throws CoordinateConversionException
448       *   see {@link #getCentralSkyFrequency(IFPair, DopplerTracker, Date)}.
449       */
450      public String[] calcLoIfSetupParams(DopplerTracker acTracker, DopplerTracker bdTracker,
451                                          Date when)
452        throws CoordinateConversionException
453      {
454        boolean useAC = correlatorMode.uses(IFPair.AC) && !correlatorMode.equals(CorrelatorMode.PB);
455        boolean useBD = correlatorMode.uses(IFPair.BD) && !correlatorMode.equals(CorrelatorMode.PA);
456        
457        //Cover all bases; will occur only if someone screws up CorrelatorMode class
458        if (!useAC && !useBD)
459        {
460          throw new RuntimeException("PROGRAMMER ERROR: Neither A/C nor B/D being used.  " +
461                                     "Corr mode = " + correlatorMode); 
462        }
463        
464        ReceiverBand band = signalSource.getActiveReceivers().first();
465        
466        //Determine center sky frequencies, using doppler if approp, and half bandwidths
467        Frequency skyFreqCtrAC = null, skyFreqCtrBD = null;
468        Frequency halfBwAC     = null, halfBwBD     = null;
469    
470        if (useAC)      //using A/C and possibly B/D
471        {
472          skyFreqCtrAC = getCentralSkyFrequency(IFPair.AC, acTracker, when);
473          halfBwAC     = getBandwidthNominal(IFPair.AC).divideBy("2.0");
474    
475          skyFreqCtrBD = useBD ? getCentralSkyFrequency(IFPair.BD, bdTracker, when) : skyFreqCtrAC.clone();
476          halfBwBD     = useBD ? getBandwidthNominal(IFPair.BD).divideBy("2.0")     : halfBwAC.clone();
477        }
478        else if (useBD) //using B/D, not using AC
479        {
480          skyFreqCtrBD = getCentralSkyFrequency(IFPair.BD, bdTracker, when);
481          halfBwBD     = getBandwidthNominal(IFPair.BD).divideBy("2.0");
482            
483          skyFreqCtrAC = skyFreqCtrBD.clone();
484          halfBwAC     = halfBwBD.clone();
485        }
486        //case of !A/C & !B/D already covered w/ runtime exception, above
487        
488        Frequency skyFreqLowAC = skyFreqCtrAC.clone().subtract(halfBwAC);
489        Frequency skyFreqLowBD = skyFreqCtrBD.clone().subtract(halfBwBD);
490        
491        //Introduced for EVL-901, 2009-Apr-27
492        if (useBD && !useAC && band.equals(ReceiverBand.EVLA_Ka))
493        {
494          skyFreqLowAC = new Frequency("35.0", FrequencyUnits.GIGAHERTZ);
495          skyFreqCtrAC = skyFreqLowAC.clone().add(halfBwAC);
496        }
497    
498        //Determine SSLOs and T303 param.
499        //NOTE: code below ASSUMES 8-bit samplers and VLA correlator
500        Frequency ssloAC = null, ssloBD = null;
501        int t303Path = -1;
502        
503        boolean swappedIFs = false;
504        
505        switch (band)
506        {
507          case EVLA_X:
508            t303Path = 0;
509            ssloAC   = skyFreqCtrAC.add(halfBwAC);
510            ssloBD   = skyFreqCtrBD.add(halfBwBD);
511            break;
512            
513          case EVLA_Ku:
514            t303Path = 2;
515            ssloAC   = skyFreqCtrAC.subtract(halfBwAC);
516            ssloBD   = skyFreqCtrBD.subtract(halfBwBD);
517            break;
518            
519          case EVLA_Ka:
520            //A/C freq cannot be < 32GHz
521            if (skyFreqLowAC.compareTo(MIN_AC_LOW_FREQ) < 0)
522            {
523              //If both IFs are < min, set AC to its minimum allowed value
524              if (skyFreqLowBD.compareTo(MIN_AC_LOW_FREQ) < 0)
525              {
526                skyFreqCtrAC.set(MIN_AC_LOW_FREQ.getValue(), MIN_AC_LOW_FREQ.getUnits());
527                skyFreqCtrAC.add(halfBwAC);
528              }
529              else //only A/C is below min; B/D is at or over
530              {
531                //If not using conversion path, swap.
532                //(If using conversion path, logic in cases K & Q below will swap anyway.)
533                //An example is a request for A/C=31GHz, B/D=33GHz.
534                if (!useConversionPath(skyFreqLowAC, skyFreqLowBD, band))
535                {
536                  Frequency swap = skyFreqCtrAC;
537                  skyFreqCtrAC = skyFreqCtrBD;
538                  skyFreqCtrBD = swap;
539                
540                  swap     = halfBwAC;
541                  halfBwAC = halfBwBD;
542                  halfBwBD = swap;
543                
544                  swappedIFs = true;
545                }
546              }
547            }
548            //INTENTIONAL FALL-THROUGH HERE; no "break" statement
549          case EVLA_K:
550          case EVLA_Q:
551            if (useConversionPath(skyFreqLowAC, skyFreqLowBD, band))
552            {
553              //Need higher freq to be A/C
554              if (skyFreqCtrBD.compareTo(skyFreqCtrAC) > 0)
555              {
556                Frequency swap = skyFreqCtrAC;
557                skyFreqCtrAC = skyFreqCtrBD;
558                skyFreqCtrBD = swap;
559                
560                swap     = halfBwAC;
561                halfBwAC = halfBwBD;
562                halfBwBD = swap;
563                
564                swappedIFs = true;
565              }
566              
567              t303Path = 1;
568              ssloAC   = skyFreqCtrAC.subtract(halfBwAC);
569              ssloBD   = skyFreqCtrBD.add(halfBwBD);
570            }
571            else
572            {
573              t303Path = 0;
574              ssloAC   = skyFreqCtrAC.subtract(halfBwAC);
575              ssloBD   = skyFreqCtrBD.subtract(halfBwBD);
576            }
577            break;
578            
579          default:
580            t303Path = 0;
581            ssloAC   = skyFreqCtrAC.subtract(halfBwAC);
582            ssloBD   = skyFreqCtrBD.subtract(halfBwBD);
583        }
584        
585        //Put into ready-to-use text form
586        String[] result = new String[5];
587        
588        result[0] = band.getFrequencyName();
589        result[1] = sUtil.formatNoScientificNotation(ssloAC.toUnits(MEGAHERTZ), 1, 10);
590        result[2] = sUtil.formatNoScientificNotation(ssloBD.toUnits(MEGAHERTZ), 1, 10);
591        result[3] = Integer.toString(t303Path);
592        result[4] = swappedIFs ? "swapped" : "not swapped";
593        
594        return result;
595      }
596      
597      //Updated based on conversation w/ Ken S. 13-Jan-2009.
598      //Ken made changes to LoIfSetup.  He suggests that having a bias toward
599      //using the conversion path is safer.  To that end we will now use the
600      //copy path only when the IF pairs are quite close together.
601      //Updated again based on Ken S. email 02-Feb-2009.
602      //For Ka, use conversion path if either IF is < 32GHz, no matter
603      //how close the IF pairs are together.
604      private boolean useConversionPath(Frequency acLowSky, Frequency bdLowSky,
605                                        ReceiverBand band)
606      {
607        //Quick exit for Ka if either IF < minAC
608        if (band.equals(ReceiverBand.EVLA_Ka) &&
609            (acLowSky.compareTo(MIN_AC_LOW_FREQ) < 0 || bdLowSky.compareTo(MIN_AC_LOW_FREQ) < 0))
610          return true;
611        
612        //Normal logic: if IFs are close together, don't need conversion path
613        final double bandwidthProxy = 50.0;    //MHz
614        final double maxSpan        = 3000.0;  //3.0GHz
615        
616        FrequencyRange lowEdgeSpan = new FrequencyRange(acLowSky, bdLowSky);
617        
618        double span = lowEdgeSpan.getWidth().toUnits(MEGAHERTZ).doubleValue() + bandwidthProxy;
619        
620        return span >= maxSpan;
621      }
622      
623      /** @deprecated Do not use;
624       *  see {@link #calcLoIfSetupParams(DopplerTracker, DopplerTracker, Date)} instead.
625       */
626      public Frequency getSSLO(IFPair ifPair, DopplerTracker dt, Date dateTime)
627        throws CoordinateConversionException
628      {
629        Frequency sslo   = getCentralSkyFrequency(ifPair, dt, dateTime);
630        Frequency halfBw = getBandwidth(ifPair).divideBy("2.0");
631        
632        String keyText = "";
633    
634        if (signalSource != null)
635          keyText = signalSource.getActiveReceivers().first().name() + "." + ifPair.name();
636        
637        if (RCVR_IF_PAIR_ADD.contains(keyText))
638          sslo.add(halfBw);
639        else
640          sslo.subtract(halfBw);
641        
642        return sslo;
643      }
644      /** @deprecated Do not use; see {@link #getSSLO(IFPair, DopplerTracker, Date)} instead.
645       */
646      public Frequency getSSLO(IFPair ifPair)
647      {
648        Frequency sslo   = getCentralFrequency(ifPair);
649        Frequency halfBw = getBandwidth(ifPair).divideBy("2.0");
650        
651        String keyText = "";
652    
653        if (signalSource != null)
654          keyText = signalSource.getActiveReceivers().first().name() + "." + ifPair.name();
655        
656        if (RCVR_IF_PAIR_ADD.contains(keyText))
657          sslo.add(halfBw);
658        else
659          sslo.subtract(halfBw);
660        
661        return sslo;
662      }
663    
664      //----------------------------------------------------------------------------
665      // Integration Time
666      //----------------------------------------------------------------------------
667      private static final List<BigDecimal> INT_TIMES = new ArrayList<BigDecimal>();
668      private static final BigDecimal DIVISOR = new BigDecimal("3.0");
669      static
670      {
671        // Changed first item from BigDecimal.TEN to the following because I need
672        // 10.0 for comparisons later, not 10, and I can NOT use compareTo instead
673        // of equals, I have no control over that code.  -- BWT, 2008-08-28
674        INT_TIMES.add(new BigDecimal("10.0"));
675        INT_TIMES.add(new BigDecimal("25.0" ).divide(DIVISOR, MathContext.DECIMAL128));
676        INT_TIMES.add(new BigDecimal("20.0" ).divide(DIVISOR, MathContext.DECIMAL128));
677        INT_TIMES.add(new BigDecimal("5.0"));
678        INT_TIMES.add(new BigDecimal("10.0" ).divide(DIVISOR, MathContext.DECIMAL128));
679        INT_TIMES.add(new BigDecimal( "5.0" ).divide(DIVISOR, MathContext.DECIMAL128));
680        INT_TIMES.add(new BigDecimal( "2.5" ).divide(DIVISOR, MathContext.DECIMAL128));
681        INT_TIMES.add(new BigDecimal( "1.25").divide(DIVISOR, MathContext.DECIMAL128));
682      }
683      
684      /**
685       * Returns a list of valid integration times for the VLA correlator.
686       * The unit of time for the returned values is <i>seconds</i>. 
687       * @return a list of valid integration times for the VLA correlator.
688       */
689      public List<BigDecimal> getValidIntegrationTimes()
690      {
691        return new ArrayList<BigDecimal>(INT_TIMES);
692      }
693      
694      /**
695       * Sets a new integration time for this configuration.
696       * 
697       * @param seconds
698       *   a new integration time for this configuration.
699       *   The value should be chosen from the list of
700       *   {@link #getValidIntegrationTimes() valid values}.
701       *   
702       * @throws IllegalArgumentException
703       *   if {@code seconds} is not one of the valid values from
704       *   {@link #getValidIntegrationTimes()}.  
705       */
706      public void setIntegrationTime(BigDecimal seconds)
707      {
708        if (seconds == null)
709          throw new IllegalArgumentException("NULL is not a valid integration time.");
710     
711        integrationTimeInSeconds = null; //Helps w/ loop, below
712        
713        //This logic relies on the fact that the list of valid values is sorted
714        //from highest to lowest.
715        BigDecimal smaller = INT_TIMES.get(0);
716        if (seconds.compareTo(smaller) >= 0)
717        {
718          integrationTimeInSeconds = smaller;
719        }
720        else
721        {
722          int count = INT_TIMES.size();
723          for (int i=1; i < count; i++)
724          {
725            BigDecimal larger = smaller;  //index = i-1
726            smaller = INT_TIMES.get(i);
727            if (seconds.compareTo(smaller) >= 0)
728            {
729              double distToLarger  = larger.subtract(seconds).doubleValue();
730              double distToSmaller = seconds.subtract(smaller).doubleValue();
731              
732              integrationTimeInSeconds =
733                (distToLarger <= distToSmaller) ? larger : smaller;
734                
735              break;
736            }
737          }
738          if (integrationTimeInSeconds == null)
739            integrationTimeInSeconds = smaller;
740        }
741      }
742    
743      /**
744       * Returns a copy of the integration time for this configuration.
745       * @return a copy of the integration time for this configuration.
746       */
747      public BigDecimal getIntegrationTime()
748      {
749        return integrationTimeInSeconds;
750      }
751    
752      //----------------------------------------------------------------------------
753      // Correlator Modes
754      //----------------------------------------------------------------------------
755      
756      /**
757       * Sets the correlator mode for this configuration.
758       * 
759       * @param newMode
760       *   the new correlator mode for this configuration.
761       *   
762       * @throws IllegalArgumentException
763       *   if {@code newMode} is <i>null</i>.
764       */
765      public void setCorrelatorMode(CorrelatorMode newMode)
766      {
767        if (newMode == null)
768          throw new IllegalArgumentException("Cannot set NULL correlator mode.");
769        
770        correlatorMode = newMode;
771      }
772      
773      /**
774       * Returns the correlator mode for this configuration.
775       * @return the correlator mode for this configuration.
776       */
777      public CorrelatorMode getCorrelatorMode()  { return correlatorMode; }
778      
779      //----------------------------------------------------------------------------
780      // Bandwidth Codes
781      //----------------------------------------------------------------------------
782      
783      /**
784       * Sets the bandwidth code to use for the given IF.
785       * 
786       * @param ifCode
787       *   the IF for which {@code bandCode} will be applied.
788       *   
789       * @param bwCode
790       *   the bandwidth code to use for the given IF code.
791       *   The bandwidth code determines bandwidth and the maximum number of
792       *   spectral channels for the given IF.
793       *   
794       * @throws IllegalArgumentException
795       *   if either parameter is <i>null</i>.
796       */
797      public void setBandwidthCode(IFCode ifCode, BandwidthCode bwCode)
798      {
799        if (bwCode == null)
800          throw new IllegalArgumentException("Cannot set NULL bandwidth code.");
801    
802        if (ifCode == null)
803          throw new IllegalArgumentException("Cannot use NULL ifCode to set bandwidth code.");
804        
805        if      (IFCode.A.equals(ifCode))  bwCodeIfA = bwCode;
806        else if (IFCode.B.equals(ifCode))  bwCodeIfB = bwCode;
807        else if (IFCode.C.equals(ifCode))  bwCodeIfC = bwCode;
808        else if (IFCode.D.equals(ifCode))  bwCodeIfD = bwCode;
809        else
810          throw new RuntimeException("PROGRAMMER ERROR: did not expect ifCode = " + ifCode);
811      }
812      
813      /**
814       * Sets the bandwidth codes for all four IFs at once.
815       * 
816       * @param ifA the bandwidth code to use for IF A.
817       * @param ifB the bandwidth code to use for IF B.
818       * @param ifC the bandwidth code to use for IF C.
819       * @param ifD the bandwidth code to use for IF D.
820       * 
821       * @throws IllegalArgumentException
822       *   if any of the parameters are <i>null</i>.
823       */
824      public void setBandwidthCodes(BandwidthCode ifA, BandwidthCode ifB,
825                                    BandwidthCode ifC, BandwidthCode ifD)
826      {
827        bwCodeIfA = checkForNull(ifA, "A");
828        bwCodeIfB = checkForNull(ifB, "B");
829        bwCodeIfC = checkForNull(ifC, "C");
830        bwCodeIfD = checkForNull(ifD, "D");
831      }
832      
833      /**
834       * Sets the bandwidth codes for all four IFs at once, setting A & C to
835       * one value and B & D to the other. 
836       * @see #setBandwidthCodes(BandwidthCode, BandwidthCode, BandwidthCode, BandwidthCode)
837       */
838      public void setBandwidthCodes(BandwidthCode ifsAC, BandwidthCode ifsBD)
839      {
840        setBandwidthCodes(ifsAC, ifsBD, ifsAC, ifsBD);
841      }
842    
843      private BandwidthCode checkForNull(BandwidthCode bwc, String ifCode)
844      {
845        if (bwc == null)
846          throw new IllegalArgumentException("NULL is not a valid bandwidth code for IF " +
847                                             ifCode + ".");
848        return bwc;
849      }
850      
851      /**
852       * Returns the bandwidth code used for the given IF.
853       * 
854       * @param ifCode
855       *   the IF for which the returned bandwidth code is used.
856       * @return
857       *   the bandwidth code used for the given IF.
858       * @throws IllegalArgumentException
859       *   if {@code ifCode} is <i>null</i>.
860       */
861      public BandwidthCode getBandwidthCode(IFCode ifCode)
862      {
863        if (ifCode == null)
864          throw new IllegalArgumentException("Cannot use NULL ifCode to fetch bandwidth code.");
865        
866        if      (IFCode.A.equals(ifCode))  return bwCodeIfA;
867        else if (IFCode.B.equals(ifCode))  return bwCodeIfB;
868        else if (IFCode.C.equals(ifCode))  return bwCodeIfC;
869        else if (IFCode.D.equals(ifCode))  return bwCodeIfD;
870        else
871          throw new RuntimeException("PROGRAMMER ERROR: did not expect ifCode = " + ifCode);
872      }
873      
874      //----------------------------------------------------------------------------
875      // Spectral Processing
876      //----------------------------------------------------------------------------
877    
878      /**
879       * Returns the collection of spectral processing options used by this
880       * configuration.
881       * The returned set is the one held internally by this configuration, so
882       * any changes made to it will change this configurations options.
883       * 
884       * @return
885       *   the collection of spectral processing options used by this configuration.
886       */
887      public Set<ProcessingType> getSpectralProcessing()
888      {
889        return spectralProcessing;
890      }
891      
892      /**
893       * Sets this configuration's spectral processing options.
894       * This object will hold a reference to the parameter, so any changes
895       * made to the set passed to this method after the method call will have
896       * an effect on this configuration.  The one exception is for a parameter
897       * value of <i>null</i>.  If <i>null</i> is received, this method will
898       * create a new empty set to use instead.
899       * 
900       * @param options
901       *   new spectral processing options for this configuration.
902       */
903      public void setSpectralProcessing(Set<ProcessingType> options)
904      {
905        spectralProcessing =
906          (options == null) ? new HashSet<ProcessingType>() : options;
907      }
908    
909      //This pair of methods converts Set<ProcessingType> to/from single string
910      //and is used w/ Hibernate.
911      @SuppressWarnings("unused")
912      private String getPersistentSpecOpts()
913      {
914        Collection<String> strings = new HashSet<String>();
915        
916        for (ProcessingType pt : spectralProcessing)
917          strings.add(pt.name());
918        
919        return sUtil.fromCollection(strings, " | ");
920      }
921      
922      @SuppressWarnings("unused")
923      private void setPersistentSpecOpts(String text)
924      {
925        spectralProcessing.clear();
926    
927        if (text != null)
928        {
929          Collection<String> ptNames = sUtil.toCollection(text, " | ", null);
930          
931          for (String ptName : ptNames)
932            spectralProcessing.add(ProcessingType.valueOf(ptName));
933        }
934      }
935    
936      //============================================================================
937      // 
938      //============================================================================
939    
940      /**
941       * Sets the central frequency and bandwidth of the unused IF pair, if any,
942       * to that of the IF pair that is being used.  This is convenient for
943       * both validation and for script generation.
944       */
945      public void tidyUp()
946      {
947        //See if we have an unused IF pair
948        if (correlatorMode.getIfPairCount() == 1)
949        {
950          //If we have A/C, set B/D values
951          if (correlatorMode.getCode().contains("A") ||
952              correlatorMode.getCode().contains("C"))
953          {
954            centralFrequencyBD = centralFrequencyAC.clone();
955            
956            bwCodeIfB = bwCodeIfA;
957            bwCodeIfD = bwCodeIfC;
958          }
959          //If we have B/D, set A/C values
960          else
961          {
962            //Special situation for Ka band
963            ReceiverBand band = null;
964            
965            AntennaElectronics src = getSignalSource();
966            if (src != null)
967            {
968              Set<ReceiverBand> bands = src.getActiveReceivers();
969    
970              if (!bands.isEmpty())
971                band = bands.iterator().next();
972            }
973    
974            if (band != null && band.equals(ReceiverBand.EVLA_Ka))
975              centralFrequencyAC = new Frequency("33.0"); //~middle of band
976            else
977              centralFrequencyAC = centralFrequencyBD.clone();
978            
979            bwCodeIfA = bwCodeIfB;
980            bwCodeIfC = bwCodeIfD;
981          }
982        }
983      }
984      
985      //============================================================================
986      // 
987      //============================================================================
988      
989      @Override
990      public VlaConfiguration clone()
991      {
992        VlaConfiguration clone = null;
993        
994        try
995        {
996          //This line takes care of the primitive & immutable fields properly
997          clone = (VlaConfiguration)super.clone();
998    
999          clone.centralFrequencyAC = this.centralFrequencyAC.clone();
1000          clone.centralFrequencyBD = this.centralFrequencyBD.clone();
1001          
1002          clone.spectralProcessing =
1003            new HashSet<ProcessingType>(this.spectralProcessing);
1004          
1005          //All other properties are immutable
1006       }
1007        catch (Exception ex)
1008        {
1009          throw new RuntimeException(ex);
1010        }
1011    
1012        return clone;
1013      }
1014      
1015      /** Returns <i>true</i> if {@code o} is equal to this configuration. */
1016      @Override
1017      public boolean equals(Object o)
1018      {
1019        //Quick exit if o is this
1020        if (o == this)
1021          return true;
1022        
1023        //Quick exit if super class finds differences
1024        if (!super.equals(o))
1025          return false;
1026       
1027        //A safe cast if we got this far
1028        VlaConfiguration other = (VlaConfiguration)o;
1029        
1030        return
1031          other.correlatorMode.equals(this.correlatorMode) &&
1032          other.bwCodeIfA.equals(this.bwCodeIfA) &&
1033          other.bwCodeIfB.equals(this.bwCodeIfB) &&
1034          other.bwCodeIfC.equals(this.bwCodeIfC) &&
1035          other.bwCodeIfD.equals(this.bwCodeIfD) &&
1036          other.spectralProcessing.equals(this.spectralProcessing) &&
1037          other.integrationTimeInSeconds.equals(this.integrationTimeInSeconds) &&
1038          other.centralFrequencyAC.equals(this.centralFrequencyAC) &&
1039          other.centralFrequencyBD.equals(this.centralFrequencyBD);
1040    
1041        //Note the comparison of the spectralProcessing Set is OK because
1042        //the elements are immutable.
1043      }
1044      
1045      /** Returns a hash code value for this configuration. */
1046      @Override
1047      public int hashCode()
1048      {
1049        //Taken from the Effective Java book by Joshua Bloch.
1050        //The constants 17 & 37 are arbitrary & carry no meaning.
1051        int result = super.hashCode();
1052        
1053        //You MUST keep this method in sync w/ the equals method
1054        
1055        result = 37 * result + correlatorMode.hashCode();
1056        result = 37 * result + bwCodeIfA.hashCode();
1057        result = 37 * result + bwCodeIfB.hashCode();
1058        result = 37 * result + bwCodeIfC.hashCode();
1059        result = 37 * result + bwCodeIfD.hashCode();
1060        result = 37 * result + spectralProcessing.hashCode();
1061        result = 37 * result + integrationTimeInSeconds.hashCode();
1062        result = 37 * result + centralFrequencyAC.hashCode();
1063        result = 37 * result + centralFrequencyBD.hashCode();
1064    
1065        return result;
1066      }
1067    
1068      //============================================================================
1069      // 
1070      //============================================================================
1071      /*
1072      //Integration time
1073      public static void main(String... args) throws Exception
1074      {
1075        VlaConfiguration vla =
1076          (VlaConfiguration)CorrelatorConfiguration.makeFor(CorrelatorName.VLA,
1077            edu.nrao.sss.model.resource.TelescopeType.EVLA.makeElectronics());
1078        
1079        //Display of integration times
1080        for (BigDecimal time : vla.getValidIntegrationTimes())
1081          System.out.println(time);
1082        
1083        System.out.println();
1084    
1085        for (BigDecimal time : vla.getValidIntegrationTimes())
1086          System.out.println(time.setScale(3, java.math.RoundingMode.HALF_UP));
1087        
1088        //Setting int times
1089        double s = 1.0/3.0;
1090        for (int i=1; i <= 31; i++)
1091        {
1092          double seconds = s * i;
1093          vla.setIntegrationTime(new BigDecimal(seconds));
1094          System.out.println("Set to " + seconds + ", but is now " +
1095                             vla.getIntegrationTime().setScale(3, java.math.RoundingMode.HALF_UP));
1096        }
1097      }
1098      */
1099      /*
1100      //SSLO
1101      public static void main(String... args) throws Exception
1102      {
1103        AntennaElectronics antElec =
1104          edu.nrao.sss.model.resource.TelescopeType.EVLA.makeElectronics();
1105        
1106        VlaConfiguration vla =
1107          (VlaConfiguration)CorrelatorConfiguration.makeFor(CorrelatorName.VLA, antElec);
1108        
1109        for (ReceiverBand band : edu.nrao.sss.model.resource.TelescopeType.EVLA.getReceivers())
1110        {
1111          antElec.configureFor(band);
1112          Frequency cf = band.getFrequencyRange().getCenterFrequency();
1113          vla.setCentralFrequency(IFPair.AC, cf);
1114          vla.setCentralFrequency(IFPair.BD, cf);
1115          System.out.println(band +": cf = "+cf+", SSLO A/C = "+vla.getSSLO(IFPair.AC)+", SSLO B/D = "+vla.getSSLO(IFPair.BD));
1116        }
1117      }
1118      */
1119    }