ppdmod 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ppdmod/__init__.py +1 -0
- ppdmod/base.py +225 -0
- ppdmod/components.py +557 -0
- ppdmod/config/standard_parameters.toml +290 -0
- ppdmod/data.py +485 -0
- ppdmod/fitting.py +546 -0
- ppdmod/options.py +164 -0
- ppdmod/parameter.py +152 -0
- ppdmod/plot.py +1241 -0
- ppdmod/utils.py +575 -0
- ppdmod-2.0.0.dist-info/METADATA +68 -0
- ppdmod-2.0.0.dist-info/RECORD +15 -0
- ppdmod-2.0.0.dist-info/WHEEL +5 -0
- ppdmod-2.0.0.dist-info/licenses/LICENSE +21 -0
- ppdmod-2.0.0.dist-info/top_level.txt +1 -0
ppdmod/utils.py
ADDED
@@ -0,0 +1,575 @@
|
|
1
|
+
import shutil
|
2
|
+
import time as time
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import Any, Callable, List, Tuple
|
5
|
+
|
6
|
+
import astropy.units as u
|
7
|
+
import numpy as np
|
8
|
+
from astropy.io import fits
|
9
|
+
from numpy.typing import NDArray
|
10
|
+
from scipy.interpolate import interp1d
|
11
|
+
from scipy.stats import linregress
|
12
|
+
|
13
|
+
from .options import OPTIONS
|
14
|
+
|
15
|
+
|
16
|
+
def save_model_to_fits(
|
17
|
+
components: List, fits_files: List[Path], save_dir: Path, suffix: str = ".fits"
|
18
|
+
) -> None:
|
19
|
+
"""Calculates the model and saves it into the corresponding fits files."""
|
20
|
+
for fits_file in fits_files:
|
21
|
+
for t in range(OPTIONS.data.nt):
|
22
|
+
shutil.copy(fits_file, save_dir / f"{fits_file.stem}_MODEL_t{t}{suffix}")
|
23
|
+
with fits.open(
|
24
|
+
save_dir / f"{fits_file.stem}_MODEL_t{t}{suffix}", "update"
|
25
|
+
) as hdul:
|
26
|
+
instrument = hdul[0].header.get("instrume", "unknown").lower()
|
27
|
+
sci_index = (
|
28
|
+
OPTIONS.data.gravity.index if instrument == "gravity" else None
|
29
|
+
)
|
30
|
+
wavelength = (
|
31
|
+
hdul["oi_wavelength", sci_index].data["eff_wave"] * u.m
|
32
|
+
).to(u.um)
|
33
|
+
for extension in ["oi_vis", "oi_vis2", "oi_t3"]:
|
34
|
+
if extension in ["oi_vis", "oi_vis2"]:
|
35
|
+
ucoord = np.insert(
|
36
|
+
hdul[extension, sci_index].data["ucoord"], 0, 0
|
37
|
+
)
|
38
|
+
vcoord = np.insert(
|
39
|
+
hdul[extension, sci_index].data["vcoord"], 0, 0
|
40
|
+
)
|
41
|
+
elif extension == "oi_t3":
|
42
|
+
u1 = hdul[extension, sci_index].data["u1coord"]
|
43
|
+
u2 = hdul[extension, sci_index].data["u2coord"]
|
44
|
+
v1 = hdul[extension, sci_index].data["v1coord"]
|
45
|
+
v2 = hdul[extension, sci_index].data["v2coord"]
|
46
|
+
u123 = np.insert([u1, u2, u1 + u2], 0, 0, axis=1)
|
47
|
+
v123 = np.insert([v1, v2, v1 + v2], 0, 0, axis=1)
|
48
|
+
ucoord, vcoord, i123 = get_t3_indices(u123, v123)
|
49
|
+
|
50
|
+
complex_vis = np.sum(
|
51
|
+
[
|
52
|
+
comp.compute_complex_vis(ucoord, vcoord, t, wavelength)
|
53
|
+
for comp in components
|
54
|
+
],
|
55
|
+
axis=0,
|
56
|
+
)
|
57
|
+
|
58
|
+
if extension in ["oi_vis", "oi_vis2"]:
|
59
|
+
model_flux = complex_vis[:, 0].reshape(-1, 1)
|
60
|
+
if extension == "oi_vis2":
|
61
|
+
val, err = "vis2data", "vis2err"
|
62
|
+
complex_vis /= model_flux
|
63
|
+
complex_vis *= complex_vis
|
64
|
+
else:
|
65
|
+
val, err = "visamp", "visamperr"
|
66
|
+
hdul["oi_flux", sci_index].data["fluxdata"] = np.abs(
|
67
|
+
model_flux.squeeze(-1)
|
68
|
+
)
|
69
|
+
hdul["oi_flux", sci_index].data["fluxerr"] = np.nan
|
70
|
+
hdul["oi_flux", sci_index].data["flag"] = False
|
71
|
+
|
72
|
+
if (
|
73
|
+
"visphi".upper()
|
74
|
+
in hdul[extension, sci_index].columns.names
|
75
|
+
):
|
76
|
+
diff_phases = []
|
77
|
+
for phase in np.angle(complex_vis, deg=True).T[1:]:
|
78
|
+
res = linregress(1 / wavelength.value, phase)
|
79
|
+
diff_phases.append(
|
80
|
+
phase
|
81
|
+
- (
|
82
|
+
res.slope * 1 / wavelength.value
|
83
|
+
+ res.intercept
|
84
|
+
)
|
85
|
+
)
|
86
|
+
|
87
|
+
hdul[extension, sci_index].data["visphi"] = np.array(
|
88
|
+
diff_phases
|
89
|
+
)
|
90
|
+
hdul[extension, sci_index].data["visphierr"] = np.nan
|
91
|
+
|
92
|
+
hdul[extension, sci_index].data[val] = compute_vis(
|
93
|
+
complex_vis[:, 1:]
|
94
|
+
).T
|
95
|
+
hdul[extension, sci_index].data[err] = np.nan
|
96
|
+
hdul[extension, sci_index].data["flag"] = False
|
97
|
+
|
98
|
+
elif extension == "oi_t3":
|
99
|
+
hdul[extension, sci_index].data["t3phi"] = compute_t3(
|
100
|
+
complex_vis, i123
|
101
|
+
)[:, 1:].T
|
102
|
+
hdul[extension, sci_index].data["t3phierr"] = np.nan
|
103
|
+
hdul[extension, sci_index].data["flag"] = False
|
104
|
+
|
105
|
+
hdul.flush()
|
106
|
+
|
107
|
+
|
108
|
+
def get_binning_windows(wavelength: NDArray[Any]) -> u.Quantity[u.um]:
|
109
|
+
"""Gets all the binning windows."""
|
110
|
+
skip_set = set()
|
111
|
+
all_binning_windows = []
|
112
|
+
for band in list(map(get_band, wavelength)):
|
113
|
+
windows = getattr(OPTIONS.data.binning, band).value
|
114
|
+
if band in skip_set:
|
115
|
+
continue
|
116
|
+
|
117
|
+
if isinstance(windows, (list, tuple, np.ndarray)):
|
118
|
+
all_binning_windows.extend(windows)
|
119
|
+
skip_set.add(band)
|
120
|
+
else:
|
121
|
+
all_binning_windows.append(windows)
|
122
|
+
return all_binning_windows * u.um
|
123
|
+
|
124
|
+
|
125
|
+
def create_adaptive_bins(
|
126
|
+
wavelength_range: List[float],
|
127
|
+
wavelength_range_fine: List[float],
|
128
|
+
bin_window_fine: float,
|
129
|
+
bin_window_coarse: float,
|
130
|
+
) -> Tuple[NDArray[Any], NDArray[Any]]:
|
131
|
+
"""Create an adaptive binning wavelength grid.
|
132
|
+
|
133
|
+
Parameters
|
134
|
+
----------
|
135
|
+
wavelength_range : list of float
|
136
|
+
The wavlenength range where bins with corresponding windows are to be created.
|
137
|
+
wavelength_range_fine : list of float
|
138
|
+
The wavelength range where the bins are to be fine.
|
139
|
+
bin_window_fine : float
|
140
|
+
The fine binning window (left and right of the bin).
|
141
|
+
bin_window_coarse : float
|
142
|
+
The coarse binning window (left and right of the bin).
|
143
|
+
|
144
|
+
Returns
|
145
|
+
-------
|
146
|
+
bins : numpy.ndarray
|
147
|
+
windows : numpy.ndarray
|
148
|
+
"""
|
149
|
+
range_min, range_max = wavelength_range
|
150
|
+
range_min_fine, range_max_fine = wavelength_range_fine
|
151
|
+
|
152
|
+
if range_min >= range_max or range_min_fine >= range_max_fine:
|
153
|
+
raise ValueError("Invalid wavelength ranges.")
|
154
|
+
if not (range_min <= range_min_fine and range_max_fine <= range_max):
|
155
|
+
raise ValueError("Wavelength range must be within the full wavelength range.")
|
156
|
+
|
157
|
+
fine_bins = np.arange(range_min_fine, range_max_fine, bin_window_fine)
|
158
|
+
lower_coarse_bins = np.arange(
|
159
|
+
range_min, fine_bins[0] - bin_window_fine / 2, bin_window_coarse
|
160
|
+
)
|
161
|
+
upper_coarse_bins = np.arange(
|
162
|
+
fine_bins[-1] + (bin_window_fine + bin_window_coarse) / 2,
|
163
|
+
range_max,
|
164
|
+
bin_window_coarse,
|
165
|
+
)
|
166
|
+
bins = np.unique(np.concatenate((lower_coarse_bins, fine_bins, upper_coarse_bins)))
|
167
|
+
windows = np.concatenate(
|
168
|
+
(
|
169
|
+
np.full(lower_coarse_bins.shape, bin_window_coarse),
|
170
|
+
np.full(fine_bins.shape, bin_window_fine),
|
171
|
+
np.full(upper_coarse_bins.shape, bin_window_coarse),
|
172
|
+
)
|
173
|
+
)
|
174
|
+
return bins, windows
|
175
|
+
|
176
|
+
|
177
|
+
def compare_angles(phi: float, psi: float) -> float:
|
178
|
+
"""Subtracts two angles [-π, π].
|
179
|
+
|
180
|
+
Parameters
|
181
|
+
----------
|
182
|
+
phi : float
|
183
|
+
Angle (rad).
|
184
|
+
psi : float
|
185
|
+
Angle (rad).
|
186
|
+
|
187
|
+
Returns
|
188
|
+
-------
|
189
|
+
float
|
190
|
+
Difference of angles (rad).
|
191
|
+
"""
|
192
|
+
diff = phi - psi
|
193
|
+
diff = np.where(diff > np.pi, diff - 2 * np.pi, diff)
|
194
|
+
diff = np.where(diff < -np.pi, diff + 2 * np.pi, diff)
|
195
|
+
return diff
|
196
|
+
|
197
|
+
|
198
|
+
def windowed_linspace(start: float, end: float, window: float) -> NDArray[Any]:
|
199
|
+
"""Creates a numpy.linspace with a number of points so that the windowing doesn't overlap"""
|
200
|
+
return np.linspace(start, end, int((end - start) // (2 * window / 2)) + 1)
|
201
|
+
|
202
|
+
|
203
|
+
def get_band_limits(band: str) -> Tuple[float, float]:
|
204
|
+
"""Gets the limits of the respective band"""
|
205
|
+
match band:
|
206
|
+
case "hband":
|
207
|
+
return 1.5, 1.8
|
208
|
+
case "kband":
|
209
|
+
return 1.9, 2.5
|
210
|
+
case "lband":
|
211
|
+
return 2.6, 3.99
|
212
|
+
case "mband":
|
213
|
+
return 4.0, 6.0
|
214
|
+
case "nband":
|
215
|
+
return 7.5, 16.0
|
216
|
+
return 0, 0
|
217
|
+
|
218
|
+
|
219
|
+
def get_band(wavelength: u.um) -> str:
|
220
|
+
"""Gets the band of the (.fits)-file."""
|
221
|
+
wavelength = wavelength.value if isinstance(wavelength, u.Quantity) else wavelength
|
222
|
+
wl_min, wl_max = wavelength.min(), wavelength.max()
|
223
|
+
if wl_min > 1.5 and wl_max < 1.8:
|
224
|
+
return "hband"
|
225
|
+
if wl_min > 1.9 and wl_max < 2.5:
|
226
|
+
return "kband"
|
227
|
+
if wl_min > 2.6 and wl_max < 4.0:
|
228
|
+
return "lband"
|
229
|
+
if wl_min >= 4.0 and wl_max < 6.0:
|
230
|
+
return "mband"
|
231
|
+
if wl_min > 2.6 and wl_max < 6:
|
232
|
+
return "lmband"
|
233
|
+
if wl_min > 7.5 and wl_max < 16.0:
|
234
|
+
return "nband"
|
235
|
+
return "unknown"
|
236
|
+
|
237
|
+
|
238
|
+
def get_band_indices(grid: NDArray[Any], bands: List[str]) -> NDArray[Any]:
|
239
|
+
"""Gets the indices for a 1D grid that is matched to the bands."""
|
240
|
+
indices = []
|
241
|
+
for band in bands:
|
242
|
+
limits = get_band_limits(band)
|
243
|
+
indices.append(np.where((grid >= limits[0]) & (grid <= limits[1]))[0])
|
244
|
+
|
245
|
+
return np.concatenate(indices)
|
246
|
+
|
247
|
+
|
248
|
+
def smooth_interpolation(
|
249
|
+
interpolation_points: NDArray[Any],
|
250
|
+
grid: NDArray[Any],
|
251
|
+
values: NDArray[Any],
|
252
|
+
kind: str | None = None,
|
253
|
+
fill_value: str | None = None,
|
254
|
+
) -> NDArray[Any]:
|
255
|
+
"""Rebins the grid to a higher factor and then interpolates and averages
|
256
|
+
to the original grid.
|
257
|
+
|
258
|
+
Parameters
|
259
|
+
----------
|
260
|
+
interpolation_points : numpy.ndarray
|
261
|
+
The points to interpolate to.
|
262
|
+
points : numpy.ndarray
|
263
|
+
The points to interpolate from.
|
264
|
+
values : numpy.ndarray
|
265
|
+
The values to interpolate.
|
266
|
+
"""
|
267
|
+
kind = OPTIONS.data.interpolation.kind if kind is None else kind
|
268
|
+
fill_value = (
|
269
|
+
OPTIONS.data.interpolation.fill_value if fill_value is None else fill_value
|
270
|
+
)
|
271
|
+
points = interpolation_points.flatten()
|
272
|
+
windows = get_binning_windows(points).value
|
273
|
+
interpolation_grid = (
|
274
|
+
np.linspace(-1, 1, OPTIONS.data.interpolation.dim) * windows[:, np.newaxis] / 2
|
275
|
+
).T + points
|
276
|
+
return (
|
277
|
+
np.interp(interpolation_grid, grid, values)
|
278
|
+
.mean(axis=0)
|
279
|
+
.reshape(interpolation_points.shape)
|
280
|
+
)
|
281
|
+
|
282
|
+
|
283
|
+
def get_indices(
|
284
|
+
values: NDArray[Any],
|
285
|
+
array: NDArray[Any],
|
286
|
+
windows: NDArray[Any] | float | None = None,
|
287
|
+
) -> List[np.ndarray]:
|
288
|
+
"""Gets the indices of values occurring in a numpy array
|
289
|
+
and returns it in a list corresponding to the input values.
|
290
|
+
|
291
|
+
Parameters
|
292
|
+
----------
|
293
|
+
values : numpy.typing.NDArray
|
294
|
+
The values to find.
|
295
|
+
array : numpy.typing.NDArray
|
296
|
+
The array to search in.
|
297
|
+
window : numpy.typing.NDArray or float, optional
|
298
|
+
The window around the value to search in.
|
299
|
+
"""
|
300
|
+
array = array.value if isinstance(array, u.Quantity) else array
|
301
|
+
values = values.value if isinstance(values, u.Quantity) else values
|
302
|
+
values = [values] if not isinstance(values, (list, tuple, np.ndarray)) else values
|
303
|
+
windows = windows.value if isinstance(windows, u.Quantity) else windows
|
304
|
+
|
305
|
+
if windows is not None:
|
306
|
+
if isinstance(windows, (list, tuple, np.ndarray)):
|
307
|
+
indices = [
|
308
|
+
np.where(((v - w / 2) < array) & ((v + w / 2) > array))[0]
|
309
|
+
for v, w in zip(values, windows)
|
310
|
+
]
|
311
|
+
else:
|
312
|
+
indices = [
|
313
|
+
np.where(((v - windows / 2) < array) & ((v + windows / 2) > array))[0]
|
314
|
+
for v in values
|
315
|
+
]
|
316
|
+
else:
|
317
|
+
indices = []
|
318
|
+
for value in values:
|
319
|
+
index = np.where(array == value)[0]
|
320
|
+
if index.size == 0:
|
321
|
+
if value < array[0] or value > array[-1]:
|
322
|
+
indices.append(index.astype(int).flatten())
|
323
|
+
continue
|
324
|
+
|
325
|
+
index = np.where(array == min(array, key=lambda x: abs(x - value)))[0]
|
326
|
+
|
327
|
+
indices.append(index.astype(int).flatten())
|
328
|
+
return indices
|
329
|
+
|
330
|
+
|
331
|
+
def transform_coordinates(
|
332
|
+
x: float | np.ndarray,
|
333
|
+
y: float | np.ndarray,
|
334
|
+
cinc: float | None = None,
|
335
|
+
pa: float | None = None,
|
336
|
+
axis: str = "y",
|
337
|
+
) -> Tuple[float | np.ndarray, float | np.ndarray]:
|
338
|
+
"""Stretches and rotates the coordinate space depending on the
|
339
|
+
cosine of inclination and the positional angle.
|
340
|
+
|
341
|
+
Parameters
|
342
|
+
----------
|
343
|
+
x: float or numpy.ndarray or astropy.units.Quantity
|
344
|
+
The x-coordinate.
|
345
|
+
y: float or numpy.ndarray or astropy.units.Quantity
|
346
|
+
The y-coordinate.
|
347
|
+
cinc: float, optional
|
348
|
+
The cosine of the inclination.
|
349
|
+
pa: float, optional
|
350
|
+
The positional angle of the object (in degree).
|
351
|
+
axis: str, optional
|
352
|
+
The axis to stretch the coordinates on.
|
353
|
+
|
354
|
+
Returns
|
355
|
+
-------
|
356
|
+
xt: float or numpy.ndarray
|
357
|
+
Transformed x coordinate.
|
358
|
+
yt: float or numpy.ndarray
|
359
|
+
Transformed y coordinate.
|
360
|
+
"""
|
361
|
+
if pa is not None:
|
362
|
+
xt = x * np.cos(pa) - y * np.sin(pa)
|
363
|
+
yt = x * np.sin(pa) + y * np.cos(pa)
|
364
|
+
else:
|
365
|
+
xt, yt = x, y
|
366
|
+
|
367
|
+
if cinc is not None:
|
368
|
+
if axis == "x":
|
369
|
+
xt /= cinc
|
370
|
+
elif axis == "y":
|
371
|
+
xt *= cinc
|
372
|
+
|
373
|
+
return xt, yt
|
374
|
+
|
375
|
+
|
376
|
+
def translate_vis(
|
377
|
+
ucoord: np.ndarray, vcoord: np.ndarray, x: float, y: float
|
378
|
+
) -> np.ndarray:
|
379
|
+
"""Translates a coordinate shift in image space to Fourier space.
|
380
|
+
|
381
|
+
Parameters
|
382
|
+
----------
|
383
|
+
"""
|
384
|
+
translation = np.exp(-2j * np.pi * (x * ucoord + y * vcoord))
|
385
|
+
return translation.astype(OPTIONS.data.dtype.complex)
|
386
|
+
|
387
|
+
|
388
|
+
def translate_image(
|
389
|
+
xx: np.ndarray, yy: np.ndarray, x: float, y: float
|
390
|
+
) -> Tuple[np.ndarray, np.ndarray]:
|
391
|
+
"""Shifts the coordinates in image space according to an offset."""
|
392
|
+
xxs = (xx - x).astype(OPTIONS.data.dtype.real)
|
393
|
+
yys = (yy - y).astype(OPTIONS.data.dtype.real)
|
394
|
+
return xxs, yys
|
395
|
+
|
396
|
+
|
397
|
+
def qval_to_opacity(qval_file: Path) -> u.cm**2 / u.g:
|
398
|
+
"""Reads a qval file, then calculates and returns the
|
399
|
+
opacity.
|
400
|
+
|
401
|
+
Parameters
|
402
|
+
----------
|
403
|
+
qval_file : pathlib.Path
|
404
|
+
|
405
|
+
Returns
|
406
|
+
-------
|
407
|
+
opacity : astropy.units.cm**2/u.g
|
408
|
+
|
409
|
+
Notes
|
410
|
+
-----
|
411
|
+
The qval-files give the grain size in microns and the
|
412
|
+
density in g/cm^3.
|
413
|
+
"""
|
414
|
+
with open(qval_file, "r+", encoding="utf8") as file:
|
415
|
+
_, grain_size, density = map(float, file.readline().strip().split())
|
416
|
+
wavelength_grid, qval = np.loadtxt(
|
417
|
+
qval_file, skiprows=1, unpack=True, usecols=(0, 1)
|
418
|
+
)
|
419
|
+
return wavelength_grid * u.um, 3 * qval / (
|
420
|
+
4 * (grain_size * u.um).to(u.cm) * (density * u.g / u.cm**3)
|
421
|
+
)
|
422
|
+
|
423
|
+
|
424
|
+
def get_opacity(
|
425
|
+
source_dir: Path,
|
426
|
+
weights: np.ndarray,
|
427
|
+
names: List[str],
|
428
|
+
method: str,
|
429
|
+
individual: bool = False,
|
430
|
+
**kwargs,
|
431
|
+
) -> Tuple[np.ndarray, np.ndarray]:
|
432
|
+
"""Gets the opacity from input parameters."""
|
433
|
+
grf_dict = {
|
434
|
+
"olivine": "Olivine",
|
435
|
+
"pyroxene": "MgPyroxene",
|
436
|
+
"forsterite": "Forsterite",
|
437
|
+
"enstatite": "Enstatite",
|
438
|
+
"silica": "Silica",
|
439
|
+
}
|
440
|
+
|
441
|
+
files = []
|
442
|
+
for name in names:
|
443
|
+
name = name.lower()
|
444
|
+
for size in ["small", "large"]:
|
445
|
+
if method == "grf":
|
446
|
+
size = 0.1 if size == "small" else 2
|
447
|
+
file_name = f"{grf_dict[name]}{size:.1f}.Combined.Kappa"
|
448
|
+
else:
|
449
|
+
size = "Big" if size == "large" else size.title()
|
450
|
+
file_name = f"{size}{name.title()}.kappa"
|
451
|
+
|
452
|
+
files.append(source_dir / method / file_name)
|
453
|
+
|
454
|
+
usecols = (0, 2) if method == "grf" else (0, 1)
|
455
|
+
wl, opacity = load_data(files, usecols=usecols, **kwargs)
|
456
|
+
|
457
|
+
if individual:
|
458
|
+
return wl, opacity
|
459
|
+
|
460
|
+
opacity = (opacity * weights[:, np.newaxis]).sum(axis=0)
|
461
|
+
return wl, opacity
|
462
|
+
|
463
|
+
|
464
|
+
def load_data(
|
465
|
+
files: Path | List[Path],
|
466
|
+
load_func: Callable | None = None,
|
467
|
+
comments: str = "#",
|
468
|
+
skiprows: int = 1,
|
469
|
+
usecols: Tuple[int, int] = (0, 1),
|
470
|
+
method: str = "shortest",
|
471
|
+
kind: str | None = None,
|
472
|
+
fill_value: str | None = None,
|
473
|
+
) -> Tuple[np.ndarray, np.ndarray]:
|
474
|
+
"""Loads data from a file.
|
475
|
+
|
476
|
+
Can either be one or multiple files, but in case
|
477
|
+
of multiple files they need to have the same structure
|
478
|
+
|
479
|
+
and size (as they will be converted to numpy.ndarrays).
|
480
|
+
|
481
|
+
Parameters
|
482
|
+
----------
|
483
|
+
files : list of pathlib.Path
|
484
|
+
The files to load the data from.
|
485
|
+
load_func : callable, optional
|
486
|
+
The function to load the data with.
|
487
|
+
comments : str, optional
|
488
|
+
Comment identifier.
|
489
|
+
skiprows : str, optional
|
490
|
+
The rows to skip.
|
491
|
+
usecols : tuple of int, optional
|
492
|
+
The columns to use.
|
493
|
+
method : str, optional
|
494
|
+
The grid to interpolate/extrapolate the data on.
|
495
|
+
Default is 'shortest'. Other option is 'longest' or
|
496
|
+
'median'.
|
497
|
+
kind : str, optional
|
498
|
+
The interpolation kind.
|
499
|
+
Default is 'cubic'.
|
500
|
+
fill_value : str, optional
|
501
|
+
If "extrapolate", the data is extrapolated.
|
502
|
+
Default is None.
|
503
|
+
|
504
|
+
Returns
|
505
|
+
-------
|
506
|
+
wavelength_grid : numpy.ndarray
|
507
|
+
data : numpy.ndarray
|
508
|
+
"""
|
509
|
+
kind = OPTIONS.data.interpolation.kind if kind is None else kind
|
510
|
+
fill_value = (
|
511
|
+
OPTIONS.data.interpolation.fill_value if fill_value is None else fill_value
|
512
|
+
)
|
513
|
+
|
514
|
+
files = files if isinstance(files, list) else [files]
|
515
|
+
wavelength_grids, contents = [], []
|
516
|
+
for file in files:
|
517
|
+
if load_func is not None:
|
518
|
+
wavelengths, content = load_func(file)
|
519
|
+
else:
|
520
|
+
wavelengths, content = np.loadtxt(
|
521
|
+
file, skiprows=skiprows, usecols=usecols, comments=comments, unpack=True
|
522
|
+
)
|
523
|
+
|
524
|
+
if isinstance(wavelengths, u.Quantity):
|
525
|
+
wavelengths = wavelengths.value
|
526
|
+
content = content.value
|
527
|
+
|
528
|
+
wavelength_grids.append(wavelengths)
|
529
|
+
contents.append(content)
|
530
|
+
|
531
|
+
sizes = [np.size(wl) for wl in wavelength_grids]
|
532
|
+
if method == "longest":
|
533
|
+
wavelength_grid = wavelength_grids[np.argmax(sizes)]
|
534
|
+
elif method == "shortest":
|
535
|
+
wavelength_grid = wavelength_grids[np.argmin(sizes)]
|
536
|
+
else:
|
537
|
+
wavelength_grid = wavelength_grids[
|
538
|
+
np.median(sizes).astype(int) == wavelength_grids
|
539
|
+
]
|
540
|
+
|
541
|
+
data = []
|
542
|
+
for wavelengths, content in zip(wavelength_grids, contents):
|
543
|
+
if np.array_equal(wavelengths, wavelength_grid):
|
544
|
+
data.append(content)
|
545
|
+
continue
|
546
|
+
|
547
|
+
data.append(
|
548
|
+
interp1d(wavelengths, content, kind=kind, fill_value=fill_value)(
|
549
|
+
wavelength_grid
|
550
|
+
)
|
551
|
+
)
|
552
|
+
|
553
|
+
return wavelength_grid.squeeze(), np.array(data).squeeze()
|
554
|
+
|
555
|
+
|
556
|
+
def get_t3_indices(x: np.ndarray, y: np.ndarray):
|
557
|
+
"""Gets the unique indices of t3 so it can be quickly calcualted as a 1D array."""
|
558
|
+
unique_coords = np.unique(np.column_stack((x.ravel(), y.ravel())), axis=0)
|
559
|
+
ucoord, vcoord = unique_coords[:, 0], unique_coords[:, 1]
|
560
|
+
index123 = np.vectorize(lambda x: np.where(ucoord == x)[0][0])(x)
|
561
|
+
return ucoord, vcoord, index123
|
562
|
+
|
563
|
+
|
564
|
+
def compute_vis(vis: np.ndarray) -> np.ndarray:
|
565
|
+
"""Computes the visibilities from the visibility function."""
|
566
|
+
return np.abs(vis).astype(OPTIONS.data.dtype.real)
|
567
|
+
|
568
|
+
|
569
|
+
def compute_t3(vis: np.ndarray, indices: List[float]) -> np.ndarray:
|
570
|
+
"""Computes the closure phase from the visibility function."""
|
571
|
+
if vis.size == 0:
|
572
|
+
return np.array([])
|
573
|
+
|
574
|
+
vis = vis[..., indices]
|
575
|
+
return np.angle(vis[:, 0] * vis[:, 1] * vis[:, 2].conj(), deg=True)
|
@@ -0,0 +1,68 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: ppdmod
|
3
|
+
Version: 2.0.0
|
4
|
+
Summary: A package for modelling and model-fitting protoplanetary disks
|
5
|
+
Author-email: Marten Scheuck <code@mbscheuck.com>
|
6
|
+
License: MIT License
|
7
|
+
|
8
|
+
Copyright (c) 2024 Marten B. Scheuck
|
9
|
+
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
12
|
+
in the Software without restriction, including without limitation the rights
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
15
|
+
furnished to do so, subject to the following conditions:
|
16
|
+
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
18
|
+
copies or substantial portions of the Software.
|
19
|
+
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
26
|
+
SOFTWARE.
|
27
|
+
|
28
|
+
Project-URL: repository, https://codeberg.org/MBSck/ppdmod.git
|
29
|
+
Classifier: Framework :: Pytest
|
30
|
+
Classifier: Framework :: Sphinx
|
31
|
+
Classifier: License :: OSI Approved :: MIT License
|
32
|
+
Classifier: Natural Language :: English
|
33
|
+
Classifier: Operating System :: OS Independent
|
34
|
+
Classifier: Programming Language :: Python :: 3
|
35
|
+
Classifier: Programming Language :: Python :: 3.9
|
36
|
+
Classifier: Topic :: Scientific/Engineering :: Astronomy
|
37
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
38
|
+
Requires-Python: <3.11,>=3.10
|
39
|
+
Description-Content-Type: text/markdown
|
40
|
+
License-File: LICENSE
|
41
|
+
Requires-Dist: astropy>=6.1.4
|
42
|
+
Requires-Dist: corner>=2.2.2
|
43
|
+
Requires-Dist: dynesty>=2.1.4
|
44
|
+
Requires-Dist: emcee>=3.1.6
|
45
|
+
Requires-Dist: h5py>=3.12.1
|
46
|
+
Requires-Dist: matplotlib>=3.9.2
|
47
|
+
Requires-Dist: numpy>=2.0.2
|
48
|
+
Requires-Dist: openpyxl>=3.1.5
|
49
|
+
Requires-Dist: pandas>=2.2.3
|
50
|
+
Requires-Dist: scipy>=1.14.1
|
51
|
+
Requires-Dist: toml>=0.10.2
|
52
|
+
Requires-Dist: tqdm>=4.67.1
|
53
|
+
Dynamic: license-file
|
54
|
+
|
55
|
+
# PPDMod
|
56
|
+
## Installation
|
57
|
+
Run the following to install:
|
58
|
+
```
|
59
|
+
python pip install ppdmod
|
60
|
+
```
|
61
|
+
## Usage
|
62
|
+
## Developing PPDMod
|
63
|
+
To install ppdmod, along with the tools you need to develop and run tests,
|
64
|
+
run the following in your virtualenv:
|
65
|
+
|
66
|
+
```
|
67
|
+
$ pip install -e .
|
68
|
+
```
|
@@ -0,0 +1,15 @@
|
|
1
|
+
ppdmod/__init__.py,sha256=_7OlQdbVkK4jad0CLdpI0grT-zEAb-qgFmH5mFzDXiA,22
|
2
|
+
ppdmod/base.py,sha256=nGhchWsftlLQXer5_31TudvprxPT3Pq7A1ET4Tis7qY,8014
|
3
|
+
ppdmod/components.py,sha256=u4T7N6dVFswzrSFnn4Q5F7fDjQEnJW_cnfveTWYJspM,18667
|
4
|
+
ppdmod/data.py,sha256=c0nny-mdIcuiF0L2h6kLayuZyrMahjNSd8xE6BUxWVY,18403
|
5
|
+
ppdmod/fitting.py,sha256=SOcyHVVMBHP3Oh3LdppX9lyX02hlQLZ_F-SSvnoWHSY,16867
|
6
|
+
ppdmod/options.py,sha256=cuZDJjXR75Kv8LwCOa2OF5NwcLvjmjA2v2zplMOoKBU,4127
|
7
|
+
ppdmod/parameter.py,sha256=7i0Y6MhXtb1uXc5U9Aeg8vVVJJnOqtYUXJAYQ3Nz4gg,4561
|
8
|
+
ppdmod/plot.py,sha256=rsWFHbOzBgESRfjL1So1sAWUpcAEKIHuxODP3XM3RDE,42649
|
9
|
+
ppdmod/utils.py,sha256=qBdYhezhY_8lJP9bRbFWEVwlkehzyBQRjIbIiZ-hATw,19531
|
10
|
+
ppdmod/config/standard_parameters.toml,sha256=IpVZ7h-J8fB0fEVq3BjMyXrhysu3ugxOtn1hHWpVYKk,4659
|
11
|
+
ppdmod-2.0.0.dist-info/licenses/LICENSE,sha256=irnxzjYhypw50bcLL0NPDQtca3F8YVx_-C9ZZWw8PvA,1074
|
12
|
+
ppdmod-2.0.0.dist-info/METADATA,sha256=Y6pTfuEN98XUY1VdDHrvs4PGmjnnPSfGtuW6cemi8_c,2625
|
13
|
+
ppdmod-2.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
14
|
+
ppdmod-2.0.0.dist-info/top_level.txt,sha256=eGG2QZ-yCSQHXGJnZNwMXJomy9SKSUI_0Jk62wkLpGg,7
|
15
|
+
ppdmod-2.0.0.dist-info/RECORD,,
|
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2024 Marten B. Scheuck
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
@@ -0,0 +1 @@
|
|
1
|
+
ppdmod
|