Skip to content

Commit fe26398

Browse files
Fixup.
1 parent de1e859 commit fe26398

File tree

10 files changed

+772
-499
lines changed

10 files changed

+772
-499
lines changed

src/kbmod/mocking/callbacks.py

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,42 @@
1+
import random
2+
3+
from astropy.time import Time
4+
import astropy.units as u
5+
16
__all__ = [
27
"IncrementObstime",
38
"ObstimeIterator",
49
]
510

611

712
class IncrementObstime:
13+
default_unit = "day"
814
def __init__(self, start, dt):
9-
self.start = start
15+
self.start = Time(start)
16+
if not isinstance(dt, u.Quantity):
17+
dt = dt * getattr(u, self.default_unit)
1018
self.dt = dt
1119

12-
def __call__(self, mut_val):
20+
def __call__(self, header_val):
1321
curr = self.start
1422
self.start += self.dt
15-
return curr
23+
return curr.fits
1624

1725

1826
class ObstimeIterator:
19-
def __init__(self, obstimes):
20-
self.obstimes = obstimes
27+
def __init__(self, obstimes, **kwargs):
28+
self.obstimes = Time(obstimes, **kwargs)
2129
self.generator = (t for t in obstimes)
2230

23-
def __call__(self, mut_val):
24-
return next(self.generator)
31+
def __call__(self, header_val):
32+
return Time(next(self.generator)).fits
33+
34+
35+
class DitherValue:
36+
def __init__(self, value, dither_range):
37+
self.value = value
38+
self.dither_range = dither_range
39+
40+
def __call__(self, header_val):
41+
return self.value + random.uniform(self.dither_range)
42+

src/kbmod/mocking/catalogs.py

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
import numpy as np
44
from astropy.table import QTable
5+
from astropy.coordinates import SkyCoord
6+
57
from .config import Config
68

79

810
__all__ = [
9-
"gen_catalog",
11+
"gen_random_catalog",
1012
"CatalogFactory",
1113
"SimpleCatalog",
1214
"SourceCatalogConfig",
@@ -16,7 +18,7 @@
1618
]
1719

1820

19-
def gen_catalog(n, param_ranges, seed=None):
21+
def gen_random_catalog(n, param_ranges, seed=None):
2022
cat = QTable()
2123
rng = np.random.default_rng(seed)
2224

@@ -30,9 +32,6 @@ def gen_catalog(n, param_ranges, seed=None):
3032

3133
# conversion assumes a gaussian
3234
if "flux" in param_ranges and "amplitude" not in param_ranges:
33-
xstd = cat["x_stddev"] if "x_stddev" in cat.colnames else 1.0
34-
ystd = cat["y_stddev"] if "y_stddev" in cat.colnames else 1.0
35-
3635
cat["amplitude"] = cat["flux"] / (2.0 * np.pi * xstd * ystd)
3736

3837
return cat
@@ -45,7 +44,8 @@ def mock(self, *args, **kwargs):
4544

4645

4746
class SimpleCatalogConfig(Config):
48-
mode = "static"
47+
mode = "static" # folding
48+
kind = "pixel" # world
4949
return_copy = False
5050
seed = None
5151
n = 100
@@ -56,28 +56,32 @@ class SimpleCatalog(CatalogFactory):
5656
default_config = SimpleCatalogConfig
5757

5858
def __init__(self, config, table, **kwargs):
59-
config = self.default_config(**kwargs)
60-
self.config = config
59+
self.config = self.default_config(config=config, **kwargs)
6160
self.table = table
6261
self.current = 0
6362

6463
@classmethod
6564
def from_config(cls, config, **kwargs):
6665
config = cls.default_config(config=config, **kwargs)
67-
table = gen_catalog(config.n, config.param_ranges, config.seed)
66+
table = gen_random_catalog(config["n"], config["param_ranges"], config["seed"])
6867
return cls(config, table)
6968

7069
@classmethod
7170
def from_defaults(cls, param_ranges=None, **kwargs):
7271
config = cls.default_config(**kwargs)
7372
if param_ranges is not None:
74-
config.param_ranges.update(param_ranges)
73+
config["param_ranges"].update(param_ranges)
7574
return cls.from_config(config)
7675

7776
@classmethod
7877
def from_table(cls, table, **kwargs):
78+
if "x_stddev" not in table.columns:
79+
table["x_stddev"] = table["stddev"]
80+
if "y_stddev" not in table.columns:
81+
table["y_stddev"] = table["stddev"]
82+
7983
config = cls.default_config(**kwargs)
80-
config.n = len(table)
84+
config["n"] = len(table)
8185
params = {}
8286
for col in table.keys():
8387
params[col] = (table[col].min(), table[col].max())
@@ -86,7 +90,7 @@ def from_table(cls, table, **kwargs):
8690

