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