001    package edu.nrao.sss.model.resource;
002    
003    import java.util.ArrayList;
004    import java.util.Collections;
005    import java.util.Comparator;
006    import java.util.List;
007    import java.util.Set;
008    
009    import edu.nrao.sss.measure.Frequency;
010    import edu.nrao.sss.measure.FrequencyRange;
011    import edu.nrao.sss.measure.FrequencySpectrum;
012    
013    /**
014     * A very simple receiver selector.
015     * <p>
016     * This selector merely looks for overlaps between the frequency range
017     * of the specifications and the frequency ranges of the available
018     * receivers.</p>
019     * <p>
020     * <b>Version Info:</b>
021     * <table style="margin-left:2em">
022     *   <tr><td>$Revision: 2294 $</td></tr>
023     *   <tr><td>$Date: 2009-05-11 15:11:56 -0600 (Mon, 11 May 2009) $</td></tr>
024     *   <tr><td>$Author: dharland $</td></tr>
025     * </table></p>
026     *  
027     * @author David M. Harland
028     * @since 2006-09-13
029     */
030    
031    //Note to programmers: There is an opportunity to reduce the amount of code in
032    //this class by taking the FrequencyRange-parameter methods and calling the
033    //Frequency-Spectrum methods, after converting the range to a spectrum.
034    //This effort may, or may not, be worthwhile.
035    
036    public class SimpleReceiverSelector
037      extends ReceiverSelectorAbs
038    {
039      private static final double EPSILON = 1e-8;
040      
041      private static ReceiverSelectionComparator SELECTION_COMPARATOR = 
042        new ReceiverSelectionComparator();
043      
044      //============================================================================
045      // 
046      //============================================================================
047    
048      /**
049       * Creates a new instance that uses {@code provider} as a source of
050       * receivers.
051       * 
052       * @param provider the receiver container for this selector.
053       */
054      public SimpleReceiverSelector(ReceiverProvider provider)
055      {
056        super(provider);
057      }
058      
059      /** Creates a new instance. */
060      public SimpleReceiverSelector()
061      {
062        this(null);
063      }
064      
065      //============================================================================
066      // SELECTION BASED ON SINGLE FREQUENCY RANGE
067      //============================================================================
068    
069      /* (non-Javadoc)
070       * @see ReceiverSelectorAbs#selectReceivers(FrequencyRange)
071       */
072      @Override
073      public List<ReceiverSelection> selectReceivers(FrequencyRange targetRange)
074      {
075        List<ReceiverSelection> result = new ArrayList<ReceiverSelection>();
076    
077        //Temporarily set originalSpecs based on targetSpectrum
078        boolean haveNullSpecs = (originalSpecs == null);
079        if (haveNullSpecs)
080          originalSpecs = makeResrcSpecFrom(targetRange);
081    
082        boolean done = (receiverProvider == null);
083        
084        //Try to find receiver or rcvr combo that covers whole tgt range
085        if (!done)
086        {
087          List<ReceiverBand> perfectReceivers = completelyCover(targetRange);
088        
089          if (perfectReceivers.size() > 0)
090          {
091            result.add(new ReceiverSelection(perfectReceivers, originalSpecs,
092                                                               originalSpecs));
093            done = true;
094          }
095        }
096        
097        //If we can't cover the whole range, give less than perfect options
098        if (!done)
099        {
100          //Make one selection for every receiver that overlaps
101          for (ReceiverBand r : receiverProvider.getReceivers())
102          {
103            if (r.getWidestRange().overlaps(targetRange))
104              result.add(buildSelection(r));
105          }
106         
107          //Make one selection for every receiver combination that has overlap
108          //on more than one receiver
109          for (Set<ReceiverBand> combo :
110               receiverProvider.getValidReceiverCombinations())
111          {
112            List<ReceiverBand> overlappingReceivers = new ArrayList<ReceiverBand>();
113            
114            //Find all receivers in the combo that overlap the target range
115            for (ReceiverBand r : combo)
116            {
117              if (r.getWidestRange().overlaps(targetRange))
118                overlappingReceivers.add(r);
119            }
120            
121            //If more than one receiver in the current combo overlaps,
122            //add this combo to the list of selections
123            if (overlappingReceivers.size() > 1)
124              result.add(buildSelection(overlappingReceivers));
125          }
126          
127          //Sort the list based on pctg of coverage
128          if (result.size() > 1)
129            Collections.sort(result, SELECTION_COMPARATOR);
130        }
131        
132        //If we have no overlapping receivers at all, find the closest receiver
133        if (!done)
134          result.add(buildSelection(findBestReceiverFor(targetRange)));
135        
136        if (haveNullSpecs)
137          originalSpecs = null;
138        
139        return result;
140      }
141      
142      /**
143       * Returns a list of receivers that completely cover the target frequency
144       * range AND that may be used simultaneously.  If the receiver container
145       * has no such combination of receivers, the returned list will be empty.
146       */
147      private List<ReceiverBand> completelyCover(FrequencyRange targetRange)
148      {
149        List<ReceiverBand> selection = new ArrayList<ReceiverBand>();
150        
151        boolean keepLooking = (receiverProvider != null);
152        
153        //First see if any single receiver contains the whole range
154        if (keepLooking)
155        {
156          for (ReceiverBand r : receiverProvider.getReceivers())
157          {
158            if (r.getWidestRange().contains(targetRange))
159            {
160              selection.add(r);
161              keepLooking = false;
162              break;
163            }
164          }
165        }
166        
167        //If no single receiver can cover the whole range,
168        //see if any legal combinations of receivers can.
169        if (keepLooking)
170        {
171          Frequency targetBandwidth = targetRange.getWidth();
172          
173          //For each valid combination, see if the combined receiver ranges
174          //completely covers the target range.
175          for (Set<ReceiverBand> combo :
176               receiverProvider.getValidReceiverCombinations())
177          {
178            FrequencySpectrum receiverSpectrum = new FrequencySpectrum();
179            
180            //Add each receiver's range to the spectrum
181            for (ReceiverBand r : combo)
182              receiverSpectrum.addCoveredRange(r.getWidestRange());
183            
184            //Find width of overlap between target and current combo of receivers
185            Frequency coveredBandwidth =
186              targetRange.getOverlapWith(receiverSpectrum).getAmountCovered();
187            
188            //If the receiver combo covers all the bandwidth, select the receivers
189            //from the combo and stop searching.
190            if (coveredBandwidth.compareTo(targetBandwidth) >= 0)
191            {
192              for (ReceiverBand r : combo)
193                selection.add(r);
194              
195              break;
196            }
197          }
198        }
199        
200        return selection;
201      }
202      
203      /* (non-Javadoc)
204       * @see ReceiverSelector#selectBestReceiverFor(FrequencyRange)
205       */
206      public ReceiverBand selectBestReceiverFor(FrequencyRange targetRange)
207      {
208        return findBestReceiverFor(targetRange);
209      }
210      
211      /** 
212       * Returns the receiver whose frequency range comes closest to the target.
213       */
214      private ReceiverBand findBestReceiverFor(FrequencyRange targetRange)
215      {
216        Set<ReceiverBand> receivers =
217          (receiverProvider == null) ? null : receiverProvider.getReceivers();
218        
219        if ((receivers == null) || (receivers.size() == 0))
220          return null;
221        
222        boolean foundOverlappingReceiver = false;
223        
224        //Init to any arbitrary receiver
225        ReceiverBand result = receivers.iterator().next();
226    
227        //Pick receiver with the most overlap.  If there aren't any, pick
228        //receiver whose range has smallest gap to target. 
229        for (ReceiverBand r : receivers)
230        {
231          FrequencyRange receiverRange = r.getWidestRange();
232    
233          boolean currReceiverHasOverlap = receiverRange.overlaps(targetRange);
234          
235          //If the current receiver does not overlap the target range, and we
236          //already found one that does, don't bother w/ this receiver
237          if (foundOverlappingReceiver && !currReceiverHasOverlap)
238            continue;
239          
240          if (currReceiverHasOverlap)
241          {
242            //If this is the first overlapping receiver, save it
243            if (!foundOverlappingReceiver)
244            {
245              result = r;
246              foundOverlappingReceiver = true;
247            }
248            //Otherwise, make it the result only if it has a greater overlap
249            else
250            {
251              Frequency newOverlap =
252                receiverRange.getOverlapWith(targetRange).getWidth();
253              
254              Frequency oldOverlap =
255                result.getWidestRange().getOverlapWith(targetRange).getWidth();
256              
257              if (newOverlap.compareTo(oldOverlap) > 0)
258                result = r;
259            }
260          }
261          else //curr rcvr does not overlap AND we haven't found ANY w/ overlap yet
262          {
263            Frequency newGap =
264              receiverRange.getGapBetween(targetRange).getWidth();
265            
266            Frequency oldGap =
267              result.getWidestRange().getGapBetween(targetRange).getWidth();
268            
269            if (newGap.compareTo(oldGap) < 0)
270              result = r;
271          }
272        }
273        
274        return result;
275      }
276    
277      //============================================================================
278      // SELECTION BASED ON MULTIPLE-RANGE FREQUENCY SPECTRUM
279      //============================================================================
280    
281      /* (non-Javadoc)
282       * @see ReceiverSelectorAbs#selectReceivers(FrequencySpectrum)
283       */
284      @Override
285      public List<ReceiverSelection> selectReceivers(FrequencySpectrum targetSpectrum)
286      {
287        List<ReceiverSelection> result = new ArrayList<ReceiverSelection>();
288    
289        //Temporarily set originalSpecs based on targetSpectrum
290        boolean haveNullSpecs = (originalSpecs == null);
291        if (haveNullSpecs)
292          originalSpecs = makeResrcSpecFrom(targetSpectrum);
293        
294        boolean done = (receiverProvider == null);
295        
296        //Try to find receiver or rcvr combo that covers whole tgt range
297        if (!done)
298        {
299          List<ReceiverBand> perfectReceivers = completelyCover(targetSpectrum);
300          if (perfectReceivers.size() > 0)
301          {
302            result.add(new ReceiverSelection(perfectReceivers, originalSpecs,
303                                                               originalSpecs));
304            done = true;
305          }
306        }
307    
308        //If we can't cover the whole spectrum, give less than perfect options
309        if (!done)
310        {
311          //Make one selection for every receiver that overlaps
312          for (ReceiverBand r : receiverProvider.getReceivers())
313          {
314            if (r.getWidestRange().overlaps(targetSpectrum))
315              result.add(buildSelection(r));
316          }
317          
318          //Make one selection for every receiver combination that has overlap
319          //on more than one receiver
320          for (Set<ReceiverBand> combo :
321               receiverProvider.getValidReceiverCombinations())
322          {
323            List<ReceiverBand> overlappingReceivers = new ArrayList<ReceiverBand>();
324            
325            //Find all receivers in the combo that overlap the target range
326            for (ReceiverBand r : combo)
327            {
328              if (r.getWidestRange().overlaps(targetSpectrum))
329                overlappingReceivers.add(r);
330            }
331            
332            //If more than one receiver in the current combo overlaps,
333            //add this combo to the list of selections
334            if (overlappingReceivers.size() > 1)
335              result.add(buildSelection(overlappingReceivers));
336          }
337    
338          //Sort the list based on pctg of coverage
339          if (result.size() > 1)
340            Collections.sort(result, SELECTION_COMPARATOR);
341          
342          done = (result.size() > 0);
343        }
344        
345        //If we have no overlapping receivers at all, find the closest receiver
346        if (!done)
347          result.add(buildSelection(findBestReceiverFor(targetSpectrum)));
348    
349        if (haveNullSpecs)
350          originalSpecs = null;
351    
352        return result;
353      }
354      
355      /**
356       * Returns a list of receivers that completely cover the target frequency
357       * range AND that may be used simultaneously.  If the receiver container
358       * has no such combination of receivers, the returned list will be empty.
359       */
360      private List<ReceiverBand> completelyCover(FrequencySpectrum targetSpectrum)
361      {
362        List<ReceiverBand> selection = new ArrayList<ReceiverBand>();
363        
364        boolean keepLooking = (receiverProvider != null);
365        
366        //First see if any single receiver contains the whole spectrum
367        if (keepLooking)
368        {
369          for (ReceiverBand r : receiverProvider.getReceivers())
370          {
371            if (r.getWidestRange().contains(targetSpectrum.getSpan()))
372            {
373              selection.add(r);
374              keepLooking = false;
375              break;
376            }
377          }
378        }
379    
380        //If no single receiver can cover the whole range,
381        //see if any legal combinations of receivers can.
382        if (keepLooking)
383        {
384          //For each valid combination, see if the combined receiver ranges
385          //completely covers the target spectrum.
386          for (Set<ReceiverBand> combo :
387               receiverProvider.getValidReceiverCombinations())
388          {
389            FrequencySpectrum receiverSpectrum = new FrequencySpectrum();
390          
391            //Add each receiver's range to the spectrum
392            for (ReceiverBand r : combo)
393              receiverSpectrum.addCoveredRange(r.getWidestRange());
394    
395            //If the spectrum from the combined receivers completely covers
396            //each covered range of the target spectrum, then we have
397            //a combination of receivers that completely covers the target.
398            for (FrequencyRange targetRange : targetSpectrum.getCoveredRanges())
399            {
400              if (receiverSpectrum.getAmountCoveredAsFractionOf(targetRange) >= (1.0-EPSILON))
401              {
402                for (ReceiverBand r : combo)
403                  selection.add(r);
404                
405                keepLooking = false;
406                break;
407              }
408            }
409             
410            if (!keepLooking)
411              break;
412          }
413        }
414    
415        return selection;
416      }
417      
418      /** 
419       * Returns the receiver whose frequency range comes closest one of
420       * the covered ranges in the target spectrum.
421       */
422      private ReceiverBand findBestReceiverFor(FrequencySpectrum targetSpectrum)
423      {
424        Set<ReceiverBand> receivers =
425          (receiverProvider == null) ? null : receiverProvider.getReceivers();
426        
427        if ((receivers == null) || (receivers.size() == 0))
428          return null;
429        
430        //First, see if any receivers have overlap with target spectrum.
431        //If so, return the one with the biggest overlap.
432        ReceiverBand  bestReceiver   = null;
433        Frequency biggestOverlap = new Frequency("0.0");
434        
435        for (ReceiverBand r : receivers)
436        {
437          Frequency overlap =
438            r.getWidestRange().getOverlapWith(targetSpectrum).getAmountCovered();
439          
440          if (overlap.compareTo(biggestOverlap) > 0)
441          {
442            bestReceiver   = r;
443            biggestOverlap = overlap;
444          }
445        }
446        
447        //If none of the receivers had overlap with the target spectrum,
448        //find the receiver with the smallest gap to one of the spectrum's
449        //covered ranges.
450        if (bestReceiver == null)
451        {
452          FrequencyRange smallestGap = new FrequencyRange(); //an infinite range
453    
454          for (ReceiverBand r : receivers)
455          {
456            FrequencyRange gap =
457              targetSpectrum.getSmallestGapTo(r.getWidestRange());
458            
459            if (gap.getWidth().compareTo(smallestGap.getWidth()) < 0)
460            {
461              bestReceiver = r;
462              smallestGap  = gap;
463            }
464          }
465        }
466        
467        return bestReceiver;
468      }
469    
470      //============================================================================
471      // 
472      //============================================================================
473      
474      private ResourceSpecification makeResrcSpecFrom(FrequencyRange skyRange)
475      {
476        SkyFrequencySpecification skyFreqSpec = new SkyFrequencySpecification(skyRange);
477        
478        return ResourceSpecification.makeFrom(skyFreqSpec);
479      }
480      
481      private ResourceSpecification makeResrcSpecFrom(FrequencySpectrum skySpectrum)
482      {
483        ResourceSpecification specs = new ResourceSpecification();
484    
485        List<SkyFrequencySpecification> skyFreqSpecs = specs.getSkyFrequencySpecs();
486        
487        for (FrequencyRange skyRange : skySpectrum.getCoveredRanges())
488          skyFreqSpecs.add(new SkyFrequencySpecification(skyRange));
489        
490        return specs;
491      }
492      
493      /**
494       * Returns a new {@code ReceiverSelection} that holds {@code receiver},
495       * the original resource specifications, and resource specifications
496       * that have been modified based on the capabilities of {@code receiver}.
497       */
498      private ReceiverSelection buildSelection(ReceiverBand receiver)
499      {
500        ResourceSpecification modifiedSpecs = null;
501        ArrayList<ReceiverBand> receivers = new ArrayList<ReceiverBand>();
502    
503        if (receiver != null)
504        {
505          modifiedSpecs = originalSpecs.clone();
506        
507          modifiedSpecs.modifyToFit(receiver.getWidestRange());
508        
509          receivers.add(receiver);
510        }
511        else
512        {
513          modifiedSpecs = new ResourceSpecification();
514        }
515        
516        return new ReceiverSelection(receivers, originalSpecs, modifiedSpecs);
517      }
518      
519      /**
520       * Returns a new {@code ReceiverSelection} that holds {@code receivers},
521       * the original resource specifications, and resource specifications
522       * that have been modified based on the capabilities of the receivers.
523       */
524      private ReceiverSelection buildSelection(List<ReceiverBand> receivers)
525      {
526        FrequencySpectrum receiverSpectrum = new FrequencySpectrum();
527        
528        for (ReceiverBand r : receivers)
529          receiverSpectrum.addCoveredRange(r.getWidestRange());
530        
531        ResourceSpecification modifiedSpecs = originalSpecs.clone();
532        
533        modifiedSpecs.modifyToFit(receiverSpectrum);
534        
535        return new ReceiverSelection(receivers, originalSpecs, modifiedSpecs);
536      }
537    
538      //============================================================================
539      // 
540      //============================================================================
541    
542      private static class ReceiverSelectionComparator
543        implements Comparator<ReceiverSelection>
544      {
545        public int compare(ReceiverSelection rs1, ReceiverSelection rs2)
546        {
547          //We want the receivers with larger fractional coverage to precede others
548          return (int)Math.signum(rs2.getFractionalBandwidthCoverage() -
549                                  rs1.getFractionalBandwidthCoverage());
550        }
551      }
552      
553      //============================================================================
554      // 
555      //============================================================================
556      /*
557      //For quick, manual, testing
558      public static void main(String[] args)
559      {
560        ReceiverSelector rs = new SimpleReceiverSelector();
561        
562        rs.setTelescope(TelescopeType.VLA.getTelescope());
563        
564        ContinuumSpecification spec = new ContinuumSpecification();
565        spec.setCenterFrequency(new Frequency(21));
566        spec.setBandwidth(new Frequency(50,FrequencyUnits.MEGAHERTZ));
567        
568        for (Receiver r : rs.selectReceivers(spec))
569          System.out.println(r.getName());
570      }
571      */
572      /*
573      public static void main(String... args)
574      {
575        TelescopeType telescope = TelescopeType.EVLA;
576        
577        FrequencyRange tgtRange = new FrequencyRange(new Frequency("35.1"), new Frequency("45.0"));
578        
579        ReceiverBand rb = telescope.selectBestReceiverFor(tgtRange);
580        
581        System.out.println("Receiver " + rb.getDisplayName() + " has " +
582                           rb.getFrequencyRange().getOverlapWith(tgtRange) +
583                           " overlap w/ tgt range " + tgtRange);
584      }
585      */
586    }