8791
def mock(self):
8892
self.current += 1
89-
if self.config.return_copy:
93+
if self.config["return_copy:"]:
9094
return self.table.copy()
9195
return self.table
9296

@@ -127,7 +131,7 @@ def __init__(self, config, table, **kwargs):
127131
kwargs["return_copy"] = True
128132
super().__init__(config, table, **kwargs)
129133
self._realization = self.table.copy()
130-
self.mode = self.config.mode
134+
self.mode = self.config["mode"]
131135

132136
@property
133137
def mode(self):
@@ -137,8 +141,10 @@ def mode(self):
137141
def mode(self, val):
138142
if val == "folding":
139143
self._gen_realization = self.fold
140-
elif val == "progressive":
141-
self._gen_realization = self.next
144+
elif val == "progressive" and self.config["kind"] == "pixel":
145+
self._gen_realization = self.next_pixel
146+
elif val == "progressive" and self.config["kind"] == "world":
147+
self._gen_realization = self.next_world
142148
elif val == "static":
143149
self._gen_realization = self.static
144150
else:
@@ -155,25 +161,38 @@ def reset(self):
155161
def static(self, **kwargs):
156162
return self.table.copy()
157163

158-
def next(self, dt, **kwargs):
159-
self._realization["x_mean"] = self.table["x_mean"] + self.current * self._realization["vx"] * dt
160-
self._realization["y_mean"] = self.table["y_mean"] + self.current * self._realization["vy"] * dt
164+
def _next(self, dt, keys):
165+
a, va, b, vb = keys
166+
self._realization[a] = self.table[a] + self.current * self.table[va] * dt
167+
self._realization[b] = self.table[b] + self.current * self.table[vb] * dt
161168
self.current += 1
162169
return self._realization.copy()
163170

171+
def next_world(self, dt):
172+
return self._next(dt, ["ra_mean", "v_ra", "dec_mean", "v_dec"])
173+
174+
def next_pixel(self, dt):
175+
return self._next(dt, ["x_mean", "vx", "y_mean", "vy"])
176+
164177
def fold(self, t, **kwargs):
165178
self._realization = self.table[self.table["obstime"] == t]
166179
self.current += 1
167180
return self._realization.copy()
168181

169-
def mock(self, n=1, **kwargs):
182+
def mock(self, n=1, dt=None, t=None, wcs=None):
170183
data = []
171184

172185
if self.mode == "folding":
173-
for t in kwargs["t"]:
174-
data.append(self.fold(t=t))
186+
for i, ts in enumerate(t):
187+
data.append(self.fold(t=ts))
175188
else:
176189
for i in range(n):
177-
data.append(self._gen_realization(**kwargs))
190+
data.append(self._gen_realization(dt))
191+
192+
if self.config["kind"] == "world":
193+
for cat, w in zip(data, wcs):
194+
x, y = w.world_to_pixel(SkyCoord(ra=cat["ra_mean"], dec=cat["dec_mean"], unit="deg"))
195+
cat["x_mean"] = x
196+
cat["y_mean"] = y
178197

179198
return data

src/kbmod/mocking/config.py

Lines changed: 22 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,10 @@ class ConfigurationError(Exception):
88

99

