microlens-submit 0.16.4__tar.gz → 0.17.0__tar.gz
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-0.16.4 → microlens_submit-0.17.0}/CHANGELOG.md +36 -0
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/CITATION.cff +2 -2
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/PKG-INFO +8 -4
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/README.md +7 -3
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/__init__.py +1 -1
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/cli/commands/solutions.py +29 -1
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/cli/commands/validation.py +8 -3
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/dossier/event_page.py +20 -4
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/dossier/full_report.py +42 -3
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/dossier/solution_page.py +44 -5
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/dossier/utils.py +63 -0
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/models/solution.py +17 -0
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/models/submission.py +4 -2
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/tier_validation.py +6 -9
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/validate_parameters.py +522 -190
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit.egg-info/PKG-INFO +8 -4
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit.egg-info/SOURCES.txt +2 -0
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/pyproject.toml +1 -1
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/setup.py +1 -1
- microlens_submit-0.17.0/tests/test_bic_calculation.py +235 -0
- microlens_submit-0.17.0/tests/test_physical_validation.py +55 -0
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/tests/test_tier_validation.py +2 -2
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/LICENSE +0 -0
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/MANIFEST.in +0 -0
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/assets/github-desktop_logo.png +0 -0
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/assets/rges-pit_logo.png +0 -0
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/cli/__init__.py +0 -0
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/cli/__main__.py +0 -0
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/cli/commands/__init__.py +0 -0
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/cli/commands/dossier.py +0 -0
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/cli/commands/export.py +0 -0
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/cli/commands/init.py +0 -0
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/cli/main.py +0 -0
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/dossier/__init__.py +0 -0
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/dossier/dashboard.py +0 -0
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/error_messages.py +0 -0
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/models/__init__.py +0 -0
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/models/event.py +0 -0
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/text_symbols.py +0 -0
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/utils.py +0 -0
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit.egg-info/dependency_links.txt +0 -0
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit.egg-info/entry_points.txt +0 -0
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit.egg-info/requires.txt +0 -0
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit.egg-info/top_level.txt +0 -0
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/setup.cfg +0 -0
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/tests/test_api.py +0 -0
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/tests/test_cli.py +0 -0
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/tests/test_dossier_generation.py +0 -0
- {microlens_submit-0.16.4 → microlens_submit-0.17.0}/tests/test_dossier_pages.py +0 -0
|
@@ -5,6 +5,42 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.17.0] - 2026-02-04
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Physical Parameters**: Added 24 physical parameters (Mtot, M1-M4, D_L, D_S, thetaE, piE components, mu_rel components, phi) to parameter spec
|
|
12
|
+
- **Physical Parameter Validation**: Comprehensive validation including mass consistency checks, vector magnitude verification, distance constraints, and unit confusion warnings
|
|
13
|
+
- **Uncertainty Metadata**: Added three new Solution fields:
|
|
14
|
+
- `uncertainty_method`: Method used to derive uncertainties (mcmc_posterior, fisher_matrix, bootstrap, propagation, inference, literature, other)
|
|
15
|
+
- `confidence_level`: Confidence level for uncertainties (default: 0.68 for 1-sigma)
|
|
16
|
+
- `physical_parameter_uncertainties`: Uncertainties for physical parameters (symmetric or asymmetric)
|
|
17
|
+
- **CLI Options**: Added `--physical-param-uncertainty`, `--uncertainty-method`, and `--confidence-level` options to `add-solution` command
|
|
18
|
+
- **Tests**: Added 3 new comprehensive BIC calculation tests verifying parameter counting
|
|
19
|
+
- **Version Management**: Enhanced `bump_version.py` to update and validate `parameter_spec.yaml` (includes drift check)
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- **BIC Calculation Bug**: Fixed critical bug where BIC calculation counted ALL parameters including metadata (t_ref, limb_darkening_coeffs) and physical parameters (Mtot, D_L, etc.) as "free parameters". Now correctly counts only fitted model parameters using new `count_model_parameters()` function. This affects relative probability calculations during export and solution comparison.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
- Physical parameters now validated with `validate_physical_parameters()` automatically when present
|
|
26
|
+
- Solution metadata validation now includes uncertainty metadata checks
|
|
27
|
+
- BIC calculation in `submission.py` and `validation.py` now uses `count_model_parameters()` instead of `len(s.parameters)`
|
|
28
|
+
|
|
29
|
+
### Documentation
|
|
30
|
+
- Added comprehensive examples of physical parameters and uncertainty metadata to tutorial and usage examples
|
|
31
|
+
- Tutorial emphasizes uncertainties are **optional but strongly recommended** for evaluation readiness
|
|
32
|
+
- Created `PHYSICAL_PARAMS_SUMMARY.md` with complete implementation guide
|
|
33
|
+
|
|
34
|
+
## [Unreleased]
|
|
35
|
+
|
|
36
|
+
## [0.16.5] - 2026-02-04
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
- Dossier assets are now copied into the dossier output and linked with URL-safe relative paths,
|
|
40
|
+
improving image loading in Roman Research Nexus and similar environments.
|
|
41
|
+
- Event data, plots, and posteriors now resolve reliably when paths include spaces or nested folders.
|
|
42
|
+
|
|
43
|
+
|
|
8
44
|
## [0.16.4] - 2026-01-14
|
|
9
45
|
|
|
10
46
|
### Added
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
cff-version: 1.2.0
|
|
2
2
|
message: "If you use microlens-submit, please cite it as below."
|
|
3
3
|
title: "microlens-submit"
|
|
4
|
-
version: "0.
|
|
4
|
+
version: "0.17.0"
|
|
5
5
|
authors:
|
|
6
6
|
- family-names: Malpas
|
|
7
7
|
given-names: Amber
|
|
8
8
|
url: "https://github.com/AmberLee2427/microlens-submit"
|
|
9
|
-
doi: "10.5281/zenodo.
|
|
9
|
+
doi: "10.5281/zenodo.18488304"
|
|
@@ -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
|
|
@@ -95,6 +95,10 @@ The CLI is the recommended way to interact with your submission project.
|
|
|
95
95
|
|
|
96
96
|
You can pass ``--no-color`` to any command if your terminal does not support ANSI colors.
|
|
97
97
|
|
|
98
|
+
**Windows note:** If ``microlens-submit`` is not found after ``pip install``, your Python Scripts folder is likely missing from PATH.
|
|
99
|
+
Try ``py -m pip install microlens-submit`` and run ``py -m microlens_submit.cli --help``, or add the Scripts path shown by
|
|
100
|
+
``py -m pip show -f microlens-submit`` to PATH.
|
|
101
|
+
|
|
98
102
|
1. Initialize your project:
|
|
99
103
|
|
|
100
104
|
```bash
|
|
@@ -206,15 +210,15 @@ import this file directly.
|
|
|
206
210
|
|
|
207
211
|
Bibtex:
|
|
208
212
|
```
|
|
209
|
-
@software{
|
|
213
|
+
@software{malpas_2025_18488304,
|
|
210
214
|
author = {Malpas, Amber},
|
|
211
215
|
title = {microlens-submit},
|
|
212
216
|
month = oct,
|
|
213
217
|
year = 2025,
|
|
214
218
|
publisher = {Zenodo},
|
|
215
219
|
version = {v0.16.3},
|
|
216
|
-
doi = {10.5281/zenodo.
|
|
217
|
-
url = {https://doi.org/10.5281/zenodo.
|
|
220
|
+
doi = {10.5281/zenodo.18488304},
|
|
221
|
+
url = {https://doi.org/10.5281/zenodo.18488304},
|
|
218
222
|
}
|
|
219
223
|
```
|
|
220
224
|
|
|
@@ -45,6 +45,10 @@ The CLI is the recommended way to interact with your submission project.
|
|
|
45
45
|
|
|
46
46
|
You can pass ``--no-color`` to any command if your terminal does not support ANSI colors.
|
|
47
47
|
|
|
48
|
+
**Windows note:** If ``microlens-submit`` is not found after ``pip install``, your Python Scripts folder is likely missing from PATH.
|
|
49
|
+
Try ``py -m pip install microlens-submit`` and run ``py -m microlens_submit.cli --help``, or add the Scripts path shown by
|
|
50
|
+
``py -m pip show -f microlens-submit`` to PATH.
|
|
51
|
+
|
|
48
52
|
1. Initialize your project:
|
|
49
53
|
|
|
50
54
|
```bash
|
|
@@ -156,15 +160,15 @@ import this file directly.
|
|
|
156
160
|
|
|
157
161
|
Bibtex:
|
|
158
162
|
```
|
|
159
|
-
@software{
|
|
163
|
+
@software{malpas_2025_18488304,
|
|
160
164
|
author = {Malpas, Amber},
|
|
161
165
|
title = {microlens-submit},
|
|
162
166
|
month = oct,
|
|
163
167
|
year = 2025,
|
|
164
168
|
publisher = {Zenodo},
|
|
165
169
|
version = {v0.16.3},
|
|
166
|
-
doi = {10.5281/zenodo.
|
|
167
|
-
url = {https://doi.org/10.5281/zenodo.
|
|
170
|
+
doi = {10.5281/zenodo.18488304},
|
|
171
|
+
url = {https://doi.org/10.5281/zenodo.18488304},
|
|
168
172
|
}
|
|
169
173
|
```
|
|
170
174
|
|
{microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/cli/commands/solutions.py
RENAMED
|
@@ -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
|
{microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/cli/commands/validation.py
RENAMED
|
@@ -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(
|
|
@@ -12,6 +12,7 @@ from pathlib import Path
|
|
|
12
12
|
from .. import __version__
|
|
13
13
|
from ..models import Event, Submission
|
|
14
14
|
from .solution_page import generate_solution_page
|
|
15
|
+
from .utils import resolve_dossier_asset_path
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
def generate_event_page(event: Event, submission: Submission, output_dir: Path) -> None:
|
|
@@ -50,7 +51,17 @@ def generate_event_page(event: Event, submission: Submission, output_dir: Path)
|
|
|
50
51
|
to individual solution pages.
|
|
51
52
|
"""
|
|
52
53
|
# Prepare output directory (already created)
|
|
53
|
-
|
|
54
|
+
project_root = Path(submission.project_path)
|
|
55
|
+
event_data_link = ""
|
|
56
|
+
if hasattr(event, "event_data_path") and event.event_data_path:
|
|
57
|
+
event_data_link = resolve_dossier_asset_path(
|
|
58
|
+
event.event_data_path,
|
|
59
|
+
project_root,
|
|
60
|
+
output_dir,
|
|
61
|
+
subdir="event-data",
|
|
62
|
+
prefix=f"{event.event_id}_event_data",
|
|
63
|
+
)
|
|
64
|
+
html = _generate_event_page_content(event, submission, event_data_link=event_data_link)
|
|
54
65
|
with (output_dir / f"{event.event_id}.html").open("w", encoding="utf-8") as f:
|
|
55
66
|
f.write(html)
|
|
56
67
|
|
|
@@ -59,7 +70,12 @@ def generate_event_page(event: Event, submission: Submission, output_dir: Path)
|
|
|
59
70
|
generate_solution_page(sol, event, submission, output_dir)
|
|
60
71
|
|
|
61
72
|
|
|
62
|
-
def _generate_event_page_content(
|
|
73
|
+
def _generate_event_page_content(
|
|
74
|
+
event: Event,
|
|
75
|
+
submission: Submission,
|
|
76
|
+
*,
|
|
77
|
+
event_data_link: str = "",
|
|
78
|
+
) -> str:
|
|
63
79
|
"""Generate the HTML content for an event dossier page.
|
|
64
80
|
|
|
65
81
|
Creates the complete HTML content for a single event page, including
|
|
@@ -163,10 +179,10 @@ def _generate_event_page_content(event: Event, submission: Submission) -> str:
|
|
|
163
179
|
)
|
|
164
180
|
# Optional raw data link
|
|
165
181
|
raw_data_html = ""
|
|
166
|
-
if
|
|
182
|
+
if event_data_link:
|
|
167
183
|
raw_data_html = (
|
|
168
184
|
f'<p class="text-rtd-text">Raw Event Data: '
|
|
169
|
-
f'<a href="{
|
|
185
|
+
f'<a href="{event_data_link}" '
|
|
170
186
|
f'class="text-rtd-accent hover:underline">Download Data</a></p>'
|
|
171
187
|
)
|
|
172
188
|
# HTML content
|
|
@@ -15,6 +15,7 @@ from ..models.solution import Solution
|
|
|
15
15
|
from .dashboard import _generate_dashboard_content
|
|
16
16
|
from .event_page import _generate_event_page_content
|
|
17
17
|
from .solution_page import _generate_solution_page_content
|
|
18
|
+
from .utils import resolve_dossier_asset_path
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
def generate_full_dossier_report_html(submission: Submission, output_dir: Path) -> None:
|
|
@@ -63,19 +64,57 @@ def generate_full_dossier_report_html(submission: Submission, output_dir: Path)
|
|
|
63
64
|
all_html_sections.append('<hr class="my-8 border-t-2 border-rtd-accent">') # Divider after dashboard
|
|
64
65
|
|
|
65
66
|
# Events and solutions
|
|
67
|
+
project_root = Path(submission.project_path)
|
|
66
68
|
for event in submission.events.values():
|
|
67
|
-
|
|
69
|
+
event_data_link = ""
|
|
70
|
+
if hasattr(event, "event_data_path") and event.event_data_path:
|
|
71
|
+
event_data_link = resolve_dossier_asset_path(
|
|
72
|
+
event.event_data_path,
|
|
73
|
+
project_root,
|
|
74
|
+
output_dir,
|
|
75
|
+
subdir="event-data",
|
|
76
|
+
prefix=f"{event.event_id}_event_data",
|
|
77
|
+
)
|
|
78
|
+
event_html = _generate_event_page_content(event, submission, event_data_link=event_data_link)
|
|
68
79
|
event_body = extract_main_content_body(event_html, section_type="event", section_id=event.event_id)
|
|
69
80
|
all_html_sections.append(event_body)
|
|
70
81
|
all_html_sections.append('<hr class="my-8 border-t-2 border-rtd-accent">') # Divider after event
|
|
71
82
|
|
|
72
83
|
for sol in event.get_active_solutions():
|
|
73
|
-
|
|
84
|
+
lc_plot = resolve_dossier_asset_path(
|
|
85
|
+
sol.lightcurve_plot_path,
|
|
86
|
+
project_root,
|
|
87
|
+
output_dir,
|
|
88
|
+
subdir="plots",
|
|
89
|
+
prefix=f"{event.event_id}_{sol.solution_id}_lightcurve",
|
|
90
|
+
)
|
|
91
|
+
lens_plot = resolve_dossier_asset_path(
|
|
92
|
+
sol.lens_plane_plot_path,
|
|
93
|
+
project_root,
|
|
94
|
+
output_dir,
|
|
95
|
+
subdir="plots",
|
|
96
|
+
prefix=f"{event.event_id}_{sol.solution_id}_lens",
|
|
97
|
+
)
|
|
98
|
+
posterior = resolve_dossier_asset_path(
|
|
99
|
+
sol.posterior_path,
|
|
100
|
+
project_root,
|
|
101
|
+
output_dir,
|
|
102
|
+
subdir="posteriors",
|
|
103
|
+
prefix=f"{event.event_id}_{sol.solution_id}_posterior",
|
|
104
|
+
)
|
|
105
|
+
sol_html = _generate_solution_page_content(
|
|
106
|
+
sol,
|
|
107
|
+
event,
|
|
108
|
+
submission,
|
|
109
|
+
lc_plot=lc_plot,
|
|
110
|
+
lens_plot=lens_plot,
|
|
111
|
+
posterior=posterior,
|
|
112
|
+
)
|
|
74
113
|
sol_body = extract_main_content_body(
|
|
75
114
|
sol_html,
|
|
76
115
|
section_type="solution",
|
|
77
116
|
section_id=sol.solution_id,
|
|
78
|
-
project_root=
|
|
117
|
+
project_root=project_root,
|
|
79
118
|
solution=sol,
|
|
80
119
|
)
|
|
81
120
|
all_html_sections.append(sol_body)
|
{microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/dossier/solution_page.py
RENAMED
|
@@ -8,6 +8,7 @@ notes rendering, and evaluator-only sections.
|
|
|
8
8
|
|
|
9
9
|
from datetime import datetime
|
|
10
10
|
from pathlib import Path
|
|
11
|
+
from typing import Optional
|
|
11
12
|
|
|
12
13
|
import markdown
|
|
13
14
|
|
|
@@ -15,6 +16,7 @@ from .. import __version__
|
|
|
15
16
|
from ..models.event import Event
|
|
16
17
|
from ..models.solution import Solution
|
|
17
18
|
from ..models.submission import Submission
|
|
19
|
+
from .utils import resolve_dossier_asset_path
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
def generate_solution_page(solution: Solution, event: Event, submission: Submission, output_dir: Path) -> None:
|
|
@@ -55,12 +57,49 @@ def generate_solution_page(solution: Solution, event: Event, submission: Submiss
|
|
|
55
57
|
Notes are rendered with syntax highlighting for code blocks.
|
|
56
58
|
"""
|
|
57
59
|
# Prepare output directory (already created)
|
|
58
|
-
|
|
60
|
+
project_root = Path(submission.project_path)
|
|
61
|
+
lc_plot = resolve_dossier_asset_path(
|
|
62
|
+
solution.lightcurve_plot_path,
|
|
63
|
+
project_root,
|
|
64
|
+
output_dir,
|
|
65
|
+
subdir="plots",
|
|
66
|
+
prefix=f"{event.event_id}_{solution.solution_id}_lightcurve",
|
|
67
|
+
)
|
|
68
|
+
lens_plot = resolve_dossier_asset_path(
|
|
69
|
+
solution.lens_plane_plot_path,
|
|
70
|
+
project_root,
|
|
71
|
+
output_dir,
|
|
72
|
+
subdir="plots",
|
|
73
|
+
prefix=f"{event.event_id}_{solution.solution_id}_lens",
|
|
74
|
+
)
|
|
75
|
+
posterior = resolve_dossier_asset_path(
|
|
76
|
+
solution.posterior_path,
|
|
77
|
+
project_root,
|
|
78
|
+
output_dir,
|
|
79
|
+
subdir="posteriors",
|
|
80
|
+
prefix=f"{event.event_id}_{solution.solution_id}_posterior",
|
|
81
|
+
)
|
|
82
|
+
html = _generate_solution_page_content(
|
|
83
|
+
solution,
|
|
84
|
+
event,
|
|
85
|
+
submission,
|
|
86
|
+
lc_plot=lc_plot,
|
|
87
|
+
lens_plot=lens_plot,
|
|
88
|
+
posterior=posterior,
|
|
89
|
+
)
|
|
59
90
|
with (output_dir / f"{solution.solution_id}.html").open("w", encoding="utf-8") as f:
|
|
60
91
|
f.write(html)
|
|
61
92
|
|
|
62
93
|
|
|
63
|
-
def _generate_solution_page_content(
|
|
94
|
+
def _generate_solution_page_content(
|
|
95
|
+
solution: Solution,
|
|
96
|
+
event: Event,
|
|
97
|
+
submission: Submission,
|
|
98
|
+
*,
|
|
99
|
+
lc_plot: Optional[str] = None,
|
|
100
|
+
lens_plot: Optional[str] = None,
|
|
101
|
+
posterior: Optional[str] = None,
|
|
102
|
+
) -> str:
|
|
64
103
|
"""Generate the HTML content for a solution dossier page.
|
|
65
104
|
|
|
66
105
|
Creates the complete HTML content for a single solution page, including
|
|
@@ -138,9 +177,9 @@ def _generate_solution_page_content(solution: Solution, event: Event, submission
|
|
|
138
177
|
# Higher-order effects
|
|
139
178
|
hoe_str = ", ".join(solution.higher_order_effects) if solution.higher_order_effects else "None"
|
|
140
179
|
# Plot paths (relative to solution page)
|
|
141
|
-
lc_plot = solution.lightcurve_plot_path or ""
|
|
142
|
-
lens_plot = solution.lens_plane_plot_path or ""
|
|
143
|
-
posterior = solution.posterior_path or ""
|
|
180
|
+
lc_plot = lc_plot if lc_plot is not None else (solution.lightcurve_plot_path or "")
|
|
181
|
+
lens_plot = lens_plot if lens_plot is not None else (solution.lens_plane_plot_path or "")
|
|
182
|
+
posterior = posterior if posterior is not None else (solution.posterior_path or "")
|
|
144
183
|
# Physical parameters table
|
|
145
184
|
phys_rows = []
|
|
146
185
|
phys = solution.physical_parameters or {}
|
|
@@ -6,7 +6,11 @@ generation package, including hardware formatting, GitHub URL parsing,
|
|
|
6
6
|
and other helper functions.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
+
import hashlib
|
|
10
|
+
import shutil
|
|
11
|
+
from pathlib import Path
|
|
9
12
|
from typing import Any, Dict, Optional
|
|
13
|
+
from urllib.parse import quote, urlparse
|
|
10
14
|
|
|
11
15
|
|
|
12
16
|
def format_hardware_info(hardware_info: Optional[Dict[str, Any]]) -> str:
|
|
@@ -109,3 +113,62 @@ def extract_github_repo_name(repo_url: str) -> str:
|
|
|
109
113
|
return f"{parts[-2]}/{parts[-1]}"
|
|
110
114
|
|
|
111
115
|
return None
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def resolve_dossier_asset_path(
|
|
119
|
+
path_value: Optional[str],
|
|
120
|
+
project_root: Path,
|
|
121
|
+
output_dir: Path,
|
|
122
|
+
*,
|
|
123
|
+
subdir: str,
|
|
124
|
+
prefix: Optional[str] = None,
|
|
125
|
+
) -> str:
|
|
126
|
+
"""Resolve and copy a local asset into the dossier assets folder.
|
|
127
|
+
|
|
128
|
+
If the input path is a URL, it is returned unchanged. If it is a local
|
|
129
|
+
filesystem path, it is resolved relative to the project root, copied into
|
|
130
|
+
the dossier assets folder, and returned as a URL-encoded relative path.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
path_value: The original path or URL string.
|
|
134
|
+
project_root: Root directory of the submission project.
|
|
135
|
+
output_dir: Dossier output directory containing the HTML files.
|
|
136
|
+
subdir: Subdirectory under assets/ for the copied file.
|
|
137
|
+
prefix: Optional prefix for the copied file name to avoid collisions.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
str: A URL or a relative path suitable for HTML src/href attributes.
|
|
141
|
+
"""
|
|
142
|
+
if not path_value:
|
|
143
|
+
return ""
|
|
144
|
+
|
|
145
|
+
parsed = urlparse(path_value)
|
|
146
|
+
if parsed.scheme in {"http", "https", "data"}:
|
|
147
|
+
return path_value
|
|
148
|
+
|
|
149
|
+
if parsed.scheme == "file":
|
|
150
|
+
source_path = Path(parsed.path)
|
|
151
|
+
else:
|
|
152
|
+
source_path = Path(path_value).expanduser()
|
|
153
|
+
|
|
154
|
+
if not source_path.is_absolute():
|
|
155
|
+
source_path = (project_root / source_path).resolve()
|
|
156
|
+
else:
|
|
157
|
+
source_path = source_path.resolve()
|
|
158
|
+
|
|
159
|
+
if not source_path.exists():
|
|
160
|
+
return path_value
|
|
161
|
+
|
|
162
|
+
assets_dir = output_dir / "assets" / subdir
|
|
163
|
+
assets_dir.mkdir(parents=True, exist_ok=True)
|
|
164
|
+
|
|
165
|
+
digest = hashlib.sha1(str(source_path).encode("utf-8")).hexdigest()[:8]
|
|
166
|
+
safe_prefix = prefix or "asset"
|
|
167
|
+
dest_name = f"{safe_prefix}_{digest}_{source_path.name}"
|
|
168
|
+
dest_path = assets_dir / dest_name
|
|
169
|
+
|
|
170
|
+
if not dest_path.exists():
|
|
171
|
+
shutil.copy2(source_path, dest_path)
|
|
172
|
+
|
|
173
|
+
rel_path = dest_path.relative_to(output_dir).as_posix()
|
|
174
|
+
return quote(rel_path)
|
|
@@ -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]] = {}
|