File Coverage

blib/lib/Astro/WaveBand.pm
Criterion Covered Total %
statement 264 275 96.0
branch 125 164 76.2
condition 28 42 66.6
subroutine 34 34 100.0
pod 17 17 100.0
total 468 532 87.9


line stmt bran cond sub pod time code
1             package Astro::WaveBand;
2              
3             =head1 NAME
4              
5             Astro::WaveBand - Transparently work in waveband, wavelength or filter
6              
7             =head1 SYNOPSIS
8              
9             use Astro::WaveBand;
10              
11             $w = Astro::WaveBand->new(Filter => $filter);
12             $w = Astro::WaveBand->new(Wavelength => $wavelength);
13              
14             $w = Astro::WaveBand->new(
15             Wavelength => $wavelength,
16             Instrument => 'CGS4');
17              
18             $filter = $w->filter;
19             $wave = $w->wavelength;
20             $band = $w->waveband; # radio, xray, submm
21             $freq = $w->frequency;
22             $wnum = $w->wavenumber;
23              
24             $natural = $w->natural;
25             $natural = "$w";
26              
27             $w->natural_unit("wavelength");
28              
29             if ($w1 > $w2) {...}
30             if ($w1 == $w2) {...}
31              
32             =head1 DESCRIPTION
33              
34             Class to transparently deal with the conversion between filters,
35             wavelength, frequency and other methods of specifying a location
36             in the electro-magentic spectrum.
37              
38             The class tries to determine the natural form of the numbers such that
39             a request for a summary of the object when it contains 2.2 microns
40             would return the filter name but would return the wavelength if it was
41             not a standard filter. In ambiguous cases an instrument name is
42             required to decide what to return. In really ambiguous cases the user
43             can specify the unit in which to display the numbers on
44             stringification.
45              
46             Used mainly as a way of storing a single number in a database table
47             but using logic to determine the number that an observer is most likely
48             to understand.
49              
50             Numerical comparison operators can be used to compare two C
51             objects. When checking equality, the "natural" and "instrument" methods are
52             used, so if two C objects return the same value from those
53             methods, they are considered to be equal. When checking other comparisons
54             such as greater than, the wavelength is used.
55              
56             =cut
57              
58 1     1   133374 use 5.006;
  1         5  
59 1     1   7 use strict;
  1         8  
  1         29  
60 1     1   6 use warnings;
  1         2  
  1         68  
61 1     1   6 use Carp;
  1         2  
  1         93  
62              
63             # Register an Astro::WaveBand warning category
64 1     1   6 use warnings::register;
  1         2  
  1         108  
65              
66             # CVS version: $Revision$
67             our $VERSION = '0.12';
68              
69             # Overloading
70             use overload
71 1         7 '""' => "natural",
72             '==' => "equals",
73             '!=' => "not_equals",
74             '<=>' => "compare",
75 1     1   743 'fallback' => 1;
  1         2155  
76              
77             # Constants
78              
79             # Speed of light in m/s
80 1     1   153 use constant CLIGHT => 299792458;
  1         2  
  1         4119  
