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