001    package edu.nrao.sss.model.resource.evla;
002    
003    import java.math.BigDecimal;
004    import java.util.ArrayList;
005    import java.util.GregorianCalendar;
006    import java.util.List;
007    import java.util.Random;
008    
009    import static edu.nrao.sss.astronomy.PolarizationType.L;
010    import static edu.nrao.sss.astronomy.PolarizationType.R;
011    import static edu.nrao.sss.measure.FrequencyUnits.HERTZ;
012    import static edu.nrao.sss.measure.FrequencyUnits.KILOHERTZ;
013    import static edu.nrao.sss.measure.FrequencyUnits.MEGAHERTZ;
014    
015    import ca.nrc.widar.jaxb.vci.AutoCorrAlgorithmType;
016    import ca.nrc.widar.jaxb.vci.AutoCorrMode;
017    import ca.nrc.widar.jaxb.vci.AutoCorrSubset;
018    import ca.nrc.widar.jaxb.vci.BaseBand;
019    import ca.nrc.widar.jaxb.vci.BlbProdIntegration;
020    import ca.nrc.widar.jaxb.vci.BurstMode;
021    import ca.nrc.widar.jaxb.vci.BurstModeType;
022    import ca.nrc.widar.jaxb.vci.EnableDisableType;
023    import ca.nrc.widar.jaxb.vci.OnOffType;
024    import ca.nrc.widar.jaxb.vci.PhasedArray;
025    import ca.nrc.widar.jaxb.vci.PulsarBinning;
026    import ca.nrc.widar.jaxb.vci.PulsarGating;
027    import ca.nrc.widar.jaxb.vci.RadarMode;
028    import ca.nrc.widar.jaxb.vci.StartFromType;
029    import ca.nrc.widar.jaxb.vci.ToneExtraction;
030    import ca.nrc.widar.jaxb.vci.WbcPolProduct;
031    import ca.nrc.widar.jaxb.vci.WideBandCorrelator;
032    import edu.nrao.sss.astronomy.PolarizationType;
033    import edu.nrao.sss.astronomy.StokesParameter;
034    import edu.nrao.sss.electronics.DigitalSignal;
035    import edu.nrao.sss.electronics.LocalOscillator;
036    import edu.nrao.sss.electronics.SignalMixer;
037    import edu.nrao.sss.measure.Frequency;
038    import edu.nrao.sss.measure.FrequencyRange;
039    import edu.nrao.sss.measure.TimeDuration;
040    import edu.nrao.sss.measure.TimeUnits;
041    import edu.nrao.sss.model.resource.AntennaElectronics;
042    import edu.nrao.sss.model.resource.CorrelatorBaseband;
043    import edu.nrao.sss.model.resource.CorrelatorConfiguration;
044    import edu.nrao.sss.model.resource.CorrelatorName;
045    import edu.nrao.sss.model.resource.ReceiverBand;
046    import edu.nrao.sss.model.resource.TelescopeType;
047    import edu.nrao.sss.util.EnumerationUtility;
048    
049    /**
050     * Helper for test classes; builds widar components.
051     * <p>
052     * <b>Version Info:</b>
053     * <table style="margin-left:2em">
054     *   <tr><td>$Revision: 2200 $</td></tr>
055     *   <tr><td>$Date: 2009-04-15 16:00:47 -0600 (Wed, 15 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-12-09
061     */
062    public class WidarBuilder
063    {
064      private static final EnumerationUtility ENUM_UTIL =
065        EnumerationUtility.getSharedInstance();
066    
067      private Random random = new Random(System.currentTimeMillis());
068    
069      private SignalMixer     signalMixer;
070      private LocalOscillator locOsc;
071      
072      /**
073       * 
074       */
075      public WidarBuilder()
076      {
077        signalMixer = SignalMixer.makeSubtractiveMixer("dummy");
078        locOsc      = new LocalOscillator(new FrequencyRange(new Frequency("0.0"),
079                                                             new Frequency("99.0")));
080        signalMixer.getLocalOscillatorInputPipe().connectInputTo(locOsc);
081      }
082      
083      //============================================================================
084      // CORRELATOR
085      //============================================================================
086      
087      /**
088       * 
089       */
090      public EvlaWidarConfiguration makeCorrelator()
091      {
092        AntennaElectronics evla = new EvlaAntennaElectronics();
093        
094        EvlaWidarConfiguration widar = (EvlaWidarConfiguration)
095          CorrelatorConfiguration.makeFor(CorrelatorName.WIDAR, evla);
096    
097        configure(widar);
098        
099        return widar;
100      }
101      
102      /**
103       * 
104       */
105      public void configure(EvlaWidarConfiguration widar)
106      {
107        //Configure for a particular receiver
108        ReceiverBand[] bands = new ReceiverBand[0];
109        bands = TelescopeType.EVLA.getReceivers().toArray(bands);
110        AntennaElectronics evla = widar.getSignalSource();
111        evla.configureFor(bands[random.nextInt(bands.length)]);
112        evla.execute();
113    
114        for (CorrelatorBaseband bb : widar.getBasebands())
115          configure((WidarBaseband)bb);
116      }
117      
118      //============================================================================
119      // BASEBAND
120      //============================================================================
121    
122      /**
123       * 
124       */
125      public WidarBaseband makeBaseband()
126      {
127        return random.nextDouble() < 0.2 ? makeBasebandSinglet() : makeBasebandPair();
128      }
129      
130      /**
131       * 
132       */
133      public WidarBasebandPair makeBasebandPair()
134      {
135        DigitalSignal    ds1   = makeDigitalSignal();
136        PolarizationType polzn = ds1.getPolarization().equals(L) ? R : L;
137        DigitalSignal    ds2   = (DigitalSignal)ds1.clone(polzn);
138        
139        String ds1Name = ds1.getName();
140        if      (ds1Name.equals("A0")) ds2.appendToDevicePath(":C0");
141        else if (ds1Name.equals("A1")) ds2.appendToDevicePath(":C1");
142        else if (ds1Name.equals("C0")) ds2.appendToDevicePath(":A0");
143        else if (ds1Name.equals("C1")) ds2.appendToDevicePath(":A1");
144    
145        ds1 = mixDown(ds1);
146        ds2 = mixDown(ds2);
147    
148        WidarBasebandPair bbp = new WidarBasebandPair(ds1, ds2);
149        
150        configure(bbp);
151        
152        return bbp;
153      }
154      
155      /**
156       * 
157       */
158      public WidarBasebandSinglet makeBasebandSinglet()
159      {
160        DigitalSignal ds = mixDown(makeDigitalSignal());
161    
162        WidarBasebandSinglet bb = new WidarBasebandSinglet(ds);
163        
164        configure(bb);
165        
166        return bb;
167      }
168      
169      /**
170       * 
171       */
172      public void configure(WidarBaseband bb)
173      {
174        bb.setSinglePhaseCenter(random.nextDouble() < 0.8);
175        
176        //Delay models lifespan
177        if (random.nextBoolean())
178        {
179          TimeDuration delayModLife = bb.overrideDefaultDelayModelLifespan(true);
180          int seconds = 1 + random.nextInt(9);
181          delayModLife.set(""+seconds+"s");
182        }
183        
184        //Pulsar gating
185        if (random.nextDouble() < 0.2)
186        {
187          configure(bb.overrideDefaultPulsarGating(true));
188        }
189        
190        //Wide band correlation
191        if (random.nextDouble() < 0.2)
192        {
193          configure(bb.overrideDefaultWideBandCorrelator(true), bb);
194        }
195        
196        int sbCount = random.nextInt(17); //0..16
197        for (int i=1; i <= sbCount; i++)
198        {
199          WidarSubband sb = bb.makeNewSubband();
200          bb.addSubband(sb);
201          configure(sb);
202        }
203      }
204      
205      //============================================================================
206      // SUBBAND
207      //============================================================================
208      
209      /**
210       * Creates a new subband whose values were assigned somewhat randomly.
211       * The newly created subband may belong to a baseband pair, baseband singlet,
212       * or to no baseband at all.
213       */
214      public WidarSubband makeSubband()
215      {
216        WidarSubband sb = new WidarSubband();
217    
218        double rand = random.nextDouble();
219        
220        if (rand < 0.9)
221        {
222          WidarBaseband bb = makeBaseband();
223          bb.removeAllSubbands();
224          bb.addSubband(sb);
225        }
226        
227        configure(sb);
228        
229        return sb;
230      }
231      
232      /**
233       * 
234       */
235      public void configure(WidarSubband sb)
236      {
237        boolean haveBaseband = (sb.getBaseband() != null);
238        
239        if (random.nextDouble() < 0.5)
240          sb.setUseStage2Mixer(random.nextBoolean());
241        
242        int rqBits = 4;
243        if (haveBaseband)
244        {
245          int iqBits = sb.getBaseband().getInitialQuantization();
246          boolean unlikely = (random.nextDouble() < 0.2);
247          
248          //Bias towards 8->7 and 3->4
249          if (iqBits == 3)  rqBits = unlikely ? 7 : 4;
250          else              rqBits = unlikely ? 4 : 7;
251          
252          sb.setRequantization(rqBits);
253        }
254        
255        setFreqRangeFor(sb, haveBaseband);
256    
257        configure((WidarCorrelationProductGroup)sb.addNewCorrelationProductGroup());
258    
259        sb.setMixerPhaseErrorCorr(sb.getUseStage2Mixer() ? random.nextBoolean() : false);
260        
261        if (random.nextDouble() < 0.1)  sb.setSignalToNoiseRatio(random.nextInt(101));
262        if (random.nextDouble() < 0.1)  sb.setPulsarGatingPhase(random.nextInt(101));
263        
264        if (random.nextBoolean())  configure(sb.overrideDefaultAutoCorrMode  (true));
265        if (random.nextBoolean())  configure(sb.overrideDefaultBurstMode     (true));
266        if (random.nextBoolean())  configure(sb.overrideDefaultPhasedArray   (true));
267        if (random.nextBoolean())  configure(sb.overrideDefaultPulsarBinning (true));
268        if (random.nextBoolean())  configure(sb.overrideDefaultRadarMode     (true));
269        if (random.nextBoolean())  configure(sb.overrideDefaultToneExtraction(true));
270      }
271      
272      /**
273       * 
274       */
275      private DigitalSignal makeDigitalSignal()
276      {
277        int    quant = random.nextBoolean() ? 3 : 8;
278        String bw    = (quant == 3) ? "2.048" : "1.024";
279        String rate  = (quant == 3) ? "4.096" : "2.048";
280        
281        PolarizationType polzn = random.nextBoolean() ? L : R;
282        
283        int lowFreqMHz = random.nextInt(47952);
284        
285        Frequency bandwidth = new Frequency(bw);
286        Frequency low       = new Frequency(new BigDecimal(lowFreqMHz), MEGAHERTZ);
287        Frequency high      = low.clone().add(bandwidth);
288        
289        FrequencyRange range = new FrequencyRange(low, high);
290        
291        DigitalSignal ds = new DigitalSignal(range, polzn, new Frequency(rate), quant);
292        
293        String c1 = polzn.equals(R) ? "A" : "C"; 
294        String c2 = (quant == 3) ? "1" : "0";
295        ds.appendToDevicePath(":" + c1 + c2);
296    
297        return ds;
298      }
299      
300      private DigitalSignal mixDown(DigitalSignal ds)
301      {
302        DigitalSignal mixed;
303        
304        FrequencyRange sigRange  = ds.getCurrentRange();
305        Frequency      oldCenter = sigRange.getCenterFrequency();
306        Frequency      newCenter = sigRange.getWidth().divideBy("2.0");
307        
308        locOsc.tuneTo(oldCenter.add(newCenter));
309        mixed = (DigitalSignal)signalMixer.mix(ds);
310    
311        if (random.nextBoolean()) //mix again to reverse direction of frequencies
312        {
313          locOsc.tuneTo(sigRange.getWidth());
314          mixed = (DigitalSignal)signalMixer.mix(mixed);
315        }
316        
317        return mixed;
318      }
319      
320      private void setFreqRangeFor(WidarSubband sb, boolean haveBaseband)
321      {
322        //Bandwidth
323        double minHz = sb.getMinimumBandwidth().toUnits(HERTZ).doubleValue();
324        double maxHz = sb.getMaximumBandwidth().toUnits(HERTZ).doubleValue();
325        
326        double hertz = minHz + (maxHz - minHz) * random.nextDouble();
327        
328        sb.setBandwidth(new Frequency(BigDecimal.valueOf(hertz), HERTZ));
329        
330        //Center frequency
331        Frequency center;
332        if (!haveBaseband)
333        {
334          center = sb.getBandwidth().divideBy("2.0");
335        }
336        else
337        {
338          double kHz = sb.getBaseband().getBandwidth().toUnits(KILOHERTZ).doubleValue();
339          kHz *= random.nextDouble();
340          kHz = Math.rint(kHz);
341          center = new Frequency(BigDecimal.valueOf(kHz), KILOHERTZ);
342        }
343        sb.setCentralFrequency(center.normalize());
344      }
345    
346      //============================================================================
347      // CORRELATION PRODUCTS
348      //============================================================================
349      
350      /**
351       * @param group
352       */
353      public void configure(WidarCorrelationProductGroup group)
354      {
355        group.setRecirculationFactor(makeRecircFactor());
356        
357        configure(group.getIntegrationTime());
358        
359        if (random.nextDouble() < 0.2)
360          configure(group.overrideDefaultAutoCorrSubset(true));
361        
362        //Add polarization products
363        group.removeAllPolarizationProducts();
364        if (group.getAllowablePolarizationProducts().size() > 0)
365        {
366          boolean success = false;
367          while (!success)
368            for (StokesParameter sp : makePolarizations())
369              success |= group.addPolarizationProduct(sp);
370        }
371        
372        //Allocate BLBPs
373        int minCount = group.getSubband().getRequantization() == 4 ? 1 : 4;
374        group.setBlbpCount(random.nextInt(5) + minCount);
375      }
376    
377      public void configure(WbcPolProduct wpp, int id, int laggedId, int promptId)
378      {
379        wpp.setPpid(id);
380        wpp.setLaggedBBID(laggedId);
381        wpp.setPromptBBID(promptId);
382        
383        if (random.nextBoolean())
384          wpp.setIntegFactor(10 + random.nextInt(91));
385        
386        int mult = 1 + random.nextInt(64);
387        
388        //TODO temp code due to min of 64 in VCI schema
389        if (mult == 1) mult = 2;
390        
391        wpp.setSpectChannels(32 * mult);
392      }
393      
394      /**
395       * Randomly sets the integration time.
396       * This method sometimes sets the individual components and
397       * sometimes sets the total time.  Roughly once every 50 calls
398       * it sets the total time to its minimum.
399       */
400      public void configure(WidarIntegrationTime wit)
401      {
402        double r = random.nextDouble();
403        
404        if (r < 0.02)
405        {
406          wit.setTotalIntegrationTime(wit.getMinimumTotalIntegrationTime());
407        }
408        else if (r < 0.50)
409        {
410          BlbProdIntegration prodInt = new BlbProdIntegration();
411          configure(prodInt);
412    
413          wit.setMinimumHardwareIntegrationTime(
414            new TimeDuration(""+prodInt.getMinIntegTime(), TimeUnits.MICROSECOND));
415          
416          wit.setCorrelatorChipMultiplier     (prodInt.getCcIntegFactor());
417          wit.setLongTermAccumulatorMultiplier(prodInt.getLtaIntegFactor());
418          wit.setBackEndMultiplier            (prodInt.getCbeIntegFactor());
419        }
420        else
421        {
422          double microSec = 1.0 + random.nextDouble() * 1e6 * 30.0; //30s max
423          wit.setTotalIntegrationTime(new TimeDuration(""+microSec, TimeUnits.MICROSECOND));
424        }
425      }
426      
427      private int makeRecircFactor()
428      {
429        //No recirc 1/2 the time
430        if (random.nextBoolean())
431          return 1;
432        
433        int power = random.nextInt(7) + 1;
434        
435        return (int)Math.pow(2.0, power);
436      }
437      
438      /** Returns a list of 1, 2, or 4 unique stokes params based on L & R polzns. */
439      private List<StokesParameter> makePolarizations()
440      {
441        List<StokesParameter> polarizations = new ArrayList<StokesParameter>();
442        
443        //Do all 4 products half the time
444        if (random.nextBoolean())
445        {
446          polarizations.add(StokesParameter.LL);
447          polarizations.add(StokesParameter.RR);
448          polarizations.add(StokesParameter.LR);
449          polarizations.add(StokesParameter.RL);
450        }
451        //Do 2 products 1/4 of the time
452        else if (random.nextBoolean())
453        {
454          if (random.nextBoolean())
455          {
456            polarizations.add(StokesParameter.LL);
457            polarizations.add(StokesParameter.RR);
458          }
459          else
460          {
461            polarizations.add(StokesParameter.LR);
462            polarizations.add(StokesParameter.RL);
463          }
464        }
465        //Do 1 product 1/4 of the time
466        else
467        {
468          polarizations.add(random.nextBoolean() ? StokesParameter.LL : StokesParameter.RR);
469        }
470        
471        return polarizations;
472      }
473    
474      //============================================================================
475      // MISC
476      //============================================================================
477    
478      /**
479       * 
480       */
481      private void configure(AutoCorrMode acm)
482      {
483        acm.setStatus(ENUM_UTIL.getRandomValueFor(OnOffType.class));
484        
485        BlbProdIntegration prodInt = new BlbProdIntegration();
486        configure(prodInt);
487        acm.setBlbProdIntegration(prodInt);
488        
489        //Not ready to deal w/ BLBPs and BLBs yet
490      }
491      
492      /**
493       * 
494       */
495      private void configure(AutoCorrSubset acs)
496      {
497        acs.setAlgorithm(ENUM_UTIL.getRandomValueFor(AutoCorrAlgorithmType.class));
498        acs.setStartFrom(ENUM_UTIL.getRandomValueFor(StartFromType.class));
499        acs.setDwellTime(2 + random.nextInt(18));
500      }
501    
502      /**
503       * 
504       */
505      private void configure(BlbProdIntegration prodInt)
506      {
507        //2.5 - 400.0 microSec
508      //prodInt.setMinIntegTime(397.5 * random.nextDouble() + 2.5);
509        //TODO: vci says 250 is max; need to confirm
510        prodInt.setMinIntegTime(247.5 * random.nextDouble() + 2.5);
511        prodInt.setCcIntegFactor (random.nextInt(29)+1);
512        prodInt.setLtaIntegFactor(random.nextInt(29)+1);
513        prodInt.setCbeIntegFactor(random.nextInt(29)+1);
514        prodInt.setRecirculation(makeRecircFactor());
515      }
516      
517      /**
518       * 
519       */
520      private void configure(BurstMode bm)
521      {
522        //Current time is fine
523        if (VciJaxbUtil.DTF != null)
524          bm.setEpoch(VciJaxbUtil.DTF.newXMLGregorianCalendar(new GregorianCalendar()));
525    
526        if (random.nextDouble() < 0.8)
527        {
528          if (random.nextBoolean())
529          {
530            bm.setMode(BurstModeType.CMIB_CONTROLLED);
531            bm.setSchedule("TBD");
532          }
533          else
534          {
535            bm.setMode(BurstModeType.DUMPTRIG_CONTROLLED);
536          }
537        }
538        else
539        {
540          bm.setMode(BurstModeType.OFF);
541        }
542        
543        bm.setDuration   (random.nextInt(100) + 1);
544        bm.setBlankPeriod(random.nextInt(100) + 1);
545      }
546    
547      /**
548       * 
549       */
550      private void configure(PhasedArray pa)
551      {
552        //TODO code me
553      }
554    
555      /**
556       * 
557       */
558      private void configure(PulsarBinning pb)
559      {
560        //Not ready to deal w/ BLBPs and BLBs yet
561    
562        //Current time is fine
563        if (VciJaxbUtil.DTF != null)
564          pb.setEpoch(VciJaxbUtil.DTF.newXMLGregorianCalendar(new GregorianCalendar()));
565        
566        //Sometimes we'll omit, but if we won't include 2nd deriv w/out 1st
567        if (random.nextBoolean())
568        {
569          pb.setFirstDerivative(random.nextInt(201) - 100);
570          if (random.nextBoolean())
571            pb.setSecondDerivative(random.nextInt(201) - 100);
572        }
573        
574        if (random.nextBoolean())
575          pb.setNumBins(1000 + random.nextInt(3001));
576        
577        pb.setPeriod(1 + random.nextInt(100));
578        
579        pb.setStatus(ENUM_UTIL.getRandomValueFor(OnOffType.class));
580      }
581    
582      /**
583       * 
584       */
585      private void configure(PulsarGating pg)
586      {
587        //Current time is fine
588        if (VciJaxbUtil.DTF != null)
589          pg.setEpoch(VciJaxbUtil.DTF.newXMLGregorianCalendar(new GregorianCalendar()));
590        
591        //Sometimes we'll omit, but if we won't include 2nd deriv w/out 1st
592        if (random.nextBoolean())
593        {
594          pg.setFirstDerivative(random.nextInt(201) - 100);
595          if (random.nextBoolean())
596            pg.setSecondDerivative(random.nextInt(201) - 100);
597        }
598        
599        if (random.nextBoolean())
600          pg.setGateWidth((double)random.nextInt(1001) / 1000.0);
601        
602        pg.setPeriod((double)random.nextInt(10000) / 100.0);
603        
604        //Status: usually we'll enable, sometimes disable, sometimes omit
605        double d = random.nextDouble();
606        if (d < 0.1)
607          pg.setStatus(EnableDisableType.DISABLE);
608        else if (d < 0.9)
609          pg.setStatus(EnableDisableType.ENABLE);
610        //else leave at default
611      }
612    
613      /**
614       * 
615       */
616      private void configure(RadarMode rm)
617      {
618        if (random.nextBoolean())
619        {
620          rm.setStatus(OnOffType.ON);
621          rm.setDuration(1 + random.nextInt(100));
622        }
623        else
624        {
625          rm.setStatus(OnOffType.OFF);
626        }
627        
628        if (random.nextDouble() < 0.2)
629          rm.setDestination("http://some.random.org/radarMode/data/");
630      }
631    
632      /**
633       * 
634       */
635      private void configure(ToneExtraction te)
636      {
637        if (random.nextDouble() < 0.2)
638          te.setDestination("http://some.random.org/toneExtraction/data/");
639    
640        if (random.nextBoolean())
641          te.setDwellTime(1 + random.nextInt(100));
642    
643        if (random.nextBoolean())
644          te.setIntegFactor(10 + random.nextInt(991)); //10..1000
645        
646        te.setNumTones(random.nextInt(33));
647        
648        te.setStatus(ENUM_UTIL.getRandomValueFor(EnableDisableType.class));
649      }
650    
651      /**
652       * 
653       */
654      private void configure(WideBandCorrelator wbc, WidarBaseband containingBB)
655      {
656        List<WbcPolProduct> products = wbc.getWbcPolProduct();
657        products.clear();
658        
659        BaseBand vciBB = containingBB.toVci()[0];
660        
661        int idA = vciBB.getBbA();
662        int idB = containingBB.isPair() ? vciBB.getBbB() : idA;
663        
664        //VCI says 0..32 is valid number; we'll stick w/ lower #
665        int count = random.nextInt(5); //0..4
666        int id = 0;
667        
668        while (++id <= count)
669        {
670          WbcPolProduct newProd = new WbcPolProduct();
671          configure(newProd, id, idA, idB);
672          products.add(newProd);
673        }
674      }
675      
676      //============================================================================
677      // 
678      //============================================================================
679      /*
680      public static void main(String... args) throws Exception
681      {
682        WidarBuilder builder = new WidarBuilder();
683        WidarBaseband bb = builder.makeBaseband();
684        bb.toVci();
685      }
686      */
687    }