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 }