Source code for fitting.functions.trigonometric

"""Trigonometric and hyperbolic fitting functions."""

from typing import Callable, List, Optional, Tuple

import numpy as np
from numpy.typing import NDArray

from fitting.functions._base import (
    DataLike,
    Numeric,
    estimate_hyperbolic_bounds,
    estimate_hyperbolic_parameters,
    estimate_phase_shift,
    estimate_trigonometric_parameters,
    generic_fit,
    get_equation_format_for_function,
    get_equation_param_names_for_function,
    merge_bounds,
    merge_initial_guess,
)


[docs] def generate_trigonometric_function( func_type: str, with_phase: bool = False ) -> Callable[..., Numeric]: """ Generate a trigonometric function dynamically based on the function type. Args: func_type: Type of function ('sin', 'cos', 'sinh', 'cosh'). with_phase: Whether to include a phase shift parameter c. Returns: A function that takes t and parameters (a, b, [c]) as arguments. """ func_map = {"sin": np.sin, "cos": np.cos, "sinh": np.sinh, "cosh": np.cosh} if func_type not in func_map: raise ValueError(f"Unknown function type: {func_type}") np_func = func_map[func_type] if with_phase: def trig_func(t: Numeric, a: float, b: float, c: float) -> Numeric: return a * np_func(b * t + c) else: def trig_func(t: Numeric, a: float, b: float) -> Numeric: return a * np_func(b * t) return trig_func
sin_function = generate_trigonometric_function("sin", with_phase=False) sin_function_with_c = generate_trigonometric_function("sin", with_phase=True) cos_function = generate_trigonometric_function("cos", with_phase=False) cos_function_with_c = generate_trigonometric_function("cos", with_phase=True) sinh_function = generate_trigonometric_function("sinh", with_phase=False) cosh_function = generate_trigonometric_function("cosh", with_phase=False)
[docs] def tan_function(t: Numeric, a: float, b: float) -> Numeric: """Tangent function: y = a * tan(b*t)""" return a * np.tan(b * t)
[docs] def tan_function_with_c(t: Numeric, a: float, b: float, c: float) -> Numeric: """Tangent function with phase: y = a * tan(b*t + c)""" return a * np.tan(b * t + c)
[docs] def fit_sin_function( data: DataLike, x_name: str, y_name: str, initial_guess_override: Optional[List[Optional[float]]] = None, bounds_override: Optional[ Tuple[List[Optional[float]], List[Optional[float]]] ] = None, ) -> Tuple[str, NDArray, str]: """ Fit a sine model without phase, :math:`y = a \\sin(b x)`. Args: data: Data source with ``x``, ``y`` and uncertainties. x_name: Name of the independent variable column. y_name: Name of the dependent variable column. initial_guess_override: Optional overrides for ``[a, b]``. bounds_override: Optional bounds for ``[a, b]``. Returns: Tuple ``(text, y_fitted, equation)`` from :func:`generic_fit`. """ x = data[x_name] y = data[y_name] amplitude, frequency = estimate_trigonometric_parameters(x, y) initial_guess = merge_initial_guess([amplitude, frequency], initial_guess_override) bounds = ( merge_bounds(None, bounds_override[0], bounds_override[1], 2) if bounds_override is not None else None ) return generic_fit( data, x_name, y_name, fit_func=sin_function, param_names=get_equation_param_names_for_function("fit_sin_function"), equation_template=get_equation_format_for_function("fit_sin_function"), initial_guess=initial_guess, bounds=bounds, )
[docs] def fit_sin_function_with_c( data: DataLike, x_name: str, y_name: str, initial_guess_override: Optional[List[Optional[float]]] = None, bounds_override: Optional[ Tuple[List[Optional[float]], List[Optional[float]]] ] = None, ) -> Tuple[str, NDArray, str]: """ Fit a sine model with phase, :math:`y = a \\sin(b x + c)`. Args: data: Data source with ``x``, ``y`` and uncertainties. x_name: Name of the independent variable column. y_name: Name of the dependent variable column. initial_guess_override: Optional overrides for ``[a, b, c]``. bounds_override: Optional bounds for ``[a, b, c]``. Returns: Tuple ``(text, y_fitted, equation)`` from :func:`generic_fit`. """ x = data[x_name] y = data[y_name] amplitude, frequency = estimate_trigonometric_parameters(x, y) phase = estimate_phase_shift(x, y, amplitude, frequency) initial_guess = merge_initial_guess( [amplitude, frequency, phase], initial_guess_override ) bounds = ( merge_bounds(None, bounds_override[0], bounds_override[1], 3) if bounds_override is not None else None ) return generic_fit( data, x_name, y_name, fit_func=sin_function_with_c, param_names=get_equation_param_names_for_function("fit_sin_function_with_c"), equation_template=get_equation_format_for_function("fit_sin_function_with_c"), initial_guess=initial_guess, bounds=bounds, )
[docs] def fit_cos_function( data: DataLike, x_name: str, y_name: str, initial_guess_override: Optional[List[Optional[float]]] = None, bounds_override: Optional[ Tuple[List[Optional[float]], List[Optional[float]]] ] = None, ) -> Tuple[str, NDArray, str]: """ Fit a cosine model without phase, :math:`y = a \\cos(b x)`. Args: data: Data source with ``x``, ``y`` and uncertainties. x_name: Name of the independent variable column. y_name: Name of the dependent variable column. initial_guess_override: Optional overrides for ``[a, b]``. bounds_override: Optional bounds for ``[a, b]``. Returns: Tuple ``(text, y_fitted, equation)`` from :func:`generic_fit`. """ x = data[x_name] y = data[y_name] amplitude, frequency = estimate_trigonometric_parameters(x, y) initial_guess = merge_initial_guess([amplitude, frequency], initial_guess_override) bounds = ( merge_bounds(None, bounds_override[0], bounds_override[1], 2) if bounds_override is not None else None ) return generic_fit( data, x_name, y_name, fit_func=cos_function, param_names=get_equation_param_names_for_function("fit_cos_function"), equation_template=get_equation_format_for_function("fit_cos_function"), initial_guess=initial_guess, bounds=bounds, )
[docs] def fit_cos_function_with_c( data: DataLike, x_name: str, y_name: str, initial_guess_override: Optional[List[Optional[float]]] = None, bounds_override: Optional[ Tuple[List[Optional[float]], List[Optional[float]]] ] = None, ) -> Tuple[str, NDArray, str]: """ Fit a cosine model with phase, :math:`y = a \\cos(b x + c)`. Args: data: Data source with ``x``, ``y`` and uncertainties. x_name: Name of the independent variable column. y_name: Name of the dependent variable column. initial_guess_override: Optional overrides for ``[a, b, c]``. bounds_override: Optional bounds for ``[a, b, c]``. Returns: Tuple ``(text, y_fitted, equation)`` from :func:`generic_fit`. """ x = data[x_name] y = data[y_name] amplitude, frequency = estimate_trigonometric_parameters(x, y) phase = estimate_phase_shift(x, y, amplitude, frequency) initial_guess = merge_initial_guess( [amplitude, frequency, phase], initial_guess_override ) bounds = ( merge_bounds(None, bounds_override[0], bounds_override[1], 3) if bounds_override is not None else None ) return generic_fit( data, x_name, y_name, fit_func=cos_function_with_c, param_names=get_equation_param_names_for_function("fit_cos_function_with_c"), equation_template=get_equation_format_for_function("fit_cos_function_with_c"), initial_guess=initial_guess, bounds=bounds, )
[docs] def fit_sinh_function( data: DataLike, x_name: str, y_name: str, initial_guess_override: Optional[List[Optional[float]]] = None, bounds_override: Optional[ Tuple[List[Optional[float]], List[Optional[float]]] ] = None, ) -> Tuple[str, NDArray, str]: """ Fit a hyperbolic sine model, :math:`y = a \\sinh(b x)`. Args: data: Data source with ``x``, ``y`` and uncertainties. x_name: Name of the independent variable column. y_name: Name of the dependent variable column. initial_guess_override: Optional overrides for ``[a, b]``. bounds_override: Optional bounds for ``[a, b]``. Returns: Tuple ``(text, y_fitted, equation)`` from :func:`generic_fit`. """ x = data[x_name] y = data[y_name] amplitude, frequency = estimate_hyperbolic_parameters(x, y) initial_guess = merge_initial_guess([amplitude, frequency], initial_guess_override) computed_bounds = estimate_hyperbolic_bounds(x) bounds = ( merge_bounds(computed_bounds, bounds_override[0], bounds_override[1], 2) if bounds_override is not None else computed_bounds ) return generic_fit( data, x_name, y_name, fit_func=sinh_function, param_names=get_equation_param_names_for_function("fit_sinh_function"), equation_template=get_equation_format_for_function("fit_sinh_function"), initial_guess=initial_guess, bounds=bounds, )
[docs] def fit_cosh_function( data: DataLike, x_name: str, y_name: str, initial_guess_override: Optional[List[Optional[float]]] = None, bounds_override: Optional[ Tuple[List[Optional[float]], List[Optional[float]]] ] = None, ) -> Tuple[str, NDArray, str]: """ Fit a hyperbolic cosine model, :math:`y = a \\cosh(b x)`. Args: data: Data source with ``x``, ``y`` and uncertainties. x_name: Name of the independent variable column. y_name: Name of the dependent variable column. initial_guess_override: Optional overrides for ``[a, b]``. bounds_override: Optional bounds for ``[a, b]``. Returns: Tuple ``(text, y_fitted, equation)`` from :func:`generic_fit`. """ x = data[x_name] y = data[y_name] amplitude, frequency = estimate_hyperbolic_parameters(x, y) initial_guess = merge_initial_guess([amplitude, frequency], initial_guess_override) computed_bounds = estimate_hyperbolic_bounds(x) bounds = ( merge_bounds(computed_bounds, bounds_override[0], bounds_override[1], 2) if bounds_override is not None else computed_bounds ) return generic_fit( data, x_name, y_name, fit_func=cosh_function, param_names=get_equation_param_names_for_function("fit_cosh_function"), equation_template=get_equation_format_for_function("fit_cosh_function"), initial_guess=initial_guess, bounds=bounds, )
[docs] def fit_tan_function( data: DataLike, x_name: str, y_name: str, initial_guess_override: Optional[List[Optional[float]]] = None, bounds_override: Optional[ Tuple[List[Optional[float]], List[Optional[float]]] ] = None, ) -> Tuple[str, NDArray, str]: """ Fit a tangent model without phase, :math:`y = a \\tan(b x)`. Args: data: Data source with ``x``, ``y`` and uncertainties. x_name: Name of the independent variable column. y_name: Name of the dependent variable column. initial_guess_override: Optional overrides for ``[a, b]``. bounds_override: Optional bounds for ``[a, b]``. Returns: Tuple ``(text, y_fitted, equation)`` from :func:`generic_fit`. """ x = data[x_name] y = data[y_name] amplitude, frequency = estimate_trigonometric_parameters(x, y) initial_guess = merge_initial_guess([amplitude, frequency], initial_guess_override) bounds = ( merge_bounds(None, bounds_override[0], bounds_override[1], 2) if bounds_override is not None else None ) return generic_fit( data, x_name, y_name, fit_func=tan_function, param_names=get_equation_param_names_for_function("fit_tan_function"), equation_template=get_equation_format_for_function("fit_tan_function"), initial_guess=initial_guess, bounds=bounds, )
[docs] def fit_tan_function_with_c( data: DataLike, x_name: str, y_name: str, initial_guess_override: Optional[List[Optional[float]]] = None, bounds_override: Optional[ Tuple[List[Optional[float]], List[Optional[float]]] ] = None, ) -> Tuple[str, NDArray, str]: """ Fit a tangent model with phase, :math:`y = a \\tan(b x + c)`. Args: data: Data source with ``x``, ``y`` and uncertainties. x_name: Name of the independent variable column. y_name: Name of the dependent variable column. initial_guess_override: Optional overrides for ``[a, b, c]``. bounds_override: Optional bounds for ``[a, b, c]``. Returns: Tuple ``(text, y_fitted, equation)`` from :func:`generic_fit`. """ x = data[x_name] y = data[y_name] amplitude, frequency = estimate_trigonometric_parameters(x, y) phase = estimate_phase_shift(x, y, amplitude, frequency) initial_guess = merge_initial_guess( [amplitude, frequency, phase], initial_guess_override ) bounds = ( merge_bounds(None, bounds_override[0], bounds_override[1], 3) if bounds_override is not None else None ) return generic_fit( data, x_name, y_name, fit_func=tan_function_with_c, param_names=get_equation_param_names_for_function("fit_tan_function_with_c"), equation_template=get_equation_format_for_function("fit_tan_function_with_c"), initial_guess=initial_guess, bounds=bounds, )