001    package edu.nrao.sss.model.project;
002    
003    import edu.nrao.sss.astronomy.CelestialCoordinateSystem;
004    import edu.nrao.sss.astronomy.DopplerTracker;
005    import edu.nrao.sss.astronomy.Epoch;
006    import edu.nrao.sss.astronomy.SkyPosition;
007    import edu.nrao.sss.astronomy.CoordinateConversionException;
008    import edu.nrao.sss.astronomy.VelocityFrame;
009    import edu.nrao.sss.measure.ArcUnits;
010    import edu.nrao.sss.measure.Angle;
011    import edu.nrao.sss.measure.Frequency;
012    import edu.nrao.sss.measure.FrequencyUnits;
013    import edu.nrao.sss.measure.JulianDate;
014    import edu.nrao.sss.measure.LinearVelocityUnits;
015    import edu.nrao.sss.measure.LocalSiderealTime;
016    import edu.nrao.sss.model.project.scan.Scan;
017    import edu.nrao.sss.model.project.scan.ScanIntent;
018    import edu.nrao.sss.model.project.scan.DelayScan;
019    import edu.nrao.sss.model.project.scan.FocusScan;
020    import edu.nrao.sss.model.project.scan.PointingScan;
021    import edu.nrao.sss.model.project.scan.SimpleScan;
022    import edu.nrao.sss.model.project.scan.SwitchingScan;
023    import edu.nrao.sss.model.project.scan.TippingOrder;
024    import edu.nrao.sss.model.project.scan.TippingScan;
025    import edu.nrao.sss.model.project.scan.AntennaWrap;
026    import edu.nrao.sss.model.source.Source;
027    import edu.nrao.sss.model.resource.CorrelatorName;
028    import edu.nrao.sss.model.resource.Resource;
029    import edu.nrao.sss.model.resource.ReceiverBand;
030    import edu.nrao.sss.model.resource.TelescopeBackend;
031    import edu.nrao.sss.model.resource.vla.CorrelatorMode;
032    import edu.nrao.sss.model.resource.vla.IFCode;
033    import edu.nrao.sss.model.resource.vla.IFPair;
034    import edu.nrao.sss.model.resource.vla.ProcessingType;
035    import edu.nrao.sss.model.resource.vla.VlaConfiguration;
036    import edu.nrao.sss.util.StringUtil;
037    import edu.nrao.sss.util.SourceNotFoundException;
038    import edu.nrao.sss.validation.FailureSeverity;
039    import edu.nrao.sss.validation.ValidationException;
040    import edu.nrao.sss.validation.ValidationFailure;
041    
042    import java.math.BigDecimal;
043    import java.util.Date;
044    import java.util.List;
045    import java.util.Map;
046    import java.util.Set;
047    
048    import org.apache.log4j.Logger;
049    
050    /**
051     * This class provides methods for converting a scheduling block to a Jython
052     * Observe Script for the EVLA Monitor and Control system.
053     * <p>This class expects the M&C system to provide the generated script with
054     * this set of header code:
055     * <code><pre>
056     * from edu.nrao.evla.observe import Array
057     * from edu.nrao.evla.observe import Antenna
058     * from edu.nrao.evla.observe import LoIfSetup
059     * from edu.nrao.evla.observe import VLALoIfSetup
060     * from edu.nrao.evla.observe import Source
061     * from edu.nrao.evla.observe import Subarray
062     * from edu.nrao.evla.observe import Fringefinders
063     * from edu.nrao.evla.observe import VlaCorrelatorSetup
064     * from edu.nrao.evla.observe import Intention
065     * from edu.nrao.evla.observe import AzElInterferometerModel
066     * </pre></code>
067     * as well as defining the {@code array} and {@code subarray} variables to
068     * point at appropriate {@code edu.nrao.evla.observe.Array} and {@code
069     * edu.nrao.evla.observe.Subarray} java objects.</p>
070     * <p>The scripts generated by this class will also depend on standard helper
071     * scripts being available at {@code /home/mchost/evla/include}. Specifically:</p>
072     * <table border="1">
073     * <thead><tr><th>Script</th><th>Use</th></tr></thead>
074     * <tbody>
075     * <tr><td>focus.py</td><td></td></tr>
076     * <tr><td>nodder.py</td><td></td></tr>
077     * <tr><td>nod.py</td><td></td></tr>
078     * <tr><td>onsource.py</td><td></td></tr>
079     * <tr><td>pointCycle.py</td><td></td></tr>
080     * <tr><td>point.py</td><td></td></tr>
081     * <tr><td>printers.py</td><td></td></tr>
082     * <tr><td>radecname.py</td><td></td></tr>
083     * <tr><td>raster.py</td><td></td></tr>
084     * <tr><td>refPoint.py</td><td></td></tr>
085     * <tr><td>sdvlbi.py</td><td></td></tr>
086     * <tr><td>tip.py</td><td></td></tr>
087     * <tr><td>tmjd.py</td><td></td></tr>
088     * <tr><td>vlapoint.py</td><td></td></tr>
089     * <tr><td>vlaraster.py</td><td></td></tr>
090     * </tbody>
091     * </table>
092     */
093    public class ObserveScriptBuilder extends AbstractScheduleIterator
094    {
095            private static final Logger log = Logger.getLogger(ObserveScriptBuilder.class);
096    
097      private static final StringUtil sUtil = StringUtil.getInstance();
098    
099      private StringBuilder  script           = null;
100            private StringBuilder  indentString     = null;
101      private String         projectCode      = null;
102      private Date           overrideStart    = null;
103      private boolean        onLastScan       = false;
104      private boolean        foundTippingScan = false;
105      private boolean        foundPtgScan     = false;
106      private boolean        swappedIFs       = false;
107    
108            /**
109             * Creates an ObserveScriptBuilder.
110             */
111            public ObserveScriptBuilder() {}
112    
113            /**
114             * @return {@code getScriptAsStringBuilder(sb).toString()}.
115             */
116            public String toScript(SchedulingBlock sb)
117        throws ValidationException
118            {
119                    return getScriptAsStringBuilder(sb).toString();
120            }
121    
122            /**
123             * @return {@code getScriptAsStringBuilder(sb, start).toString()}.
124             */
125            public String toScript(SchedulingBlock sb, Date start)
126        throws ValidationException
127      {
128                    return getScriptAsStringBuilder(sb, start).toString();
129      }
130    
131            /**
132             * @return a StringBuilder holding python code that will carry out
133             * instructions represented by {@code sb}.  The python code will calculate
134             * all source positions and scan start times using startTime as a reference.
135             */
136            public StringBuilder getScriptAsStringBuilder(SchedulingBlock sb)
137        throws ValidationException
138      {
139        return getScriptAsStringBuilder(sb, null);
140      }
141    
142      /**
143       * If start != null, override the SB's start time to be {@code start} even if
144       * sb is supposed to be dynamically scheduled.  NOTE: in the event that sb is
145       * dynamic, a dynamic schedule is still generated, but {@code start} is used to
146       * calculate all slew times.
147       *
148       * @see #getScriptAsStringBuilder(SchedulingBlock)
149       */
150            public StringBuilder getScriptAsStringBuilder(SchedulingBlock sb, Date start)
151        throws ValidationException
152      {
153        this.overrideStart = start;
154    
155        this.script = new StringBuilder();
156        this.indentString           = new StringBuilder();
157    
158        this.updatedRefPtgResultsAvailable = false;
159        this.refPtgResultsAvailable        = false;
160        this.refPtgInUse                   = false;
161        this.foundTippingScan              = false;
162        this.foundPtgScan                  = false;
163        this.onLastScan                    = false;
164    
165        this.projectCode = sb.getProgramBlock().getProject().getProjectCode();
166    
167        try
168        {
169          iterateOverSchedulingBlock(sb);
170        }
171    
172        catch (CoordinateConversionException cce)
173        {
174          getValidationFailures().add(new ValidationFailure(
175            "There has been an internal error trying to convert coordinate systems.",
176            "There has been an internal error trying to convert coordinate systems." + cce.getMessage(),
177            FailureSeverity.ERROR,
178            sb,
179            "",
180            ""
181          ));
182    
183          log.error(cce);
184        }
185    
186        catch (IllegalArgumentException iae)
187        {
188          getValidationFailures().add(new ValidationFailure(
189            iae.getMessage(),
190            iae.getMessage(),
191            FailureSeverity.ERROR,
192            sb,
193            "",
194            ""
195          ));
196        }
197    
198        catch (IllegalStateException ise)
199        {
200          getValidationFailures().add(new ValidationFailure(
201            ise.getMessage(),
202            ise.getMessage(),
203            FailureSeverity.ERROR,
204            sb,
205            "",
206            ""
207          ));
208        }
209    
210        // This is being caught by validation, only need to handle it separately
211        // here if we're not validating for some reason.
212        catch (SourceNotFoundException snf)
213        {
214          if (!getValidatesSchedulingBlock())
215          {
216            getValidationFailures().add(new ValidationFailure(
217              snf.getMessage(),
218              snf.getMessage(),
219              FailureSeverity.ERROR,
220              sb,
221              "",
222              ""
223            ));
224          }
225        }
226    
227        return this.script;
228      }
229    
230      public void setSimulatorStartPosition(Angle az, Angle el)
231        throws IllegalArgumentException
232      {
233        this.sim.setCurrentAntennaAzimuth(az);
234        this.sim.setCurrentAntennaElevation(el);
235      }
236    
237      protected void beforeIteratingAction()
238      {
239        // override the start time (even if we're dynamically scheduled) if a time
240        // was specified.
241        if (this.overrideStart != null)
242        {
243          this.startMjd = new JulianDate(this.overrideStart);
244          this.dynamicSched = false;
245        }
246    
247        if (this.dynamicSched)
248        {
249          this.script.append(this.indentString);
250          this.script.append("startTime = array.time()\n");
251        }
252      }
253    
254      protected void beforeLastScanAction(Scan scan)
255      {
256        this.onLastScan = true;
257      }
258    
259      protected void afterLastScanAction(Scan scan)
260      {
261        this.script.insert(0, "programName = 'OPT'\n");
262        this.script.insert(0, "obsCode = '\"" + this.projectCode + "\"'\n");
263    
264        if (this.foundTippingScan)
265          this.script.insert(0, "execfile('/home/mchost/evla/include/tip.py')\n\n");
266    
267        if (this.foundPtgScan)
268        {
269          this.script.insert(0, "execfile('/home/mchost/evla/include/vlapoint.py')\n");
270          this.script.insert(0, "execfile('/home/mchost/evla/include/point.py')\n");
271          this.script.insert(0, "execfile('/home/mchost/evla/include/raster.py')\n");
272        }
273    
274        this.script.insert(0, "execfile('/home/mchost/evla/include/printers.py')\n");
275        this.script.insert(0, "from math import cos, sin, asin, pi, fabs\n");
276        this.script.insert(0, getFormattedOperatorComments(scan.getSchedulingBlock()));
277        this.script.insert(0, "# EVLA Project Code: " + this.projectCode + "\n\n");
278    
279        // Output a bogus termination source to ensure our last scan actually
280        // finishes (this is due somewhat to the fact that the Executor scans ahead
281        // 1 scan)
282        this.script.append(this.indentString);
283        this.script.append("\n\n# Bogus Terminations Scan (to ensure the user's last scan gets its full time).\n");
284    
285                    this.script.append(this.indentString);
286                    this.script.append("myPrint('Setting up termination source.')\n");
287    
288                    this.script.append(this.indentString);
289        this.script.append("intentionEnd = Intention()\n");
290    
291                    this.script.append(this.indentString);
292        this.script.append("intentionEnd.addIntent('suppressData=\"true\"')\n");
293    
294        Source src = scan.getSource(this.startMjd.toDate());
295    
296                    this.script.append(this.indentString);
297        this.script.append("src = subarray.getSource()\n");
298    
299                    this.script.append(this.indentString);
300        this.script.append("src.setIntention(intentionEnd)\n");
301    
302                    this.script.append(this.indentString);
303        this.script.append("subarray.setSource(src)\n");
304    
305        // Since this is the last scan, if we're dynamic, startTime didn't
306        // automatically get updated, so we do it here.
307                    this.script.append(this.indentString);
308                    this.script.append("subarray.execute(");
309        if (this.dynamicSched)
310        {
311          this.script.append("startTime + ");
312          this.script.append(sUtil.formatNoScientificNotation(this.endMjd.modifiedValue().subtract(this.startMjd.modifiedValue()), 1, 8));
313        }
314    
315        else
316          this.script.append(sUtil.formatNoScientificNotation(this.endMjd.modifiedValue(), 1, 8));
317    
318                    this.script.append(")\n");
319      }
320    
321            /**
322             * Generates python code that is necessary regardless of the scan mode, that
323             * must come before each scan.
324             */
325      protected void beforeScanAction(Scan scan)
326      {
327        swappedIFs = false;
328        
329        this.script.append(this.indentString);
330        this.script.append("\n\n# Scan num. ");
331        this.script.append(this.currentScanNumber);
332        this.script.append("\n");
333    
334        String loifVar    = "loif";
335        String loifVarVla = "vlaloif";
336        String corrmodeVar = "corrmode";
337    
338        if (!scan.getUseResourceOfPriorScan())
339        {
340          createLoIfSetup(loifVar, loifVarVla, scan, this.script);
341          createVlaCorrelatorSetup(corrmodeVar, loifVarVla, scan, this.script);
342        }
343    
344        // TippingScans do not have a source set, so only do the source/intention
345        // setup if this is not a TippingScan
346        String operatorMessage = "";
347        if (!(scan instanceof TippingScan))
348        {
349          Source src = scan.getSource(this.startMjd.toDate());
350    
351          operatorMessage += "Setting up source " + src.getName() + " ";
352    
353          String sourceVar = "src";
354          createSource(sourceVar, src, scan, this.script);
355    
356          if (!scan.getUseResourceOfPriorScan())
357          {
358            //intents = Intention()
359            this.script.append(this.indentString);
360            this.script.append("intents = Intention()\n");
361          }
362    
363          // Make a new intents object out of the old one but don't allow the
364          // CalibratorCode and ObserveMode entries to be copied over.
365          else
366          {
367            this.script.append(this.indentString);
368            this.script.append("newIntents = Intention()\n");
369            this.script.append(this.indentString);
370            this.script.append("for i in intents.getIntents():\n");
371            this.script.append(this.indentString);
372            this.script.append("\tif not (i.startswith('ObserveMode') or i.startswith('CalibratorCode')):\n");
373            this.script.append(this.indentString);
374            this.script.append("\t\tnewIntents.addIntent(i)\n");
375            this.script.append(this.indentString);
376            this.script.append("intents = newIntents\n");
377          }
378    
379          // intents.addIntent("ObsCode", "projCode")
380          this.script.append(this.indentString);
381          this.script.append("intents.addIntent('ObsCode', obsCode)\n");
382    
383          // intents.addIntent("ScanNumber","1")
384          this.script.append(this.indentString);
385          this.script.append("intents.addIntent('ScanNumber=");
386          this.script.append(this.currentScanNumber);
387          this.script.append("')\n");
388    
389          // intents.addIntent("SourceQualifier","1")
390          /* TODO:  Figure out if scientists actually want this or not and how to implement it.
391          this.script.append(this.indentString);
392          this.script.append("intents.addIntent('SourceQualifier', '");
393          this.script.append(this.currentScanNumber);
394          this.script.append("')\n");
395          */
396    
397          this.script.append(this.indentString);
398          this.script.append("intents.addIntent('stopLST', ");
399    
400          if (this.dynamicSched)
401          {
402            // intents.addIntent('stopLST', str(array.lst(array.time()+140./86400)))
403            this.script.append("str(array.lst(startTime + ");
404            this.script.append(sUtil.formatNoScientificNotation(this.endMjd.modifiedValue().subtract(this.startMjd.modifiedValue()), 1, 8));
405            this.script.append("))");
406          }
407    
408          else
409          {
410            LocalSiderealTime stopLST = new LocalSiderealTime(this.endMjd);
411            this.script.append("'");
412            this.script.append(sUtil.formatNoScientificNotation(stopLST.toTimeOfDay().toFractionOfDay(), 1, 8));
413            this.script.append("'");
414          }
415    
416          this.script.append(")\n");
417    
418          Set<ScanIntent> intents = scan.getIntents();
419          if (!intents.isEmpty())
420          {
421            this.script.append(this.indentString);
422            this.script.append("intents.addIntent('ScanIntent=\"");
423    
424            String observeMode = null;
425            boolean setCalCode = false;
426    
427            // Fill the "ScanIntent" Intention
428            for (ScanIntent intent : intents)
429            {
430              this.script.append(intent.getSdmText());
431              this.script.append(" ");
432    
433              switch (intent)
434              {
435                // Not supported yet.
436                case CALIBRATE_ABSOLUTE_POSITION:
437                  break;
438    
439                case CALIBRATE_BANDPASS:
440                  setCalCode = true;
441                  break;
442    
443                case CALIBRATE_COMPLEX_GAIN:
444                  setCalCode = true;
445                  break;
446    
447                  // Not supported yet.
448                case CALIBRATE_DELAY_AMPLITUDE_STYLE:
449                  break;
450    
451                case CALIBRATE_DELAY_PHASE_STYLE:
452                  setCalCode = true;
453                  observeMode = "D";
454                  break;
455    
456                case CALIBRATE_FLUX_DENSITY_SCALE:
457                  setCalCode = true;
458                  break;
459    
460                  // Not supported yet.
461                case CALIBRATE_FOCUS:
462                  break;
463    
464                case CALIBRATE_OFFSET_POINTING:
465                  setCalCode = true;
466                  observeMode = "IR";
467                  break;
468    
469                case CALIBRATE_POLARIZATION_ANGLE:
470                  setCalCode = true;
471                  break;
472    
473                case CALIBRATE_POLARIZATION_LEAKAGE:
474                  setCalCode = true;
475                  break;
476    
477                  // Not supported yet.
478                case DETERMINE_ANTENNA_GLOBAL_POINTING_MODEL:
479                  break;
480    
481                  // Not supported yet.
482                case DETERMINE_AUTOPHASE:
483                  break;
484    
485                case DETERMINE_OPACITY_TIPPING_STYLE:
486                  observeMode = "TE";
487                  break;
488    
489                  // Not supported yet.
490                case DETERMINE_RFI:
491                  break;
492    
493                  // Not supported yet.
494                case DETERMINE_SINGLE_DISH_POINTING:
495                  break;
496    
497                  // Not supported yet.
498                case MAP_ANTENNA_SURFACE:
499                  break;
500    
501                  // Not supported yet.
502                case OBSERVE_PULSAR:
503                  break;
504    
505                case OBSERVE_TARGET:
506                  break;
507    
508                  // Not supported yet.
509                case OTHER:
510                  break;
511    
512                  // Not supported yet.
513                case TIME_PULSAR:
514                  break;
515              }
516            }
517    
518            // remove the last space
519            this.script.deleteCharAt(this.script.length() - 1);
520    
521            // finish the method call.
522            this.script.append("\"')\n");
523    
524            // append an ObserveMode if necessary
525            if (observeMode != null)
526            {
527              // intents.addIntent("ObserveMode","D|IR")
528              this.script.append(this.indentString);
529              this.script.append("intents.addIntent('ObserveMode=\"");
530              this.script.append(observeMode);
531              this.script.append("\"')\n");
532            }
533    
534            String calCode = " ";
535            if (setCalCode)
536            {
537              //Now we know it's a calibrator so default it to "Z"
538              calCode = "Z";
539    
540              // Look up A,B,C,T calibrator code in the source.  If we can't find
541              // one, leave it set to 'Z';
542              Map<String,String> udvs = src.getUserDefinedValues();
543              String posErr = udvs.get("Positional Error");
544    
545              if (posErr != null && posErr.length() > 0)
546              {
547                if (posErr.startsWith("< 0.002"))
548                  calCode = "A";
549    
550                else if (posErr.startsWith("0.002 - 0.01"))
551                  calCode = "B";
552    
553                else if (posErr.startsWith("0.01 - 0.15"))
554                  calCode = "C";
555    
556                else if (posErr.startsWith("> 0.15"))
557                  calCode = "T";
558              }
559            }
560    
561            //intents.addIntent("CalibratorCode", "Z")
562            this.script.append(this.indentString);
563            this.script.append("intents.addIntent('CalibratorCode=\"");
564            this.script.append(calCode);
565            this.script.append("\"')\n");
566          }
567    
568          // Only output these intentions if getUseResourceOfPriorScan() is false.
569          // If it is true, we still need these intentions set, however, we are
570          // doing that by re-using the previous intents script variable so they
571          // are already set!
572          if (!scan.getUseResourceOfPriorScan())
573          {
574            VlaConfiguration conf = getVlaConfiguration(scan);
575            CorrelatorMode mode = conf.getCorrelatorMode();
576            char[] restFrame = null;
577            char[] velocConv = null;
578    
579            Date start = this.startMjd.toDate();
580    
581            //intents.addIntent('BandwidthCode="4444"')
582            /* 2008-Dec-31 email from Ken S. to Emmanuel M. said this is not needed
583               this.script.append("intents.addIntent('BandwidthCode=\"");
584               this.script.append(conf.getBandwidthCode(IFCode.A).getCodeNumber());
585               this.script.append(conf.getBandwidthCode(IFCode.B).getCodeNumber());
586               this.script.append(conf.getBandwidthCode(IFCode.C).getCodeNumber());
587               this.script.append(conf.getBandwidthCode(IFCode.D).getCodeNumber());
588               this.script.append("\"')\n");
589             */
590            if (mode.uses(IFPair.AC) && !conf.isSkyFrequency(IFPair.AC))
591            {
592              Frequency acCenter = conf.getCentralFrequency(IFPair.AC);
593    
594              // varName.append('RestFreqA=xyz')
595              BigDecimal f = acCenter.toUnits(FrequencyUnits.MEGAHERTZ);
596              this.script.append(this.indentString);
597              this.script.append("intents.addIntent('RestFreqA=");
598              this.script.append(sUtil.formatNoScientificNotation(f, 0, 8));
599              this.script.append("')\n");
600    
601              this.script.append(this.indentString);
602              this.script.append("intents.addIntent('RestFreqC=");
603              this.script.append(sUtil.formatNoScientificNotation(f, 0, 8));
604              this.script.append("')\n");
605    
606              DopplerTracker acTracker = scan.getDopplerTracker("AC", start, acCenter);
607    
608              f = acTracker.getSourceVelocity().toUnits(LinearVelocityUnits.KILOMETERS_PER_SECOND);
609              this.script.append(this.indentString);
610              this.script.append("intents.addIntent('VelocityA=");
611              this.script.append(sUtil.formatNoScientificNotation(f, 0, 8));
612              this.script.append("')\n");
613    
614              this.script.append(this.indentString);
615              this.script.append("intents.addIntent('VelocityC=");
616              this.script.append(sUtil.formatNoScientificNotation(f, 0, 8));
617              this.script.append("')\n");
618    
619              // Check Velocity Frame
620              restFrame = new char[4];
621              restFrame[0] = 'L';
622              restFrame[1] = 'L';
623              restFrame[2] = 'L';
624              restFrame[3] = 'L';
625              VelocityFrame frame = acTracker.getVelocityFrame();
626              switch (frame)
627              {
628                case TOPOCENTRIC:
629                  restFrame[0] = 'T';
630                  restFrame[2] = 'T';
631                  break;
632    
633                case GEOCENTRIC:
634                  restFrame[0] = 'G';
635                  restFrame[2] = 'G';
636                  break;
637    
638                case BARYCENTRIC:
639                  restFrame[0] = 'B';
640                  restFrame[2] = 'B';
641                  break;
642    
643                case LSR_KINEMATIC:
644                  restFrame[0] = 'L';
645                  restFrame[2] = 'L';
646                  break;
647    
648                default:
649                  throw new IllegalArgumentException("We do not support the rest frame: " + frame);
650              }
651    
652              // Check Velocity Convention
653              char velocConvCode = freqVelSwitch(acTracker);
654    
655              velocConv = new char[4];
656              velocConv[0] = velocConvCode;
657              velocConv[1] = 'V';
658              velocConv[2] = velocConvCode;
659              velocConv[3] = 'V';
660            }
661    
662            if (mode.uses(IFPair.BD) && !conf.isSkyFrequency(IFPair.BD))
663            {
664              Frequency bdCenter = conf.getCentralFrequency(IFPair.BD);
665    
666              // varName.append('RestFreqB=xyz')
667              BigDecimal f = bdCenter.toUnits(FrequencyUnits.MEGAHERTZ);
668              this.script.append(this.indentString);
669              this.script.append("intents.addIntent('RestFreqB=");
670              this.script.append(sUtil.formatNoScientificNotation(f, 0, 8));
671              this.script.append("')\n");
672    
673              this.script.append(this.indentString);
674              this.script.append("intents.addIntent('RestFreqD=");
675              this.script.append(sUtil.formatNoScientificNotation(f, 0, 8));
676              this.script.append("')\n");
677    
678              DopplerTracker bdTracker = scan.getDopplerTracker("BD", start, bdCenter);
679    
680              f = bdTracker.getSourceVelocity().toUnits(LinearVelocityUnits.KILOMETERS_PER_SECOND);
681              this.script.append(this.indentString);
682              this.script.append("intents.addIntent('VelocityB=");
683              this.script.append(sUtil.formatNoScientificNotation(f, 0, 8));
684              this.script.append("')\n");
685    
686              this.script.append(this.indentString);
687              this.script.append("intents.addIntent('VelocityD=");
688              this.script.append(sUtil.formatNoScientificNotation(f, 0, 8));
689              this.script.append("')\n");
690    
691              // Check Velocity Frame
692              if (restFrame == null)
693              {
694                restFrame = new char[4];
695                restFrame[0] = 'L';
696                restFrame[1] = 'L';
697                restFrame[2] = 'L';
698                restFrame[3] = 'L';
699              }
700    
701              VelocityFrame frame = bdTracker.getVelocityFrame();
702              switch (frame)
703              {
704                case TOPOCENTRIC:
705                  restFrame[1] = 'T';
706                  restFrame[3] = 'T';
707                  break;
708    
709                case GEOCENTRIC:
710                  restFrame[1] = 'G';
711                  restFrame[3] = 'G';
712                  break;
713    
714                case BARYCENTRIC:
715                  restFrame[1] = 'B';
716                  restFrame[3] = 'B';
717                  break;
718    
719                case LSR_KINEMATIC:
720                  restFrame[1] = 'L';
721                  restFrame[3] = 'L';
722                  break;
723    
724                default:
725                  throw new IllegalArgumentException("We do not support the rest frame: " + frame);
726              }
727    
728              // Check Velocity Convention
729              char velocConvCode = freqVelSwitch(bdTracker);
730    
731              if (velocConv == null)
732              {
733                velocConv = new char[4];
734                velocConv[0] = 'V';
735                velocConv[2] = 'V';
736              }
737              velocConv[1] = velocConvCode;
738              velocConv[3] = velocConvCode;
739            }
740    
741            if (restFrame != null)
742            {
743              this.script.append(this.indentString);
744              this.script.append("intents.addIntent('RestFrame=\"");
745              this.script.append(restFrame);
746              this.script.append("\"')\n");
747            }
748    
749            if (velocConv != null)
750            {
751              this.script.append(this.indentString);
752              this.script.append("intents.addIntent('FreqVelSwitch=\"");
753              this.script.append(velocConv);
754              this.script.append("\"')\n");
755            }
756          }
757    
758          //src.setIntention(intents)
759          this.script.append(this.indentString);
760          this.script.append(sourceVar);
761          this.script.append(".setIntention(intents)\n");
762    
763          //subarray.setSource(src)
764          this.script.append(this.indentString);
765          this.script.append("subarray.setSource(");
766          this.script.append(sourceVar);
767          this.script.append(")\n");
768        }
769    
770        else
771          operatorMessage += "Setting up tipping scan ";
772    
773    
774        // get the receiver band for output message
775        if (!scan.getUseResourceOfPriorScan())
776        {
777          Resource resrc = scan.getResource();
778          if (resrc != null)
779          {
780            Set<ReceiverBand> bands = resrc.getAntennaElectronics().getActiveReceivers();
781            ReceiverBand first = bands.iterator().next();
782            operatorMessage += "at " + first.getDisplayName() + "-band ";
783          }
784    
785          else
786            operatorMessage += "using previous hardware configuration ";
787        }
788    
789        operatorMessage += " [source #" + this.currentScanNumber + "].";
790    
791        if (!scan.getUseResourceOfPriorScan())
792        {
793          //subarray.setLoIfSetup(loifVar)
794          this.script.append(this.indentString);
795          this.script.append("subarray.setLoIfSetup(");
796          this.script.append(loifVar);
797          this.script.append(")\n");
798    
799          //subarray.setVLALoIfSetup(loifVarVla)
800          this.script.append(this.indentString);
801          this.script.append("subarray.setVLALoIfSetup(");
802          this.script.append(loifVarVla);
803          this.script.append(")\n");
804    
805          //subarray.setCorrelatorSetup(corrmode)
806          this.script.append(this.indentString);
807          this.script.append("subarray.setCorrelatorSetup(");
808          this.script.append(corrmodeVar);
809          this.script.append(")\n");
810        }
811    
812                    if (scan.getAllowOverTheTop())
813                    {
814                            this.script.append(this.indentString);
815                            this.script.append("subarray.setOverTheTop(1)\n");
816                    }
817    
818                    AntennaWrap wrap = scan.getAntennaWrap();
819                    this.script.append(this.indentString);
820                    switch(wrap)
821                    {
822                            case COUNTERCLOCKWISE:
823                                    this.script.append("subarray.setWrap(-1)\n");
824                                    break;
825    
826                            case CLOCKWISE:
827                                    this.script.append("subarray.setWrap(1)\n");
828                                    break;
829    
830                            default:
831                                    this.script.append("subarray.setWrap(0)\n");
832                    }
833    
834        //Now take care of the state for Ref. Pointing results.
835        if (scan.getApplyLastReferencePointing())
836        {
837          if (this.refPtgResultsAvailable)
838          {
839            if (!this.refPtgInUse || this.updatedRefPtgResultsAvailable)
840            {
841              this.script.append(this.indentString);
842              this.script.append("subarray.useRefPointing(refptg)\n");
843              this.refPtgInUse = true;
844              this.updatedRefPtgResultsAvailable = false;
845            }
846          }
847    
848          else
849          {
850            //Error! This should have been caught by validation before getting here.
851            throw new IllegalStateException("There are no pointing results to apply for scan: " +
852              scan.getName() + ".  This should have been caught by validation!");
853          }
854        }
855    
856        else
857        {
858          if (this.refPtgInUse)
859          {
860            this.script.append(this.indentString);
861            this.script.append("subarray.noRefPointing()\n");
862            this.refPtgInUse = false;
863          }
864        }
865    
866                    this.script.append(this.indentString);
867        this.script.append("myPrint('");
868        this.script.append(operatorMessage);
869        this.script.append("')\n");
870        this.script.append("printTime('startTime=',");
871    
872        if (this.dynamicSched)
873          this.script.append("startTime");
874    
875        else
876          this.script.append(sUtil.formatNoScientificNotation(this.startMjd.modifiedValue(), 1, 8));
877    
878        this.script.append(")\n");
879      }
880      
881      //Returns a character for use in this kind of line:
882      //  intention.addIntent('FreqVelSwitch="VVVV"')
883      private char freqVelSwitch(DopplerTracker dt)
884      {
885        //Issue: turns out DopplerTracker assumes a convention based on velocity units.
886        //Actually, DT uses SpectralLine class, which makes that assumption.
887        //The only conventions it uses is Radio & Redshift.  The script logic handles
888        //only Radio and Optical.  Luckily, when Z is converted to km/s, the Optical
889        //convention is then equivalent to the Redshift one.
890        
891        //TODO this code points out that the DopplerTracker and/or associated classes
892        //     need to become convention aware.
893        
894        if (dt.getSourceVelocity().getUnits().equals(LinearVelocityUnits.Z))
895          return 'Z';
896        else
897          return 'V';
898      }
899    
900      protected void scanAction(Scan s)
901      {
902        switch(s.getMode())
903        {
904          case AMPLITUDE_DELAY_CALIBRATING:
905            delayCalibratingToScript((DelayScan)s);
906            break;
907    
908          case REFERENCE_FOCUSING:
909            focusingToScript((FocusScan)s);
910            break;
911    
912          case FAST_SWITCHING:
913            switchingToScript((SwitchingScan)s);
914            break;
915    
916          case TIPPING:
917            tippingToScript((TippingScan)s);
918            break;
919    
920          case HOLOGRAPHY:
921            holographyToScript((PointingScan)s);
922            break;
923    
924          case MOSAICKING:
925            mosaicingToScript((PointingScan)s);
926            break;
927    
928          case SINGLE_DISH_POINTING:
929            singleDishPointingToScript((PointingScan)s);
930            break;
931    
932          case INTERFEROMETRIC_POINTING:
933            interferometricPointingToScript((PointingScan)s);
934            break;
935    
936          case STANDARD_OBSERVING:
937          default:
938            simpleScanToScript((SimpleScan)s);
939        }
940      }
941    
942            /**
943             * Generates python code that is necessary regardless of the scan mode, that
944             * must come after each scan.
945             */
946      protected void afterScanAction(Scan scan)
947      {
948        // Don't bother incrementing the startTime if this is the last scan.
949        if (this.dynamicSched && !this.onLastScan)
950        {
951          this.script.append(this.indentString);
952          this.script.append("startTime += ");
953          this.script.append(sUtil.formatNoScientificNotation(this.endMjd.modifiedValue().subtract(this.startMjd.modifiedValue()), 1, 8));
954          this.script.append("\n");
955        }
956      }
957    
958      /**
959       * Returns  the operator comments contained in {@code sb} formatted by
960       * placing "#"'s at the beginning of the string and before and after any line
961       * breaks in the comments.  Also includes any comments in {@code sb}'s
962       * Project.  This is currently being used to contain the user's contact info.
963       */
964      private String getFormattedOperatorComments(SchedulingBlock sb)
965      {
966        String comments = sb.getProgramBlock().getProject().getComments() + "\n" +
967          sb.getCommentsToOperator();
968    
969        comments = comments.replaceAll("\n", "\n# ");
970    
971        // TODO: remove this hard coded comment when we have nothing but evla
972        // antennas left.
973        return "# " + comments + "\n# This script should only be run on EVLA antennas.\n\n\n";
974      }
975    
976      private VlaConfiguration getVlaConfiguration(Scan scan)
977      {
978        Resource resrc = scan.getResource();
979        if (resrc != null)
980        {
981          List<TelescopeBackend> backends = resrc.getBackends();
982          if (backends != null && !backends.isEmpty())
983          {
984            TelescopeBackend conf = backends.get(0);
985            if (conf.getName().equals(CorrelatorName.VLA))
986              return (VlaConfiguration)conf;
987    
988            else
989            {
990              //ERROR, this should have been caught by validation before getting here!
991              throw new IllegalStateException("The backend assigned to resource: " +
992                  resrc.getName() + " is not the VLA correlator.  This should have been caught by validation!");
993            }
994          }
995    
996          else
997          {
998            //ERROR, this should have been caught by validation before getting here!
999            throw new IllegalStateException("There are is no backend assigned to resource: " +
1000                resrc.getName() + ".  This should have been caught by validation!");
1001          }
1002        }
1003    
1004        else
1005        {
1006          //ERROR, this should have been caught by validation before getting here!
1007          throw new IllegalStateException("There is no resource assigned to scan: " +
1008              scan.getName() + ".  This should have been caught by validation!");
1009        }
1010      }
1011    
1012            /**
1013       * Adds a new python definition for the LoIfSetup used by {@code scan} to
1014       * {@code builder} with the variable name {@code varName}.
1015             */
1016            private void createLoIfSetup(String varName, String vlaVarName, Scan scan, StringBuilder builder)
1017      {
1018        VlaConfiguration conf = getVlaConfiguration(scan);
1019    
1020        //Now output a resource definition
1021        Frequency acCenter = conf.getCentralFrequency(IFPair.AC);
1022        Frequency bdCenter = conf.getCentralFrequency(IFPair.BD);
1023    
1024        Date start = this.startMjd.toDate();
1025    
1026        CorrelatorMode mode = conf.getCorrelatorMode();
1027    
1028        DopplerTracker acTracker = (mode.uses(IFPair.AC) && !conf.isSkyFrequency(IFPair.AC))?
1029          scan.getDopplerTracker("AC", start, acCenter) : null;
1030    
1031        DopplerTracker bdTracker = (mode.uses(IFPair.BD) && !conf.isSkyFrequency(IFPair.BD))?
1032          scan.getDopplerTracker("BD", start, bdCenter) : null;
1033    
1034        try
1035        {
1036          String[] params = conf.calcLoIfSetupParams(acTracker, bdTracker, start);
1037          
1038          builder.append(varName).append(" = LoIfSetup(\"");
1039          builder.append(params[0]).append("\",");
1040          builder.append(params[1]).append(",");
1041          builder.append(params[2]).append(",");
1042          builder.append(params[3]).append(")\n");
1043          
1044          builder.append(vlaVarName).append(" = VLALoIfSetup(\"");
1045          builder.append(params[0]).append("\")\n");
1046          
1047          swappedIFs = params[4].equals("swapped");
1048        }
1049    
1050        catch (CoordinateConversionException cce)
1051        {
1052          getValidationFailures().add(new ValidationFailure(
1053                "There has been an internal error trying to convert coordinate systems.",
1054                "There has been an internal error trying to convert coordinate systems." + cce.getMessage(),
1055                FailureSeverity.ERROR,
1056                scan.getSchedulingBlock(),
1057                "",
1058                ""
1059                ));
1060    
1061          log.error(cce);
1062        }
1063      }
1064    
1065            /**
1066       * Adds a new python definition for the VlaCorrelatorSetup used by {@code
1067       * scan} to {@code builder} with the variable name {@code varName}.
1068             */
1069      private void createVlaCorrelatorSetup(String varName, String vlaLoifVarName,
1070                                            Scan scan, StringBuilder builder)
1071      {
1072        VlaConfiguration conf = getVlaConfiguration(scan);
1073    
1074        //corrmode = VlaCorrelatorSetup()
1075        builder.append(varName);
1076        builder.append(" = VlaCorrelatorSetup()\n");
1077    
1078        CorrelatorMode mode = conf.getCorrelatorMode();
1079    
1080        // CONTINUUM is the default, no need to set it unless we're not
1081        // CONTINUUM. (saves bytes in the script).
1082        if (!CorrelatorMode.CONTINUUM.equals(mode))
1083        {
1084          //corrmode.setMode("...")
1085          builder.append(varName);
1086          builder.append(".setMode(\"");
1087          builder.append(conf.getCorrelatorMode().getCode());
1088          builder.append("\")\n");
1089        }
1090    
1091        //corrmode.setBandwidth(...)
1092        int a = conf.getBandwidthCode(IFCode.A).getCodeNumber();
1093        int b = conf.getBandwidthCode(IFCode.B).getCodeNumber();
1094        int c = conf.getBandwidthCode(IFCode.C).getCodeNumber();
1095        int d = conf.getBandwidthCode(IFCode.D).getCodeNumber();
1096        
1097        if (swappedIFs)
1098        {
1099          int oldA = a;  a = b;  b = oldA;
1100          int oldC = c;  c = d;  d = oldC;
1101        }
1102        
1103        builder.append(varName);
1104        builder.append(".setBandwidth(");
1105        builder.append(a);
1106        builder.append(",");
1107        builder.append(b);
1108        builder.append(",");
1109        builder.append(c);
1110        builder.append(",");
1111        builder.append(d);
1112        builder.append(")\n");
1113    
1114        //corrmode.setIntegration(...)
1115        builder.append(varName);
1116        builder.append(".setIntegration(");
1117        builder.append(sUtil.formatNoScientificNotation(conf.getIntegrationTime(), 1, 8));
1118        builder.append(")\n");
1119    
1120        String ptCode = ProcessingType.getCombinationCode(conf.getSpectralProcessing());
1121    
1122        //If a blank code was returned don't bother setting it, as that is the
1123        //default anyway.
1124        if (!"  ".equals(ptCode))
1125        {
1126          //corrmode.setProcessing("...")
1127          builder.append(varName);
1128          builder.append(".setProcessing(\"");
1129          builder.append(ptCode);
1130          builder.append("\")\n");
1131        }
1132        
1133        //VLA LO/IF bandwidths (which really shouldn't be needed)
1134        builder.append(vlaLoifVarName).append(".setBandwidthA(").append(a).append(")\n");
1135        builder.append(vlaLoifVarName).append(".setBandwidthB(").append(b).append(")\n");
1136        builder.append(vlaLoifVarName).append(".setBandwidthC(").append(c).append(")\n");
1137        builder.append(vlaLoifVarName).append(".setBandwidthD(").append(d).append(")\n");
1138      }
1139    
1140            /**
1141       * Adds a new python definition for the Source {@code s} to {@code builder}
1142       * with the variable name {@code varName}.
1143             */
1144      private void createSource(String varName, Source s, Scan scan, StringBuilder builder)
1145      {
1146        createSource(varName, s, scan, builder, false);
1147      }
1148    
1149      private void createSource(String varName, Source s, Scan scan, StringBuilder builder, boolean bogusFinishScan)
1150      {
1151        SkyPosition pos;
1152    
1153        Date startTime = this.startMjd.toDate();
1154    
1155        try
1156        {
1157          EVLA_LST.setSolarTime(startTime);
1158          pos = s.getPosition().toPosition(CelestialCoordinateSystem.EQUATORIAL,
1159              Epoch.J2000, EVLA_LOCATION, EVLA_LST);
1160        }
1161        catch (CoordinateConversionException cce)
1162        {
1163          getValidationFailures().add(new ValidationFailure(
1164            "There has been an internal error trying to convert coordinate systems.",
1165            "There has been an internal error trying to convert coordinate systems." + cce.getMessage(),
1166            FailureSeverity.ERROR,
1167            scan.getSchedulingBlock(),
1168            "",
1169            ""
1170          ));
1171    
1172          log.error(cce);
1173          pos = null;
1174        }
1175    
1176        String ra = sUtil.formatNoScientificNotation(pos.getLongitude(startTime).toUnits(ArcUnits.RADIAN), 1, 10);
1177        String dec = sUtil.formatNoScientificNotation(pos.getLatitude(startTime).toUnits(ArcUnits.RADIAN), 1, 10);
1178    
1179        //src = Source(raInRad, decInRad)
1180        //src.setName("Name")
1181        builder.append(varName);
1182        builder.append(" = Source(");
1183        builder.append(ra);
1184        builder.append(", ");
1185        builder.append(dec);
1186        builder.append(")\n");
1187        builder.append(varName);
1188        builder.append(".setName(\"");
1189        builder.append(bogusFinishScan? "FINISH" : s.getName());
1190        builder.append("\")\n");
1191      }
1192    
1193            /**
1194             * Generates python code specific to a SimpleScan.
1195             */
1196            private void simpleScanToScript(SimpleScan scan)
1197            {
1198                    //subarray.execute(mjd)
1199                    this.script.append(this.indentString);
1200                    this.script.append("subarray.execute(");
1201    
1202        if (this.dynamicSched)
1203          this.script.append("startTime");
1204    
1205        else
1206          this.script.append(sUtil.formatNoScientificNotation(this.startMjd.modifiedValue(), 1, 8));
1207    
1208                    this.script.append(")\n");
1209            }
1210    
1211            /**
1212             * Generates python code specific to a DelayScan.
1213             */
1214            private void delayCalibratingToScript(DelayScan scan) {}
1215    
1216            /**
1217             * Generates python code specific to a FocusScan.
1218             */
1219            private void focusingToScript(FocusScan scan) {}
1220    
1221            /**
1222             * Generates python code specific to a Holography Scan.
1223             */
1224            private void holographyToScript(PointingScan scan) {}
1225    
1226            /**
1227             * Generates python code specific to a Mosaicing Scan.
1228             */
1229            private void mosaicingToScript(PointingScan scan) {}
1230    
1231            /**
1232       * Generates python code specific to a Single Dish Pointing Scan.
1233             */
1234            private void singleDishPointingToScript(PointingScan scan) {}
1235    
1236      private boolean updatedRefPtgResultsAvailable = false;
1237      private boolean refPtgResultsAvailable        = false;
1238      private boolean refPtgInUse                   = false;
1239    
1240            /**
1241       * Generates python code specific to a Interferometric Pointing Scan.
1242             */
1243            private void interferometricPointingToScript(PointingScan scan)
1244            {
1245        this.foundPtgScan = true;
1246    
1247        /*
1248           Must save the results and keep track of any scans that have the
1249           applyLastPointing flag set to true.  If it's true,
1250           subarray.useRefPointing(ptg) else subarray.noRefPointing().
1251        */
1252        this.script.append(this.indentString);
1253        this.script.append("refptg = vlaPointing(");
1254    
1255        if (this.dynamicSched)
1256        {
1257          this.script.append("startTime, ");
1258          this.script.append("startTime + ");
1259          this.script.append(sUtil.formatNoScientificNotation(this.endMjd.modifiedValue().subtract(this.startMjd.modifiedValue()), 1, 8));
1260          this.script.append(", ");
1261        }
1262    
1263        else
1264        {
1265          this.script.append(sUtil.formatNoScientificNotation(this.startMjd.modifiedValue(), 1, 8));
1266          this.script.append(", ");
1267          this.script.append(sUtil.formatNoScientificNotation(this.endMjd.modifiedValue(), 1, 8));
1268          this.script.append(", ");
1269        }
1270    
1271        this.script.append("10.0, 2, [])\n");
1272    
1273        // update a flag if we are using ptg results already and have just updated the ptg (i.e. secondary ptg).
1274        this.updatedRefPtgResultsAvailable = this.refPtgResultsAvailable && this.refPtgInUse;
1275        this.refPtgResultsAvailable = true;
1276            }
1277    
1278            /**
1279             * Generates python code specific to a SwitchingScan.
1280             */
1281            private void switchingToScript(SwitchingScan scan) {}
1282    
1283            /**
1284             * Generates python code specific to a TippingScan.
1285             */
1286            private void tippingToScript(TippingScan scan)
1287      {
1288        this.foundTippingScan = true;
1289    
1290        this.script.append(this.indentString);
1291        this.script.append("src = Source(");
1292        this.script.append(sUtil.formatNoScientificNotation(scan.getAzimuth().toUnits(ArcUnits.RADIAN), 1, 8));
1293        if (scan.getOrder().equals(TippingOrder.LOW_TO_HIGH))
1294        {
1295          // 20 deg. in rad
1296          this.script.append(",0.349065850399)\n");
1297          this.script.append(this.indentString);
1298          this.script.append("src.setName('TIPUP')\n");
1299        }
1300    
1301        else
1302        {
1303          // 55 deg. in rad
1304          this.script.append(",0.959931088597)\n");
1305          this.script.append(this.indentString);
1306          this.script.append("src.setName('TIPDOWN')\n");
1307        }
1308    
1309        this.script.append(this.indentString);
1310        this.script.append("intents = Intention()\n");
1311        this.script.append(this.indentString);
1312        this.script.append("intents.addIntent('ScanIntent=\"SKYDIP\"')\n");
1313        this.script.append(this.indentString);
1314        this.script.append("src.setIntention(intents)\n");
1315    
1316        this.script.append(this.indentString);
1317        this.script.append("subarray.setSource(src)\n");
1318    
1319        this.script.append(this.indentString);
1320        this.script.append("vlaTip(7,0,");
1321    
1322        if (this.dynamicSched)
1323        {
1324          this.script.append("startTime, ");
1325          this.script.append("startTime + ");
1326          this.script.append(sUtil.formatNoScientificNotation(this.endMjd.modifiedValue().subtract(this.startMjd.modifiedValue()), 1, 8));
1327        }
1328    
1329        else
1330        {
1331          this.script.append(sUtil.formatNoScientificNotation(this.startMjd.modifiedValue(), 1, 8));
1332          this.script.append(", ");
1333          this.script.append(sUtil.formatNoScientificNotation(this.endMjd.modifiedValue(), 1, 8));
1334        }
1335    
1336        this.script.append(")\n");
1337            }
1338    }