001    package edu.nrao.sss.model.resource;
002    
003    import java.util.HashMap;
004    import java.util.HashSet;
005    import java.util.Map;
006    import java.util.Set;
007    
008    import edu.nrao.sss.astronomy.PolarizationType;
009    import edu.nrao.sss.electronics.LocalOscillatorPath;
010    import edu.nrao.sss.electronics.Signal;
011    import edu.nrao.sss.electronics.SignalManifold;
012    import edu.nrao.sss.electronics.SignalMixer;
013    import edu.nrao.sss.electronics.SignalPipe;
014    import edu.nrao.sss.electronics.SignalSource;
015    import edu.nrao.sss.measure.Frequency;
016    import edu.nrao.sss.measure.FrequencyRange;
017    
018    /**
019     * The portion of an antenna's electronics that immediately follows
020     * the collector of the sky signal.
021     * <p>
022     * <b>Version Info:</b>
023     * <table style="margin-left:2em">
024     *   <tr><td>$Revision: 1710 $</td></tr>
025     *   <tr><td>$Date: 2008-11-14 11:54:07 -0700 (Fri, 14 Nov 2008) $</td></tr>
026     *   <tr><td>$Author: dharland $ (last person to modify)</td></tr>
027     * </table></p>
028     * 
029     * @author David M. Harland
030     * @since 2007-11-01
031     */
032    public class AntennaFrontEnd
033    {
034      private ReceiverBand        receiverBand;
035      private Signal              signalTemplate;
036      private LocalOscillatorPath loSignalPath;
037      private SignalManifold      loSignalFork;
038      
039      private boolean useSubtractiveMixers;
040    
041      private HashMap<PolarizationType, SignalMixer> mixers;
042      private HashMap<PolarizationType, SignalPipe>  outputs;
043    
044      private boolean isOn;
045      
046      //============================================================================
047      // OBJECT CONSTRUCTION
048      //============================================================================
049    
050      /**
051       * Creates a new front end whose output signals are the same as those of
052       * its receiver band.  To create a front end that mixes its receiver band's
053       * signals up or down, see the
054       * {@link #AntennaFrontEnd(ReceiverBand, Set, SignalSource, int, FrequencyRange, boolean)
055       * other constructor}.
056       * This constructor is equivalent to calling
057       * {@code AntennaFrontEnd(band, polarizations, null, 1.0, true)}.
058       * Note that this front end is initially turned off.
059       * 
060       * @param band
061       *   the receiver band that this front end handles.
062       *   
063       * @param polarizations
064       *   the polarizations of the outputs of this feed.  This feed will have one
065       *   output for each of these polarizations.
066       *   
067       * @see #turnOn()
068       */
069      public AntennaFrontEnd(ReceiverBand band, Set<PolarizationType> polarizations)
070      {
071        this(band, polarizations, null, 1, null, true);
072      }
073    
074      /**
075       * Creates a new front end that is initially turned off.
076       * 
077       * @param band
078       *   the receiver band that this front end handles.
079       *   
080       * @param polarizations
081       *   the polarizations of the outputs of this feed.  This feed will have one
082       *   output for each of these polarizations.
083       *   
084       * @param localOscillator
085       *   the source of the signal, if any, to mix with the feed before producing
086       *   the outputs of this front end.  For front ends that do no mixing,
087       *   use a value of <i>null</i> here.
088       *   
089       * @param loMultiplier
090       *   the factor to apply to the signal of the {@code localOscillator}
091       *   before mixing with the sky signal.  This value will be ignored if
092       *   {@code localOscillator} is <i>null</i>.  If this feed has an LO
093       *   that does not get multiplied, use a value of <tt>1.0</tt> here.
094       *   
095       * @param filterRange
096       *   the range of the filter, if any, attached to the output of the
097       *   multiplier, or directly to the LO if there is no multiplier.
098       *   If no filter is used, pass a value of <i>null</i>.
099       * 
100       * @param mixDown
101       *   <i>true</i> if the {@code localOscillator}'s signal should be used
102       *   to mix this front end's signal down to lower frequencies, <i>false</i>
103       *   if it should use the LO to mix it upward.  Even if no mixing is required
104       *   (as indicated by a <i>null</i> {@code localOscillator}), this value has
105       *   an impact on the way
106       *   {@link #suggestLOFrequencyToMoveOutputCenterTo(Frequency)}
107       *   calculates its result.
108       *   
109       * @see #turnOn()
110       */
111      public AntennaFrontEnd(ReceiverBand band, Set<PolarizationType> polarizations,
112                             SignalSource localOscillator, int loMultiplier,
113                             FrequencyRange filterRange, boolean mixDown)
114      {
115        //TODO parameter checking
116        
117        receiverBand = band;
118        
119        signalTemplate = new Signal(receiverBand.getWidestRange(),
120                                    PolarizationType.UNSPECIFIED);
121        
122        useSubtractiveMixers = mixDown;
123        
124        outputs = new HashMap<PolarizationType, SignalPipe>();
125        
126        if (localOscillator == null)
127        {
128          initForNoLocOsc(polarizations);
129        }
130        else
131        {
132          initForLocOsc(polarizations, localOscillator, loMultiplier, filterRange);
133        }
134      }
135      
136      private void initForNoLocOsc(Set<PolarizationType> polarizations)
137      {
138        loSignalPath = null;
139        loSignalFork = null;
140        mixers       = null;
141    
142        //Pass the receiver band's signal straight to output
143        for (PolarizationType p : polarizations)
144          outputs.put(p, SignalPipe.makeAndWeldInputTo(new RadioSource(p)));
145      }
146      
147      private void initForLocOsc(Set<PolarizationType> polarizations,
148                                 SignalSource LO, int mult, FrequencyRange filterRange)
149      {
150        int outputCount = polarizations.size();
151        
152        loSignalPath = new LocalOscillatorPath(LO, mult, filterRange);
153        loSignalFork = new SignalManifold(outputCount);
154        loSignalFork.getInputPipe().connectInputTo(loSignalPath);
155        
156        mixers = new HashMap<PolarizationType, SignalMixer>();
157    
158        int outFork = 0;
159        
160        for (PolarizationType p : polarizations)
161        {
162          SignalMixer mixer =
163            useSubtractiveMixers ? SignalMixer.makeSubtractiveMixer("mix-down")
164                                 : SignalMixer.makeAdditiveMixer("mix-up");
165            
166          SignalPipe output = SignalPipe.makeAndWeldInputTo(mixer.getOutputPipe());
167          
168          mixers.put(p, mixer);
169          outputs.put(p, output);
170        
171          //Connect receiver band's signal to mixer
172          mixer.getInputPipe().connectInputTo(new RadioSource(p));
173          mixer.getLocalOscillatorInputPipe()
174                .connectInputTo(loSignalFork.getOutputPipe(outFork++));
175        }
176      }
177      
178      //============================================================================
179      // EVLA HELPERS
180      //============================================================================
181    
182      private static final Map<ReceiverBand, Integer> EVLA_LO_MULTIPLIERS =
183        new HashMap<ReceiverBand, Integer>();
184      
185      static
186      {
187        EVLA_LO_MULTIPLIERS.put(ReceiverBand.EVLA_Q,  3);
188        EVLA_LO_MULTIPLIERS.put(ReceiverBand.EVLA_Ka, 3);
189        EVLA_LO_MULTIPLIERS.put(ReceiverBand.EVLA_K , 2);
190      }
191    
192      private static final Map<ReceiverBand, FrequencyRange> EVLA_LO_FILTERS =
193        new HashMap<ReceiverBand, FrequencyRange>();
194      
195      static
196      {
197        EVLA_LO_FILTERS.put(ReceiverBand.EVLA_Q,
198                            new FrequencyRange(new Frequency("48.0"),
199                                               new Frequency("58.5")));
200        EVLA_LO_FILTERS.put(ReceiverBand.EVLA_Ka,
201                            new FrequencyRange(new Frequency("44.0"),
202                                               new Frequency("49.0")));
203        EVLA_LO_FILTERS.put(ReceiverBand.EVLA_K,
204                            new FrequencyRange(new Frequency("30.0"),
205                                               new Frequency("36.0")));
206      }
207      
208      /**
209       * Creates all of the EVLA front ends and returns them in a map whose
210       * key is the receiver band of that front end.
211       * 
212       * @param loSignals
213       *   a map whose key is a receiver band and whose value will be treated
214       *   as a local oscillator of the front end for that band.  Only those
215       *   front ends that use LOs need have an entry in this map.
216       *   
217       * @return
218       *   a map containing all the EVLA front ends, keyed by receiver band.
219       */
220      public static Map<ReceiverBand, AntennaFrontEnd>
221        makeEvlaFrontEnds(Map<ReceiverBand, SignalSource> loSignals)
222      {
223        HashMap<ReceiverBand, AntennaFrontEnd> frontEnds =
224          new HashMap<ReceiverBand, AntennaFrontEnd>();
225        
226        HashSet<PolarizationType> polarizations = new HashSet<PolarizationType>();
227        polarizations.add(PolarizationType.L);
228        polarizations.add(PolarizationType.R);
229    
230        for (ReceiverBand band : TelescopeType.EVLA.getReceivers())
231        {
232          int multiplier =
233            EVLA_LO_MULTIPLIERS.containsKey(band) ? EVLA_LO_MULTIPLIERS.get(band) : 1;
234            
235          FrequencyRange filterRange = EVLA_LO_FILTERS.get(band);
236          
237          frontEnds.put(band,
238                        new AntennaFrontEnd(band, polarizations, loSignals.get(band),
239                                            multiplier, filterRange, true));
240        }
241        
242        return frontEnds; 
243      }
244      
245      //============================================================================
246      // 
247      //============================================================================
248    
249      /**
250       * Returns the receiver band handled by this front end.
251       * @return the receiver band handled by this front end.
252       */
253      public ReceiverBand getBand()
254      {
255        return receiverBand;
256      }
257      
258      /**
259       * Returns the polarizations of the outputs produced by this front end.
260       * This front end will have one output for each of these polarizations.
261       * 
262       * @return the polarizations of the outputs produced by this front end.
263       */
264      public Set<PolarizationType> getPolarizations()
265      {
266        return new HashSet<PolarizationType>(outputs.keySet());
267      }
268    
269      /**
270       * Returns the output of this front end that produces signals with the given
271       * polarization.  If this front end has no such outputs, <i>null</i> is
272       * returned.
273       * 
274       * @param polarizationType
275       *   the polarization of signals produced by the returned output.
276       *   
277       * @return
278       *   an output whose signals have {@code polarizationType} polarization.
279       */
280      public SignalPipe getOuputPipe(PolarizationType polarizationType)
281      {
282        return outputs.get(polarizationType);
283      }
284      
285      /**
286       * Returns the local oscillator path, if any, for this front end.
287       * 
288       * @return
289       *   the local oscillator path for this front end.  If this front end
290       *   has no local oscillator path, <i>null</i> is returned.
291       */
292      public LocalOscillatorPath getLocalOscillatorPath()
293      {
294        return loSignalPath;
295      }
296      
297      /**
298       * Returns a frequency that can be used to shift the center frequency
299       * of this front end to {@code ifCenter}.
300       * <p><i>TODO: determine how to handle impossible requests.</i></p>
301       * 
302       * @param ifCenter
303       *   the central frequency produced by mixing this front end's signal
304       *   with a local oscillator's signal.
305       *   
306       * @return a frequency that can be used to shift the center frequency
307       *         of this front end to {@code ifCenter}.
308       */
309      public Frequency suggestLOFrequencyToMoveOutputCenterTo(Frequency ifCenter)
310      {
311        Frequency loFreq  = null;
312        Frequency currCtr = receiverBand.getNominalRange().getCenterFrequency();
313        
314        if (loSignalPath == null)
315        {
316          loFreq = useSubtractiveMixers ? ifCenter.clone().add(currCtr)
317                                        : ifCenter.clone().subtract(currCtr);
318        }
319        else
320        {
321          loFreq = useSubtractiveMixers ?
322            loSignalPath.getLOFreqToMoveCenterViaSubtraction(currCtr, ifCenter) : 
323            loSignalPath.getLOFreqToMoveCenterViaAddition(currCtr, ifCenter);
324        }
325        
326        return loFreq;
327      }
328    
329      /**
330       * Executes all the devices that are connected to the outputs of this
331       * front end.
332       */
333      public void execute()
334      {
335        for (SignalPipe output : outputs.values())
336          output.executeFromStartOfChainUpTo(null);
337      }
338    
339      /**
340       * Returns <i>true</i> if this front end is picking up signals.
341       * @return <i>true</i> if this front end is picking up signals.
342       */
343      public boolean isOn()  { return isOn; }
344      
345      /**
346       * Returns <i>true</i> if this front end is not picking up signals.
347       * @return <i>true</i> if this front end is not picking up signals.
348       */
349      public boolean isOff()  { return !isOn; }
350      
351      /** Turns on this front end so that it picks up signals. */
352      public void turnOn()  { isOn = true; }
353      
354      /** Turns off this front end so that it does not pick up signals. */
355      public void turnOff() { isOn = false; }
356      
357      //============================================================================
358      // HELPER CLASSES
359      //============================================================================
360      
361      /**
362       * This class is a stand-in for a sky source.  It produces a signal equivalent
363       * to the feed of this front end, polarized according to the particular output
364       * that is using it.
365       */
366      private class RadioSource implements SignalSource
367      {
368        private Signal signal;
369        
370        RadioSource(PolarizationType p)
371        {
372          signal = signalTemplate.clone(p);
373          signal.appendToDevicePath(receiverBand.name()+"-"+p.name());
374        }
375        
376        public Signal getSignal()
377        {
378          return isOn ? signal.clone() : null;
379        }
380      }
381    
382      //============================================================================
383      // 
384      //============================================================================
385      /*
386      public static void main(String[] args) throws Exception
387      {
388        //L301s
389        Frequency low  = new Frequency(11904.0, edu.nrao.sss.measure.FrequencyUnits.MEGAHERTZ);
390        Frequency high = new Frequency(20352.0, edu.nrao.sss.measure.FrequencyUnits.MEGAHERTZ);
391        Frequency step = new Frequency(  256.0, edu.nrao.sss.measure.FrequencyUnits.MEGAHERTZ);
392        
393        edu.nrao.sss.electronics.LocalOscillator L301_1 =
394          new edu.nrao.sss.electronics.LocalOscillator(
395            new edu.nrao.sss.measure.FrequencyRange(low,high),step);
396    
397        Map<ReceiverBand, SignalSource> LOs = new HashMap<ReceiverBand, SignalSource>();
398        LOs.put(ReceiverBand.EVLA_Q,  L301_1);
399        LOs.put(ReceiverBand.EVLA_Ka, L301_1);
400        LOs.put(ReceiverBand.EVLA_K,  L301_1);
401        
402        Map<ReceiverBand, AntennaFrontEnd> FEs = AntennaFrontEnd.makeEvlaFrontEnds(LOs);
403        
404        for (AntennaFrontEnd fe : FEs.values())
405        {
406          if (LOs.get(fe.getBand()) != null)
407          {
408            L301_1.tuneTo(fe.suggestLOFrequencyToMoveOutputCenterTo(new Frequency(12.0)));
409          }
410          
411          fe.execute();
412          
413          for (PolarizationType p : fe.getPolarizations())
414          {
415            System.out.print(fe.getBand().name()+", ");
416            System.out.println(p.toString());
417            System.out.println(fe.getOuputPipe(p).getSignal());
418            System.out.println();
419          }
420        }
421      }
422      */
423    }