Skip to content

fitresult

FitResult dataclass

Fit result object.

Source code in slimfit/fitresult.py
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
@dataclass
class FitResult:
    """
    Fit result object.
    """

    fit_parameters: dict[str, np.ndarray]
    """Fitted parameter values"""

    gof_qualifiers: dict
    """Goodness-of-fit qualifiers"""

    fixed_parameters: dict[str, float | np.ndarray] = field(default_factory=dict)  # Numerical dtype
    """Values of the model's fixed parameters"""

    guess: Optional[dict] = None
    """Initial guesses"""

    minimizer: Optional[Minimizer] = None

    hessian: Optional[np.ndarray] = None

    metadata: dict = field(default_factory=dict)
    """Additional metadata"""

    base_result: Optional[Any] = field(default=None, repr=False)
    """Source fit result object. Can be dicts of sub results"""

    def __post_init__(self) -> None:
        if "datetime" not in self.metadata:
            now = datetime.now()
            self.metadata["datetime"] = now.strftime("%Y/%m/%d %H:%M:%S")
            self.metadata["timestamp"] = int(now.timestamp())

    def __str__(self):
        if any(np.ndim(v) != 0 for v in self.fit_parameters.values()):
            raise ValueError("Cannot print fit result with array values.")

        s = ""
        stdev = self.stdev if self.hessian is not None else None

        p_size = max(len(k) for k in self.fit_parameters)
        if stdev:
            s += f"{'Parameter':<{p_size}} {'Value':>10} {'Stdev':>10}\n"
        else:
            s += f"{'Parameter':<{p_size}} {'Value':>10}\n"

        for k, v in self.fit_parameters.items():
            s += f"{k:<{max(p_size, 9)}} {v:>10.3g}"
            if stdev:
                s += f" {stdev[k]:>10.3g}"
            s += "\n"

        return s

    def to_dict(self) -> dict:
        """
        Convert the fit result to a dictionary.

        Returns:
            Dictionary representation of the fit result.
        """
        keys = [
            "gof_qualifiers",
            "fit_parameters",
            "fixed_parameters",
            "guess",
            "metadata",
        ]
        if self.hessian is not None:
            keys += ["stdev"]

        d = {k: v for k in keys if (v := getattr(self, k)) is not None}

        return clean_types(d)

    def to_yaml(self, path: Union[os.PathLike[str], str], sort_keys: bool = False) -> None:
        """
        Save the fit result as yaml.

        Args:
            path: Path to save to.
            sort_keys: Boolean indicating whether to sort the keys.

        """
        Path(path).write_text(yaml.dump(self.to_dict(), sort_keys=sort_keys))

    def to_pickle(self, path: Union[os.PathLike[str], str]) -> None:
        """
        Save the fit result as pickle.

        Args:
            path: Path to save to.
        """
        try:
            del self.minimizer.model.numerical
        except AttributeError:
            pass

        with Path(path).open("wb") as f:
            pickle.dump(self, f)

    def eval_hessian(self, hessian: Optional[Hessian] = None) -> np.ndarray:
        # TODO Hessian as parameter / strategy
        """Evaluate the hessian at the fitted parameters values"""

        hessian = hessian or rgetattr(self.minimizer, "objective.hessian", None)
        if hessian is None:
            raise ValueError("No Hessian available")

        if hasattr(self.minimizer, "objective") and list(hessian.shapes.items()) != list(
            self.minimizer.objective.shapes.items()
        ):
            raise ValueError("Mismatch between objective and fit parameters")

        x = pack(self.fit_parameters.values())
        self.hessian = hessian(x)

        return self.hessian

    @property
    def variance(self) -> dict[str, float | np.ndarray]:
        if self.hessian is None:
            self.eval_hessian()

        hess_inv = np.linalg.inv(self.hessian)
        var = np.diag(hess_inv)
        parameter_shapes = {k: v.shape for k, v in self.fit_parameters.items()}
        return unpack(var, parameter_shapes)

    @property
    def stdev(self) -> dict[str, float | np.ndarray]:
        return {k: np.sqrt(v) for k, v in self.variance.items()}

    @property
    def parameters(self) -> dict[str, float | np.ndarray]:
        return {**self.fit_parameters, **self.fixed_parameters}

base_result = field(default=None, repr=False) class-attribute instance-attribute

Source fit result object. Can be dicts of sub results

fit_parameters instance-attribute

Fitted parameter values

fixed_parameters = field(default_factory=dict) class-attribute instance-attribute

Values of the model's fixed parameters

gof_qualifiers instance-attribute

Goodness-of-fit qualifiers

guess = None class-attribute instance-attribute

Initial guesses

metadata = field(default_factory=dict) class-attribute instance-attribute

Additional metadata

eval_hessian(hessian=None)

Evaluate the hessian at the fitted parameters values

Source code in slimfit/fitresult.py
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
def eval_hessian(self, hessian: Optional[Hessian] = None) -> np.ndarray:
    # TODO Hessian as parameter / strategy
    """Evaluate the hessian at the fitted parameters values"""

    hessian = hessian or rgetattr(self.minimizer, "objective.hessian", None)
    if hessian is None:
        raise ValueError("No Hessian available")

    if hasattr(self.minimizer, "objective") and list(hessian.shapes.items()) != list(
        self.minimizer.objective.shapes.items()
    ):
        raise ValueError("Mismatch between objective and fit parameters")

    x = pack(self.fit_parameters.values())
    self.hessian = hessian(x)

    return self.hessian

to_dict()

Convert the fit result to a dictionary.

Returns:

Type Description
dict

Dictionary representation of the fit result.

Source code in slimfit/fitresult.py
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
def to_dict(self) -> dict:
    """
    Convert the fit result to a dictionary.

    Returns:
        Dictionary representation of the fit result.
    """
    keys = [
        "gof_qualifiers",
        "fit_parameters",
        "fixed_parameters",
        "guess",
        "metadata",
    ]
    if self.hessian is not None:
        keys += ["stdev"]

    d = {k: v for k in keys if (v := getattr(self, k)) is not None}

    return clean_types(d)

to_pickle(path)

Save the fit result as pickle.

Parameters:

Name Type Description Default
path Union[PathLike[str], str]

Path to save to.

required
Source code in slimfit/fitresult.py
107
108
109
110
111
112
113
114
115
116
117
118
119
120
def to_pickle(self, path: Union[os.PathLike[str], str]) -> None:
    """
    Save the fit result as pickle.

    Args:
        path: Path to save to.
    """
    try:
        del self.minimizer.model.numerical
    except AttributeError:
        pass

    with Path(path).open("wb") as f:
        pickle.dump(self, f)

to_yaml(path, sort_keys=False)

Save the fit result as yaml.

Parameters:

Name Type Description Default
path Union[PathLike[str], str]

Path to save to.

required
sort_keys bool

Boolean indicating whether to sort the keys.

False
Source code in slimfit/fitresult.py
 96
 97
 98
 99
100
101
102
103
104
105
def to_yaml(self, path: Union[os.PathLike[str], str], sort_keys: bool = False) -> None:
    """
    Save the fit result as yaml.

    Args:
        path: Path to save to.
        sort_keys: Boolean indicating whether to sort the keys.

    """
    Path(path).write_text(yaml.dump(self.to_dict(), sort_keys=sort_keys))