81              
82             # list of instruments specific to a telescope
83             my %TELESCOPE = (
84             UKIRT => [
85             qw/CGS4 IRCAM UFTI UIST MICHELLE WFCAM/],
86             JCMT => [
87             qw/SCUBA SCUBA-2 RXA3 RXA3M RXB3 RXW RXH3
88             ACSIS DAS HARP ALAIHI UU AWEOWEO
89             KUNTUR/
90             ],
91             );
92              
93             # Continuum Filters are keyed by instrument
94             # although if an instrument is not specified the filters
95             # hash will be searched for a match if none is available in
96             # GENERIC
97             my %FILTERS = (
98             GENERIC => {
99             U => 0.365,
100             B => 0.44,
101             V => 0.55,
102             R => 0.70,
103             I => 0.90,
104             J => 1.25,
105             H => 1.65,
106             K => 2.2,
107             L => 3.45,
108             M => 4.7,
109             N => 10.2,
110             Q => 20.0,
111             up => 0.355,
112             gp => 0.470,
113             rp => 0.620,
114             ip => 0.750,
115             zp => 0.880,
116             Pu => 0.355,
117             Pg => 0.470,
118             Pr => 0.620,
119             Pi => 0.750,
120             Pz => 0.880,
121             Y => 1.020, # this will get incorrectly classed as infrared
122             w => 0.608,
123             SO => 0.600,
124             },
125             WFCAM => {
126             "Z" => 0.83,
127             "Y" => 0.97,
128             "J" => 1.17,
129             "H" => 1.49,
130             "K" => 2.03,
131             "1-0S1" => 2.111,
132             "BGamma" => 2.155,
133             "1.205nbJ" => 1.205,
134             "1.619nbH" => 1.619,
135             "1.644FeII" => 1.631,
136             "Blank" => 0,
137             },
138             IRCAM => {
139             "J98" => "1.250",
140             "H98" => "1.635",
141             "K98" => "2.150",
142             "Lp98" => "3.6",
143             "Mp98" => "4.800",
144             "2.1c" => "2.100",
145             "2.122S1" => "2.122",
146             "BrG" => "2.0",
147             "2.2c" => "2.200",
148             "2.248S1" => "2.248",
149             "3.6nbLp" => "3.6",
150             "4.0c" => "4.000",
151             "BrA" => "4.0",
152             "Ice" => "3.1",
153             "Dust" => "3.28",
154             "3.4nbL" => "3.4",
155             "3.5mbL" => "3.5",
156             },
157             UFTI => {
158             "Y_MK" => "1.022",
159             "I" => "0.9",
160             "Z" => "1.033",
161             "J98" => "1.250",
162             "H98" => "1.635",
163             "K98" => "2.150",
164             "Kprime" => "2.120",
165             "1.644" => "1.644",
166             '1.69CH4_l' => '1.690',
167             "1.57" => "1.57",
168             "2.122" => "2.122",
169             "2.122MK" => "2.122",
170             "BrG" => "2.166",
171             "BrGz" => "2.173",
172             "2.248S(1)" => "2.248",
173             "2.27" => "2.270",
174             "Blank" => "-2.222", # -ve version of OT wavelength
175             "Mask" => "-2.32", # ditto
176             },
177             UIST => {
178             # "K-target" => 1.64, # old
179             "Y_MK" => 1.022,
180             "ZMK" => 1.033,
181             "Hartmann" => 1.64,
182             "J98" => 1.25,
183             "H98" => 1.64,
184             "1.57" => 1.573,
185             "1.66" => 1.664, # old
186             "1.58CH4_s" => 1.604,
187             "1.69CH4_l" => 1.674,
188             "1.644Fe" => 1.643,
189             "K98" => 2.20,
190             "Kshort" => 2.159,
191             "Klong" => 2.227,
192             "2.122S(1)" => 2.121,
193             "2.122MK" => 2.127,
194             "2.248S(1)" => 2.248,
195             "2.248MK" => 2.263,
196             "BrG" => 2.166,
197             "2.27" => 2.274,
198             "2.32CO" => 2.324, # old
199             "2.42CO" => 2.425,
200             "3.05ice" => 3.048,
201             "Dust" => 3.278,
202             "3.30PAH" => 3.286,
203             "3.4nbL" => 3.415,
204             "3.5mbL" => 3.489,
205             "3.6nbLp" => 3.593,
206             "3.99" => 3.990,
207             "BrA" => 4.053,
208             "Lp98" => 3.77,
209             "Mp98" => 4.69,
210             },
211             MICHELLE => {
212             "F105B53" => 10.5,
213             "F79B10" => 7.9,
214             "F88B10" => 8.8,
215             "F97B10" => 9.7,
216             "F103B10" => 10.3,
217             "F116B9" => 11.6,
218             "F125B9" => 12.5,
219             "F107B4" => 10.7,
220             "F122B3" => 12.2,
221             "F128B2" => 12.8,
222             "F209B42" => 20.9,
223             "F185B9" => 18.5,
224             "NBlock" => 10.6,
225             "QBlock" => 20.9,
226             "F22B15" => 2.2,
227             "F34B9" => 3.4,
228             "F47B5" => 4.7,
229             },
230             SCUBA => {
231             "850W" => 863,
232             "450W" => 443,
233             "450N" => 442,
234             "850N" => 862,
235             "750N" => 741,
236             "350N" => 344,
237             "P2000" => 2000,
238             "P1350" => 1350,
239             "P1100" => 1100,
240             # This is a kluge until the class can
241             # be extended to support multiple wavelength
242             # instruments.
243             "850S:PHOT" => 1100,
244             "450W:850W" => 443,
245             "450N:850N" => 442,
246             "350N:750N" => 344,
247             },
248             'SCUBA-2' => {
249             850 => 863, # guesses
250             450 => 445,
251             },
252             );
253              
254             # Instruments that have natural units
255             my %NATURAL = (
256             WFCAM => 'filter',
257             CGS4 => 'wavelength',
258             SCUBA => 'filter',
259             'SCUBA-2' => 'filter',
260             UFTI => 'filter',
261             IRCAM => 'filter',
262             MICHELLE => 'filter',
263             ACSIS => 'frequency',
264             DAS => 'frequency',
265             RXA3 => 'frequency',
266             RXA3M => 'frequency',
267             RXB3 => 'frequency',
268             RXW => 'frequency',
269             RXWB => 'frequency',
270             RXWC => 'frequency',
271             RXWD => 'frequency',
272             RXWD2 => 'frequency',
273             RXH3 => 'frequency',
274             HARP => 'frequency',
275             ALAIHI => 'frequency',
276             UU => 'frequency',
277             AWEOWEO => 'frequency',
278             KUNTUR => 'frequency',
279             UIST => 'filter',
280             );
281              
282             # Unit prefixes, in descending size order.
283             my @PREFIXES = (
284             [Q => 30],
285             [R => 27],
286             [Y => 24],
287             [Z => 21],
288             [E => 18],
289             [P => 15],
290             [T => 12],
291             [G => 9],
292             [M => 6],
293             [k => 3],
294             ['' => 0],
295             [m => -3],
296             [mu => -6],
297             [n => -9],
298             [p => -12],
299             [f => -15],
300             [a => -18],
301             [z => -21],
302             [y => -24],
303             [r => -27],
304             [q => -30],
305             );
306              
307             =head1 METHODS
308              
309             =head2 Constructor
310              
311             =over 4
312              
313             =item B
314              
315             Create a new instance of an C object.
316              
317             $w = Astro::WaveBand->new(Filter => $filter);
318              
319             Allowed keys for constructor are one of:
320              
321             Filter - filter name
322             Wavelength - wavelength in microns
323             Frequency - frequency in Hertz
324             Wavenumber - wavenumber in cm^-1
325              
326             plus optionally:
327              
328             Instrument - name of associated instrument
329              
330             In the future there may be a C key to allow the units to be
331             supplied in alternative forms.
332              
333             If a mandatory key is missing or there is more than one
334             mandatory key the constructor will fail and return C.
335             Additionally a warning (of class C) will
336             be issued.
337              
338             =cut
339              
340             sub new {
341 29     29 1 246709 my $proto = shift;
342 29   33     184 my $class = ref($proto) || $proto;
343              
344 29         106 my %args = @_;
345              
346             # Check the hash contains one of the following
347 29         81 my @keys = qw/Filter Wavelength Frequency Wavenumber/;
348 29         51 my $found = 0;
349 29         66 for my $key (@keys) {
350 116 100       289 $found++ if exists $args{$key};
351             }
352              
353 29 100       92 if ($found == 0) {
    100          
354 2 50       118 warnings::warn("Missing a mandatory key")
355             if warnings::enabled();
356 2         10 return undef;
357             }
358             elsif ($found > 1) {
359 1 50       118 warnings::warn("More than one mandatory key")
360             if warnings::enabled();
361 1         12 return undef;
362             }
363              
364 26         94 my $w = bless {Cache => {}}, $class;
365              
366             # Now insert the information into the object
367             # Do Instrument first since we may need it to convert
368             # filter to wavelength
369 26 100       80 if (exists $args{Instrument}) {
370 19         63 $w->instrument($args{Instrument});
371             }
372              
373 26         69 for my $key (keys %args) {
374 47         117 my $method = lc($key);
375 47 100       123 next if $method eq 'instrument';
376 28 50       138 if ($w->can($method)) {
377 28         79 $w->$method($args{$key});
378             }
379             }
380              
381             # We are now done so just return the object
382 26         108 return $w;
383             }
384              
385             =back
386              
387             =head2 Accessor methods
388              
389             All the accessor methods associated with conversions will
390             automatically convert to the correct format on demand and will cache
391             it for later. If a new value is provided all caches will be cleared.
392              
393             All input values are converted to microns internally (since a
394             single base unit should be chosen to simplify internal conversions).
395              
396             If a numeric accessor (wavelength, frequency, wavenumber) is called
397             with a hash reference as the first argument then it is interpreted
398             as a set of options. (The filter accessor also accepts an initial
399             hash reference for consistency.) Options can include:
400              
401             =over 4
402              
403             =item format
404              
405             If returning a value, construct a formatted string including the units.
406              
407             =item ndp
408              
409             When returning a string (C is true), round the value to this number
410             of decimal places. If not specified, 3 is used.
411              
412             =back
413              
414             For example:
415              
416             $w->frequency({format => 1})
417              
418             Could return a string such as "345.796 GHz".
419              
420             =over 4
421              
422             =item B
423              
424             Wavelength in microns.
425              
426             $wav = $w->wavelength;
427             $w->wavelength(450.0);
428              
429             =cut
430              
431             sub wavelength {
432 55     55 1 17797 my $self = shift;
433 55 100 100     118 my %opt = %{@_ && 'HASH' eq ref $_[0] ? shift : {}};
  55         295  
434              
435 55 100       144 if (@_) {
436 8         15 my $value = shift;
437 8         27 $self->_store_in_cache('wavelength' => $value);
438             }
439             else {
440 47         122 my $value = $self->_fetch_from_cache('wavelength');
441 47 100 50     165 return $self->_format_with_unit($value * 1e-6, 'm', $opt{'ndp'} // 3) if $opt{'format'};
442 46         138 return $value;
443             }
444 8         22 return;
445             }
446              
447             =item B
448              
449             Frequency in Hertz.
450              
451             $frequency = $w->frequency;
452             $w->frequency(345E9);
453              
454             =cut
455              
456             sub frequency {
457 13     13 1 1958 my $self = shift;
458 13 100 100     25 my %opt = %{@_ && 'HASH' eq ref $_[0] ? shift : {}};
  13         89  
459              
460 13 100       42 if (@_) {
461 5         13 my $value = shift;
462              
463             # store value and wavelength in cache
464 5         16 $self->_cache_value_and_wav('frequency', $value);
465             }
466             else {
467             # Read value from the cache
468 8         24 my $value = $self->_read_value_with_convert("frequency");
469 8 100 100     52 return $self->_format_with_unit($value, 'Hz', $opt{'ndp'} // 3) if $opt{'format'};
470 5         18 return $value;
471             }
472              
473 5         13 return;
474             }
475              
476             =item B
477              
478             Wavenumber (reciprocal of wavelength) in inverse centimetres.
479              
480             $value = $w->wavenumber;
481             $w->wavenumber(1500);
482              
483             =cut
484              
485             sub wavenumber {
486 12     12 1 8636 my $self = shift;
487 12 100 100     30 my %opt = %{@_ && 'HASH' eq ref $_[0] ? shift : {}};
  12         99  
488              
489 12 100       38 if (@_) {
490 1         3 my $value = shift;
491              
492             # store value and wavelength in cache
493 1         4 $self->_cache_value_and_wav('wavenumber', $value);
494             }
495             else {
496             # Read value from the cache
497 11         31 my $value = $self->_read_value_with_convert("wavenumber");
498 11 100 50     39 return $self->_format_fixed_unit($value, '/cm', $opt{'ndp'} // 3) if $opt{'format'};
499 10         30 return $value;
500             }
501              
502 1         3 return;
503             }
504              
505             =item B
506              
507             Set or retrieve filter name.
508              
509             Returns C if the filter can not be determined. If the filter
510             name can not be translated to a wavelength it will not be possible
511             to do any conversions to other forms.
512              
513             =cut
514              
515             sub filter {
516 55     55 1 14338 my $self = shift;
517 55 100 100     96 my %opt = %{@_ && 'HASH' eq ref $_[0] ? shift : {}};
  55         403  
518              
519 55 100       136 if (@_) {
520 13         29 my $value = shift;
521              
522             # store value and wavelength in cache
523 13         41 $self->_cache_value_and_wav('filter', $value);
524             }
525             else {
526             # Read value from the cache
527 42         111 return $self->_read_value_with_convert("filter");
528             }
529              
530 13         32 return;
531             }
532              
533             =item B
534              
535             Name of associated instrument.
536              
537             $inst = $w->instrument;
538             $w->instrument('SCUBA');
539              
540             Used to aid in the choice of natural unit.
541              
542             =cut
543              
544             sub instrument {
545 83     83 1 147 my $self = shift;
546 83 100       220 if (@_) {$self->{Instrument} = uc(shift);}
  19         115  
547 83         258 return $self->{Instrument};
548             }
549              
550             =item B
551              
552             Override the natural unit to be used for stringification. If this
553             value is not set the class will determine the unit of choice by
554             looking at the instrument name and then by taking an informed guess.
555              
556             $w->natural_unit('filter');
557              
558             =cut
559              
560             sub natural_unit {
561 27     27 1 49 my $self = shift;
562 27 50       64 if (@_) {$self->{NaturalUnit} = shift;}
  0         0  
563 27         68 return $self->{NaturalUnit};
564             }
565              
566             =item B
567              
568             Name of species involved in an emission line.
569              
570             =cut
571              
572             sub species {
573 4     4 1 530 my $self = shift;
574 4 100       12 if (@_) {$self->{'Species'} = shift;}
  2         5  
575 4         23 return $self->{'Species'};
576             }
577              
578             =item B
579              
580             Transition giving rise to line emission.
581              
582             =cut
583              
584             sub transition {
585 4     4 1 10 my $self = shift;
586 4 100       13 if (@_) {$self->{'Transition'} = shift;}
  2         4  
587 4         14 return $self->{'Transition'};
588             }
589              
590             =back
591              
592             =head2 General Methods
593              
594             =over 4
595              
596             =item B
597              
598             Return the name of the waveband associated with the object.
599              
600             Returns C if none can be determined.
601              
602             $band = $w->waveband;
603              
604             =cut
605              
606             sub waveband {
607 19     19 1 15082 my $self = shift;
608              
609 19         59 my $lambda = $self->wavelength;
610 19 50       49 return undef unless defined $lambda;
611              
612 19         28 my $band;
613 19 100       104 if ($lambda >= 10000) { # > 1cm
    50          
    100          
    100          
    50          
    0          
    0          
614 1         4 $band = 'radio';
615             }
616             elsif ($lambda >= 1000) {
617 0         0 $band = 'mm';
618             }
619             elsif ($lambda >= 100) {
620 3         9 $band = 'submm';
621             }
622             elsif ($lambda >= 1) {
623 11         22 $band = 'infrared';
624             }
625             elsif ($lambda >= 0.3) {
626 4         10 $band = 'optical';
627             }
628             elsif ($lambda >= 0.01) {
629 0         0 $band = 'ultraviolet';
630             }
631             elsif ($lambda >= 0.00001) {
632 0         0 $band = 'x-ray';
633             }
634             else {
635 0         0 $band = 'gamma-ray';
636             }
637              
638 19         53 return $band;
639             }
640              
641             =item B
642              
643             Return the contents of the object in its most natural form. For
644             example, with UFTI the filter name will be returned whereas with ACSIS
645             the frequency will be returned. The choice of unit is chosen using
646             the supplied default unit (see C) or the instrument name.
647             If none of these is specified filter will be used and if no match is
648             present wavelength in microns.
649              
650             $value = $w->natural;
651              
652             Returns C if the value can not be determined.
653              
654             This method is called automatically when the object is stringified.
655             Note that you will not know the unit that was chosen a priori.
656              
657             If a hash reference (containing formatting options) is given
658             then it is passed to the relevant accessor method.
659              
660             =cut
661              
662             sub natural {
663 27     27 1 15261 my $self = shift;
664 27 100 66     57 my %opt = %{@_ && 'HASH' eq ref $_[0] ? shift : {}};
  27         145  
665              
666             # First see if the default unit is set
667 27         83 my $unit = $self->natural_unit;
668              
669 27 50       67 unless (defined $unit) {
670             # Check the instrument
671 27         80 my $inst = $self->instrument;
672 27 50 66     123 if ($inst and exists $NATURAL{$inst}) {
673 19         46 $unit = $NATURAL{$inst};
674             }
675             }
676              
677             # Guess at filter if we have no choice
678 27 100       69 $unit = 'filter' unless defined $unit;
679              
680             # retrieve the value
681 27         46 my $value;
682 27 50       136 if ($self->can($unit)) {
683 27         84 $value = $self->$unit(\%opt);
684             }
685              
686             # All else fails... try wavelength
687 27 100       466 $value = $self->wavelength(\%opt) unless defined $value;
688              
689 27         106 return $value;
690             }
691              
692             =item B
693              
694             Compares two C objects.
695              
696             if ($wb1->compare($wb2)) {...}
697              
698             This method will return -1 if, in the above example, $wb1 is of
699             a shorter wavelength than $wb2, 0 if the wavelengths are equal,
700             and +1 if $wb1 is of a longer wavelength than $wb2. Please note
701             that for strict waveband equality the C method should be
702             used, as that method uses the C method to check if two
703             wavebands are identical.
704              
705             This method is overloaded with the standard numerical comparison
706             operators, so to check if one waveband is shorter than another
707             you would do
708              
709             if ($wb1 < $wb2) {...}
710              
711             and it will work as you expect. This method does not overload
712             the == operator; see the C method for that.
713              
714             =cut
715              
716             sub compare {
717 2     2 1 7 my ($object1, $object2, $was_reversed) = @_;
718 2 50       8 ($object1, $object2) = ($object2, $object1) if $was_reversed;
719              
720 2         6 return $object1->wavelength <=> $object2->wavelength;
721             }
722              
723             =item B
724              
725             Compares two C objects for equality.
726              
727             if ($wb1->equals($wb2)) {...}
728              
729             This method will return 1 if, in the above example, both
730             C objects return the same value from the
731             C method AND for the C method (if it
732             is defined for both objects) , and 0 of they return different values.
733              
734             This method is overloaded using the == operator, so
735              
736             if ($wb1 == $wb2) {...}
737              
738             is functionally the same as the first example.
739              
740             =cut
741              
742             sub equals {
743 3     3 1 12 my $self = shift;
744 3         5 my $comp = shift;
745              
746 3 100 66     10 if (defined($self->instrument) && defined($comp->instrument)) {
747 1   33     5 return (($self->natural eq $comp->natural)
748             && ($self->instrument eq $comp->instrument));
749             }
750             else {
751 2         8 return ($self->natural eq $comp->natural);
752             }
753             }
754              
755             =item B
756              
757             Compares two C objects for inequality.
758              
759             if ($wb1->not_equals($wb2)) {...}
760              
761             This method will return 1 if, in the above example, either the
762             C method or the C method return different
763             values. If the instrument is undefined for either object, then
764             the C method will be used.
765              
766             This method is overloaded using the != operator, so
767              
768             if ($wb1 != $wb2) {...}
769              
770             is functionally the same as the first example.
771              
772             =cut
773              
774             sub not_equals {
775 1     1 1 8 my $self = shift;
776 1         2 my $comp = shift;
777              
778 1 50 33     4 if (!defined($self->instrument) || !defined($comp->instrument)) {
779 0         0 return ($self->natural ne $comp->natural);
780             }
781             else {
782 1   33     4 return (($self->natural ne $comp->natural)
783             || ($self->instrument ne $comp->instrument));
784             }
785             }
786              
787             =back
788              
789             =begin __PRIVATE_METHODS__
790              
791             =head2 Private Methods
792              
793             =over 4
794              
795             =item B<_cache>
796              
797             Retrieve the hash reference associated with the cache (in a scalar
798             context) or the contents of the hash (in a list context).
799              
800             $ref = $w->cache;
801             %cache = $w->cache;
802              
803             =cut
804              
805             sub _cache {
806 179     179   288 my $self = shift;
807 179 50       372 if (wantarray) {
808 0         0 return %{$self->{Cache}};
  0         0  
809             }
810             else {
811 179         478 return $self->{Cache};
812             }
813             }
814              
815             =item B<_store_in_cache>
816              
817             Store values in the cache associated with particular types.
818              
819             $w->_store_in_cache(
820             "filter" => "K",
821             "frequency" => 1.4E14);
822              
823             If the cache already contains a value for this entry the cache
824             is cleared prior to storing it (unless it contains the same value)
825             on the assumption that the cache is no longer consistent.
826              
827             More than one key can be supplied. All keys are tested for prior
828             existence before inserting the new ones.
829              
830             =cut
831              
832             sub _store_in_cache {
833 44     44   107 my $self = shift;
834 44         132 my %entries = @_;
835              
836             # Get the cache
837 44         98 my $cache = $self->_cache;
838              
839             # First check to see whether we have any entries in the
840             # cache that clash
841 44         125 for my $key (keys %entries) {
842             # No worries if it is not there
843 62 100       193 next unless exists $cache->{$key};
844              
845             # Check to see if the value is the same as is already present
846             # Use a string comparison for filter
847 1 50       4 if ($key eq 'filter') {
848 0 0       0 next if $cache->{$key} eq $entries{$key};
849             }
850             else {
851             # Number
852 1 50       5 next if $cache->{$key} == $entries{$key};
853             }
854              
855             # Now we have a key that exists but its value is
856             # different. Clear the cache and exit the loop.
857             # This means the loop never really reaches the end
858             # of the block...
859 1         6 $self->_clear_cache;
860              
861 1         3 last;
862             }
863              
864             # Now insert the values
865 44         98 for my $key (keys %entries) {
866 63         150 $cache->{$key} = $entries{$key};
867             }
868              
869             # finished
870 44         133 return;
871             }
872              
873             =item B<_clear_cache>
874              
875             Empty the cache.
876              
877             =cut
878              
879             sub _clear_cache {
880 1     1   3 my $self = shift;
881 1         2 %{$self->_cache} = ();
  1         3  
882 1         2 return;
883             }
884              
885             =item B<_fetch_from_cache>
886              
887             Retrieve an item from the cache. Returns C if the item is
888             not stored in the cache.
889              
890             $filter = $w->_fetch_from_cache("filter");
891              
892             Could be combined into a single method with C<_store_in_cache> but
893             separated for simplicity.
894              
895             =cut
896              
897             sub _fetch_from_cache {
898 134     134   219 my $self = shift;
899 134 50       323 return undef unless @_;
900              
901 134         209 my $key = shift;
902 134 50       314 return undef unless $key;
903 134         229 $key = lc($key); # level playing field
904              
905             # Return the value from the cache if it exists
906 134         282 my $cache = $self->_cache;
907 134 100       534 return $cache->{$key} if exists $cache->{$key};
908              
909 26         71 return undef;
910             }
911              
912             =item B<_cache_value_and_wav>
913              
914             Cache the supplied value, converting it to the internal format
915             if necessary.
916              
917             $w->_cache_value_and_wav('frequency', $frequency);
918              
919             If the wavelength can not be determind the cache is cleared
920             and the supplied value is inserted (but without wavelength
921             information)..
922              
923             =cut
924              
925             sub _cache_value_and_wav {
926 19     19   35 my $self = shift;
927              
928 19         35 my $category = shift;
929 19         35 my $value = shift;
930 19 50       47 return unless defined $value;
931              
932             # Convert to the internal format (wavelength)
933 19         58 my $internal = $self->_convert_from($category, $value);
934              
935             # Store all defined values into cache
936 19         37 my %store;
937 19         48 $store{$category} = $value;
938 19 50       60 $store{wavelength} = $internal if defined $internal;
939              
940             # Clear cache if wavelength is not to be supplied
941 19 50       65 $self->_clear_cache() unless defined $internal;
942              
943 19         69 $self->_store_in_cache(%store);
944              
945 19         47 return;
946             }
947              
948             =item B<_read_value_with_convert>
949              
950             Read a value from the cache, converting it to the required units
951             as necessary.
952              
953             $value = $w->_read_value_with_convert('frequency');
954              
955             Returns C if no value has been stored in the object.
956              
957             =cut
958              
959             sub _read_value_with_convert {
960 61     61   149 my $self = shift;
961 61         137 my $category = lc(shift);
962              
963 61         149 my $value = $self->_fetch_from_cache($category);
964              
965             # Convert it if necessary
966 61 100       140 unless ($value) {
967             # Convert it from the default value (if set)
968 26         65 $value = $self->_convert_to($category);
969              
970             # Cache it if necessary
971 26 100       94 $self->_store_in_cache($category => $value)
972             if $value;
973             }
974              
975 61         171 return $value;
976             }
977              
978             =item B<_convert_to>
979              
980             Convert the value stored internally as the default format to the
981             required format. This simplifies the conversion routines since
982             there is only a single format to convert from and to.
983              
984             $value = $w->_convert_to('frequency');
985              
986             Returns the converted value or undef on error. The internal format
987             (wavelength) is read directly from the cache.
988              
989             =cut
990              
991             sub _convert_to {
992 26     26   42 my $self = shift;
993 26         40 my $category = shift;
994              
995 26         51 my $lambda = $self->_fetch_from_cache('wavelength');
996 26 50       60 return undef unless defined $lambda;
997              
998             # Check all types
999 26         44 my $output;
1000 26 50       103 if ($category eq 'wavelength') {
    100          
    100          
    50          
1001 0         0 $output = $lambda;
1002             }
1003             elsif ($category eq 'frequency') {
1004             # Microns
1005 2         7 $output = CLIGHT / ($lambda * 1.0E-6);
1006             }
1007             elsif ($category eq 'wavenumber') {
1008             # Inverse cm
1009 10         28 $output = 1.0 / ($lambda / 10_000);
1010             }
1011             elsif ($category eq 'filter') {
1012             # This is slightly harder since we know the value but
1013             # not the key. Go through each hash looking for a matching
1014             # key. If we know the instrument we start looking there
1015             # Else we have to look through GENERIC followed by all the
1016             # remaining instruments
1017              
1018 14         39 my $instrument = $self->instrument;
1019 14         66 my @search = ('GENERIC', keys %FILTERS);
1020 14 100       46 unshift(@search, $instrument) if defined $instrument;
1021              
1022             # There will be a precision issue here so we convert
1023             # the base wavelegnth to use 8 significant figures
1024 14         103 $lambda = sprintf("%8e", $lambda);
1025              
1026 14         33 OUTER: foreach my $inst (@search) {
1027 91 100       234 next unless exists $FILTERS{$inst};
1028 88         143 my $hash = $FILTERS{$inst};
1029 88         135 for my $key (keys %{$hash}) {
  88         445  
1030             # Make sure we use the same rounding scheme on the values
1031             # returned from the hash, so we don't have to worry about
1032             # rounding issues fouling things up (like saying 8.3e-1 !=
1033             # 0.83).
1034 1509 100       5173 if (sprintf("%8e", $hash->{$key}) eq $lambda) {
1035 5         10 $output = $key;
1036 5         20 last OUTER;
1037             }
1038             }
1039             }
1040             }
1041              
1042 26         91 return $output;
1043             }
1044              
1045             =item B<_convert_from>
1046              
1047             Convert from the supplied values to the internal format (wavelength).
1048              
1049             $value = $w->_convert_from('frequency', $frequency);
1050              
1051             Returns the converted value. Returns C if the conversion
1052             is not possible.
1053              
1054             =cut
1055              
1056             sub _convert_from {
1057 19     19   38 my $self = shift;
1058              
1059 19         45 my $category = lc(shift);
1060 19         33 my $value = shift;
1061 19 50       48 return undef unless defined $value;
1062              
1063             # Go through each type
1064 19         36 my $output;
1065 19 50       90 if ($category eq 'wavelength') {
    100          
    100          
    50          
1066 0         0 $output = $value;
1067             }
1068             elsif ($category eq 'frequency') {
1069             # Convert frequency to wavelength
1070             # converting from metres to microns
1071 5         17 $output = CLIGHT / ($value * 1.0E-6);
1072              
1073             }
1074             elsif ($category eq 'wavenumber') {
1075             # 1 / cm then convert cm to microns
1076 1         4 $output = (1.0 / $value) * 10_000;
1077             }
1078             elsif ($category eq 'filter') {
1079             # Convert filter to wavelength
1080             # Need to walk through %FILTERS first for a
1081             # instrument match and then for a generic match
1082 13         31 my $instrument = $self->instrument;
1083 13         32 my @search = ('GENERIC');
1084 13 100       38 unshift(@search, $instrument) if defined $instrument;
1085              
1086 13         33 foreach my $name (@search) {
1087             # First look for a match in %FILTERS
1088 13 50       39 if (exists $FILTERS{$name}) {
1089             # Now look for the filter itself
1090 13 50       39 if (exists $FILTERS{$name}{$value}) {
1091 13         31 $output = $FILTERS{$name}{$value};
1092 13         35 last;
1093             }
1094             }
1095             }
1096             }
1097              
1098 19         61 return $output;
1099             }
1100              
1101             =item B<_format_with_unit>
1102              
1103             Format a numeric value by selecting an appropriate unit
1104             prefix. The number is then returned formatted to the given
1105             number of decimal places followed by a space and then
1106             the combined unit string.
1107              
1108             =cut
1109              
1110             sub _format_with_unit {
1111 4     4   9 my $self = shift;
1112 4         8 my $value = shift;
1113 4         7 my $baseunit = shift;
1114 4         7 my $ndp = shift;
1115              
1116 4 50       11 return '' unless defined $value;
1117              
1118 4         13 my $range = log($value) / log(10);
1119 4         9 my ($unit, $prefrange);
1120 4         10 foreach my $pr (@PREFIXES) {
1121 37         69 ($unit, $prefrange) = @$pr;
1122 37 100       74 if ($range >= $prefrange) {
1123 4         10 last;
1124             }
1125             }
1126              
1127             # Special case: show microns as just "mu"?
1128 4 100 66     18 $unit .= $baseunit unless $unit eq 'mu' and $baseunit eq 'm';
1129              
1130 4         83 return sprintf '%.*f %s', $ndp, ($value / 10 ** $prefrange), $unit;
1131             }
1132              
1133             =item B<_format_fixed_unit>
1134              
1135             Method to format a value in the same way as C<_format_with_unit>
1136             but applying a fixed unit string. (I.e. no automatic prefix.)
1137              
1138             Note: since this is currently only used with '/cm' for wavenumber,
1139             no space is included between the value and unit string.
1140              
1141             =cut
1142              
1143             sub _format_fixed_unit {
1144 1     1   2 my $self = shift;
1145 1         1 my $value = shift;
1146 1         2 my $unit = shift;
1147 1         2 my $ndp = shift;
1148              
1149 1 50       2 return '' unless defined $value;
1150              
1151 1         10 return sprintf '%.*f%s', $ndp, $value, $unit;
1152             }
1153              
1154             =back
1155              
1156             =end __PRIVATE_METHODS__
1157              
1158             =head2 Static functions
1159              
1160             These functions enable the user to obtain an overview of
1161             the supported filter, instrument and telescope combinations.
1162              
1163             =over 4
1164              
1165             =item B
1166              
1167             Returns true if the a particular instrument has a particular filter,
1168             otherwise returns C, e.g.
1169              
1170             if (Astro::WaveBand::has_filter(UIST => "Kprime")) {
1171             ...
1172             }
1173              
1174             if you pass a hash containing multiple instrument combinations,
1175             all must be valid or the method will return undef.
1176              
1177             =cut
1178              
1179             sub has_filter {
1180 4 50   4 1 920 return undef unless @_;
1181              
1182             # grab instrument and filter list
1183 4         15 my %list = @_;
1184              
1185 4         10 my $counter = 0;
1186 4         17 foreach my $key (sort keys %list) {
1187             # if the filter exists in the filter list for that instrument,
1188             # increment the counter
1189 6 100       23 $counter++ if exists $FILTERS{$key}{$list{$key}};
1190             }
1191              
1192             # if the counter is the same size as the input list then all conditons
1193             # have been proved to be true...
1194 4 100       20 return undef unless scalar(keys %list) == $counter;
1195 2         13 return 1;
1196             }
1197              
1198             =item B
1199              
1200             Returns true if the a particular instrument exists for a particular
1201             telescope, otherwise returns C, e.g.
1202              
1203             if (Astro::WaveBand::has_instrument(UKIRT => "UIST")) {
1204             ...
1205             }
1206              
1207             if you pass a hash containing multiple instrument combinations,
1208             all must be valid or the method will return undef.
1209              
1210             =cut
1211              
1212             sub has_instrument {
1213 2 50   2 1 8 return undef unless @_;
1214              
1215             # grab instrument and filter list
1216 2         8 my %list = @_;
1217              
1218 2         5 my $counter = 0;
1219 2         7 foreach my $key (sort keys %list) {
1220             # if the filter exists in the filter list for that instrument,
1221             # increment the counter
1222 2         5 for my $i (0 ... $#{$TELESCOPE{$key}}) {
  2         9  
1223 10 100       29 if ($TELESCOPE{$key}->[$i] eq $list{$key}) {
1224 1         3 $counter++;
1225 1         3 last;
1226             }
1227             }
1228             }
1229              
1230             # if the counter is the same size as the input list then all conditons
1231             # have been proved to be true...
1232 2 100       12 return undef unless scalar(keys %list) == $counter;
1233 1         9 return 1;
1234             }
1235              
1236              
1237             =item B
1238              
1239             Returns true if the a particular telescope and filter combination is
1240             avaialable, otherwise returns C, e.g.
1241              
1242             if (Astro::WaveBand::is_observable(UKIRT => 'Kprime')) {
1243             ...
1244             }
1245              
1246             =cut
1247              
1248             sub is_observable {
1249              
1250             #my $self = shift;
1251 3 50   3 1 12 return undef unless @_;
1252              
1253             # grab instrument and filter list
1254 3         12 my %list = @_;
1255              
1256 3         6 my $counter = 0;
1257 3         11 foreach my $key (sort keys %list) {
1258             # if the filter exists in the filter list for that instrument,
1259             # increment the counter
1260             #print "TELESCOPE $key\n";
1261 3         5 for my $i (0 ... $#{$TELESCOPE{$key}}) {
  3         11  
1262             #print " INSTRUMENT ${$TELESCOPE{$key}}[$i]\n";
1263             #print " \$list{\$key} = $list{$key}\n";
1264 10         14 my $instrument = ${$TELESCOPE{$key}}[$i];
  10         23  
1265              
1266 10 100       16 if (${$FILTERS{$instrument}}{$list{$key}}) {
  10         38  
1267 2         5 $counter++;
1268             #print "$counter: $key\n";
1269             #print " $list{$key}, $instrument, $list{$key}, ".
1270             # "${$FILTERS{${$TELESCOPE{$key}}[$i]}}{$list{$key}}\n";
1271 2         7 last;
1272             }
1273             }
1274             }
1275              
1276             # if the counter is the same size as the input list then all conditons
1277             # have been proved to be true...
1278 3 100       14 return undef unless scalar(keys %list) == $counter;
1279 2         9 return 1;
1280             }
1281              
1282             =back
1283              
1284             =head1 BUGS
1285              
1286             Does not automatically convert metres to microns and GHz to Hz etc.
1287              
1288             Can not handle filters that correspond to multiple wavelengths.
1289             Currently SCUBA is the main issue. With a 450:850 filter this class
1290             always returns the shortest wavelength (since that is the wavelength
1291             that affects scheduling the most).
1292              
1293             Should handle velocities and redshifts in order to disambiguate rest
1294             frequencies and observed frequencies. Would also be nice if the class
1295             used the species and transition to allow the natural unit
1296             to appear as something like: "CO 3-2 @ 30km/s LSR radio".
1297              
1298             =head1 AUTHORS
1299              
1300             Tim Jenness Et.jenness@jach.hawaii.eduE
1301             Alasdair Allan Eaa@astro.ex.ac.ukE
1302             Tim Lister Etlister@lcogt.netE
1303              
1304             =head1 COPYRIGHT
1305              
1306             Copyright (C) 2001-2003 Particle Physics and Astronomy Research Council.
1307             All Rights Reserved.
1308              
1309             This program is free software; you can redistribute it and/or modify
1310             it under the same terms as Perl itself.
1311              
1312             =cut
1313              
1314             1;