001      package edu.nrao.sss.model.resource.vla;
002    
003    import java.math.BigDecimal;
004    import java.util.ArrayList;
005    import java.util.List;
006    import java.util.Set;
007    
008    import static edu.nrao.sss.measure.FrequencyUnits.GIGAHERTZ;
009    
010    import edu.nrao.sss.measure.Frequency;
011    import edu.nrao.sss.measure.FrequencyRange;
012    import edu.nrao.sss.measure.FrequencyUnits;
013    import edu.nrao.sss.model.resource.AntennaElectronics;
014    import edu.nrao.sss.model.resource.ReceiverBand;
015    import edu.nrao.sss.validation.AbstractValidator;
016    import edu.nrao.sss.validation.FailureSeverity;
017    import edu.nrao.sss.validation.Validation;
018    import edu.nrao.sss.validation.ValidationPurpose;
019    
020    /**
021     * A validator of {@link VlaConfiguration VLA Correlator Configurations}.
022     * <p>
023     * <u>Validations Performed for All Purposes</u>
024     * <ol>
025     *   <li>If two central frequencies are used, they are within 12GHz of each
026     *       other.</li>
027     *   <li>Ensure that the selected receiver band at least partially covers
028     *       the reflected sky frequency range(s).</li>
029     * </ol></p>
030     * <p>
031     * <b>Version Info:</b>
032     * <table style="margin-left:2em">
033     *   <tr><td>$Revision: 2264 $</td></tr>
034     *   <tr><td>$Date: 2009-04-27 10:37:20 -0600 (Mon, 27 Apr 2009) $</td></tr>
035     *   <tr><td>$Author: dharland $ (last person to modify)</td></tr>
036     * </table></p>
037     * 
038     * @author David M. Harland
039     * @since 2008-05-02
040     */
041    public class VlaConfigurationValidator
042      extends AbstractValidator<VlaConfiguration>
043    {
044      static final BigDecimal MIN_AC_LOW_GHz  = new BigDecimal("32.240"); //GHz
045      static final Frequency  MIN_AC_LOW_FREQ = new Frequency(MIN_AC_LOW_GHz, GIGAHERTZ);
046    
047      /** Creates a new instance. */
048      public VlaConfigurationValidator()
049      {
050        super(VlaConfigurationValidator.class.getName(), VlaConfiguration.class);
051      }
052    
053      /* (non-Javadoc)
054       * @see AbstractValidator#makeValidationList(ValidationPurpose)
055       */
056      @Override
057      protected List<Validation<VlaConfiguration>>
058        makeValidationList(ValidationPurpose purpose)
059      {
060        List<Validation<VlaConfiguration>> validations =
061          new ArrayList<Validation<VlaConfiguration>>();
062       
063        validations.add(new CentralFreqSpreadValidation(this, purpose));
064        validations.add(new KaMinFreqAC(this, purpose));
065        validations.add(new ReceiverCoverageValidation(this, purpose, IFPair.AC));
066        validations.add(new ReceiverCoverageValidation(this, purpose, IFPair.BD));
067        
068        return validations;
069      }
070    
071      /* (non-Javadoc)
072       * @see edu.nrao.sss.model.validation.AbstractValidator#validate()
073       */
074      @Override
075      protected void validate()
076      {
077        target.tidyUp();
078        super.validate();
079      }
080    
081      //============================================================================
082      // VALIDATION: 10GHz Max Spread for Central Frequencies
083      //============================================================================
084    
085      /**
086       * If two IF pairs are used, warns if the separation between the central
087       * sky frequencies is > 10GHz.
088       */
089      class CentralFreqSpreadValidation extends Validation<VlaConfiguration>
090      {
091        private boolean programmerError;
092        private boolean bothAreSkyFreqs;
093        
094        protected CentralFreqSpreadValidation(
095                    AbstractValidator<VlaConfiguration> validationContainer,
096                    ValidationPurpose                   reasonForValidation)
097        {
098          super(validationContainer, reasonForValidation);
099          programmerError = false;
100          bothAreSkyFreqs = false;
101          severity = FailureSeverity.WARNING;
102            //(reasonForValidation == ValidationPurpose.CERTIFY_READY_TO_USE) ?
103            //FailureSeverity.ERROR : FailureSeverity.WARNING;
104        }
105        
106        BigDecimal MAX_SPREAD_WARNING = new BigDecimal("10.0"); //GHz
107        BigDecimal MAX_SPREAD_ERROR   = new BigDecimal("10.5"); //GHz
108        
109        /* (non-Javadoc)
110         * @see edu.nrao.sss.validation.Validation#passesTest()
111         */
112        protected boolean passesTest()
113        {
114          programmerError = false;
115          
116          CorrelatorMode corrMode = target.getCorrelatorMode(); 
117          int ifPairs = corrMode.getIfPairCount();
118          
119          switch (ifPairs)
120          {
121            case 1:
122              return true;
123            
124            case 2:
125              //PA & PB really use only one IF pair
126              if (corrMode.equals(CorrelatorMode.PA) || corrMode.equals(CorrelatorMode.PB))
127                return true;
128              
129              bothAreSkyFreqs = target.isSkyFrequency(IFPair.AC) &&
130                                target.isSkyFrequency(IFPair.BD);
131              
132              BigDecimal ac = target.getCentralFrequency(IFPair.AC)
133                                    .toUnits(FrequencyUnits.GIGAHERTZ);
134              
135              BigDecimal bd = target.getCentralFrequency(IFPair.BD)
136                                    .toUnits(FrequencyUnits.GIGAHERTZ);
137              
138              BigDecimal spread = ac.subtract(bd).abs();
139    
140              severity =
141                bothAreSkyFreqs && (spread.compareTo(MAX_SPREAD_ERROR) > 0) ? FailureSeverity.ERROR
142                                                                            : FailureSeverity.WARNING;
143              
144              return ac.subtract(bd).abs().compareTo(MAX_SPREAD_WARNING) <= 0;
145    
146            default:
147              programmerError = true;
148              return false;
149          }
150        }
151    
152        /* (non-Javadoc)
153         * @see edu.nrao.sss.validation.Validation#displayMessage()
154         */
155        protected String displayMessage()
156        {
157          if (programmerError)
158            return programmerErrorMsg();
159          
160          StringBuilder buff = new StringBuilder();
161    
162          buff.append("We recommend keeping the A/C (")
163              .append(target.getCentralFrequency(IFPair.AC))
164              .append(") and B/D (")
165              .append(target.getCentralFrequency(IFPair.BD))
166              .append(") central sky frequencies within ")
167              .append(MAX_SPREAD_WARNING).append("GHz of each other. ")
168              .append("If spread any farther apart, this scan may fail.");
169          
170          if (!bothAreSkyFreqs)
171          {
172            buff.append("  NOTE: If you are using Doppler tracking, ")
173                .append("the effect of the velocity might be to convert the rest ")
174                .append("frequencies into sky frequencies that are within this constraint. ")
175                .append("This observation configuration software is currently NOT checking ")
176                .append("the effect of Doppler tracking.");
177          }
178    
179          return buff.toString();
180        }
181        
182        protected String debugMessage()
183        {
184          if (programmerError)
185            return programmerErrorMsg();
186    
187          StringBuilder buff = new StringBuilder("Central freq spread > ");
188          
189          buff.append(MAX_SPREAD_WARNING).append("GHz.  AC=");
190          
191          buff.append(target.getCentralFrequency(IFPair.AC)).append(", BD =")
192              .append(target.getCentralFrequency(IFPair.BD));
193          
194          return buff.toString();
195        }
196        
197        private String programmerErrorMsg()
198        {
199          return "PROGRAMMER ERROR: Did not expect " +
200                  target.getCorrelatorMode().getIfPairCount() + " IF pairs.";
201        }
202      }
203    
204      //============================================================================
205      // VALIDATION: One Ka Freq >= 32GHz
206      //============================================================================
207      
208      class KaMinFreqAC extends Validation<VlaConfiguration>
209      {
210        private boolean      haveSignalSource;
211        private boolean      acOnlyMode; //queried only when test fails
212        private ReceiverBand targetBand;
213    
214        protected KaMinFreqAC(
215                    AbstractValidator<VlaConfiguration> validationContainer,
216                    ValidationPurpose                   reasonForValidation)
217        {
218          super(validationContainer, reasonForValidation);
219          
220          haveSignalSource = false;
221          acOnlyMode       = false;
222          
223          severity = 
224            (reasonForValidation == ValidationPurpose.CERTIFY_READY_TO_USE) ?
225            FailureSeverity.ERROR : FailureSeverity.WARNING;
226        }
227        
228        /* (non-Javadoc)
229         * @see edu.nrao.sss.validation.Validation#passesTest()
230         */
231        protected boolean passesTest()
232        {
233          boolean passes;
234          
235          AntennaElectronics sigSrc = target.getSignalSource();
236          
237          Set<ReceiverBand> receivers =
238            (sigSrc == null) ? null : sigSrc.getActiveReceivers();
239          
240          haveSignalSource = (receivers != null) && !receivers.isEmpty();
241    
242          if (haveSignalSource)
243          {
244            targetBand = receivers.iterator().next();
245            CorrelatorMode corrMode = target.getCorrelatorMode();
246            
247            if (!targetBand.equals(ReceiverBand.EVLA_Ka))
248            {
249              passes = true;
250            }
251            else //This is the test.  It is done only for Ka band.
252            {
253              int ifPairCount = corrMode.getIfPairCount();
254              
255              acOnlyMode = (ifPairCount < 2 && corrMode.uses(IFPair.AC)) ||
256                            corrMode.equals(CorrelatorMode.PA);
257    
258              //Since doppler shift normally makes frequencies even lower,
259              //we are not excluding IFs that will use doppler tracking.
260              Frequency freqLowAC = target.getFrequencyRange(IFPair.AC).getLowFrequency();
261    
262              //A/C-only modes
263              if (acOnlyMode)
264              {
265                passes = freqLowAC.compareTo(MIN_AC_LOW_FREQ) >= 0;
266              }
267              //A/C & B/D in use
268              else if (ifPairCount > 1 && !corrMode.equals(CorrelatorMode.PB))
269              {
270                Frequency freqLowBD = target.getFrequencyRange(IFPair.BD).getLowFrequency();
271    
272                //As long as at least one freq >= MIN, all is OK.
273                passes = (freqLowAC.compareTo(MIN_AC_LOW_FREQ) >= 0 ||
274                          freqLowBD.compareTo(MIN_AC_LOW_FREQ) >= 0);
275              }
276              else //B/D-only modes
277              {
278                passes = true;
279              }
280            }
281          }
282          else //no receiver band yet
283          {
284            passes = true;
285          }
286          
287          return passes;
288        }
289    
290        /* (non-Javadoc)
291         * @see edu.nrao.sss.validation.Validation#displayMessage()
292         */
293        protected String displayMessage()
294        {
295          return acOnlyMode ?
296            "A/C low frequency must not be lower than " + MIN_AC_LOW_FREQ +
297            ". Either use a higher center frequency, a narrower bandwidth, or try mode \"" +
298            getAcReplacement() + "\" instead."
299              
300              :
301              
302           "At least one of the IF pairs must be completely at or above the lower limit for " +
303           "the higher frequency IF pair.  This lower limit is " + MIN_AC_LOW_FREQ + 
304           ".  It would also be best to set the frequencies such that AC >= BD.";
305        }
306        
307        private CorrelatorMode getAcReplacement()
308        {
309          CorrelatorMode corrMode = target.getCorrelatorMode();
310          
311          switch (corrMode)
312          {
313            case ONE_A:   corrMode = CorrelatorMode.ONE_B;  break;
314            case ONE_C:   corrMode = CorrelatorMode.ONE_D;  break;
315            case TWO_AC:  corrMode = CorrelatorMode.TWO_BD; break;
316            case PA:      corrMode = CorrelatorMode.PB;     break;
317            
318            default:
319              ; //leave corrMode as is
320          }
321          
322          return corrMode;
323        }
324        
325        protected String debugMessage()
326        {
327          return acOnlyMode ? "A/C freq < min":
328                              "Both IF pairs are below A/C min freq.";
329        }
330      }
331    
332      //============================================================================
333      // VALIDATION: Sky Frequency Ranges Within Receiver Band
334      //============================================================================
335    
336      /**
337       * 
338       */
339      class ReceiverCoverageValidation extends Validation<VlaConfiguration>
340      {
341        private ValidationPurpose valPurpose;
342        
343        private ReceiverBand targetBand;
344        private IFPair       ifPair;
345        
346        private FrequencyRange desiredRange;
347        private FrequencyRange nominalRange;
348        
349        private List<ReceiverBand.NamedRange> extRanges;
350        
351        private boolean noSignalSource;
352        private boolean completelyNominal; //True if tests passed, false otherwise
353        private boolean completelyOutside;
354        
355        protected ReceiverCoverageValidation(
356                    AbstractValidator<VlaConfiguration> validationContainer,
357                    ValidationPurpose                   reasonForValidation,
358                    IFPair                              ifp)
359        {
360          super(validationContainer, reasonForValidation);
361         
362          targetBand        = null;
363          ifPair            = ifp;
364          desiredRange      = null;
365          nominalRange      = null;
366          extRanges         = null;
367          noSignalSource    = true;
368          completelyNominal = false;
369          completelyOutside = true;
370        
371          valPurpose = reasonForValidation;
372        }
373        
374        /* (non-Javadoc)
375         * @see edu.nrao.sss.validation.Validation#passesTest()
376         */
377        protected boolean passesTest()
378        {
379          //If we're testing an unused IF pair or if the central frequency of that
380          //ifPair is a rest frequency, deem the test as "passed".
381          //This suppresses unnecessary, and possibly confusing, messages.
382          if (!target.getCorrelatorMode().uses(ifPair) || !target.isSkyFrequency(ifPair))
383          {
384            completelyNominal = true;
385            //Don't worry about state of other variables; the msg methods
386            //will exit quickly if test was passed.
387          }
388          else //we're running a real test
389          {
390            AntennaElectronics sigSrc = target.getSignalSource();
391            
392            Set<ReceiverBand> receivers =
393              (sigSrc == null) ? null : sigSrc.getActiveReceivers();
394            
395            noSignalSource = (receivers == null) || receivers.isEmpty();
396    
397            if (noSignalSource)
398            {
399              completelyNominal = false;
400              //Don't worry about state of other variables; the msg methods
401              //will look first at noSignalSource.
402            }
403            else //finally testing requested range vs selected receiver band
404            {
405              targetBand = receivers.iterator().next();
406              
407              desiredRange = target.getFrequencyRange(ifPair);
408              nominalRange = targetBand.getNominalRange();
409              extRanges    = targetBand.getExtendedRanges();
410    
411              if (desiredRange.overlaps(targetBand.getWidestRange()))
412              {
413                completelyNominal = targetBand.getNominalRange().contains(desiredRange);
414                completelyOutside = false;
415              }
416              else //no overlap at all w/ receiver
417              {
418                completelyNominal = false;
419                completelyOutside = true;
420              }
421            }
422          }
423          
424          setSeverity();
425          
426          return completelyNominal;
427        }
428    
429        /* (non-Javadoc)
430         * @see edu.nrao.sss.validation.Validation#displayMessage()
431         */
432        protected String displayMessage()
433        {
434          //Some quick exits
435          if (completelyNominal)  return "";
436          if (noSignalSource)     return noSigSrcMsg();
437          if (completelyOutside)  return noOverlapMsg();
438    
439          //Messages for less-than-optimal, but not catastrophic, situations
440    
441          //See if part of desired range is outside widest extension
442          FrequencyRange widestRange = targetBand.getWidestRange();
443          boolean haveOutside = !widestRange.contains(desiredRange);
444          
445          //Start with the nominal range
446          StringBuilder buff = new StringBuilder();
447          
448          buff.append("Of the ").append(desiredRange.getWidth().normalize());
449          buff.append(" in the requested range for IF pair ").append(ifPair).append(", ");
450          
451          Frequency overlap;
452          
453          if (desiredRange.overlaps(nominalRange))
454            overlap = desiredRange.getOverlapWith(nominalRange).getWidth().normalize();
455          else
456            overlap = new Frequency("0.0", FrequencyUnits.HERTZ);
457          
458          buff.append(overlap).append(" is in this receiver's nominal range");
459          
460          //Show overlap w/ all extended ranges
461          FrequencyRange inner = nominalRange;
462    
463          int extRangeCount = extRanges.size();
464          for (int i=0; i < extRangeCount; i++)
465          {
466            FrequencyRange outer = extRanges.get(i).getRange();
467            overlap = calcOverlap(inner, outer, desiredRange);
468    
469            if (overlap.getValue().signum() > 0)
470            {
471              //if (i == (extRangeCount-1) && !haveOutside)
472              if (outer.contains(desiredRange))
473                buff.append(", and ");
474              else
475                buff.append(", ");
476              
477              buff.append(overlap).append(" is in this receiver's extended ");
478              buff.append(extRanges.get(i).getName()).append(" range");
479            }
480            
481            inner = outer;
482          }
483          
484          //Show amount outside widest extension
485          if (haveOutside)
486          {
487            List<FrequencyRange> nonOverlaps = widestRange.getComplementIn(desiredRange);
488            
489            Frequency f = new Frequency("0.0");
490            for (FrequencyRange r : nonOverlaps)
491              f.add(r.getWidth());
492    
493            buff.append(", and ").append(f.normalize()).append(" lies outside of that.");
494          }
495          else
496          {
497            buff.append('.');
498          }
499          
500          if (severity.equals(FailureSeverity.WARNING))
501          {
502            if (determineSeverity(ValidationPurpose.CERTIFY_READY_TO_USE)
503                .equals(FailureSeverity.ERROR))
504            {
505              buff.append("  While this is fine for now, you will need to adjust ")
506                  .append("the sky frequency range for this IF pair so that at ")
507                  .append("least some part of it falls within this receiver's ")
508                  .append("range before using this configuration in an ")
509                  .append("observation.");
510            }
511            else
512            {
513              buff.append("  While this is configuration is not optimal, you will ")
514                  .append("be permitted to use it in an observation.");
515            }
516          }
517          
518          return buff.toString();
519        }
520     
521        /**
522         * Returns amount of overlap between tgt and the zones of outer that
523         * extend beyond inner.
524         */
525        private Frequency calcOverlap(FrequencyRange inner, FrequencyRange outer,
526                                      FrequencyRange tgt)
527        {
528          List<FrequencyRange> extensions = inner.getComplementIn(outer);
529    
530          Frequency f = new Frequency("0.0", FrequencyUnits.HERTZ);
531          for (FrequencyRange e : extensions)
532            f.add(e.intersectWith(tgt).getWidth());
533          
534          return f.normalize();
535        }
536        
537        protected String debugMessage()
538        {
539          if (completelyNominal)  return "";
540          if (noSignalSource)     return noSigSrcMsg();
541    
542          StringBuilder buff = new StringBuilder("Receiver doesn't cover whole sky range.  ");
543          
544          buff.append(ifPair).append(" desired=").append(desiredRange);
545          buff.append(", ").append(targetBand.getDisplayName())
546              .append(" nominal=").append(nominalRange);
547          
548          int size = extRanges.size();
549          if (size > 0)
550            buff.append(", extended=").append(extRanges.get(size-1).getRange());
551          
552          return buff.toString();
553        }
554    
555        private String noSigSrcMsg()
556        {
557          return "PROGRAMMER ERROR: no signal source found.";
558        }
559        
560        private String noOverlapMsg()
561        {
562          StringBuilder buff = new StringBuilder();
563          
564          buff.append("No part of the requested sky frequency range of ");
565          buff.append(desiredRange);
566          buff.append(" for IF pair ").append(ifPair);
567          buff.append(" overlaps the receiver's ");
568          
569          int index = extRanges == null ? -1 : extRanges.size() - 1;
570    
571          if (index == -1)
572          {
573            buff.append("range of ").append(nominalRange);
574          }
575          else
576          {
577            buff.append("extended ").append(extRanges.get(index).getName());
578            buff.append(" range of ").append(extRanges.get(index).getRange());
579          }
580          
581          buff.append(". You will need to change your central frequency, ");
582          buff.append("bandwidth, and/or receiver band.");
583          
584          return buff.toString();
585        }
586        
587        private void setSeverity()
588        {
589          severity = determineSeverity(valPurpose);
590        }
591        
592        private FailureSeverity determineSeverity(ValidationPurpose purpose)
593        {
594          return (noSignalSource || completelyOutside) ? FailureSeverity.ERROR
595                                                       : FailureSeverity.WARNING;
596        }
597      }
598      
599      //============================================================================
600      // 
601      //============================================================================
602      /*
603      public static void main(String... args)
604      {
605        //Purpose: see what the failure msgs look like
606        
607        ResourceBuilder rb = new ResourceBuilder();
608        Resource r = rb.makeResource("junk", TelescopeType.EVLA);
609        
610        VlaConfigurationValidator vcv = new VlaConfigurationValidator();
611        ReceiverCoverageValidation rcv =
612          vcv.new ReceiverCoverageValidation(vcv, ValidationPurpose.SAVE_IN_REPOSITORY, IFPair.AC);
613        
614        vcv.validate(r.getBackend(CorrelatorName.VLA), ValidationPurpose.SAVE_IN_REPOSITORY);
615         
616        //Force some scenarios
617        rcv.passedTest     = false;
618        rcv.noSignalSource = false;
619        rcv.targetBand     = ReceiverBand.EVLA_X;
620        rcv.nominalRange   = rcv.targetBand.getFrequencyRange();
621        rcv.extRanges      = rcv.targetBand.getExtendedRanges();
622        
623        rcv.desiredRange      = new FrequencyRange(new Frequency("9.0"), new Frequency("10.0"));
624        rcv.completelyNominal = true;
625        rcv.completelyOutside = false;
626        rcv.setSeverity();
627        printMsg("Completely Nominal", rcv);
628        
629        rcv.desiredRange      = new FrequencyRange(new Frequency("6.5"), new Frequency("6.6"));
630        rcv.completelyNominal = false;
631        rcv.completelyOutside = true;
632        rcv.setSeverity();
633        printMsg("Completely Outside", rcv);
634        
635        rcv.desiredRange      = new FrequencyRange(new Frequency("11.0"), new Frequency("12.5"));
636        rcv.completelyOutside = false;
637        rcv.setSeverity();
638        printMsg("Nominal & Extended", rcv);
639        
640        rcv.desiredRange      = new FrequencyRange(new Frequency("7.8"), new Frequency("12.3"));
641        printMsg("Nominal & 2-Sided Extended", rcv);
642        
643        rcv.desiredRange      = new FrequencyRange(new Frequency("12.1"), new Frequency("12.6"));
644        printMsg("Only Extended", rcv);
645        
646        rcv.desiredRange      = new FrequencyRange(new Frequency("12.5"), new Frequency("13.5"));
647        printMsg("Extended & Outside", rcv);
648        
649        rcv.desiredRange      = new FrequencyRange(new Frequency("7.5"), new Frequency("8.5"));
650        printMsg("Nominal, Extended, & Outside - 1-Sided", rcv);
651        
652        rcv.desiredRange      = new FrequencyRange(new Frequency("7.0"), new Frequency("13.0"));
653        printMsg("Nominal, Extended, & Outside - 2-Sided", rcv);
654      }
655      
656      private static void printMsg(String msg, ReceiverCoverageValidation rcv)
657      {
658        System.out.println(msg);
659        try
660        {
661          System.out.println("DEBUG:   " + rcv.debugMessage());
662          System.out.println("DISPLAY: " + rcv.displayMessage());
663        }
664        catch (Exception ex)
665        {
666          System.out.println(ex.getMessage());
667        }
668        System.out.println();
669      }
670      */
671    }
672