microlens-submit 0.12.2__py3-none-any.whl → 0.16.1__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.
- microlens_submit/__init__.py +7 -157
- microlens_submit/cli/__init__.py +5 -0
- microlens_submit/cli/__main__.py +6 -0
- microlens_submit/cli/commands/__init__.py +1 -0
- microlens_submit/cli/commands/dossier.py +139 -0
- microlens_submit/cli/commands/export.py +177 -0
- microlens_submit/cli/commands/init.py +172 -0
- microlens_submit/cli/commands/solutions.py +722 -0
- microlens_submit/cli/commands/validation.py +241 -0
- microlens_submit/cli/main.py +120 -0
- microlens_submit/dossier/__init__.py +51 -0
- microlens_submit/dossier/dashboard.py +503 -0
- microlens_submit/dossier/event_page.py +370 -0
- microlens_submit/dossier/full_report.py +330 -0
- microlens_submit/dossier/solution_page.py +534 -0
- microlens_submit/dossier/utils.py +111 -0
- microlens_submit/error_messages.py +283 -0
- microlens_submit/models/__init__.py +28 -0
- microlens_submit/models/event.py +406 -0
- microlens_submit/models/solution.py +569 -0
- microlens_submit/models/submission.py +569 -0
- microlens_submit/tier_validation.py +208 -0
- microlens_submit/utils.py +373 -0
- microlens_submit/validate_parameters.py +478 -180
- {microlens_submit-0.12.2.dist-info → microlens_submit-0.16.1.dist-info}/METADATA +52 -14
- microlens_submit-0.16.1.dist-info/RECORD +32 -0
- microlens_submit/api.py +0 -1257
- microlens_submit/cli.py +0 -1803
- microlens_submit/dossier.py +0 -1443
- microlens_submit-0.12.2.dist-info/RECORD +0 -13
- {microlens_submit-0.12.2.dist-info → microlens_submit-0.16.1.dist-info}/WHEEL +0 -0
- {microlens_submit-0.12.2.dist-info → microlens_submit-0.16.1.dist-info}/entry_points.txt +0 -0
- {microlens_submit-0.12.2.dist-info → microlens_submit-0.16.1.dist-info}/licenses/LICENSE +0 -0
- {microlens_submit-0.12.2.dist-info → microlens_submit-0.16.1.dist-info}/top_level.txt +0 -0
|
@@ -31,7 +31,7 @@ The module defines:
|
|
|
31
31
|
|
|
32
32
|
Example:
|
|
33
33
|
>>> from microlens_submit.validate_parameters import check_solution_completeness
|
|
34
|
-
>>>
|
|
34
|
+
>>>
|
|
35
35
|
>>> # Validate a simple 1S1L solution
|
|
36
36
|
>>> parameters = {"t0": 2459123.5, "u0": 0.1, "tE": 20.0}
|
|
37
37
|
>>> messages = check_solution_completeness("1S1L", parameters)
|
|
@@ -39,7 +39,7 @@ Example:
|
|
|
39
39
|
... print("Solution is complete!")
|
|
40
40
|
>>> else:
|
|
41
41
|
... print("Issues found:", messages)
|
|
42
|
-
|
|
42
|
+
|
|
43
43
|
>>> # Validate a binary lens with parallax
|
|
44
44
|
>>> parameters = {
|
|
45
45
|
... "t0": 2459123.5, "u0": 0.1, "tE": 20.0,
|
|
@@ -57,8 +57,7 @@ Note:
|
|
|
57
57
|
custom parameters and future model types.
|
|
58
58
|
"""
|
|
59
59
|
|
|
60
|
-
from typing import Dict, List,
|
|
61
|
-
|
|
60
|
+
from typing import Any, Dict, List, Optional
|
|
62
61
|
|
|
63
62
|
MODEL_DEFINITIONS = {
|
|
64
63
|
# Single Source, Single Lens (PSPL)
|
|
@@ -74,19 +73,36 @@ MODEL_DEFINITIONS = {
|
|
|
74
73
|
# Binary Source, Single Lens
|
|
75
74
|
"2S1L": {
|
|
76
75
|
"description": "Binary Source, Single Point Lens",
|
|
77
|
-
"required_params_core": ["t0", "u0", "tE"],
|
|
76
|
+
"required_params_core": ["t0", "u0", "tE"], # Core lens params
|
|
77
|
+
},
|
|
78
|
+
# Other/Unknown model type (allows any parameters)
|
|
79
|
+
"other": {
|
|
80
|
+
"description": "Other or unknown model type",
|
|
81
|
+
"required_params_core": [], # No required parameters for unknown models
|
|
78
82
|
},
|
|
79
83
|
# Add other model types as needed:
|
|
80
|
-
# "2S2L": {
|
|
81
|
-
#
|
|
82
|
-
#
|
|
84
|
+
# "2S2L": {
|
|
85
|
+
# "description": "Binary Source, Binary Point Lens",
|
|
86
|
+
# "required_params_core": ["t0", "u0", "tE", "s", "q", "alpha"]
|
|
87
|
+
# },
|
|
88
|
+
# "1S3L": {
|
|
89
|
+
# "description": "Point Source, Triple Point Lens",
|
|
90
|
+
# "required_params_core": ["t0", "u0", "tE", "s1", "q1", "alpha1", "s2", "q2", "alpha2"]
|
|
91
|
+
# },
|
|
92
|
+
# "2S3L": {
|
|
93
|
+
# "description": "Binary Source, Triple Point Lens",
|
|
94
|
+
# "required_params_core": ["t0", "u0", "tE", "s1", "q1", "alpha1", "s2", "q2", "alpha2"]
|
|
95
|
+
# },
|
|
83
96
|
}
|
|
84
97
|
|
|
85
98
|
HIGHER_ORDER_EFFECT_DEFINITIONS = {
|
|
86
99
|
"parallax": {
|
|
87
100
|
"description": "Microlens parallax effect",
|
|
88
|
-
"requires_t_ref": True,
|
|
89
|
-
"required_higher_order_params": [
|
|
101
|
+
"requires_t_ref": True, # A flag to check for the 't_ref' attribute
|
|
102
|
+
"required_higher_order_params": [
|
|
103
|
+
"piEN",
|
|
104
|
+
"piEE",
|
|
105
|
+
], # These are often part of the main parameters if fitted
|
|
90
106
|
},
|
|
91
107
|
"finite-source": {
|
|
92
108
|
"description": "Finite source size effect",
|
|
@@ -97,7 +113,7 @@ HIGHER_ORDER_EFFECT_DEFINITIONS = {
|
|
|
97
113
|
"description": "Orbital motion of the lens components",
|
|
98
114
|
"requires_t_ref": True,
|
|
99
115
|
"required_higher_order_params": ["dsdt", "dadt"],
|
|
100
|
-
"optional_higher_order_params": ["dzdt"],
|
|
116
|
+
"optional_higher_order_params": ["dzdt"], # Relative radial rate of change of lenses (if needed)
|
|
101
117
|
},
|
|
102
118
|
"xallarap": {
|
|
103
119
|
"description": "Source orbital motion (xallarap)",
|
|
@@ -106,104 +122,246 @@ HIGHER_ORDER_EFFECT_DEFINITIONS = {
|
|
|
106
122
|
},
|
|
107
123
|
"gaussian-process": {
|
|
108
124
|
"description": "Gaussian process model for time-correlated noise",
|
|
109
|
-
"requires_t_ref": False,
|
|
110
|
-
"required_higher_order_params": [],
|
|
111
|
-
"optional_higher_order_params": [
|
|
125
|
+
"requires_t_ref": False, # GP parameters are usually not time-referenced in this way
|
|
126
|
+
"required_higher_order_params": [], # Placeholder for common GP hyperparameters
|
|
127
|
+
"optional_higher_order_params": [
|
|
128
|
+
"ln_K",
|
|
129
|
+
"ln_lambda",
|
|
130
|
+
"ln_period",
|
|
131
|
+
"ln_gamma",
|
|
132
|
+
], # Common GP params, or specific names like "amplitude", "timescale", "periodicity" etc.
|
|
112
133
|
},
|
|
113
134
|
"stellar-rotation": {
|
|
114
135
|
"description": "Effect of stellar rotation on the light curve (e.g., spots)",
|
|
115
|
-
"requires_t_ref": False,
|
|
116
|
-
"required_higher_order_params": [],
|
|
117
|
-
|
|
136
|
+
"requires_t_ref": False, # Usually not time-referenced directly in this context
|
|
137
|
+
"required_higher_order_params": [], # Specific parameters
|
|
138
|
+
# (e.g., rotation period, inclination)
|
|
139
|
+
# to be added here
|
|
140
|
+
"optional_higher_order_params": [
|
|
141
|
+
"v_rot_sin_i",
|
|
142
|
+
"epsilon",
|
|
143
|
+
], # Guessing common params: rotational velocity times sin(inclination),
|
|
144
|
+
# spot coverage
|
|
118
145
|
},
|
|
119
146
|
"fitted-limb-darkening": {
|
|
120
147
|
"description": "Limb darkening coefficients fitted as parameters",
|
|
121
148
|
"requires_t_ref": False,
|
|
122
|
-
"required_higher_order_params": [],
|
|
123
|
-
|
|
149
|
+
"required_higher_order_params": [], # Parameters are usually u1, u2, etc.
|
|
150
|
+
# (linear, quadratic)
|
|
151
|
+
"optional_higher_order_params": [
|
|
152
|
+
"u1",
|
|
153
|
+
"u2",
|
|
154
|
+
"u3",
|
|
155
|
+
"u4",
|
|
156
|
+
], # Common limb darkening coefficients (linear, quadratic, cubic, quartic)
|
|
124
157
|
},
|
|
125
|
-
# The "other" effect type is handled by allowing any other string in
|
|
158
|
+
# The "other" effect type is handled by allowing any other string in
|
|
159
|
+
# `higher_order_effects` list itself.
|
|
126
160
|
}
|
|
127
161
|
|
|
128
162
|
# This dictionary defines properties/constraints for each known parameter
|
|
129
|
-
# (e.g., expected type, units, a more detailed description, corresponding
|
|
163
|
+
# (e.g., expected type, units, a more detailed description, corresponding
|
|
164
|
+
# uncertainty field name)
|
|
130
165
|
PARAMETER_PROPERTIES = {
|
|
131
166
|
# Core Microlensing Parameters
|
|
132
167
|
"t0": {"type": "float", "units": "HJD", "description": "Time of closest approach"},
|
|
133
|
-
"u0": {
|
|
134
|
-
|
|
135
|
-
|
|
168
|
+
"u0": {
|
|
169
|
+
"type": "float",
|
|
170
|
+
"units": "thetaE",
|
|
171
|
+
"description": "Minimum impact parameter",
|
|
172
|
+
},
|
|
173
|
+
"tE": {
|
|
174
|
+
"type": "float",
|
|
175
|
+
"units": "days",
|
|
176
|
+
"description": "Einstein radius crossing time",
|
|
177
|
+
},
|
|
178
|
+
"s": {
|
|
179
|
+
"type": "float",
|
|
180
|
+
"units": "thetaE",
|
|
181
|
+
"description": "Binary separation scaled by Einstein radius",
|
|
182
|
+
},
|
|
136
183
|
"q": {"type": "float", "units": "mass ratio", "description": "Mass ratio M2/M1"},
|
|
137
|
-
"alpha": {
|
|
138
|
-
|
|
184
|
+
"alpha": {
|
|
185
|
+
"type": "float",
|
|
186
|
+
"units": "rad",
|
|
187
|
+
"description": "Angle of source trajectory relative to binary axis",
|
|
188
|
+
},
|
|
139
189
|
# Higher-Order Effect Parameters
|
|
140
|
-
"rho": {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
"
|
|
146
|
-
|
|
190
|
+
"rho": {
|
|
191
|
+
"type": "float",
|
|
192
|
+
"units": "thetaE",
|
|
193
|
+
"description": "Source radius scaled by Einstein radius (Finite Source)",
|
|
194
|
+
},
|
|
195
|
+
"piEN": {
|
|
196
|
+
"type": "float",
|
|
197
|
+
"units": "Einstein radius",
|
|
198
|
+
"description": "Parallax vector component (North) (Parallax)",
|
|
199
|
+
},
|
|
200
|
+
"piEE": {
|
|
201
|
+
"type": "float",
|
|
202
|
+
"units": "Einstein radius",
|
|
203
|
+
"description": "Parallax vector component (East) (Parallax)",
|
|
204
|
+
},
|
|
205
|
+
"dsdt": {
|
|
206
|
+
"type": "float",
|
|
207
|
+
"units": "thetaE/year",
|
|
208
|
+
"description": "Rate of change of binary separation (Lens Orbital Motion)",
|
|
209
|
+
},
|
|
210
|
+
"dadt": {
|
|
211
|
+
"type": "float",
|
|
212
|
+
"units": "rad/year",
|
|
213
|
+
"description": "Rate of change of binary angle (Lens Orbital Motion)",
|
|
214
|
+
},
|
|
215
|
+
"dzdt": {
|
|
216
|
+
"type": "float",
|
|
217
|
+
"units": "au/year",
|
|
218
|
+
"description": "Relative radial rate of change of lenses (Lens Orbital Motion, if applicable)",
|
|
219
|
+
}, # Example, may vary
|
|
147
220
|
# Flux Parameters (dynamically generated by get_required_flux_params)
|
|
148
221
|
# Ensure these names precisely match how they're generated by get_required_flux_params
|
|
149
|
-
"F0_S": {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
"
|
|
155
|
-
|
|
222
|
+
"F0_S": {
|
|
223
|
+
"type": "float",
|
|
224
|
+
"units": "counts/s",
|
|
225
|
+
"description": "Source flux in band 0",
|
|
226
|
+
},
|
|
227
|
+
"F0_B": {
|
|
228
|
+
"type": "float",
|
|
229
|
+
"units": "counts/s",
|
|
230
|
+
"description": "Blend flux in band 0",
|
|
231
|
+
},
|
|
232
|
+
"F1_S": {
|
|
233
|
+
"type": "float",
|
|
234
|
+
"units": "counts/s",
|
|
235
|
+
"description": "Source flux in band 1",
|
|
236
|
+
},
|
|
237
|
+
"F1_B": {
|
|
238
|
+
"type": "float",
|
|
239
|
+
"units": "counts/s",
|
|
240
|
+
"description": "Blend flux in band 1",
|
|
241
|
+
},
|
|
242
|
+
"F2_S": {
|
|
243
|
+
"type": "float",
|
|
244
|
+
"units": "counts/s",
|
|
245
|
+
"description": "Source flux in band 2",
|
|
246
|
+
},
|
|
247
|
+
"F2_B": {
|
|
248
|
+
"type": "float",
|
|
249
|
+
"units": "counts/s",
|
|
250
|
+
"description": "Blend flux in band 2",
|
|
251
|
+
},
|
|
156
252
|
# Binary Source Flux Parameters (e.g., for "2S" models)
|
|
157
|
-
"F0_S1": {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
"
|
|
163
|
-
|
|
253
|
+
"F0_S1": {
|
|
254
|
+
"type": "float",
|
|
255
|
+
"units": "counts/s",
|
|
256
|
+
"description": "Primary source flux in band 0",
|
|
257
|
+
},
|
|
258
|
+
"F0_S2": {
|
|
259
|
+
"type": "float",
|
|
260
|
+
"units": "counts/s",
|
|
261
|
+
"description": "Secondary source flux in band 0",
|
|
262
|
+
},
|
|
263
|
+
"F1_S1": {
|
|
264
|
+
"type": "float",
|
|
265
|
+
"units": "counts/s",
|
|
266
|
+
"description": "Primary source flux in band 1",
|
|
267
|
+
},
|
|
268
|
+
"F1_S2": {
|
|
269
|
+
"type": "float",
|
|
270
|
+
"units": "counts/s",
|
|
271
|
+
"description": "Secondary source flux in band 1",
|
|
272
|
+
},
|
|
273
|
+
"F2_S1": {
|
|
274
|
+
"type": "float",
|
|
275
|
+
"units": "counts/s",
|
|
276
|
+
"description": "Primary source flux in band 2",
|
|
277
|
+
},
|
|
278
|
+
"F2_S2": {
|
|
279
|
+
"type": "float",
|
|
280
|
+
"units": "counts/s",
|
|
281
|
+
"description": "Secondary source flux in band 2",
|
|
282
|
+
},
|
|
164
283
|
# Gaussian Process parameters (examples, often ln-scaled)
|
|
165
|
-
"ln_K": {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
284
|
+
"ln_K": {
|
|
285
|
+
"type": "float",
|
|
286
|
+
"units": "mag^2",
|
|
287
|
+
"description": "Log-amplitude of the GP kernel (GP)",
|
|
288
|
+
},
|
|
289
|
+
"ln_lambda": {
|
|
290
|
+
"type": "float",
|
|
291
|
+
"units": "days",
|
|
292
|
+
"description": "Log-lengthscale of the GP kernel (GP)",
|
|
293
|
+
},
|
|
294
|
+
"ln_period": {
|
|
295
|
+
"type": "float",
|
|
296
|
+
"units": "days",
|
|
297
|
+
"description": "Log-period of the GP kernel (GP)",
|
|
298
|
+
},
|
|
299
|
+
"ln_gamma": {
|
|
300
|
+
"type": "float",
|
|
301
|
+
"units": " ",
|
|
302
|
+
"description": "Log-smoothing parameter of the GP kernel (GP)",
|
|
303
|
+
}, # Specific interpretation varies by kernel
|
|
170
304
|
# Stellar Rotation parameters (examples)
|
|
171
|
-
"v_rot_sin_i": {
|
|
172
|
-
|
|
173
|
-
|
|
305
|
+
"v_rot_sin_i": {
|
|
306
|
+
"type": "float",
|
|
307
|
+
"units": "km/s",
|
|
308
|
+
"description": "Rotational velocity times sin(inclination) (Stellar Rotation)",
|
|
309
|
+
},
|
|
310
|
+
"epsilon": {
|
|
311
|
+
"type": "float",
|
|
312
|
+
"units": " ",
|
|
313
|
+
"description": "Spot coverage/brightness parameter (Stellar Rotation)",
|
|
314
|
+
}, # Example, may vary
|
|
174
315
|
# Fitted Limb Darkening coefficients (examples)
|
|
175
|
-
"u1": {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
316
|
+
"u1": {
|
|
317
|
+
"type": "float",
|
|
318
|
+
"units": " ",
|
|
319
|
+
"description": "Linear limb darkening coefficient (Fitted Limb Darkening)",
|
|
320
|
+
},
|
|
321
|
+
"u2": {
|
|
322
|
+
"type": "float",
|
|
323
|
+
"units": " ",
|
|
324
|
+
"description": "Quadratic limb darkening coefficient (Fitted Limb Darkening)",
|
|
325
|
+
},
|
|
326
|
+
"u3": {
|
|
327
|
+
"type": "float",
|
|
328
|
+
"units": " ",
|
|
329
|
+
"description": "Cubic limb darkening coefficient (Fitted Limb Darkening)",
|
|
330
|
+
},
|
|
331
|
+
"u4": {
|
|
332
|
+
"type": "float",
|
|
333
|
+
"units": " ",
|
|
334
|
+
"description": "Quartic limb darkening coefficient (Fitted Limb Darkening)",
|
|
335
|
+
},
|
|
179
336
|
}
|
|
180
337
|
|
|
338
|
+
|
|
181
339
|
def get_required_flux_params(model_type: str, bands: List[str]) -> List[str]:
|
|
182
340
|
"""Get the required flux parameters for a given model type and bands.
|
|
183
|
-
|
|
341
|
+
|
|
184
342
|
Determines which flux parameters are required based on the model type
|
|
185
343
|
(single vs binary source) and the photometric bands used. For single
|
|
186
344
|
source models, each band requires source and blend flux parameters.
|
|
187
345
|
For binary source models, each band requires two source fluxes and
|
|
188
346
|
a common blend flux.
|
|
189
|
-
|
|
347
|
+
|
|
190
348
|
Args:
|
|
191
349
|
model_type: The type of microlensing model (e.g., "1S1L", "2S1L").
|
|
192
350
|
bands: List of band IDs as strings (e.g., ["0", "1", "2"]).
|
|
193
|
-
|
|
351
|
+
|
|
194
352
|
Returns:
|
|
195
353
|
List of required flux parameter names (e.g., ["F0_S", "F0_B", "F1_S", "F1_B"]).
|
|
196
|
-
|
|
354
|
+
|
|
197
355
|
Example:
|
|
198
356
|
>>> get_required_flux_params("1S1L", ["0", "1"])
|
|
199
357
|
['F0_S', 'F0_B', 'F1_S', 'F1_B']
|
|
200
|
-
|
|
358
|
+
|
|
201
359
|
>>> get_required_flux_params("2S1L", ["0", "1"])
|
|
202
360
|
['F0_S1', 'F0_S2', 'F0_B', 'F1_S1', 'F1_S2', 'F1_B']
|
|
203
|
-
|
|
361
|
+
|
|
204
362
|
>>> get_required_flux_params("1S1L", [])
|
|
205
363
|
[]
|
|
206
|
-
|
|
364
|
+
|
|
207
365
|
Note:
|
|
208
366
|
This function handles the most common model types (1S and 2S).
|
|
209
367
|
For models with more than 2 sources, additional logic would be needed.
|
|
@@ -211,16 +369,17 @@ def get_required_flux_params(model_type: str, bands: List[str]) -> List[str]:
|
|
|
211
369
|
"""
|
|
212
370
|
flux_params = []
|
|
213
371
|
if not bands:
|
|
214
|
-
return flux_params
|
|
215
|
-
|
|
372
|
+
return flux_params # No bands, no flux parameters
|
|
373
|
+
|
|
216
374
|
for band in bands:
|
|
217
|
-
if model_type.startswith("1S"):
|
|
218
|
-
flux_params.append(f"F{band}_S")
|
|
219
|
-
flux_params.append(f"F{band}_B")
|
|
220
|
-
elif model_type.startswith("2S"):
|
|
221
|
-
flux_params.append(f"F{band}_S1")
|
|
222
|
-
flux_params.append(f"F{band}_S2")
|
|
223
|
-
flux_params.append(f"F{band}_B") # Blend flux for this band
|
|
375
|
+
if model_type.startswith("1S"): # Single source models
|
|
376
|
+
flux_params.append(f"F{band}_S") # Source flux for this band
|
|
377
|
+
flux_params.append(f"F{band}_B") # Blend flux for this band
|
|
378
|
+
elif model_type.startswith("2S"): # Binary source models
|
|
379
|
+
flux_params.append(f"F{band}_S1") # First source flux for this band
|
|
380
|
+
flux_params.append(f"F{band}_S2") # Second source flux for this band
|
|
381
|
+
flux_params.append(f"F{band}_B") # Blend flux for this band
|
|
382
|
+
# (common for binary sources)
|
|
224
383
|
# Add more source types (e.g., 3S) if necessary in the future
|
|
225
384
|
return flux_params
|
|
226
385
|
|
|
@@ -231,21 +390,21 @@ def check_solution_completeness(
|
|
|
231
390
|
higher_order_effects: Optional[List[str]] = None,
|
|
232
391
|
bands: Optional[List[str]] = None,
|
|
233
392
|
t_ref: Optional[float] = None,
|
|
234
|
-
**kwargs
|
|
393
|
+
**kwargs,
|
|
235
394
|
) -> List[str]:
|
|
236
395
|
"""Check if a solution has all required parameters based on its model type and effects.
|
|
237
|
-
|
|
396
|
+
|
|
238
397
|
This function validates that all required parameters are present for the given
|
|
239
398
|
model type and any higher-order effects. It returns a list of human-readable
|
|
240
399
|
warning or error messages instead of raising exceptions immediately.
|
|
241
|
-
|
|
400
|
+
|
|
242
401
|
The validation checks:
|
|
243
402
|
- Required core parameters for the model type
|
|
244
403
|
- Required parameters for each higher-order effect
|
|
245
404
|
- Flux parameters for specified photometric bands
|
|
246
405
|
- Reference time requirements for time-dependent effects
|
|
247
406
|
- Recognition of unknown parameters (warnings only)
|
|
248
|
-
|
|
407
|
+
|
|
249
408
|
Args:
|
|
250
409
|
model_type: The type of microlensing model (e.g., '1S1L', '1S2L').
|
|
251
410
|
parameters: Dictionary of model parameters with parameter names as keys.
|
|
@@ -256,40 +415,45 @@ def check_solution_completeness(
|
|
|
256
415
|
t_ref: Reference time for time-dependent effects (Julian Date).
|
|
257
416
|
Required for effects that specify requires_t_ref=True.
|
|
258
417
|
**kwargs: Additional solution attributes to validate (currently unused).
|
|
259
|
-
|
|
418
|
+
|
|
260
419
|
Returns:
|
|
261
420
|
List of validation messages. Empty list if all validations pass.
|
|
262
421
|
Messages indicate missing required parameters, unknown effects,
|
|
263
422
|
missing reference times, or unrecognized parameters.
|
|
264
|
-
|
|
423
|
+
|
|
265
424
|
Example:
|
|
266
425
|
>>> # Simple 1S1L solution - should pass
|
|
267
426
|
>>> params = {"t0": 2459123.5, "u0": 0.1, "tE": 20.0}
|
|
268
427
|
>>> messages = check_solution_completeness("1S1L", params)
|
|
269
428
|
>>> print(messages)
|
|
270
429
|
[]
|
|
271
|
-
|
|
430
|
+
|
|
272
431
|
>>> # Missing required parameter
|
|
273
432
|
>>> params = {"t0": 2459123.5, "u0": 0.1} # Missing tE
|
|
274
433
|
>>> messages = check_solution_completeness("1S1L", params)
|
|
275
434
|
>>> print(messages)
|
|
276
435
|
["Missing required core parameter 'tE' for model type '1S1L'"]
|
|
277
|
-
|
|
436
|
+
|
|
278
437
|
>>> # Binary lens with parallax
|
|
279
438
|
>>> params = {
|
|
280
439
|
... "t0": 2459123.5, "u0": 0.1, "tE": 20.0,
|
|
281
440
|
... "s": 1.2, "q": 0.5, "alpha": 45.0,
|
|
282
441
|
... "piEN": 0.1, "piEE": 0.05
|
|
283
442
|
... }
|
|
284
|
-
>>> messages = check_solution_completeness(
|
|
443
|
+
>>> messages = check_solution_completeness(
|
|
444
|
+
... "1S2L",
|
|
445
|
+
... params,
|
|
446
|
+
... ["parallax"],
|
|
447
|
+
... t_ref=2459123.0
|
|
448
|
+
... )
|
|
285
449
|
>>> print(messages)
|
|
286
450
|
[]
|
|
287
|
-
|
|
451
|
+
|
|
288
452
|
>>> # Missing reference time for parallax
|
|
289
453
|
>>> messages = check_solution_completeness("1S2L", params, ["parallax"])
|
|
290
454
|
>>> print(messages)
|
|
291
455
|
["Reference time (t_ref) required for effect 'parallax'"]
|
|
292
|
-
|
|
456
|
+
|
|
293
457
|
Note:
|
|
294
458
|
This function is designed to be comprehensive but not overly strict.
|
|
295
459
|
Unknown parameters generate warnings rather than errors to accommodate
|
|
@@ -297,116 +461,119 @@ def check_solution_completeness(
|
|
|
297
461
|
the predefined MODEL_DEFINITIONS and HIGHER_ORDER_EFFECT_DEFINITIONS.
|
|
298
462
|
"""
|
|
299
463
|
messages = []
|
|
300
|
-
|
|
464
|
+
|
|
301
465
|
# Validate model type
|
|
302
466
|
if model_type not in MODEL_DEFINITIONS:
|
|
303
|
-
messages.append(f"Unknown model type: '{model_type}'. Valid types: {list(MODEL_DEFINITIONS.keys())}")
|
|
467
|
+
messages.append(f"Unknown model type: '{model_type}'. " f"Valid types: {list(MODEL_DEFINITIONS.keys())}")
|
|
304
468
|
return messages
|
|
305
|
-
|
|
469
|
+
|
|
306
470
|
model_def = MODEL_DEFINITIONS[model_type]
|
|
307
|
-
|
|
471
|
+
|
|
308
472
|
# Check required core parameters
|
|
309
|
-
required_core_params = model_def.get(
|
|
473
|
+
required_core_params = model_def.get("required_params_core", [])
|
|
310
474
|
for param in required_core_params:
|
|
311
475
|
if param not in parameters:
|
|
312
|
-
messages.append(f"Missing required core parameter '{param}' for model type '{model_type}'")
|
|
313
|
-
|
|
476
|
+
messages.append(f"Missing required core parameter '{param}' for model type " f"'{model_type}'")
|
|
477
|
+
|
|
314
478
|
# Validate higher-order effects
|
|
315
479
|
if higher_order_effects:
|
|
316
480
|
for effect in higher_order_effects:
|
|
317
481
|
if effect not in HIGHER_ORDER_EFFECT_DEFINITIONS:
|
|
318
|
-
messages.append(
|
|
482
|
+
messages.append(
|
|
483
|
+
f"Unknown higher-order effect: '{effect}'. "
|
|
484
|
+
f"Valid effects: {list(HIGHER_ORDER_EFFECT_DEFINITIONS.keys())}"
|
|
485
|
+
)
|
|
319
486
|
continue
|
|
320
|
-
|
|
487
|
+
|
|
321
488
|
effect_def = HIGHER_ORDER_EFFECT_DEFINITIONS[effect]
|
|
322
|
-
|
|
489
|
+
|
|
323
490
|
# Check required parameters for this effect
|
|
324
|
-
effect_required = effect_def.get(
|
|
491
|
+
effect_required = effect_def.get("required_higher_order_params", [])
|
|
325
492
|
for param in effect_required:
|
|
326
493
|
if param not in parameters:
|
|
327
|
-
messages.append(f"Missing required parameter '{param}' for effect '{effect}'")
|
|
328
|
-
|
|
494
|
+
messages.append(f"Missing required parameter '{param}' for effect " f"'{effect}'")
|
|
495
|
+
|
|
329
496
|
# Check optional parameters for this effect
|
|
330
|
-
effect_optional = effect_def.get(
|
|
497
|
+
effect_optional = effect_def.get("optional_higher_order_params", [])
|
|
331
498
|
for param in effect_optional:
|
|
332
499
|
if param not in parameters:
|
|
333
|
-
messages.append(f"Warning: Optional parameter '{param}' not provided for effect '{effect}'")
|
|
334
|
-
|
|
500
|
+
messages.append(f"Warning: Optional parameter '{param}' not provided " f"for effect '{effect}'")
|
|
501
|
+
|
|
335
502
|
# Check if t_ref is required for this effect
|
|
336
|
-
if effect_def.get(
|
|
503
|
+
if effect_def.get("requires_t_ref", False) and t_ref is None:
|
|
337
504
|
messages.append(f"Reference time (t_ref) required for effect '{effect}'")
|
|
338
|
-
|
|
505
|
+
|
|
339
506
|
# Validate band-specific parameters
|
|
340
507
|
if bands:
|
|
341
508
|
required_flux_params = get_required_flux_params(model_type, bands)
|
|
342
509
|
for param in required_flux_params:
|
|
343
510
|
if param not in parameters:
|
|
344
|
-
messages.append(f"Missing required flux parameter '{param}' for bands {bands}")
|
|
345
|
-
|
|
511
|
+
messages.append(f"Missing required flux parameter '{param}' for bands " f"{bands}")
|
|
512
|
+
|
|
346
513
|
# Check for invalid parameters (not in any definition)
|
|
347
514
|
all_valid_params = set()
|
|
348
|
-
|
|
515
|
+
|
|
349
516
|
# Add core model parameters
|
|
350
517
|
all_valid_params.update(required_core_params)
|
|
351
|
-
|
|
518
|
+
|
|
352
519
|
# Add higher-order effect parameters
|
|
353
520
|
if higher_order_effects:
|
|
354
521
|
for effect in higher_order_effects:
|
|
355
522
|
if effect in HIGHER_ORDER_EFFECT_DEFINITIONS:
|
|
356
523
|
effect_def = HIGHER_ORDER_EFFECT_DEFINITIONS[effect]
|
|
357
|
-
all_valid_params.update(effect_def.get(
|
|
358
|
-
all_valid_params.update(effect_def.get(
|
|
359
|
-
|
|
524
|
+
all_valid_params.update(effect_def.get("required_higher_order_params", []))
|
|
525
|
+
all_valid_params.update(effect_def.get("optional_higher_order_params", []))
|
|
526
|
+
|
|
360
527
|
# Add band-specific parameters if bands are specified
|
|
361
528
|
if bands:
|
|
362
529
|
all_valid_params.update(get_required_flux_params(model_type, bands))
|
|
363
|
-
|
|
530
|
+
|
|
364
531
|
# Check for invalid parameters
|
|
365
532
|
invalid_params = set(parameters.keys()) - all_valid_params
|
|
366
533
|
for param in invalid_params:
|
|
367
|
-
messages.append(f"Warning: Parameter '{param}' not recognized for model type '{model_type}'")
|
|
368
|
-
|
|
534
|
+
messages.append(f"Warning: Parameter '{param}' not recognized for model type " f"'{model_type}'")
|
|
535
|
+
|
|
369
536
|
return messages
|
|
370
537
|
|
|
371
538
|
|
|
372
539
|
def validate_parameter_types(
|
|
373
540
|
parameters: Dict[str, Any],
|
|
374
|
-
model_type: str
|
|
541
|
+
model_type: str,
|
|
375
542
|
) -> List[str]:
|
|
376
543
|
"""Validate parameter types and value ranges against expected types.
|
|
377
|
-
|
|
544
|
+
|
|
378
545
|
Checks that parameters have the correct data types as defined in
|
|
379
546
|
PARAMETER_PROPERTIES. Currently supports validation of float, int,
|
|
380
547
|
and string types. Parameters not defined in PARAMETER_PROPERTIES
|
|
381
548
|
are skipped (no validation performed).
|
|
382
|
-
|
|
549
|
+
|
|
383
550
|
Args:
|
|
384
551
|
parameters: Dictionary of model parameters with parameter names as keys.
|
|
385
552
|
model_type: The type of microlensing model (used for context in messages).
|
|
386
|
-
|
|
553
|
+
|
|
387
554
|
Returns:
|
|
388
555
|
List of validation messages. Empty list if all validations pass.
|
|
389
556
|
Messages indicate type mismatches for known parameters.
|
|
390
|
-
|
|
557
|
+
|
|
391
558
|
Example:
|
|
392
559
|
>>> # Valid parameters
|
|
393
560
|
>>> params = {"t0": 2459123.5, "u0": 0.1, "tE": 20.0}
|
|
394
561
|
>>> messages = validate_parameter_types(params, "1S1L")
|
|
395
562
|
>>> print(messages)
|
|
396
563
|
[]
|
|
397
|
-
|
|
564
|
+
|
|
398
565
|
>>> # Invalid type for t0
|
|
399
566
|
>>> params = {"t0": "2459123.5", "u0": 0.1, "tE": 20.0} # t0 is string
|
|
400
567
|
>>> messages = validate_parameter_types(params, "1S1L")
|
|
401
568
|
>>> print(messages)
|
|
402
569
|
["Parameter 't0' should be numeric, got str"]
|
|
403
|
-
|
|
570
|
+
|
|
404
571
|
>>> # Unknown parameter (no validation performed)
|
|
405
572
|
>>> params = {"t0": 2459123.5, "custom_param": "value"}
|
|
406
573
|
>>> messages = validate_parameter_types(params, "1S1L")
|
|
407
574
|
>>> print(messages)
|
|
408
575
|
[]
|
|
409
|
-
|
|
576
|
+
|
|
410
577
|
Note:
|
|
411
578
|
This function only validates parameters that are defined in
|
|
412
579
|
PARAMETER_PROPERTIES. Unknown parameters are ignored to allow
|
|
@@ -414,50 +581,49 @@ def validate_parameter_types(
|
|
|
414
581
|
is currently limited to basic type checking (float, int, str).
|
|
415
582
|
"""
|
|
416
583
|
messages = []
|
|
417
|
-
|
|
584
|
+
|
|
418
585
|
if model_type not in MODEL_DEFINITIONS:
|
|
419
586
|
return [f"Unknown model type: '{model_type}'"]
|
|
420
|
-
|
|
587
|
+
|
|
421
588
|
for param, value in parameters.items():
|
|
422
589
|
if param in PARAMETER_PROPERTIES:
|
|
423
590
|
prop = PARAMETER_PROPERTIES[param]
|
|
424
|
-
|
|
591
|
+
|
|
425
592
|
# Check type
|
|
426
|
-
expected_type = prop.get(
|
|
427
|
-
if expected_type ==
|
|
428
|
-
messages.append(f"Parameter '{param}' should be numeric, got {type(value).__name__}")
|
|
429
|
-
elif expected_type ==
|
|
430
|
-
messages.append(f"Parameter '{param}' should be integer, got {type(value).__name__}")
|
|
431
|
-
elif expected_type ==
|
|
432
|
-
messages.append(f"Parameter '{param}' should be string, got {type(value).__name__}")
|
|
433
|
-
|
|
593
|
+
expected_type = prop.get("type")
|
|
594
|
+
if expected_type == "float" and not isinstance(value, (int, float)):
|
|
595
|
+
messages.append(f"Parameter '{param}' should be numeric, got " f"{type(value).__name__}")
|
|
596
|
+
elif expected_type == "int" and not isinstance(value, int):
|
|
597
|
+
messages.append(f"Parameter '{param}' should be integer, got " f"{type(value).__name__}")
|
|
598
|
+
elif expected_type == "str" and not isinstance(value, str):
|
|
599
|
+
messages.append(f"Parameter '{param}' should be string, got " f"{type(value).__name__}")
|
|
600
|
+
|
|
434
601
|
return messages
|
|
435
602
|
|
|
436
603
|
|
|
437
604
|
def validate_parameter_uncertainties(
|
|
438
|
-
parameters: Dict[str, Any],
|
|
439
|
-
uncertainties: Optional[Dict[str, Any]] = None
|
|
605
|
+
parameters: Dict[str, Any], uncertainties: Optional[Dict[str, Any]] = None
|
|
440
606
|
) -> List[str]:
|
|
441
607
|
"""Validate parameter uncertainties for reasonableness and consistency.
|
|
442
|
-
|
|
608
|
+
|
|
443
609
|
Performs comprehensive validation of parameter uncertainties, including:
|
|
444
610
|
- Format validation (single value or [lower, upper] pairs)
|
|
445
611
|
- Sign validation (uncertainties must be positive)
|
|
446
612
|
- Consistency checks (lower ≤ upper for asymmetric uncertainties)
|
|
447
613
|
- Reasonableness checks (relative uncertainty between 0.1% and 50%)
|
|
448
|
-
|
|
614
|
+
|
|
449
615
|
Args:
|
|
450
616
|
parameters: Dictionary of model parameters with parameter names as keys.
|
|
451
617
|
uncertainties: Dictionary of parameter uncertainties. Can be None if
|
|
452
618
|
no uncertainties are provided. Supports two formats:
|
|
453
619
|
- Single value: {"param": 0.1} (symmetric uncertainty)
|
|
454
620
|
- Asymmetric bounds: {"param": [0.05, 0.15]} (lower, upper)
|
|
455
|
-
|
|
621
|
+
|
|
456
622
|
Returns:
|
|
457
623
|
List of validation messages. Empty list if all validations pass.
|
|
458
624
|
Messages indicate format errors, sign issues, consistency problems,
|
|
459
625
|
or warnings about very large/small relative uncertainties.
|
|
460
|
-
|
|
626
|
+
|
|
461
627
|
Example:
|
|
462
628
|
>>> # Valid symmetric uncertainties
|
|
463
629
|
>>> params = {"t0": 2459123.5, "u0": 0.1, "tE": 20.0}
|
|
@@ -465,31 +631,31 @@ def validate_parameter_uncertainties(
|
|
|
465
631
|
>>> messages = validate_parameter_uncertainties(params, unc)
|
|
466
632
|
>>> print(messages)
|
|
467
633
|
[]
|
|
468
|
-
|
|
634
|
+
|
|
469
635
|
>>> # Valid asymmetric uncertainties
|
|
470
636
|
>>> unc = {"t0": [0.05, 0.15], "u0": [0.005, 0.015]}
|
|
471
637
|
>>> messages = validate_parameter_uncertainties(params, unc)
|
|
472
638
|
>>> print(messages)
|
|
473
639
|
[]
|
|
474
|
-
|
|
640
|
+
|
|
475
641
|
>>> # Invalid format
|
|
476
642
|
>>> unc = {"t0": [0.1, 0.2, 0.3]} # Too many values
|
|
477
643
|
>>> messages = validate_parameter_uncertainties(params, unc)
|
|
478
644
|
>>> print(messages)
|
|
479
645
|
["Uncertainty for 't0' should be [lower, upper] or single value"]
|
|
480
|
-
|
|
646
|
+
|
|
481
647
|
>>> # Inconsistent bounds
|
|
482
648
|
>>> unc = {"t0": [0.2, 0.1]} # Lower > upper
|
|
483
649
|
>>> messages = validate_parameter_uncertainties(params, unc)
|
|
484
650
|
>>> print(messages)
|
|
485
651
|
["Lower uncertainty for 't0' (0.2) > upper uncertainty (0.1)"]
|
|
486
|
-
|
|
652
|
+
|
|
487
653
|
>>> # Very large relative uncertainty
|
|
488
654
|
>>> unc = {"t0": 1000.0} # Very large uncertainty
|
|
489
655
|
>>> messages = validate_parameter_uncertainties(params, unc)
|
|
490
656
|
>>> print(messages)
|
|
491
657
|
["Warning: Uncertainty for 't0' is very large (40.8% of parameter value)"]
|
|
492
|
-
|
|
658
|
+
|
|
493
659
|
Note:
|
|
494
660
|
This function provides warnings rather than errors for very large
|
|
495
661
|
or very small relative uncertainties, as these might be legitimate
|
|
@@ -497,22 +663,22 @@ def validate_parameter_uncertainties(
|
|
|
497
663
|
typical microlensing parameter uncertainties.
|
|
498
664
|
"""
|
|
499
665
|
messages = []
|
|
500
|
-
|
|
666
|
+
|
|
501
667
|
if not uncertainties:
|
|
502
668
|
return messages
|
|
503
|
-
|
|
669
|
+
|
|
504
670
|
for param_name, uncertainty in uncertainties.items():
|
|
505
671
|
if param_name not in parameters:
|
|
506
672
|
messages.append(f"Uncertainty provided for unknown parameter '{param_name}'")
|
|
507
673
|
continue
|
|
508
|
-
|
|
674
|
+
|
|
509
675
|
param_value = parameters[param_name]
|
|
510
|
-
|
|
676
|
+
|
|
511
677
|
# Handle different uncertainty formats
|
|
512
678
|
if isinstance(uncertainty, (list, tuple)):
|
|
513
679
|
# [lower, upper] format
|
|
514
680
|
if len(uncertainty) != 2:
|
|
515
|
-
messages.append(f"Uncertainty for '{param_name}' should be [lower, upper] or single value")
|
|
681
|
+
messages.append(f"Uncertainty for '{param_name}' should be [lower, upper] " f"or single value")
|
|
516
682
|
continue
|
|
517
683
|
lower, upper = uncertainty
|
|
518
684
|
if not (isinstance(lower, (int, float)) and isinstance(upper, (int, float))):
|
|
@@ -522,7 +688,7 @@ def validate_parameter_uncertainties(
|
|
|
522
688
|
messages.append(f"Uncertainty bounds for '{param_name}' must be positive")
|
|
523
689
|
continue
|
|
524
690
|
if lower > upper:
|
|
525
|
-
messages.append(f"Lower uncertainty for '{param_name}' ({lower}) > upper uncertainty ({upper})")
|
|
691
|
+
messages.append(f"Lower uncertainty for '{param_name}' ({lower}) > " f"upper uncertainty ({upper})")
|
|
526
692
|
continue
|
|
527
693
|
else:
|
|
528
694
|
# Single value format
|
|
@@ -533,21 +699,30 @@ def validate_parameter_uncertainties(
|
|
|
533
699
|
messages.append(f"Uncertainty for '{param_name}' must be positive")
|
|
534
700
|
continue
|
|
535
701
|
lower = upper = uncertainty
|
|
536
|
-
|
|
702
|
+
|
|
537
703
|
# Check if uncertainty is reasonable relative to parameter value
|
|
538
704
|
if isinstance(param_value, (int, float)) and param_value != 0:
|
|
539
705
|
# Calculate relative uncertainty
|
|
540
706
|
if isinstance(uncertainty, (list, tuple)):
|
|
541
|
-
rel_uncertainty = max(
|
|
707
|
+
rel_uncertainty = max(
|
|
708
|
+
abs(lower / param_value),
|
|
709
|
+
abs(upper / param_value),
|
|
710
|
+
)
|
|
542
711
|
else:
|
|
543
|
-
rel_uncertainty = abs(uncertainty/param_value)
|
|
544
|
-
|
|
712
|
+
rel_uncertainty = abs(uncertainty / param_value)
|
|
713
|
+
|
|
545
714
|
# Warn if uncertainty is very large (>50%) or very small (<0.1%)
|
|
546
715
|
if rel_uncertainty > 0.5:
|
|
547
|
-
messages.append(
|
|
716
|
+
messages.append(
|
|
717
|
+
f"Warning: Uncertainty for '{param_name}' is very large "
|
|
718
|
+
f"({rel_uncertainty:.1%} of parameter value)"
|
|
719
|
+
)
|
|
548
720
|
elif rel_uncertainty < 0.001:
|
|
549
|
-
messages.append(
|
|
550
|
-
|
|
721
|
+
messages.append(
|
|
722
|
+
f"Warning: Uncertainty for '{param_name}' is very small "
|
|
723
|
+
f"({rel_uncertainty:.1%} of parameter value)"
|
|
724
|
+
)
|
|
725
|
+
|
|
551
726
|
return messages
|
|
552
727
|
|
|
553
728
|
|
|
@@ -557,52 +732,52 @@ def validate_solution_consistency(
|
|
|
557
732
|
**kwargs: Any,
|
|
558
733
|
) -> List[str]:
|
|
559
734
|
"""Validate internal consistency of solution parameters.
|
|
560
|
-
|
|
735
|
+
|
|
561
736
|
Performs physical consistency checks on microlensing parameters to
|
|
562
737
|
identify potentially problematic values. This includes range validation,
|
|
563
738
|
physical constraints, and model-specific consistency checks.
|
|
564
|
-
|
|
739
|
+
|
|
565
740
|
Args:
|
|
566
741
|
model_type: The type of microlensing model (e.g., '1S1L', '1S2L').
|
|
567
742
|
parameters: Dictionary of model parameters with parameter names as keys.
|
|
568
743
|
**kwargs: Additional solution attributes. Currently supports:
|
|
569
744
|
relative_probability: Probability value for range checking (0-1).
|
|
570
|
-
|
|
745
|
+
|
|
571
746
|
Returns:
|
|
572
747
|
List of validation messages. Empty list if all validations pass.
|
|
573
748
|
Messages indicate physical inconsistencies, range violations,
|
|
574
749
|
or warnings about unusual parameter combinations.
|
|
575
|
-
|
|
750
|
+
|
|
576
751
|
Example:
|
|
577
752
|
>>> # Valid parameters
|
|
578
753
|
>>> params = {"t0": 2459123.5, "u0": 0.1, "tE": 20.0}
|
|
579
754
|
>>> messages = validate_solution_consistency("1S1L", params)
|
|
580
755
|
>>> print(messages)
|
|
581
756
|
[]
|
|
582
|
-
|
|
757
|
+
|
|
583
758
|
>>> # Invalid tE (must be positive)
|
|
584
759
|
>>> params = {"t0": 2459123.5, "u0": 0.1, "tE": -5.0}
|
|
585
760
|
>>> messages = validate_solution_consistency("1S1L", params)
|
|
586
761
|
>>> print(messages)
|
|
587
762
|
["Einstein crossing time (tE) must be positive"]
|
|
588
|
-
|
|
763
|
+
|
|
589
764
|
>>> # Invalid mass ratio
|
|
590
765
|
>>> params = {"t0": 2459123.5, "u0": 0.1, "tE": 20.0, "q": 1.5}
|
|
591
766
|
>>> messages = validate_solution_consistency("1S2L", params)
|
|
592
767
|
>>> print(messages)
|
|
593
768
|
["Mass ratio (q) should be between 0 and 1"]
|
|
594
|
-
|
|
769
|
+
|
|
595
770
|
>>> # Invalid relative probability
|
|
596
771
|
>>> messages = validate_solution_consistency("1S1L", params, relative_probability=1.5)
|
|
597
772
|
>>> print(messages)
|
|
598
773
|
["Relative probability should be between 0 and 1"]
|
|
599
|
-
|
|
774
|
+
|
|
600
775
|
>>> # Binary lens with unusual separation
|
|
601
776
|
>>> params = {"t0": 2459123.5, "u0": 0.1, "tE": 20.0, "s": 0.1, "q": 0.5}
|
|
602
777
|
>>> messages = validate_solution_consistency("1S2L", params)
|
|
603
778
|
>>> print(messages)
|
|
604
779
|
["Warning: Separation (s) outside typical caustic crossing range (0.5-2.0)"]
|
|
605
|
-
|
|
780
|
+
|
|
606
781
|
Note:
|
|
607
782
|
This function focuses on physical consistency rather than statistical
|
|
608
783
|
validation. Warnings are provided for unusual but not impossible
|
|
@@ -610,30 +785,153 @@ def validate_solution_consistency(
|
|
|
610
785
|
lenses is a guideline based on typical microlensing events.
|
|
611
786
|
"""
|
|
612
787
|
messages = []
|
|
613
|
-
|
|
788
|
+
|
|
614
789
|
# Check for physically impossible values
|
|
615
|
-
if
|
|
790
|
+
if "tE" in parameters and parameters["tE"] <= 0:
|
|
616
791
|
messages.append("Einstein crossing time (tE) must be positive")
|
|
617
|
-
|
|
618
|
-
if
|
|
792
|
+
|
|
793
|
+
if "q" in parameters and (parameters["q"] <= 0 or parameters["q"] > 1):
|
|
619
794
|
messages.append("Mass ratio (q) should be between 0 and 1")
|
|
620
|
-
|
|
621
|
-
if
|
|
795
|
+
|
|
796
|
+
if "s" in parameters and parameters["s"] <= 0:
|
|
622
797
|
messages.append("Separation (s) must be positive")
|
|
623
798
|
|
|
624
799
|
rel_prob = kwargs.get("relative_probability")
|
|
625
800
|
if rel_prob is not None and not 0 <= rel_prob <= 1:
|
|
626
801
|
messages.append("Relative probability should be between 0 and 1")
|
|
627
|
-
|
|
802
|
+
|
|
628
803
|
# Check for binary lens specific consistency (1S2L, 2S2L models)
|
|
629
|
-
if model_type in [
|
|
630
|
-
if
|
|
804
|
+
if model_type in ["1S2L", "2S2L"]:
|
|
805
|
+
if "q" in parameters and "s" in parameters:
|
|
631
806
|
# Check for caustic crossing conditions
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
807
|
+
s = parameters["s"]
|
|
808
|
+
|
|
635
809
|
# Simple caustic crossing check
|
|
636
810
|
if s < 0.5 or s > 2.0:
|
|
637
|
-
messages.append("Warning: Separation (s) outside typical caustic crossing range (0.5-2.0)")
|
|
638
|
-
|
|
811
|
+
messages.append("Warning: " "Separation (s) outside typical caustic crossing range " "(0.5-2.0)")
|
|
812
|
+
|
|
813
|
+
return messages
|
|
814
|
+
|
|
815
|
+
|
|
816
|
+
def validate_solution_rigorously(
|
|
817
|
+
model_type: str,
|
|
818
|
+
parameters: Dict[str, Any],
|
|
819
|
+
higher_order_effects: Optional[List[str]] = None,
|
|
820
|
+
bands: Optional[List[str]] = None,
|
|
821
|
+
t_ref: Optional[float] = None,
|
|
822
|
+
) -> List[str]:
|
|
823
|
+
"""Extremely rigorous validation of solution parameters.
|
|
824
|
+
|
|
825
|
+
This function performs comprehensive validation that catches ALL parameter errors:
|
|
826
|
+
- Parameter types must be correct (t_ref must be float, etc.)
|
|
827
|
+
- No invalid parameters for model type (e.g., 's' parameter for 1S1L)
|
|
828
|
+
- t_ref only allowed when required by higher-order effects
|
|
829
|
+
- bands must be a list of strings
|
|
830
|
+
- All required flux parameters must be present for each band
|
|
831
|
+
- Only "other" model types or effects can have unknown parameters
|
|
832
|
+
|
|
833
|
+
Args:
|
|
834
|
+
model_type: The type of microlensing model
|
|
835
|
+
parameters: Dictionary of model parameters
|
|
836
|
+
higher_order_effects: List of higher-order effects
|
|
837
|
+
bands: List of photometric bands
|
|
838
|
+
t_ref: Reference time for time-dependent effects
|
|
839
|
+
|
|
840
|
+
Returns:
|
|
841
|
+
List of validation error messages. Empty list if all validations pass.
|
|
842
|
+
"""
|
|
843
|
+
messages = []
|
|
844
|
+
higher_order_effects = higher_order_effects or []
|
|
845
|
+
bands = bands or []
|
|
846
|
+
|
|
847
|
+
# 1. Validate t_ref type
|
|
848
|
+
if t_ref is not None and not isinstance(t_ref, (int, float)):
|
|
849
|
+
messages.append(f"t_ref must be numeric, got {type(t_ref).__name__}")
|
|
850
|
+
|
|
851
|
+
# 2. Validate bands format
|
|
852
|
+
if not isinstance(bands, list):
|
|
853
|
+
messages.append(f"bands must be a list, got {type(bands).__name__}")
|
|
854
|
+
else:
|
|
855
|
+
for i, band in enumerate(bands):
|
|
856
|
+
if not isinstance(band, str):
|
|
857
|
+
messages.append(f"band {i} must be a string, got {type(band).__name__}")
|
|
858
|
+
|
|
859
|
+
# 3. Check if t_ref is provided when not needed
|
|
860
|
+
t_ref_required = False
|
|
861
|
+
for effect in higher_order_effects:
|
|
862
|
+
if effect in HIGHER_ORDER_EFFECT_DEFINITIONS:
|
|
863
|
+
if HIGHER_ORDER_EFFECT_DEFINITIONS[effect].get("requires_t_ref", False):
|
|
864
|
+
t_ref_required = True
|
|
865
|
+
break
|
|
866
|
+
|
|
867
|
+
if not t_ref_required and t_ref is not None:
|
|
868
|
+
messages.append("t_ref provided but not required by any higher-order effects")
|
|
869
|
+
|
|
870
|
+
# 4. Get all valid parameters for this model and effects
|
|
871
|
+
valid_params = set()
|
|
872
|
+
|
|
873
|
+
# Add core model parameters
|
|
874
|
+
if model_type in MODEL_DEFINITIONS:
|
|
875
|
+
valid_params.update(MODEL_DEFINITIONS[model_type]["required_params_core"])
|
|
876
|
+
elif model_type != "other":
|
|
877
|
+
messages.append(f"Unknown model type: '{model_type}'")
|
|
878
|
+
|
|
879
|
+
# Add higher-order effect parameters
|
|
880
|
+
for effect in higher_order_effects:
|
|
881
|
+
if effect in HIGHER_ORDER_EFFECT_DEFINITIONS:
|
|
882
|
+
effect_def = HIGHER_ORDER_EFFECT_DEFINITIONS[effect]
|
|
883
|
+
valid_params.update(effect_def.get("required_higher_order_params", []))
|
|
884
|
+
valid_params.update(effect_def.get("optional_higher_order_params", []))
|
|
885
|
+
elif effect != "other":
|
|
886
|
+
messages.append(f"Unknown higher-order effect: '{effect}'")
|
|
887
|
+
|
|
888
|
+
# Add band-specific parameters
|
|
889
|
+
if bands:
|
|
890
|
+
valid_params.update(get_required_flux_params(model_type, bands))
|
|
891
|
+
|
|
892
|
+
# 5. Check for invalid parameters (unless model_type or effects are "other")
|
|
893
|
+
if model_type != "other" and "other" not in higher_order_effects:
|
|
894
|
+
invalid_params = set(parameters.keys()) - valid_params
|
|
895
|
+
for param in invalid_params:
|
|
896
|
+
messages.append(f"Invalid parameter '{param}' for model type '{model_type}'")
|
|
897
|
+
|
|
898
|
+
# 6. Validate parameter types for all parameters
|
|
899
|
+
for param, value in parameters.items():
|
|
900
|
+
if param in PARAMETER_PROPERTIES:
|
|
901
|
+
prop = PARAMETER_PROPERTIES[param]
|
|
902
|
+
expected_type = prop.get("type")
|
|
903
|
+
|
|
904
|
+
if expected_type == "float" and not isinstance(value, (int, float)):
|
|
905
|
+
messages.append(f"Parameter '{param}' must be numeric, got {type(value).__name__}")
|
|
906
|
+
elif expected_type == "int" and not isinstance(value, int):
|
|
907
|
+
messages.append(f"Parameter '{param}' must be integer, got {type(value).__name__}")
|
|
908
|
+
elif expected_type == "str" and not isinstance(value, str):
|
|
909
|
+
messages.append(f"Parameter '{param}' must be string, got {type(value).__name__}")
|
|
910
|
+
|
|
911
|
+
# 7. Check for missing required parameters
|
|
912
|
+
missing_core = []
|
|
913
|
+
if model_type in MODEL_DEFINITIONS:
|
|
914
|
+
for param in MODEL_DEFINITIONS[model_type]["required_params_core"]:
|
|
915
|
+
if param not in parameters:
|
|
916
|
+
missing_core.append(param)
|
|
917
|
+
|
|
918
|
+
if missing_core:
|
|
919
|
+
messages.append(f"Missing required parameters for {model_type}: {missing_core}")
|
|
920
|
+
|
|
921
|
+
# 8. Check for missing higher-order effect parameters
|
|
922
|
+
for effect in higher_order_effects:
|
|
923
|
+
if effect in HIGHER_ORDER_EFFECT_DEFINITIONS:
|
|
924
|
+
effect_def = HIGHER_ORDER_EFFECT_DEFINITIONS[effect]
|
|
925
|
+
required_params = effect_def.get("required_higher_order_params", [])
|
|
926
|
+
missing_params = [param for param in required_params if param not in parameters]
|
|
927
|
+
if missing_params:
|
|
928
|
+
messages.append(f"Missing required parameters for effect '{effect}': {missing_params}")
|
|
929
|
+
|
|
930
|
+
# 9. Check for missing flux parameters
|
|
931
|
+
if bands:
|
|
932
|
+
required_flux = get_required_flux_params(model_type, bands)
|
|
933
|
+
missing_flux = [param for param in required_flux if param not in parameters]
|
|
934
|
+
if missing_flux:
|
|
935
|
+
messages.append(f"Missing required flux parameters for bands {bands}: {missing_flux}")
|
|
936
|
+
|
|
639
937
|
return messages
|