Skip to content

Commit

Permalink
🚧Add CompatResult for better Notebook Compatibility
Browse files Browse the repository at this point in the history
and add in comparison
  • Loading branch information
jsnel committed Jul 7, 2024
1 parent b81a4ea commit f49f2bb
Show file tree
Hide file tree
Showing 6 changed files with 211 additions and 8 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ repos:
- "--allow-init-docstring=True"
- "--skip-checking-short-docstrings=False"
name: "flake8 lint docstrings"
exclude: "^(docs/|tests?/)"
exclude: "^(docs/|tests?/|pyglotaran_extras/compat)"
additional_dependencies: [pydoclint==0.3.8]

- repo: https://github.com/codespell-project/codespell
Expand Down
2 changes: 2 additions & 0 deletions .ruff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"

[lint.per-file-ignores]
"tests/*" = ["ARG001"]
"pyglotaran_extras/compat/compat_result.py" = ["D", "N", "E", "ERA"]
"pyglotaran_extras/compat/convert_result_dataset.py" = ["D", "N", "F", "C"]

[lint.isort]
required-imports = ["from __future__ import annotations"]
Expand Down
2 changes: 2 additions & 0 deletions pyglotaran_extras/compat/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Provides compatibility utilities for pyglotaran."""

from __future__ import annotations

from pyglotaran_extras.compat.convert_result_dataset import convert

__all__ = ["convert"]
193 changes: 193 additions & 0 deletions pyglotaran_extras/compat/compat_result.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
from __future__ import annotations

from typing import TYPE_CHECKING
from typing import Any

import numpy as np

if TYPE_CHECKING:
from glotaran.model.experiment_model import ExperimentModel
from glotaran.parameter.parameters import Parameters

from glotaran.project.result import Result
from glotaran.utils.ipython import MarkdownStr
from tabulate import tabulate


class CompatResult(Result):
"""A compatibility class for Result.
Inherits:
data: dict[str, xr.Dataset]
experiments: dict[str, ExperimentModel]
optimization: OptimizationResult
parameters_intitial: Parameters
parameters_optimized: Parameters
"""

@property
def number_of_function_evaluations(self) -> int:
return self.optimization.number_of_function_evaluations

@property
def number_of_residuals(self) -> int:
return self.optimization.number_of_data_points

@property
def number_of_free_parameters(self) -> int:
return self.optimization.number_of_parameters

@property
def number_of_clps(self) -> int:
return self.optimization.number_of_clps

@property
def degrees_of_freedom(self) -> int:
return self.optimization.degrees_of_freedom

@property
def chi_square(self) -> float:
return self.optimization.chi_square

@property
def reduced_chi_square(self) -> float:
return self.optimization.reduced_chi_square

@property
def reduced_chi_squared(self) -> float:
return self.optimization.reduced_chi_square

@property
def root_mean_square_error(self) -> float:
return self.optimization.root_mean_square_error

@property
def additional_penalty(self) -> list | None:
return None # TODO: implement

@property
def optimized_parameters(self) -> Parameters:
return self.parameters_optimized

@property
def initial_parameters(self) -> Parameters:
return self.parameters_intitial

@property
def model(self) -> ExperimentModel:
return self.experiments[next(iter(self.experiments.keys()))]

@classmethod
def from_result(cls, result: Result) -> CompatResult:
return cls(
data=result.data,
experiments=result.experiments,
optimization=result.optimization,
parameters_intitial=result.parameters_intitial,
parameters_optimized=result.parameters_optimized,
)

def markdown(
self,
with_model: bool = True,
*,
base_heading_level: int = 1,
wrap_model_in_details: bool = False,
) -> MarkdownStr:
"""Format the model as a markdown text.
Parameters
----------
with_model : bool
If `True`, the model will be printed with initial and optimized parameters filled in.
base_heading_level : int
The level of the base heading.
wrap_model_in_details: bool
Wraps model into details tag. Defaults to ``False``
Returns
-------
MarkdownStr : str
The scheme as markdown string.
"""
general_table_rows: list[list[Any]] = [
["Number of residual evaluation", self.number_of_function_evaluations],
["Number of residuals", self.number_of_residuals],
["Number of free parameters", self.number_of_free_parameters],
["Number of conditionally linear parameters", self.number_of_clps],
["Degrees of freedom", self.degrees_of_freedom],
["Chi Square", f"{self.chi_square or np.nan:.2e}"],
["Reduced Chi Square", f"{self.reduced_chi_square or np.nan:.2e}"],
["Root Mean Square Error (RMSE)", f"{self.root_mean_square_error or np.nan:.2e}"],
]
if self.additional_penalty is not None and any(
len(penalty) != 0 for penalty in self.additional_penalty
):
general_table_rows.append(["RMSE additional penalty", self.additional_penalty])

result_table = tabulate(
general_table_rows,
headers=["Optimization Result", ""],
tablefmt="github",
disable_numparse=True,
)
if len(self.data) > 1:
RMSE_rows = [
[
f"{index}.{label}:",
dataset.weighted_root_mean_square_error,
dataset.root_mean_square_error,
]
for index, (label, dataset) in enumerate(self.data.items(), start=1)
]

RMSE_table = tabulate(
RMSE_rows,
headers=["RMSE (per dataset)", "weighted", "unweighted"],
floatfmt=".2e",
tablefmt="github",
)

result_table = f"{result_table}\n\n{RMSE_table}"

if with_model:
result_table += (
"\n\n> **Warning:** Printing model is not yet implemented for `CompatResult`."
)
result_table += f"\n\n> **unused:** {base_heading_level=}, {wrap_model_in_details=}."

# model_md = self.model.markdown(
# parameters=self.optimized_parameters,
# initial_parameters=self.initial_parameters,
# base_heading_level=base_heading_level,
# )
# if wrap_model_in_details is False:
# result_table = f"{result_table}\n\n{model_md}"
# else:
# # The section part is just a hack to generate properly rendering docs due to a bug
# # in sphinx which causes a wrong tag opening and closing order of html tags
# # Since model_md contains 2 heading levels we need to close 2 sections
# result_table = (
# f"{result_table}\n\n<br><details>\n\n{model_md}\n"
# f"{'</section>'*(2)}"
# "</details>"
# f"{'<section>'*(2)}"
# )

return MarkdownStr(result_table)

def _repr_markdown_(self) -> str:
"""Return a markdown representation str.
Special method used by ``ipython`` to render markdown.
Returns
-------
str
The scheme as markdown string.
"""
return str(self.markdown(base_heading_level=3, wrap_model_in_details=True))

def __str__(self) -> str:
"""Overwrite of ``__str__``."""
return str(self.markdown(with_model=False))
19 changes: 12 additions & 7 deletions pyglotaran_extras/compat/convert_result_dataset.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
"""Convert a new pyglotaran (result) dataset to a version compatible with pyglotaran-extras."""

from __future__ import annotations

import copy

import xarray as xr
from glotaran.project.result import Result

from pyglotaran_extras.compat.compat_result import CompatResult


def _adjust_estimations_to_spectra(ds: xr.Dataset, *, cleanup: bool = False) -> None:
"""Adjust the estimations to spectra names and flatten data."""
Expand All @@ -27,7 +32,9 @@ def _adjust_estimations_to_spectra(ds: xr.Dataset, *, cleanup: bool = False) ->
if cleanup:
ds = ds.drop_vars("species_associated_estimation")
if "damped_oscillation_associated_estimation" in ds:
ds["damped_oscillation_associated_spectra"] = ds["damped_oscillation_associated_estimation"]
ds["damped_oscillation_associated_spectra"] = ds[
"damped_oscillation_associated_estimation"
]
if cleanup:
ds = ds.drop_vars("damped_oscillation_associated_estimation")

Expand Down Expand Up @@ -77,7 +84,7 @@ def _adjust_activation_to_irf(ds: xr.Dataset, *, cleanup: bool = False) -> None:
pass


def convert(input: xr.Dataset | Result, cleanup: bool = False) -> xr.Dataset | Result:
def convert(input: xr.Dataset | Result, cleanup: bool = False) -> xr.Dataset | CompatResult:
"""Convert a glotaran Result or xarray Dataset to a different format.
Parameters
Expand Down Expand Up @@ -128,16 +135,14 @@ def convert_dataset(dataset: xr.Dataset, cleanup: bool = False) -> xr.Dataset:
return converted_ds


def convert_result(result: Result, cleanup: bool = False) -> Result:
def convert_result(result: Result, cleanup: bool = False) -> CompatResult:
"""Convert the result format used in staging (to be v0.8) to the format of main (v0.7)."""

converted_result = copy.copy(result)
converted_result = CompatResult.from_result(result)

# convert the datasets
for key in converted_result.data:
converted_result.data[key] = convert_dataset(
converted_result.data[key], cleanup=cleanup
)
converted_result.data[key] = convert_dataset(converted_result.data[key], cleanup=cleanup)

# convert the parameters
return converted_result
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ disallow_untyped_defs = false
exclude = [
"docs",
"tests/*",
"*/compat/*",
]
ignore-init-module = true
fail-under = 100

0 comments on commit f49f2bb

Please sign in to comment.