1010
class Config:
11-
"""Base configuration class.
11+
"""Base class for Standardizer configuration.
1212
13-
Config classes that inherit from this class define configuration as their
14-
class attributes. Particular attributes can be overriden on an per-instance
15-
basis by providing a config overrides at initialization time.
16-
17-
Configs inheriting from this config support basic dictionary operations.
13+
Not all standardizers will (can) use the same parameters so refer to their
14+
respective documentation for a more complete list.
1815
1916
Parameters
2017
----------
@@ -24,66 +21,33 @@ class attributes. Particular attributes can be overriden on an per-instance
2421
Keyword arguments, assigned as configuration key-values.
2522
"""
2623

27-
def __init__(self, config=None, method="default", **kwargs):
24+
def __init__(self, config=None, **kwargs):
2825
# This is a bit hacky, but it makes life a lot easier because it
2926
# enables automatic loading of the default configuration and separation
3027
# of default config from instance bound config
3128
keys = list(set(dir(self.__class__)) - set(dir(Config)))
3229

3330
# First fill out all the defaults by copying cls attrs
34-
self._conf = {k: copy.copy(getattr(self, k)) for k in keys}
31+
self._conf = {k: getattr(self, k) for k in keys}
3532

3633
# Then override with any user-specified values
37-
self.update(config=config, method=method, **kwargs)
38-
39-
@classmethod
40-
def from_configs(cls, *args):
41-
config = cls()
42-
for conf in args:
43-
config.update(config=conf, method="extend")
44-
return config
34+
if config is not None:
35+
self._conf.update(config)
36+
self._conf.update(kwargs)
4537

38+
# now just shortcut the most common dict operations
4639
def __getitem__(self, key):
4740
return self._conf[key]
4841

49-
# now just shortcut the most common dict operations
50-
def __getattribute__(self, key):
51-
hasconf = "_conf" in object.__getattribute__(self, "__dict__")
52-
if hasconf:
53-
conf = object.__getattribute__(self, "_conf")
54-
if key in conf:
55-
return conf[key]
56-
return object.__getattribute__(self, key)
57-
5842
def __setitem__(self, key, value):
5943
self._conf[key] = value
6044

61-
def __repr__(self):
62-
res = f"{self.__class__.__name__}("
63-
for k, v in self.items():
64-
res += f"{k}: {v}, "
65-
return res[:-2] + ")"
66-
6745
def __str__(self):
6846
res = f"{self.__class__.__name__}("
6947
for k, v in self.items():
7048
res += f"{k}: {v}, "
7149
return res[:-2] + ")"
7250

73-
def _repr_html_(self):
74-
repr = f"""
75-
<table style='tr:nth-child(even){{background-color: #dddddd;}};'>
76-
<caption>{self.__class__.__name__}</caption>
77-
<tr>
78-
<th>Key</th>
79-
<th>Value</th>
80-
</tr>
81-
"""
82-
for k, v in self.items():
83-
repr += f"<tr><td>{k}</td><td>{v}\n"
84-
repr += "</table>"
85-
return repr
86-
8751
def __len__(self):
8852
return len(self._conf)
8953

@@ -107,7 +71,7 @@ def __or__(self, other):
10771
elif isinstance(other, dict):
10872
return self.__class__(config=self._conf | other)
10973
else:
110-
raise TypeError("unsupported operand type(s) for |: {type(self)}and {type(other)}")
74+
raise TypeError("unsupported operand type(s) for |: {type(self)} " "and {type(other)}")
11175

11276
def keys(self):
11377
"""A set-like object providing a view on config's keys."""
@@ -121,68 +85,22 @@ def items(self):
12185
"""A set-like object providing a view on config's items."""
12286
return self._conf.items()
12387

124-
def copy(self):
125-
return self.__class__(config=self._conf.copy())
126-
127-
def update(self, config=None, method="default", **kwargs):
128-
"""Update this config from dict/other config/iterable and
129-
apply any explicit keyword overrides.
130-
131-
A dict-like update. If ``conf`` is given and has a ``.keys()``
132-
method, performs:
133-
134-
for k in conf: this[k] = conf[k]
135-
136-
If ``conf`` is given but lacks a ``.keys()`` method, performs:
88+
def update(self, conf=None, **kwargs):
89+
"""Update this config from dict/other config/iterable.
13790
138-
for k, v in conf: this[k] = v
91+
A dict-like update. If ``conf`` is present and has a ``.keys()``
92+
method, then does: ``for k in conf: this[k] = conf[k]``. If ``conf``
93+
is present but lacks a ``.keys()`` method, then does:
94+
``for k, v in conf: this[k] = v``.
13995
140-
In both cases, explicit overrides are applied at the end:
141-
142-
for k in kwargs: this[k] = kwargs[k]
96+
In either case, this is followed by:
97+
``for k in kwargs: this[k] = kwargs[k]``
14398
"""
144-
# Python < 3.9 does not support set operations for dicts
145-
# [fixme]: Update this to: other = conf | kwargs
146-
# and remove current implementation when 3.9 gets too old. Order of
147-
# conf and kwargs matter to correctly apply explicit overrides
148-
149-
# Check if both conf and kwargs are given, just conf or just
150-
# kwargs. If none are given do nothing to comply with default
151-
# dict behavior
152-
if config is not None and kwargs:
153-
other = {**config, **kwargs}
154-
elif config is not None:
155-
other = config
156-
elif kwargs is not None:
157-
other = kwargs
158-
else:
159-
return
160-
161-
# then, see if we the given config and overrides are a subset of this
162-
# config or it's superset. Depending on the selected method then raise
163-
# errors, ignore or extend the current config if the given config is a
164-
# superset (or disjoint) from the current one.
165-
subset = {k: v for k, v in other.items() if k in self._conf}
166-
superset = {k: v for k, v in other.items() if k not in subset}
167-
168-
if method.lower() == "default":
169-
if superset:
170-
raise ConfigurationError(
171-
"Tried setting the following fields, not a part of "
172-
f"this configuration options: {superset}"
173-
)
174-
conf = other # == subset
175-
elif method.lower() == "subset":
176-
conf = subset
177-
elif method.lower() == "extend":
178-
conf = other
179-
else:
180-
raise ValueError(
181-
"Method expected to be one of 'default', " f"'subset' or 'extend'. Got {method} instead."
182-
)
183-
184-
self._conf.update(conf)
99+
if conf is not None:
100+
self._conf.update(conf)
101+
self._conf.update(kwargs)
185102

186103
def toDict(self):
187104
"""Return this config as a dict."""
188105
return self._conf
106+

0 commit comments

Comments
 (0)