View Javadoc
1   /*
2    * #%L
3    * nom.tam FITS library
4    * %%
5    * Copyright (C) 2004 - 2024 nom-tam-fits
6    * %%
7    * This is free and unencumbered software released into the public domain.
8    *
9    * Anyone is free to copy, modify, publish, use, compile, sell, or
10   * distribute this software, either in source code form or as a compiled
11   * binary, for any purpose, commercial or non-commercial, and by any
12   * means.
13   *
14   * In jurisdictions that recognize copyright laws, the author or authors
15   * of this software dedicate any and all copyright interest in the
16   * software to the public domain. We make this dedication for the benefit
17   * of the public at large and to the detriment of our heirs and
18   * successors. We intend this dedication to be an overt act of
19   * relinquishment in perpetuity of all present and future rights to this
20   * software under copyright law.
21   *
22   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23   * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
25   * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
26   * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
27   * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
28   * OTHER DEALINGS IN THE SOFTWARE.
29   * #L%
30   */
31  
32  package nom.tam.util;
33  
34  import java.util.StringTokenizer;
35  import java.util.logging.Logger;
36  
37  import nom.tam.fits.FitsFactory;
38  import nom.tam.fits.LongValueException;
39  
40  /**
41   * <p>
42   * A no-frills complex value, for representing complex numbers in FITS headers. It is a non-mutable object that is
43   * created with a real and imaginary parts, which can be retrieved thereafter, and provides string formatting that is
44   * suited specifically for representation in FITS headers.
45   * </p>
46   * <p>
47   * Note that binary tables handle complex data differently, with elements of `float[2]` or `double[2]`.
48   * </p>
49   *
50   * @author Attila Kovacs
51   *
52   * @since  1.16
53   */
54  public class ComplexValue {
55  
56      private static final Logger LOG = Logger.getLogger(ComplexValue.class.getName());
57  
58      /** The complex zero **/
59      public static final ComplexValue ZERO = new ComplexValue(0.0, 0.0);
60  
61      /** The complex unity along the real axis, or (1.0, 0.0) **/
62      public static final ComplexValue ONE = new ComplexValue(1.0, 0.0);
63  
64      /** The unity along the imaginary axis <i>i</i>, or (0.0, 1.0) **/
65      public static final ComplexValue I = new ComplexValue(0.0, 1.0);
66  
67      /** The real and imaginary parts */
68      private double re, im;
69  
70      /**
71       * The minimum size string needed to represent a complex value with even just single digits for the real and
72       * imaginary parts.
73       */
74      private static final int MIN_STRING_LENGTH = 5; // "(#,#)"
75  
76      /**
77       * Private constructor
78       */
79      private ComplexValue() {
80  
81      }
82  
83      /**
84       * Instantiates a new complex number value with the specified real and imaginary components.
85       *
86       * @param re the real part
87       * @param im thei maginary part
88       */
89      public ComplexValue(double re, double im) {
90          this();
91          this.re = re;
92          this.im = im;
93      }
94  
95      /**
96       * Returns the real part of this complex value.
97       *
98       * @return the real part
99       *
100      * @see    #im()
101      */
102     public final double re() {
103         return re;
104     }
105 
106     /**
107      * Returns the imaginary part of this complex value.
108      *
109      * @return the imaginary part
110      *
111      * @see    #re()
112      */
113     public final double im() {
114         return im;
115     }
116 
117     @Override
118     public int hashCode() {
119         return Double.hashCode(re()) ^ Double.hashCode(im());
120     }
121 
122     @Override
123     public boolean equals(Object o) {
124         if (o == this) {
125             return true;
126         }
127 
128         if (!(o instanceof ComplexValue)) {
129             return false;
130         }
131 
132         ComplexValue z = (ComplexValue) o;
133         return z.re() == re() && z.im() == im();
134     }
135 
136     /**
137      * Checks if the complex value is zero. That is, if both the real or imaginary parts are zero.
138      *
139      * @return <code>true</code>if both the real or imaginary parts are zero. Otherwise <code>false</code>.
140      */
141     public final boolean isZero() {
142         return re() == 0.0 && im() == 0.0;
143     }
144 
145     /**
146      * Checks if the complex value is finite. That is, if neither the real or imaginary parts are NaN or Infinite.
147      *
148      * @return <code>true</code>if neither the real or imaginary parts are NaN or Infinite. Otherwise
149      *             <code>false</code>.
150      */
151     public final boolean isFinite() {
152         return Double.isFinite(re()) && Double.isFinite(im());
153     }
154 
155     @Override
156     public String toString() {
157         return "(" + re() + "," + im() + ")";
158     }
159 
160     /**
161      * Converts this complex value to its string representation with up to the specified number of decimal places
162      * showing after the leading figure, for both the real and imaginary parts.
163      *
164      * @param  decimals the maximum number of decimal places to show.
165      *
166      * @return          the string representation with the specified precision, which may be used in a FITS header.
167      *
168      * @see             FlexFormat
169      */
170     public String toString(int decimals) {
171         FlexFormat f = new FlexFormat().setPrecision(decimals);
172         return "(" + f.format(re()) + "," + f.format(im()) + ")";
173     }
174 
175     /**
176      * <p>
177      * Instantiates a new complex number value from the string repressentation of it in a FITS header value. By default,
178      * it will parse complex numbers as a comma-separated pair of real values enclosed in a bracket, such as
179      * <code>(1.0, -2.0)</code>, or standard real values, such as <code>123.456</code> or <code>123</code> (as real-only
180      * values). There can be any number of spaces around the brackets, number components or the comma.
181      * </p>
182      * <p>
183      * If {@link FitsFactory#setAllowHeaderRepairs(boolean)} is set <code>true</code>, the parsing becomes more
184      * tolerant, working around missing closing brackets, different number of comma-separated components, and missing
185      * empty components. So, for example <code>(,-1,abc</code> may be parsed assuming it was meant to be -<i>i</i>.
186      * </p>
187      *
188      * @param  text                     The FITS header value representing the complex number, in brackets with the real
189      *                                      and imaginary pars separated by a comma. Additional spaces may surround the
190      *                                      component parts.
191      *
192      * @throws IllegalArgumentException if the supplied string does not appear to be a FITS standard representation of a
193      *                                      complex value.
194      *
195      * @see                             FitsFactory#setAllowHeaderRepairs(boolean)
196      */
197     public ComplexValue(String text) throws IllegalArgumentException {
198         this();
199 
200         // Allow the use of 'D' or 'd' to mark the exponent, instead of the standard 'E' or 'e'...
201         text = text.trim().toUpperCase().replace('D', 'E');
202 
203         boolean hasOpeningBracket = text.charAt(0) == '(';
204         boolean hasClosingBracket = text.charAt(text.length() - 1) == ')';
205 
206         if (!(hasOpeningBracket || hasClosingBracket)) {
207             // Use just the real value.
208             re = Double.parseDouble(text);
209             return;
210         }
211         if (!hasOpeningBracket || !hasClosingBracket) {
212             if (!FitsFactory.isAllowHeaderRepairs()) {
213                 throw new IllegalArgumentException("Missing bracket around complex value: '" + text
214                         + "'\n\n --> Try FitsFactory.setAllowHeaderRepair(true).\n");
215             }
216             LOG.warning("Ignored missing bracket in '" + text + "'.");
217         }
218 
219         int start = hasOpeningBracket ? 1 : 0;
220         int end = hasClosingBracket ? text.length() - 1 : text.length();
221         StringTokenizer tokens = new StringTokenizer(text.substring(start, end),
222                 FitsFactory.isAllowHeaderRepairs() ? ",; \t" : ", ");
223         if (tokens.countTokens() != 2) {
224             if (!FitsFactory.isAllowHeaderRepairs()) {
225                 throw new IllegalArgumentException(
226                         "Invalid complex value: '" + text + "'\n\n --> Try FitsFactory.setAllowHeaderRepair(true).\n");
227             }
228             LOG.warning("Ignored wrong number of components (" + tokens.countTokens() + ") in '" + text + "'.");
229         }
230 
231         if (tokens.hasMoreTokens()) {
232             re = Double.parseDouble(tokens.nextToken());
233         }
234         if (tokens.hasMoreTokens()) {
235             im = Double.parseDouble(tokens.nextToken());
236         }
237     }
238 
239     /**
240      * Converts this comlex value to its string representation using up to the specified number of characters only. The
241      * precision may be reduced as necessary to ensure that the representation fits in the allotted space.
242      *
243      * @param  maxLength          the maximum length of the returned string representation
244      *
245      * @return                    the string representation, possibly with reduced precision to fit into the alotted
246      *                                space.
247      *
248      * @throws LongValueException if the space was too short to fit the value even with the minimal (1-digit) precision.
249      */
250     public String toBoundedString(int maxLength) throws LongValueException {
251         if (maxLength < MIN_STRING_LENGTH) {
252             throw new LongValueException(maxLength, toString());
253         }
254 
255         String s = toString();
256         if (s.length() <= maxLength) {
257             return s;
258         }
259 
260         int decimals = FlexFormat.DOUBLE_DECIMALS;
261 
262         s = toString(decimals);
263         while (s.length() > maxLength) {
264             // Assume both real and imaginary parts shorten the same amount...
265             decimals -= (s.length() - maxLength + 1) / 2;
266 
267             if (decimals < 0) {
268                 throw new LongValueException(maxLength, toString());
269             }
270             s = toString(decimals);
271         }
272 
273         return s;
274     }
275 
276     /**
277      * Converts this complex number to an array of 2.
278      * 
279      * @return An array of 2 floating point values.
280      * 
281      * @since  1.20
282      */
283     Object toArray() {
284         return new double[] {re, im};
285     }
286 
287     /**
288      * Single-precision complex values.
289      * 
290      * @author Attila Kovacs
291      * 
292      * @since  1.18
293      */
294     public static final class Float extends ComplexValue {
295 
296         /**
297          * Instantiates a new single-precision complex number value with the specified real and imaginary components.
298          *
299          * @param re the real part
300          * @param im thei maginary part
301          * 
302          * @since    1.20
303          */
304         public Float(float re, float im) {
305             super(re, im);
306         }
307 
308         /**
309          * <p>
310          * Instantiates a new single-precision complex number value from the string repressentation of it in a FITS
311          * header value. By default, it will parse complex numbers as a comma-separated pair of real values enclosed in
312          * a bracket, such as <code>(1.0, -2.0)</code>, or standard real values, such as <code>123.456</code> or
313          * <code>123</code> (as real-only values). There can be any number of spaces around the brackets, number
314          * components or the comma.
315          * </p>
316          * 
317          * @param  str                      the FITS string representation of the complex value
318          * 
319          * @throws IllegalArgumentException if the supplied string does not appear to be a FITS standard representation
320          *                                      of a complex value.
321          * 
322          * @since                           1.20
323          */
324         public Float(String str) throws IllegalArgumentException {
325             super(str);
326         }
327 
328         @Override
329         Object toArray() {
330             return new float[] {(float) re(), (float) im()};
331         }
332     }
333 }