Source code for appliedchemlabwork_tayra.D1._calc_process

# SPDX-FileCopyrightText: 2026-present Tayra Sakurai <tayra_sakurai@icloud.com>
#
# SPDX-License-Identifier: AGPL-3.0-or-later
"""The process calculators."""
from scipy.optimize import least_squares
import numpy as np
from typing import Any, overload
import pandas
import numpy.typing as npt

__all__ = ['calc_k_from_data', 'calc_left']


@overload
def calc_k_from_data(
    k_pred: float | np.floating[Any],
    v_r: float | np.floating[Any],
    v_hcl: float | np.floating[Any],
    c_base: float | np.floating[Any],
    c_hcl: float | np.floating[Any],
    a: float | np.floating[Any],
    b: float | np.floating[Any],
    vt_or_table: np.ndarray[
        tuple[int],
        np.dtype[np.floating[Any]]
    ],
    t: np.ndarray[
        tuple[int],
        np.dtype[np.floating[Any]]
    ]
) -> pandas.DataFrame:
    return pandas.DataFrame()


@overload
def calc_k_from_data(
    k_pred: float | np.floating[Any],
    v_r: float | np.floating[Any],
    v_hcl: float | np.floating[Any],
    c_base: float | np.floating[Any],
    c_hcl: float | np.floating[Any],
    a: float | np.floating[Any],
    b: float | np.floating[Any],
    vt_or_table: pandas.DataFrame
) -> pandas.DataFrame:
    return pandas.DataFrame()


[docs] def calc_k_from_data( k_pred: float | np.floating[Any], v_r: float | np.floating[Any], v_hcl: float | np.floating[Any], c_base: float | np.floating[Any], c_hcl: float | np.floating[Any], a: float | np.floating[Any], b: float | np.floating[Any], vt_or_table: np.ndarray[ tuple[int], np.dtype[np.floating[Any]] ] | pandas.DataFrame, t: np.ndarray[ tuple[int], np.dtype[np.number[Any, int | float]] ] | None = None ): """Calculates the ``k`` value. Parameters ---------- k_pred : floating[Any] The predicted reaction pace coefficient. v_r : floating[Any] The collected volume of the reaction solution. v_hcl : floating[Any] The added volume of the HCl aq. c_base : floating[Any] The concentration of titrating base. c_hcl : floating[Any] The concentration of HCl used to stop the reaction. a : floating number The initial concentration (in molarity) of AcOEt in the reaction solution. b : floating number The initial concentration of the base in the reaction solution. vt_or_table : DataFrame or NDArray in shape (n,) The table of time and titrated volume of the base, or an ``NDArray`` which represents the titrated volume of the base. Other Parameters ---------------- t : NDArray in shape (n,) Necessary when you have given ``vt_or_table`` an ``NDArray``. The times when you collected the sample. Returns ------- df : DataFrame The table of values and errors. Raises ------ TypeError The parameters' types are not valid. Notes ----- When ``vt_or_table`` was given as a ``DataFrame``, the table style must be like: +-----------+-------+-------+ | t / s | 76 | ... | +-----------+-------+-------+ | Vt / cm^3 | 18.82 | ... | +-----------+-------+-------+ """ if ( isinstance(vt_or_table, np.ndarray) and isinstance(t, np.ndarray) ): result = least_squares( _calc_residual, np.array( ( k_pred, a, b, ) ), args=( vt_or_table, t, v_r, v_hcl, c_hcl, c_base, ) ) f = result.fun j = np.array(result.jac) dof = len(f) - len(result.x) rss = np.sum(f ** 2) mse = rss / dof cov_matrix = np.linalg.inv(np.dot(j.T, j)) * mse stderr = np.sqrt(np.diag(cov_matrix)) df = pandas.DataFrame( data=[ result.x, stderr ], columns=('Value', 'Error'), index=('k', 'a', 'b',) ) return df elif isinstance(vt_or_table, pandas.DataFrame): vt: pandas.Series[np.dtype[np.floating[Any]]] = vt_or_table.iloc[1] time: pandas.Series[np.dtype[np.floating[Any]]] = vt_or_table.iloc[0] result = least_squares( _calc_residual, np.array( ( k_pred, a, b, ) ), args=( vt.to_numpy(), time.to_numpy(), v_r, v_hcl, c_hcl, c_base, ) ) f = result.fun j = np.array(result.jac) dof = len(f) - len(result.x) rss = np.sum(f ** 2) mse = rss / dof cov_matrix = np.linalg.inv(np.dot(j.T, j)) * mse stderr = np.sqrt(np.diag(cov_matrix)) df = pandas.DataFrame( data=[ result.x, stderr ], columns=('Value', 'Error'), index=('k', 'a', 'b',) ) return df
[docs] def calc_left( x: npt.NDArray[np.floating[Any]], a: float | np.floating[Any], b: float | np.floating[Any] ) -> npt.NDArray[np.floating[Any]]: """Calculates the value which is proprtional to ``k`` and ``t``. Parameters ---------- x : NDArray[floating[Any]] The reduced concentration of the reactant. a : floating[Any] The initial concentration of the reactant ester. b : floating[Any] The initial concentration of the base. Returns ------- val : NDArray[floating[Any]] The value of the formula. Notes ----- The value :math:`v` is equivalent to .. math:: v ={} \\ln \\frac{a \\left( b -{} x \\right)}{b \\left( a -{} x \\right)} """ up = a * (b - x) den = b * (a - x) return np.log(up / den)
def _model( v_hcl: float | np.floating[Any], v_t: npt.NDArray[np.floating[Any]], c_base: float | np.floating[Any], c_hcl: float | np.floating[Any], v_r: float | np.floating[Any], a: float | np.floating[Any], b: float | np.floating[Any] ) -> npt.NDArray[np.floating[Any]]: x = (v_t * c_base - v_hcl * c_hcl + v_r * b) / (2 * v_r) return calc_left(x, a, b) def _calc_residual[ Shape: ( tuple[int], tuple[int, int], tuple[int, int, int], tuple[int, ...] ) ]( parameters: np.ndarray[ tuple[int], np.dtype[np.floating[Any]] ], v_t: np.ndarray[ Shape, np.dtype[np.floating[Any]] ], t: np.ndarray[ Shape, np.dtype[np.floating[Any]] ], v_r: float | np.floating[Any], v_hcl: float | np.floating[Any], c_hcl: float | np.floating[Any], c_base: float | np.floating[Any] ) -> np.floating[Any]: k, a, b = parameters residual = k * t - _model( v_hcl, v_t, c_base, c_hcl, v_r, a, b ) return np.sqrt(np.sum(residual ** 2))