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/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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -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