from __future__ import annotations
from copy import deepcopy
from functools import partial
from inspect import signature
from typing import Any, Callable, Optional, cast
import matplotlib.pyplot as plt
import numpy as np
from numpy.typing import ArrayLike
from scipy.optimize import curve_fit
from .data_plotting_1d import Curve, Scatter
from .exceptions import InvalidParameterError, PlottingError
from .graph_elements import Point
from .inherit import INHERIT, Inherit, Styled, strip_inherit
try:
from typing import Self
except ImportError:
from typing_extensions import Self
def _run_curve_fit(func, x_data, y_data, **kwargs):
"""
Runs ``scipy.optimize.curve_fit`` and wraps any failure with GraphingLib context.
A non-converging fit otherwise surfaces as a bare scipy ``RuntimeError`` with no
indication that it came from a GraphingLib fit.
"""
try:
return curve_fit(func, x_data, y_data, **kwargs)
except Exception as exc:
raise PlottingError(
f"The curve fit did not succeed ({exc}). Try adjusting the initial guesses "
"or increasing the maximum number of iterations."
) from exc
def _repair_positive_guess(
guesses: ArrayLike, index: int, number_of_parameters: int
) -> np.ndarray:
"""
Copies the initial guesses and forces the guess of a bounded parameter to be strictly positive.
"""
guesses = np.array(guesses, dtype=float)
if guesses.shape != (number_of_parameters,):
raise InvalidParameterError(
f"Expected {number_of_parameters} initial guesses, "
f"but got an array of shape {guesses.shape}."
)
guesses[index] = max(abs(guesses[index]), 1e-10)
return guesses
class GeneralFit(Curve):
"""
Dummy class for curve fits. Defines the interface for all curve fits.
.. attention:: Not to be used directly.
Parameters
----------
curve_to_be_fit : :class:`~graphinglib.data_plotting_1d.Curve` or :class:`~graphinglib.data_plotting_1d.Scatter`
The object to be fit.
label : str, optional
Label to be displayed in the legend.
color : str
Color of the curve.
Default depends on the ``figure_style`` configuration.
line_width : int
Line width of the curve.
Typical range is ``0.5`` to ``4``.
Default depends on the ``figure_style`` configuration.
line_style : str
Line style of the curve.
Values include ``"-"``, ``"--"``, ``"-."``, ``":"``, ``"solid"``, ``"dashed"``, ``"dashdot"``, and
``"dotted"``.
Default depends on the ``figure_style`` configuration.
alpha : float
Opacity of the curve.
Range is ``0`` (transparent) to ``1`` (opaque).
Default depends on the ``figure_style`` configuration.
Notes
-----
Color parameters accept Matplotlib color formats: named colors (``"blue"``), short color strings
(``"b"``), hex strings (``"#0000ff"``), grayscale strings (``"0.5"``), and RGB/RGBA tuples with
values between ``0`` and ``1`` (``(0, 0, 1)`` or ``(0, 0, 1, 0.5)``).
"""
def __init__(
self,
curve_to_be_fit: Curve | Scatter,
label: Optional[str] = None,
color: str | Inherit = INHERIT,
line_width: int | Inherit = INHERIT,
line_style: str | Inherit = INHERIT,
alpha: float | Inherit = INHERIT,
) -> None:
"""
Parameters
----------
curve_to_be_fit : :class:`~graphinglib.data_plotting_1d.Curve` or :class:`~graphinglib.data_plotting_1d.Scatter`
The object to be fit.
label : str, optional
Label to be displayed in the legend.
color : str
Color of the curve.
Default depends on the ``figure_style`` configuration.
line_width : int
Line width of the curve.
Typical range is ``0.5`` to ``4``.
Default depends on the ``figure_style`` configuration.
line_style : str
Line style of the curve.
Values include ``"-"``, ``"--"``, ``"-."``, ``":"``, ``"solid"``, ``"dashed"``, ``"dashdot"``, and
``"dotted"``.
Default depends on the ``figure_style`` configuration.
alpha : float
Opacity of the curve.
Range is ``0`` (transparent) to ``1`` (opaque).
Default depends on the ``figure_style`` configuration.
Notes
-----
Color parameters accept Matplotlib color formats: named colors (``"blue"``), short color strings
(``"b"``), hex strings (``"#0000ff"``), grayscale strings (``"0.5"``), and RGB/RGBA tuples with
values between ``0`` and ``1`` (``(0, 0, 1)`` or ``(0, 0, 1, 0.5)``).
"""
self._curve_to_be_fit = curve_to_be_fit
self._color = color
self._line_width = line_width
if label:
self._label = label + " : " + "$f(x) = $" + str(self)
else:
self._label = "$f(x) = $" + str(self)
self._line_style = line_style
self._alpha = alpha
self._function: Callable[[float | np.ndarray], float | np.ndarray]
self._parameters: np.ndarray
self._cov_matrix: np.ndarray
self._standard_deviation: np.ndarray
self._setup_attributes()
def _setup_attributes(self) -> None:
self._res_curves_to_be_plotted = False
self._res_sigma_multiplier = None
self._res_color = None
self._res_line_width = None
self._res_line_style = None
self._show_errorbars: bool = False
self._errorbars_color: Styled[str | None] = None
self._errorbars_line_width: Styled[float | None] = None
self._cap_thickness: Styled[float | None] = None
self._cap_width: Styled[float | None] = None
self._show_error_curves: bool = False
self._error_curves_fill_between: bool = False
self._error_curves_color = None
self._error_curves_line_style = None
self._error_curves_line_width = None
self._fill_between_bounds: Optional[tuple[float, float]] = None
self._fill_between_other_curve: Optional[Self] = None
self._fill_between_color: Optional[str] = None
@property
def curve_to_be_fit(self) -> Curve | Scatter:
return self._curve_to_be_fit
@curve_to_be_fit.setter
def curve_to_be_fit(self, curve: Curve | Scatter) -> None:
self._curve_to_be_fit = curve
@property
def function(self) -> Callable[[float | np.ndarray], float | np.ndarray]:
return self._function
@property
def parameters(self) -> np.ndarray:
return self._parameters
@property
def cov_matrix(self) -> np.ndarray:
return self._cov_matrix
@property
def standard_deviation(self) -> np.ndarray:
return self._standard_deviation
def __str__(self) -> str:
"""
Create a string representation of the fit function.
"""
raise NotImplementedError()
def _evaluate_scalar(self, x: float) -> float:
value = self._function(x)
return float(np.asarray(value).flat[0])
def get_coordinates_at_x(
self, x: float, interpolation_method: str = "linear"
) -> tuple[float, float]:
return (x, self._evaluate_scalar(x))
def create_point_at_x(
self,
x: float,
interpolation_method: str = "linear",
label: str | None = None,
face_color: str | Inherit = INHERIT,
edge_color: str | Inherit = INHERIT,
marker_size: float | Inherit = INHERIT,
marker_style: str | Inherit = INHERIT,
line_width: float | Inherit = INHERIT,
alpha: float | Inherit = INHERIT,
) -> Point:
"""
Gets the point on the curve at a given x value.
Parameters
----------
x : float
x value of the point.
interpolation_method : str
Interpolation method parameter.
Since fit curves are analytic, this value is ignored.
Default is ``"linear"``.
label : str, optional
Label to be displayed in the legend.
face_color : str
Face color of the point.
Default depends on the ``figure_style`` configuration.
edge_color : str
Edge color of the point.
Default depends on the ``figure_style`` configuration.
marker_size : float
Size of the point.
Typical range is ``10`` to ``100``.
Default depends on the ``figure_style`` configuration.
marker_style : str
Style of the point.
Common values include ``"."``, ``","``, ``"o"``, ``"v"``, ``"^"``, ``"<"``, ``">"``, ``"s"``,
``"p"``, ``"*"``, ``"h"``, ``"H"``, ``"+"``, ``"x"``, ``"D"``, ``"d"``, ``"|"``, ``"_"``,
``"P"``, ``"X"``, ``"None"``, ``" "``, and ``""``.
Default depends on the ``figure_style`` configuration.
line_width : float
Width of the edge of the point.
Typical range is ``0`` to ``3`` points.
Default depends on the ``figure_style`` configuration.
alpha : float
Opacity of the point.
Range is ``0`` (transparent) to ``1`` (opaque).
Default depends on the ``figure_style`` configuration.
Notes
-----
Color parameters accept Matplotlib color formats: named colors (``"blue"``), short color strings
(``"b"``), hex strings (``"#0000ff"``), grayscale strings (``"0.5"``), and RGB/RGBA tuples with
values between ``0`` and ``1`` (``(0, 0, 1)`` or ``(0, 0, 1, 0.5)``).
Returns
-------
:class:`~graphinglib.graph_elements.Point` object on the curve at the given x value.
"""
return Point(
x,
self._evaluate_scalar(x),
label=label,
face_color=face_color,
edge_color=edge_color,
marker_size=marker_size,
marker_style=marker_style,
edge_width=line_width,
alpha=alpha,
)
def get_coordinates_at_y(
self, y: float, interpolation_method: str = "linear"
) -> list[tuple[float, float]]:
return super().get_coordinates_at_y(y, interpolation_method)
def create_points_at_y(
self,
y: float,
interpolation_method: str = "linear",
label: str | None = None,
face_color: str | Inherit = INHERIT,
edge_color: str | Inherit = INHERIT,
marker_size: float | Inherit = INHERIT,
marker_style: str | Inherit = INHERIT,
line_width: float | Inherit = INHERIT,
alpha: float | Inherit = INHERIT,
) -> list[Point]:
"""
Creates the Points on the curve at a given y value.
Parameters
----------
y : float
y value of the point.
interpolation_method : str
Kind of interpolation to be used.
Default is "linear".
label : str, optional
Label to be displayed in the legend.
face_color : str
Face color of the point.
Default depends on the ``figure_style`` configuration.
edge_color : str
Edge color of the point.
Default depends on the ``figure_style`` configuration.
marker_size : float
Size of the point.
Typical range is ``10`` to ``100``.
Default depends on the ``figure_style`` configuration.
marker_style : str
Style of the point.
Common values include ``"."``, ``","``, ``"o"``, ``"v"``, ``"^"``, ``"<"``, ``">"``, ``"s"``,
``"p"``, ``"*"``, ``"h"``, ``"H"``, ``"+"``, ``"x"``, ``"D"``, ``"d"``, ``"|"``, ``"_"``,
``"P"``, ``"X"``, ``"None"``, ``" "``, and ``""``.
Default depends on the ``figure_style`` configuration.
line_width : float
Width of the edge of the point.
Typical range is ``0`` to ``3`` points.
Default depends on the ``figure_style`` configuration.
alpha : float
Opacity of the point.
Range is ``0`` (transparent) to ``1`` (opaque).
Default depends on the ``figure_style`` configuration.
Notes
-----
Color parameters accept Matplotlib color formats: named colors (``"blue"``), short color strings
(``"b"``), hex strings (``"#0000ff"``), grayscale strings (``"0.5"``), and RGB/RGBA tuples with
values between ``0`` and ``1`` (``(0, 0, 1)`` or ``(0, 0, 1, 0.5)``).
Returns
-------
list[:class:`~graphinglib.graph_elements.Point`]
List of :class:`~graphinglib.graph_elements.Point` objects on the curve at the given y value.
"""
coord_pairs = self.get_coordinates_at_y(y, interpolation_method)
points = [
Point(
coord[0],
coord[1],
label=label,
face_color=face_color,
edge_color=edge_color,
marker_size=marker_size,
marker_style=marker_style,
edge_width=line_width,
alpha=alpha,
)
for coord in coord_pairs
]
return points
def _plot_element(self, axes: plt.Axes, z_order: int, **kwargs) -> None:
"""
Plots the element in the specified
Axes
"""
params = {
"color": self._color,
"linewidth": self._line_width,
"linestyle": self._line_style,
"alpha": self._alpha,
}
params = strip_inherit(params)
(self.handle,) = axes.plot(
self._x_data,
self._y_data,
label=self._label,
zorder=z_order,
**params,
)
if self._res_curves_to_be_plotted:
y_fit = self._y_data
residuals = self.get_residuals()
std = np.std(residuals)
y_fit_plus_std = y_fit + (self._res_sigma_multiplier * std)
y_fit_minus_std = y_fit - (self._res_sigma_multiplier * std)
params = {
"color": self._res_color,
"linewidth": self._res_line_width,
"linestyle": self._res_line_style,
"alpha": self._alpha,
}
params = strip_inherit(params)
axes.plot(
self._x_data,
y_fit_minus_std,
zorder=z_order,
**params,
)
axes.plot(
self._x_data,
y_fit_plus_std,
zorder=z_order,
**params,
)
if self._fill_between_bounds:
kwargs = {"alpha": 0.2}
if self._fill_between_color:
kwargs["color"] = self._fill_between_color
else:
kwargs["color"] = self.handle.get_color()
params = strip_inherit(kwargs)
axes.fill_between(
self._x_data,
self._y_data,
where=np.logical_and(
self._x_data >= self._fill_between_bounds[0],
self._x_data <= self._fill_between_bounds[1],
).tolist(),
zorder=z_order - 2,
**params,
)
def show_residual_curves(
self,
sigma_multiplier: float = 1,
color: str | Inherit = INHERIT,
line_width: float | Inherit = INHERIT,
line_style: str | Inherit = INHERIT,
) -> None:
"""
Displays two curves ``"sigma_multiplier"`` standard deviations above and below the fit curve.
Parameters
----------
sigma_multiplier : float
Distance in standard deviations from the fit curve.
Default is 1.
color : str
Color of the residual curves.
Default depends on the ``figure_style`` configuration.
line_width : float
Line width of the residual curves.
Typical range is ``0.5`` to ``4``.
Default depends on the ``figure_style`` configuration.
line_style : str
Line style of the residual curves.
Values include ``"-"``, ``"--"``, ``"-."``, ``":"``, ``"solid"``, ``"dashed"``, ``"dashdot"``, and
``"dotted"``.
Default depends on the ``figure_style`` configuration.
Notes
-----
Color parameters accept Matplotlib color formats: named colors (``"blue"``), short color strings
(``"b"``), hex strings (``"#0000ff"``), grayscale strings (``"0.5"``), and RGB/RGBA tuples with
values between ``0`` and ``1`` (``(0, 0, 1)`` or ``(0, 0, 1, 0.5)``).
"""
self._res_curves_to_be_plotted = True
self._res_sigma_multiplier = sigma_multiplier
self._res_color = color
self._res_line_width = line_width
self._res_line_style = line_style
def get_residuals(self) -> np.ndarray:
"""
Calculates the residuals of the fit curve.
Returns
-------
residuals : np.ndarray
Array of residuals.
"""
y_data = self._function(self._curve_to_be_fit._x_data)
residuals = y_data - self._curve_to_be_fit._y_data
return residuals
def get_Rsquared(self) -> float:
"""
Calculates the :math:`R^2` value of the fit curve.
Returns
-------
Rsquared : float
:math:`R^2` value
"""
y_data = self._curve_to_be_fit._y_data
total_variance = np.sum((y_data - np.mean(y_data)) ** 2)
if total_variance == 0:
# Scale the tolerance to the data's own magnitude, since comparing residuals
# to an absolute tolerance of 0 makes np.allclose's rtol term vanish. Only
# fall back to an absolute tolerance when the data is identically zero.
magnitude = np.max(np.abs(y_data))
scale = magnitude if magnitude > 0 else 1.0
is_exact_fit = np.allclose(self.get_residuals(), 0, atol=1e-8 * scale)
return 1.0 if is_exact_fit else float("nan")
return 1 - np.sum(self.get_residuals() ** 2) / total_variance
def copy(self) -> Self:
return deepcopy(self)
[docs]
class FitFromPolynomial(GeneralFit):
"""
Creates a curve fit (continuous :class:`~graphinglib.data_plotting_1d.Curve`) from an existing curve object using a polynomial fit.
Fits a polynomial of the form :math:`f(x) = a_0 + a_1 x + a_2 x^2 + ... + a_n x^n` to the given curve. All standard Curve attributes
and methods are available.
Parameters
----------
curve_to_be_fit : :class:`~graphinglib.data_plotting_1d.Curve` or :class:`~graphinglib.data_plotting_1d.Scatter`
The object to be fit.
degree : int
Degree of the polynomial fit.
label : str, optional
Label to be displayed in the legend.
color : str
Color of the :class:`~graphinglib.data_plotting_1d.Curve`.
Default depends on the ``figure_style`` configuration.
line_width : int
Line width of the :class:`~graphinglib.data_plotting_1d.Curve`.
Typical range is ``0.5`` to ``4``.
Default depends on the ``figure_style`` configuration.
line_style : str
Line style of the :class:`~graphinglib.data_plotting_1d.Curve`.
Values include ``"-"``, ``"--"``, ``"-."``, ``":"``, ``"solid"``, ``"dashed"``, ``"dashdot"``, and
``"dotted"``.
Default depends on the ``figure_style`` configuration.
alpha : float
Opacity of the :class:`~graphinglib.data_plotting_1d.Curve`.
Range is ``0`` (transparent) to ``1`` (opaque).
Default depends on the ``figure_style`` configuration.
Notes
-----
Color parameters accept Matplotlib color formats: named colors (``"blue"``), short color strings
(``"b"``), hex strings (``"#0000ff"``), grayscale strings (``"0.5"``), and RGB/RGBA tuples with
values between ``0`` and ``1`` (``(0, 0, 1)`` or ``(0, 0, 1, 0.5)``).
Attributes
----------
coeffs : np.ndarray
Coefficients of the polynomial fit. The first element is the coefficient of the lowest order term (constant term).
cov_matrix : np.ndarray
Covariance matrix of the polynomial fit (using the same order as the coeffs attribute).
standard_deviation : np.ndarray
Standard deviation of the coefficients of the polynomial fit (same order as coeffs).
function : Callable
Polynomial function with the parameters of the fit.
"""
[docs]
def __init__(
self,
curve_to_be_fit: Curve | Scatter,
degree: int,
label: Optional[str] = None,
color: str | Inherit = INHERIT,
line_width: int | Inherit = INHERIT,
line_style: str | Inherit = INHERIT,
alpha: float | Inherit = INHERIT,
) -> None:
"""
Creates a curve fit (continuous :class:`~graphinglib.data_plotting_1d.Curve`) from an existing curve object using a polynomial fit.
Fits a polynomial of the form :math:`f(x) = a_0 + a_1 x + a_2 x^2 + ... + a_n x^n` to the given curve. All standard Curve attributes
and methods are available.
Parameters
----------
curve_to_be_fit : :class:`~graphinglib.data_plotting_1d.Curve` or :class:`~graphinglib.data_plotting_1d.Scatter`
The object to be fit.
degree : int
Degree of the polynomial fit.
label : str, optional
Label to be displayed in the legend.
color : str
Color of the :class:`~graphinglib.data_plotting_1d.Curve`.
Default depends on the ``figure_style`` configuration.
line_width : int
Line width of the :class:`~graphinglib.data_plotting_1d.Curve`.
Typical range is ``0.5`` to ``4``.
Default depends on the ``figure_style`` configuration.
line_style : str
Line style of the :class:`~graphinglib.data_plotting_1d.Curve`.
Values include ``"-"``, ``"--"``, ``"-."``, ``":"``, ``"solid"``, ``"dashed"``, ``"dashdot"``, and
``"dotted"``.
Default depends on the ``figure_style`` configuration.
alpha : float
Opacity of the :class:`~graphinglib.data_plotting_1d.Curve`.
Range is ``0`` (transparent) to ``1`` (opaque).
Default depends on the ``figure_style`` configuration.
Notes
-----
Color parameters accept Matplotlib color formats: named colors (``"blue"``), short color strings
(``"b"``), hex strings (``"#0000ff"``), grayscale strings (``"0.5"``), and RGB/RGBA tuples with
values between ``0`` and ``1`` (``(0, 0, 1)`` or ``(0, 0, 1, 0.5)``).
Attributes
----------
coeffs : np.ndarray
Coefficients of the polynomial fit. The first element is the coefficient of the lowest order term (constant term).
cov_matrix : np.ndarray
Covariance matrix of the polynomial fit (using the same order as the coeffs attribute).
standard_deviation : np.ndarray
Standard deviation of the coefficients of the polynomial fit (same order as coeffs).
function : Callable
Polynomial function with the parameters of the fit.
"""
self._curve_to_be_fit = curve_to_be_fit
inversed_coeffs, inversed_cov_matrix = np.polyfit(
self._curve_to_be_fit._x_data,
self._curve_to_be_fit._y_data,
degree,
cov=True,
)
self._coeffs = inversed_coeffs[::-1]
self._cov_matrix = np.flip(inversed_cov_matrix)
self._standard_deviation = np.sqrt(np.diag(self._cov_matrix))
self._function = self._polynomial_func_with_params()
self._color = color
self._line_width = line_width
if label:
self._label = label + " : " + "$f(x) = $" + str(self)
else:
self._label = "$f(x) = $" + str(self)
self._line_style = line_style
self._alpha = alpha
self._res_curves_to_be_plotted = False
number_of_points = (
len(self._curve_to_be_fit._x_data)
if len(self._curve_to_be_fit._x_data) > 500
else 500
)
self._x_data = np.linspace(
np.min(self._curve_to_be_fit._x_data),
np.max(self._curve_to_be_fit._x_data),
number_of_points,
)
self._y_data = self._function(self._x_data)
self._setup_attributes()
@property
def coeffs(self) -> np.ndarray:
return self._coeffs
@property
def parameters(self) -> np.ndarray:
return self._coeffs
def __str__(self) -> str:
"""
Creates a string representation of the polynomial function.
"""
coeff_chunks = []
power_chunks = []
ordered_rounded_coeffs = [round(coeff, 3) for coeff in self._coeffs[::-1]]
for coeff, power in zip(
ordered_rounded_coeffs, range(len(ordered_rounded_coeffs) - 1, -1, -1)
):
if coeff == 0:
continue
coeff_chunks.append(self._format_coeff(coeff))
power_chunks.append(self._format_power(power))
if len(coeff_chunks) == 0:
return "$0$"
coeff_chunks[0] = coeff_chunks[0].lstrip("+ ")
return (
"$"
+ "".join(
[coeff_chunks[i] + power_chunks[i] for i in range(len(coeff_chunks))]
)
+ "$"
)
@staticmethod
def _format_coeff(coeff: float) -> str:
"""
Formats a coefficient to be displayed in the string representation of the polynomial function.
"""
return " - {0}".format(abs(coeff)) if coeff < 0 else " + {0}".format(coeff)
@staticmethod
def _format_power(power: int) -> str:
"""
Formats a power to be displayed in the string representation of the polynomial function.
"""
return "x^{0}".format(power) if power != 0 else ""
def _polynomial_func_with_params(
self,
) -> Callable[[float | np.ndarray], float | np.ndarray]:
"""
Creates a polynomial function with the parameters of the fit.
Returns
-------
function : Callable
Polynomial function with the parameters of the fit.
"""
return lambda x: sum(
coeff * x**exponent for exponent, coeff in enumerate(self._coeffs)
)
[docs]
class FitFromSine(GeneralFit):
"""
Create a curve fit (continuous :class:`~graphinglib.data_plotting_1d.Curve`) from an existing
:class:`~graphinglib.data_plotting_1d.Curve` object using a sinusoidal fit.
Fits a sine function of the form :math:`f(x) = a sin(bx + c) + d` to the given curve. All standard
:class:`~graphinglib.data_plotting_1d.Curve` attributes and methods are available.
Parameters
----------
curve_to_be_fit : :class:`~graphinglib.data_plotting_1d.Curve` or :class:`~graphinglib.data_plotting_1d.Scatter`
The object to be fit.
label : str, optional
Label to be displayed in the legend.
guesses : ArrayLike, optional
Initial guesses for the parameters of the fit (order: amplitude (a), frequency (b), phase (c), vertical shift (d) as written above).
color : str
Color of the curve.
Default depends on the ``figure_style`` configuration.
line_width : int
Line width of the curve.
Typical range is ``0.5`` to ``4``.
Default depends on the ``figure_style`` configuration.
line_style : str
Line style of the curve.
Values include ``"-"``, ``"--"``, ``"-."``, ``":"``, ``"solid"``, ``"dashed"``, ``"dashdot"``, and
``"dotted"``.
Default depends on the ``figure_style`` configuration.
alpha : float
Opacity of the curve.
Range is ``0`` (transparent) to ``1`` (opaque).
Default depends on the ``figure_style`` configuration.
max_iterations : int
Maximum number of iterations for the fit.
Default is 10000.
Notes
-----
Color parameters accept Matplotlib color formats: named colors (``"blue"``), short color strings
(``"b"``), hex strings (``"#0000ff"``), grayscale strings (``"0.5"``), and RGB/RGBA tuples with
values between ``0`` and ``1`` (``(0, 0, 1)`` or ``(0, 0, 1, 0.5)``).
Attributes
----------
amplitude : float
Amplitude of the sine function.
frequency_rad : float
Frequency of the sine function in radians.
phase : float
Phase of the sine function.
vertical_shift : float
Vertical shift of the sine function.
cov_matrix : np.ndarray
Covariance matrix of the parameters of the fit.
standard_deviation : np.ndarray
Standard deviation of the parameters of the fit.
function : Callable
Sine function with the parameters of the fit.
"""
[docs]
def __init__(
self,
curve_to_be_fit: Curve | Scatter,
label: Optional[str] = None,
guesses: Optional[ArrayLike] = None,
color: str | Inherit = INHERIT,
line_width: float | Inherit = INHERIT,
line_style: str | Inherit = INHERIT,
alpha: float | Inherit = INHERIT,
max_iterations: int = 10000,
) -> None:
"""
Create a curve fit (continuous :class:`~graphinglib.data_plotting_1d.Curve`) from an existing
:class:`~graphinglib.data_plotting_1d.Curve` object using a sinusoidal fit.
Fits a sine function of the form :math:`f(x) = a sin(bx + c) + d` to the given curve. All standard
:class:`~graphinglib.data_plotting_1d.Curve` attributes and methods are available.
Parameters
----------
curve_to_be_fit : :class:`~graphinglib.data_plotting_1d.Curve` or :class:`~graphinglib.data_plotting_1d.Scatter`
The object to be fit.
label : str, optional
Label to be displayed in the legend.
guesses : ArrayLike, optional
Initial guesses for the parameters of the fit (order: amplitude (a), frequency (b), phase (c), vertical shift (d) as written above).
color : str
Color of the curve.
Default depends on the ``figure_style`` configuration.
line_width : int
Line width of the curve.
Typical range is ``0.5`` to ``4``.
Default depends on the ``figure_style`` configuration.
line_style : str
Line style of the curve.
Values include ``"-"``, ``"--"``, ``"-."``, ``":"``, ``"solid"``, ``"dashed"``, ``"dashdot"``, and
``"dotted"``.
Default depends on the ``figure_style`` configuration.
alpha : float
Opacity of the curve.
Range is ``0`` (transparent) to ``1`` (opaque).
Default depends on the ``figure_style`` configuration.
max_iterations : int
Maximum number of iterations for the fit.
Default is 10000.
Notes
-----
Color parameters accept Matplotlib color formats: named colors (``"blue"``), short color strings
(``"b"``), hex strings (``"#0000ff"``), grayscale strings (``"0.5"``), and RGB/RGBA tuples with
values between ``0`` and ``1`` (``(0, 0, 1)`` or ``(0, 0, 1, 0.5)``).
Attributes
----------
amplitude : float
Amplitude of the sine function.
frequency_rad : float
Frequency of the sine function in radians.
frequency_deg : float
Frequency of the sine function in degrees.
phase_rad : float
Phase of the sine function.
phase_deg : float
Phase of the sine function in degrees.
vertical_shift : float
Vertical shift of the sine function.
parameters : np.ndarray
Parameters of the fit (amplitude, frequency (rad), phase (rad), vertical shift)
cov_matrix : np.ndarray
Covariance matrix of the parameters of the fit.
standard_deviation : np.ndarray
Standard deviation of the parameters of the fit.
function : Callable
Sine function with the parameters of the fit.
"""
self._curve_to_be_fit = curve_to_be_fit
self._guesses = guesses
self._max_iterations = max_iterations
self._calculate_parameters()
self._function = self._sine_func_with_params()
self._color = color
if label:
self._label = label + " : " + "$f(x) = $" + str(self)
else:
self._label = "$f(x) = $" + str(self)
self._line_width = line_width
self._line_style = line_style
self._alpha = alpha
self._res_curves_to_be_plotted = False
number_of_points = (
len(self._curve_to_be_fit._x_data)
if len(self._curve_to_be_fit._x_data) > 500
else 500
)
self._x_data = np.linspace(
np.min(self._curve_to_be_fit._x_data),
np.max(self._curve_to_be_fit._x_data),
number_of_points,
)
self._y_data = self._function(self._x_data)
self._setup_attributes()
@property
def max_iterations(self) -> int:
return self._max_iterations
@property
def amplitude(self) -> float:
return self._amplitude
@property
def frequency_rad(self) -> float:
return self._frequency_rad
@property
def frequency_deg(self) -> float:
return self._frequency_deg
@property
def phase_rad(self) -> float:
return self._phase_rad
@property
def phase_deg(self) -> float:
return self._phase_deg
@property
def vertical_shift(self) -> float:
return self._vertical_shift
def __str__(self) -> str:
"""
Creates a string representation of the sine function.
"""
part1 = rf"{self._amplitude:.3f} \sin({self._frequency_rad:.3f}x"
part2 = (
f" + {self._phase_rad:.3f})"
if self._phase_rad >= 0
else f" - {abs(self._phase_rad):.3f})"
)
part3 = (
f" + {self._vertical_shift:.3f}"
if self._vertical_shift >= 0
else f" - {abs(self._vertical_shift):.3f}"
)
return f"${part1 + part2 + part3}$"
def _calculate_parameters(self) -> None:
"""
Calculates the parameters of the fit.
"""
self._parameters, self._cov_matrix = _run_curve_fit(
self._sine_func_template,
self._curve_to_be_fit._x_data,
self._curve_to_be_fit._y_data,
p0=self._guesses,
maxfev=self._max_iterations,
)
self._amplitude, self._frequency_rad, self._phase_rad, self._vertical_shift = (
self._parameters
)
self._standard_deviation = np.sqrt(np.diag(self._cov_matrix))
# Calculate the frequency and phase in degrees
self._frequency_deg = np.degrees(self._frequency_rad)
self._phase_deg = np.degrees(self._phase_rad)
@staticmethod
def _sine_func_template(
x: np.ndarray, a: float, b: float, c: float, d: float
) -> np.ndarray:
"""
Function to be passed to the ``curve_fit`` function.
"""
return a * np.sin(b * x + c) + d
def _sine_func_with_params(
self,
) -> Callable[[float | np.ndarray], float | np.ndarray]:
"""
Creates a sine function with the parameters of the fit.
Returns
-------
Callable
Sine function with the parameters of the fit.
"""
return lambda x: (
self._amplitude * np.sin(self._frequency_rad * x + self._phase_rad)
+ self._vertical_shift
)
[docs]
class FitFromExponential(GeneralFit):
"""
Create a curve fit (continuous :class:`~graphinglib.data_plotting_1d.Curve`) from an existing
:class:`~graphinglib.data_plotting_1d.Curve` object using an exponential fit.
Parameters
----------
curve_to_be_fit : :class:`~graphinglib.data_plotting_1d.Curve` or :class:`~graphinglib.data_plotting_1d.Scatter`
The object to be fit.
label : str, optional
Label to be displayed in the legend.
guesses : ArrayLike, optional
Initial guesses for the parameters of the fit. Order is a, b, c as written above.
color : str
Color of the curve.
Default depends on the ``figure_style`` configuration.
line_width : int
Line width of the curve.
Typical range is ``0.5`` to ``4``.
Default depends on the ``figure_style`` configuration.
line_style : str
Line style of the curve.
Values include ``"-"``, ``"--"``, ``"-."``, ``":"``, ``"solid"``, ``"dashed"``, ``"dashdot"``, and
``"dotted"``.
Default depends on the ``figure_style`` configuration.
alpha : float
Opacity of the curve.
Range is ``0`` (transparent) to ``1`` (opaque).
Default depends on the ``figure_style`` configuration.
max_iterations : int
Maximum number of iterations for the fit.
Default is 10000.
Notes
-----
Color parameters accept Matplotlib color formats: named colors (``"blue"``), short color strings
(``"b"``), hex strings (``"#0000ff"``), grayscale strings (``"0.5"``), and RGB/RGBA tuples with
values between ``0`` and ``1`` (``(0, 0, 1)`` or ``(0, 0, 1, 0.5)``).
Attributes
----------
parameters : np.ndarray
Parameters of the fit (same order as guesses).
cov_matrix : np.ndarray
Covariance matrix of the parameters of the fit.
standard_deviation : np.ndarray
Standard deviation of the parameters of the fit.
function : Callable
Exponential function with the parameters of the fit.
"""
[docs]
def __init__(
self,
curve_to_be_fit: Curve | Scatter,
label: Optional[str] = None,
guesses: Optional[ArrayLike] = None,
color: str | Inherit = INHERIT,
line_width: int | Inherit = INHERIT,
line_style: str | Inherit = INHERIT,
alpha: float | Inherit = INHERIT,
max_iterations: int = 10000,
) -> None:
"""
Create a curve fit (continuous :class:`~graphinglib.data_plotting_1d.Curve`)
of the form :math:`f(x) = a \\exp(bx + c)` from an existing :class:`~graphinglib.data_plotting_1d.Curve`
object using an exponential fit.
Parameters
----------
curve_to_be_fit : :class:`~graphinglib.data_plotting_1d.Curve` or :class:`~graphinglib.data_plotting_1d.Scatter`
The object to be fit.
label : str, optional
Label to be displayed in the legend.
guesses : ArrayLike, optional
Initial guesses for the parameters of the fit. Order is a, b, c as written above.
color : str
Color of the curve.
Default depends on the ``figure_style`` configuration.
line_width : int
Line width of the curve.
Typical range is ``0.5`` to ``4``.
Default depends on the ``figure_style`` configuration.
line_style : str
Line style of the curve.
Values include ``"-"``, ``"--"``, ``"-."``, ``":"``, ``"solid"``, ``"dashed"``, ``"dashdot"``, and
``"dotted"``.
Default depends on the ``figure_style`` configuration.
alpha : float
Opacity of the curve.
Range is ``0`` (transparent) to ``1`` (opaque).
Default depends on the ``figure_style`` configuration.
max_iterations : int
Maximum number of iterations for the fit.
Default is 10000.
Notes
-----
Color parameters accept Matplotlib color formats: named colors (``"blue"``), short color strings
(``"b"``), hex strings (``"#0000ff"``), grayscale strings (``"0.5"``), and RGB/RGBA tuples with
values between ``0`` and ``1`` (``(0, 0, 1)`` or ``(0, 0, 1, 0.5)``).
Attributes
----------
parameters : np.ndarray
Parameters of the fit (same order as guesses).
cov_matrix : np.ndarray
Covariance matrix of the parameters of the fit.
standard_deviation : np.ndarray
Standard deviation of the parameters of the fit.
function : Callable
Exponential function with the parameters of the fit.
"""
self._curve_to_be_fit = curve_to_be_fit
self._guesses = guesses
self._max_iterations = max_iterations
self._calculate_parameters()
self._function = self._exp_func_with_params()
self._color = color
if label:
self._label = label + " : " + "$f(x) = $" + str(self)
else:
self._label = "$f(x) = $" + str(self)
self._line_width = line_width
self._line_style = line_style
self._alpha = alpha
self._res_curves_to_be_plotted = False
number_of_points = (
len(self._curve_to_be_fit._x_data)
if len(self._curve_to_be_fit._x_data) > 500
else 500
)
self._x_data = np.linspace(
np.min(self._curve_to_be_fit._x_data),
np.max(self._curve_to_be_fit._x_data),
number_of_points,
)
self._y_data = self._function(self._x_data)
self._setup_attributes()
@property
def max_iterations(self) -> int:
return self._max_iterations
def __str__(self) -> str:
"""
Creates a string representation of the exponential function.
"""
part1 = rf"{self._parameters[0]:.3f} \exp({self._parameters[1]:.3f}x"
part2 = (
f" + {self._parameters[2]:.3f})"
if self._parameters[2] >= 0
else f" - {abs(self._parameters[2]):.3f})"
)
return f"${part1 + part2}$"
def _calculate_parameters(self) -> None:
"""
Calculates the parameters of the fit.
"""
self._parameters, self._cov_matrix = _run_curve_fit(
self._exp_func_template,
self._curve_to_be_fit._x_data,
self._curve_to_be_fit._y_data,
p0=self._guesses,
maxfev=self._max_iterations,
)
self._standard_deviation = np.sqrt(np.diag(self._cov_matrix))
@staticmethod
def _exp_func_template(x: np.ndarray, a: float, b: float, c: float) -> np.ndarray:
"""
Function to be passed to the ``curve_fit`` function.
"""
return a * np.exp(b * x + c)
def _exp_func_with_params(
self,
) -> Callable[[float | np.ndarray], float | np.ndarray]:
"""
Creates an exponential function with the parameters of the fit.
Returns
-------
function : Callable
Exponential function with the parameters of the fit.
"""
return lambda x: (
self._parameters[0] * np.exp(self._parameters[1] * x + self._parameters[2])
)
[docs]
class FitFromGaussian(GeneralFit):
"""
Create a curve fit (continuous :class:`~graphinglib.data_plotting_1d.Curve`) from an existing
:class:`~graphinglib.data_plotting_1d.Curve` object using a gaussian fit.
Fits a gaussian function of the form :math:`f(x) = A e^{-\\frac{(x - \\mu)^2}{2 \\sigma^2}}` to the given curve.
All standard :class:`~graphinglib.data_plotting_1d.Curve` attributes and methods are available.
Parameters
----------
curve_to_be_fit : :class:`~graphinglib.data_plotting_1d.Curve` or :class:`~graphinglib.data_plotting_1d.Scatter`
The object to be fit.
label : str, optional
Label to be displayed in the legend.
guesses : ArrayLike, optional
Initial guesses for the parameters of the fit. Order is amplitude (A), mean (mu), standard deviation (sigma).
color : str
Color of the curve.
Default depends on the ``figure_style`` configuration.
line_width : int
Line width of the curve.
Typical range is ``0.5`` to ``4``.
Default depends on the ``figure_style`` configuration.
line_style : str
Line style of the curve.
Values include ``"-"``, ``"--"``, ``"-."``, ``":"``, ``"solid"``, ``"dashed"``, ``"dashdot"``, and
``"dotted"``.
Default depends on the ``figure_style`` configuration.
alpha : float
Opacity of the curve.
Range is ``0`` (transparent) to ``1`` (opaque).
Default depends on the ``figure_style`` configuration.
max_iterations : int
Maximum number of iterations for the fit.
Default is 10000.
Notes
-----
Color parameters accept Matplotlib color formats: named colors (``"blue"``), short color strings
(``"b"``), hex strings (``"#0000ff"``), grayscale strings (``"0.5"``), and RGB/RGBA tuples with
values between ``0`` and ``1`` (``(0, 0, 1)`` or ``(0, 0, 1, 0.5)``).
Attributes
----------
amplitude : float
Amplitude of the gaussian function.
mean : float
Mean of the gaussian function.
standard_deviation : float
Standard deviation of the gaussian function.
.. warning::
The ``standard_deviation`` attribute doesn't represent the standard deviation of the fit parameters as it does in the other fit classes. Instead, it represents the standard deviation of the gaussian function (it is one of parameters of the fit). The standard deviation of the fit parameters can be found in the ``standard_deviation_of_fit_params`` attribute.
cov_matrix : np.ndarray
Covariance matrix of the parameters of the fit.
standard_deviation_of_fit_params : np.ndarray
Standard deviation of the parameters of the fit.
function : Callable
Gaussian function with the parameters of the fit.
"""
[docs]
def __init__(
self,
curve_to_be_fit: Curve | Scatter,
label: Optional[str] = None,
guesses: Optional[ArrayLike] = None,
color: str | Inherit = INHERIT,
line_width: int | Inherit = INHERIT,
line_style: str | Inherit = INHERIT,
alpha: float | Inherit = INHERIT,
max_iterations: int = 10000,
) -> None:
"""
Create a curve fit (continuous :class:`~graphinglib.data_plotting_1d.Curve`) from an existing
:class:`~graphinglib.data_plotting_1d.Curve` object using a gaussian fit.
Fits a gaussian function of the form :math:`f(x) = A e^{-\\frac{(x - \\mu)^2}{2 \\sigma^2}}` to the given curve.
All standard :class:`~graphinglib.data_plotting_1d.Curve` attributes and methods are available.
Parameters
----------
curve_to_be_fit : :class:`~graphinglib.data_plotting_1d.Curve` or :class:`~graphinglib.data_plotting_1d.Scatter`
The object to be fit.
label : str, optional
Label to be displayed in the legend.
guesses : ArrayLike, optional
Initial guesses for the parameters of the fit.
color : str
Color of the curve.
Default depends on the ``figure_style`` configuration.
line_width : int
Line width of the curve.
Typical range is ``0.5`` to ``4``.
Default depends on the ``figure_style`` configuration.
line_style : str
Line style of the curve.
Values include ``"-"``, ``"--"``, ``"-."``, ``":"``, ``"solid"``, ``"dashed"``, ``"dashdot"``, and
``"dotted"``.
Default depends on the ``figure_style`` configuration.
alpha : float
Opacity of the curve.
Range is ``0`` (transparent) to ``1`` (opaque).
Default depends on the ``figure_style`` configuration.
max_iterations : int
Maximum number of iterations for the fit.
Default is 10000.
Notes
-----
Color parameters accept Matplotlib color formats: named colors (``"blue"``), short color strings
(``"b"``), hex strings (``"#0000ff"``), grayscale strings (``"0.5"``), and RGB/RGBA tuples with
values between ``0`` and ``1`` (``(0, 0, 1)`` or ``(0, 0, 1, 0.5)``).
Attributes
----------
amplitude : float
Amplitude of the gaussian function.
mean : float
Mean of the gaussian function.
standard_deviation : float
Standard deviation of the gaussian function.
cov_matrix : np.ndarray
Covariance matrix of the parameters of the fit.
standard_deviation_of_fit_params : np.ndarray
Standard deviation of the parameters of the fit.
function : Callable
Gaussian function with the parameters of the fit.
Warning
-------
The ``standard_deviation`` attribute doesn't represent the standard deviation of the fit parameters as it does in the other fit classes. Instead, it represents the standard deviation of the gaussian function (it is one of parameters of the fit). The standard deviation of the fit parameters can be found in the ``standard_deviation_of_fit_params`` attribute.
"""
self._curve_to_be_fit = curve_to_be_fit
self._guesses = guesses
self._max_iterations = max_iterations
self._calculate_parameters()
self._function = self._gaussian_func_with_params()
self._color = color
if label:
self._label = label + " : " + str(self)
else:
self._label = str(self)
self._line_width = line_width
self._line_style = line_style
self._alpha = alpha
self._res_curves_to_be_plotted = False
number_of_points = (
len(self._curve_to_be_fit._x_data)
if len(self._curve_to_be_fit._x_data) > 500
else 500
)
self._x_data = np.linspace(
np.min(self._curve_to_be_fit._x_data),
np.max(self._curve_to_be_fit._x_data),
number_of_points,
)
self._y_data = self._function(self._x_data)
self._setup_attributes()
@property
def max_iterations(self) -> int:
return self._max_iterations
@property
def amplitude(self) -> float:
return self._amplitude
@property
def mean(self) -> float:
return self._mean
@property
def standard_deviation(self) -> float:
# Unlike the other fit classes, this is the fitted sigma of the gaussian itself,
# not the standard deviation of the fit parameters (see standard_deviation_of_fit_params).
return float(cast(Any, self._standard_deviation))
@property
def standard_deviation_of_fit_params(self) -> np.ndarray:
return self._standard_deviation_of_fit_params
def __str__(self) -> str:
"""
Creates a string representation of the gaussian function.
"""
return rf"$\mu = {self._mean:.3f}, \sigma = {self._standard_deviation:.3f}, A = {self._amplitude:.3f}$"
def _calculate_parameters(self) -> None:
"""
Calculates the parameters of the fit.
"""
guesses = self._guesses
if guesses is not None:
# The standard deviation guess must be positive to satisfy the fit's bounds,
# regardless of the sign the caller happened to guess.
guesses = _repair_positive_guess(guesses, index=2, number_of_parameters=3)
self._parameters, self._cov_matrix = _run_curve_fit(
self._gaussian_func_template,
self._curve_to_be_fit._x_data,
self._curve_to_be_fit._y_data,
p0=guesses,
maxfev=self._max_iterations,
bounds=([-np.inf, -np.inf, 1e-10], [np.inf, np.inf, np.inf]),
)
self._amplitude = self._parameters[0]
self._mean = self._parameters[1]
self._standard_deviation = self._parameters[2]
self._standard_deviation_of_fit_params = np.sqrt(np.diag(self._cov_matrix))
@staticmethod
def _gaussian_func_template(
x: np.ndarray, amplitude: float, mean: float, standard_deviation: float
) -> np.ndarray:
"""
Function to be passed to the ``curve_fit`` function.
"""
return amplitude * np.exp(-(((x - mean) / standard_deviation) ** 2) / 2)
def _gaussian_func_with_params(
self,
) -> Callable[[float | np.ndarray], float | np.ndarray]:
"""
Creates a gaussian function with the parameters of the fit.
Returns
-------
function : Callable
Gaussian function with the parameters of the fit.
"""
return lambda x: (
self._amplitude
* np.exp(-(((x - self._mean) / self._standard_deviation) ** 2) / 2)
)
[docs]
class FitFromSquareRoot(GeneralFit):
"""
Create a curve fit (continuous :class:`~graphinglib.data_plotting_1d.Curve`) from an existing
:class:`~graphinglib.data_plotting_1d.Curve` object using a square root fit.
Fits a square root function of the form :math:`f(x) = a \\sqrt{x + b} + c` to the given curve. All standard
:class:`~graphinglib.data_plotting_1d.Curve` attributes and methods are available.
Parameters
----------
curve_to_be_fit : :class:`~graphinglib.data_plotting_1d.Curve` or :class:`~graphinglib.data_plotting_1d.Scatter`
The object to be fit.
label : str, optional
Label to be displayed in the legend.
guesses : ArrayLike, optional
Initial guesses for the parameters of the fit. Order is a, b, c as written above.
color : str
Color of the curve.
Default depends on the ``figure_style`` configuration.
line_width : int
Line width of the curve.
Typical range is ``0.5`` to ``4``.
Default depends on the ``figure_style`` configuration.
line_style : str
Line style of the curve.
Values include ``"-"``, ``"--"``, ``"-."``, ``":"``, ``"solid"``, ``"dashed"``, ``"dashdot"``, and
``"dotted"``.
Default depends on the ``figure_style`` configuration.
alpha : float
Opacity of the curve.
Range is ``0`` (transparent) to ``1`` (opaque).
Default depends on the ``figure_style`` configuration.
max_iterations : int
Maximum number of iterations for the fit.
Default is 10000.
Notes
-----
Color parameters accept Matplotlib color formats: named colors (``"blue"``), short color strings
(``"b"``), hex strings (``"#0000ff"``), grayscale strings (``"0.5"``), and RGB/RGBA tuples with
values between ``0`` and ``1`` (``(0, 0, 1)`` or ``(0, 0, 1, 0.5)``).
Attributes
----------
parameters : np.ndarray
Parameters of the fit (same order as guesses).
cov_matrix : np.ndarray
Covariance matrix of the parameters of the fit.
standard_deviation : np.ndarray
Standard deviation of the parameters of the fit.
function : Callable
Square root function with the parameters of the fit.
"""
[docs]
def __init__(
self,
curve_to_be_fit: Curve | Scatter,
label: Optional[str] = None,
guesses: Optional[ArrayLike] = None,
color: str | Inherit = INHERIT,
line_width: int | Inherit = INHERIT,
line_style: str | Inherit = INHERIT,
alpha: float | Inherit = INHERIT,
max_iterations: int = 10000,
) -> None:
"""
Create a curve fit (continuous :class:`~graphinglib.data_plotting_1d.Curve`) from an existing
:class:`~graphinglib.data_plotting_1d.Curve` object using a square root fit.
Fits a square root function of the form :math:`f(x) = a \\sqrt{x + b} + c` to the given curve. All standard
:class:`~graphinglib.data_plotting_1d.Curve` attributes and methods are available.
Parameters
----------
curve_to_be_fit : :class:`~graphinglib.data_plotting_1d.Curve` or :class:`~graphinglib.data_plotting_1d.Scatter`
The object to be fit.
label : str, optional
Label to be displayed in the legend.
guesses : ArrayLike, optional
Initial guesses for the parameters of the fit. Order is a, b, c as written above.
color : str
Color of the curve.
Default depends on the ``figure_style`` configuration.
line_width : int
Line width of the curve.
Typical range is ``0.5`` to ``4``.
Default depends on the ``figure_style`` configuration.
line_style : str
Line style of the curve.
Values include ``"-"``, ``"--"``, ``"-."``, ``":"``, ``"solid"``, ``"dashed"``, ``"dashdot"``, and
``"dotted"``.
Default depends on the ``figure_style`` configuration.
alpha : float
Opacity of the curve.
Range is ``0`` (transparent) to ``1`` (opaque).
Default depends on the ``figure_style`` configuration.
max_iterations : int
Maximum number of iterations for the fit.
Default is 10000.
Notes
-----
Color parameters accept Matplotlib color formats: named colors (``"blue"``), short color strings
(``"b"``), hex strings (``"#0000ff"``), grayscale strings (``"0.5"``), and RGB/RGBA tuples with
values between ``0`` and ``1`` (``(0, 0, 1)`` or ``(0, 0, 1, 0.5)``).
Attributes
----------
parameters : np.ndarray
Parameters of the fit (same order as guesses).
cov_matrix : np.ndarray
Covariance matrix of the parameters of the fit.
standard_deviation : np.ndarray
Standard deviation of the parameters of the fit.
function : Callable
Square root function with the parameters of the fit.
"""
self._curve_to_be_fit = curve_to_be_fit
self._guesses = guesses
self._max_iterations = max_iterations
self._calculate_parameters()
self._function = self._square_root_func_with_params()
self._color = color
if label:
self._label = label + " : " + str(self)
else:
self._label = str(self)
self._line_width = line_width
self._line_style = line_style
self._alpha = alpha
self._res_curves_to_be_plotted = False
number_of_points = (
len(self._curve_to_be_fit._x_data)
if len(self._curve_to_be_fit._x_data) > 500
else 500
)
self._x_data = np.linspace(
np.min(self._curve_to_be_fit._x_data),
np.max(self._curve_to_be_fit._x_data),
number_of_points,
)
self._y_data = self._function(self._x_data)
self._setup_attributes()
@property
def max_iterations(self) -> int:
return self._max_iterations
def __str__(self) -> str:
"""
Creates a string representation of the square root function.
"""
return rf"${self._parameters[0]:.3f} \sqrt{{x {'+' if self._parameters[1] > 0 else '-'} {abs(self._parameters[1]):.3f}}} {'+' if self._parameters[2] > 0 else '-'} {abs(self._parameters[2]):.3f}$"
def _calculate_parameters(self) -> None:
"""
Calculates the parameters of the fit.
"""
self._parameters, self._cov_matrix = _run_curve_fit(
self._square_root_func_template,
self._curve_to_be_fit._x_data,
self._curve_to_be_fit._y_data,
p0=self._guesses,
maxfev=self._max_iterations,
)
self._standard_deviation = np.sqrt(np.diag(self._cov_matrix))
@staticmethod
def _square_root_func_template(
x: np.ndarray, a: float, b: float, c: float
) -> np.ndarray:
"""
Function to be passed to the ``curve_fit`` function.
"""
return a * np.sqrt(x + b) + c
def _square_root_func_with_params(
self,
) -> Callable[[float | np.ndarray], float | np.ndarray]:
"""
Creates a square root function with the parameters of the fit.
Returns
-------
function : Callable
Square root function with the parameters of the fit.
"""
return lambda x: (
self._parameters[0] * np.sqrt(x + self._parameters[1]) + self._parameters[2]
)
[docs]
class FitFromLog(GeneralFit):
"""
Create a curve fit (continuous :class:`~graphinglib.data_plotting_1d.Curve`) from an existing
:class:`~graphinglib.data_plotting_1d.Curve` object using a logarithmic fit.
Fits a logarithmic function of the form :math:`f(x) = a \\log_{base}(x + b) + c` to the given curve. All standard
:class:`~graphinglib.data_plotting_1d.Curve` attributes and methods are available.
Parameters
----------
curve_to_be_fit : :class:`~graphinglib.data_plotting_1d.Curve` or :class:`~graphinglib.data_plotting_1d.Scatter`
The object to be fit.
label : str, optional
Label to be displayed in the legend.
log_base : float
Base of the logarithm.
Default is e.
guesses : ArrayLike, optional
Initial guesses for the parameters of the fit. Order is a, b, c as written above.
color : str
Color of the curve.
Default depends on the ``figure_style`` configuration.
line_width : int
Line width of the curve.
Typical range is ``0.5`` to ``4``.
Default depends on the ``figure_style`` configuration.
line_style : str
Line style of the curve.
Values include ``"-"``, ``"--"``, ``"-."``, ``":"``, ``"solid"``, ``"dashed"``, ``"dashdot"``, and
``"dotted"``.
Default depends on the ``figure_style`` configuration.
alpha : float
Opacity of the curve.
Range is ``0`` (transparent) to ``1`` (opaque).
Default depends on the ``figure_style`` configuration.
max_iterations : int
Maximum number of iterations for the fit.
Default is 10000.
Notes
-----
Color parameters accept Matplotlib color formats: named colors (``"blue"``), short color strings
(``"b"``), hex strings (``"#0000ff"``), grayscale strings (``"0.5"``), and RGB/RGBA tuples with
values between ``0`` and ``1`` (``(0, 0, 1)`` or ``(0, 0, 1, 0.5)``).
Attributes
----------
parameters : np.ndarray
Parameters of the fit (same order as guesses).
cov_matrix : np.ndarray
Covariance matrix of the parameters of the fit.
standard_deviation : np.ndarray
Standard deviation of the parameters of the fit.
function : Callable
Logarithmic function with the parameters of the fit.
"""
[docs]
def __init__(
self,
curve_to_be_fit: Curve | Scatter,
label: Optional[str] = None,
log_base: float = np.e,
guesses: Optional[ArrayLike] = None,
color: str | Inherit = INHERIT,
line_width: int | Inherit = INHERIT,
line_style: str | Inherit = INHERIT,
alpha: float | Inherit = INHERIT,
max_iterations: int = 10000,
) -> None:
"""
Create a curve fit (continuous :class:`~graphinglib.data_plotting_1d.Curve`) from an existing
:class:`~graphinglib.data_plotting_1d.Curve` object using a logarithmic fit.
Fits a logarithmic function of the form :math:`f(x) = a \\log_{base}(x + b) + c` to the given curve. All standard
:class:`~graphinglib.data_plotting_1d.Curve` attributes and methods are available.
Parameters
----------
curve_to_be_fit : :class:`~graphinglib.data_plotting_1d.Curve` or :class:`~graphinglib.data_plotting_1d.Scatter`
The object to be fit.
label : str, optional
Label to be displayed in the legend.
log_base : float
Base of the logarithm.
Default is e.
guesses : ArrayLike, optional
Initial guesses for the parameters of the fit. Order is a, b, c as written above.
color : str
Color of the curve.
Default depends on the ``figure_style`` configuration.
line_width : int
Line width of the curve.
Typical range is ``0.5`` to ``4``.
Default depends on the ``figure_style`` configuration.
line_style : str
Line style of the curve.
Values include ``"-"``, ``"--"``, ``"-."``, ``":"``, ``"solid"``, ``"dashed"``, ``"dashdot"``, and
``"dotted"``.
Default depends on the ``figure_style`` configuration.
alpha : float
Opacity of the curve.
Range is ``0`` (transparent) to ``1`` (opaque).
Default depends on the ``figure_style`` configuration.
max_iterations : int
Maximum number of iterations for the fit.
Default is 10000.
Notes
-----
Color parameters accept Matplotlib color formats: named colors (``"blue"``), short color strings
(``"b"``), hex strings (``"#0000ff"``), grayscale strings (``"0.5"``), and RGB/RGBA tuples with
values between ``0`` and ``1`` (``(0, 0, 1)`` or ``(0, 0, 1, 0.5)``).
Attributes
----------
parameters : np.ndarray
Parameters of the fit (same order as guesses).
cov_matrix : np.ndarray
Covariance matrix of the parameters of the fit.
standard_deviation : np.ndarray
Standard deviation of the parameters of the fit.
function : Callable
Logarithmic function with the parameters of the fit.
"""
self._curve_to_be_fit = curve_to_be_fit
self._log_base = log_base
self._guesses = guesses
self._max_iterations = max_iterations
self._calculate_parameters()
self._function = self._log_func_with_params()
self._color = color
if label:
self._label = label + " : " + str(self)
else:
self._label = str(self)
self._line_width = line_width
self._line_style = line_style
self._alpha = alpha
self._res_curves_to_be_plotted = False
number_of_points = (
len(self._curve_to_be_fit._x_data)
if len(self._curve_to_be_fit._x_data) > 500
else 500
)
self._x_data = np.linspace(
np.min(self._curve_to_be_fit._x_data),
np.max(self._curve_to_be_fit._x_data),
number_of_points,
)
self._y_data = self._function(self._x_data)
self._setup_attributes()
@property
def max_iterations(self) -> int:
return self._max_iterations
def __str__(self) -> str:
"""
Creates a string representation of the logarithmic function.
"""
return f"${self._parameters[0]:.3f} log_{self._log_base if self._log_base != np.e else 'e'}(x {'-' if self._parameters[1] < 0 else '+'} {abs(self._parameters[1]):.3f}) {'-' if self._parameters[2] < 0 else '+'} {abs(self._parameters[2]):.3f}$"
def _calculate_parameters(self) -> None:
"""
Calculates the parameters of the fit.
"""
self._parameters, self._cov_matrix = _run_curve_fit(
self._log_func_template(),
self._curve_to_be_fit._x_data,
self._curve_to_be_fit._y_data,
p0=self._guesses,
maxfev=self._max_iterations,
)
self._standard_deviation = np.sqrt(np.diag(self._cov_matrix))
def _log_func_template(
self,
) -> Callable[[float | np.ndarray, float, float, float], float | np.ndarray]:
"""
Function to be passed to the ``curve_fit`` function.
"""
return lambda x, a, b, c: a * (np.log(x + b) / np.log(self._log_base)) + c
def _log_func_with_params(
self,
) -> Callable[[float | np.ndarray], float | np.ndarray]:
"""
Creates a logarithmic function with the parameters of the fit.
Returns
-------
function : Callable
Logarithmic function with the parameters of the fit.
"""
return lambda x: (
self._parameters[0]
* (np.log(x + self._parameters[1]) / np.log(self._log_base))
+ self._parameters[2]
)
[docs]
class FitFromFunction(GeneralFit):
"""
Create a curve fit (continuous :class:`~graphinglib.data_plotting_1d.Curve`) from a
:class:`~graphinglib.data_plotting_1d.Curve` object using an arbitrary function passed as an argument.
Fits a function of the form :math:`f(x, a, b, c, ...)` to the given curve. All standard
:class:`~graphinglib.data_plotting_1d.Curve` attributes and methods are available.
Parameters
----------
function : Callable
Function to fit. The first argument must be the x values, followed by
fit parameters (``f(x, a, b, c, ...)``), and it must return scalar or
array-like y values.
curve_to_be_fit : :class:`~graphinglib.data_plotting_1d.Curve` or :class:`~graphinglib.data_plotting_1d.Scatter`
The object to be fit.
label : str, optional
Label to be displayed in the legend.
guesses : ArrayLike, optional
Initial guesses for the parameters of the fit. Order is a, b, c, ...
color : str
Color of the curve.
Default depends on the ``figure_style`` configuration.
line_width : int
Line width of the curve.
Typical range is ``0.5`` to ``4``.
Default depends on the ``figure_style`` configuration.
line_style : str
Line style of the curve.
Values include ``"-"``, ``"--"``, ``"-."``, ``":"``, ``"solid"``, ``"dashed"``, ``"dashdot"``, and
``"dotted"``.
Default depends on the ``figure_style`` configuration.
alpha : float
Opacity of the curve.
Range is ``0`` (transparent) to ``1`` (opaque).
Default depends on the ``figure_style`` configuration.
max_iterations : int
Maximum number of iterations for the fit.
Default is 10000.
Notes
-----
Color parameters accept Matplotlib color formats: named colors (``"blue"``), short color strings
(``"b"``), hex strings (``"#0000ff"``), grayscale strings (``"0.5"``), and RGB/RGBA tuples with
values between ``0`` and ``1`` (``(0, 0, 1)`` or ``(0, 0, 1, 0.5)``).
Attributes
----------
parameters : np.ndarray
Parameters of the fit (same order as guesses).
cov_matrix : np.ndarray
Covariance matrix of the parameters of the fit.
standard_deviation : np.ndarray
Standard deviation of the parameters of the fit.
function : Callable
Fitted function with the parameters bound. Accepts scalar or array x values.
"""
[docs]
def __init__(
self,
function: Callable[..., float | np.ndarray],
curve_to_be_fit: Curve | Scatter,
label: Optional[str] = None,
guesses: Optional[ArrayLike] = None,
color: str | Inherit = INHERIT,
line_width: int | Inherit = INHERIT,
line_style: str | Inherit = INHERIT,
alpha: float | Inherit = INHERIT,
max_iterations: int = 10000,
):
"""
Create a curve fit (continuous :class:`~graphinglib.data_plotting_1d.Curve`) from a
:class:`~graphinglib.data_plotting_1d.Curve` object using an arbitrary function passed as an argument.
Fits a function of the form :math:`f(x, a, b, c, ...)` to the given curve. All standard
:class:`~graphinglib.data_plotting_1d.Curve` attributes and methods are available.
Parameters
----------
function : Callable
Function to fit. The first argument must be the x values, followed by
fit parameters (``f(x, a, b, c, ...)``), and it must return scalar or
array-like y values.
curve_to_be_fit : :class:`~graphinglib.data_plotting_1d.Curve` or :class:`~graphinglib.data_plotting_1d.Scatter`
The object to be fit.
label : str, optional
Label to be displayed in the legend.
guesses : ArrayLike, optional
Initial guesses for the parameters of the fit. Order is a, b, c, ... as written above.
color : str
Color of the curve.
Default depends on the ``figure_style`` configuration.
line_width : int
Line width of the curve.
Typical range is ``0.5`` to ``4``.
Default depends on the ``figure_style`` configuration.
line_style : str
Line style of the curve.
Values include ``"-"``, ``"--"``, ``"-."``, ``":"``, ``"solid"``, ``"dashed"``, ``"dashdot"``, and
``"dotted"``.
Default depends on the ``figure_style`` configuration.
alpha : float
Opacity of the curve.
Range is ``0`` (transparent) to ``1`` (opaque).
Default depends on the ``figure_style`` configuration.
max_iterations : int
Maximum number of iterations for the fit.
Default is 10000.
Notes
-----
Color parameters accept Matplotlib color formats: named colors (``"blue"``), short color strings
(``"b"``), hex strings (``"#0000ff"``), grayscale strings (``"0.5"``), and RGB/RGBA tuples with
values between ``0`` and ``1`` (``(0, 0, 1)`` or ``(0, 0, 1, 0.5)``).
Attributes
----------
parameters : np.ndarray
Parameters of the fit (same order as guesses).
cov_matrix : np.ndarray
Covariance matrix of the parameters of the fit.
standard_deviation : np.ndarray
Standard deviation of the parameters of the fit.
function : Callable
Fitted function with the parameters bound. Accepts scalar or array x values.
"""
self._function_template = function
self._curve_to_be_fit = curve_to_be_fit
self._guesses = guesses
self._color = color
self._line_width = line_width
self._line_style = line_style
self._alpha = alpha
self._max_iterations = max_iterations
self._calculate_parameters()
self._function = self._get_function_with_params()
if label:
self._label = label + " : " + str(self)
else:
self._label = str(self)
self._res_curves_to_be_plotted = False
number_of_points = (
len(self._curve_to_be_fit._x_data)
if len(self._curve_to_be_fit._x_data) > 500
else 500
)
self._x_data = np.linspace(
np.min(self._curve_to_be_fit._x_data),
np.max(self._curve_to_be_fit._x_data),
number_of_points,
)
self._y_data = self._function(self._x_data)
self._setup_attributes()
@property
def max_iterations(self) -> int:
return self._max_iterations
def __str__(self) -> str:
"""
Creates a string representation of the fitted function.
"""
function_name = getattr(
self._function_template,
"__name__",
type(self._function_template).__name__,
)
return f"Fit from function: {function_name}"
def _calculate_parameters(self) -> None:
"""
Calculates the parameters of the fit.
"""
self._parameters, self._cov_matrix = _run_curve_fit(
self._function_template,
self._curve_to_be_fit._x_data,
self._curve_to_be_fit._y_data,
p0=self._guesses,
)
self._standard_deviation = np.sqrt(np.diag(self._cov_matrix))
def _get_function_with_params(
self,
) -> Callable[[float | np.ndarray], float | np.ndarray]:
"""
Creates a function with the parameters of the fit.
Returns
-------
function : Callable
Fitted function with the parameters bound. Accepts scalar or array x values.
"""
argument_names = list(signature(self._function_template).parameters)[1:]
args_dict = {
argument_names[i]: self._parameters[i] for i in range(len(argument_names))
}
return cast(
Callable[[float | np.ndarray], float | np.ndarray],
partial(self._function_template, **args_dict),
)
[docs]
class FitFromFOTF(GeneralFit):
"""
Create a curve fit (continuous :class:`~graphinglib.data_plotting_1d.Curve`) from an existing :class:`~graphinglib.data_plotting_1d.Curve` object using a first order transfer function (FOTF) fit.
Fits a first order transfer function of the form :math:`f(x) = K\\left(1-e^{-\\frac{t}{\\tau}}\\right)` to the given curve. All standard :class:`~graphinglib.data_plotting_1d.Curve` attributes and methods are available.
Parameters
----------
curve_to_be_fit : :class:`~graphinglib.data_plotting_1d.Curve` or :class:`~graphinglib.data_plotting_1d.Scatter`
The object to be fit.
label : str, optional
Label to be displayed in the legend.
guesses : ArrayLike, optional
Initial guesses for the parameters of the fit. Order is K, tau.
color : str
Color of the curve.
Default depends on the ``figure_style`` configuration.
line_width : int
Line width of the curve.
Typical range is ``0.5`` to ``4``.
Default depends on the ``figure_style`` configuration.
line_style : str
Line style of the curve.
Values include ``"-"``, ``"--"``, ``"-."``, ``":"``, ``"solid"``, ``"dashed"``, ``"dashdot"``, and
``"dotted"``.
Default depends on the ``figure_style`` configuration.
alpha : float
Opacity of the curve.
Range is ``0`` (transparent) to ``1`` (opaque).
Default depends on the ``figure_style`` configuration.
max_iterations : int
Maximum number of iterations for the fit.
Default is 10000.
Notes
-----
Color parameters accept Matplotlib color formats: named colors (``"blue"``), short color strings
(``"b"``), hex strings (``"#0000ff"``), grayscale strings (``"0.5"``), and RGB/RGBA tuples with
values between ``0`` and ``1`` (``(0, 0, 1)`` or ``(0, 0, 1, 0.5)``).
Attributes
----------
gain : float
Gain of the first order transfer function.
time_constant : float
Time constant of the first order transfer function.
cov_matrix : np.ndarray
Covariance matrix of the parameters of the fit.
standard_deviation : np.ndarray
Standard deviation of the parameters of the fit.
function : Callable
First order transfer function with the parameters of the fit.
"""
[docs]
def __init__(
self,
curve_to_be_fit: Curve | Scatter,
label: Optional[str] = None,
guesses: Optional[ArrayLike] = None,
color: str | Inherit = INHERIT,
line_width: int | Inherit = INHERIT,
line_style: str | Inherit = INHERIT,
alpha: float | Inherit = INHERIT,
max_iterations: int = 10000,
) -> None:
"""
Create a curve fit (continuous :class:`~graphinglib.data_plotting_1d.Curve`) from an existing :class:`~graphinglib.data_plotting_1d.Curve` object using a first order transfer function (FOTF) fit.
Fits a first order transfer function of the form :math:`f(x) = K \\left(1 - e^{-\\frac{t}{\\tau}}\\right)` to the given curve. All standard :class:`~graphinglib.data_plotting_1d.Curve` attributes and methods are available.
Parameters
----------
curve_to_be_fit : :class:`~graphinglib.data_plotting_1d.Curve` or :class:`~graphinglib.data_plotting_1d.Scatter`
The object to be fit.
label : str, optional
Label to be displayed in the legend.
guesses : ArrayLike, optional
Initial guesses for the parameters of the fit. Order is K, tau.
color : str
Color of the curve.
Default depends on the ``figure_style`` configuration.
line_width : int
Line width of the curve.
Typical range is ``0.5`` to ``4``.
Default depends on the ``figure_style`` configuration.
line_style : str
Line style of the curve.
Values include ``"-"``, ``"--"``, ``"-."``, ``":"``, ``"solid"``, ``"dashed"``, ``"dashdot"``, and
``"dotted"``.
Default depends on the ``figure_style`` configuration.
alpha : float
Opacity of the curve.
Range is ``0`` (transparent) to ``1`` (opaque).
Default depends on the ``figure_style`` configuration.
max_iterations : int
Maximum number of iterations for the fit.
Default is 10000.
Notes
-----
Color parameters accept Matplotlib color formats: named colors (``"blue"``), short color strings
(``"b"``), hex strings (``"#0000ff"``), grayscale strings (``"0.5"``), and RGB/RGBA tuples with
values between ``0`` and ``1`` (``(0, 0, 1)`` or ``(0, 0, 1, 0.5)``).
Attributes
----------
gain : float
Gain of the first order transfer function.
time_constant : float
Time constant of the first order transfer function.
cov_matrix : np.ndarray
Covariance matrix of the parameters of the fit.
standard_deviation : np.ndarray
Standard deviation of the parameters of the fit.
function : Callable
First order transfer function with the parameters of the fit.
"""
self._curve_to_be_fit = curve_to_be_fit
self._guesses = guesses
self._max_iterations = max_iterations
self._calculate_parameters()
self._function = self._fotf_func_with_params()
self._color = color
if label:
self._label = label + " : " + str(self)
else:
self._label = str(self)
self._line_width = line_width
self._line_style = line_style
self._alpha = alpha
self._res_curves_to_be_plotted = False
number_of_points = (
len(self._curve_to_be_fit._x_data)
if len(self._curve_to_be_fit._x_data) > 500
else 500
)
self._x_data = np.linspace(
np.min(self._curve_to_be_fit._x_data),
np.max(self._curve_to_be_fit._x_data),
number_of_points,
)
self._y_data = self._function(self._x_data)
self._setup_attributes()
@property
def max_iterations(self) -> int:
return self._max_iterations
@property
def gain(self) -> float:
return self._gain
@property
def time_constant(self) -> float:
return self._time_constant
def __str__(self) -> str:
"""
Creates a string representation of the first order transfer function.
"""
return rf"$K = {self._gain:.3f}, \tau = {self._time_constant:.3f}$"
def _calculate_parameters(self) -> None:
"""
Calculates the parameters of the fit.
"""
guesses = self._guesses
if guesses is not None:
# The time constant guess must be positive to satisfy the fit's bounds,
# regardless of the sign the caller happened to guess.
guesses = _repair_positive_guess(guesses, index=1, number_of_parameters=2)
self._parameters, self._cov_matrix = _run_curve_fit(
self._fotf_func_template,
self._curve_to_be_fit._x_data,
self._curve_to_be_fit._y_data,
p0=guesses,
maxfev=self._max_iterations,
bounds=([-np.inf, 1e-10], [np.inf, np.inf]),
)
self._gain = self._parameters[0]
self._time_constant = self._parameters[1]
self._standard_deviation = np.sqrt(np.diag(self._cov_matrix))
@staticmethod
def _fotf_func_template(
x: np.ndarray, gain: float, time_constant: float
) -> np.ndarray:
"""
Function to be passed to the ``curve_fit`` function.
"""
return gain * (1 - np.exp(-x / time_constant))
def _fotf_func_with_params(
self,
) -> Callable[[float | np.ndarray], float | np.ndarray]:
"""
Creates a first order transfer function with the parameters of the fit.
Returns
-------
function : Callable
First order transfer function with the parameters of the fit.
"""
return lambda x: self._gain * (1 - np.exp(-x / self._time_constant))