Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/easyreflectometry/summary/html_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,10 @@
<td>Minimization engine</td>
<td>minimization_engine</td>
</tr>
<!-- <tr> -->
<!-- <td>Goodness-of-fit: reduced <i>&chi;</i><sup>2</sup></td> -->
<!-- <td>goodness_of_fit</td> -->
<!-- </tr> -->
<tr>
<td>Goodness-of-fit: reduced <i>&chi;</i><sup>2</sup></td>
<td>goodness_of_fit</td>
</tr>
<tr>
<td>No. of parameters:</td>
<td>num_total_params</td>
Expand Down
65 changes: 60 additions & 5 deletions src/easyreflectometry/summary/summary.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# SPDX-FileCopyrightText: 2024 EasyScience contributors <https://github.com/easyscience>
# SPDX-License-Identifier: BSD-3-Clause

from html import escape
from urllib.parse import quote

Check warning on line 5 in src/easyreflectometry/summary/summary.py

View check run for this annotation

Codecov / codecov/patch

src/easyreflectometry/summary/summary.py#L4-L5

Added lines #L4 - L5 were not covered by tests

import matplotlib.pyplot as plt
import numpy as np
from easyscience import global_object
Expand All @@ -16,6 +19,39 @@
from .html_templates import HTML_REFINEMENT_TEMPLATE
from .html_templates import HTML_TEMPLATE

_NAME_MAX_LEN = 20

Check warning on line 22 in src/easyreflectometry/summary/summary.py

View check run for this annotation

Codecov / codecov/patch

src/easyreflectometry/summary/summary.py#L22

Added line #L22 was not covered by tests
# Custom href scheme used to pass the full name to QML via TextEdit.hoveredLink.
_TOOLTIP_SCHEME = 'nametooltip'

Check warning on line 24 in src/easyreflectometry/summary/summary.py

View check run for this annotation

Codecov / codecov/patch

src/easyreflectometry/summary/summary.py#L24

Added line #L24 was not covered by tests


def _format_error(error: float) -> str:

Check warning on line 27 in src/easyreflectometry/summary/summary.py

View check run for this annotation

Codecov / codecov/patch

src/easyreflectometry/summary/summary.py#L27

Added line #L27 was not covered by tests
"""Format a parameter error the same way the Analysis table does.

Mirrors the JS ``formatError`` in Fittables.qml:
2 significant figures; fall back to 1-decimal exponential for long strings.
Zero (no fit run yet) is shown as '0.0'.
"""
if error == 0.0:
return '0.0'
s = f'{error:.2g}'
if len(s) <= 6:
return s
return f'{error:.1e}'

Check warning on line 39 in src/easyreflectometry/summary/summary.py

View check run for this annotation

Codecov / codecov/patch

src/easyreflectometry/summary/summary.py#L34-L39

Added lines #L34 - L39 were not covered by tests


def _truncate_name(name: str, max_len: int = _NAME_MAX_LEN) -> str:

Check warning on line 42 in src/easyreflectometry/summary/summary.py

View check run for this annotation

Codecov / codecov/patch

src/easyreflectometry/summary/summary.py#L42

Added line #L42 was not covered by tests
"""Return an HTML snippet with a truncated name and tooltip for the full text.

Browsers use the ``title`` attribute; QML reads the ``href`` via
``TextEdit.hoveredLink`` and shows a native ToolTip.
"""
safe = escape(name)
if len(name) <= max_len:
return safe
short = escape(name[:max_len].rstrip())
encoded = quote(name, safe='')
return f'<a style="color:inherit;text-decoration:none;" href="{_TOOLTIP_SCHEME}:{encoded}" title="{safe}">{short}…</a>'

Check warning on line 53 in src/easyreflectometry/summary/summary.py

View check run for this annotation

Codecov / codecov/patch

src/easyreflectometry/summary/summary.py#L48-L53

Added lines #L48 - L53 were not covered by tests


class Summary:
def __init__(self, project: Project):
Expand Down Expand Up @@ -140,7 +176,8 @@
html_parameter = html_parameter.replace('parameter_name', f'{name}')
html_parameter = html_parameter.replace('parameter_value', f'{value}')
html_parameter = html_parameter.replace('parameter_unit', f'{unit}')
html_parameter = html_parameter.replace('parameter_error', f'{error}')
error_str = _format_error(error)
html_parameter = html_parameter.replace('parameter_error', error_str)

