microlens-submit 0.16.5__py3-none-any.whl → 0.17.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.
- microlens_submit/__init__.py +1 -1
- microlens_submit/cli/commands/solutions.py +29 -1
- microlens_submit/cli/commands/validation.py +8 -3
- microlens_submit/models/solution.py +17 -0
- microlens_submit/models/submission.py +4 -2
- microlens_submit/tier_validation.py +6 -9
- microlens_submit/validate_parameters.py +522 -190
- {microlens_submit-0.16.5.dist-info → microlens_submit-0.17.0.dist-info}/METADATA +4 -4
- {microlens_submit-0.16.5.dist-info → microlens_submit-0.17.0.dist-info}/RECORD +13 -13
- {microlens_submit-0.16.5.dist-info → microlens_submit-0.17.0.dist-info}/WHEEL +0 -0
- {microlens_submit-0.16.5.dist-info → microlens_submit-0.17.0.dist-info}/entry_points.txt +0 -0
- {microlens_submit-0.16.5.dist-info → microlens_submit-0.17.0.dist-info}/licenses/LICENSE +0 -0
- {microlens_submit-0.16.5.dist-info → microlens_submit-0.17.0.dist-info}/top_level.txt +0 -0
microlens_submit/__init__.py
CHANGED
|
@@ -187,7 +187,31 @@ def add_solution(
|
|
|
187
187
|
physical_param: Optional[List[str]] = typer.Option(
|
|
188
188
|
None,
|
|
189
189
|
"--physical-param",
|
|
190
|
-
help=("Physical parameters (
|
|
190
|
+
help=("Physical parameters (Mtot, D_L, thetaE, etc.) " "derived from model parameters [ADVANCED]"),
|
|
191
|
+
),
|
|
192
|
+
physical_param_uncertainty: Optional[List[str]] = typer.Option(
|
|
193
|
+
None,
|
|
194
|
+
"--physical-param-uncertainty",
|
|
195
|
+
help=(
|
|
196
|
+
"Physical parameter uncertainties as key=value. "
|
|
197
|
+
"Can be single value (symmetric) or [lower,upper] (asymmetric) [ADVANCED]"
|
|
198
|
+
),
|
|
199
|
+
),
|
|
200
|
+
uncertainty_method: Optional[str] = typer.Option(
|
|
201
|
+
None,
|
|
202
|
+
"--uncertainty-method",
|
|
203
|
+
help=(
|
|
204
|
+
"Method used to derive uncertainties [ADVANCED]. "
|
|
205
|
+
"Options: mcmc_posterior, fisher_matrix, bootstrap, propagation, inference, literature, other"
|
|
206
|
+
),
|
|
207
|
+
),
|
|
208
|
+
confidence_level: Optional[float] = typer.Option(
|
|
209
|
+
None,
|
|
210
|
+
"--confidence-level",
|
|
211
|
+
help=(
|
|
212
|
+
"Confidence level for uncertainties (default: 0.68 = 1σ) [ADVANCED]. "
|
|
213
|
+
"Standard values: 0.68 (1σ), 0.95 (2σ), 0.997 (3σ)"
|
|
214
|
+
),
|
|
191
215
|
),
|
|
192
216
|
relative_probability: Optional[float] = typer.Option(
|
|
193
217
|
None,
|
|
@@ -281,6 +305,10 @@ def add_solution(
|
|
|
281
305
|
sol.limb_darkening_coeffs = _parse_pairs(limb_darkening_coeff)
|
|
282
306
|
sol.parameter_uncertainties = _parse_pairs(parameter_uncertainty) or uncertainties
|
|
283
307
|
sol.physical_parameters = _parse_pairs(physical_param)
|
|
308
|
+
sol.physical_parameter_uncertainties = _parse_pairs(physical_param_uncertainty)
|
|
309
|
+
sol.uncertainty_method = uncertainty_method
|
|
310
|
+
if confidence_level is not None:
|
|
311
|
+
sol.confidence_level = confidence_level
|
|
284
312
|
sol.log_likelihood = log_likelihood
|
|
285
313
|
sol.relative_probability = relative_probability
|
|
286
314
|
sol.n_data_points = n_data_points
|
|
@@ -12,6 +12,7 @@ from rich.table import Table
|
|
|
12
12
|
from microlens_submit.error_messages import enhance_validation_messages
|
|
13
13
|
from microlens_submit.text_symbols import symbol
|
|
14
14
|
from microlens_submit.utils import load
|
|
15
|
+
from microlens_submit.validate_parameters import count_model_parameters
|
|
15
16
|
|
|
16
17
|
console = Console()
|
|
17
18
|
|
|
@@ -196,13 +197,17 @@ def compare_solutions(
|
|
|
196
197
|
need_calc = [s for s in solutions if s.relative_probability is None]
|
|
197
198
|
if need_calc:
|
|
198
199
|
can_calc = all(
|
|
199
|
-
s.log_likelihood is not None
|
|
200
|
+
s.log_likelihood is not None
|
|
201
|
+
and s.n_data_points
|
|
202
|
+
and s.n_data_points > 0
|
|
203
|
+
and count_model_parameters(s.parameters) > 0
|
|
200
204
|
for s in need_calc
|
|
201
205
|
)
|
|
202
206
|
remaining = max(1.0 - provided_sum, 0.0)
|
|
203
207
|
if can_calc:
|
|
204
208
|
bic_vals = {
|
|
205
|
-
s.solution_id:
|
|
209
|
+
s.solution_id: count_model_parameters(s.parameters) * math.log(s.n_data_points)
|
|
210
|
+
- 2 * s.log_likelihood
|
|
206
211
|
for s in need_calc
|
|
207
212
|
}
|
|
208
213
|
bic_min = min(bic_vals.values())
|
|
@@ -219,7 +224,7 @@ def compare_solutions(
|
|
|
219
224
|
|
|
220
225
|
rows = []
|
|
221
226
|
for sol in solutions:
|
|
222
|
-
k =
|
|
227
|
+
k = count_model_parameters(sol.parameters)
|
|
223
228
|
bic = k * math.log(sol.n_data_points) - 2 * sol.log_likelihood
|
|
224
229
|
rp = sol.relative_probability if sol.relative_probability is not None else rel_prob_map.get(sol.solution_id)
|
|
225
230
|
rows.append(
|
|
@@ -134,7 +134,10 @@ class Solution(BaseModel):
|
|
|
134
134
|
limb_darkening_model: Optional[str] = None
|
|
135
135
|
limb_darkening_coeffs: Optional[dict] = None
|
|
136
136
|
parameter_uncertainties: Optional[dict] = None
|
|
137
|
+
uncertainty_method: Optional[str] = None
|
|
138
|
+
confidence_level: Optional[float] = 0.68
|
|
137
139
|
physical_parameters: Optional[dict] = None
|
|
140
|
+
physical_parameter_uncertainties: Optional[dict] = None
|
|
138
141
|
log_likelihood: Optional[float] = None
|
|
139
142
|
relative_probability: Optional[float] = None
|
|
140
143
|
n_data_points: Optional[int] = None
|
|
@@ -155,6 +158,7 @@ class Solution(BaseModel):
|
|
|
155
158
|
higher_order_effects = values.get("higher_order_effects", [])
|
|
156
159
|
bands = values.get("bands", [])
|
|
157
160
|
t_ref = values.get("t_ref")
|
|
161
|
+
limb_darkening_coeffs = values.get("limb_darkening_coeffs")
|
|
158
162
|
|
|
159
163
|
# Only check for totally broken objects (e.g., wrong types)
|
|
160
164
|
basic_errors = []
|
|
@@ -176,6 +180,7 @@ class Solution(BaseModel):
|
|
|
176
180
|
higher_order_effects=higher_order_effects,
|
|
177
181
|
bands=bands,
|
|
178
182
|
t_ref=t_ref,
|
|
183
|
+
limb_darkening_coeffs=limb_darkening_coeffs,
|
|
179
184
|
)
|
|
180
185
|
if validation_warnings:
|
|
181
186
|
warnings.warn(f"Solution created with potential issues: {'; '.join(validation_warnings)}", UserWarning)
|
|
@@ -388,6 +393,18 @@ class Solution(BaseModel):
|
|
|
388
393
|
)
|
|
389
394
|
messages.extend(consistency_messages)
|
|
390
395
|
|
|
396
|
+
# Check solution metadata (uncertainties, etc.)
|
|
397
|
+
from ..validate_parameters import validate_solution_metadata
|
|
398
|
+
|
|
399
|
+
metadata_messages = validate_solution_metadata(
|
|
400
|
+
parameter_uncertainties=self.parameter_uncertainties,
|
|
401
|
+
physical_parameters=self.physical_parameters,
|
|
402
|
+
physical_parameter_uncertainties=self.physical_parameter_uncertainties,
|
|
403
|
+
uncertainty_method=self.uncertainty_method,
|
|
404
|
+
confidence_level=self.confidence_level,
|
|
405
|
+
)
|
|
406
|
+
messages.extend(metadata_messages)
|
|
407
|
+
|
|
391
408
|
return messages
|
|
392
409
|
|
|
393
410
|
def _save(self, event_path: Path) -> None:
|
|
@@ -18,6 +18,7 @@ import psutil
|
|
|
18
18
|
from pydantic import BaseModel, Field
|
|
19
19
|
|
|
20
20
|
from ..text_symbols import symbol
|
|
21
|
+
from ..validate_parameters import count_model_parameters
|
|
21
22
|
from .event import Event
|
|
22
23
|
from .solution import Solution
|
|
23
24
|
|
|
@@ -500,14 +501,15 @@ class Submission(BaseModel):
|
|
|
500
501
|
s.log_likelihood is None
|
|
501
502
|
or s.n_data_points is None
|
|
502
503
|
or s.n_data_points <= 0
|
|
503
|
-
or
|
|
504
|
+
or count_model_parameters(s.parameters) == 0
|
|
504
505
|
):
|
|
505
506
|
can_calc = False
|
|
506
507
|
break
|
|
507
508
|
remaining = max(1.0 - provided_sum, 0.0)
|
|
508
509
|
if can_calc:
|
|
509
510
|
bic_vals = {
|
|
510
|
-
s.solution_id:
|
|
511
|
+
s.solution_id: count_model_parameters(s.parameters) * math.log(s.n_data_points)
|
|
512
|
+
- 2 * s.log_likelihood
|
|
511
513
|
for s in need_calc
|
|
512
514
|
}
|
|
513
515
|
bic_min = min(bic_vals.values())
|
|
@@ -38,21 +38,21 @@ Note:
|
|
|
38
38
|
from typing import Dict, List, Optional, Set
|
|
39
39
|
|
|
40
40
|
# Tier definitions with their associated event lists
|
|
41
|
+
# --- BEGIN AUTO-GENERATED: TIER_DEFINITIONS ---
|
|
41
42
|
TIER_DEFINITIONS = {
|
|
42
43
|
"beginner": {
|
|
43
44
|
"description": "Beginner challenge tier with limited event set",
|
|
44
45
|
"event_prefix": "rmdc26_",
|
|
45
|
-
"event_range": [0,
|
|
46
|
+
"event_range": [0, 253],
|
|
46
47
|
},
|
|
47
48
|
"experienced": {
|
|
48
49
|
"description": "Experienced challenge tier with full event set",
|
|
49
50
|
"event_prefix": "rmdc26_",
|
|
50
|
-
"event_range": [0,
|
|
51
|
+
"event_range": [0, 3000],
|
|
51
52
|
},
|
|
52
53
|
"test": {
|
|
53
54
|
"description": "Testing tier for development",
|
|
54
55
|
"event_list": [
|
|
55
|
-
# Add test events here
|
|
56
56
|
"evt",
|
|
57
57
|
"test-event",
|
|
58
58
|
"EVENT001",
|
|
@@ -68,17 +68,14 @@ TIER_DEFINITIONS = {
|
|
|
68
68
|
"description": "2018 test events tier",
|
|
69
69
|
"event_prefix": "ulwdc1_",
|
|
70
70
|
"event_range": [0, 293],
|
|
71
|
-
"event_list": [
|
|
72
|
-
# Add 2018 test events here
|
|
73
|
-
"2018-EVENT-001",
|
|
74
|
-
"2018-EVENT-002",
|
|
75
|
-
],
|
|
71
|
+
"event_list": ["2018-EVENT-001", "2018-EVENT-002"],
|
|
76
72
|
},
|
|
77
73
|
"None": {
|
|
78
74
|
"description": "No validation tier (skips event validation)",
|
|
79
|
-
"event_list": [],
|
|
75
|
+
"event_list": [],
|
|
80
76
|
},
|
|
81
77
|
}
|
|
78
|
+
# --- END AUTO-GENERATED: TIER_DEFINITIONS ---
|
|
82
79
|
|
|
83
80
|
# Cache for event lists to avoid repeated list creation
|
|
84
81
|
_EVENT_LIST_CACHE: Dict[str, Set[str]] = {}
|
|
@@ -60,43 +60,49 @@ Note:
|
|
|
60
60
|
import re
|
|
61
61
|
from typing import Any, Dict, List, Optional
|
|
62
62
|
|
|
63
|
+
# --- BEGIN AUTO-GENERATED: MODEL_DEFINITIONS ---
|
|
63
64
|
MODEL_DEFINITIONS = {
|
|
64
|
-
# Single Source, Single Lens (PSPL)
|
|
65
65
|
"1S1L": {
|
|
66
66
|
"description": "Point Source, Single Point Lens (standard microlensing)",
|
|
67
67
|
"required_params_core": ["t0", "u0", "tE"],
|
|
68
68
|
},
|
|
69
|
-
# Single Source, Binary Lens
|
|
70
69
|
"1S2L": {
|
|
71
70
|
"description": "Point Source, Binary Point Lens",
|
|
72
71
|
"required_params_core": ["t0", "u0", "tE", "s", "q", "alpha"],
|
|
73
72
|
},
|
|
74
|
-
# Binary Source, Single Lens
|
|
75
73
|
"2S1L": {
|
|
76
74
|
"description": "Binary Source, Single Point Lens",
|
|
77
|
-
"required_params_core": ["t0", "u0", "tE"
|
|
75
|
+
"required_params_core": ["t0", "u0", "tE", "t0_source2", "u0_source2", "flux_ratio"],
|
|
78
76
|
},
|
|
79
|
-
#
|
|
80
|
-
"
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
#
|
|
77
|
+
# '2S2L': {
|
|
78
|
+
# "description": 'Binary Source, Binary Point Lens',
|
|
79
|
+
# "required_params_core": ['t0', 'u0', 'tE', 's', 'q', 'alpha', 't0_source2', 'u0_source2', 'flux_ratio'],
|
|
80
|
+
# },
|
|
81
|
+
# '1S3L': {
|
|
82
|
+
# "description": 'Point Source, Triple Point Lens',
|
|
83
|
+
# "required_params_core": ['t0', 'u0', 'tE'],
|
|
84
|
+
# },
|
|
85
|
+
# '2S3L': {
|
|
86
|
+
# "description": 'Binary Source, Triple Point Lens',
|
|
87
|
+
# "required_params_core": ['t0', 'u0', 'tE', 't0_source2', 'u0_source2', 'flux_ratio'],
|
|
88
88
|
# },
|
|
89
|
-
#
|
|
90
|
-
# "description":
|
|
91
|
-
# "required_params_core": [
|
|
89
|
+
# '1S4L': {
|
|
90
|
+
# "description": 'Point Source, Quadruple Point Lens',
|
|
91
|
+
# "required_params_core": ['t0', 'u0', 'tE'],
|
|
92
92
|
# },
|
|
93
|
-
#
|
|
94
|
-
# "description":
|
|
95
|
-
# "required_params_core": [
|
|
93
|
+
# '2S4L': {
|
|
94
|
+
# "description": 'Binary Source, Quadruple Point Lens',
|
|
95
|
+
# "required_params_core": ['t0', 'u0', 'tE', 't0_source2', 'u0_source2', 'flux_ratio'],
|
|
96
96
|
# },
|
|
97
|
+
"other": {
|
|
98
|
+
"description": "Other or unknown model type",
|
|
99
|
+
"required_params_core": [],
|
|
100
|
+
},
|
|
97
101
|
}
|
|
102
|
+
# --- END AUTO-GENERATED: MODEL_DEFINITIONS ---
|
|
98
103
|
|
|
99
104
|
_FLUX_PARAM_RE = re.compile(r"^F(?P<band>\\d+)_S(?:[12])?$|^F(?P<band_b>\\d+)_B$")
|
|
105
|
+
_LD_PARAM_RE = re.compile(r"^u_(?P<band>\d+)$")
|
|
100
106
|
|
|
101
107
|
|
|
102
108
|
def _find_flux_params(parameters: Dict[str, Any]) -> List[str]:
|
|
@@ -104,6 +110,11 @@ def _find_flux_params(parameters: Dict[str, Any]) -> List[str]:
|
|
|
104
110
|
return [param for param in parameters.keys() if isinstance(param, str) and _FLUX_PARAM_RE.match(param)]
|
|
105
111
|
|
|
106
112
|
|
|
113
|
+
def _find_ld_params(parameters: Dict[str, Any]) -> List[str]:
|
|
114
|
+
"""Return a list of parameters that look like band-specific limb darkening terms."""
|
|
115
|
+
return [param for param in parameters.keys() if isinstance(param, str) and _LD_PARAM_RE.match(param)]
|
|
116
|
+
|
|
117
|
+
|
|
107
118
|
def _infer_bands_from_flux_params(flux_params: List[str]) -> List[str]:
|
|
108
119
|
"""Infer band identifiers from flux parameter names."""
|
|
109
120
|
bands = set()
|
|
@@ -117,14 +128,94 @@ def _infer_bands_from_flux_params(flux_params: List[str]) -> List[str]:
|
|
|
117
128
|
return sorted(bands)
|
|
118
129
|
|
|
119
130
|
|
|
131
|
+
# Metadata parameters that should NOT be counted in BIC calculations
|
|
132
|
+
# These are solution-level metadata, not model parameters
|
|
133
|
+
METADATA_PARAMETERS = {
|
|
134
|
+
"t_ref", # Reference time (metadata for time-dependent effects)
|
|
135
|
+
"limb_darkening_coeffs", # Fixed LD coefficients (not fitted)
|
|
136
|
+
"limb_darkening_model", # Name of LD model (not a parameter)
|
|
137
|
+
"bands", # List of photometric bands (not a parameter)
|
|
138
|
+
"used_astrometry", # Boolean flag (not a parameter)
|
|
139
|
+
"used_postage_stamps", # Boolean flag (not a parameter)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
# Physical parameters (derived, not fitted in the microlensing model)
|
|
143
|
+
PHYSICAL_PARAMETERS = {
|
|
144
|
+
"Mtot",
|
|
145
|
+
"M1",
|
|
146
|
+
"M2",
|
|
147
|
+
"M3",
|
|
148
|
+
"M4", # Masses
|
|
149
|
+
"D_L",
|
|
150
|
+
"D_S", # Distances
|
|
151
|
+
"thetaE", # Einstein radius
|
|
152
|
+
"piE",
|
|
153
|
+
"piE_N",
|
|
154
|
+
"piE_E",
|
|
155
|
+
"piE_parallel",
|
|
156
|
+
"piE_perpendicular",
|
|
157
|
+
"piE_l",
|
|
158
|
+
"piE_b", # Parallax
|
|
159
|
+
"mu_rel",
|
|
160
|
+
"mu_rel_N",
|
|
161
|
+
"mu_rel_E",
|
|
162
|
+
"mu_rel_l",
|
|
163
|
+
"mu_rel_b", # Proper motion
|
|
164
|
+
"phi", # Position angle
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def count_model_parameters(parameters: Dict[str, Any]) -> int:
|
|
169
|
+
"""
|
|
170
|
+
Count the number of actual model parameters for BIC calculation.
|
|
171
|
+
|
|
172
|
+
Excludes:
|
|
173
|
+
- Metadata parameters (t_ref, limb_darkening_coeffs, etc.)
|
|
174
|
+
- Physical parameters (Mtot, D_L, etc.) - these are derived, not fitted
|
|
175
|
+
|
|
176
|
+
Includes:
|
|
177
|
+
- Core model parameters (t0, u0, tE, s, q, alpha, etc.)
|
|
178
|
+
- Higher-order effect parameters (piEN, piEE, rho, etc.)
|
|
179
|
+
- Flux parameters (F0_S, F0_B, etc.)
|
|
180
|
+
- Fitted limb darkening parameters (u_0, u_1, etc.)
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
parameters: Dictionary of solution parameters
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
Number of model parameters for BIC calculation
|
|
187
|
+
|
|
188
|
+
Example:
|
|
189
|
+
>>> params = {
|
|
190
|
+
... 't0': 2459123.5, 'u0': 0.1, 'tE': 20.0,
|
|
191
|
+
... 'piEN': 0.1, 'piEE': 0.05,
|
|
192
|
+
... 'F0_S': 1000.0, 'F0_B': 500.0,
|
|
193
|
+
... 't_ref': 2459123.0, # metadata, not counted
|
|
194
|
+
... 'Mtot': 0.45, # physical parameter, not counted
|
|
195
|
+
... }
|
|
196
|
+
>>> count_model_parameters(params)
|
|
197
|
+
6
|
|
198
|
+
"""
|
|
199
|
+
count = 0
|
|
200
|
+
for param in parameters.keys():
|
|
201
|
+
# Skip metadata parameters
|
|
202
|
+
if param in METADATA_PARAMETERS:
|
|
203
|
+
continue
|
|
204
|
+
# Skip physical parameters (derived, not fitted)
|
|
205
|
+
if param in PHYSICAL_PARAMETERS:
|
|
206
|
+
continue
|
|
207
|
+
# Count everything else (core params, higher-order params, flux params, LD params)
|
|
208
|
+
count += 1
|
|
209
|
+
return count
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
# --- BEGIN AUTO-GENERATED: HIGHER_ORDER_EFFECT_DEFINITIONS ---
|
|
120
213
|
HIGHER_ORDER_EFFECT_DEFINITIONS = {
|
|
121
214
|
"parallax": {
|
|
122
|
-
"description": "Microlens parallax effect",
|
|
123
|
-
"requires_t_ref": True,
|
|
124
|
-
"required_higher_order_params": [
|
|
125
|
-
|
|
126
|
-
"piEE",
|
|
127
|
-
], # These are often part of the main parameters if fitted
|
|
215
|
+
"description": "Microlens parallax effect (annual parallax from Earth's orbital motion)",
|
|
216
|
+
"requires_t_ref": True,
|
|
217
|
+
"required_higher_order_params": ["piEN", "piEE"],
|
|
218
|
+
"optional_higher_order_params": ["t_ref"],
|
|
128
219
|
},
|
|
129
220
|
"finite-source": {
|
|
130
221
|
"description": "Finite source size effect",
|
|
@@ -132,64 +223,56 @@ HIGHER_ORDER_EFFECT_DEFINITIONS = {
|
|
|
132
223
|
"required_higher_order_params": ["rho"],
|
|
133
224
|
},
|
|
134
225
|
"lens-orbital-motion": {
|
|
135
|
-
"description": "Orbital motion of
|
|
226
|
+
"description": "Orbital motion of lens components",
|
|
136
227
|
"requires_t_ref": True,
|
|
137
228
|
"required_higher_order_params": ["dsdt", "dadt"],
|
|
138
|
-
"optional_higher_order_params": ["dzdt"
|
|
229
|
+
"optional_higher_order_params": ["dzdt", "t_ref", "d2sdt2", "d2adt2"],
|
|
139
230
|
},
|
|
140
231
|
"xallarap": {
|
|
141
|
-
"description": "Source orbital motion (xallarap)",
|
|
142
|
-
"requires_t_ref": True,
|
|
143
|
-
"required_higher_order_params": [
|
|
232
|
+
"description": "Source orbital motion (xallarap is 'parallax' spelled backwards)",
|
|
233
|
+
"requires_t_ref": True,
|
|
234
|
+
"required_higher_order_params": ["xiEN", "xiEE", "P_xi"],
|
|
235
|
+
"optional_higher_order_params": ["e_xi", "omega_xi", "i_xi"],
|
|
144
236
|
},
|
|
145
237
|
"gaussian-process": {
|
|
146
238
|
"description": "Gaussian process model for time-correlated noise",
|
|
147
|
-
"requires_t_ref": False,
|
|
148
|
-
"required_higher_order_params": [],
|
|
149
|
-
"optional_higher_order_params": [
|
|
150
|
-
"ln_K",
|
|
151
|
-
"ln_lambda",
|
|
152
|
-
"ln_period",
|
|
153
|
-
"ln_gamma",
|
|
154
|
-
], # Common GP params, or specific names like "amplitude", "timescale", "periodicity" etc.
|
|
239
|
+
"requires_t_ref": False,
|
|
240
|
+
"required_higher_order_params": [],
|
|
241
|
+
"optional_higher_order_params": ["ln_K", "ln_lambda", "ln_period", "ln_gamma", "ln_a", "ln_c"],
|
|
155
242
|
},
|
|
156
243
|
"stellar-rotation": {
|
|
157
244
|
"description": "Effect of stellar rotation on the light curve (e.g., spots)",
|
|
158
|
-
"requires_t_ref": False,
|
|
159
|
-
"required_higher_order_params": [],
|
|
160
|
-
|
|
161
|
-
# to be added here
|
|
162
|
-
"optional_higher_order_params": [
|
|
163
|
-
"v_rot_sin_i",
|
|
164
|
-
"epsilon",
|
|
165
|
-
], # Guessing common params: rotational velocity times sin(inclination),
|
|
166
|
-
# spot coverage
|
|
245
|
+
"requires_t_ref": False,
|
|
246
|
+
"required_higher_order_params": [],
|
|
247
|
+
"optional_higher_order_params": ["v_rot_sin_i", "epsilon"],
|
|
167
248
|
},
|
|
168
249
|
"fitted-limb-darkening": {
|
|
169
250
|
"description": "Limb darkening coefficients fitted as parameters",
|
|
170
251
|
"requires_t_ref": False,
|
|
171
|
-
"required_higher_order_params": [],
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
], # Common limb darkening coefficients (linear, quadratic, cubic, quartic)
|
|
179
|
-
},
|
|
180
|
-
# The "other" effect type is handled by allowing any other string in
|
|
181
|
-
# `higher_order_effects` list itself.
|
|
252
|
+
"required_higher_order_params": [],
|
|
253
|
+
},
|
|
254
|
+
"other": {
|
|
255
|
+
"description": "Custom higher-order effect",
|
|
256
|
+
"requires_t_ref": False,
|
|
257
|
+
"required_higher_order_params": [],
|
|
258
|
+
},
|
|
182
259
|
}
|
|
260
|
+
# --- END AUTO-GENERATED: HIGHER_ORDER_EFFECT_DEFINITIONS ---
|
|
183
261
|
|
|
184
262
|
# This dictionary defines properties/constraints for each known parameter
|
|
185
263
|
# (e.g., expected type, units, a more detailed description, corresponding
|
|
186
264
|
# uncertainty field name)
|
|
265
|
+
# --- BEGIN AUTO-GENERATED: PARAMETER_PROPERTIES ---
|
|
187
266
|
PARAMETER_PROPERTIES = {
|
|
188
267
|
# Core Microlensing Parameters
|
|
189
|
-
"t0": {
|
|
268
|
+
"t0": {
|
|
269
|
+
"type": "float",
|
|
270
|
+
"units": "HJD",
|
|
271
|
+
"description": "Time of closest approach",
|
|
272
|
+
},
|
|
190
273
|
"u0": {
|
|
191
274
|
"type": "float",
|
|
192
|
-
"units": "
|
|
275
|
+
"units": "θE",
|
|
193
276
|
"description": "Minimum impact parameter",
|
|
194
277
|
},
|
|
195
278
|
"tE": {
|
|
@@ -197,165 +280,248 @@ PARAMETER_PROPERTIES = {
|
|
|
197
280
|
"units": "days",
|
|
198
281
|
"description": "Einstein radius crossing time",
|
|
199
282
|
},
|
|
283
|
+
# Binary Lens Parameters
|
|
200
284
|
"s": {
|
|
201
285
|
"type": "float",
|
|
202
|
-
"units": "
|
|
286
|
+
"units": "θE",
|
|
203
287
|
"description": "Binary separation scaled by Einstein radius",
|
|
204
288
|
},
|
|
205
|
-
"q": {
|
|
289
|
+
"q": {
|
|
290
|
+
"type": "float",
|
|
291
|
+
"units": "dimensionless",
|
|
292
|
+
"description": "Mass ratio M2/M1",
|
|
293
|
+
},
|
|
206
294
|
"alpha": {
|
|
207
295
|
"type": "float",
|
|
208
296
|
"units": "rad",
|
|
209
297
|
"description": "Angle of source trajectory relative to binary axis",
|
|
210
298
|
},
|
|
211
|
-
#
|
|
299
|
+
# Binary_Source
|
|
300
|
+
"t0_source2": {
|
|
301
|
+
"type": "float",
|
|
302
|
+
"units": "BJD",
|
|
303
|
+
"description": "Time of closest approach for second source",
|
|
304
|
+
},
|
|
305
|
+
"u0_source2": {
|
|
306
|
+
"type": "float",
|
|
307
|
+
"units": "θE",
|
|
308
|
+
"description": "Minimum impact parameter for second source",
|
|
309
|
+
},
|
|
310
|
+
"flux_ratio": {
|
|
311
|
+
"type": "float",
|
|
312
|
+
"units": "dimensionless",
|
|
313
|
+
"description": "Flux ratio of second source to first source",
|
|
314
|
+
},
|
|
315
|
+
# Finite Source Parameters
|
|
212
316
|
"rho": {
|
|
213
317
|
"type": "float",
|
|
214
|
-
"units": "
|
|
215
|
-
"description": "Source radius scaled by Einstein radius
|
|
318
|
+
"units": "θE",
|
|
319
|
+
"description": "Source radius scaled by Einstein radius",
|
|
216
320
|
},
|
|
321
|
+
# Parallax Parameters
|
|
217
322
|
"piEN": {
|
|
218
323
|
"type": "float",
|
|
219
|
-
"units": "
|
|
220
|
-
"description": "Parallax vector component (North)
|
|
324
|
+
"units": "θE",
|
|
325
|
+
"description": "Parallax vector component (North)",
|
|
221
326
|
},
|
|
222
327
|
"piEE": {
|
|
223
328
|
"type": "float",
|
|
224
|
-
"units": "
|
|
225
|
-
"description": "Parallax vector component (East)
|
|
329
|
+
"units": "θE",
|
|
330
|
+
"description": "Parallax vector component (East)",
|
|
226
331
|
},
|
|
332
|
+
# Lens Orbital Motion Parameters
|
|
227
333
|
"dsdt": {
|
|
228
334
|
"type": "float",
|
|
229
|
-
"units": "
|
|
230
|
-
"description": "Rate of change of binary separation
|
|
335
|
+
"units": "θE/year",
|
|
336
|
+
"description": "Rate of change of binary separation",
|
|
231
337
|
},
|
|
232
338
|
"dadt": {
|
|
233
339
|
"type": "float",
|
|
234
340
|
"units": "rad/year",
|
|
235
|
-
"description": "Rate of change of binary
|
|
341
|
+
"description": "Rate of change of binary orientation",
|
|
236
342
|
},
|
|
237
343
|
"dzdt": {
|
|
238
344
|
"type": "float",
|
|
239
345
|
"units": "au/year",
|
|
240
|
-
"description": "Relative radial rate of change of lenses
|
|
241
|
-
},
|
|
242
|
-
#
|
|
243
|
-
|
|
244
|
-
"F0_S": {
|
|
346
|
+
"description": "Relative radial rate of change of lenses",
|
|
347
|
+
},
|
|
348
|
+
# Xallarap
|
|
349
|
+
"xiEN": {
|
|
245
350
|
"type": "float",
|
|
246
|
-
"units": "
|
|
247
|
-
"description": "
|
|
351
|
+
"units": "θE",
|
|
352
|
+
"description": "Xallarap vector component (North)",
|
|
248
353
|
},
|
|
249
|
-
"
|
|
354
|
+
"xiEE": {
|
|
250
355
|
"type": "float",
|
|
251
|
-
"units": "
|
|
252
|
-
"description": "
|
|
356
|
+
"units": "θE",
|
|
357
|
+
"description": "Xallarap vector component (East)",
|
|
253
358
|
},
|
|
254
|
-
"
|
|
359
|
+
"P_xi": {
|
|
255
360
|
"type": "float",
|
|
256
|
-
"units": "
|
|
257
|
-
"description": "
|
|
361
|
+
"units": "days",
|
|
362
|
+
"description": "Orbital period of the source companion",
|
|
258
363
|
},
|
|
259
|
-
"
|
|
364
|
+
"e_xi": {
|
|
260
365
|
"type": "float",
|
|
261
|
-
"units": "
|
|
262
|
-
"description": "
|
|
366
|
+
"units": "dimensionless",
|
|
367
|
+
"description": "Eccentricity of source orbit",
|
|
263
368
|
},
|
|
264
|
-
"
|
|
369
|
+
"omega_xi": {
|
|
265
370
|
"type": "float",
|
|
266
|
-
"units": "
|
|
267
|
-
"description": "
|
|
371
|
+
"units": "rad",
|
|
372
|
+
"description": "Argument of periapsis for source orbit",
|
|
268
373
|
},
|
|
269
|
-
"
|
|
374
|
+
"i_xi": {
|
|
270
375
|
"type": "float",
|
|
271
|
-
"units": "
|
|
272
|
-
"description": "
|
|
376
|
+
"units": "deg",
|
|
377
|
+
"description": "Inclination of source orbit",
|
|
273
378
|
},
|
|
274
|
-
#
|
|
275
|
-
"
|
|
379
|
+
# Gaussian Process Parameters
|
|
380
|
+
"ln_K": {
|
|
276
381
|
"type": "float",
|
|
277
|
-
"units": "
|
|
278
|
-
"description": "
|
|
382
|
+
"units": "mag²",
|
|
383
|
+
"description": "Log-amplitude of the GP kernel",
|
|
279
384
|
},
|
|
280
|
-
"
|
|
385
|
+
"ln_lambda": {
|
|
281
386
|
"type": "float",
|
|
282
|
-
"units": "
|
|
283
|
-
"description": "
|
|
387
|
+
"units": "days",
|
|
388
|
+
"description": "Log-lengthscale of the GP kernel",
|
|
284
389
|
},
|
|
285
|
-
"
|
|
390
|
+
"ln_period": {
|
|
286
391
|
"type": "float",
|
|
287
|
-
"units": "
|
|
288
|
-
"description": "
|
|
392
|
+
"units": "days",
|
|
393
|
+
"description": "Log-period of the GP kernel",
|
|
289
394
|
},
|
|
290
|
-
"
|
|
395
|
+
"ln_gamma": {
|
|
291
396
|
"type": "float",
|
|
292
|
-
"units": "
|
|
293
|
-
"description": "
|
|
397
|
+
"units": "dimensionless",
|
|
398
|
+
"description": "Log-smoothing parameter of the GP kernel",
|
|
294
399
|
},
|
|
295
|
-
|
|
400
|
+
# Stellar Rotation Parameters
|
|
401
|
+
"v_rot_sin_i": {
|
|
296
402
|
"type": "float",
|
|
297
|
-
"units": "
|
|
298
|
-
"description": "
|
|
403
|
+
"units": "km/s",
|
|
404
|
+
"description": "Rotational velocity times sin(inclination)",
|
|
299
405
|
},
|
|
300
|
-
"
|
|
406
|
+
"epsilon": {
|
|
301
407
|
"type": "float",
|
|
302
|
-
"units": "
|
|
303
|
-
"description": "
|
|
408
|
+
"units": "dimensionless",
|
|
409
|
+
"description": "Spot coverage/brightness parameter",
|
|
304
410
|
},
|
|
305
|
-
#
|
|
306
|
-
"
|
|
411
|
+
# Other
|
|
412
|
+
"flux_parameters": {
|
|
307
413
|
"type": "float",
|
|
308
|
-
"units": "
|
|
309
|
-
"description": "
|
|
414
|
+
"units": "",
|
|
415
|
+
"description": "",
|
|
310
416
|
},
|
|
311
|
-
|
|
417
|
+
# Derived Physical Parameters
|
|
418
|
+
"Mtot": {
|
|
312
419
|
"type": "float",
|
|
313
|
-
"units": "
|
|
314
|
-
"description": "
|
|
420
|
+
"units": "M_sun",
|
|
421
|
+
"description": "Total lens mass",
|
|
315
422
|
},
|
|
316
|
-
"
|
|
423
|
+
"M1": {
|
|
317
424
|
"type": "float",
|
|
318
|
-
"units": "
|
|
319
|
-
"description": "
|
|
425
|
+
"units": "M_sun",
|
|
426
|
+
"description": "Primary lens mass",
|
|
320
427
|
},
|
|
321
|
-
"
|
|
428
|
+
"M2": {
|
|
322
429
|
"type": "float",
|
|
323
|
-
"units": "
|
|
324
|
-
"description": "
|
|
325
|
-
},
|
|
326
|
-
|
|
327
|
-
"v_rot_sin_i": {
|
|
430
|
+
"units": "M_sun",
|
|
431
|
+
"description": "Secondary lens mass",
|
|
432
|
+
},
|
|
433
|
+
"M3": {
|
|
328
434
|
"type": "float",
|
|
329
|
-
"units": "
|
|
330
|
-
"description": "
|
|
435
|
+
"units": "M_sun",
|
|
436
|
+
"description": "Tertiary lens mass",
|
|
331
437
|
},
|
|
332
|
-
"
|
|
438
|
+
"M4": {
|
|
439
|
+
"type": "float",
|
|
440
|
+
"units": "M_sun",
|
|
441
|
+
"description": "Quaternary lens mass",
|
|
442
|
+
},
|
|
443
|
+
"D_L": {
|
|
444
|
+
"type": "float",
|
|
445
|
+
"units": "kpc",
|
|
446
|
+
"description": "Lens distance from observer",
|
|
447
|
+
},
|
|
448
|
+
"D_S": {
|
|
449
|
+
"type": "float",
|
|
450
|
+
"units": "kpc",
|
|
451
|
+
"description": "Source distance from observer",
|
|
452
|
+
},
|
|
453
|
+
"thetaE": {
|
|
454
|
+
"type": "float",
|
|
455
|
+
"units": "mas",
|
|
456
|
+
"description": "Angular Einstein radius",
|
|
457
|
+
},
|
|
458
|
+
"piE": {
|
|
459
|
+
"type": "float",
|
|
460
|
+
"units": "dimensionless",
|
|
461
|
+
"description": "Microlens parallax magnitude",
|
|
462
|
+
},
|
|
463
|
+
"piE_N": {
|
|
464
|
+
"type": "float",
|
|
465
|
+
"units": "dimensionless",
|
|
466
|
+
"description": "North component of microlens parallax vector",
|
|
467
|
+
},
|
|
468
|
+
"piE_E": {
|
|
469
|
+
"type": "float",
|
|
470
|
+
"units": "dimensionless",
|
|
471
|
+
"description": "East component of microlens parallax vector",
|
|
472
|
+
},
|
|
473
|
+
"piE_parallel": {
|
|
474
|
+
"type": "float",
|
|
475
|
+
"units": "dimensionless",
|
|
476
|
+
"description": "Component of parallax vector parallel to lens-source relative motion",
|
|
477
|
+
},
|
|
478
|
+
"piE_perpendicular": {
|
|
479
|
+
"type": "float",
|
|
480
|
+
"units": "dimensionless",
|
|
481
|
+
"description": "Component of parallax vector perpendicular to lens-source relative motion",
|
|
482
|
+
},
|
|
483
|
+
"piE_l": {
|
|
484
|
+
"type": "float",
|
|
485
|
+
"units": "dimensionless",
|
|
486
|
+
"description": "Galactic longitude component of microlens parallax vector",
|
|
487
|
+
},
|
|
488
|
+
"piE_b": {
|
|
489
|
+
"type": "float",
|
|
490
|
+
"units": "dimensionless",
|
|
491
|
+
"description": "Galactic latitude component of microlens parallax vector",
|
|
492
|
+
},
|
|
493
|
+
"mu_rel": {
|
|
333
494
|
"type": "float",
|
|
334
|
-
"units": "
|
|
335
|
-
"description": "
|
|
336
|
-
},
|
|
337
|
-
|
|
338
|
-
"u1": {
|
|
495
|
+
"units": "mas/yr",
|
|
496
|
+
"description": "Magnitude of the relative proper motion between lens and source",
|
|
497
|
+
},
|
|
498
|
+
"mu_rel_N": {
|
|
339
499
|
"type": "float",
|
|
340
|
-
"units": "
|
|
341
|
-
"description": "
|
|
500
|
+
"units": "mas/yr",
|
|
501
|
+
"description": "North component of the relative proper motion between lens and source",
|
|
342
502
|
},
|
|
343
|
-
"
|
|
503
|
+
"mu_rel_E": {
|
|
344
504
|
"type": "float",
|
|
345
|
-
"units": "
|
|
346
|
-
"description": "
|
|
505
|
+
"units": "mas/yr",
|
|
506
|
+
"description": "East component of the relative proper motion between lens and source",
|
|
347
507
|
},
|
|
348
|
-
"
|
|
508
|
+
"mu_rel_l": {
|
|
349
509
|
"type": "float",
|
|
350
|
-
"units": "
|
|
351
|
-
"description": "
|
|
510
|
+
"units": "mas/yr",
|
|
511
|
+
"description": "Galactic longitude component of the relative proper motion between lens and source",
|
|
352
512
|
},
|
|
353
|
-
"
|
|
513
|
+
"mu_rel_b": {
|
|
354
514
|
"type": "float",
|
|
355
|
-
"units": "
|
|
356
|
-
"description": "
|
|
515
|
+
"units": "mas/yr",
|
|
516
|
+
"description": "Galactic latitude component of the relative proper motion between lens and source",
|
|
517
|
+
},
|
|
518
|
+
"phi": {
|
|
519
|
+
"type": "float",
|
|
520
|
+
"units": "rad",
|
|
521
|
+
"description": "Angle of lens-source relative proper motion relative to ecliptic",
|
|
357
522
|
},
|
|
358
523
|
}
|
|
524
|
+
# --- END AUTO-GENERATED: PARAMETER_PROPERTIES ---
|
|
359
525
|
|
|
360
526
|
|
|
361
527
|
def get_required_flux_params(model_type: str, bands: List[str]) -> List[str]:
|
|
@@ -536,12 +702,23 @@ def check_solution_completeness(
|
|
|
536
702
|
f"{example_bands!r})."
|
|
537
703
|
)
|
|
538
704
|
|
|
705
|
+
ld_params = _find_ld_params(parameters)
|
|
706
|
+
if ld_params and not bands:
|
|
707
|
+
messages.append("Limb darkening parameters (u_{band}) provided but bands is empty.")
|
|
708
|
+
|
|
539
709
|
if bands:
|
|
540
710
|
required_flux_params = get_required_flux_params(model_type, bands)
|
|
541
711
|
for param in required_flux_params:
|
|
542
712
|
if param not in parameters:
|
|
543
713
|
messages.append(f"Missing required flux parameter '{param}' for bands " f"{bands}")
|
|
544
714
|
|
|
715
|
+
# If fitted-limb-darkening is active, check for LD params
|
|
716
|
+
if higher_order_effects and "fitted-limb-darkening" in higher_order_effects:
|
|
717
|
+
for band in bands:
|
|
718
|
+
expected_ld = f"u_{band}"
|
|
719
|
+
if expected_ld not in parameters:
|
|
720
|
+
messages.append(f"Missing required limb darkening parameter '{expected_ld}' for band '{band}'")
|
|
721
|
+
|
|
545
722
|
# Check for invalid parameters (not in any definition)
|
|
546
723
|
all_valid_params = set()
|
|
547
724
|
|
|
@@ -560,12 +737,23 @@ def check_solution_completeness(
|
|
|
560
737
|
if bands:
|
|
561
738
|
all_valid_params.update(get_required_flux_params(model_type, bands))
|
|
562
739
|
|
|
740
|
+
# Add allowed LD params if effect is active
|
|
741
|
+
if higher_order_effects and "fitted-limb-darkening" in higher_order_effects:
|
|
742
|
+
for band in bands:
|
|
743
|
+
all_valid_params.add(f"u_{band}")
|
|
744
|
+
|
|
563
745
|
# Check for invalid parameters
|
|
564
746
|
invalid_params = set(parameters.keys()) - all_valid_params
|
|
565
747
|
if flux_params and not bands:
|
|
566
748
|
invalid_params -= set(flux_params)
|
|
749
|
+
|
|
750
|
+
# Allow LD params if fitted-limb-darkening is active
|
|
751
|
+
# (even if not strictly in all_valid_params above if bands missing)
|
|
752
|
+
if higher_order_effects and "fitted-limb-darkening" in higher_order_effects:
|
|
753
|
+
invalid_params -= set(ld_params)
|
|
754
|
+
|
|
567
755
|
for param in invalid_params:
|
|
568
|
-
messages.append(f"Warning: Parameter '{param}' not recognized for model type
|
|
756
|
+
messages.append(f"Warning: Parameter '{param}' not recognized for model type '{model_type}'")
|
|
569
757
|
|
|
570
758
|
return messages
|
|
571
759
|
|
|
@@ -576,43 +764,7 @@ def validate_parameter_types(
|
|
|
576
764
|
) -> List[str]:
|
|
577
765
|
"""Validate parameter types and value ranges against expected types.
|
|
578
766
|
|
|
579
|
-
|
|
580
|
-
PARAMETER_PROPERTIES. Currently supports validation of float, int,
|
|
581
|
-
and string types. Parameters not defined in PARAMETER_PROPERTIES
|
|
582
|
-
are skipped (no validation performed).
|
|
583
|
-
|
|
584
|
-
Args:
|
|
585
|
-
parameters: Dictionary of model parameters with parameter names as keys.
|
|
586
|
-
model_type: The type of microlensing model (used for context in messages).
|
|
587
|
-
|
|
588
|
-
Returns:
|
|
589
|
-
List of validation messages. Empty list if all validations pass.
|
|
590
|
-
Messages indicate type mismatches for known parameters.
|
|
591
|
-
|
|
592
|
-
Example:
|
|
593
|
-
>>> # Valid parameters
|
|
594
|
-
>>> params = {"t0": 2459123.5, "u0": 0.1, "tE": 20.0}
|
|
595
|
-
>>> messages = validate_parameter_types(params, "1S1L")
|
|
596
|
-
>>> print(messages)
|
|
597
|
-
[]
|
|
598
|
-
|
|
599
|
-
>>> # Invalid type for t0
|
|
600
|
-
>>> params = {"t0": "2459123.5", "u0": 0.1, "tE": 20.0} # t0 is string
|
|
601
|
-
>>> messages = validate_parameter_types(params, "1S1L")
|
|
602
|
-
>>> print(messages)
|
|
603
|
-
["Parameter 't0' should be numeric, got str"]
|
|
604
|
-
|
|
605
|
-
>>> # Unknown parameter (no validation performed)
|
|
606
|
-
>>> params = {"t0": 2459123.5, "custom_param": "value"}
|
|
607
|
-
>>> messages = validate_parameter_types(params, "1S1L")
|
|
608
|
-
>>> print(messages)
|
|
609
|
-
[]
|
|
610
|
-
|
|
611
|
-
Note:
|
|
612
|
-
This function only validates parameters that are defined in
|
|
613
|
-
PARAMETER_PROPERTIES. Unknown parameters are ignored to allow
|
|
614
|
-
for custom parameters and future extensions. The validation
|
|
615
|
-
is currently limited to basic type checking (float, int, str).
|
|
767
|
+
... (omitted docstring for brevity) ...
|
|
616
768
|
"""
|
|
617
769
|
messages = []
|
|
618
770
|
|
|
@@ -622,15 +774,17 @@ def validate_parameter_types(
|
|
|
622
774
|
for param, value in parameters.items():
|
|
623
775
|
if param in PARAMETER_PROPERTIES:
|
|
624
776
|
prop = PARAMETER_PROPERTIES[param]
|
|
625
|
-
|
|
626
777
|
# Check type
|
|
627
778
|
expected_type = prop.get("type")
|
|
628
779
|
if expected_type == "float" and not isinstance(value, (int, float)):
|
|
629
|
-
messages.append(f"Parameter '{param}' should be numeric, got
|
|
780
|
+
messages.append(f"Parameter '{param}' should be numeric, got {type(value).__name__}")
|
|
630
781
|
elif expected_type == "int" and not isinstance(value, int):
|
|
631
|
-
messages.append(f"Parameter '{param}' should be integer, got
|
|
782
|
+
messages.append(f"Parameter '{param}' should be integer, got {type(value).__name__}")
|
|
632
783
|
elif expected_type == "str" and not isinstance(value, str):
|
|
633
|
-
messages.append(f"Parameter '{param}' should be string, got
|
|
784
|
+
messages.append(f"Parameter '{param}' should be string, got {type(value).__name__}")
|
|
785
|
+
elif _LD_PARAM_RE.match(param):
|
|
786
|
+
if not isinstance(value, (int, float)):
|
|
787
|
+
messages.append(f"Parameter '{param}' should be numeric, got {type(value).__name__}")
|
|
634
788
|
|
|
635
789
|
return messages
|
|
636
790
|
|
|
@@ -844,6 +998,123 @@ def validate_solution_consistency(
|
|
|
844
998
|
if s < 0.5 or s > 2.0:
|
|
845
999
|
messages.append("Warning: " "Separation (s) outside typical caustic crossing range " "(0.5-2.0)")
|
|
846
1000
|
|
|
1001
|
+
# Run physical parameter validation
|
|
1002
|
+
messages.extend(validate_physical_parameters(parameters))
|
|
1003
|
+
|
|
1004
|
+
return messages
|
|
1005
|
+
|
|
1006
|
+
|
|
1007
|
+
def validate_uncertainty_metadata(
|
|
1008
|
+
parameter_uncertainties: Optional[Dict[str, Any]],
|
|
1009
|
+
physical_parameter_uncertainties: Optional[Dict[str, Any]],
|
|
1010
|
+
uncertainty_method: Optional[str],
|
|
1011
|
+
confidence_level: Optional[float],
|
|
1012
|
+
) -> List[str]:
|
|
1013
|
+
"""Validate uncertainty metadata for completeness and consistency.
|
|
1014
|
+
|
|
1015
|
+
Provides recommendations (not requirements) for uncertainty reporting.
|
|
1016
|
+
"""
|
|
1017
|
+
warnings = []
|
|
1018
|
+
|
|
1019
|
+
# Check if uncertainties are provided without metadata
|
|
1020
|
+
has_param_unc = parameter_uncertainties is not None and len(parameter_uncertainties) > 0
|
|
1021
|
+
has_phys_unc = physical_parameter_uncertainties is not None and len(physical_parameter_uncertainties) > 0
|
|
1022
|
+
|
|
1023
|
+
if (has_param_unc or has_phys_unc) and not uncertainty_method:
|
|
1024
|
+
warnings.append(
|
|
1025
|
+
"Recommendation: Uncertainties provided without uncertainty_method. "
|
|
1026
|
+
"Consider adding --uncertainty-method to improve evaluation "
|
|
1027
|
+
"(options: mcmc_posterior, fisher_matrix, bootstrap, propagation, inference, literature, other)"
|
|
1028
|
+
)
|
|
1029
|
+
|
|
1030
|
+
# Validate confidence level if provided
|
|
1031
|
+
if confidence_level is not None:
|
|
1032
|
+
if not 0 < confidence_level < 1:
|
|
1033
|
+
warnings.append(f"Confidence level ({confidence_level}) should be between 0 and 1")
|
|
1034
|
+
elif confidence_level not in [0.68, 0.95, 0.997]:
|
|
1035
|
+
warnings.append(
|
|
1036
|
+
f"Unusual confidence_level: {confidence_level}. " "Standard values are 0.68 (1σ), 0.95 (2σ), 0.997 (3σ)"
|
|
1037
|
+
)
|
|
1038
|
+
|
|
1039
|
+
# Validate uncertainty method if provided
|
|
1040
|
+
valid_methods = ["mcmc_posterior", "fisher_matrix", "bootstrap", "propagation", "inference", "literature", "other"]
|
|
1041
|
+
if uncertainty_method and uncertainty_method not in valid_methods:
|
|
1042
|
+
warnings.append(
|
|
1043
|
+
f"Unknown uncertainty_method: '{uncertainty_method}'. " f"Valid options: {', '.join(valid_methods)}"
|
|
1044
|
+
)
|
|
1045
|
+
|
|
1046
|
+
# Recommend physical parameter uncertainties if physical parameters exist
|
|
1047
|
+
# (This is handled in validate_physical_parameters now)
|
|
1048
|
+
|
|
1049
|
+
return warnings
|
|
1050
|
+
|
|
1051
|
+
|
|
1052
|
+
def validate_physical_parameters(parameters: Dict[str, Any]) -> List[str]:
|
|
1053
|
+
"""Validate physical parameters for consistency and range.
|
|
1054
|
+
|
|
1055
|
+
Checks:
|
|
1056
|
+
- Mass consistency (Mtot vs sum of components)
|
|
1057
|
+
- Vector magnitude consistency
|
|
1058
|
+
- Distance limits and ordering (D_L < D_S)
|
|
1059
|
+
- Reasonable mass ranges (warn on unit confusion)
|
|
1060
|
+
"""
|
|
1061
|
+
messages = []
|
|
1062
|
+
|
|
1063
|
+
# 1. Mass Consistency
|
|
1064
|
+
# Check if Mtot matches sum of components (M1, M2, M3, M4)
|
|
1065
|
+
mass_components = []
|
|
1066
|
+
for k in ["M1", "M2", "M3", "M4"]:
|
|
1067
|
+
if k in parameters:
|
|
1068
|
+
mass_components.append(parameters[k])
|
|
1069
|
+
|
|
1070
|
+
if "Mtot" in parameters and mass_components:
|
|
1071
|
+
total_comp = sum(mass_components)
|
|
1072
|
+
# Allow 1% error or 1e-6 absolute
|
|
1073
|
+
if abs(parameters["Mtot"] - total_comp) > max(parameters["Mtot"] * 0.01, 1e-6):
|
|
1074
|
+
messages.append(f"Total mass Mtot ({parameters['Mtot']}) does not match sum of components ({total_comp})")
|
|
1075
|
+
|
|
1076
|
+
# 2. Vector Consistency
|
|
1077
|
+
# piE magnitude vs components (piE_N, piE_E)
|
|
1078
|
+
if "piE" in parameters and "piE_N" in parameters and "piE_E" in parameters:
|
|
1079
|
+
mag = (parameters["piE_N"] ** 2 + parameters["piE_E"] ** 2) ** 0.5
|
|
1080
|
+
if abs(parameters["piE"] - mag) > max(parameters["piE"] * 0.01, 1e-6):
|
|
1081
|
+
messages.append(
|
|
1082
|
+
f"piE magnitude ({parameters['piE']}) inconsistent with N/E components (calculated {mag:.4f})"
|
|
1083
|
+
)
|
|
1084
|
+
|
|
1085
|
+
# mu_rel magnitude vs components (mu_rel_N, mu_rel_E)
|
|
1086
|
+
if "mu_rel" in parameters and "mu_rel_N" in parameters and "mu_rel_E" in parameters:
|
|
1087
|
+
mag = (parameters["mu_rel_N"] ** 2 + parameters["mu_rel_E"] ** 2) ** 0.5
|
|
1088
|
+
if abs(parameters["mu_rel"] - mag) > max(parameters["mu_rel"] * 0.01, 1e-6):
|
|
1089
|
+
messages.append(
|
|
1090
|
+
f"mu_rel magnitude ({parameters['mu_rel']}) inconsistent with N/E components (calculated {mag:.4f})"
|
|
1091
|
+
)
|
|
1092
|
+
|
|
1093
|
+
# 3. Distance Checks
|
|
1094
|
+
if "D_L" in parameters and parameters["D_L"] > 25.0:
|
|
1095
|
+
messages.append(f"Warning: Lens distance D_L ({parameters['D_L']} kpc) is unusually large (> 25 kpc)")
|
|
1096
|
+
|
|
1097
|
+
if "D_S" in parameters and parameters["D_S"] > 25.0:
|
|
1098
|
+
messages.append(f"Warning: Source distance D_S ({parameters['D_S']} kpc) is unusually large (> 25 kpc)")
|
|
1099
|
+
|
|
1100
|
+
if "D_L" in parameters and "D_S" in parameters:
|
|
1101
|
+
if parameters["D_L"] >= parameters["D_S"]:
|
|
1102
|
+
messages.append(
|
|
1103
|
+
f"Lens distance D_L ({parameters['D_L']}) must be smaller "
|
|
1104
|
+
f"than source distance D_S ({parameters['D_S']})"
|
|
1105
|
+
)
|
|
1106
|
+
|
|
1107
|
+
# 4. Mass Magnitude Warnings
|
|
1108
|
+
# Warn if any mass component is > 20 M_sun (possible unit confusion with Jupiter masses or unreasonable value)
|
|
1109
|
+
# Jupiter mass is ~0.001 Solar Mass (so 1 M_J ~ 0.001 M_S).
|
|
1110
|
+
# If they enter '1' meaning M_J, they get 1 M_S (reasonable).
|
|
1111
|
+
# If they enter '1000' meaning M_J (approx 1 M_S), they get 1000 M_S (unreasonable).
|
|
1112
|
+
for m_key in ["Mtot", "M1", "M2", "M3", "M4"]:
|
|
1113
|
+
if m_key in parameters and parameters[m_key] > 20.0:
|
|
1114
|
+
messages.append(
|
|
1115
|
+
f"Warning: {m_key} ({parameters[m_key]} M_sun) is very large. Check units (should be Solar masses)."
|
|
1116
|
+
)
|
|
1117
|
+
|
|
847
1118
|
return messages
|
|
848
1119
|
|
|
849
1120
|
|
|
@@ -853,6 +1124,7 @@ def validate_solution_rigorously(
|
|
|
853
1124
|
higher_order_effects: Optional[List[str]] = None,
|
|
854
1125
|
bands: Optional[List[str]] = None,
|
|
855
1126
|
t_ref: Optional[float] = None,
|
|
1127
|
+
limb_darkening_coeffs: Optional[Dict[str, List[float]]] = None,
|
|
856
1128
|
) -> List[str]:
|
|
857
1129
|
"""Extremely rigorous validation of solution parameters.
|
|
858
1130
|
|
|
@@ -863,6 +1135,7 @@ def validate_solution_rigorously(
|
|
|
863
1135
|
- bands must be a list of strings
|
|
864
1136
|
- All required flux parameters must be present for each band
|
|
865
1137
|
- Only "other" model types or effects can have unknown parameters
|
|
1138
|
+
- If limb_darkening_coeffs is provided, it must match bands
|
|
866
1139
|
|
|
867
1140
|
Args:
|
|
868
1141
|
model_type: The type of microlensing model
|
|
@@ -870,6 +1143,7 @@ def validate_solution_rigorously(
|
|
|
870
1143
|
higher_order_effects: List of higher-order effects
|
|
871
1144
|
bands: List of photometric bands
|
|
872
1145
|
t_ref: Reference time for time-dependent effects
|
|
1146
|
+
limb_darkening_coeffs: Dictionary of fixed limb darkening coefficients
|
|
873
1147
|
|
|
874
1148
|
Returns:
|
|
875
1149
|
List of validation error messages. Empty list if all validations pass.
|
|
@@ -890,6 +1164,28 @@ def validate_solution_rigorously(
|
|
|
890
1164
|
if not isinstance(band, str):
|
|
891
1165
|
messages.append(f"band {i} must be a string, got {type(band).__name__}")
|
|
892
1166
|
|
|
1167
|
+
# Validate limb_darkening_coeffs if provided
|
|
1168
|
+
if limb_darkening_coeffs:
|
|
1169
|
+
if not isinstance(limb_darkening_coeffs, dict):
|
|
1170
|
+
messages.append(f"limb_darkening_coeffs must be a dict, got {type(limb_darkening_coeffs).__name__}")
|
|
1171
|
+
elif bands:
|
|
1172
|
+
# Check coverage of bands if bands are specified
|
|
1173
|
+
missing_ld_bands = [b for b in bands if b not in limb_darkening_coeffs]
|
|
1174
|
+
if missing_ld_bands:
|
|
1175
|
+
messages.append(f"limb_darkening_coeffs missing bands: {missing_ld_bands}")
|
|
1176
|
+
|
|
1177
|
+
# Check for extra bands (warning)
|
|
1178
|
+
extra_ld_bands = [b for b in limb_darkening_coeffs if b not in bands]
|
|
1179
|
+
if extra_ld_bands:
|
|
1180
|
+
messages.append(f"limb_darkening_coeffs contains bands not in solution.bands: {extra_ld_bands}")
|
|
1181
|
+
|
|
1182
|
+
# Validate structure
|
|
1183
|
+
for band, coeffs in limb_darkening_coeffs.items():
|
|
1184
|
+
if not isinstance(coeffs, list):
|
|
1185
|
+
messages.append(f"limb_darkening_coeffs[{band!r}] must be a list of floats")
|
|
1186
|
+
elif not all(isinstance(c, (int, float)) for c in coeffs):
|
|
1187
|
+
messages.append(f"limb_darkening_coeffs[{band!r}] must contain only numeric values")
|
|
1188
|
+
|
|
893
1189
|
flux_params = _find_flux_params(parameters)
|
|
894
1190
|
if flux_params and not bands:
|
|
895
1191
|
inferred_bands = _infer_bands_from_flux_params(flux_params)
|
|
@@ -980,4 +1276,40 @@ def validate_solution_rigorously(
|
|
|
980
1276
|
if missing_flux:
|
|
981
1277
|
messages.append(f"Missing required flux parameters for bands {bands}: {missing_flux}")
|
|
982
1278
|
|
|
1279
|
+
# 10. Validate physical parameters
|
|
1280
|
+
physical_messages = validate_physical_parameters(parameters)
|
|
1281
|
+
messages.extend(physical_messages)
|
|
1282
|
+
|
|
1283
|
+
return messages
|
|
1284
|
+
|
|
1285
|
+
|
|
1286
|
+
def validate_solution_metadata(
|
|
1287
|
+
parameter_uncertainties: Optional[Dict[str, Any]] = None,
|
|
1288
|
+
physical_parameters: Optional[Dict[str, Any]] = None,
|
|
1289
|
+
physical_parameter_uncertainties: Optional[Dict[str, Any]] = None,
|
|
1290
|
+
uncertainty_method: Optional[str] = None,
|
|
1291
|
+
confidence_level: Optional[float] = None,
|
|
1292
|
+
) -> List[str]:
|
|
1293
|
+
"""Validate solution metadata including uncertainties.
|
|
1294
|
+
|
|
1295
|
+
This is a convenience wrapper that calls all metadata validators.
|
|
1296
|
+
"""
|
|
1297
|
+
messages = []
|
|
1298
|
+
|
|
1299
|
+
# Validate uncertainty metadata
|
|
1300
|
+
unc_messages = validate_uncertainty_metadata(
|
|
1301
|
+
parameter_uncertainties,
|
|
1302
|
+
physical_parameter_uncertainties,
|
|
1303
|
+
uncertainty_method,
|
|
1304
|
+
confidence_level,
|
|
1305
|
+
)
|
|
1306
|
+
messages.extend(unc_messages)
|
|
1307
|
+
|
|
1308
|
+
# Recommend physical parameter uncertainties if physical parameters provided
|
|
1309
|
+
if physical_parameters and not physical_parameter_uncertainties:
|
|
1310
|
+
messages.append(
|
|
1311
|
+
"Recommendation: Physical parameters provided without uncertainties. "
|
|
1312
|
+
"Consider adding --physical-param-uncertainty for better evaluation."
|
|
1313
|
+
)
|
|
1314
|
+
|
|
983
1315
|
return messages
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: microlens-submit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.17.0
|
|
4
4
|
Summary: A tool for managing and submitting microlensing solutions
|
|
5
5
|
Home-page: https://github.com/AmberLee2427/microlens-submit
|
|
6
6
|
Author: Amber Malpas
|
|
@@ -210,15 +210,15 @@ import this file directly.
|
|
|
210
210
|
|
|
211
211
|
Bibtex:
|
|
212
212
|
```
|
|
213
|
-
@software{
|
|
213
|
+
@software{malpas_2025_18488304,
|
|
214
214
|
author = {Malpas, Amber},
|
|
215
215
|
title = {microlens-submit},
|
|
216
216
|
month = oct,
|
|
217
217
|
year = 2025,
|
|
218
218
|
publisher = {Zenodo},
|
|
219
219
|
version = {v0.16.3},
|
|
220
|
-
doi = {10.5281/zenodo.
|
|
221
|
-
url = {https://doi.org/10.5281/zenodo.
|
|
220
|
+
doi = {10.5281/zenodo.18488304},
|
|
221
|
+
url = {https://doi.org/10.5281/zenodo.18488304},
|
|
222
222
|
}
|
|
223
223
|
```
|
|
224
224
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
microlens_submit/__init__.py,sha256=
|
|
1
|
+
microlens_submit/__init__.py,sha256=uhBoW3tMwuP8KDUYsxCKJ1pLdZ9w-5YodyoLTt7p8bk,399
|
|
2
2
|
microlens_submit/error_messages.py,sha256=8Wzx1NQiAF7hUOjlt-uGhtPC7akknv_PDkokoxhWquk,10411
|
|
3
3
|
microlens_submit/text_symbols.py,sha256=PbQSkqF_FwTBs45TkUL0zZl74IYDz7L4xVxy8eKxQsU,3146
|
|
4
|
-
microlens_submit/tier_validation.py,sha256=
|
|
4
|
+
microlens_submit/tier_validation.py,sha256=_t6oRuEx00TLwWxTRMNdcjDJQI9o8E1l8lcJnZC_cKY,6601
|
|
5
5
|
microlens_submit/utils.py,sha256=0VwCgFOAD4xoNpvY-k15axrb667cXaspzuKrZCGLJ-w,14676
|
|
6
|
-
microlens_submit/validate_parameters.py,sha256=
|
|
6
|
+
microlens_submit/validate_parameters.py,sha256=3luau3pLIK9IOk03Xcq7egeMu44mzm1vlcJwA7UYAnY,51475
|
|
7
7
|
microlens_submit/assets/github-desktop_logo.png,sha256=pb4rallKrYQPHt6eC0TmJe_UyyMtf1IrP8_OWK19nH8,479821
|
|
8
8
|
microlens_submit/assets/rges-pit_logo.png,sha256=45AJypXCymvt3lMeK7MHt1SBhwPpnKCMj6S000Cejtc,537645
|
|
9
9
|
microlens_submit/cli/__init__.py,sha256=u4ZgVOzUe_gf89FBhY61XWjcfK4oxXCStabYTjBuRRo,82
|
|
@@ -13,8 +13,8 @@ microlens_submit/cli/commands/__init__.py,sha256=rzIgY7T2Bz4Lhzts_RiWeoBbMoCuxOD
|
|
|
13
13
|
microlens_submit/cli/commands/dossier.py,sha256=6gRJNzUgr29YmYJRcUj9aoiRhjb1r9Uy4dip6z2LaHI,5100
|
|
14
14
|
microlens_submit/cli/commands/export.py,sha256=ojG2KkOcl90eA0tse6hqy74fHc-YX0o0alzAAbpzJew,8513
|
|
15
15
|
microlens_submit/cli/commands/init.py,sha256=tpO8YlWZLJmo4PuqQTKHXzvniIyWl7WXxUOURp_yfn4,7425
|
|
16
|
-
microlens_submit/cli/commands/solutions.py,sha256=
|
|
17
|
-
microlens_submit/cli/commands/validation.py,sha256=
|
|
16
|
+
microlens_submit/cli/commands/solutions.py,sha256=XjQtG6V2EA1F-3Aw1G7FtVI9-1LwRkG9Tzry03Yebzg,33248
|
|
17
|
+
microlens_submit/cli/commands/validation.py,sha256=Z6C-2eQzC7mSYqyOTvUY-CtGhsklNnjTxG05iingaos,9225
|
|
18
18
|
microlens_submit/dossier/__init__.py,sha256=INAacbrY0Wi5ueH8c7b156bGzelyUFcynbE7_YRiku0,1948
|
|
19
19
|
microlens_submit/dossier/dashboard.py,sha256=4OvTUCxIC4LbAqKwimIFhi65fNo5MMJswiQ5OWtyWFA,19907
|
|
20
20
|
microlens_submit/dossier/event_page.py,sha256=7740o3tpW9Urv7GSzYdp2TiphvDi6U7XnjlLZYipvLw,14878
|
|
@@ -23,11 +23,11 @@ microlens_submit/dossier/solution_page.py,sha256=-5kgkOZ9ziNRNAFpVeT_9-6aCcQRL4v
|
|
|
23
23
|
microlens_submit/dossier/utils.py,sha256=-DbWByBMsEeQZ-eUyRT74O_3lakE1vHKUD62jPj1_t4,5839
|
|
24
24
|
microlens_submit/models/__init__.py,sha256=1sHFjAWyFtGgQBRSo8lBYiPzToo4tIoHP3uBjtgJSPY,861
|
|
25
25
|
microlens_submit/models/event.py,sha256=ifQqE7d7PJTTI9lGylwWV3EGxgyyNGiJtHbm_DLmuys,17105
|
|
26
|
-
microlens_submit/models/solution.py,sha256=
|
|
27
|
-
microlens_submit/models/submission.py,sha256=
|
|
28
|
-
microlens_submit-0.
|
|
29
|
-
microlens_submit-0.
|
|
30
|
-
microlens_submit-0.
|
|
31
|
-
microlens_submit-0.
|
|
32
|
-
microlens_submit-0.
|
|
33
|
-
microlens_submit-0.
|
|
26
|
+
microlens_submit/models/solution.py,sha256=Mk6cxmWCykWiV6jvZGbLsXIoAxsMY_iAj_3kuXWWLGw,24464
|
|
27
|
+
microlens_submit/models/submission.py,sha256=i16Wd5irnVI9thjrDjXP0QG7-qt-IKSbYMCGDfvwRok,28068
|
|
28
|
+
microlens_submit-0.17.0.dist-info/licenses/LICENSE,sha256=cy1qkVR-kGxD6FXVsparmU2vHJXYeoyAAHv6SgT67sw,1069
|
|
29
|
+
microlens_submit-0.17.0.dist-info/METADATA,sha256=ioshF71XBRmmKyC-wQnEDhzfvAXCuhNVSqnr1QVcMzY,10673
|
|
30
|
+
microlens_submit-0.17.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
31
|
+
microlens_submit-0.17.0.dist-info/entry_points.txt,sha256=kA85yhxYrpQnUvVZCRS2giz52gaf1ZOfZFjY4RHIZ2s,62
|
|
32
|
+
microlens_submit-0.17.0.dist-info/top_level.txt,sha256=uJ9_bADYRySlhEpP-8vTm90ZLV2SrKEzutAaRx8WF0k,17
|
|
33
|
+
microlens_submit-0.17.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|