Skip to content

Commit 2ff5605

Browse files
committed
use class-structure for built-in features
1 parent 9337bc3 commit 2ff5605

6 files changed

Lines changed: 308 additions & 299 deletions

File tree

phoebe/features/common.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ class BaseFeature:
55
def __init__(self, **kwargs):
66
self.kwargs = kwargs
77

8+
@classmethod
9+
def create_feature_parameters(self, feature, **kwargs):
10+
raise NotImplementedError("create_feature_parameters must be implemented in the feature subclass")
11+
812
@classmethod
913
def parse_from_feature_ps(cls, b, feature_ps, param_list):
1014
_skip_filter_checks = {'check_default': False,
@@ -28,8 +32,3 @@ def parse_bundle(cls, b, feature_ps):
2832
@classmethod
2933
def from_bundle(cls, b, feature_ps):
3034
return cls(**cls.parse_bundle(b, feature_ps))
31-
32-
@classmethod
33-
def get_parameters(self, **kwargs):
34-
raise NotImplementedError("get_parameters must be implemented in the feature subclass")
35-

phoebe/features/component_features.py

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import astropy.units as u
33

44
import phoebe.parameters.feature as _parameters_feature
5+
from phoebe.parameters import FloatParameter, ParameterSet, constraint
56
from phoebe.features.common import BaseFeature
67

78
import logging
@@ -115,6 +116,46 @@ class Spot(ComponentFeature):
115116
remeshing_required = False
116117
proto_coords = True
117118

119+
@classmethod
120+
def create_feature_parameters(cls, feature, **kwargs):
121+
"""
122+
Create a <phoebe.parameters.ParameterSet> for a spot feature.
123+
124+
Generally, this will be used as an input to the kind argument in
125+
<phoebe.frontend.bundle.Bundle.add_feature>. If attaching through
126+
<phoebe.frontend.bundle.Bundle.add_feature>, all `**kwargs` will be
127+
passed on to set the values as described in the arguments below. Alternatively,
128+
see <phoebe.parameters.ParameterSet.set_value> to set/change the values
129+
after creating the Parameters.
130+
131+
Allowed to attach to:
132+
* components with kind: star
133+
* datasets: not allowed
134+
135+
Arguments
136+
----------
137+
* `colat` (float/quantity, optional): colatitude of the center of the spot
138+
wrt spin axis.
139+
* `long` (float/quantity, optional): longitude of the center of the spot wrt
140+
spin axis.
141+
* `radius` (float/quantity, optional): angular radius of the spot.
142+
* `relteff` (float/quantity, optional): temperature of the spot relative
143+
to the intrinsic temperature.
144+
145+
Returns
146+
--------
147+
* (<phoebe.parameters.ParameterSet>, list): ParameterSet of all newly created
148+
<phoebe.parameters.Parameter> objects and a list of all necessary
149+
constraints.
150+
"""
151+
params = []
152+
params += [FloatParameter(qualifier="colat", value=kwargs.get('colat', 0.0), default_unit=u.deg, description='Colatitude of the center of the spot wrt spin axis')]
153+
params += [FloatParameter(qualifier="long", value=kwargs.get('long', 0.0), default_unit=u.deg, description='Longitude of the center of the spot wrt spin axis')]
154+
params += [FloatParameter(qualifier='radius', value=kwargs.get('radius', 1.0), default_unit=u.deg, description='Angular radius of the spot')]
155+
params += [FloatParameter(qualifier='relteff', value=kwargs.get('relteff', 1.0), limits=(0.,None), default_unit=u.dimensionless_unscaled, description='Temperature of the spot relative to the intrinsic temperature')]
156+
157+
return ParameterSet(params), []
158+
118159
@classmethod
119160
def parse_bundle(cls, b, feature_ps):
120161
"""
@@ -188,9 +229,9 @@ def pointing_vector(self, s, time):
188229
exp = np.cross(eyp, ezp)
189230

190231
# now we can express the pointing vector in terms of the primed basis
191-
pv = (np.sin(self._colat)*np.cos(longitude)*exp +
192-
np.sin(self._colat)*np.sin(longitude)*eyp +
193-
np.cos(self._colat)*ezp)
232+
pv = (np.sin(colat)*np.cos(longitude)*exp +
233+
np.sin(colat)*np.sin(longitude)*eyp +
234+
np.cos(colat)*ezp)
194235

195236
# renormalize and return pointing vector
196237
return pv / np.linalg.norm(pv)

phoebe/features/dataset_features.py

Lines changed: 200 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@
55
logger = logging.getLogger("DATASET_FEATURES")
66
logger.addHandler(logging.NullHandler())
77

8-
from phoebe.parameters import FloatParameter, ParameterSet
9-
from phoebe.parameters import constraint
10-
import phoebe.parameters.feature as _parameters_feature
8+
from phoebe.parameters import FloatParameter, ChoiceParameter, StringParameter, ParameterSet
119
from phoebe.features.common import BaseFeature
1210

1311
__all__ = ['DatasetFeature']
@@ -31,21 +29,216 @@ def __repr__(self):
3129
def get_parameters(self, **kwargs):
3230
raise NotImplementedError("get_parameters must be implemented in the feature subclass")
3331

34-
def modify_data_for_estimators(self, b, feature_ps, data_ps, **data_arrays):
32+
def modify_data_for_estimators(self, b, data_ps, **data_arrays):
3533
"""
3634
Modify the data parameters for the estimators.
3735
This is called before the data is passed to the estimators.
3836
"""
3937
return {}
4038

41-
def modify_model(self, b, feature_ps, model_ps):
39+
def modify_model(self, b, model_ps):
4240
raise NotImplementedError("modify_model must be implemented in the feature subclass")
4341

42+
class GPSKLearn(DatasetFeature):
43+
allowed_dataset_kinds = ['lc']
4444

45-
class Rv_Offset(DatasetFeature):
45+
@classmethod
46+
def create_feature_parameters(self, feature, **kwargs):
47+
"""
48+
Create a <phoebe.parameters.ParameterSet> for a gp_sklearn feature.
49+
50+
Requires scikit-learn to be installed. See https://scikit-learn.org/stable/modules/gaussian_process.html/.
51+
If using gp_sklearn, consider citing:
52+
* https://jmlr.csail.mit.edu/papers/v12/pedregosa11a.html
53+
54+
See also:
55+
* <phoebe.frontend.bundle.Bundle.references>
56+
57+
Generally, this will be used as an input to the kind argument in
58+
<phoebe.frontend.bundle.Bundle.add_feature>. If attaching through
59+
<phoebe.frontend.bundle.Bundle.add_feature>, all `**kwargs` will be
60+
passed on to set the values as described in the arguments below. Alternatively,
61+
see <phoebe.parameters.ParameterSet.set_value> to set/change the values
62+
after creating the Parameters.
63+
64+
Allowed to attach to:
65+
* components: not allowed
66+
* datasets with kind: lc
67+
68+
If `compute_times` or `compute_phases` is used: the underlying model without
69+
gaussian_processes will be computed at the given times/phases but will then
70+
be interpolated into the times of the underlying dataset to include the
71+
contribution of gaussian processes and will be exposed at the dataset
72+
times (with a warning in the logger and in
73+
<phoebe.frontend.bundle.Bundle.run_checks_compute>). If the system is
74+
time-dependent without GPs
75+
(see <phoebe.parameters.HierarchyParameter.is_time_dependent>), then
76+
the underlying model will need to cover the entire dataset or an error
77+
will be raised by <phoebe.frontend.bundle.Bundle.run_checks_compute>.
78+
79+
80+
Arguments
81+
----------
82+
* `kernel` (string, optional, default='white'): Kernel for the gaussian
83+
process (see https://scikit-learn.org/stable/modules/gaussian_process.html#kernels-for-gaussian-processes)
84+
* `constant_value` (float, optional, default=1.0): only applicable if `kernel` is
85+
'constant'.
86+
* `noise_level` (float, optional, default=1.0): only applicable if `kernel` is 'white'.
87+
* `length_scale` (float, optional, default=1.0): only applicable if `kernel` is 'rbf', 'rational_quadratic',
88+
'exp_sine_squared' or 'matern'.
89+
* `nu` (float, optional, default=1.5): only applicable if `kernel` is 'matern'.
90+
* `alpha` (float, optional, default=1.0): only applicable if `kernel` is 'rational_quadratic'.
91+
* `periodicity` (float, optional, default=1.0): only applicable if `kernel` is 'exp_sine_sqaured'.
92+
* `sigma_0` (float, optional, default=1.0): only applicable if `kernel` is 'sigma_0'.
93+
* `alg_operation` (string, default='sum'): algebraic operation for the kernel with previously added ones.
94+
95+
Returns
96+
--------
97+
* (<phoebe.parameters.ParameterSet>, list): ParameterSet of all newly created
98+
<phoebe.parameters.Parameter> objects and a list of all necessary
99+
constraints.
100+
"""
101+
params = []
102+
params += [ChoiceParameter(qualifier='kernel', value=kwargs.get('kernel', 'white'), choices=['constant', 'white', 'rbf', 'matern', 'rational_quadratic', 'exp_sine_squared', 'dot_product'], description='Kernel for the gaussian process (see https://scikit-learn.org/stable/modules/gaussian_process.html)')]
103+
104+
# sklearn kernel parameters
105+
params += [FloatParameter(visible_if='kernel:constant', qualifier='constant_value', value=kwargs.get('constant_value', 1.0), default_unit=u.dimensionless_unscaled, description='Value of the constant kernel')]
106+
params += [FloatParameter(visible_if='kernel:white', qualifier='noise_level', value=kwargs.get('noise_level', 1.0), default_unit=u.dimensionless_unscaled, description='Noise level of the white kernel')]
107+
params += [FloatParameter(visible_if='kernel:rbf|rational_quadratic|exp_sine_squared|matern', qualifier='length_scale', value=kwargs.get('length_scale', 1.0), default_unit=u.dimensionless_unscaled, description='Length scale of the kernel')]
108+
params += [FloatParameter(visible_if='kernel:matern', qualifier='nu', value=kwargs.get('nu', 1.5), default_unit=u.dimensionless_unscaled, description='Smoothness factor of the Matern kernel')]
109+
params += [FloatParameter(visible_if='kernel:rational_quadratic', qualifier='alpha', value=kwargs.get('alpha', 1.0), default_unit=u.dimensionless_unscaled, description='Scale mixture parameter of the RationalQuadratic kernel')]
110+
params += [FloatParameter(visible_if='kernel:exp_sine_squared', qualifier='periodicity', value=kwargs.get('periodicity', 1.0), default_unit=u.dimensionless_unscaled, description='Periodicity parameter of the ExpSineSquared kernel')]
111+
params += [FloatParameter(visible_if='kernel:dot_product', qualifier='sigma_0', value=kwargs.get('sigma_0', 1.0), default_unit=u.dimensionless_unscaled, description='Constant factor of the DotProduct kernel')]
112+
113+
params += [StringParameter(visible_if='kernel:constant', qualifier='constant_value_bounds', value='fixed', default_unit=u.dimensionless_unscaled, description='Value bounds of the constant kernel')]
114+
params += [StringParameter(visible_if='kernel:white', qualifier='noise_level_bounds', value='fixed', default_unit=u.dimensionless_unscaled, description='Noise level bounds of the white kernel')]
115+
params += [StringParameter(visible_if='kernel:rbf|rational_quadratic|exp_sine_squared|matern', qualifier='length_scale_bounds', value='fixed', default_unit=u.dimensionless_unscaled, description='Length scale bounds of the kernel')]
116+
params += [StringParameter(visible_if='kernel:matern', qualifier='nu_bounds', value='fixed', default_unit=u.dimensionless_unscaled, description='Smoothness factor bounds of the Matern kernel')]
117+
params += [StringParameter(visible_if='kernel:rational_quadratic', qualifier='alpha_bounds', value='fixed', default_unit=u.dimensionless_unscaled, description='Scale mixture parameter bounds of the RationalQuadratic kernel')]
118+
params += [StringParameter(visible_if='kernel:exp_sine_squared', qualifier='periodicity_bounds', value='fixed', default_unit=u.dimensionless_unscaled, description='Periodicity parameter bounds of the ExpSineSquared kernel')]
119+
params += [StringParameter(visible_if='kernel:dot_product', qualifier='sigma_0_bounds', value='fixed', default_unit=u.dimensionless_unscaled, description='Constant factor bounds of the DotProduct kernel')]
120+
121+
# additional parameters for GPs
122+
params += [ChoiceParameter(qualifier='alg_operation', value='sum', choices=['sum', 'product'], default_unit=u.dimensionless_unscaled, description='Algebraic operation of this kernel with previous ones. Can be one of [sum, product]')]
123+
124+
return ParameterSet(params), []
125+
126+
def modify_model(self, b, model_ps):
127+
# GPS are handled separately and all simultaneously
128+
return
129+
130+
131+
class GPCelerite2(DatasetFeature):
132+
@classmethod
133+
def create_feature_parameters(self, feature, **kwargs):
134+
"""
135+
Create a <phoebe.parameters.ParameterSet> for a gp_celerite2 feature.
136+
137+
Requires celerite2 to be installed. See https://celerite2.readthedocs.io/en/stable/.
138+
If using gaussian processes, consider citing:
139+
* https://ui.adsabs.harvard.edu/abs/2017AJ....154..220F
140+
141+
See also:
142+
* <phoebe.frontend.bundle.Bundle.references>
143+
144+
Generally, this will be used as an input to the kind argument in
145+
<phoebe.frontend.bundle.Bundle.add_feature>. If attaching through
146+
<phoebe.frontend.bundle.Bundle.add_feature>, all `**kwargs` will be
147+
passed on to set the values as described in the arguments below. Alternatively,
148+
see <phoebe.parameters.ParameterSet.set_value> to set/change the values
149+
after creating the Parameters.
150+
151+
Allowed to attach to:
152+
* components: not allowed
153+
* datasets with kind: lc
154+
155+
If `compute_times` or `compute_phases` is used: the underlying model without
156+
gaussian_processes will be computed at the given times/phases but will then
157+
be interpolated into the times of the underlying dataset to include the
158+
contribution of gaussian processes and will be exposed at the dataset
159+
times (with a warning in the logger and in
160+
<phoebe.frontend.bundle.Bundle.run_checks_compute>). If the system is
161+
time-dependent without GPs
162+
(see <phoebe.parameters.HierarchyParameter.is_time_dependent>), then
163+
the underlying model will need to cover the entire dataset or an error
164+
will be raised by <phoebe.frontend.bundle.Bundle.run_checks_compute>.
165+
166+
167+
Arguments
168+
----------
169+
* `kernel` (string, optional, default='sho'): Kernel for the gaussian
170+
process (see https://celerite2.readthedocs.io/en/stable/api/python/#celerite2.terms)
171+
* `rho` (float, optional, default=1.0): only applicable if `kernel` is
172+
'sho' or 'matern32'.
173+
* `tau` (float, optional, default=1.0): only applicable if `kernel` is
174+
'sho'.
175+
* `sigma` (float, optional, default=1.0)
176+
* `period` (float, optional, default=1.0): only applicable if `kernel` is
177+
'rotation'.
178+
* `Q0` (float, optional, default=1.0): only applicable if `kernel` is
179+
'rotation'.
180+
* `dQ` (float, optional, default=1.0): only applicable if `kernel` is
181+
'rotation'.
182+
* `f` (float, optional, default=1.0): only applicable if `kernel` is
183+
'rotation'.
184+
* `eps` (float, optional, default=1e-5): only applicable if `kernel` is
185+
'sho' or 'matern32'.
186+
* `alg_operation` (string, default='sum'): algebraic operation for the kernel with previously added ones.
187+
188+
Returns
189+
--------
190+
* (<phoebe.parameters.ParameterSet>, list): ParameterSet of all newly created
191+
<phoebe.parameters.Parameter> objects and a list of all necessary
192+
constraints.
193+
"""
194+
195+
params = []
196+
params += [ChoiceParameter(qualifier='kernel', value=kwargs.get('kernel', 'sho'), choices=['sho', 'rotation', 'matern32'], description='Kernel for the gaussian process')]
197+
198+
# celerite2 kernel parameters
199+
params += [FloatParameter(visible_if='kernel:sho|matern32', qualifier='rho', value=kwargs.get('rho', 1.0), default_unit = u.dimensionless_unscaled, description='Periodicity of the SHO kernel.')]
200+
params += [FloatParameter(visible_if='kernel:sho', qualifier='tau', value=kwargs.get('tau', 1.0), default_unit = u.dimensionless_unscaled, description='Damping timescale of the SHO kernel.')]
201+
params += [FloatParameter(visible_if='kernel:sho|rotation|matern32', qualifier='sigma', value=kwargs.get('sigma', 1.0), default_unit = u.dimensionless_unscaled, description='Standard deviation of the process.')]
202+
params += [FloatParameter(visible_if='kernel:rotation', qualifier='period', value=kwargs.get('period', 1.0), default_unit = u.dimensionless_unscaled, description='The primary period of variability of the rotation kernel.')]
203+
params += [FloatParameter(visible_if='kernel:rotation', qualifier='Q0', value=kwargs.get('Q0', 1.0), default_unit = u.dimensionless_unscaled, description='The quality factor for the secondary oscillation.')]
204+
params += [FloatParameter(visible_if='kernel:rotation', qualifier='dQ', value=kwargs.get('dQ', 1.0), default_unit = u.dimensionless_unscaled, description='The difference between the quality factors of the first and the second modes.')]
205+
params += [FloatParameter(visible_if='kernel:rotation', qualifier='f', value=kwargs.get('f', 1.0), default_unit = u.dimensionless_unscaled, description='The fractional amplitude of the secondary mode compared to the primary.')]
206+
params += [FloatParameter(visible_if='kernel:sho|matern32', qualifier='eps', value=kwargs.get('eps', 1e-5), default_unit = u.dimensionless_unscaled, description='A regularization parameter used for numerical stability.')]
207+
208+
# additional parameters for GPs
209+
params += [ChoiceParameter(qualifier='alg_operation', value='sum', choices=['sum', 'product'], default_unit=u.dimensionless_unscaled, description='Algebraic operation of this kernel with previous ones. Can be one of [sum, product]')]
210+
211+
return ParameterSet(params), []
212+
213+
def modify_model(self, b, model_ps):
214+
# GPS are handled separately and all simultaneously
215+
return
216+
217+
218+
class RVOffset(DatasetFeature):
46219
allowed_component_kinds = ['star']
47220
allowed_dataset_kinds = ['rv']
48221

222+
@classmethod
223+
def create_feature_parameters(self, feature, **kwargs):
224+
"""
225+
Create a <phoebe.parameters.ParameterSet> for an rvoffset feature.
226+
227+
Generally, this will be used as an input to the kind argument in
228+
<phoebe.frontend.bundle.Bundle.add_feature>. If attaching through
229+
<phoebe.frontend.bundle.Bundle.add_feature>, all `**kwargs` will be
230+
passed on to set the values as described in the arguments below. Alternatively,
231+
see <phoebe.parameters.ParameterSet.set_value> to set/change the values
232+
after creating the Parameters.
233+
234+
Allowed to attach to:
235+
* datasets: rv
236+
"""
237+
params = []
238+
params += [FloatParameter(qualifier='rv_offset', copy_for={'kind': ['star'], 'component': '*'}, component='_default', value=kwargs.get('rv_offset', 0.0), default_unit=u.km/u.s, description='Per-component offset to add to synthetic RVs (i.e. for hot stars)')]
239+
240+
return ParameterSet(params), []
241+
49242
@classmethod
50243
def parse_bundle(cls, b, feature_ps):
51244
"""
@@ -54,7 +247,6 @@ def parse_bundle(cls, b, feature_ps):
54247
rv_offsets = feature_ps.filter(qualifier='rv_offset', **_skip_filter_checks)
55248
return {param.component: param.get_quantity(**_skip_filter_checks) for param in rv_offsets.to_list()}
56249

57-
def modify_model(self, b, feature_ps, model_ps):
250+
def modify_model(self, b, model_ps):
58251
for rv_param in model_ps.filter(qualifier='rvs', kind=['rv', 'mesh'], **_skip_filter_checks).to_list():
59252
rv_param.set_value(rv_param.get_value() + self.kwargs.get(rv_param.component).to_value(rv_param.default_unit), ignore_readonly=True, **_skip_filter_checks)
60-

0 commit comments

Comments
 (0)