Check warning on line 180 in src/easyreflectometry/summary/summary.py

View check run for this annotation

Codecov / codecov/patch

src/easyreflectometry/summary/summary.py#L179-L180

Added lines #L179 - L180 were not covered by tests
html_parameters.append(html_parameter)

html_parameters_str = '\n'.join(html_parameters)
Expand All @@ -162,7 +199,7 @@
range_max = max(experiment.y)
range_units = 'Å⁻¹'
html_experiment = HTML_DATA_COLLECTION_TEMPLATE
html_experiment = html_experiment.replace('experiment_name', f'{experiment_name}')
html_experiment = html_experiment.replace('experiment_name', _truncate_name(experiment_name))

Check warning on line 202 in src/easyreflectometry/summary/summary.py

View check run for this annotation

Codecov / codecov/patch

src/easyreflectometry/summary/summary.py#L202

Added line #L202 was not covered by tests
html_experiment = html_experiment.replace('range_min', f'{range_min}')
html_experiment = html_experiment.replace('range_max', f'{range_max}')
html_experiment = html_experiment.replace('range_units', f'{range_units}')
Expand All @@ -185,19 +222,37 @@
num_free_params = sum(1 for parameter in parameters if parameter.free)
num_fixed_params = sum(1 for parameter in parameters if not parameter.free)
num_params = num_free_params + num_fixed_params
# goodness_of_fit = self._project.status.goodnessOfFit
# goodness_of_fit = goodness_of_fit.split(' → ')[-1]
num_constraints = sum(1 for parameter in parameters if not parameter.independent)

goodness_of_fit = self._compute_goodness_of_fit()

Check warning on line 227 in src/easyreflectometry/summary/summary.py

View check run for this annotation

Codecov / codecov/patch

src/easyreflectometry/summary/summary.py#L227

Added line #L227 was not covered by tests

html_refinement = html_refinement.replace('calculation_engine', f'{self._project._calculator.current_interface_name}')
html_refinement = html_refinement.replace('minimization_engine', f'{self._project.minimizer.name}')
# html = html.replace('goodness_of_fit', f'{goodness_of_fit}')
html_refinement = html_refinement.replace('goodness_of_fit', goodness_of_fit)

Check warning on line 231 in src/easyreflectometry/summary/summary.py

View check run for this annotation

Codecov / codecov/patch

src/easyreflectometry/summary/summary.py#L231

Added line #L231 was not covered by tests
html_refinement = html_refinement.replace('num_total_params', f'{num_params}')
html_refinement = html_refinement.replace('num_free_params', f'{num_free_params}')
html_refinement = html_refinement.replace('num_fixed_params', f'{num_fixed_params}')
html_refinement = html_refinement.replace('num_constriants', f'{num_constraints}')
return html_refinement

def _compute_goodness_of_fit(self) -> str:

Check warning on line 238 in src/easyreflectometry/summary/summary.py

View check run for this annotation

Codecov / codecov/patch

src/easyreflectometry/summary/summary.py#L238

Added line #L238 was not covered by tests
"""Return reduced chi² as a formatted string, or 'N/A' if no fit has been run."""
last_fit_results = getattr(self._project, '_last_fit_results', None)
if not last_fit_results:
return 'N/A'
try:
if len(last_fit_results) == 1:
gof = float(last_fit_results[0].reduced_chi2)

Check warning on line 245 in src/easyreflectometry/summary/summary.py

View check run for this annotation

Codecov / codecov/patch

src/easyreflectometry/summary/summary.py#L240-L245

Added lines #L240 - L245 were not covered by tests
else:
total_chi2 = sum(float(r.chi2) for r in last_fit_results)
total_points = sum(len(r.x) for r in last_fit_results)
n_pars = last_fit_results[0].n_pars
dof = total_points - n_pars
gof = total_chi2 / dof if dof > 0 else 0.0
return f'{gof:.4g}'
except (AttributeError, TypeError, ValueError, ZeroDivisionError):
return 'N/A'

Check warning on line 254 in src/easyreflectometry/summary/summary.py

View check run for this annotation

Codecov / codecov/patch

src/easyreflectometry/summary/summary.py#L247-L254

Added lines #L247 - L254 were not covered by tests

def _figures_section(self) -> None:
"""Figures section."""
html_figures = HTML_FIGURES_TEMPLATE
Expand Down
Loading