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/__init__.py
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
__version__ = "2.0.0"
|
ppdmod/base.py
ADDED
@@ -0,0 +1,225 @@
|
|
1
|
+
import copy
|
2
|
+
from typing import Dict, Tuple
|
3
|
+
|
4
|
+
import astropy.units as u
|
5
|
+
import numpy as np
|
6
|
+
|
7
|
+
from .options import OPTIONS
|
8
|
+
from .parameter import MultiParam, Parameter
|
9
|
+
from .utils import transform_coordinates, translate_image, translate_vis
|
10
|
+
|
11
|
+
|
12
|
+
class Component:
|
13
|
+
"""The base class for the component."""
|
14
|
+
|
15
|
+
name = "GenComp"
|
16
|
+
label = None
|
17
|
+
description = "This is base component are derived."
|
18
|
+
|
19
|
+
def __init__(self, **kwargs):
|
20
|
+
"""The class's constructor."""
|
21
|
+
self.flux_lnf = Parameter(name="flux_lnf", base="lnf")
|
22
|
+
self.t3_lnf = Parameter(name="t3_lnf", base="lnf")
|
23
|
+
self.vis_lnf = Parameter(name="vis_lnf", base="lnf")
|
24
|
+
|
25
|
+
def eval(self, **kwargs) -> None:
|
26
|
+
"""Sets the parameters (values) from the keyword arguments."""
|
27
|
+
for key, val in kwargs.items():
|
28
|
+
if hasattr(self, key):
|
29
|
+
if isinstance(val, (Parameter, MultiParam)):
|
30
|
+
setattr(self, key, val.copy())
|
31
|
+
else:
|
32
|
+
if isinstance(getattr(self, key), Parameter):
|
33
|
+
getattr(self, key).value = val
|
34
|
+
else:
|
35
|
+
setattr(self, key, val)
|
36
|
+
|
37
|
+
def copy(self) -> "Component":
|
38
|
+
"""Copies the component."""
|
39
|
+
return copy.deepcopy(self)
|
40
|
+
|
41
|
+
def get_params(
|
42
|
+
self, free: bool = False, shared: bool = False, time: bool = False
|
43
|
+
) -> Dict[str, Parameter]:
|
44
|
+
"""Gets all the parameters of a component.
|
45
|
+
|
46
|
+
Parameters
|
47
|
+
----------
|
48
|
+
component : Component
|
49
|
+
The component for which the parameters should be fetched.
|
50
|
+
free : bool, optional
|
51
|
+
If free parameters should be returned, by default False.
|
52
|
+
shared : bool, optional
|
53
|
+
If shared parameters should be returned, by default False.
|
54
|
+
time: bool, optional
|
55
|
+
If time-dependent parameters should be returned, by default False.
|
56
|
+
|
57
|
+
Returns
|
58
|
+
-------
|
59
|
+
params : dict of Parameter
|
60
|
+
"""
|
61
|
+
params = {}
|
62
|
+
for attribute in dir(self):
|
63
|
+
param = getattr(self, attribute)
|
64
|
+
if isinstance(param, Parameter):
|
65
|
+
if shared and free:
|
66
|
+
if not (param.shared and param.free):
|
67
|
+
continue
|
68
|
+
elif free:
|
69
|
+
if not param.free or param.shared:
|
70
|
+
continue
|
71
|
+
elif shared:
|
72
|
+
if not param.shared or param.free:
|
73
|
+
continue
|
74
|
+
|
75
|
+
params[attribute] = param
|
76
|
+
elif isinstance(param, MultiParam):
|
77
|
+
for p in param.params:
|
78
|
+
if shared and free:
|
79
|
+
if not (p.shared and p.free):
|
80
|
+
continue
|
81
|
+
elif free:
|
82
|
+
if not p.free or p.shared:
|
83
|
+
continue
|
84
|
+
elif shared:
|
85
|
+
if not p.shared or p.free:
|
86
|
+
continue
|
87
|
+
|
88
|
+
params[p.name] = p
|
89
|
+
return params
|
90
|
+
|
91
|
+
def flux_func(self, t: int, wl: u.um, **kwargs) -> np.ndarray:
|
92
|
+
"""Calculates the flux."""
|
93
|
+
return np.array([]).astype(OPTIONS.data.dtype.real)
|
94
|
+
|
95
|
+
def compute_flux(self, t: int, wl: u.um, **kwargs) -> np.ndarray:
|
96
|
+
"""Computes the fluxes."""
|
97
|
+
return np.abs(self.flux_func(t, wl, **kwargs)).astype(OPTIONS.data.dtype.real)
|
98
|
+
|
99
|
+
|
100
|
+
class FourierComponent(Component):
|
101
|
+
"""The base class for the Fourier (analytical) component.
|
102
|
+
|
103
|
+
Parameters
|
104
|
+
----------
|
105
|
+
xx : float
|
106
|
+
The x-coordinate of the component.
|
107
|
+
yy : float
|
108
|
+
The x-coordinate of the component.
|
109
|
+
dim : float
|
110
|
+
The dimension (px).
|
111
|
+
"""
|
112
|
+
|
113
|
+
name = "FourierComp"
|
114
|
+
description = "The component from which all analytical components are derived."
|
115
|
+
_asymmetric = False
|
116
|
+
|
117
|
+
def __init__(self, **kwargs):
|
118
|
+
"""The class's constructor."""
|
119
|
+
super().__init__(**kwargs)
|
120
|
+
self.fr = Parameter(base="fr")
|
121
|
+
self.r = Parameter(base="r")
|
122
|
+
self.phi = Parameter(base="phi")
|
123
|
+
self.pa = Parameter(base="pa")
|
124
|
+
self.cinc = Parameter(base="cinc")
|
125
|
+
self.dim = Parameter(base="dim")
|
126
|
+
self.modulation = Parameter(base="modulation")
|
127
|
+
|
128
|
+
self.eval(**kwargs)
|
129
|
+
|
130
|
+
for i in range(1, self.modulation.value + 1):
|
131
|
+
rho_str, theta_str = f"rho{i}", f"theta{i}"
|
132
|
+
rho = Parameter(name=rho_str, free=self.asymmetric, base="rho")
|
133
|
+
theta = Parameter(name=theta_str, free=self.asymmetric, base="theta")
|
134
|
+
setattr(self, rho_str, rho)
|
135
|
+
setattr(self, theta_str, theta)
|
136
|
+
|
137
|
+
def x(self, t, wl) -> u.Quantity:
|
138
|
+
r = self.r(t, wl)
|
139
|
+
if self.r(t, wl).unit == u.au:
|
140
|
+
r = (r.to(u.au) / self.dist(t, wl).to(u.pc)).value * 1e3 * u.mas
|
141
|
+
return r * np.sin(self.phi(t, wl).to(u.rad))
|
142
|
+
|
143
|
+
def y(self, t, wl) -> u.Quantity:
|
144
|
+
r = self.r(t, wl)
|
145
|
+
if self.r(t, wl).unit == u.au:
|
146
|
+
r = (r.to(u.au) / self.dist(t, wl).to(u.pc)).value * 1e3 * u.mas
|
147
|
+
return r * np.cos(self.phi(t, wl).to(u.rad))
|
148
|
+
|
149
|
+
@property
|
150
|
+
def asymmetric(self) -> bool:
|
151
|
+
"""Gets if the component is asymmetric."""
|
152
|
+
return self._asymmetric
|
153
|
+
|
154
|
+
@asymmetric.setter
|
155
|
+
def asymmetric(self, value: bool) -> None:
|
156
|
+
"""Sets the position angle and the parameters to free or false
|
157
|
+
if asymmetry is set."""
|
158
|
+
self._asymmetric = value
|
159
|
+
for i in range(1, self.modulation.value + 1):
|
160
|
+
getattr(self, f"rho{i}").free = value
|
161
|
+
getattr(self, f"theta{i}").free = value
|
162
|
+
|
163
|
+
def compute_internal_grid(self) -> Tuple[u.Quantity[u.au], u.Quantity[u.au]]:
|
164
|
+
"""Calculates the model grid.
|
165
|
+
|
166
|
+
Parameters
|
167
|
+
----------
|
168
|
+
|
169
|
+
Returns
|
170
|
+
-------
|
171
|
+
xx : astropy.units.au
|
172
|
+
The x-coordinate grid.
|
173
|
+
yy : astropy.units.au
|
174
|
+
The y-coordinate grid.
|
175
|
+
"""
|
176
|
+
return np.array([]) * u.au, np.array([]) * u.au
|
177
|
+
|
178
|
+
def vis_func(self, spf: 1 / u.rad, psi: u.rad, wl: u.um, **kwargs) -> np.ndarray:
|
179
|
+
"""Computes the correlated fluxes."""
|
180
|
+
return np.array([]).astype(OPTIONS.data.dtype.complex)
|
181
|
+
|
182
|
+
def compute_complex_vis(
|
183
|
+
self, ucoord: u.m, vcoord: u.m, t: int, wl: u.um, **kwargs
|
184
|
+
) -> np.ndarray:
|
185
|
+
"""Computes the correlated fluxes."""
|
186
|
+
ut, vt = transform_coordinates(
|
187
|
+
ucoord, vcoord, self.cinc(t, wl), self.pa(t, wl).to(u.rad)
|
188
|
+
)
|
189
|
+
wl = wl.reshape(-1, 1)
|
190
|
+
utb = (ut / wl.to(u.m)).value[..., np.newaxis] / u.rad
|
191
|
+
vtb = (vt / wl.to(u.m)).value[..., np.newaxis] / u.rad
|
192
|
+
spf, psi = np.hypot(utb, vtb), np.arctan2(utb, vtb)
|
193
|
+
|
194
|
+
shift = translate_vis(
|
195
|
+
utb.value,
|
196
|
+
vtb.value,
|
197
|
+
self.x(t, wl).to(u.rad).value,
|
198
|
+
self.y(t, wl).to(u.rad).value,
|
199
|
+
)
|
200
|
+
shift = shift.reshape(shift.shape[:-1]) if shift.shape[-1] == 1 else shift
|
201
|
+
vis = self.vis_func(spf, psi, t, wl, **kwargs)
|
202
|
+
vis = vis.reshape(vis.shape[:-1]) if vis.shape[-1] == 1 else vis
|
203
|
+
return (self.fr(t, wl).value * vis * shift).astype(OPTIONS.data.dtype.complex)
|
204
|
+
|
205
|
+
def image_func(
|
206
|
+
self, xx: u.mas, yy: u.mas, pixel_size: u.mas, t: int, wl: u.um
|
207
|
+
) -> np.ndarray:
|
208
|
+
"""Calculates the image."""
|
209
|
+
return np.array([]).astype(OPTIONS.data.dtype.real)
|
210
|
+
|
211
|
+
def compute_image(
|
212
|
+
self, dim: int, pixel_size: u.mas, t: int, wl: u.um
|
213
|
+
) -> np.ndarray:
|
214
|
+
"""Computes the image."""
|
215
|
+
wl = wl[np.newaxis, np.newaxis]
|
216
|
+
xx = np.linspace(-0.5, 0.5, dim, endpoint=False) * pixel_size * dim
|
217
|
+
xxt, yyt = transform_coordinates(
|
218
|
+
*np.meshgrid(xx, xx, sparse=True),
|
219
|
+
self.cinc(t, wl),
|
220
|
+
self.pa(t, wl).to(u.rad),
|
221
|
+
axis="x",
|
222
|
+
)
|
223
|
+
xxs, yys = translate_image(xxt, yyt, self.x(t, wl), self.y(t, wl))
|
224
|
+
image = self.image_func(xxs, yys, pixel_size, t, wl)
|
225
|
+
return (self.fr(t, wl) * image).value.astype(OPTIONS.data.dtype.real)
|