From 3db8eb2989db390cfd91c8d9708acb70e864e68a Mon Sep 17 00:00:00 2001 From: Fabian Joswig Date: Wed, 25 Dec 2024 11:09:58 +0100 Subject: [PATCH 01/57] [Feat] Add type hints to pyerrors modules --- pyerrors/correlators.py | 133 +++++++++++++------------- pyerrors/covobs.py | 15 +-- pyerrors/dirac.py | 8 +- pyerrors/fits.py | 29 +++--- pyerrors/integrate.py | 5 +- pyerrors/linalg.py | 29 +++--- pyerrors/misc.py | 13 ++- pyerrors/mpm.py | 5 +- pyerrors/obs.py | 201 ++++++++++++++++++++-------------------- pyerrors/roots.py | 4 +- pyerrors/special.py | 1 + 11 files changed, 236 insertions(+), 207 deletions(-) diff --git a/pyerrors/correlators.py b/pyerrors/correlators.py index 0375155f..74db429d 100644 --- a/pyerrors/correlators.py +++ b/pyerrors/correlators.py @@ -1,3 +1,4 @@ +from __future__ import annotations import warnings from itertools import permutations import numpy as np @@ -9,6 +10,8 @@ from .fits import least_squares from .roots import find_root from . import linalg +from numpy import float64, int64, ndarray, ufunc +from typing import Any, Callable, List, Optional, Tuple, Union class Corr: @@ -42,7 +45,7 @@ class Corr: __slots__ = ["content", "N", "T", "tag", "prange"] - def __init__(self, data_input, padding=[0, 0], prange=None): + def __init__(self, data_input: Any, padding: List[int]=[0, 0], prange: Optional[List[int]]=None): """ Initialize a Corr object. Parameters @@ -119,7 +122,7 @@ def __init__(self, data_input, padding=[0, 0], prange=None): self.T = len(self.content) self.prange = prange - def __getitem__(self, idx): + def __getitem__(self, idx: Union[slice, int]) -> Union[CObs, Obs, ndarray, List[ndarray]]: """Return the content of timeslice idx""" if self.content[idx] is None: return None @@ -151,7 +154,7 @@ def gamma_method(self, **kwargs): gm = gamma_method - def projected(self, vector_l=None, vector_r=None, normalize=False): + def projected(self, vector_l: Optional[Union[ndarray, List[Optional[ndarray]]]]=None, vector_r: None=None, normalize: bool=False) -> "Corr": """We need to project the Correlator with a Vector to get a single value at each timeslice. The method can use one or two vectors. @@ -190,7 +193,7 @@ def projected(self, vector_l=None, vector_r=None, normalize=False): newcontent = [None if (_check_for_none(self, self.content[t]) or vector_l[t] is None or vector_r[t] is None) else np.asarray([vector_l[t].T @ self.content[t] @ vector_r[t]]) for t in range(self.T)] return Corr(newcontent) - def item(self, i, j): + def item(self, i: int, j: int) -> "Corr": """Picks the element [i,j] from every matrix and returns a correlator containing one Obs per timeslice. Parameters @@ -205,7 +208,7 @@ def item(self, i, j): newcontent = [None if (item is None) else item[i, j] for item in self.content] return Corr(newcontent) - def plottable(self): + def plottable(self) -> Union[Tuple[List[int], List[float64], List[float64]], Tuple[List[int], List[float], List[float64]]]: """Outputs the correlator in a plotable format. Outputs three lists containing the timeslice index, the value on each @@ -219,7 +222,7 @@ def plottable(self): return x_list, y_list, y_err_list - def symmetric(self): + def symmetric(self) -> "Corr": """ Symmetrize the correlator around x0=0.""" if self.N != 1: raise ValueError('symmetric cannot be safely applied to multi-dimensional correlators.') @@ -240,7 +243,7 @@ def symmetric(self): raise ValueError("Corr could not be symmetrized: No redundant values") return Corr(newcontent, prange=self.prange) - def anti_symmetric(self): + def anti_symmetric(self) -> "Corr": """Anti-symmetrize the correlator around x0=0.""" if self.N != 1: raise TypeError('anti_symmetric cannot be safely applied to multi-dimensional correlators.') @@ -277,7 +280,7 @@ def is_matrix_symmetric(self): return False return True - def trace(self): + def trace(self) -> "Corr": """Calculates the per-timeslice trace of a correlator matrix.""" if self.N == 1: raise ValueError("Only works for correlator matrices.") @@ -289,7 +292,7 @@ def trace(self): newcontent.append(np.trace(self.content[t])) return Corr(newcontent) - def matrix_symmetric(self): + def matrix_symmetric(self) -> "Corr": """Symmetrizes the correlator matrices on every timeslice.""" if self.N == 1: raise ValueError("Trying to symmetrize a correlator matrix, that already has N=1.") @@ -299,7 +302,7 @@ def matrix_symmetric(self): transposed = [None if _check_for_none(self, G) else G.T for G in self.content] return 0.5 * (Corr(transposed) + self) - def GEVP(self, t0, ts=None, sort="Eigenvalue", vector_obs=False, **kwargs): + def GEVP(self, t0: int, ts: Optional[int]=None, sort: Optional[str]="Eigenvalue", vector_obs: bool=False, **kwargs) -> Union[List[List[Optional[ndarray]]], ndarray, List[Optional[ndarray]]]: r'''Solve the generalized eigenvalue problem on the correlator matrix and returns the corresponding eigenvectors. The eigenvectors are sorted according to the descending eigenvalues, the zeroth eigenvector(s) correspond to the @@ -405,7 +408,7 @@ def _get_mat_at_t(t, vector_obs=vector_obs): else: return reordered_vecs - def Eigenvalue(self, t0, ts=None, state=0, sort="Eigenvalue", **kwargs): + def Eigenvalue(self, t0: int, ts: None=None, state: int=0, sort: str="Eigenvalue", **kwargs) -> "Corr": """Determines the eigenvalue of the GEVP by solving and projecting the correlator Parameters @@ -418,7 +421,7 @@ def Eigenvalue(self, t0, ts=None, state=0, sort="Eigenvalue", **kwargs): vec = self.GEVP(t0, ts=ts, sort=sort, **kwargs)[state] return self.projected(vec) - def Hankel(self, N, periodic=False): + def Hankel(self, N: int, periodic: bool=False) -> "Corr": """Constructs an NxN Hankel matrix C(t) c(t+1) ... c(t+n-1) @@ -459,7 +462,7 @@ def wrap(i): return Corr(new_content) - def roll(self, dt): + def roll(self, dt: int) -> "Corr": """Periodically shift the correlator by dt timeslices Parameters @@ -469,11 +472,11 @@ def roll(self, dt): """ return Corr(list(np.roll(np.array(self.content, dtype=object), dt, axis=0))) - def reverse(self): + def reverse(self) -> "Corr": """Reverse the time ordering of the Corr""" return Corr(self.content[:: -1]) - def thin(self, spacing=2, offset=0): + def thin(self, spacing: int=2, offset: int=0) -> "Corr": """Thin out a correlator to suppress correlations Parameters @@ -491,7 +494,7 @@ def thin(self, spacing=2, offset=0): new_content.append(self.content[t]) return Corr(new_content) - def correlate(self, partner): + def correlate(self, partner: Union[Corr, float, Obs]) -> "Corr": """Correlate the correlator with another correlator or Obs Parameters @@ -520,7 +523,7 @@ def correlate(self, partner): return Corr(new_content) - def reweight(self, weight, **kwargs): + def reweight(self, weight: Obs, **kwargs) -> "Corr": """Reweight the correlator. Parameters @@ -543,7 +546,7 @@ def reweight(self, weight, **kwargs): new_content.append(np.array(reweight(weight, t_slice, **kwargs))) return Corr(new_content) - def T_symmetry(self, partner, parity=+1): + def T_symmetry(self, partner: "Corr", parity: int=+1) -> "Corr": """Return the time symmetry average of the correlator and its partner Parameters @@ -573,7 +576,7 @@ def T_symmetry(self, partner, parity=+1): return (self + T_partner) / 2 - def deriv(self, variant="symmetric"): + def deriv(self, variant: Optional[str]="symmetric") -> "Corr": """Return the first derivative of the correlator with respect to x0. Parameters @@ -638,7 +641,7 @@ def deriv(self, variant="symmetric"): else: raise ValueError("Unknown variant.") - def second_deriv(self, variant="symmetric"): + def second_deriv(self, variant: Optional[str]="symmetric") -> "Corr": r"""Return the second derivative of the correlator with respect to x0. Parameters @@ -701,7 +704,7 @@ def second_deriv(self, variant="symmetric"): else: raise ValueError("Unknown variant.") - def m_eff(self, variant='log', guess=1.0): + def m_eff(self, variant: str='log', guess: float=1.0) -> "Corr": """Returns the effective mass of the correlator as correlator object Parameters @@ -785,7 +788,7 @@ def root_function(x, d): else: raise ValueError('Unknown variant.') - def fit(self, function, fitrange=None, silent=False, **kwargs): + def fit(self, function: Callable, fitrange: Optional[Union[str, List[int]]]=None, silent: bool=False, **kwargs) -> Fit_result: r'''Fits function to the data Parameters @@ -819,7 +822,7 @@ def fit(self, function, fitrange=None, silent=False, **kwargs): result = least_squares(xs, ys, function, silent=silent, **kwargs) return result - def plateau(self, plateau_range=None, method="fit", auto_gamma=False): + def plateau(self, plateau_range: Optional[List[int]]=None, method: str="fit", auto_gamma: bool=False) -> Obs: """ Extract a plateau value from a Corr object Parameters @@ -856,7 +859,7 @@ def const_func(a, t): else: raise ValueError("Unsupported plateau method: " + method) - def set_prange(self, prange): + def set_prange(self, prange: List[Union[int, float]]): """Sets the attribute prange of the Corr object.""" if not len(prange) == 2: raise ValueError("prange must be a list or array with two values") @@ -868,7 +871,7 @@ def set_prange(self, prange): self.prange = prange return - def show(self, x_range=None, comp=None, y_range=None, logscale=False, plateau=None, fit_res=None, fit_key=None, ylabel=None, save=None, auto_gamma=False, hide_sigma=None, references=None, title=None): + def show(self, x_range: Optional[List[int64]]=None, comp: Optional[Corr]=None, y_range: None=None, logscale: bool=False, plateau: None=None, fit_res: Optional[Fit_result]=None, fit_key: Optional[str]=None, ylabel: None=None, save: None=None, auto_gamma: bool=False, hide_sigma: None=None, references: None=None, title: None=None): """Plots the correlator using the tag of the correlator as label if available. Parameters @@ -993,7 +996,7 @@ def show(self, x_range=None, comp=None, y_range=None, logscale=False, plateau=No else: raise TypeError("'save' has to be a string.") - def spaghetti_plot(self, logscale=True): + def spaghetti_plot(self, logscale: bool=True): """Produces a spaghetti plot of the correlator suited to monitor exceptional configurations. Parameters @@ -1022,7 +1025,7 @@ def spaghetti_plot(self, logscale=True): plt.title(name) plt.draw() - def dump(self, filename, datatype="json.gz", **kwargs): + def dump(self, filename: str, datatype: str="json.gz", **kwargs): """Dumps the Corr into a file of chosen type Parameters ---------- @@ -1046,10 +1049,10 @@ def dump(self, filename, datatype="json.gz", **kwargs): else: raise ValueError("Unknown datatype " + str(datatype)) - def print(self, print_range=None): + def print(self, print_range: Optional[List[int]]=None): print(self.__repr__(print_range)) - def __repr__(self, print_range=None): + def __repr__(self, print_range: Optional[List[int]]=None) -> str: if print_range is None: print_range = [0, None] @@ -1074,7 +1077,7 @@ def __repr__(self, print_range=None): content_string += '\n' return content_string - def __str__(self): + def __str__(self) -> str: return self.__repr__() # We define the basic operations, that can be performed with correlators. @@ -1084,14 +1087,14 @@ def __str__(self): __array_priority__ = 10000 - def __eq__(self, y): + def __eq__(self, y: Union[Corr, Obs, int]) -> ndarray: if isinstance(y, Corr): comp = np.asarray(y.content, dtype=object) else: comp = np.asarray(y) return np.asarray(self.content, dtype=object) == comp - def __add__(self, y): + def __add__(self, y: Any) -> "Corr": if isinstance(y, Corr): if ((self.N != y.N) or (self.T != y.T)): raise ValueError("Addition of Corrs with different shape") @@ -1119,7 +1122,7 @@ def __add__(self, y): else: raise TypeError("Corr + wrong type") - def __mul__(self, y): + def __mul__(self, y: Any) -> "Corr": if isinstance(y, Corr): if not ((self.N == 1 or y.N == 1 or self.N == y.N) and self.T == y.T): raise ValueError("Multiplication of Corr object requires N=N or N=1 and T=T") @@ -1147,7 +1150,7 @@ def __mul__(self, y): else: raise TypeError("Corr * wrong type") - def __matmul__(self, y): + def __matmul__(self, y: Union[Corr, ndarray]) -> "Corr": if isinstance(y, np.ndarray): if y.ndim != 2 or y.shape[0] != y.shape[1]: raise ValueError("Can only multiply correlators by square matrices.") @@ -1174,7 +1177,7 @@ def __matmul__(self, y): else: return NotImplemented - def __rmatmul__(self, y): + def __rmatmul__(self, y: ndarray) -> "Corr": if isinstance(y, np.ndarray): if y.ndim != 2 or y.shape[0] != y.shape[1]: raise ValueError("Can only multiply correlators by square matrices.") @@ -1190,7 +1193,7 @@ def __rmatmul__(self, y): else: return NotImplemented - def __truediv__(self, y): + def __truediv__(self, y: Union[Corr, float, ndarray, int]) -> "Corr": if isinstance(y, Corr): if not ((self.N == 1 or y.N == 1 or self.N == y.N) and self.T == y.T): raise ValueError("Multiplication of Corr object requires N=N or N=1 and T=T") @@ -1244,37 +1247,37 @@ def __truediv__(self, y): else: raise TypeError('Corr / wrong type') - def __neg__(self): + def __neg__(self) -> "Corr": newcontent = [None if _check_for_none(self, item) else -1. * item for item in self.content] return Corr(newcontent, prange=self.prange) - def __sub__(self, y): + def __sub__(self, y: Union[Corr, float, ndarray, int]) -> "Corr": return self + (-y) - def __pow__(self, y): + def __pow__(self, y: Union[float, int]) -> "Corr": if isinstance(y, (Obs, int, float, CObs)): newcontent = [None if _check_for_none(self, item) else item**y for item in self.content] return Corr(newcontent, prange=self.prange) else: raise TypeError('Type of exponent not supported') - def __abs__(self): + def __abs__(self) -> "Corr": newcontent = [None if _check_for_none(self, item) else np.abs(item) for item in self.content] return Corr(newcontent, prange=self.prange) # The numpy functions: - def sqrt(self): + def sqrt(self) -> "Corr": return self ** 0.5 - def log(self): + def log(self) -> "Corr": newcontent = [None if _check_for_none(self, item) else np.log(item) for item in self.content] return Corr(newcontent, prange=self.prange) - def exp(self): + def exp(self) -> "Corr": newcontent = [None if _check_for_none(self, item) else np.exp(item) for item in self.content] return Corr(newcontent, prange=self.prange) - def _apply_func_to_corr(self, func): + def _apply_func_to_corr(self, func: Union[Callable, ufunc]) -> "Corr": newcontent = [None if _check_for_none(self, item) else func(item) for item in self.content] for t in range(self.T): if _check_for_none(self, newcontent[t]): @@ -1287,57 +1290,57 @@ def _apply_func_to_corr(self, func): raise ValueError('Operation returns undefined correlator') return Corr(newcontent) - def sin(self): + def sin(self) -> "Corr": return self._apply_func_to_corr(np.sin) - def cos(self): + def cos(self) -> "Corr": return self._apply_func_to_corr(np.cos) - def tan(self): + def tan(self) -> "Corr": return self._apply_func_to_corr(np.tan) - def sinh(self): + def sinh(self) -> "Corr": return self._apply_func_to_corr(np.sinh) - def cosh(self): + def cosh(self) -> "Corr": return self._apply_func_to_corr(np.cosh) - def tanh(self): + def tanh(self) -> "Corr": return self._apply_func_to_corr(np.tanh) - def arcsin(self): + def arcsin(self) -> "Corr": return self._apply_func_to_corr(np.arcsin) - def arccos(self): + def arccos(self) -> "Corr": return self._apply_func_to_corr(np.arccos) - def arctan(self): + def arctan(self) -> "Corr": return self._apply_func_to_corr(np.arctan) - def arcsinh(self): + def arcsinh(self) -> "Corr": return self._apply_func_to_corr(np.arcsinh) - def arccosh(self): + def arccosh(self) -> "Corr": return self._apply_func_to_corr(np.arccosh) - def arctanh(self): + def arctanh(self) -> "Corr": return self._apply_func_to_corr(np.arctanh) # Right hand side operations (require tweak in main module to work) def __radd__(self, y): return self + y - def __rsub__(self, y): + def __rsub__(self, y: int) -> "Corr": return -self + y - def __rmul__(self, y): + def __rmul__(self, y: Union[float, int]) -> "Corr": return self * y - def __rtruediv__(self, y): + def __rtruediv__(self, y: int) -> "Corr": return (self / y) ** (-1) @property - def real(self): + def real(self) -> "Corr": def return_real(obs_OR_cobs): if isinstance(obs_OR_cobs.flatten()[0], CObs): return np.vectorize(lambda x: x.real)(obs_OR_cobs) @@ -1347,7 +1350,7 @@ def return_real(obs_OR_cobs): return self._apply_func_to_corr(return_real) @property - def imag(self): + def imag(self) -> "Corr": def return_imag(obs_OR_cobs): if isinstance(obs_OR_cobs.flatten()[0], CObs): return np.vectorize(lambda x: x.imag)(obs_OR_cobs) @@ -1356,7 +1359,7 @@ def return_imag(obs_OR_cobs): return self._apply_func_to_corr(return_imag) - def prune(self, Ntrunc, tproj=3, t0proj=2, basematrix=None): + def prune(self, Ntrunc: int, tproj: int=3, t0proj: int=2, basematrix: None=None) -> "Corr": r''' Project large correlation matrix to lowest states This method can be used to reduce the size of an (N x N) correlation matrix @@ -1414,7 +1417,7 @@ def prune(self, Ntrunc, tproj=3, t0proj=2, basematrix=None): return Corr(newcontent) -def _sort_vectors(vec_set_in, ts): +def _sort_vectors(vec_set_in: List[Optional[ndarray]], ts: int) -> List[Optional[Union[ndarray, List[ndarray]]]]: """Helper function used to find a set of Eigenvectors consistent over all timeslices""" if isinstance(vec_set_in[ts][0][0], Obs): @@ -1446,12 +1449,12 @@ def _sort_vectors(vec_set_in, ts): return sorted_vec_set -def _check_for_none(corr, entry): +def _check_for_none(corr: Corr, entry: Optional[ndarray]) -> bool: """Checks if entry for correlator corr is None""" return len(list(filter(None, np.asarray(entry).flatten()))) < corr.N ** 2 -def _GEVP_solver(Gt, G0, method='eigh', chol_inv=None): +def _GEVP_solver(Gt: Optional[ndarray], G0: ndarray, method: str='eigh', chol_inv: Optional[ndarray]=None) -> ndarray: r"""Helper function for solving the GEVP and sorting the eigenvectors. Solves $G(t)v_i=\lambda_i G(t_0)v_i$ and returns the eigenvectors v_i diff --git a/pyerrors/covobs.py b/pyerrors/covobs.py index 64d9d6ec..e3056f04 100644 --- a/pyerrors/covobs.py +++ b/pyerrors/covobs.py @@ -1,9 +1,12 @@ +from __future__ import annotations import numpy as np +from numpy import float64, ndarray +from typing import Any, List, Optional, Union class Covobs: - def __init__(self, mean, cov, name, pos=None, grad=None): + def __init__(self, mean: Optional[Union[float, float64, int]], cov: Any, name: str, pos: Optional[int]=None, grad: Optional[Union[ndarray, List[float]]]=None): """ Initialize Covobs object. Parameters @@ -39,12 +42,12 @@ def __init__(self, mean, cov, name, pos=None, grad=None): self._set_grad(grad) self.value = mean - def errsq(self): + def errsq(self) -> float: """ Return the variance (= square of the error) of the Covobs """ return np.dot(np.transpose(self.grad), np.dot(self.cov, self.grad)).item() - def _set_cov(self, cov): + def _set_cov(self, cov: Any): """ Set the covariance matrix of the covobs Parameters @@ -79,7 +82,7 @@ def _set_cov(self, cov): if ev < 0: raise Exception('Covariance matrix is not positive-semidefinite!') - def _set_grad(self, grad): + def _set_grad(self, grad: Union[List[float], ndarray]): """ Set the gradient of the covobs Parameters @@ -96,9 +99,9 @@ def _set_grad(self, grad): raise Exception('Invalid dimension of grad!') @property - def cov(self): + def cov(self) -> ndarray: return self._cov @property - def grad(self): + def grad(self) -> ndarray: return self._grad diff --git a/pyerrors/dirac.py b/pyerrors/dirac.py index 016e4722..9d4244aa 100644 --- a/pyerrors/dirac.py +++ b/pyerrors/dirac.py @@ -1,4 +1,6 @@ +from __future__ import annotations import numpy as np +from numpy import ndarray gammaX = np.array( @@ -22,7 +24,7 @@ dtype=complex) -def epsilon_tensor(i, j, k): +def epsilon_tensor(i: int, j: int, k: int) -> float: """Rank-3 epsilon tensor Based on https://codegolf.stackexchange.com/a/160375 @@ -39,7 +41,7 @@ def epsilon_tensor(i, j, k): return (i - j) * (j - k) * (k - i) / 2 -def epsilon_tensor_rank4(i, j, k, o): +def epsilon_tensor_rank4(i: int, j: int, k: int, o: int) -> float: """Rank-4 epsilon tensor Extension of https://codegolf.stackexchange.com/a/160375 @@ -57,7 +59,7 @@ def epsilon_tensor_rank4(i, j, k, o): return (i - j) * (j - k) * (k - i) * (i - o) * (j - o) * (o - k) / 12 -def Grid_gamma(gamma_tag): +def Grid_gamma(gamma_tag: str) -> ndarray: """Returns gamma matrix in Grid labeling.""" if gamma_tag == 'Identity': g = identity diff --git a/pyerrors/fits.py b/pyerrors/fits.py index 8ed540c5..8a27c1b1 100644 --- a/pyerrors/fits.py +++ b/pyerrors/fits.py @@ -1,3 +1,4 @@ +from __future__ import annotations import gc from collections.abc import Sequence import warnings @@ -15,6 +16,8 @@ from numdifftools import Jacobian as num_jacobian from numdifftools import Hessian as num_hessian from .obs import Obs, derived_observable, covariance, cov_Obs, invert_corr_cov_cholesky +from numpy import ndarray +from typing import Any, Callable, Dict, List, Optional, Tuple, Union class Fit_result(Sequence): @@ -36,10 +39,10 @@ class Fit_result(Sequence): def __init__(self): self.fit_parameters = None - def __getitem__(self, idx): + def __getitem__(self, idx: int) -> Obs: return self.fit_parameters[idx] - def __len__(self): + def __len__(self) -> int: return len(self.fit_parameters) def gamma_method(self, **kwargs): @@ -48,7 +51,7 @@ def gamma_method(self, **kwargs): gm = gamma_method - def __str__(self): + def __str__(self) -> str: my_str = 'Goodness of fit:\n' if hasattr(self, 'chisquare_by_dof'): my_str += '\u03C7\u00b2/d.o.f. = ' + f'{self.chisquare_by_dof:2.6f}' + '\n' @@ -65,12 +68,12 @@ def __str__(self): my_str += str(i_par) + '\t' + ' ' * int(par >= 0) + str(par).rjust(int(par < 0.0)) + '\n' return my_str - def __repr__(self): + def __repr__(self) -> str: m = max(map(len, list(self.__dict__.keys()))) + 1 return '\n'.join([key.rjust(m) + ': ' + repr(value) for key, value in sorted(self.__dict__.items())]) -def least_squares(x, y, func, priors=None, silent=False, **kwargs): +def least_squares(x: Any, y: Union[Dict[str, ndarray], List[Obs], ndarray, Dict[str, List[Obs]]], func: Union[Callable, Dict[str, Callable]], priors: Optional[Union[Dict[int, str], List[str], List[Obs], Dict[int, Obs]]]=None, silent: bool=False, **kwargs) -> Fit_result: r'''Performs a non-linear fit to y = func(x). ``` @@ -503,7 +506,7 @@ def chisqfunc_compact(d): return output -def total_least_squares(x, y, func, silent=False, **kwargs): +def total_least_squares(x: List[Obs], y: List[Obs], func: Callable, silent: bool=False, **kwargs) -> Fit_result: r'''Performs a non-linear fit to y = func(x) and returns a list of Obs corresponding to the fit parameters. Parameters @@ -707,7 +710,7 @@ def odr_chisquare_compact_y(d): return output -def fit_lin(x, y, **kwargs): +def fit_lin(x: List[Union[Obs, int, float]], y: List[Obs], **kwargs) -> List[Obs]: """Performs a linear fit to y = n + m * x and returns two Obs n, m. Parameters @@ -738,7 +741,7 @@ def f(a, x): raise TypeError('Unsupported types for x') -def qqplot(x, o_y, func, p, title=""): +def qqplot(x: ndarray, o_y: List[Obs], func: Callable, p: List[Obs], title: str=""): """Generates a quantile-quantile plot of the fit result which can be used to check if the residuals of the fit are gaussian distributed. @@ -768,7 +771,7 @@ def qqplot(x, o_y, func, p, title=""): plt.draw() -def residual_plot(x, y, func, fit_res, title=""): +def residual_plot(x: ndarray, y: List[Obs], func: Callable, fit_res: List[Obs], title: str=""): """Generates a plot which compares the fit to the data and displays the corresponding residuals For uncorrelated data the residuals are expected to be distributed ~N(0,1). @@ -805,7 +808,7 @@ def residual_plot(x, y, func, fit_res, title=""): plt.draw() -def error_band(x, func, beta): +def error_band(x: List[int], func: Callable, beta: List[Obs]) -> ndarray: """Calculate the error band for an array of sample values x, for given fit function func with optimized parameters beta. Returns @@ -829,7 +832,7 @@ def error_band(x, func, beta): return err -def ks_test(objects=None): +def ks_test(objects: Optional[List[Fit_result]]=None): """Performs a Kolmogorov–Smirnov test for the p-values of all fit object. Parameters @@ -873,7 +876,7 @@ def ks_test(objects=None): print(scipy.stats.kstest(p_values, 'uniform')) -def _extract_val_and_dval(string): +def _extract_val_and_dval(string: str) -> Tuple[float, float]: split_string = string.split('(') if '.' in split_string[0] and '.' not in split_string[1][:-1]: factor = 10 ** -len(split_string[0].partition('.')[2]) @@ -882,7 +885,7 @@ def _extract_val_and_dval(string): return float(split_string[0]), float(split_string[1][:-1]) * factor -def _construct_prior_obs(i_prior, i_n): +def _construct_prior_obs(i_prior: Union[Obs, str], i_n: int) -> Obs: if isinstance(i_prior, Obs): return i_prior elif isinstance(i_prior, str): diff --git a/pyerrors/integrate.py b/pyerrors/integrate.py index 3b48f3fb..74e0e4a5 100644 --- a/pyerrors/integrate.py +++ b/pyerrors/integrate.py @@ -1,10 +1,13 @@ +from __future__ import annotations import numpy as np from .obs import derived_observable, Obs from autograd import jacobian from scipy.integrate import quad as squad +from numpy import ndarray +from typing import Callable, Dict, List, Tuple, Union -def quad(func, p, a, b, **kwargs): +def quad(func: Callable, p: Union[List[Union[float, Obs]], List[float], ndarray], a: Union[Obs, float, int], b: Union[Obs, float, int], **kwargs) -> Union[Tuple[Obs, float], Tuple[float, float], Tuple[Obs, float, Dict[str, Union[int, ndarray]]]]: '''Performs a (one-dimensional) numeric integration of f(p, x) from a to b. The integration is performed using scipy.integrate.quad(). diff --git a/pyerrors/linalg.py b/pyerrors/linalg.py index 5a489e26..f850a830 100644 --- a/pyerrors/linalg.py +++ b/pyerrors/linalg.py @@ -1,9 +1,12 @@ +from __future__ import annotations import numpy as np import autograd.numpy as anp # Thinly-wrapped numpy from .obs import derived_observable, CObs, Obs, import_jackknife +from numpy import ndarray +from typing import Callable, Tuple, Union -def matmul(*operands): +def matmul(*operands) -> ndarray: """Matrix multiply all operands. Parameters @@ -59,7 +62,7 @@ def multi_dot(operands): return derived_observable(multi_dot, operands, array_mode=True) -def jack_matmul(*operands): +def jack_matmul(*operands) -> ndarray: """Matrix multiply both operands making use of the jackknife approximation. Parameters @@ -120,7 +123,7 @@ def _imp_from_jack_c(matrix, name, idl): return _imp_from_jack(r, name, idl) -def einsum(subscripts, *operands): +def einsum(subscripts: str, *operands) -> Union[CObs, Obs, ndarray]: """Wrapper for numpy.einsum Parameters @@ -194,24 +197,24 @@ def _imp_from_jack_c(matrix, name, idl): return result -def inv(x): +def inv(x: ndarray) -> ndarray: """Inverse of Obs or CObs valued matrices.""" return _mat_mat_op(anp.linalg.inv, x) -def cholesky(x): +def cholesky(x: ndarray) -> ndarray: """Cholesky decomposition of Obs valued matrices.""" if any(isinstance(o, CObs) for o in x.ravel()): raise Exception("Cholesky decomposition is not implemented for CObs.") return _mat_mat_op(anp.linalg.cholesky, x) -def det(x): +def det(x: Union[ndarray, int]) -> Obs: """Determinant of Obs valued matrices.""" return _scalar_mat_op(anp.linalg.det, x) -def _scalar_mat_op(op, obs, **kwargs): +def _scalar_mat_op(op: Callable, obs: Union[ndarray, int], **kwargs) -> Obs: """Computes the matrix to scalar operation op to a given matrix of Obs.""" def _mat(x, **kwargs): dim = int(np.sqrt(len(x))) @@ -232,7 +235,7 @@ def _mat(x, **kwargs): return derived_observable(_mat, raveled_obs, **kwargs) -def _mat_mat_op(op, obs, **kwargs): +def _mat_mat_op(op: Callable, obs: ndarray, **kwargs) -> ndarray: """Computes the matrix to matrix operation op to a given matrix of Obs.""" # Use real representation to calculate matrix operations for complex matrices if any(isinstance(o, CObs) for o in obs.ravel()): @@ -258,31 +261,31 @@ def _mat_mat_op(op, obs, **kwargs): return derived_observable(lambda x, **kwargs: op(x), [obs], array_mode=True)[0] -def eigh(obs, **kwargs): +def eigh(obs: ndarray, **kwargs) -> Tuple[ndarray, ndarray]: """Computes the eigenvalues and eigenvectors of a given hermitian matrix of Obs according to np.linalg.eigh.""" w = derived_observable(lambda x, **kwargs: anp.linalg.eigh(x)[0], obs) v = derived_observable(lambda x, **kwargs: anp.linalg.eigh(x)[1], obs) return w, v -def eig(obs, **kwargs): +def eig(obs: ndarray, **kwargs) -> ndarray: """Computes the eigenvalues of a given matrix of Obs according to np.linalg.eig.""" w = derived_observable(lambda x, **kwargs: anp.real(anp.linalg.eig(x)[0]), obs) return w -def eigv(obs, **kwargs): +def eigv(obs: ndarray, **kwargs) -> ndarray: """Computes the eigenvectors of a given hermitian matrix of Obs according to np.linalg.eigh.""" v = derived_observable(lambda x, **kwargs: anp.linalg.eigh(x)[1], obs) return v -def pinv(obs, **kwargs): +def pinv(obs: ndarray, **kwargs) -> ndarray: """Computes the Moore-Penrose pseudoinverse of a matrix of Obs.""" return derived_observable(lambda x, **kwargs: anp.linalg.pinv(x), obs) -def svd(obs, **kwargs): +def svd(obs: ndarray, **kwargs) -> Tuple[ndarray, ndarray, ndarray]: """Computes the singular value decomposition of a matrix of Obs.""" u = derived_observable(lambda x, **kwargs: anp.linalg.svd(x, full_matrices=False)[0], obs) s = derived_observable(lambda x, **kwargs: anp.linalg.svd(x, full_matrices=False)[1], obs) diff --git a/pyerrors/misc.py b/pyerrors/misc.py index 94a7d4c2..8832e0ef 100644 --- a/pyerrors/misc.py +++ b/pyerrors/misc.py @@ -1,3 +1,4 @@ +from __future__ import annotations import platform import numpy as np import scipy @@ -7,6 +8,8 @@ import pickle from .obs import Obs from .version import __version__ +from numpy import float64, int64, ndarray +from typing import List, Type, Union def print_config(): @@ -54,7 +57,7 @@ def errorbar(x, y, axes=plt, **kwargs): axes.errorbar(val["x"], val["y"], xerr=err["x"], yerr=err["y"], **kwargs) -def dump_object(obj, name, **kwargs): +def dump_object(obj: Corr, name: str, **kwargs): """Dump object into pickle file. Parameters @@ -78,7 +81,7 @@ def dump_object(obj, name, **kwargs): pickle.dump(obj, fb) -def load_object(path): +def load_object(path: str) -> Union[Obs, Corr]: """Load object from pickle file. Parameters @@ -95,7 +98,7 @@ def load_object(path): return pickle.load(file) -def pseudo_Obs(value, dvalue, name, samples=1000): +def pseudo_Obs(value: Union[float, int64, float64, int], dvalue: Union[float, float64, int], name: str, samples: int=1000) -> Obs: """Generate an Obs object with given value, dvalue and name for test purposes Parameters @@ -132,7 +135,7 @@ def pseudo_Obs(value, dvalue, name, samples=1000): return res -def gen_correlated_data(means, cov, name, tau=0.5, samples=1000): +def gen_correlated_data(means: Union[ndarray, List[float]], cov: ndarray, name: str, tau: Union[float, ndarray]=0.5, samples: int=1000) -> List[Obs]: """ Generate observables with given covariance and autocorrelation times. Parameters @@ -174,7 +177,7 @@ def gen_correlated_data(means, cov, name, tau=0.5, samples=1000): return [Obs([dat], [name]) for dat in corr_data.T] -def _assert_equal_properties(ol, otype=Obs): +def _assert_equal_properties(ol: Union[List[Obs], List[CObs], ndarray], otype: Type[Obs]=Obs): otype = type(ol[0]) for o in ol[1:]: if not isinstance(o, otype): diff --git a/pyerrors/mpm.py b/pyerrors/mpm.py index bf782af6..b539797e 100644 --- a/pyerrors/mpm.py +++ b/pyerrors/mpm.py @@ -1,10 +1,13 @@ +from __future__ import annotations import numpy as np import scipy.linalg from .obs import Obs from .linalg import svd, eig +from pyerrors.obs import Obs +from typing import List -def matrix_pencil_method(corrs, k=1, p=None, **kwargs): +def matrix_pencil_method(corrs: List[Obs], k: int=1, p: None=None, **kwargs) -> List[Obs]: """Matrix pencil method to extract k energy levels from data Implementation of the matrix pencil method based on diff --git a/pyerrors/obs.py b/pyerrors/obs.py index 623c37fd..661d3c72 100644 --- a/pyerrors/obs.py +++ b/pyerrors/obs.py @@ -1,3 +1,4 @@ +from __future__ import annotations import warnings import hashlib import pickle @@ -10,6 +11,8 @@ import numdifftools as nd from itertools import groupby from .covobs import Covobs +from numpy import bool, float64, int64, ndarray +from typing import Any, Callable, Dict, List, Optional, Union # Improve print output of numpy.ndarrays containing Obs objects. np.set_printoptions(formatter={'object': lambda x: str(x)}) @@ -57,7 +60,7 @@ class Obs: N_sigma_global = 1.0 N_sigma_dict = {} - def __init__(self, samples, names, idl=None, **kwargs): + def __init__(self, samples: Union[List[List[int]], List[ndarray], ndarray, List[List[float64]], List[List[float]]], names: List[Union[int, Any, str]], idl: Optional[Any]=None, **kwargs): """ Initialize Obs object. Parameters @@ -141,27 +144,27 @@ def __init__(self, samples, names, idl=None, **kwargs): self.tag = None @property - def value(self): + def value(self) -> Union[float, int64, float64, int]: return self._value @property - def dvalue(self): + def dvalue(self) -> Union[float, float64]: return self._dvalue @property - def e_names(self): + def e_names(self) -> List[str]: return sorted(set([o.split('|')[0] for o in self.names])) @property - def cov_names(self): + def cov_names(self) -> List[Union[Any, str]]: return sorted(set([o for o in self.covobs.keys()])) @property - def mc_names(self): + def mc_names(self) -> List[Union[Any, str]]: return sorted(set([o.split('|')[0] for o in self.names if o not in self.cov_names])) @property - def e_content(self): + def e_content(self) -> Dict[str, List[str]]: res = {} for e, e_name in enumerate(self.e_names): res[e_name] = sorted(filter(lambda x: x.startswith(e_name + '|'), self.names)) @@ -170,7 +173,7 @@ def e_content(self): return res @property - def covobs(self): + def covobs(self) -> Dict[str, Covobs]: return self._covobs def gamma_method(self, **kwargs): @@ -341,7 +344,7 @@ def _compute_drho(i): gm = gamma_method - def _calc_gamma(self, deltas, idx, shape, w_max, fft, gapsize): + def _calc_gamma(self, deltas: ndarray, idx: Union[range, List[int], List[int64]], shape: int, w_max: Union[int64, int], fft: bool, gapsize: Union[int64, int]) -> ndarray: """Calculate Gamma_{AA} from the deltas, which are defined on idx. idx is assumed to be a contiguous range (possibly with a stepsize != 1) @@ -377,7 +380,7 @@ def _calc_gamma(self, deltas, idx, shape, w_max, fft, gapsize): return gamma - def details(self, ens_content=True): + def details(self, ens_content: bool=True): """Output detailed properties of the Obs. Parameters @@ -446,7 +449,7 @@ def details(self, ens_content=True): my_string_list.append(my_string) print('\n'.join(my_string_list)) - def reweight(self, weight): + def reweight(self, weight: "Obs") -> "Obs": """Reweight the obs with given rewighting factors. Parameters @@ -461,7 +464,7 @@ def reweight(self, weight): """ return reweight(weight, [self])[0] - def is_zero_within_error(self, sigma=1): + def is_zero_within_error(self, sigma: Union[float, int]=1) -> Union[bool, bool]: """Checks whether the observable is zero within 'sigma' standard errors. Parameters @@ -473,7 +476,7 @@ def is_zero_within_error(self, sigma=1): """ return self.is_zero() or np.abs(self.value) <= sigma * self._dvalue - def is_zero(self, atol=1e-10): + def is_zero(self, atol: float=1e-10) -> Union[bool, bool]: """Checks whether the observable is zero within a given tolerance. Parameters @@ -483,7 +486,7 @@ def is_zero(self, atol=1e-10): """ return np.isclose(0.0, self.value, 1e-14, atol) and all(np.allclose(0.0, delta, 1e-14, atol) for delta in self.deltas.values()) and all(np.allclose(0.0, delta.errsq(), 1e-14, atol) for delta in self.covobs.values()) - def plot_tauint(self, save=None): + def plot_tauint(self, save: None=None): """Plot integrated autocorrelation time for each ensemble. Parameters @@ -523,7 +526,7 @@ def plot_tauint(self, save=None): if save: fig.savefig(save + "_" + str(e)) - def plot_rho(self, save=None): + def plot_rho(self, save: None=None): """Plot normalized autocorrelation function time for each ensemble. Parameters @@ -576,7 +579,7 @@ def plot_rep_dist(self): plt.title('Replica distribution' + e_name + ' (mean=0, var=1)') plt.draw() - def plot_history(self, expand=True): + def plot_history(self, expand: bool=True): """Plot derived Monte Carlo history for each ensemble Parameters @@ -608,7 +611,7 @@ def plot_history(self, expand=True): plt.title(e_name + f'\nskew: {skew(y_test):.3f} (p={skewtest(y_test).pvalue:.3f}), kurtosis: {kurtosis(y_test):.3f} (p={kurtosistest(y_test).pvalue:.3f})') plt.draw() - def plot_piechart(self, save=None): + def plot_piechart(self, save: None=None) -> Dict[str, float64]: """Plot piechart which shows the fractional contribution of each ensemble to the error and returns a dictionary containing the fractions. @@ -632,7 +635,7 @@ def plot_piechart(self, save=None): return dict(zip(labels, sizes)) - def dump(self, filename, datatype="json.gz", description="", **kwargs): + def dump(self, filename: str, datatype: str="json.gz", description: str="", **kwargs): """Dump the Obs to a file 'name' of chosen format. Parameters @@ -661,7 +664,7 @@ def dump(self, filename, datatype="json.gz", description="", **kwargs): else: raise TypeError("Unknown datatype " + str(datatype)) - def export_jackknife(self): + def export_jackknife(self) -> ndarray: """Export jackknife samples from the Obs Returns @@ -687,7 +690,7 @@ def export_jackknife(self): tmp_jacks[1:] = (n * mean - full_data) / (n - 1) return tmp_jacks - def export_bootstrap(self, samples=500, random_numbers=None, save_rng=None): + def export_bootstrap(self, samples: int=500, random_numbers: Optional[ndarray]=None, save_rng: None=None) -> ndarray: """Export bootstrap samples from the Obs Parameters @@ -730,16 +733,16 @@ def export_bootstrap(self, samples=500, random_numbers=None, save_rng=None): ret[1:] = proj @ (self.deltas[name] + self.r_values[name]) return ret - def __float__(self): + def __float__(self) -> float: return float(self.value) - def __repr__(self): + def __repr__(self) -> str: return 'Obs[' + str(self) + ']' - def __str__(self): + def __str__(self) -> str: return _format_uncertainty(self.value, self._dvalue) - def __format__(self, format_type): + def __format__(self, format_type: str) -> str: if format_type == "": significance = 2 else: @@ -752,7 +755,7 @@ def __format__(self, format_type): my_str = char + my_str return my_str - def __hash__(self): + def __hash__(self) -> int: hash_tuple = (np.array([self.value]).astype(np.float32).data.tobytes(),) hash_tuple += tuple([o.astype(np.float32).data.tobytes() for o in self.deltas.values()]) hash_tuple += tuple([np.array([o.errsq()]).astype(np.float32).data.tobytes() for o in self.covobs.values()]) @@ -762,25 +765,25 @@ def __hash__(self): return int(m.hexdigest(), 16) & 0xFFFFFFFF # Overload comparisons - def __lt__(self, other): + def __lt__(self, other: Union[Obs, float, float64]) -> Union[bool, bool]: return self.value < other - def __le__(self, other): + def __le__(self, other: Union[Obs, float, int]) -> bool: return self.value <= other - def __gt__(self, other): + def __gt__(self, other: Union[Obs, float]) -> Union[bool, bool]: return self.value > other - def __ge__(self, other): + def __ge__(self, other: Union[Obs, float, int]) -> Union[bool, bool]: return self.value >= other - def __eq__(self, other): + def __eq__(self, other: Optional[Union[Obs, float64, int, float]]) -> Union[bool, bool]: if other is None: return False return (self - other).is_zero() # Overload math operations - def __add__(self, y): + def __add__(self, y: Any) -> Union[Obs, NotImplementedType, CObs, ndarray]: if isinstance(y, Obs): return derived_observable(lambda x, **kwargs: x[0] + x[1], [self, y], man_grad=[1, 1]) else: @@ -793,10 +796,10 @@ def __add__(self, y): else: return derived_observable(lambda x, **kwargs: x[0] + y, [self], man_grad=[1]) - def __radd__(self, y): + def __radd__(self, y: Union[float, int]) -> "Obs": return self + y - def __mul__(self, y): + def __mul__(self, y: Any) -> Union[Obs, ndarray, CObs, NotImplementedType]: if isinstance(y, Obs): return derived_observable(lambda x, **kwargs: x[0] * x[1], [self, y], man_grad=[y.value, self.value]) else: @@ -809,10 +812,10 @@ def __mul__(self, y): else: return derived_observable(lambda x, **kwargs: x[0] * y, [self], man_grad=[y]) - def __rmul__(self, y): + def __rmul__(self, y: Union[float, int]) -> "Obs": return self * y - def __sub__(self, y): + def __sub__(self, y: Any) -> Union[Obs, NotImplementedType, ndarray]: if isinstance(y, Obs): return derived_observable(lambda x, **kwargs: x[0] - x[1], [self, y], man_grad=[1, -1]) else: @@ -823,16 +826,16 @@ def __sub__(self, y): else: return derived_observable(lambda x, **kwargs: x[0] - y, [self], man_grad=[1]) - def __rsub__(self, y): + def __rsub__(self, y: Union[float, int]) -> "Obs": return -1 * (self - y) - def __pos__(self): + def __pos__(self) -> "Obs": return self - def __neg__(self): + def __neg__(self) -> "Obs": return -1 * self - def __truediv__(self, y): + def __truediv__(self, y: Any) -> Union[Obs, NotImplementedType, ndarray]: if isinstance(y, Obs): return derived_observable(lambda x, **kwargs: x[0] / x[1], [self, y], man_grad=[1 / y.value, - self.value / y.value ** 2]) else: @@ -843,7 +846,7 @@ def __truediv__(self, y): else: return derived_observable(lambda x, **kwargs: x[0] / y, [self], man_grad=[1 / y]) - def __rtruediv__(self, y): + def __rtruediv__(self, y: Union[float, int]) -> "Obs": if isinstance(y, Obs): return derived_observable(lambda x, **kwargs: x[0] / x[1], [y, self], man_grad=[1 / self.value, - y.value / self.value ** 2]) else: @@ -854,62 +857,62 @@ def __rtruediv__(self, y): else: return derived_observable(lambda x, **kwargs: y / x[0], [self], man_grad=[-y / self.value ** 2]) - def __pow__(self, y): + def __pow__(self, y: Union[Obs, float, int]) -> "Obs": if isinstance(y, Obs): return derived_observable(lambda x, **kwargs: x[0] ** x[1], [self, y], man_grad=[y.value * self.value ** (y.value - 1), self.value ** y.value * np.log(self.value)]) else: return derived_observable(lambda x, **kwargs: x[0] ** y, [self], man_grad=[y * self.value ** (y - 1)]) - def __rpow__(self, y): + def __rpow__(self, y: Union[float, int]) -> "Obs": return derived_observable(lambda x, **kwargs: y ** x[0], [self], man_grad=[y ** self.value * np.log(y)]) - def __abs__(self): + def __abs__(self) -> "Obs": return derived_observable(lambda x: anp.abs(x[0]), [self]) # Overload numpy functions - def sqrt(self): + def sqrt(self) -> "Obs": return derived_observable(lambda x, **kwargs: np.sqrt(x[0]), [self], man_grad=[1 / 2 / np.sqrt(self.value)]) - def log(self): + def log(self) -> "Obs": return derived_observable(lambda x, **kwargs: np.log(x[0]), [self], man_grad=[1 / self.value]) - def exp(self): + def exp(self) -> "Obs": return derived_observable(lambda x, **kwargs: np.exp(x[0]), [self], man_grad=[np.exp(self.value)]) - def sin(self): + def sin(self) -> "Obs": return derived_observable(lambda x, **kwargs: np.sin(x[0]), [self], man_grad=[np.cos(self.value)]) - def cos(self): + def cos(self) -> "Obs": return derived_observable(lambda x, **kwargs: np.cos(x[0]), [self], man_grad=[-np.sin(self.value)]) - def tan(self): + def tan(self) -> "Obs": return derived_observable(lambda x, **kwargs: np.tan(x[0]), [self], man_grad=[1 / np.cos(self.value) ** 2]) - def arcsin(self): + def arcsin(self) -> "Obs": return derived_observable(lambda x: anp.arcsin(x[0]), [self]) - def arccos(self): + def arccos(self) -> "Obs": return derived_observable(lambda x: anp.arccos(x[0]), [self]) - def arctan(self): + def arctan(self) -> "Obs": return derived_observable(lambda x: anp.arctan(x[0]), [self]) - def sinh(self): + def sinh(self) -> "Obs": return derived_observable(lambda x, **kwargs: np.sinh(x[0]), [self], man_grad=[np.cosh(self.value)]) - def cosh(self): + def cosh(self) -> "Obs": return derived_observable(lambda x, **kwargs: np.cosh(x[0]), [self], man_grad=[np.sinh(self.value)]) - def tanh(self): + def tanh(self) -> "Obs": return derived_observable(lambda x, **kwargs: np.tanh(x[0]), [self], man_grad=[1 / np.cosh(self.value) ** 2]) - def arcsinh(self): + def arcsinh(self) -> "Obs": return derived_observable(lambda x: anp.arcsinh(x[0]), [self]) - def arccosh(self): + def arccosh(self) -> "Obs": return derived_observable(lambda x: anp.arccosh(x[0]), [self]) - def arctanh(self): + def arctanh(self) -> "Obs": return derived_observable(lambda x: anp.arctanh(x[0]), [self]) @@ -917,17 +920,17 @@ class CObs: """Class for a complex valued observable.""" __slots__ = ['_real', '_imag', 'tag'] - def __init__(self, real, imag=0.0): + def __init__(self, real: Obs, imag: Union[Obs, float, int]=0.0): self._real = real self._imag = imag self.tag = None @property - def real(self): + def real(self) -> Obs: return self._real @property - def imag(self): + def imag(self) -> Union[Obs, float, int]: return self._imag def gamma_method(self, **kwargs): @@ -937,14 +940,14 @@ def gamma_method(self, **kwargs): if isinstance(self.imag, Obs): self.imag.gamma_method(**kwargs) - def is_zero(self): + def is_zero(self) -> bool: """Checks whether both real and imaginary part are zero within machine precision.""" return self.real == 0.0 and self.imag == 0.0 - def conjugate(self): + def conjugate(self) -> "CObs": return CObs(self.real, -self.imag) - def __add__(self, other): + def __add__(self, other: Any) -> Union[CObs, ndarray]: if isinstance(other, np.ndarray): return other + self elif hasattr(other, 'real') and hasattr(other, 'imag'): @@ -953,10 +956,10 @@ def __add__(self, other): else: return CObs(self.real + other, self.imag) - def __radd__(self, y): + def __radd__(self, y: Union[complex, float, Obs, int]) -> "CObs": return self + y - def __sub__(self, other): + def __sub__(self, other: Any) -> Union[CObs, ndarray]: if isinstance(other, np.ndarray): return -1 * (other - self) elif hasattr(other, 'real') and hasattr(other, 'imag'): @@ -964,10 +967,10 @@ def __sub__(self, other): else: return CObs(self.real - other, self.imag) - def __rsub__(self, other): + def __rsub__(self, other: Union[complex, float, Obs, int]) -> "CObs": return -1 * (self - other) - def __mul__(self, other): + def __mul__(self, other: Any) -> Union[CObs, ndarray]: if isinstance(other, np.ndarray): return other * self elif hasattr(other, 'real') and hasattr(other, 'imag'): @@ -986,10 +989,10 @@ def __mul__(self, other): else: return CObs(self.real * other, self.imag * other) - def __rmul__(self, other): + def __rmul__(self, other: Union[complex, Obs, float, int]) -> "CObs": return self * other - def __truediv__(self, other): + def __truediv__(self, other: Any) -> Union[CObs, ndarray]: if isinstance(other, np.ndarray): return 1 / (other / self) elif hasattr(other, 'real') and hasattr(other, 'imag'): @@ -998,32 +1001,32 @@ def __truediv__(self, other): else: return CObs(self.real / other, self.imag / other) - def __rtruediv__(self, other): + def __rtruediv__(self, other: Union[complex, float, Obs, int]) -> "CObs": r = self.real ** 2 + self.imag ** 2 if hasattr(other, 'real') and hasattr(other, 'imag'): return CObs((self.real * other.real + self.imag * other.imag) / r, (self.real * other.imag - self.imag * other.real) / r) else: return CObs(self.real * other / r, -self.imag * other / r) - def __abs__(self): + def __abs__(self) -> Obs: return np.sqrt(self.real**2 + self.imag**2) - def __pos__(self): + def __pos__(self) -> "CObs": return self - def __neg__(self): + def __neg__(self) -> "CObs": return -1 * self - def __eq__(self, other): + def __eq__(self, other: Union[CObs, int]) -> bool: return self.real == other.real and self.imag == other.imag - def __str__(self): + def __str__(self) -> str: return '(' + str(self.real) + int(self.imag >= 0.0) * '+' + str(self.imag) + 'j)' - def __repr__(self): + def __repr__(self) -> str: return 'CObs[' + str(self) + ']' - def __format__(self, format_type): + def __format__(self, format_type: str) -> str: if format_type == "": significance = 2 format_type = "2" @@ -1032,7 +1035,7 @@ def __format__(self, format_type): return f"({self.real:{format_type}}{self.imag:+{significance}}j)" -def gamma_method(x, **kwargs): +def gamma_method(x: Union[Corr, Obs, ndarray, List[Obs]], **kwargs) -> ndarray: """Vectorized version of the gamma_method applicable to lists or arrays of Obs. See docstring of pe.Obs.gamma_method for details. @@ -1043,7 +1046,7 @@ def gamma_method(x, **kwargs): gm = gamma_method -def _format_uncertainty(value, dvalue, significance=2): +def _format_uncertainty(value: Union[float, float64, int], dvalue: Union[float, float64, int], significance: int=2) -> str: """Creates a string of a value and its error in paranthesis notation, e.g., 13.02(45)""" if dvalue == 0.0 or (not np.isfinite(dvalue)): return str(value) @@ -1060,7 +1063,7 @@ def _format_uncertainty(value, dvalue, significance=2): return f"{value:.{max(0, int(significance - fexp - 1))}f}({dvalue:2.{max(0, int(significance - fexp - 1))}f})" -def _expand_deltas(deltas, idx, shape, gapsize): +def _expand_deltas(deltas: ndarray, idx: Union[range, List[int], List[int64]], shape: int, gapsize: Union[int64, int]) -> ndarray: """Expand deltas defined on idx to a regular range with spacing gapsize between two configurations and where holes are filled by 0. If idx is of type range, the deltas are not changed if the idx.step == gapsize. @@ -1086,7 +1089,7 @@ def _expand_deltas(deltas, idx, shape, gapsize): return ret -def _merge_idx(idl): +def _merge_idx(idl: List[Union[List[Union[int64, int]], range, List[int]]]) -> Union[List[Union[int64, int]], range, List[int]]: """Returns the union of all lists in idl as range or sorted list Parameters @@ -1109,7 +1112,7 @@ def _merge_idx(idl): return idunion -def _intersection_idx(idl): +def _intersection_idx(idl: List[Union[range, List[int]]]) -> Union[range, List[int]]: """Returns the intersection of all lists in idl as range or sorted list Parameters @@ -1135,7 +1138,7 @@ def _intersection_idx(idl): return idinter -def _expand_deltas_for_merge(deltas, idx, shape, new_idx, scalefactor): +def _expand_deltas_for_merge(deltas: ndarray, idx: Union[range, List[int]], shape: int, new_idx: Union[range, List[int]], scalefactor: Union[float, int]) -> ndarray: """Expand deltas defined on idx to the list of configs that is defined by new_idx. New, empty entries are filled by 0. If idx and new_idx are of type range, the smallest common divisor of the step sizes is used as new step size. @@ -1167,7 +1170,7 @@ def _expand_deltas_for_merge(deltas, idx, shape, new_idx, scalefactor): return np.array([ret[new_idx[i] - new_idx[0]] for i in range(len(new_idx))]) * len(new_idx) / len(idx) * scalefactor -def derived_observable(func, data, array_mode=False, **kwargs): +def derived_observable(func: Callable, data: Any, array_mode: bool=False, **kwargs) -> Union[Obs, ndarray]: """Construct a derived Obs according to func(data, **kwargs) using automatic differentiation. Parameters @@ -1357,7 +1360,7 @@ def __init__(self, N): return final_result -def _reduce_deltas(deltas, idx_old, idx_new): +def _reduce_deltas(deltas: Union[List[float], ndarray], idx_old: Union[range, List[int]], idx_new: Union[range, List[int], ndarray]) -> Union[List[float], ndarray]: """Extract deltas defined on idx_old on all configs of idx_new. Assumes, that idx_old and idx_new are correctly defined idl, i.e., they @@ -1386,7 +1389,7 @@ def _reduce_deltas(deltas, idx_old, idx_new): return np.array(deltas)[indices] -def reweight(weight, obs, **kwargs): +def reweight(weight: Obs, obs: Union[ndarray, List[Obs]], **kwargs) -> List[Obs]: """Reweight a list of observables. Parameters @@ -1428,7 +1431,7 @@ def reweight(weight, obs, **kwargs): return result -def correlate(obs_a, obs_b): +def correlate(obs_a: Obs, obs_b: Obs) -> Obs: """Correlate two observables. Parameters @@ -1471,7 +1474,7 @@ def correlate(obs_a, obs_b): return o -def covariance(obs, visualize=False, correlation=False, smooth=None, **kwargs): +def covariance(obs: Union[ndarray, List[Obs]], visualize: bool=False, correlation: bool=False, smooth: Optional[int]=None, **kwargs) -> ndarray: r'''Calculates the error covariance matrix of a set of observables. WARNING: This function should be used with care, especially for observables with support on multiple @@ -1541,7 +1544,7 @@ def covariance(obs, visualize=False, correlation=False, smooth=None, **kwargs): return cov -def invert_corr_cov_cholesky(corr, inverrdiag): +def invert_corr_cov_cholesky(corr: ndarray, inverrdiag: ndarray) -> ndarray: """Constructs a lower triangular matrix `chol` via the Cholesky decomposition of the correlation matrix `corr` and then returns the inverse covariance matrix `chol_inv` as a lower triangular matrix by solving `chol * x = inverrdiag`. @@ -1564,7 +1567,7 @@ def invert_corr_cov_cholesky(corr, inverrdiag): return chol_inv -def sort_corr(corr, kl, yd): +def sort_corr(corr: ndarray, kl: List[str], yd: Dict[str, List[Obs]]) -> ndarray: """ Reorders a correlation matrix to match the alphabetical order of its underlying y data. The ordering of the input correlation matrix `corr` is given by the list of keys `kl`. @@ -1627,7 +1630,7 @@ def sort_corr(corr, kl, yd): return corr_sorted -def _smooth_eigenvalues(corr, E): +def _smooth_eigenvalues(corr: ndarray, E: int) -> ndarray: """Eigenvalue smoothing as described in hep-lat/9412087 corr : np.ndarray @@ -1644,7 +1647,7 @@ def _smooth_eigenvalues(corr, E): return vec @ np.diag(vals) @ vec.T -def _covariance_element(obs1, obs2): +def _covariance_element(obs1: Obs, obs2: Obs) -> Union[float, float64]: """Estimates the covariance of two Obs objects, neglecting autocorrelations.""" def calc_gamma(deltas1, deltas2, idx1, idx2, new_idx): @@ -1704,7 +1707,7 @@ def calc_gamma(deltas1, deltas2, idx1, idx2, new_idx): return dvalue -def import_jackknife(jacks, name, idl=None): +def import_jackknife(jacks: ndarray, name: str, idl: Optional[List[range]]=None) -> Obs: """Imports jackknife samples and returns an Obs Parameters @@ -1724,7 +1727,7 @@ def import_jackknife(jacks, name, idl=None): return new_obs -def import_bootstrap(boots, name, random_numbers): +def import_bootstrap(boots: ndarray, name: str, random_numbers: ndarray) -> Obs: """Imports bootstrap samples and returns an Obs Parameters @@ -1754,7 +1757,7 @@ def import_bootstrap(boots, name, random_numbers): return ret -def merge_obs(list_of_obs): +def merge_obs(list_of_obs: List[Obs]) -> Obs: """Combine all observables in list_of_obs into one new observable Parameters @@ -1784,7 +1787,7 @@ def merge_obs(list_of_obs): return o -def cov_Obs(means, cov, name, grad=None): +def cov_Obs(means: Union[float64, int, List[float], float, List[int]], cov: Any, name: str, grad: None=None) -> Union[Obs, List[Obs]]: """Create an Obs based on mean(s) and a covariance matrix Parameters @@ -1827,7 +1830,7 @@ def covobs_to_obs(co): return ol -def _determine_gap(o, e_content, e_name): +def _determine_gap(o: Obs, e_content: Dict[str, List[str]], e_name: str) -> Union[int64, int]: gaps = [] for r_name in e_content[e_name]: if isinstance(o.idl[r_name], range): @@ -1842,7 +1845,7 @@ def _determine_gap(o, e_content, e_name): return gap -def _check_lists_equal(idl): +def _check_lists_equal(idl: List[Union[List[int], List[Union[int64, int]], range, ndarray]]): ''' Use groupby to efficiently check whether all elements of idl are identical. Returns True if all elements are equal, otherwise False. diff --git a/pyerrors/roots.py b/pyerrors/roots.py index acc5614f..c3a9f7da 100644 --- a/pyerrors/roots.py +++ b/pyerrors/roots.py @@ -1,10 +1,12 @@ +from __future__ import annotations import numpy as np import scipy.optimize from autograd import jacobian from .obs import derived_observable +from typing import Callable, List, Union -def find_root(d, func, guess=1.0, **kwargs): +def find_root(d: Union[Obs, List[Obs]], func: Callable, guess: float=1.0, **kwargs) -> Obs: r'''Finds the root of the function func(x, d) where d is an `Obs`. Parameters diff --git a/pyerrors/special.py b/pyerrors/special.py index 8b36e056..bb9e82b9 100644 --- a/pyerrors/special.py +++ b/pyerrors/special.py @@ -1,3 +1,4 @@ +from __future__ import annotations import scipy import numpy as np from autograd.extend import primitive, defvjp From 9fe375a747628f1f38d30175c7d1f8c214f2b371 Mon Sep 17 00:00:00 2001 From: Fabian Joswig Date: Wed, 25 Dec 2024 11:14:34 +0100 Subject: [PATCH 02/57] [Feat] Added type hints to input modules --- pyerrors/input/dobs.py | 54 ++++++++++++++++++++++++--------------- pyerrors/input/json.py | 21 ++++++++------- pyerrors/input/misc.py | 4 ++- pyerrors/input/openQCD.py | 29 +++++++++++---------- pyerrors/input/pandas.py | 17 +++++++----- pyerrors/input/sfcf.py | 35 +++++++++++++------------ pyerrors/input/utils.py | 4 ++- 7 files changed, 96 insertions(+), 68 deletions(-) diff --git a/pyerrors/input/dobs.py b/pyerrors/input/dobs.py index aea9b7a9..201bbff8 100644 --- a/pyerrors/input/dobs.py +++ b/pyerrors/input/dobs.py @@ -1,3 +1,4 @@ +from __future__ import annotations from collections import defaultdict import gzip import lxml.etree as et @@ -11,10 +12,13 @@ from ..obs import _merge_idx from ..covobs import Covobs from .. import version as pyerrorsversion +from lxml.etree import _Element +from numpy import ndarray +from typing import Any, Dict, List, Optional, Tuple, Union # Based on https://stackoverflow.com/a/10076823 -def _etree_to_dict(t): +def _etree_to_dict(t: _Element) -> Dict[str, Union[str, Dict[str, str], Dict[str, Union[str, Dict[str, str]]]]]: """ Convert the content of an XML file to a python dict""" d = {t.tag: {} if t.attrib else None} children = list(t) @@ -38,7 +42,7 @@ def _etree_to_dict(t): return d -def _dict_to_xmlstring(d): +def _dict_to_xmlstring(d: Dict[str, Any]) -> str: if isinstance(d, dict): iters = '' for k in d: @@ -66,7 +70,7 @@ def _dict_to_xmlstring(d): return iters -def _dict_to_xmlstring_spaces(d, space=' '): +def _dict_to_xmlstring_spaces(d: Dict[str, Dict[str, Dict[str, Union[str, Dict[str, str], List[Dict[str, str]]]]]], space: str=' ') -> str: s = _dict_to_xmlstring(d) o = '' c = 0 @@ -85,7 +89,7 @@ def _dict_to_xmlstring_spaces(d, space=' '): return o -def create_pobs_string(obsl, name, spec='', origin='', symbol=[], enstag=None): +def create_pobs_string(obsl: List[Obs], name: str, spec: str='', origin: str='', symbol: Optional[List[Union[str, Any]]]=None, enstag: None=None) -> str: """Export a list of Obs or structures containing Obs to an xml string according to the Zeuthen pobs format. @@ -113,6 +117,8 @@ def create_pobs_string(obsl, name, spec='', origin='', symbol=[], enstag=None): XML formatted string of the input data """ + if symbol is None: + symbol = [] od = {} ename = obsl[0].e_names[0] names = list(obsl[0].deltas.keys()) @@ -176,7 +182,7 @@ def create_pobs_string(obsl, name, spec='', origin='', symbol=[], enstag=None): return rs -def write_pobs(obsl, fname, name, spec='', origin='', symbol=[], enstag=None, gz=True): +def write_pobs(obsl: List[Obs], fname: str, name: str, spec: str='', origin: str='', symbol: Optional[List[Union[str, Any]]]=None, enstag: None=None, gz: bool=True): """Export a list of Obs or structures containing Obs to a .xml.gz file according to the Zeuthen pobs format. @@ -206,6 +212,8 @@ def write_pobs(obsl, fname, name, spec='', origin='', symbol=[], enstag=None, gz ------- None """ + if symbol is None: + symbol = [] pobsstring = create_pobs_string(obsl, name, spec, origin, symbol, enstag) if not fname.endswith('.xml') and not fname.endswith('.gz'): @@ -223,30 +231,30 @@ def write_pobs(obsl, fname, name, spec='', origin='', symbol=[], enstag=None, gz fp.close() -def _import_data(string): +def _import_data(string: str) -> List[Union[int, float]]: return json.loads("[" + ",".join(string.replace(' +', ' ').split()) + "]") -def _check(condition): +def _check(condition: bool): if not condition: raise Exception("XML file format not supported") class _NoTagInDataError(Exception): """Raised when tag is not in data""" - def __init__(self, tag): + def __init__(self, tag: str): self.tag = tag super().__init__('Tag %s not in data!' % (self.tag)) -def _find_tag(dat, tag): +def _find_tag(dat: _Element, tag: str) -> int: for i in range(len(dat)): if dat[i].tag == tag: return i raise _NoTagInDataError(tag) -def _import_array(arr): +def _import_array(arr: _Element) -> Union[List[Union[str, List[int], List[ndarray]]], ndarray]: name = arr[_find_tag(arr, 'id')].text.strip() index = _find_tag(arr, 'layout') try: @@ -284,12 +292,12 @@ def _import_array(arr): _check(False) -def _import_rdata(rd): +def _import_rdata(rd: _Element) -> Tuple[List[ndarray], str, List[int]]: name, idx, mask, deltas = _import_array(rd) return deltas, name, idx -def _import_cdata(cd): +def _import_cdata(cd: _Element) -> Tuple[str, ndarray, ndarray]: _check(cd[0].tag == "id") _check(cd[1][0].text.strip() == "cov") cov = _import_array(cd[1]) @@ -297,7 +305,7 @@ def _import_cdata(cd): return cd[0].text.strip(), cov, grad -def read_pobs(fname, full_output=False, gz=True, separator_insertion=None): +def read_pobs(fname: str, full_output: bool=False, gz: bool=True, separator_insertion: None=None) -> Union[Dict[str, Union[str, Dict[str, str], List[Obs]]], List[Obs]]: """Import a list of Obs from an xml.gz file in the Zeuthen pobs format. Tags are not written or recovered automatically. @@ -309,7 +317,7 @@ def read_pobs(fname, full_output=False, gz=True, separator_insertion=None): full_output : bool If True, a dict containing auxiliary information and the data is returned. If False, only the data is returned as list. - separatior_insertion: str or int + separator_insertion: str or int str: replace all occurences of "separator_insertion" within the replica names by "|%s" % (separator_insertion) when constructing the names of the replica. int: Insert the separator "|" at the position given by separator_insertion. @@ -397,7 +405,7 @@ def read_pobs(fname, full_output=False, gz=True, separator_insertion=None): # this is based on Mattia Bruno's implementation at https://github.com/mbruno46/pyobs/blob/master/pyobs/IO/xml.py -def import_dobs_string(content, full_output=False, separator_insertion=True): +def import_dobs_string(content: bytes, full_output: bool=False, separator_insertion: bool=True) -> Union[Dict[str, Union[str, Dict[str, str], List[Obs]]], List[Obs]]: """Import a list of Obs from a string in the Zeuthen dobs format. Tags are not written or recovered automatically. @@ -409,7 +417,7 @@ def import_dobs_string(content, full_output=False, separator_insertion=True): full_output : bool If True, a dict containing auxiliary information and the data is returned. If False, only the data is returned as list. - separatior_insertion: str, int or bool + separator_insertion: str, int or bool str: replace all occurences of "separator_insertion" within the replica names by "|%s" % (separator_insertion) when constructing the names of the replica. int: Insert the separator "|" at the position given by separator_insertion. @@ -571,7 +579,7 @@ def import_dobs_string(content, full_output=False, separator_insertion=True): return res -def read_dobs(fname, full_output=False, gz=True, separator_insertion=True): +def read_dobs(fname: str, full_output: bool=False, gz: bool=True, separator_insertion: bool=True) -> Union[Dict[str, Union[str, Dict[str, str], List[Obs]]], List[Obs]]: """Import a list of Obs from an xml.gz file in the Zeuthen dobs format. Tags are not written or recovered automatically. @@ -618,7 +626,7 @@ def read_dobs(fname, full_output=False, gz=True, separator_insertion=True): return import_dobs_string(content, full_output, separator_insertion=separator_insertion) -def _dobsdict_to_xmlstring(d): +def _dobsdict_to_xmlstring(d: Dict[str, Any]) -> str: if isinstance(d, dict): iters = '' for k in d: @@ -658,7 +666,7 @@ def _dobsdict_to_xmlstring(d): return iters -def _dobsdict_to_xmlstring_spaces(d, space=' '): +def _dobsdict_to_xmlstring_spaces(d: Dict[str, Union[Dict[str, Union[Dict[str, str], Dict[str, Union[str, Dict[str, str]]], Dict[str, Union[str, Dict[str, Union[str, List[str]]], List[Dict[str, Union[str, int, List[Dict[str, str]]]]]]]]], Dict[str, Union[Dict[str, str], Dict[str, Union[str, Dict[str, str]]], Dict[str, Union[str, Dict[str, Union[str, List[str]]], List[Dict[str, Union[str, int, List[Dict[str, str]]]]], List[Dict[str, Union[str, List[Dict[str, str]]]]]]]]]]], space: str=' ') -> str: s = _dobsdict_to_xmlstring(d) o = '' c = 0 @@ -677,7 +685,7 @@ def _dobsdict_to_xmlstring_spaces(d, space=' '): return o -def create_dobs_string(obsl, name, spec='dobs v1.0', origin='', symbol=[], who=None, enstags=None): +def create_dobs_string(obsl: List[Obs], name: str, spec: str='dobs v1.0', origin: str='', symbol: Optional[List[Union[str, Any]]]=None, who: None=None, enstags: Optional[Dict[Any, Any]]=None) -> str: """Generate the string for the export of a list of Obs or structures containing Obs to a .xml.gz file according to the Zeuthen dobs format. @@ -708,6 +716,8 @@ def create_dobs_string(obsl, name, spec='dobs v1.0', origin='', symbol=[], who=N xml_str : str XML string generated from the data """ + if symbol is None: + symbol = [] if enstags is None: enstags = {} od = {} @@ -866,7 +876,7 @@ def create_dobs_string(obsl, name, spec='dobs v1.0', origin='', symbol=[], who=N return rs -def write_dobs(obsl, fname, name, spec='dobs v1.0', origin='', symbol=[], who=None, enstags=None, gz=True): +def write_dobs(obsl: List[Obs], fname: str, name: str, spec: str='dobs v1.0', origin: str='', symbol: Optional[List[Union[str, Any]]]=None, who: None=None, enstags: None=None, gz: bool=True): """Export a list of Obs or structures containing Obs to a .xml.gz file according to the Zeuthen dobs format. @@ -900,6 +910,8 @@ def write_dobs(obsl, fname, name, spec='dobs v1.0', origin='', symbol=[], who=No ------- None """ + if symbol is None: + symbol = [] if enstags is None: enstags = {} diff --git a/pyerrors/input/json.py b/pyerrors/input/json.py index ca3fb0d2..2463959d 100644 --- a/pyerrors/input/json.py +++ b/pyerrors/input/json.py @@ -1,3 +1,4 @@ +from __future__ import annotations import rapidjson as json import gzip import getpass @@ -12,9 +13,11 @@ from ..correlators import Corr from ..misc import _assert_equal_properties from .. import version as pyerrorsversion +from numpy import float32, float64, int64, ndarray +from typing import Any, Dict, List, Optional, Tuple, Union -def create_json_string(ol, description='', indent=1): +def create_json_string(ol: Any, description: Union[str, Dict[str, Union[str, Dict[str, Union[Dict[str, Union[str, Dict[str, Union[int, str]]]], Dict[str, Optional[Union[str, List[str], float]]]]]]], Dict[str, Union[str, Dict[Optional[Union[int, bool]], str], float32]], Dict[str, Dict[str, Dict[str, str]]], Dict[str, Union[str, Dict[Optional[Union[int, bool]], str], Dict[int64, float64]]]]='', indent: int=1) -> str: """Generate the string for the export of a list of Obs or structures containing Obs to a .json(.gz) file @@ -216,7 +219,7 @@ def _jsonifier(obj): return json.dumps(d, indent=indent, ensure_ascii=False, default=_jsonifier, write_mode=json.WM_COMPACT) -def dump_to_json(ol, fname, description='', indent=1, gz=True): +def dump_to_json(ol: Union[Corr, List[Union[Obs, List[Obs], Corr, ndarray]], ndarray, List[Union[Obs, List[Obs], ndarray]], List[Obs]], fname: str, description: Union[str, Dict[str, Union[str, Dict[str, Union[Dict[str, Union[str, Dict[str, Union[int, str]]]], Dict[str, Optional[Union[str, List[str], float]]]]]]], Dict[str, Union[str, Dict[Optional[Union[int, bool]], str], float32]], Dict[str, Dict[str, Dict[str, str]]], Dict[str, Union[str, Dict[Optional[Union[int, bool]], str], Dict[int64, float64]]]]='', indent: int=1, gz: bool=True): """Export a list of Obs or structures containing Obs to a .json(.gz) file. Dict keys that are not JSON-serializable such as floats are converted to strings. @@ -258,7 +261,7 @@ def dump_to_json(ol, fname, description='', indent=1, gz=True): fp.close() -def _parse_json_dict(json_dict, verbose=True, full_output=False): +def _parse_json_dict(json_dict: Dict[str, Any], verbose: bool=True, full_output: bool=False) -> Any: """Reconstruct a list of Obs or structures containing Obs from a dict that was built out of a json string. @@ -470,7 +473,7 @@ def get_Corr_from_dict(o): return ol -def import_json_string(json_string, verbose=True, full_output=False): +def import_json_string(json_string: str, verbose: bool=True, full_output: bool=False) -> Union[Obs, List[Obs], Corr]: """Reconstruct a list of Obs or structures containing Obs from a json string. The following structures are supported: Obs, list, numpy.ndarray, Corr @@ -500,7 +503,7 @@ def import_json_string(json_string, verbose=True, full_output=False): return _parse_json_dict(json.loads(json_string), verbose, full_output) -def load_json(fname, verbose=True, gz=True, full_output=False): +def load_json(fname: str, verbose: bool=True, gz: bool=True, full_output: bool=False) -> Any: """Import a list of Obs or structures containing Obs from a .json(.gz) file. The following structures are supported: Obs, list, numpy.ndarray, Corr @@ -545,7 +548,7 @@ def load_json(fname, verbose=True, gz=True, full_output=False): return _parse_json_dict(d, verbose, full_output) -def _ol_from_dict(ind, reps='DICTOBS'): +def _ol_from_dict(ind: Union[Dict[Optional[Union[int, bool]], str], Dict[str, Union[Dict[str, Union[Obs, List[Obs], Dict[str, Union[int, Obs, Corr, ndarray]]]], Dict[str, Optional[Union[str, List[str], Obs, float]]], str]], Dict[str, Union[Dict[str, Union[Obs, List[Obs], Dict[str, Union[int, Obs, Corr, ndarray]]]], Dict[str, Optional[Union[str, List[str], Obs, float]]], List[str]]], Dict[str, Union[Dict[str, Union[Obs, List[Obs], Dict[str, Union[int, Obs, Corr, ndarray]]]], Dict[str, Optional[Union[str, List[str], Obs, float]]]]]], reps: str='DICTOBS') -> Union[Tuple[List[Any], Dict[Optional[Union[int, bool]], str]], Tuple[List[Union[Obs, List[Obs], Corr, ndarray]], Dict[str, Union[Dict[str, Union[str, Dict[str, Union[int, str]]]], Dict[str, Optional[Union[str, List[str], float]]]]]]]: """Convert a dictionary of Obs objects to a list and a dictionary that contains placeholders instead of the Obs objects. @@ -625,7 +628,7 @@ def obslist_replace_obs(li): return ol, nd -def dump_dict_to_json(od, fname, description='', indent=1, reps='DICTOBS', gz=True): +def dump_dict_to_json(od: Union[Dict[str, Union[Dict[str, Union[Obs, List[Obs], Dict[str, Union[int, Obs, Corr, ndarray]]]], Dict[str, Optional[Union[str, List[str], Obs, float]]], str]], Dict[str, Union[Dict[str, Union[Obs, List[Obs], Dict[str, Union[int, Obs, Corr, ndarray]]]], Dict[str, Optional[Union[str, List[str], Obs, float]]], List[str]]], List[Union[Obs, List[Obs], Corr, ndarray]], Dict[Optional[Union[int, bool]], str], Dict[str, Union[Dict[str, Union[Obs, List[Obs], Dict[str, Union[int, Obs, Corr, ndarray]]]], Dict[str, Optional[Union[str, List[str], Obs, float]]]]]], fname: str, description: Union[str, float32, Dict[int64, float64]]='', indent: int=1, reps: str='DICTOBS', gz: bool=True): """Export a dict of Obs or structures containing Obs to a .json(.gz) file Parameters @@ -665,7 +668,7 @@ def dump_dict_to_json(od, fname, description='', indent=1, reps='DICTOBS', gz=Tr dump_to_json(ol, fname, description=desc_dict, indent=indent, gz=gz) -def _od_from_list_and_dict(ol, ind, reps='DICTOBS'): +def _od_from_list_and_dict(ol: List[Union[Obs, List[Obs], Corr, ndarray]], ind: Dict[str, Dict[str, Optional[Union[str, Dict[str, Union[int, str]], List[str], float]]]], reps: str='DICTOBS') -> Dict[str, Dict[str, Any]]: """Parse a list of Obs or structures containing Obs and an accompanying dict, where the structures have been replaced by placeholders to a dict that contains the structures. @@ -728,7 +731,7 @@ def list_replace_string(li): return nd -def load_json_dict(fname, verbose=True, gz=True, full_output=False, reps='DICTOBS'): +def load_json_dict(fname: str, verbose: bool=True, gz: bool=True, full_output: bool=False, reps: str='DICTOBS') -> Dict[str, Union[Dict[str, Union[Obs, List[Obs], Dict[str, Union[int, Obs, Corr, ndarray]]]], Dict[str, Optional[Union[str, List[str], Obs, float]]], str, Dict[str, Union[Dict[str, Union[Obs, List[Obs], Dict[str, Union[int, Obs, Corr, ndarray]]]], Dict[str, Optional[Union[str, List[str], Obs, float]]]]]]]: """Import a dict of Obs or structures containing Obs from a .json(.gz) file. The following structures are supported: Obs, list, numpy.ndarray, Corr diff --git a/pyerrors/input/misc.py b/pyerrors/input/misc.py index c62f502c..0c09b429 100644 --- a/pyerrors/input/misc.py +++ b/pyerrors/input/misc.py @@ -1,3 +1,4 @@ +from __future__ import annotations import os import fnmatch import re @@ -8,9 +9,10 @@ from matplotlib import gridspec from ..obs import Obs from ..fits import fit_lin +from typing import Dict, Optional -def fit_t0(t2E_dict, fit_range, plot_fit=False, observable='t0'): +def fit_t0(t2E_dict: Dict[float, Obs], fit_range: int, plot_fit: Optional[bool]=False, observable: str='t0') -> Obs: """Compute the root of (flow-based) data based on a dictionary that contains the necessary information in key-value pairs a la (flow time: observable at flow time). diff --git a/pyerrors/input/openQCD.py b/pyerrors/input/openQCD.py index 278977d2..6c23e3f2 100644 --- a/pyerrors/input/openQCD.py +++ b/pyerrors/input/openQCD.py @@ -1,3 +1,4 @@ +from __future__ import annotations import os import fnmatch import struct @@ -8,9 +9,11 @@ from ..correlators import Corr from .misc import fit_t0 from .utils import sort_names +from io import BufferedReader +from typing import Dict, List, Optional, Tuple, Union -def read_rwms(path, prefix, version='2.0', names=None, **kwargs): +def read_rwms(path: str, prefix: str, version: str='2.0', names: Optional[List[str]]=None, **kwargs) -> List[Obs]: """Read rwms format from given folder structure. Returns a list of length nrw Parameters @@ -229,7 +232,7 @@ def read_rwms(path, prefix, version='2.0', names=None, **kwargs): return result -def _extract_flowed_energy_density(path, prefix, dtr_read, xmin, spatial_extent, postfix='ms', **kwargs): +def _extract_flowed_energy_density(path: str, prefix: str, dtr_read: int, xmin: int, spatial_extent: int, postfix: str='ms', **kwargs) -> Dict[float, Obs]: """Extract a dictionary with the flowed Yang-Mills action density from given .ms.dat files. Returns a dictionary with Obs as values and flow times as keys. @@ -422,7 +425,7 @@ def _extract_flowed_energy_density(path, prefix, dtr_read, xmin, spatial_extent, return E_dict -def extract_t0(path, prefix, dtr_read, xmin, spatial_extent, fit_range=5, postfix='ms', c=0.3, **kwargs): +def extract_t0(path: str, prefix: str, dtr_read: int, xmin: int, spatial_extent: int, fit_range: int=5, postfix: str='ms', c: Union[float, int]=0.3, **kwargs) -> Obs: """Extract t0/a^2 from given .ms.dat files. Returns t0 as Obs. It is assumed that all boundary effects have @@ -495,7 +498,7 @@ def extract_t0(path, prefix, dtr_read, xmin, spatial_extent, fit_range=5, postfi return fit_t0(t2E_dict, fit_range, plot_fit=kwargs.get('plot_fit')) -def extract_w0(path, prefix, dtr_read, xmin, spatial_extent, fit_range=5, postfix='ms', c=0.3, **kwargs): +def extract_w0(path: str, prefix: str, dtr_read: int, xmin: int, spatial_extent: int, fit_range: int=5, postfix: str='ms', c: Union[float, int]=0.3, **kwargs) -> Obs: """Extract w0/a from given .ms.dat files. Returns w0 as Obs. It is assumed that all boundary effects have @@ -577,7 +580,7 @@ def extract_w0(path, prefix, dtr_read, xmin, spatial_extent, fit_range=5, postfi return np.sqrt(fit_t0(tdtt2E_dict, fit_range, plot_fit=kwargs.get('plot_fit'), observable='w0')) -def _parse_array_openQCD2(d, n, size, wa, quadrupel=False): +def _parse_array_openQCD2(d: int, n: Tuple[int, int], size: int, wa: Union[Tuple[float, float, float, float, float, float, float, float], Tuple[float, float]], quadrupel: bool=False) -> List[List[float]]: arr = [] if d == 2: for i in range(n[0]): @@ -596,7 +599,7 @@ def _parse_array_openQCD2(d, n, size, wa, quadrupel=False): return arr -def _find_files(path, prefix, postfix, ext, known_files=[]): +def _find_files(path: str, prefix: str, postfix: str, ext: str, known_files: Union[str, List[str]]=[]) -> List[str]: found = [] files = [] @@ -636,7 +639,7 @@ def _find_files(path, prefix, postfix, ext, known_files=[]): return files -def _read_array_openQCD2(fp): +def _read_array_openQCD2(fp: BufferedReader) -> Dict[str, Union[int, Tuple[int, int], List[List[float]]]]: t = fp.read(4) d = struct.unpack('i', t)[0] t = fp.read(4 * d) @@ -662,7 +665,7 @@ def _read_array_openQCD2(fp): return {'d': d, 'n': n, 'size': size, 'arr': arr} -def read_qtop(path, prefix, c, dtr_cnfg=1, version="openQCD", **kwargs): +def read_qtop(path: str, prefix: str, c: float, dtr_cnfg: int=1, version: str="openQCD", **kwargs) -> Obs: """Read the topologial charge based on openQCD gradient flow measurements. Parameters @@ -715,7 +718,7 @@ def read_qtop(path, prefix, c, dtr_cnfg=1, version="openQCD", **kwargs): return _read_flow_obs(path, prefix, c, dtr_cnfg=dtr_cnfg, version=version, obspos=0, **kwargs) -def read_gf_coupling(path, prefix, c, dtr_cnfg=1, Zeuthen_flow=True, **kwargs): +def read_gf_coupling(path: str, prefix: str, c: float, dtr_cnfg: int=1, Zeuthen_flow: bool=True, **kwargs) -> Obs: """Read the gradient flow coupling based on sfqcd gradient flow measurements. See 1607.06423 for details. Note: The current implementation only works for c=0.3 and T=L. The definition of the coupling in 1607.06423 requires projection to topological charge zero which is not done within this function but has to be performed in a separate step. @@ -787,7 +790,7 @@ def read_gf_coupling(path, prefix, c, dtr_cnfg=1, Zeuthen_flow=True, **kwargs): return t * t * (5 / 3 * plaq - 1 / 12 * C2x1) / normdict[L] -def _read_flow_obs(path, prefix, c, dtr_cnfg=1, version="openQCD", obspos=0, sum_t=True, **kwargs): +def _read_flow_obs(path: str, prefix: str, c: float, dtr_cnfg: int=1, version: str="openQCD", obspos: int=0, sum_t: bool=True, **kwargs) -> Obs: """Read a flow observable based on openQCD gradient flow measurements. Parameters @@ -1059,7 +1062,7 @@ def _read_flow_obs(path, prefix, c, dtr_cnfg=1, version="openQCD", obspos=0, sum return result -def qtop_projection(qtop, target=0): +def qtop_projection(qtop: Obs, target: int=0) -> Obs: """Returns the projection to the topological charge sector defined by target. Parameters @@ -1085,7 +1088,7 @@ def qtop_projection(qtop, target=0): return reto -def read_qtop_sector(path, prefix, c, target=0, **kwargs): +def read_qtop_sector(path: str, prefix: str, c: float, target: int=0, **kwargs) -> Obs: """Constructs reweighting factors to a specified topological sector. Parameters @@ -1143,7 +1146,7 @@ def read_qtop_sector(path, prefix, c, target=0, **kwargs): return qtop_projection(qtop, target=target) -def read_ms5_xsf(path, prefix, qc, corr, sep="r", **kwargs): +def read_ms5_xsf(path: str, prefix: str, qc: str, corr: str, sep: str="r", **kwargs) -> Corr: """ Read data from files in the specified directory with the specified prefix and quark combination extension, and return a `Corr` object containing the data. diff --git a/pyerrors/input/pandas.py b/pyerrors/input/pandas.py index 13482983..f6cdb7bf 100644 --- a/pyerrors/input/pandas.py +++ b/pyerrors/input/pandas.py @@ -1,3 +1,4 @@ +from __future__ import annotations import warnings import gzip import sqlite3 @@ -6,9 +7,11 @@ from ..correlators import Corr from .json import create_json_string, import_json_string import numpy as np +from pandas.core.frame import DataFrame +from pandas.core.series import Series -def to_sql(df, table_name, db, if_exists='fail', gz=True, **kwargs): +def to_sql(df: DataFrame, table_name: str, db: str, if_exists: str='fail', gz: bool=True, **kwargs): """Write DataFrame including Obs or Corr valued columns to sqlite database. Parameters @@ -34,7 +37,7 @@ def to_sql(df, table_name, db, if_exists='fail', gz=True, **kwargs): con.close() -def read_sql(sql, db, auto_gamma=False, **kwargs): +def read_sql(sql: str, db: str, auto_gamma: bool=False, **kwargs) -> DataFrame: """Execute SQL query on sqlite database and obtain DataFrame including Obs or Corr valued columns. Parameters @@ -58,7 +61,7 @@ def read_sql(sql, db, auto_gamma=False, **kwargs): return _deserialize_df(extract_df, auto_gamma=auto_gamma) -def dump_df(df, fname, gz=True): +def dump_df(df: DataFrame, fname: str, gz: bool=True): """Exports a pandas DataFrame containing Obs valued columns to a (gzipped) csv file. Before making use of pandas to_csv functionality Obs objects are serialized via the standardized @@ -97,7 +100,7 @@ def dump_df(df, fname, gz=True): out.to_csv(fname, index=False) -def load_df(fname, auto_gamma=False, gz=True): +def load_df(fname: str, auto_gamma: bool=False, gz: bool=True) -> DataFrame: """Imports a pandas DataFrame from a csv.(gz) file in which Obs objects are serialized as json strings. Parameters @@ -131,7 +134,7 @@ def load_df(fname, auto_gamma=False, gz=True): return _deserialize_df(re_import, auto_gamma=auto_gamma) -def _serialize_df(df, gz=False): +def _serialize_df(df: DataFrame, gz: bool=False) -> DataFrame: """Serializes all Obs or Corr valued columns into json strings according to the pyerrors json specification. Parameters @@ -152,7 +155,7 @@ def _serialize_df(df, gz=False): return out -def _deserialize_df(df, auto_gamma=False): +def _deserialize_df(df: DataFrame, auto_gamma: bool=False) -> DataFrame: """Deserializes all pyerrors json strings into Obs or Corr objects according to the pyerrors json specification. Parameters @@ -188,7 +191,7 @@ def _deserialize_df(df, auto_gamma=False): return df -def _need_to_serialize(col): +def _need_to_serialize(col: Series) -> bool: serialize = False i = 0 while i < len(col) and col[i] is None: diff --git a/pyerrors/input/sfcf.py b/pyerrors/input/sfcf.py index e9f2837e..596a52e4 100644 --- a/pyerrors/input/sfcf.py +++ b/pyerrors/input/sfcf.py @@ -1,3 +1,4 @@ +from __future__ import annotations import os import fnmatch import re @@ -5,12 +6,14 @@ from ..obs import Obs from .utils import sort_names, check_idl import itertools +from numpy import ndarray +from typing import Any, Dict, List, Tuple, Union sep = "/" -def read_sfcf(path, prefix, name, quarks='.*', corr_type="bi", noffset=0, wf=0, wf2=0, version="1.0c", cfg_separator="n", silent=False, **kwargs): +def read_sfcf(path: str, prefix: str, name: str, quarks: str='.*', corr_type: str="bi", noffset: int=0, wf: int=0, wf2: int=0, version: str="1.0c", cfg_separator: str="n", silent: bool=False, **kwargs) -> List[Obs]: """Read sfcf files from given folder structure. Parameters @@ -75,7 +78,7 @@ def read_sfcf(path, prefix, name, quarks='.*', corr_type="bi", noffset=0, wf=0, return ret[name][quarks][str(noffset)][str(wf)][str(wf2)] -def read_sfcf_multi(path, prefix, name_list, quarks_list=['.*'], corr_type_list=['bi'], noffset_list=[0], wf_list=[0], wf2_list=[0], version="1.0c", cfg_separator="n", silent=False, keyed_out=False, **kwargs): +def read_sfcf_multi(path: str, prefix: str, name_list: List[str], quarks_list: List[str]=['.*'], corr_type_list: List[str]=['bi'], noffset_list: List[int]=[0], wf_list: List[int]=[0], wf2_list: List[int]=[0], version: str="1.0c", cfg_separator: str="n", silent: bool=False, keyed_out: bool=False, **kwargs) -> Dict[str, Dict[str, Dict[str, Dict[str, Dict[str, List[Obs]]]]]]: """Read sfcf files from given folder structure. Parameters @@ -407,22 +410,22 @@ def read_sfcf_multi(path, prefix, name_list, quarks_list=['.*'], corr_type_list= return result_dict -def _lists2key(*lists): +def _lists2key(*lists) -> List[str]: keys = [] for tup in itertools.product(*lists): keys.append(sep.join(tup)) return keys -def _key2specs(key): +def _key2specs(key: str) -> List[str]: return key.split(sep) -def _specs2key(*specs): +def _specs2key(*specs) -> str: return sep.join(specs) -def _read_o_file(cfg_path, name, needed_keys, intern, version, im): +def _read_o_file(cfg_path: str, name: str, needed_keys: List[str], intern: Dict[str, Dict[str, Union[bool, Dict[str, Dict[str, Dict[str, Dict[str, Dict[str, Union[int, str]]]]]], int]]], version: str, im: int) -> Dict[str, List[float]]: return_vals = {} for key in needed_keys: file = cfg_path + '/' + name @@ -447,7 +450,7 @@ def _read_o_file(cfg_path, name, needed_keys, intern, version, im): return return_vals -def _extract_corr_type(corr_type): +def _extract_corr_type(corr_type: str) -> Tuple[bool, bool]: if corr_type == 'bb': b2b = True single = True @@ -460,7 +463,7 @@ def _extract_corr_type(corr_type): return b2b, single -def _find_files(rep_path, prefix, compact, files=[]): +def _find_files(rep_path: str, prefix: str, compact: bool, files: List[Union[range, str, Any]]=[]) -> List[str]: sub_ls = [] if not files == []: files.sort(key=lambda x: int(re.findall(r'\d+', x)[-1])) @@ -487,7 +490,7 @@ def _find_files(rep_path, prefix, compact, files=[]): return files -def _make_pattern(version, name, noffset, wf, wf2, b2b, quarks): +def _make_pattern(version: str, name: str, noffset: str, wf: str, wf2: Union[str, int], b2b: bool, quarks: str) -> str: if version == "0.0": pattern = "# " + name + " : offset " + str(noffset) + ", wf " + str(wf) if b2b: @@ -501,7 +504,7 @@ def _make_pattern(version, name, noffset, wf, wf2, b2b, quarks): return pattern -def _find_correlator(file_name, version, pattern, b2b, silent=False): +def _find_correlator(file_name: str, version: str, pattern: str, b2b: bool, silent: bool=False) -> Tuple[int, int]: T = 0 with open(file_name, "r") as my_file: @@ -527,7 +530,7 @@ def _find_correlator(file_name, version, pattern, b2b, silent=False): return start_read, T -def _read_compact_file(rep_path, cfg_file, intern, needed_keys, im): +def _read_compact_file(rep_path: str, cfg_file: str, intern: Dict[str, Dict[str, Union[bool, Dict[str, Dict[str, Dict[str, Dict[str, Dict[str, Union[int, str]]]]]], int]]], needed_keys: List[str], im: int) -> Dict[str, List[float]]: return_vals = {} with open(rep_path + cfg_file) as fp: lines = fp.readlines() @@ -558,7 +561,7 @@ def _read_compact_file(rep_path, cfg_file, intern, needed_keys, im): return return_vals -def _read_compact_rep(path, rep, sub_ls, intern, needed_keys, im): +def _read_compact_rep(path: str, rep: str, sub_ls: List[str], intern: Dict[str, Dict[str, Union[bool, Dict[str, Dict[str, Dict[str, Dict[str, Dict[str, Union[int, str]]]]]], int]]], needed_keys: List[str], im: int) -> Dict[str, List[ndarray]]: rep_path = path + '/' + rep + '/' no_cfg = len(sub_ls) @@ -580,7 +583,7 @@ def _read_compact_rep(path, rep, sub_ls, intern, needed_keys, im): return return_vals -def _read_chunk(chunk, gauge_line, cfg_sep, start_read, T, corr_line, b2b, pattern, im, single): +def _read_chunk(chunk: List[str], gauge_line: int, cfg_sep: str, start_read: int, T: int, corr_line: int, b2b: bool, pattern: str, im: int, single: bool) -> Tuple[int, List[float]]: try: idl = int(chunk[gauge_line].split(cfg_sep)[-1]) except Exception: @@ -597,7 +600,7 @@ def _read_chunk(chunk, gauge_line, cfg_sep, start_read, T, corr_line, b2b, patte return idl, data -def _read_append_rep(filename, pattern, b2b, cfg_separator, im, single): +def _read_append_rep(filename: str, pattern: str, b2b: bool, cfg_separator: str, im: int, single: bool) -> Tuple[int, List[int], List[List[float]]]: with open(filename, 'r') as fp: content = fp.readlines() data_starts = [] @@ -646,7 +649,7 @@ def _read_append_rep(filename, pattern, b2b, cfg_separator, im, single): return T, rep_idl, data -def _get_rep_names(ls, ens_name=None): +def _get_rep_names(ls: List[str], ens_name: None=None) -> List[str]: new_names = [] for entry in ls: try: @@ -661,7 +664,7 @@ def _get_rep_names(ls, ens_name=None): return new_names -def _get_appended_rep_names(ls, prefix, name, ens_name=None): +def _get_appended_rep_names(ls: List[str], prefix: str, name: str, ens_name: None=None) -> List[str]: new_names = [] for exc in ls: if not fnmatch.fnmatch(exc, prefix + '*.' + name): diff --git a/pyerrors/input/utils.py b/pyerrors/input/utils.py index eaf41f06..5ac00ba8 100644 --- a/pyerrors/input/utils.py +++ b/pyerrors/input/utils.py @@ -1,11 +1,13 @@ """Utilities for the input""" +from __future__ import annotations import re import fnmatch import os +from typing import List -def sort_names(ll): +def sort_names(ll: List[str]) -> List[str]: """Sorts a list of names of replika with searches for `r` and `id` in the replikum string. If this search fails, a fallback method is used, where the strings are simply compared and the first diffeing numeral is used for differentiation. From 8d86295ac5a1db5c6c313b451bf7f1e9b1801a45 Mon Sep 17 00:00:00 2001 From: Fabian Joswig Date: Wed, 25 Dec 2024 11:22:44 +0100 Subject: [PATCH 03/57] [Feat] Fixed a few type hints manually --- pyerrors/obs.py | 54 ++++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/pyerrors/obs.py b/pyerrors/obs.py index 661d3c72..44f26a7e 100644 --- a/pyerrors/obs.py +++ b/pyerrors/obs.py @@ -796,7 +796,7 @@ def __add__(self, y: Any) -> Union[Obs, NotImplementedType, CObs, ndarray]: else: return derived_observable(lambda x, **kwargs: x[0] + y, [self], man_grad=[1]) - def __radd__(self, y: Union[float, int]) -> "Obs": + def __radd__(self, y: Union[float, int]) -> Obs: return self + y def __mul__(self, y: Any) -> Union[Obs, ndarray, CObs, NotImplementedType]: @@ -812,7 +812,7 @@ def __mul__(self, y: Any) -> Union[Obs, ndarray, CObs, NotImplementedType]: else: return derived_observable(lambda x, **kwargs: x[0] * y, [self], man_grad=[y]) - def __rmul__(self, y: Union[float, int]) -> "Obs": + def __rmul__(self, y: Union[float, int]) -> Obs: return self * y def __sub__(self, y: Any) -> Union[Obs, NotImplementedType, ndarray]: @@ -826,13 +826,13 @@ def __sub__(self, y: Any) -> Union[Obs, NotImplementedType, ndarray]: else: return derived_observable(lambda x, **kwargs: x[0] - y, [self], man_grad=[1]) - def __rsub__(self, y: Union[float, int]) -> "Obs": + def __rsub__(self, y: Union[float, int]) -> Obs: return -1 * (self - y) - def __pos__(self) -> "Obs": + def __pos__(self) -> Obs: return self - def __neg__(self) -> "Obs": + def __neg__(self) -> Obs: return -1 * self def __truediv__(self, y: Any) -> Union[Obs, NotImplementedType, ndarray]: @@ -846,7 +846,7 @@ def __truediv__(self, y: Any) -> Union[Obs, NotImplementedType, ndarray]: else: return derived_observable(lambda x, **kwargs: x[0] / y, [self], man_grad=[1 / y]) - def __rtruediv__(self, y: Union[float, int]) -> "Obs": + def __rtruediv__(self, y: Union[float, int]) -> Obs: if isinstance(y, Obs): return derived_observable(lambda x, **kwargs: x[0] / x[1], [y, self], man_grad=[1 / self.value, - y.value / self.value ** 2]) else: @@ -857,62 +857,62 @@ def __rtruediv__(self, y: Union[float, int]) -> "Obs": else: return derived_observable(lambda x, **kwargs: y / x[0], [self], man_grad=[-y / self.value ** 2]) - def __pow__(self, y: Union[Obs, float, int]) -> "Obs": + def __pow__(self, y: Union[Obs, float, int]) -> Obs: if isinstance(y, Obs): return derived_observable(lambda x, **kwargs: x[0] ** x[1], [self, y], man_grad=[y.value * self.value ** (y.value - 1), self.value ** y.value * np.log(self.value)]) else: return derived_observable(lambda x, **kwargs: x[0] ** y, [self], man_grad=[y * self.value ** (y - 1)]) - def __rpow__(self, y: Union[float, int]) -> "Obs": + def __rpow__(self, y: Union[float, int]) -> Obs: return derived_observable(lambda x, **kwargs: y ** x[0], [self], man_grad=[y ** self.value * np.log(y)]) - def __abs__(self) -> "Obs": + def __abs__(self) -> Obs: return derived_observable(lambda x: anp.abs(x[0]), [self]) # Overload numpy functions - def sqrt(self) -> "Obs": + def sqrt(self) -> Obs: return derived_observable(lambda x, **kwargs: np.sqrt(x[0]), [self], man_grad=[1 / 2 / np.sqrt(self.value)]) - def log(self) -> "Obs": + def log(self) -> Obs: return derived_observable(lambda x, **kwargs: np.log(x[0]), [self], man_grad=[1 / self.value]) - def exp(self) -> "Obs": + def exp(self) -> Obs: return derived_observable(lambda x, **kwargs: np.exp(x[0]), [self], man_grad=[np.exp(self.value)]) - def sin(self) -> "Obs": + def sin(self) -> Obs: return derived_observable(lambda x, **kwargs: np.sin(x[0]), [self], man_grad=[np.cos(self.value)]) - def cos(self) -> "Obs": + def cos(self) -> Obs: return derived_observable(lambda x, **kwargs: np.cos(x[0]), [self], man_grad=[-np.sin(self.value)]) - def tan(self) -> "Obs": + def tan(self) -> Obs: return derived_observable(lambda x, **kwargs: np.tan(x[0]), [self], man_grad=[1 / np.cos(self.value) ** 2]) - def arcsin(self) -> "Obs": + def arcsin(self) -> Obs: return derived_observable(lambda x: anp.arcsin(x[0]), [self]) - def arccos(self) -> "Obs": + def arccos(self) -> Obs: return derived_observable(lambda x: anp.arccos(x[0]), [self]) - def arctan(self) -> "Obs": + def arctan(self) -> Obs: return derived_observable(lambda x: anp.arctan(x[0]), [self]) - def sinh(self) -> "Obs": + def sinh(self) -> Obs: return derived_observable(lambda x, **kwargs: np.sinh(x[0]), [self], man_grad=[np.cosh(self.value)]) - def cosh(self) -> "Obs": + def cosh(self) -> Obs: return derived_observable(lambda x, **kwargs: np.cosh(x[0]), [self], man_grad=[np.sinh(self.value)]) - def tanh(self) -> "Obs": + def tanh(self) -> Obs: return derived_observable(lambda x, **kwargs: np.tanh(x[0]), [self], man_grad=[1 / np.cosh(self.value) ** 2]) - def arcsinh(self) -> "Obs": + def arcsinh(self) -> Obs: return derived_observable(lambda x: anp.arcsinh(x[0]), [self]) - def arccosh(self) -> "Obs": + def arccosh(self) -> Obs: return derived_observable(lambda x: anp.arccosh(x[0]), [self]) - def arctanh(self) -> "Obs": + def arctanh(self) -> Obs: return derived_observable(lambda x: anp.arctanh(x[0]), [self]) @@ -944,7 +944,7 @@ def is_zero(self) -> bool: """Checks whether both real and imaginary part are zero within machine precision.""" return self.real == 0.0 and self.imag == 0.0 - def conjugate(self) -> "CObs": + def conjugate(self) -> CObs: return CObs(self.real, -self.imag) def __add__(self, other: Any) -> Union[CObs, ndarray]: @@ -989,7 +989,7 @@ def __mul__(self, other: Any) -> Union[CObs, ndarray]: else: return CObs(self.real * other, self.imag * other) - def __rmul__(self, other: Union[complex, Obs, float, int]) -> "CObs": + def __rmul__(self, other: Union[complex, Obs, CObs, float, int]) -> "CObs": return self * other def __truediv__(self, other: Any) -> Union[CObs, ndarray]: @@ -1001,7 +1001,7 @@ def __truediv__(self, other: Any) -> Union[CObs, ndarray]: else: return CObs(self.real / other, self.imag / other) - def __rtruediv__(self, other: Union[complex, float, Obs, int]) -> "CObs": + def __rtruediv__(self, other: Union[complex, float, Obs, CObs, int]) -> CObs: r = self.real ** 2 + self.imag ** 2 if hasattr(other, 'real') and hasattr(other, 'imag'): return CObs((self.real * other.real + self.imag * other.imag) / r, (self.real * other.imag - self.imag * other.real) / r) From 2d34b355edb1384b1b274725252e5859f58e64be Mon Sep 17 00:00:00 2001 From: Fabian Joswig Date: Wed, 25 Dec 2024 11:44:24 +0100 Subject: [PATCH 04/57] [Fix] Fix ruff errors and a few type annotations --- pyerrors/correlators.py | 2 +- pyerrors/misc.py | 6 ++++-- pyerrors/mpm.py | 1 - pyerrors/obs.py | 11 ++++++++++- pyerrors/roots.py | 2 +- 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/pyerrors/correlators.py b/pyerrors/correlators.py index 74db429d..436ca6ad 100644 --- a/pyerrors/correlators.py +++ b/pyerrors/correlators.py @@ -7,7 +7,7 @@ import scipy.linalg from .obs import Obs, reweight, correlate, CObs from .misc import dump_object, _assert_equal_properties -from .fits import least_squares +from .fits import least_squares, Fit_result from .roots import find_root from . import linalg from numpy import float64, int64, ndarray, ufunc diff --git a/pyerrors/misc.py b/pyerrors/misc.py index 8832e0ef..9fe679e7 100644 --- a/pyerrors/misc.py +++ b/pyerrors/misc.py @@ -6,11 +6,13 @@ import matplotlib.pyplot as plt import pandas as pd import pickle -from .obs import Obs +from .obs import Obs, CObs from .version import __version__ from numpy import float64, int64, ndarray -from typing import List, Type, Union +from typing import List, Type, Union, TYPE_CHECKING +if TYPE_CHECKING: + from .correlators import Corr def print_config(): """Print information about version of python, pyerrors and dependencies.""" diff --git a/pyerrors/mpm.py b/pyerrors/mpm.py index b539797e..50b5b837 100644 --- a/pyerrors/mpm.py +++ b/pyerrors/mpm.py @@ -3,7 +3,6 @@ import scipy.linalg from .obs import Obs from .linalg import svd, eig -from pyerrors.obs import Obs from typing import List diff --git a/pyerrors/obs.py b/pyerrors/obs.py index 44f26a7e..762f2b06 100644 --- a/pyerrors/obs.py +++ b/pyerrors/obs.py @@ -1,4 +1,5 @@ from __future__ import annotations +import sys import warnings import hashlib import pickle @@ -12,7 +13,15 @@ from itertools import groupby from .covobs import Covobs from numpy import bool, float64, int64, ndarray -from typing import Any, Callable, Dict, List, Optional, Union +from typing import Any, Callable, Dict, List, Optional, Union, TYPE_CHECKING + +if sys.version_info >= (3, 10): + from types import NotImplementedType +else: + NotImplementedType = type(NotImplemented) + +if TYPE_CHECKING: + from .correlators import Corr # Improve print output of numpy.ndarrays containing Obs objects. np.set_printoptions(formatter={'object': lambda x: str(x)}) diff --git a/pyerrors/roots.py b/pyerrors/roots.py index c3a9f7da..ece0e183 100644 --- a/pyerrors/roots.py +++ b/pyerrors/roots.py @@ -2,7 +2,7 @@ import numpy as np import scipy.optimize from autograd import jacobian -from .obs import derived_observable +from .obs import Obs, derived_observable from typing import Callable, List, Union From a9e082c33385ce35de12004f59613b77e65f9c73 Mon Sep 17 00:00:00 2001 From: Fabian Joswig Date: Fri, 3 Jan 2025 17:09:05 +0100 Subject: [PATCH 05/57] [Fix] Fix type annotations for first part of obs.py --- pyerrors/obs.py | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/pyerrors/obs.py b/pyerrors/obs.py index 762f2b06..c009d765 100644 --- a/pyerrors/obs.py +++ b/pyerrors/obs.py @@ -63,13 +63,13 @@ class Obs: 'idl', 'tag', '_covobs', '__dict__'] S_global = 2.0 - S_dict = {} + S_dict: dict[str, float] = {} tau_exp_global = 0.0 - tau_exp_dict = {} + tau_exp_dict: dict[str, float] = {} N_sigma_global = 1.0 - N_sigma_dict = {} + N_sigma_dict: dict[str, int] = {} - def __init__(self, samples: Union[List[List[int]], List[ndarray], ndarray, List[List[float64]], List[List[float]]], names: List[Union[int, Any, str]], idl: Optional[Any]=None, **kwargs): + def __init__(self, samples: Union[List[List[int]], List[ndarray], ndarray, List[List[float64]], List[List[float]]], names: List[str], idl: Optional[list[Union[list[int], range]]]=None, **kwargs): """ Initialize Obs object. Parameters @@ -82,7 +82,8 @@ def __init__(self, samples: Union[List[List[int]], List[ndarray], ndarray, List[ list of ranges or lists on which the samples are defined """ - if kwargs.get("means") is None and len(samples): + means: Optional[list[float]] = kwargs.get("means") + if means is None and len(samples): if len(samples) != len(names): raise ValueError('Length of samples and names incompatible.') if idl is not None: @@ -100,17 +101,17 @@ def __init__(self, samples: Union[List[List[int]], List[ndarray], ndarray, List[ if min(len(x) for x in samples) <= 4: raise ValueError('Samples have to have at least 5 entries.') - self.names = sorted(names) + self.names: list[str] = sorted(names) self.shape = {} self.r_values = {} self.deltas = {} - self._covobs = {} + self._covobs: dict[str, Covobs] = {} - self._value = 0 - self.N = 0 - self.idl = {} + self._value: float = 0.0 + self.N: int = 0 + self.idl: dict[str, Union[list[int], range]] = {} if idl is not None: - for name, idx in sorted(zip(names, idl)): + for name, idx in sorted(zip(names, idl, strict=True)): if isinstance(idx, range): self.idl[name] = idx elif isinstance(idx, (list, np.ndarray)): @@ -124,19 +125,19 @@ def __init__(self, samples: Union[List[List[int]], List[ndarray], ndarray, List[ else: self.idl[name] = list(idx) else: - raise TypeError('incompatible type for idl[%s].' % (name)) + raise TypeError('incompatible type for idl[%s].' % name) else: - for name, sample in sorted(zip(names, samples)): + for name, sample in sorted(zip(names, samples, strict=True)): self.idl[name] = range(1, len(sample) + 1) - if kwargs.get("means") is not None: - for name, sample, mean in sorted(zip(names, samples, kwargs.get("means"))): + if means is not None: + for name, sample, mean in sorted(zip(names, samples, means, strict=True)): self.shape[name] = len(self.idl[name]) self.N += self.shape[name] self.r_values[name] = mean self.deltas[name] = sample else: - for name, sample in sorted(zip(names, samples)): + for name, sample in sorted(zip(names, samples, strict=True)): self.shape[name] = len(self.idl[name]) self.N += self.shape[name] if len(sample) != self.shape[name]: @@ -642,7 +643,7 @@ def plot_piechart(self, save: None=None) -> Dict[str, float64]: if save: fig1.savefig(save) - return dict(zip(labels, sizes)) + return dict(zip(labels, sizes, strict=True)) def dump(self, filename: str, datatype: str="json.gz", description: str="", **kwargs): """Dump the Obs to a file 'name' of chosen format. From 01982568f0c5eb0c29820f27d4ab1788cb957608 Mon Sep 17 00:00:00 2001 From: Fabian Joswig Date: Fri, 3 Jan 2025 18:19:17 +0100 Subject: [PATCH 06/57] [Fix] Fixed most type annotations in obs.py --- pyerrors/obs.py | 119 +++++++++++++++++++++++++++--------------------- 1 file changed, 66 insertions(+), 53 deletions(-) diff --git a/pyerrors/obs.py b/pyerrors/obs.py index c009d765..83b69549 100644 --- a/pyerrors/obs.py +++ b/pyerrors/obs.py @@ -13,7 +13,7 @@ from itertools import groupby from .covobs import Covobs from numpy import bool, float64, int64, ndarray -from typing import Any, Callable, Dict, List, Optional, Union, TYPE_CHECKING +from typing import Any, Callable, Optional, Union, Sequence, TYPE_CHECKING if sys.version_info >= (3, 10): from types import NotImplementedType @@ -69,7 +69,7 @@ class Obs: N_sigma_global = 1.0 N_sigma_dict: dict[str, int] = {} - def __init__(self, samples: Union[List[List[int]], List[ndarray], ndarray, List[List[float64]], List[List[float]]], names: List[str], idl: Optional[list[Union[list[int], range]]]=None, **kwargs): + def __init__(self, samples: list[Union[ndarray, list[Any]]], names: list[str], idl: Optional[list[Union[list[int], range]]]=None, **kwargs): """ Initialize Obs object. Parameters @@ -98,13 +98,16 @@ def __init__(self, samples: Union[List[List[int]], List[ndarray], ndarray, List[ else: if not isinstance(names[0], str): raise TypeError('All names have to be strings.') + # This check does not work because of nan hacks in the json.gz export + # if not all((isinstance(o, np.ndarray) and o.ndim == 1) for o in samples): + # raise TypeError('All samples have to be 1d numpy arrays.') if min(len(x) for x in samples) <= 4: raise ValueError('Samples have to have at least 5 entries.') self.names: list[str] = sorted(names) self.shape = {} - self.r_values = {} - self.deltas = {} + self.r_values: dict[str, float] = {} + self.deltas: dict[str, ndarray] = {} self._covobs: dict[str, Covobs] = {} self._value: float = 0.0 @@ -143,12 +146,12 @@ def __init__(self, samples: Union[List[List[int]], List[ndarray], ndarray, List[ if len(sample) != self.shape[name]: raise ValueError('Incompatible samples and idx for %s: %d vs. %d' % (name, len(sample), self.shape[name])) self.r_values[name] = np.mean(sample) - self.deltas[name] = sample - self.r_values[name] + self.deltas[name] = np.asarray(sample) - self.r_values[name] self._value += self.shape[name] * self.r_values[name] self._value /= self.N - self._dvalue = 0.0 - self.ddvalue = 0.0 + self._dvalue: float = 0.0 + self.ddvalue: float = 0.0 self.reweighted = False self.tag = None @@ -162,19 +165,19 @@ def dvalue(self) -> Union[float, float64]: return self._dvalue @property - def e_names(self) -> List[str]: + def e_names(self) -> list[str]: return sorted(set([o.split('|')[0] for o in self.names])) @property - def cov_names(self) -> List[Union[Any, str]]: + def cov_names(self) -> list[Union[Any, str]]: return sorted(set([o for o in self.covobs.keys()])) @property - def mc_names(self) -> List[Union[Any, str]]: + def mc_names(self) -> list[Union[Any, str]]: return sorted(set([o.split('|')[0] for o in self.names if o not in self.cov_names])) @property - def e_content(self) -> Dict[str, List[str]]: + def e_content(self) -> dict[str, list[str]]: res = {} for e, e_name in enumerate(self.e_names): res[e_name] = sorted(filter(lambda x: x.startswith(e_name + '|'), self.names)) @@ -183,7 +186,7 @@ def e_content(self) -> Dict[str, List[str]]: return res @property - def covobs(self) -> Dict[str, Covobs]: + def covobs(self) -> dict[str, Covobs]: return self._covobs def gamma_method(self, **kwargs): @@ -354,7 +357,7 @@ def _compute_drho(i): gm = gamma_method - def _calc_gamma(self, deltas: ndarray, idx: Union[range, List[int], List[int64]], shape: int, w_max: Union[int64, int], fft: bool, gapsize: Union[int64, int]) -> ndarray: + def _calc_gamma(self, deltas: ndarray, idx: Union[range, list[int], list[int64]], shape: int, w_max: Union[int64, int], fft: bool, gapsize: Union[int64, int]) -> ndarray: """Calculate Gamma_{AA} from the deltas, which are defined on idx. idx is assumed to be a contiguous range (possibly with a stepsize != 1) @@ -438,19 +441,21 @@ def details(self, ens_content: bool=True): my_string = ' ' + "\u00B7 Ensemble '" + key + "' " if len(value) == 1: my_string += f': {self.shape[value[0]]} configurations' - if isinstance(self.idl[value[0]], range): - my_string += f' (from {self.idl[value[0]].start} to {self.idl[value[0]][-1]}' + int(self.idl[value[0]].step != 1) * f' in steps of {self.idl[value[0]].step}' + ')' + my_idl = self.idl[value[0]] + if isinstance(my_idl, range): + my_string += f' (from {my_idl.start} to {my_idl[-1]}' + int(my_idl.step != 1) * f' in steps of {my_idl.step}' + ')' else: - my_string += f' (irregular range from {self.idl[value[0]][0]} to {self.idl[value[0]][-1]})' + my_string += f' (irregular range from {my_idl[0]} to {my_idl[-1]})' else: sublist = [] for v in value: my_substring = ' ' + "\u00B7 Replicum '" + v[len(key) + 1:] + "' " my_substring += f': {self.shape[v]} configurations' - if isinstance(self.idl[v], range): - my_substring += f' (from {self.idl[v].start} to {self.idl[v][-1]}' + int(self.idl[v].step != 1) * f' in steps of {self.idl[v].step}' + ')' + my_idl = self.idl[v] + if isinstance(my_idl, range): + my_substring += f' (from {my_idl.start} to {my_idl[-1]}' + int(my_idl.step != 1) * f' in steps of {my_idl.step}' + ')' else: - my_substring += f' (irregular range from {self.idl[v][0]} to {self.idl[v][-1]})' + my_substring += f' (irregular range from {my_idl[0]} to {my_idl[-1]})' sublist.append(my_substring) my_string += '\n' + '\n'.join(sublist) @@ -621,7 +626,7 @@ def plot_history(self, expand: bool=True): plt.title(e_name + f'\nskew: {skew(y_test):.3f} (p={skewtest(y_test).pvalue:.3f}), kurtosis: {kurtosis(y_test):.3f} (p={kurtosistest(y_test).pvalue:.3f})') plt.draw() - def plot_piechart(self, save: None=None) -> Dict[str, float64]: + def plot_piechart(self, save: None=None) -> dict[str, float64]: """Plot piechart which shows the fractional contribution of each ensemble to the error and returns a dictionary containing the fractions. @@ -635,7 +640,7 @@ def plot_piechart(self, save: None=None) -> Dict[str, float64]: if np.isclose(0.0, self._dvalue, atol=1e-15): raise ValueError('Error is 0.0') labels = self.e_names - sizes = [self.e_dvalue[name] ** 2 for name in labels] / self._dvalue ** 2 + sizes = np.array([self.e_dvalue[name] ** 2 for name in labels]) / self._dvalue ** 2 fig1, ax1 = plt.subplots() ax1.pie(sizes, labels=labels, startangle=90, normalize=True) ax1.axis('equal') @@ -660,8 +665,11 @@ def dump(self, filename: str, datatype: str="json.gz", description: str="", **kw path : str specifies a custom path for the file (default '.') """ - if 'path' in kwargs: - file_name = kwargs.get('path') + '/' + filename + path = kwargs.get('path') + if path is not None: + if not isinstance(path, str): + raise TypeError('path has to be a string.') + file_name = path + '/' + filename else: file_name = filename @@ -771,7 +779,8 @@ def __hash__(self) -> int: hash_tuple += tuple([np.array([o.errsq()]).astype(np.float32).data.tobytes() for o in self.covobs.values()]) hash_tuple += tuple([o.encode() for o in self.names]) m = hashlib.md5() - [m.update(o) for o in hash_tuple] + for o in hash_tuple: + m.update(o) return int(m.hexdigest(), 16) & 0xFFFFFFFF # Overload comparisons @@ -806,7 +815,7 @@ def __add__(self, y: Any) -> Union[Obs, NotImplementedType, CObs, ndarray]: else: return derived_observable(lambda x, **kwargs: x[0] + y, [self], man_grad=[1]) - def __radd__(self, y: Union[float, int]) -> Obs: + def __radd__(self, y: Union[float, int]) -> Union[Obs, NotImplementedType, CObs, ndarray]: return self + y def __mul__(self, y: Any) -> Union[Obs, ndarray, CObs, NotImplementedType]: @@ -822,7 +831,7 @@ def __mul__(self, y: Any) -> Union[Obs, ndarray, CObs, NotImplementedType]: else: return derived_observable(lambda x, **kwargs: x[0] * y, [self], man_grad=[y]) - def __rmul__(self, y: Union[float, int]) -> Obs: + def __rmul__(self, y: Union[float, int]) -> Union[Obs, NotImplementedType, CObs, ndarray]: return self * y def __sub__(self, y: Any) -> Union[Obs, NotImplementedType, ndarray]: @@ -836,13 +845,13 @@ def __sub__(self, y: Any) -> Union[Obs, NotImplementedType, ndarray]: else: return derived_observable(lambda x, **kwargs: x[0] - y, [self], man_grad=[1]) - def __rsub__(self, y: Union[float, int]) -> Obs: + def __rsub__(self, y: Union[float, int]) -> Union[Obs, NotImplementedType, CObs, ndarray]: return -1 * (self - y) def __pos__(self) -> Obs: return self - def __neg__(self) -> Obs: + def __neg__(self) -> Union[Obs, NotImplementedType, CObs, ndarray]: return -1 * self def __truediv__(self, y: Any) -> Union[Obs, NotImplementedType, ndarray]: @@ -984,7 +993,7 @@ def __mul__(self, other: Any) -> Union[CObs, ndarray]: if isinstance(other, np.ndarray): return other * self elif hasattr(other, 'real') and hasattr(other, 'imag'): - if all(isinstance(i, Obs) for i in [self.real, self.imag, other.real, other.imag]): + if isinstance(self.real, Obs) and isinstance(self.imag, Obs) and isinstance(other.real, Obs) and isinstance(other.imag, Obs): return CObs(derived_observable(lambda x, **kwargs: x[0] * x[1] - x[2] * x[3], [self.real, other.real, self.imag, other.imag], man_grad=[other.real.value, self.real.value, -other.imag.value, -self.imag.value]), @@ -1027,8 +1036,11 @@ def __pos__(self) -> "CObs": def __neg__(self) -> "CObs": return -1 * self - def __eq__(self, other: Union[CObs, int]) -> bool: - return self.real == other.real and self.imag == other.imag + def __eq__(self, other: object) -> bool: + if hasattr(other, 'real') and hasattr(other, 'imag'): + return self.real == other.real and self.imag == other.imag + else: + return False def __str__(self) -> str: return '(' + str(self.real) + int(self.imag >= 0.0) * '+' + str(self.imag) + 'j)' @@ -1045,7 +1057,7 @@ def __format__(self, format_type: str) -> str: return f"({self.real:{format_type}}{self.imag:+{significance}}j)" -def gamma_method(x: Union[Corr, Obs, ndarray, List[Obs]], **kwargs) -> ndarray: +def gamma_method(x: Union[Corr, Obs, ndarray, list[Obs]], **kwargs) -> ndarray: """Vectorized version of the gamma_method applicable to lists or arrays of Obs. See docstring of pe.Obs.gamma_method for details. @@ -1073,7 +1085,7 @@ def _format_uncertainty(value: Union[float, float64, int], dvalue: Union[float, return f"{value:.{max(0, int(significance - fexp - 1))}f}({dvalue:2.{max(0, int(significance - fexp - 1))}f})" -def _expand_deltas(deltas: ndarray, idx: Union[range, List[int], List[int64]], shape: int, gapsize: Union[int64, int]) -> ndarray: +def _expand_deltas(deltas: ndarray, idx: Union[range, list[int], list[int64]], shape: int, gapsize: Union[int64, int]) -> ndarray: """Expand deltas defined on idx to a regular range with spacing gapsize between two configurations and where holes are filled by 0. If idx is of type range, the deltas are not changed if the idx.step == gapsize. @@ -1099,7 +1111,7 @@ def _expand_deltas(deltas: ndarray, idx: Union[range, List[int], List[int64]], s return ret -def _merge_idx(idl: List[Union[List[Union[int64, int]], range, List[int]]]) -> Union[List[Union[int64, int]], range, List[int]]: +def _merge_idx(idl: list[Union[list[Union[int, int]], range, list[int]]]) -> Union[list[Union[int, int]], range, list[int]]: """Returns the union of all lists in idl as range or sorted list Parameters @@ -1122,7 +1134,7 @@ def _merge_idx(idl: List[Union[List[Union[int64, int]], range, List[int]]]) -> U return idunion -def _intersection_idx(idl: List[Union[range, List[int]]]) -> Union[range, List[int]]: +def _intersection_idx(idl: list[Union[range, list[int]]]) -> Union[range, list[int]]: """Returns the intersection of all lists in idl as range or sorted list Parameters @@ -1148,7 +1160,7 @@ def _intersection_idx(idl: List[Union[range, List[int]]]) -> Union[range, List[i return idinter -def _expand_deltas_for_merge(deltas: ndarray, idx: Union[range, List[int]], shape: int, new_idx: Union[range, List[int]], scalefactor: Union[float, int]) -> ndarray: +def _expand_deltas_for_merge(deltas: ndarray, idx: Union[range, list[int]], shape: int, new_idx: Union[range, list[int]], scalefactor: Union[float, int]) -> ndarray: """Expand deltas defined on idx to the list of configs that is defined by new_idx. New, empty entries are filled by 0. If idx and new_idx are of type range, the smallest common divisor of the step sizes is used as new step size. @@ -1218,7 +1230,7 @@ def derived_observable(func: Callable, data: Any, array_mode: bool=False, **kwar if isinstance(raveled_data[i], (int, float)): raveled_data[i] = cov_Obs(raveled_data[i], 0.0, "###dummy_covobs###") - allcov = {} + allcov: dict[str, ndarray] = {} for o in raveled_data: for name in o.cov_names: if name in allcov: @@ -1308,8 +1320,8 @@ def __init__(self, N): self.grad = np.zeros((N, 1)) new_covobs_lengths = dict(set([y for x in [[(n, o.covobs[n].N) for n in o.cov_names] for o in raveled_data] for y in x])) - d_extracted = {} - g_extracted = {} + d_extracted: dict[str, list] = {} + g_extracted: dict[str, list] = {} for name in new_sample_names: d_extracted[name] = [] ens_length = len(new_idl_d[name]) @@ -1370,7 +1382,7 @@ def __init__(self, N): return final_result -def _reduce_deltas(deltas: Union[List[float], ndarray], idx_old: Union[range, List[int]], idx_new: Union[range, List[int], ndarray]) -> Union[List[float], ndarray]: +def _reduce_deltas(deltas: Union[list[float], ndarray], idx_old: Union[range, list[int]], idx_new: Union[range, list[int], ndarray]) -> Union[list[float], ndarray]: """Extract deltas defined on idx_old on all configs of idx_new. Assumes, that idx_old and idx_new are correctly defined idl, i.e., they @@ -1399,7 +1411,7 @@ def _reduce_deltas(deltas: Union[List[float], ndarray], idx_old: Union[range, Li return np.array(deltas)[indices] -def reweight(weight: Obs, obs: Union[ndarray, List[Obs]], **kwargs) -> List[Obs]: +def reweight(weight: Obs, obs: Union[ndarray, list[Obs]], **kwargs) -> list[Obs]: """Reweight a list of observables. Parameters @@ -1484,7 +1496,7 @@ def correlate(obs_a: Obs, obs_b: Obs) -> Obs: return o -def covariance(obs: Union[ndarray, List[Obs]], visualize: bool=False, correlation: bool=False, smooth: Optional[int]=None, **kwargs) -> ndarray: +def covariance(obs: Union[ndarray, list[Obs]], visualize: bool=False, correlation: bool=False, smooth: Optional[int]=None, **kwargs) -> ndarray: r'''Calculates the error covariance matrix of a set of observables. WARNING: This function should be used with care, especially for observables with support on multiple @@ -1577,7 +1589,7 @@ def invert_corr_cov_cholesky(corr: ndarray, inverrdiag: ndarray) -> ndarray: return chol_inv -def sort_corr(corr: ndarray, kl: List[str], yd: Dict[str, List[Obs]]) -> ndarray: +def sort_corr(corr: ndarray, kl: list[str], yd: dict[str, list[Obs]]) -> ndarray: """ Reorders a correlation matrix to match the alphabetical order of its underlying y data. The ordering of the input correlation matrix `corr` is given by the list of keys `kl`. @@ -1717,7 +1729,7 @@ def calc_gamma(deltas1, deltas2, idx1, idx2, new_idx): return dvalue -def import_jackknife(jacks: ndarray, name: str, idl: Optional[List[range]]=None) -> Obs: +def import_jackknife(jacks: ndarray, name: str, idl: Optional[list[Union[list[int], range]]]=None) -> Obs: """Imports jackknife samples and returns an Obs Parameters @@ -1767,7 +1779,7 @@ def import_bootstrap(boots: ndarray, name: str, random_numbers: ndarray) -> Obs: return ret -def merge_obs(list_of_obs: List[Obs]) -> Obs: +def merge_obs(list_of_obs: list[Obs]) -> Obs: """Combine all observables in list_of_obs into one new observable Parameters @@ -1785,11 +1797,11 @@ def merge_obs(list_of_obs: List[Obs]) -> Obs: if any([len(o.cov_names) for o in list_of_obs]): raise ValueError('Not possible to merge data that contains covobs!') new_dict = {} - idl_dict = {} + idl_dict: dict[str, Union[range, list[int]]] = {} for o in list_of_obs: new_dict.update({key: o.deltas.get(key, 0) + o.r_values.get(key, 0) for key in set(o.deltas) | set(o.r_values)}) - idl_dict.update({key: o.idl.get(key, 0) for key in set(o.deltas)}) + idl_dict.update({key: o.idl.get(key) for key in set(o.deltas)}) names = sorted(new_dict.keys()) o = Obs([new_dict[name] for name in names], names, idl=[idl_dict[name] for name in names]) @@ -1797,7 +1809,7 @@ def merge_obs(list_of_obs: List[Obs]) -> Obs: return o -def cov_Obs(means: Union[float64, int, List[float], float, List[int]], cov: Any, name: str, grad: None=None) -> Union[Obs, List[Obs]]: +def cov_Obs(means: Union[int, list[float], float, list[int]], cov: Any, name: str, grad: None=None) -> Union[Obs, list[Obs]]: """Create an Obs based on mean(s) and a covariance matrix Parameters @@ -1840,13 +1852,14 @@ def covobs_to_obs(co): return ol -def _determine_gap(o: Obs, e_content: Dict[str, List[str]], e_name: str) -> Union[int64, int]: +def _determine_gap(o: Obs, e_content: dict[str, list[str]], e_name: str) -> Union[int64, int]: gaps = [] for r_name in e_content[e_name]: - if isinstance(o.idl[r_name], range): - gaps.append(o.idl[r_name].step) + my_idl =o.idl[r_name] + if isinstance(my_idl, range): + gaps.append(my_idl.step) else: - gaps.append(np.min(np.diff(o.idl[r_name]))) + gaps.append(np.min(np.diff(my_idl))) gap = min(gaps) if not np.all([gi % gap == 0 for gi in gaps]): @@ -1855,7 +1868,7 @@ def _determine_gap(o: Obs, e_content: Dict[str, List[str]], e_name: str) -> Unio return gap -def _check_lists_equal(idl: List[Union[List[int], List[Union[int64, int]], range, ndarray]]): +def _check_lists_equal(idl: Sequence[Union[list[int], list[Union[int64, int]], range, ndarray]]): ''' Use groupby to efficiently check whether all elements of idl are identical. Returns True if all elements are equal, otherwise False. From 9389ad67c9fa42d117345137621027cd0a9b2dfb Mon Sep 17 00:00:00 2001 From: Fabian Joswig Date: Fri, 3 Jan 2025 18:24:46 +0100 Subject: [PATCH 07/57] [CI] Add typing extension package to pytest run for pytthon 3.9 --- .github/workflows/pytest.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 36981809..e4b517bd 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -40,6 +40,7 @@ jobs: pip install pytest-cov pip install pytest-benchmark pip install hypothesis + pip install typing_extensions pip freeze - name: Run tests From 23d4f4c3202d539e84305b7ecebaec75eef6e077 Mon Sep 17 00:00:00 2001 From: Fabian Joswig Date: Fri, 3 Jan 2025 18:39:34 +0100 Subject: [PATCH 08/57] [Fix] Fix type hints in misc.py and remove strict zips for python 3.9 compatability --- pyerrors/misc.py | 15 +++++++++------ pyerrors/obs.py | 10 +++++----- pyproject.toml | 4 ++++ 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/pyerrors/misc.py b/pyerrors/misc.py index 9fe679e7..2582fc9e 100644 --- a/pyerrors/misc.py +++ b/pyerrors/misc.py @@ -75,8 +75,11 @@ def dump_object(obj: Corr, name: str, **kwargs): ------- None """ - if 'path' in kwargs: - file_name = kwargs.get('path') + '/' + name + '.p' + path = kwargs.get('path') + if path is not None: + if not isinstance(path, str): + raise Exception("Path has to be a string.") + file_name = path + '/' + name + '.p' else: file_name = name + '.p' with open(file_name, 'wb') as fb: @@ -100,7 +103,7 @@ def load_object(path: str) -> Union[Obs, Corr]: return pickle.load(file) -def pseudo_Obs(value: Union[float, int64, float64, int], dvalue: Union[float, float64, int], name: str, samples: int=1000) -> Obs: +def pseudo_Obs(value: Union[float, int], dvalue: Union[float, int], name: str, samples: int=1000) -> Obs: """Generate an Obs object with given value, dvalue and name for test purposes Parameters @@ -123,11 +126,11 @@ def pseudo_Obs(value: Union[float, int64, float64, int], dvalue: Union[float, fl return Obs([np.zeros(samples) + value], [name]) else: for _ in range(100): - deltas = [np.random.normal(0.0, dvalue * np.sqrt(samples), samples)] + deltas = np.array([np.random.normal(0.0, dvalue * np.sqrt(samples), samples)]) deltas -= np.mean(deltas) deltas *= dvalue / np.sqrt((np.var(deltas) / samples)) / np.sqrt(1 + 3 / samples) deltas += value - res = Obs(deltas, [name]) + res = Obs(list(deltas), [name]) res.gamma_method(S=2, tau_exp=0) if abs(res.dvalue - dvalue) < 1e-10 * dvalue: break @@ -179,7 +182,7 @@ def gen_correlated_data(means: Union[ndarray, List[float]], cov: ndarray, name: return [Obs([dat], [name]) for dat in corr_data.T] -def _assert_equal_properties(ol: Union[List[Obs], List[CObs], ndarray], otype: Type[Obs]=Obs): +def _assert_equal_properties(ol: Union[List[Obs], List[CObs], ndarray]): otype = type(ol[0]) for o in ol[1:]: if not isinstance(o, otype): diff --git a/pyerrors/obs.py b/pyerrors/obs.py index 83b69549..56780d33 100644 --- a/pyerrors/obs.py +++ b/pyerrors/obs.py @@ -114,7 +114,7 @@ def __init__(self, samples: list[Union[ndarray, list[Any]]], names: list[str], i self.N: int = 0 self.idl: dict[str, Union[list[int], range]] = {} if idl is not None: - for name, idx in sorted(zip(names, idl, strict=True)): + for name, idx in sorted(zip(names, idl)): if isinstance(idx, range): self.idl[name] = idx elif isinstance(idx, (list, np.ndarray)): @@ -130,17 +130,17 @@ def __init__(self, samples: list[Union[ndarray, list[Any]]], names: list[str], i else: raise TypeError('incompatible type for idl[%s].' % name) else: - for name, sample in sorted(zip(names, samples, strict=True)): + for name, sample in sorted(zip(names, samples)): self.idl[name] = range(1, len(sample) + 1) if means is not None: - for name, sample, mean in sorted(zip(names, samples, means, strict=True)): + for name, sample, mean in sorted(zip(names, samples, means)): self.shape[name] = len(self.idl[name]) self.N += self.shape[name] self.r_values[name] = mean self.deltas[name] = sample else: - for name, sample in sorted(zip(names, samples, strict=True)): + for name, sample in sorted(zip(names, samples)): self.shape[name] = len(self.idl[name]) self.N += self.shape[name] if len(sample) != self.shape[name]: @@ -648,7 +648,7 @@ def plot_piechart(self, save: None=None) -> dict[str, float64]: if save: fig1.savefig(save) - return dict(zip(labels, sizes, strict=True)) + return dict(zip(labels, sizes)) def dump(self, filename: str, datatype: str="json.gz", description: str="", **kwargs): """Dump the Obs to a file 'name' of chosen format. diff --git a/pyproject.toml b/pyproject.toml index 657ec5bb..de95dd75 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,3 +4,7 @@ build-backend = "setuptools.build_meta" [tool.ruff.lint] ignore = ["F403"] + +[tool.mypy] +warn_unused_configs = true +ignore_missing_imports = true From 1916de15ecfa9317c1f4ecf699ff921e643b1e9a Mon Sep 17 00:00:00 2001 From: Fabian Joswig Date: Fri, 3 Jan 2025 19:01:20 +0100 Subject: [PATCH 09/57] [Fix] Start fixing remaining type hints --- pyerrors/correlators.py | 50 ++++++++++++++++++++--------------------- pyerrors/linalg.py | 1 + pyerrors/mpm.py | 4 ++-- 3 files changed, 27 insertions(+), 28 deletions(-) diff --git a/pyerrors/correlators.py b/pyerrors/correlators.py index 436ca6ad..053a2bd5 100644 --- a/pyerrors/correlators.py +++ b/pyerrors/correlators.py @@ -76,7 +76,7 @@ def __init__(self, data_input: Any, padding: List[int]=[0, 0], prange: Optional[ T = data_input[0, 0].T N = data_input.shape[0] - input_as_list = [] + input_as_list: list[Union[None, ndarray]] = [] for t in range(T): if any([(item.content[t] is None) for item in data_input.flatten()]): if not all([(item.content[t] is None) for item in data_input.flatten()]): @@ -100,7 +100,7 @@ def __init__(self, data_input: Any, padding: List[int]=[0, 0], prange: Optional[ if all([isinstance(item, (Obs, CObs)) or item is None for item in data_input]): _assert_equal_properties([o for o in data_input if o is not None]) - self.content = [np.asarray([item]) if item is not None else None for item in data_input] + self.content: list[Union[None, ndarray]] = [np.asarray([item]) if item is not None else None for item in data_input] self.N = 1 elif all([isinstance(item, np.ndarray) or item is None for item in data_input]) and any([isinstance(item, np.ndarray) for item in data_input]): self.content = data_input @@ -124,12 +124,13 @@ def __init__(self, data_input: Any, padding: List[int]=[0, 0], prange: Optional[ def __getitem__(self, idx: Union[slice, int]) -> Union[CObs, Obs, ndarray, List[ndarray]]: """Return the content of timeslice idx""" - if self.content[idx] is None: + idx_content = self.content[idx] + if idx_content is None: return None - elif len(self.content[idx]) == 1: - return self.content[idx][0] + elif len(idx_content) == 1: + return idx_content[0] else: - return self.content[idx] + return idx_content @property def reweighted(self): @@ -154,7 +155,7 @@ def gamma_method(self, **kwargs): gm = gamma_method - def projected(self, vector_l: Optional[Union[ndarray, List[Optional[ndarray]]]]=None, vector_r: None=None, normalize: bool=False) -> "Corr": + def projected(self, vector_l: Optional[Union[ndarray, List[Optional[ndarray]]]]=None, vector_r: Optional[Union[ndarray, List[Optional[ndarray]]]]=None, normalize: bool=False) -> "Corr": """We need to project the Correlator with a Vector to get a single value at each timeslice. The method can use one or two vectors. @@ -166,7 +167,7 @@ def projected(self, vector_l: Optional[Union[ndarray, List[Optional[ndarray]]]]= if vector_l is None: vector_l, vector_r = np.asarray([1.] + (self.N - 1) * [0.]), np.asarray([1.] + (self.N - 1) * [0.]) - elif (vector_r is None): + elif vector_r is None: vector_r = vector_l if isinstance(vector_l, list) and not isinstance(vector_r, list): if len(vector_l) != self.T: @@ -177,7 +178,7 @@ def projected(self, vector_l: Optional[Union[ndarray, List[Optional[ndarray]]]]= raise ValueError("Length of vector list must be equal to T") vector_l = [vector_l] * self.T - if not isinstance(vector_l, list): + if isinstance(vector_l, ndarray) and isinstance(vector_r, ndarray): if not vector_l.shape == vector_r.shape == (self.N,): raise ValueError("Vectors are of wrong shape!") if normalize: @@ -239,7 +240,7 @@ def symmetric(self) -> "Corr": newcontent.append(None) else: newcontent.append(0.5 * (self.content[t] + self.content[self.T - t])) - if (all([x is None for x in newcontent])): + if all([x is None for x in newcontent]): raise ValueError("Corr could not be symmetrized: No redundant values") return Corr(newcontent, prange=self.prange) @@ -284,7 +285,7 @@ def trace(self) -> "Corr": """Calculates the per-timeslice trace of a correlator matrix.""" if self.N == 1: raise ValueError("Only works for correlator matrices.") - newcontent = [] + newcontent: list[Union[None, float]] = [] for t in range(self.T): if _check_for_none(self, self.content[t]): newcontent.append(None) @@ -486,7 +487,7 @@ def thin(self, spacing: int=2, offset: int=0) -> "Corr": offset : int Offset the equal spacing """ - new_content = [] + new_content: list[Union[None, list, ndarray]] = [] for t in range(self.T): if (offset + t) % spacing != 0: new_content.append(None) @@ -506,7 +507,7 @@ def correlate(self, partner: Union[Corr, float, Obs]) -> "Corr": """ if self.N != 1: raise ValueError("Only one-dimensional correlators can be safely correlated.") - new_content = [] + new_content: list[Union[None, ndarray]] = [] for x0, t_slice in enumerate(self.content): if _check_for_none(self, t_slice): new_content.append(None) @@ -538,7 +539,7 @@ def reweight(self, weight: Obs, **kwargs) -> "Corr": """ if self.N != 1: raise Exception("Reweighting only implemented for one-dimensional correlators.") - new_content = [] + new_content: list[Union[None, ndarray]] = [] for t_slice in self.content: if _check_for_none(self, t_slice): new_content.append(None) @@ -660,8 +661,8 @@ def second_deriv(self, variant: Optional[str]="symmetric") -> "Corr": """ if self.N != 1: raise ValueError("second_deriv only implemented for one-dimensional correlators.") + newcontent: list[Union[None, ndarray, Obs]] = [] if variant == "symmetric": - newcontent = [] for t in range(1, self.T - 1): if (self.content[t - 1] is None) or (self.content[t + 1] is None): newcontent.append(None) @@ -671,7 +672,6 @@ def second_deriv(self, variant: Optional[str]="symmetric") -> "Corr": raise ValueError("Derivative is undefined at all timeslices") return Corr(newcontent, padding=[1, 1]) elif variant == "big_symmetric": - newcontent = [] for t in range(2, self.T - 2): if (self.content[t - 2] is None) or (self.content[t + 2] is None): newcontent.append(None) @@ -681,7 +681,6 @@ def second_deriv(self, variant: Optional[str]="symmetric") -> "Corr": raise ValueError("Derivative is undefined at all timeslices") return Corr(newcontent, padding=[2, 2]) elif variant == "improved": - newcontent = [] for t in range(2, self.T - 2): if (self.content[t - 2] is None) or (self.content[t - 1] is None) or (self.content[t] is None) or (self.content[t + 1] is None) or (self.content[t + 2] is None): newcontent.append(None) @@ -691,7 +690,6 @@ def second_deriv(self, variant: Optional[str]="symmetric") -> "Corr": raise ValueError("Derivative is undefined at all timeslices") return Corr(newcontent, padding=[2, 2]) elif variant == 'log': - newcontent = [] for t in range(self.T): if (self.content[t] is None) or (self.content[t] <= 0): newcontent.append(None) @@ -859,7 +857,7 @@ def const_func(a, t): else: raise ValueError("Unsupported plateau method: " + method) - def set_prange(self, prange: List[Union[int, float]]): + def set_prange(self, prange: List[int]): """Sets the attribute prange of the Corr object.""" if not len(prange) == 2: raise ValueError("prange must be a list or array with two values") @@ -1098,7 +1096,7 @@ def __add__(self, y: Any) -> "Corr": if isinstance(y, Corr): if ((self.N != y.N) or (self.T != y.T)): raise ValueError("Addition of Corrs with different shape") - newcontent = [] + newcontent: list[Union[None, ndarray, Obs]] = [] for t in range(self.T): if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]): newcontent.append(None) @@ -1107,7 +1105,7 @@ def __add__(self, y: Any) -> "Corr": return Corr(newcontent) elif isinstance(y, (Obs, int, float, CObs, complex)): - newcontent = [] + newcontent: list[Union[None, ndarray, Obs]] = [] for t in range(self.T): if _check_for_none(self, self.content[t]): newcontent.append(None) @@ -1126,7 +1124,7 @@ def __mul__(self, y: Any) -> "Corr": if isinstance(y, Corr): if not ((self.N == 1 or y.N == 1 or self.N == y.N) and self.T == y.T): raise ValueError("Multiplication of Corr object requires N=N or N=1 and T=T") - newcontent = [] + newcontent: list[Union[None, ndarray, Obs]] = [] for t in range(self.T): if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]): newcontent.append(None) @@ -1156,7 +1154,7 @@ def __matmul__(self, y: Union[Corr, ndarray]) -> "Corr": raise ValueError("Can only multiply correlators by square matrices.") if not self.N == y.shape[0]: raise ValueError("matmul: mismatch of matrix dimensions") - newcontent = [] + newcontent: list[Union[None, ndarray, Obs]] = [] for t in range(self.T): if _check_for_none(self, self.content[t]): newcontent.append(None) @@ -1183,7 +1181,7 @@ def __rmatmul__(self, y: ndarray) -> "Corr": raise ValueError("Can only multiply correlators by square matrices.") if not self.N == y.shape[0]: raise ValueError("matmul: mismatch of matrix dimensions") - newcontent = [] + newcontent: list[Union[None, ndarray, Obs]] = [] for t in range(self.T): if _check_for_none(self, self.content[t]): newcontent.append(None) @@ -1197,7 +1195,7 @@ def __truediv__(self, y: Union[Corr, float, ndarray, int]) -> "Corr": if isinstance(y, Corr): if not ((self.N == 1 or y.N == 1 or self.N == y.N) and self.T == y.T): raise ValueError("Multiplication of Corr object requires N=N or N=1 and T=T") - newcontent = [] + newcontent: list[Union[None, ndarray, Obs]] = [] for t in range(self.T): if _check_for_none(self, self.content[t]) or _check_for_none(y, y.content[t]): newcontent.append(None) @@ -1232,7 +1230,7 @@ def __truediv__(self, y: Union[Corr, float, ndarray, int]) -> "Corr": elif isinstance(y, (int, float)): if y == 0: raise ValueError('Division by zero will return undefined correlator') - newcontent = [] + newcontent: list[Union[None, ndarray, Obs]] = [] for t in range(self.T): if _check_for_none(self, self.content[t]): newcontent.append(None) diff --git a/pyerrors/linalg.py b/pyerrors/linalg.py index f850a830..8b9f9e7b 100644 --- a/pyerrors/linalg.py +++ b/pyerrors/linalg.py @@ -48,6 +48,7 @@ def multi_dot_i(operands): Nr = derived_observable(multi_dot_r, extended_operands, array_mode=True) Ni = derived_observable(multi_dot_i, extended_operands, array_mode=True) + assert isinstance(Nr, ndarray) and isinstance(Ni, ndarray) res = np.empty_like(Nr) for (n, m), entry in np.ndenumerate(Nr): res[n, m] = CObs(Nr[n, m], Ni[n, m]) diff --git a/pyerrors/mpm.py b/pyerrors/mpm.py index 50b5b837..7a85ace3 100644 --- a/pyerrors/mpm.py +++ b/pyerrors/mpm.py @@ -3,10 +3,10 @@ import scipy.linalg from .obs import Obs from .linalg import svd, eig -from typing import List +from typing import Optional -def matrix_pencil_method(corrs: List[Obs], k: int=1, p: None=None, **kwargs) -> List[Obs]: +def matrix_pencil_method(corrs: list[Obs], k: int=1, p: Optional[int]=None, **kwargs) -> list[Obs]: """Matrix pencil method to extract k energy levels from data Implementation of the matrix pencil method based on From 3654635cd7ee670d546143315348333faf0f00dd Mon Sep 17 00:00:00 2001 From: Fabian Joswig Date: Fri, 3 Jan 2025 19:03:00 +0100 Subject: [PATCH 10/57] [Fix] Removed unused imports --- pyerrors/misc.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyerrors/misc.py b/pyerrors/misc.py index 2582fc9e..8d0cbb59 100644 --- a/pyerrors/misc.py +++ b/pyerrors/misc.py @@ -8,8 +8,8 @@ import pickle from .obs import Obs, CObs from .version import __version__ -from numpy import float64, int64, ndarray -from typing import List, Type, Union, TYPE_CHECKING +from numpy import ndarray +from typing import Union, TYPE_CHECKING if TYPE_CHECKING: from .correlators import Corr @@ -140,7 +140,7 @@ def pseudo_Obs(value: Union[float, int], dvalue: Union[float, int], name: str, s return res -def gen_correlated_data(means: Union[ndarray, List[float]], cov: ndarray, name: str, tau: Union[float, ndarray]=0.5, samples: int=1000) -> List[Obs]: +def gen_correlated_data(means: Union[ndarray, list[float]], cov: ndarray, name: str, tau: Union[float, ndarray]=0.5, samples: int=1000) -> list[Obs]: """ Generate observables with given covariance and autocorrelation times. Parameters @@ -182,7 +182,7 @@ def gen_correlated_data(means: Union[ndarray, List[float]], cov: ndarray, name: return [Obs([dat], [name]) for dat in corr_data.T] -def _assert_equal_properties(ol: Union[List[Obs], List[CObs], ndarray]): +def _assert_equal_properties(ol: Union[list[Obs], list[CObs], ndarray]): otype = type(ol[0]) for o in ol[1:]: if not isinstance(o, otype): From 4f1606d26ac3cfdf5d695ae2bf19e463ecc9d83d Mon Sep 17 00:00:00 2001 From: Fabian Joswig Date: Fri, 3 Jan 2025 19:06:26 +0100 Subject: [PATCH 11/57] [CI] Add E252 to flake8 exceptions --- .github/workflows/flake8.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/flake8.yml b/.github/workflows/flake8.yml index c6625b37..8abacb1b 100644 --- a/.github/workflows/flake8.yml +++ b/.github/workflows/flake8.yml @@ -21,6 +21,6 @@ jobs: - name: flake8 Lint uses: py-actions/flake8@v2 with: - ignore: "E501,W503" + ignore: "E501,W503,E252" exclude: "__init__.py, input/__init__.py" path: "pyerrors" From d45b43e6deeb82a92bf1dbdd5a987f155b3d3551 Mon Sep 17 00:00:00 2001 From: Fabian Joswig Date: Fri, 3 Jan 2025 19:07:48 +0100 Subject: [PATCH 12/57] [Fix] Fixed remaining flake8 errors --- pyerrors/misc.py | 1 + pyerrors/obs.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pyerrors/misc.py b/pyerrors/misc.py index 8d0cbb59..c4baff53 100644 --- a/pyerrors/misc.py +++ b/pyerrors/misc.py @@ -14,6 +14,7 @@ if TYPE_CHECKING: from .correlators import Corr + def print_config(): """Print information about version of python, pyerrors and dependencies.""" config = {"system": platform.system(), diff --git a/pyerrors/obs.py b/pyerrors/obs.py index 56780d33..5cf44fc8 100644 --- a/pyerrors/obs.py +++ b/pyerrors/obs.py @@ -1729,7 +1729,7 @@ def calc_gamma(deltas1, deltas2, idx1, idx2, new_idx): return dvalue -def import_jackknife(jacks: ndarray, name: str, idl: Optional[list[Union[list[int], range]]]=None) -> Obs: +def import_jackknife(jacks: ndarray, name: str, idl: Optional[list[Union[list[int], range]]]=None) -> Obs: """Imports jackknife samples and returns an Obs Parameters @@ -1855,7 +1855,7 @@ def covobs_to_obs(co): def _determine_gap(o: Obs, e_content: dict[str, list[str]], e_name: str) -> Union[int64, int]: gaps = [] for r_name in e_content[e_name]: - my_idl =o.idl[r_name] + my_idl = o.idl[r_name] if isinstance(my_idl, range): gaps.append(my_idl.step) else: From 1c6053ef61b55052c2de0bb9d372ee2568794d30 Mon Sep 17 00:00:00 2001 From: Fabian Joswig Date: Fri, 3 Jan 2025 22:43:19 +0100 Subject: [PATCH 13/57] [Fix] Simplify type hints --- pyerrors/correlators.py | 28 ++++++++++++++-------------- pyerrors/covobs.py | 8 ++++---- pyerrors/fits.py | 18 +++++++++--------- pyerrors/input/dobs.py | 34 +++++++++++++++++----------------- pyerrors/input/json.py | 18 +++++++++--------- pyerrors/input/misc.py | 4 ++-- pyerrors/input/openQCD.py | 12 ++++++------ pyerrors/input/sfcf.py | 30 +++++++++++++++--------------- pyerrors/input/utils.py | 3 +-- pyerrors/integrate.py | 4 ++-- pyerrors/linalg.py | 6 +++--- pyerrors/roots.py | 4 ++-- 12 files changed, 84 insertions(+), 85 deletions(-) diff --git a/pyerrors/correlators.py b/pyerrors/correlators.py index 053a2bd5..729cf560 100644 --- a/pyerrors/correlators.py +++ b/pyerrors/correlators.py @@ -10,8 +10,8 @@ from .fits import least_squares, Fit_result from .roots import find_root from . import linalg -from numpy import float64, int64, ndarray, ufunc -from typing import Any, Callable, List, Optional, Tuple, Union +from numpy import ndarray, ufunc +from typing import Any, Callable, Optional, Union class Corr: @@ -45,7 +45,7 @@ class Corr: __slots__ = ["content", "N", "T", "tag", "prange"] - def __init__(self, data_input: Any, padding: List[int]=[0, 0], prange: Optional[List[int]]=None): + def __init__(self, data_input: Any, padding: list[int]=[0, 0], prange: Optional[list[int]]=None): """ Initialize a Corr object. Parameters @@ -122,7 +122,7 @@ def __init__(self, data_input: Any, padding: List[int]=[0, 0], prange: Optional[ self.T = len(self.content) self.prange = prange - def __getitem__(self, idx: Union[slice, int]) -> Union[CObs, Obs, ndarray, List[ndarray]]: + def __getitem__(self, idx: Union[slice, int]) -> Union[CObs, Obs, ndarray, list[ndarray]]: """Return the content of timeslice idx""" idx_content = self.content[idx] if idx_content is None: @@ -155,7 +155,7 @@ def gamma_method(self, **kwargs): gm = gamma_method - def projected(self, vector_l: Optional[Union[ndarray, List[Optional[ndarray]]]]=None, vector_r: Optional[Union[ndarray, List[Optional[ndarray]]]]=None, normalize: bool=False) -> "Corr": + def projected(self, vector_l: Optional[Union[ndarray, list[Optional[ndarray]]]]=None, vector_r: Optional[Union[ndarray, list[Optional[ndarray]]]]=None, normalize: bool=False) -> "Corr": """We need to project the Correlator with a Vector to get a single value at each timeslice. The method can use one or two vectors. @@ -209,7 +209,7 @@ def item(self, i: int, j: int) -> "Corr": newcontent = [None if (item is None) else item[i, j] for item in self.content] return Corr(newcontent) - def plottable(self) -> Union[Tuple[List[int], List[float64], List[float64]], Tuple[List[int], List[float], List[float64]]]: + def plottable(self) -> tuple[list[int], list[float]]: """Outputs the correlator in a plotable format. Outputs three lists containing the timeslice index, the value on each @@ -303,7 +303,7 @@ def matrix_symmetric(self) -> "Corr": transposed = [None if _check_for_none(self, G) else G.T for G in self.content] return 0.5 * (Corr(transposed) + self) - def GEVP(self, t0: int, ts: Optional[int]=None, sort: Optional[str]="Eigenvalue", vector_obs: bool=False, **kwargs) -> Union[List[List[Optional[ndarray]]], ndarray, List[Optional[ndarray]]]: + def GEVP(self, t0: int, ts: Optional[int]=None, sort: Optional[str]="Eigenvalue", vector_obs: bool=False, **kwargs) -> Union[list[list[Optional[ndarray]]], ndarray, list[Optional[ndarray]]]: r'''Solve the generalized eigenvalue problem on the correlator matrix and returns the corresponding eigenvectors. The eigenvectors are sorted according to the descending eigenvalues, the zeroth eigenvector(s) correspond to the @@ -786,7 +786,7 @@ def root_function(x, d): else: raise ValueError('Unknown variant.') - def fit(self, function: Callable, fitrange: Optional[Union[str, List[int]]]=None, silent: bool=False, **kwargs) -> Fit_result: + def fit(self, function: Callable, fitrange: Optional[Union[str, list[int]]]=None, silent: bool=False, **kwargs) -> Fit_result: r'''Fits function to the data Parameters @@ -820,7 +820,7 @@ def fit(self, function: Callable, fitrange: Optional[Union[str, List[int]]]=None result = least_squares(xs, ys, function, silent=silent, **kwargs) return result - def plateau(self, plateau_range: Optional[List[int]]=None, method: str="fit", auto_gamma: bool=False) -> Obs: + def plateau(self, plateau_range: Optional[list[int]]=None, method: str="fit", auto_gamma: bool=False) -> Obs: """ Extract a plateau value from a Corr object Parameters @@ -857,7 +857,7 @@ def const_func(a, t): else: raise ValueError("Unsupported plateau method: " + method) - def set_prange(self, prange: List[int]): + def set_prange(self, prange: list[int]): """Sets the attribute prange of the Corr object.""" if not len(prange) == 2: raise ValueError("prange must be a list or array with two values") @@ -869,7 +869,7 @@ def set_prange(self, prange: List[int]): self.prange = prange return - def show(self, x_range: Optional[List[int64]]=None, comp: Optional[Corr]=None, y_range: None=None, logscale: bool=False, plateau: None=None, fit_res: Optional[Fit_result]=None, fit_key: Optional[str]=None, ylabel: None=None, save: None=None, auto_gamma: bool=False, hide_sigma: None=None, references: None=None, title: None=None): + def show(self, x_range: Optional[list[int]]=None, comp: Optional[Corr]=None, y_range: None=None, logscale: bool=False, plateau: None=None, fit_res: Optional[Fit_result]=None, fit_key: Optional[str]=None, ylabel: None=None, save: None=None, auto_gamma: bool=False, hide_sigma: None=None, references: None=None, title: None=None): """Plots the correlator using the tag of the correlator as label if available. Parameters @@ -1047,10 +1047,10 @@ def dump(self, filename: str, datatype: str="json.gz", **kwargs): else: raise ValueError("Unknown datatype " + str(datatype)) - def print(self, print_range: Optional[List[int]]=None): + def print(self, print_range: Optional[list[int]]=None): print(self.__repr__(print_range)) - def __repr__(self, print_range: Optional[List[int]]=None) -> str: + def __repr__(self, print_range: Optional[list[int]]=None) -> str: if print_range is None: print_range = [0, None] @@ -1415,7 +1415,7 @@ def prune(self, Ntrunc: int, tproj: int=3, t0proj: int=2, basematrix: None=None) return Corr(newcontent) -def _sort_vectors(vec_set_in: List[Optional[ndarray]], ts: int) -> List[Optional[Union[ndarray, List[ndarray]]]]: +def _sort_vectors(vec_set_in: list[Optional[ndarray]], ts: int) -> list[Optional[Union[ndarray, list[ndarray]]]]: """Helper function used to find a set of Eigenvectors consistent over all timeslices""" if isinstance(vec_set_in[ts][0][0], Obs): diff --git a/pyerrors/covobs.py b/pyerrors/covobs.py index e3056f04..2c654ee0 100644 --- a/pyerrors/covobs.py +++ b/pyerrors/covobs.py @@ -1,12 +1,12 @@ from __future__ import annotations import numpy as np -from numpy import float64, ndarray -from typing import Any, List, Optional, Union +from numpy import ndarray +from typing import Any, Optional, Union class Covobs: - def __init__(self, mean: Optional[Union[float, float64, int]], cov: Any, name: str, pos: Optional[int]=None, grad: Optional[Union[ndarray, List[float]]]=None): + def __init__(self, mean: Optional[Union[float, int]], cov: Any, name: str, pos: Optional[int]=None, grad: Optional[Union[ndarray, list[float]]]=None): """ Initialize Covobs object. Parameters @@ -82,7 +82,7 @@ def _set_cov(self, cov: Any): if ev < 0: raise Exception('Covariance matrix is not positive-semidefinite!') - def _set_grad(self, grad: Union[List[float], ndarray]): + def _set_grad(self, grad: Union[list[float], ndarray]): """ Set the gradient of the covobs Parameters diff --git a/pyerrors/fits.py b/pyerrors/fits.py index 8a27c1b1..2a2950ae 100644 --- a/pyerrors/fits.py +++ b/pyerrors/fits.py @@ -17,7 +17,7 @@ from numdifftools import Hessian as num_hessian from .obs import Obs, derived_observable, covariance, cov_Obs, invert_corr_cov_cholesky from numpy import ndarray -from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from typing import Any, Callable, Optional, Union class Fit_result(Sequence): @@ -73,7 +73,7 @@ def __repr__(self) -> str: return '\n'.join([key.rjust(m) + ': ' + repr(value) for key, value in sorted(self.__dict__.items())]) -def least_squares(x: Any, y: Union[Dict[str, ndarray], List[Obs], ndarray, Dict[str, List[Obs]]], func: Union[Callable, Dict[str, Callable]], priors: Optional[Union[Dict[int, str], List[str], List[Obs], Dict[int, Obs]]]=None, silent: bool=False, **kwargs) -> Fit_result: +def least_squares(x: Any, y: Union[dict[str, ndarray], list[Obs], ndarray, dict[str, list[Obs]]], func: Union[Callable, dict[str, Callable]], priors: Optional[Union[dict[int, str], list[str], list[Obs], dict[int, Obs]]]=None, silent: bool=False, **kwargs) -> Fit_result: r'''Performs a non-linear fit to y = func(x). ``` @@ -506,7 +506,7 @@ def chisqfunc_compact(d): return output -def total_least_squares(x: List[Obs], y: List[Obs], func: Callable, silent: bool=False, **kwargs) -> Fit_result: +def total_least_squares(x: list[Obs], y: list[Obs], func: Callable, silent: bool=False, **kwargs) -> Fit_result: r'''Performs a non-linear fit to y = func(x) and returns a list of Obs corresponding to the fit parameters. Parameters @@ -710,7 +710,7 @@ def odr_chisquare_compact_y(d): return output -def fit_lin(x: List[Union[Obs, int, float]], y: List[Obs], **kwargs) -> List[Obs]: +def fit_lin(x: list[Union[Obs, int, float]], y: list[Obs], **kwargs) -> list[Obs]: """Performs a linear fit to y = n + m * x and returns two Obs n, m. Parameters @@ -741,7 +741,7 @@ def f(a, x): raise TypeError('Unsupported types for x') -def qqplot(x: ndarray, o_y: List[Obs], func: Callable, p: List[Obs], title: str=""): +def qqplot(x: ndarray, o_y: list[Obs], func: Callable, p: list[Obs], title: str=""): """Generates a quantile-quantile plot of the fit result which can be used to check if the residuals of the fit are gaussian distributed. @@ -771,7 +771,7 @@ def qqplot(x: ndarray, o_y: List[Obs], func: Callable, p: List[Obs], title: str= plt.draw() -def residual_plot(x: ndarray, y: List[Obs], func: Callable, fit_res: List[Obs], title: str=""): +def residual_plot(x: ndarray, y: list[Obs], func: Callable, fit_res: list[Obs], title: str=""): """Generates a plot which compares the fit to the data and displays the corresponding residuals For uncorrelated data the residuals are expected to be distributed ~N(0,1). @@ -808,7 +808,7 @@ def residual_plot(x: ndarray, y: List[Obs], func: Callable, fit_res: List[Obs], plt.draw() -def error_band(x: List[int], func: Callable, beta: List[Obs]) -> ndarray: +def error_band(x: list[int], func: Callable, beta: list[Obs]) -> ndarray: """Calculate the error band for an array of sample values x, for given fit function func with optimized parameters beta. Returns @@ -832,7 +832,7 @@ def error_band(x: List[int], func: Callable, beta: List[Obs]) -> ndarray: return err -def ks_test(objects: Optional[List[Fit_result]]=None): +def ks_test(objects: Optional[list[Fit_result]]=None): """Performs a Kolmogorov–Smirnov test for the p-values of all fit object. Parameters @@ -876,7 +876,7 @@ def ks_test(objects: Optional[List[Fit_result]]=None): print(scipy.stats.kstest(p_values, 'uniform')) -def _extract_val_and_dval(string: str) -> Tuple[float, float]: +def _extract_val_and_dval(string: str) -> tuple[float, float]: split_string = string.split('(') if '.' in split_string[0] and '.' not in split_string[1][:-1]: factor = 10 ** -len(split_string[0].partition('.')[2]) diff --git a/pyerrors/input/dobs.py b/pyerrors/input/dobs.py index 201bbff8..d4ac638c 100644 --- a/pyerrors/input/dobs.py +++ b/pyerrors/input/dobs.py @@ -14,11 +14,11 @@ from .. import version as pyerrorsversion from lxml.etree import _Element from numpy import ndarray -from typing import Any, Dict, List, Optional, Tuple, Union +from typing import Any, Optional, Union # Based on https://stackoverflow.com/a/10076823 -def _etree_to_dict(t: _Element) -> Dict[str, Union[str, Dict[str, str], Dict[str, Union[str, Dict[str, str]]]]]: +def _etree_to_dict(t: _Element) -> dict[str, Union[str, dict[str, str], dict[str, Union[str, dict[str, str]]]]]: """ Convert the content of an XML file to a python dict""" d = {t.tag: {} if t.attrib else None} children = list(t) @@ -42,7 +42,7 @@ def _etree_to_dict(t: _Element) -> Dict[str, Union[str, Dict[str, str], Dict[str return d -def _dict_to_xmlstring(d: Dict[str, Any]) -> str: +def _dict_to_xmlstring(d: dict[str, Any]) -> str: if isinstance(d, dict): iters = '' for k in d: @@ -70,7 +70,7 @@ def _dict_to_xmlstring(d: Dict[str, Any]) -> str: return iters -def _dict_to_xmlstring_spaces(d: Dict[str, Dict[str, Dict[str, Union[str, Dict[str, str], List[Dict[str, str]]]]]], space: str=' ') -> str: +def _dict_to_xmlstring_spaces(d: dict[str, dict[str, dict[str, Union[str, dict[str, str], list[dict[str, str]]]]]], space: str=' ') -> str: s = _dict_to_xmlstring(d) o = '' c = 0 @@ -89,7 +89,7 @@ def _dict_to_xmlstring_spaces(d: Dict[str, Dict[str, Dict[str, Union[str, Dict[s return o -def create_pobs_string(obsl: List[Obs], name: str, spec: str='', origin: str='', symbol: Optional[List[Union[str, Any]]]=None, enstag: None=None) -> str: +def create_pobs_string(obsl: list[Obs], name: str, spec: str='', origin: str='', symbol: Optional[list[Union[str, Any]]]=None, enstag: None=None) -> str: """Export a list of Obs or structures containing Obs to an xml string according to the Zeuthen pobs format. @@ -182,7 +182,7 @@ def create_pobs_string(obsl: List[Obs], name: str, spec: str='', origin: str='', return rs -def write_pobs(obsl: List[Obs], fname: str, name: str, spec: str='', origin: str='', symbol: Optional[List[Union[str, Any]]]=None, enstag: None=None, gz: bool=True): +def write_pobs(obsl: list[Obs], fname: str, name: str, spec: str='', origin: str='', symbol: Optional[list[Union[str, Any]]]=None, enstag: None=None, gz: bool=True): """Export a list of Obs or structures containing Obs to a .xml.gz file according to the Zeuthen pobs format. @@ -231,7 +231,7 @@ def write_pobs(obsl: List[Obs], fname: str, name: str, spec: str='', origin: str fp.close() -def _import_data(string: str) -> List[Union[int, float]]: +def _import_data(string: str) -> list[Union[int, float]]: return json.loads("[" + ",".join(string.replace(' +', ' ').split()) + "]") @@ -254,7 +254,7 @@ def _find_tag(dat: _Element, tag: str) -> int: raise _NoTagInDataError(tag) -def _import_array(arr: _Element) -> Union[List[Union[str, List[int], List[ndarray]]], ndarray]: +def _import_array(arr: _Element) -> Union[list[Union[str, list[int], list[ndarray]]], ndarray]: name = arr[_find_tag(arr, 'id')].text.strip() index = _find_tag(arr, 'layout') try: @@ -292,12 +292,12 @@ def _import_array(arr: _Element) -> Union[List[Union[str, List[int], List[ndarra _check(False) -def _import_rdata(rd: _Element) -> Tuple[List[ndarray], str, List[int]]: +def _import_rdata(rd: _Element) -> tuple[list[ndarray], str, list[int]]: name, idx, mask, deltas = _import_array(rd) return deltas, name, idx -def _import_cdata(cd: _Element) -> Tuple[str, ndarray, ndarray]: +def _import_cdata(cd: _Element) -> tuple[str, ndarray, ndarray]: _check(cd[0].tag == "id") _check(cd[1][0].text.strip() == "cov") cov = _import_array(cd[1]) @@ -305,7 +305,7 @@ def _import_cdata(cd: _Element) -> Tuple[str, ndarray, ndarray]: return cd[0].text.strip(), cov, grad -def read_pobs(fname: str, full_output: bool=False, gz: bool=True, separator_insertion: None=None) -> Union[Dict[str, Union[str, Dict[str, str], List[Obs]]], List[Obs]]: +def read_pobs(fname: str, full_output: bool=False, gz: bool=True, separator_insertion: None=None) -> Union[dict[str, Union[str, dict[str, str], list[Obs]]], list[Obs]]: """Import a list of Obs from an xml.gz file in the Zeuthen pobs format. Tags are not written or recovered automatically. @@ -405,7 +405,7 @@ def read_pobs(fname: str, full_output: bool=False, gz: bool=True, separator_inse # this is based on Mattia Bruno's implementation at https://github.com/mbruno46/pyobs/blob/master/pyobs/IO/xml.py -def import_dobs_string(content: bytes, full_output: bool=False, separator_insertion: bool=True) -> Union[Dict[str, Union[str, Dict[str, str], List[Obs]]], List[Obs]]: +def import_dobs_string(content: bytes, full_output: bool=False, separator_insertion: bool=True) -> Union[dict[str, Union[str, dict[str, str], list[Obs]]], list[Obs]]: """Import a list of Obs from a string in the Zeuthen dobs format. Tags are not written or recovered automatically. @@ -579,7 +579,7 @@ def import_dobs_string(content: bytes, full_output: bool=False, separator_insert return res -def read_dobs(fname: str, full_output: bool=False, gz: bool=True, separator_insertion: bool=True) -> Union[Dict[str, Union[str, Dict[str, str], List[Obs]]], List[Obs]]: +def read_dobs(fname: str, full_output: bool=False, gz: bool=True, separator_insertion: bool=True) -> Union[dict[str, Union[str, dict[str, str], list[Obs]]], list[Obs]]: """Import a list of Obs from an xml.gz file in the Zeuthen dobs format. Tags are not written or recovered automatically. @@ -626,7 +626,7 @@ def read_dobs(fname: str, full_output: bool=False, gz: bool=True, separator_inse return import_dobs_string(content, full_output, separator_insertion=separator_insertion) -def _dobsdict_to_xmlstring(d: Dict[str, Any]) -> str: +def _dobsdict_to_xmlstring(d: dict[str, Any]) -> str: if isinstance(d, dict): iters = '' for k in d: @@ -666,7 +666,7 @@ def _dobsdict_to_xmlstring(d: Dict[str, Any]) -> str: return iters -def _dobsdict_to_xmlstring_spaces(d: Dict[str, Union[Dict[str, Union[Dict[str, str], Dict[str, Union[str, Dict[str, str]]], Dict[str, Union[str, Dict[str, Union[str, List[str]]], List[Dict[str, Union[str, int, List[Dict[str, str]]]]]]]]], Dict[str, Union[Dict[str, str], Dict[str, Union[str, Dict[str, str]]], Dict[str, Union[str, Dict[str, Union[str, List[str]]], List[Dict[str, Union[str, int, List[Dict[str, str]]]]], List[Dict[str, Union[str, List[Dict[str, str]]]]]]]]]]], space: str=' ') -> str: +def _dobsdict_to_xmlstring_spaces(d: dict[str, Union[dict[str, Union[dict[str, str], dict[str, Union[str, dict[str, str]]], dict[str, Union[str, dict[str, Union[str, list[str]]], list[dict[str, Union[str, int, list[dict[str, str]]]]]]]]], dict[str, Union[dict[str, str], dict[str, Union[str, dict[str, str]]], dict[str, Union[str, dict[str, Union[str, list[str]]], list[dict[str, Union[str, int, list[dict[str, str]]]]], list[dict[str, Union[str, list[dict[str, str]]]]]]]]]]], space: str=' ') -> str: s = _dobsdict_to_xmlstring(d) o = '' c = 0 @@ -685,7 +685,7 @@ def _dobsdict_to_xmlstring_spaces(d: Dict[str, Union[Dict[str, Union[Dict[str, s return o -def create_dobs_string(obsl: List[Obs], name: str, spec: str='dobs v1.0', origin: str='', symbol: Optional[List[Union[str, Any]]]=None, who: None=None, enstags: Optional[Dict[Any, Any]]=None) -> str: +def create_dobs_string(obsl: list[Obs], name: str, spec: str='dobs v1.0', origin: str='', symbol: Optional[list[Union[str, Any]]]=None, who: None=None, enstags: Optional[dict[Any, Any]]=None) -> str: """Generate the string for the export of a list of Obs or structures containing Obs to a .xml.gz file according to the Zeuthen dobs format. @@ -876,7 +876,7 @@ def create_dobs_string(obsl: List[Obs], name: str, spec: str='dobs v1.0', origin return rs -def write_dobs(obsl: List[Obs], fname: str, name: str, spec: str='dobs v1.0', origin: str='', symbol: Optional[List[Union[str, Any]]]=None, who: None=None, enstags: None=None, gz: bool=True): +def write_dobs(obsl: list[Obs], fname: str, name: str, spec: str='dobs v1.0', origin: str='', symbol: Optional[list[Union[str, Any]]]=None, who: None=None, enstags: None=None, gz: bool=True): """Export a list of Obs or structures containing Obs to a .xml.gz file according to the Zeuthen dobs format. diff --git a/pyerrors/input/json.py b/pyerrors/input/json.py index 2463959d..2768a68b 100644 --- a/pyerrors/input/json.py +++ b/pyerrors/input/json.py @@ -13,11 +13,11 @@ from ..correlators import Corr from ..misc import _assert_equal_properties from .. import version as pyerrorsversion -from numpy import float32, float64, int64, ndarray -from typing import Any, Dict, List, Optional, Tuple, Union +from numpy import ndarray +from typing import Any, Optional, Union -def create_json_string(ol: Any, description: Union[str, Dict[str, Union[str, Dict[str, Union[Dict[str, Union[str, Dict[str, Union[int, str]]]], Dict[str, Optional[Union[str, List[str], float]]]]]]], Dict[str, Union[str, Dict[Optional[Union[int, bool]], str], float32]], Dict[str, Dict[str, Dict[str, str]]], Dict[str, Union[str, Dict[Optional[Union[int, bool]], str], Dict[int64, float64]]]]='', indent: int=1) -> str: +def create_json_string(ol: Any, description: Union[str, dict[str, Union[str, dict[str, Union[dict[str, Union[str, dict[str, Union[int, str]]]], dict[str, Optional[Union[str, list[str], float]]]]]]], dict[str, Union[str, dict[Optional[Union[int, bool]], str], float]], dict[str, dict[str, dict[str, str]]], dict[str, Union[str, dict[Optional[Union[int, bool]], str], dict[int, float]]]]='', indent: int=1) -> str: """Generate the string for the export of a list of Obs or structures containing Obs to a .json(.gz) file @@ -219,7 +219,7 @@ def _jsonifier(obj): return json.dumps(d, indent=indent, ensure_ascii=False, default=_jsonifier, write_mode=json.WM_COMPACT) -def dump_to_json(ol: Union[Corr, List[Union[Obs, List[Obs], Corr, ndarray]], ndarray, List[Union[Obs, List[Obs], ndarray]], List[Obs]], fname: str, description: Union[str, Dict[str, Union[str, Dict[str, Union[Dict[str, Union[str, Dict[str, Union[int, str]]]], Dict[str, Optional[Union[str, List[str], float]]]]]]], Dict[str, Union[str, Dict[Optional[Union[int, bool]], str], float32]], Dict[str, Dict[str, Dict[str, str]]], Dict[str, Union[str, Dict[Optional[Union[int, bool]], str], Dict[int64, float64]]]]='', indent: int=1, gz: bool=True): +def dump_to_json(ol: Union[Corr, list[Union[Obs, list[Obs], Corr, ndarray]], ndarray, list[Union[Obs, list[Obs], ndarray]], list[Obs]], fname: str, description: Union[str, dict[str, Union[str, dict[str, Union[dict[str, Union[str, dict[str, Union[int, str]]]], dict[str, Optional[Union[str, list[str], float]]]]]]], dict[str, Union[str, dict[Optional[Union[int, bool]], str], float]], dict[str, dict[str, dict[str, str]]], dict[str, Union[str, dict[Optional[Union[int, bool]], str], dict[int, float]]]]='', indent: int=1, gz: bool=True): """Export a list of Obs or structures containing Obs to a .json(.gz) file. Dict keys that are not JSON-serializable such as floats are converted to strings. @@ -261,7 +261,7 @@ def dump_to_json(ol: Union[Corr, List[Union[Obs, List[Obs], Corr, ndarray]], nda fp.close() -def _parse_json_dict(json_dict: Dict[str, Any], verbose: bool=True, full_output: bool=False) -> Any: +def _parse_json_dict(json_dict: dict[str, Any], verbose: bool=True, full_output: bool=False) -> Any: """Reconstruct a list of Obs or structures containing Obs from a dict that was built out of a json string. @@ -548,7 +548,7 @@ def load_json(fname: str, verbose: bool=True, gz: bool=True, full_output: bool=F return _parse_json_dict(d, verbose, full_output) -def _ol_from_dict(ind: Union[Dict[Optional[Union[int, bool]], str], Dict[str, Union[Dict[str, Union[Obs, List[Obs], Dict[str, Union[int, Obs, Corr, ndarray]]]], Dict[str, Optional[Union[str, List[str], Obs, float]]], str]], Dict[str, Union[Dict[str, Union[Obs, List[Obs], Dict[str, Union[int, Obs, Corr, ndarray]]]], Dict[str, Optional[Union[str, List[str], Obs, float]]], List[str]]], Dict[str, Union[Dict[str, Union[Obs, List[Obs], Dict[str, Union[int, Obs, Corr, ndarray]]]], Dict[str, Optional[Union[str, List[str], Obs, float]]]]]], reps: str='DICTOBS') -> Union[Tuple[List[Any], Dict[Optional[Union[int, bool]], str]], Tuple[List[Union[Obs, List[Obs], Corr, ndarray]], Dict[str, Union[Dict[str, Union[str, Dict[str, Union[int, str]]]], Dict[str, Optional[Union[str, List[str], float]]]]]]]: +def _ol_from_dict(ind: Union[dict[Optional[Union[int, bool]], str], dict[str, Union[dict[str, Union[Obs, list[Obs], dict[str, Union[int, Obs, Corr, ndarray]]]], dict[str, Optional[Union[str, list[str], Obs, float]]], str]], dict[str, Union[dict[str, Union[Obs, list[Obs], dict[str, Union[int, Obs, Corr, ndarray]]]], dict[str, Optional[Union[str, list[str], Obs, float]]], list[str]]], dict[str, Union[dict[str, Union[Obs, list[Obs], dict[str, Union[int, Obs, Corr, ndarray]]]], dict[str, Optional[Union[str, list[str], Obs, float]]]]]], reps: str='DICTOBS') -> Union[tuple[list[Any], dict[Optional[Union[int, bool]], str]], tuple[list[Union[Obs, list[Obs], Corr, ndarray]], dict[str, Union[dict[str, Union[str, dict[str, Union[int, str]]]], dict[str, Optional[Union[str, list[str], float]]]]]]]: """Convert a dictionary of Obs objects to a list and a dictionary that contains placeholders instead of the Obs objects. @@ -628,7 +628,7 @@ def obslist_replace_obs(li): return ol, nd -def dump_dict_to_json(od: Union[Dict[str, Union[Dict[str, Union[Obs, List[Obs], Dict[str, Union[int, Obs, Corr, ndarray]]]], Dict[str, Optional[Union[str, List[str], Obs, float]]], str]], Dict[str, Union[Dict[str, Union[Obs, List[Obs], Dict[str, Union[int, Obs, Corr, ndarray]]]], Dict[str, Optional[Union[str, List[str], Obs, float]]], List[str]]], List[Union[Obs, List[Obs], Corr, ndarray]], Dict[Optional[Union[int, bool]], str], Dict[str, Union[Dict[str, Union[Obs, List[Obs], Dict[str, Union[int, Obs, Corr, ndarray]]]], Dict[str, Optional[Union[str, List[str], Obs, float]]]]]], fname: str, description: Union[str, float32, Dict[int64, float64]]='', indent: int=1, reps: str='DICTOBS', gz: bool=True): +def dump_dict_to_json(od: Union[dict[str, Union[dict[str, Union[Obs, list[Obs], dict[str, Union[int, Obs, Corr, ndarray]]]], dict[str, Optional[Union[str, list[str], Obs, float]]], str]], dict[str, Union[dict[str, Union[Obs, list[Obs], dict[str, Union[int, Obs, Corr, ndarray]]]], dict[str, Optional[Union[str, list[str], Obs, float]]], list[str]]], list[Union[Obs, list[Obs], Corr, ndarray]], dict[Optional[Union[int, bool]], str], dict[str, Union[dict[str, Union[Obs, list[Obs], dict[str, Union[int, Obs, Corr, ndarray]]]], dict[str, Optional[Union[str, list[str], Obs, float]]]]]], fname: str, description: Union[str, float, dict[int, float]]='', indent: int=1, reps: str='DICTOBS', gz: bool=True): """Export a dict of Obs or structures containing Obs to a .json(.gz) file Parameters @@ -668,7 +668,7 @@ def dump_dict_to_json(od: Union[Dict[str, Union[Dict[str, Union[Obs, List[Obs], dump_to_json(ol, fname, description=desc_dict, indent=indent, gz=gz) -def _od_from_list_and_dict(ol: List[Union[Obs, List[Obs], Corr, ndarray]], ind: Dict[str, Dict[str, Optional[Union[str, Dict[str, Union[int, str]], List[str], float]]]], reps: str='DICTOBS') -> Dict[str, Dict[str, Any]]: +def _od_from_list_and_dict(ol: list[Union[Obs, list[Obs], Corr, ndarray]], ind: dict[str, dict[str, Optional[Union[str, dict[str, Union[int, str]], list[str], float]]]], reps: str='DICTOBS') -> dict[str, dict[str, Any]]: """Parse a list of Obs or structures containing Obs and an accompanying dict, where the structures have been replaced by placeholders to a dict that contains the structures. @@ -731,7 +731,7 @@ def list_replace_string(li): return nd -def load_json_dict(fname: str, verbose: bool=True, gz: bool=True, full_output: bool=False, reps: str='DICTOBS') -> Dict[str, Union[Dict[str, Union[Obs, List[Obs], Dict[str, Union[int, Obs, Corr, ndarray]]]], Dict[str, Optional[Union[str, List[str], Obs, float]]], str, Dict[str, Union[Dict[str, Union[Obs, List[Obs], Dict[str, Union[int, Obs, Corr, ndarray]]]], Dict[str, Optional[Union[str, List[str], Obs, float]]]]]]]: +def load_json_dict(fname: str, verbose: bool=True, gz: bool=True, full_output: bool=False, reps: str='DICTOBS') -> dict[str, Union[dict[str, Union[Obs, list[Obs], dict[str, Union[int, Obs, Corr, ndarray]]]], dict[str, Optional[Union[str, list[str], Obs, float]]], str, dict[str, Union[dict[str, Union[Obs, list[Obs], dict[str, Union[int, Obs, Corr, ndarray]]]], dict[str, Optional[Union[str, list[str], Obs, float]]]]]]]: """Import a dict of Obs or structures containing Obs from a .json(.gz) file. The following structures are supported: Obs, list, numpy.ndarray, Corr diff --git a/pyerrors/input/misc.py b/pyerrors/input/misc.py index 0c09b429..252d7249 100644 --- a/pyerrors/input/misc.py +++ b/pyerrors/input/misc.py @@ -9,10 +9,10 @@ from matplotlib import gridspec from ..obs import Obs from ..fits import fit_lin -from typing import Dict, Optional +from typing import Optional -def fit_t0(t2E_dict: Dict[float, Obs], fit_range: int, plot_fit: Optional[bool]=False, observable: str='t0') -> Obs: +def fit_t0(t2E_dict: dict[float, Obs], fit_range: int, plot_fit: Optional[bool]=False, observable: str='t0') -> Obs: """Compute the root of (flow-based) data based on a dictionary that contains the necessary information in key-value pairs a la (flow time: observable at flow time). diff --git a/pyerrors/input/openQCD.py b/pyerrors/input/openQCD.py index 6c23e3f2..339c8bfd 100644 --- a/pyerrors/input/openQCD.py +++ b/pyerrors/input/openQCD.py @@ -10,10 +10,10 @@ from .misc import fit_t0 from .utils import sort_names from io import BufferedReader -from typing import Dict, List, Optional, Tuple, Union +from typing import Optional, Union -def read_rwms(path: str, prefix: str, version: str='2.0', names: Optional[List[str]]=None, **kwargs) -> List[Obs]: +def read_rwms(path: str, prefix: str, version: str='2.0', names: Optional[list[str]]=None, **kwargs) -> list[Obs]: """Read rwms format from given folder structure. Returns a list of length nrw Parameters @@ -232,7 +232,7 @@ def read_rwms(path: str, prefix: str, version: str='2.0', names: Optional[List[s return result -def _extract_flowed_energy_density(path: str, prefix: str, dtr_read: int, xmin: int, spatial_extent: int, postfix: str='ms', **kwargs) -> Dict[float, Obs]: +def _extract_flowed_energy_density(path: str, prefix: str, dtr_read: int, xmin: int, spatial_extent: int, postfix: str='ms', **kwargs) -> dict[float, Obs]: """Extract a dictionary with the flowed Yang-Mills action density from given .ms.dat files. Returns a dictionary with Obs as values and flow times as keys. @@ -580,7 +580,7 @@ def extract_w0(path: str, prefix: str, dtr_read: int, xmin: int, spatial_extent: return np.sqrt(fit_t0(tdtt2E_dict, fit_range, plot_fit=kwargs.get('plot_fit'), observable='w0')) -def _parse_array_openQCD2(d: int, n: Tuple[int, int], size: int, wa: Union[Tuple[float, float, float, float, float, float, float, float], Tuple[float, float]], quadrupel: bool=False) -> List[List[float]]: +def _parse_array_openQCD2(d: int, n: tuple[int, int], size: int, wa: Union[tuple[float, float, float, float, float, float, float, float], tuple[float, float]], quadrupel: bool=False) -> list[list[float]]: arr = [] if d == 2: for i in range(n[0]): @@ -599,7 +599,7 @@ def _parse_array_openQCD2(d: int, n: Tuple[int, int], size: int, wa: Union[Tuple return arr -def _find_files(path: str, prefix: str, postfix: str, ext: str, known_files: Union[str, List[str]]=[]) -> List[str]: +def _find_files(path: str, prefix: str, postfix: str, ext: str, known_files: Union[str, list[str]]=[]) -> list[str]: found = [] files = [] @@ -639,7 +639,7 @@ def _find_files(path: str, prefix: str, postfix: str, ext: str, known_files: Uni return files -def _read_array_openQCD2(fp: BufferedReader) -> Dict[str, Union[int, Tuple[int, int], List[List[float]]]]: +def _read_array_openQCD2(fp: BufferedReader) -> dict[str, Union[int, tuple[int, int], list[list[float]]]]: t = fp.read(4) d = struct.unpack('i', t)[0] t = fp.read(4 * d) diff --git a/pyerrors/input/sfcf.py b/pyerrors/input/sfcf.py index 596a52e4..46cfa5d4 100644 --- a/pyerrors/input/sfcf.py +++ b/pyerrors/input/sfcf.py @@ -7,13 +7,13 @@ from .utils import sort_names, check_idl import itertools from numpy import ndarray -from typing import Any, Dict, List, Tuple, Union +from typing import Any, Union sep = "/" -def read_sfcf(path: str, prefix: str, name: str, quarks: str='.*', corr_type: str="bi", noffset: int=0, wf: int=0, wf2: int=0, version: str="1.0c", cfg_separator: str="n", silent: bool=False, **kwargs) -> List[Obs]: +def read_sfcf(path: str, prefix: str, name: str, quarks: str='.*', corr_type: str="bi", noffset: int=0, wf: int=0, wf2: int=0, version: str="1.0c", cfg_separator: str="n", silent: bool=False, **kwargs) -> list[Obs]: """Read sfcf files from given folder structure. Parameters @@ -78,7 +78,7 @@ def read_sfcf(path: str, prefix: str, name: str, quarks: str='.*', corr_type: st return ret[name][quarks][str(noffset)][str(wf)][str(wf2)] -def read_sfcf_multi(path: str, prefix: str, name_list: List[str], quarks_list: List[str]=['.*'], corr_type_list: List[str]=['bi'], noffset_list: List[int]=[0], wf_list: List[int]=[0], wf2_list: List[int]=[0], version: str="1.0c", cfg_separator: str="n", silent: bool=False, keyed_out: bool=False, **kwargs) -> Dict[str, Dict[str, Dict[str, Dict[str, Dict[str, List[Obs]]]]]]: +def read_sfcf_multi(path: str, prefix: str, name_list: list[str], quarks_list: list[str]=['.*'], corr_type_list: list[str]=['bi'], noffset_list: list[int]=[0], wf_list: list[int]=[0], wf2_list: list[int]=[0], version: str="1.0c", cfg_separator: str="n", silent: bool=False, keyed_out: bool=False, **kwargs) -> dict[str, dict[str, dict[str, dict[str, dict[str, list[Obs]]]]]]: """Read sfcf files from given folder structure. Parameters @@ -410,14 +410,14 @@ def read_sfcf_multi(path: str, prefix: str, name_list: List[str], quarks_list: L return result_dict -def _lists2key(*lists) -> List[str]: +def _lists2key(*lists) -> list[str]: keys = [] for tup in itertools.product(*lists): keys.append(sep.join(tup)) return keys -def _key2specs(key: str) -> List[str]: +def _key2specs(key: str) -> list[str]: return key.split(sep) @@ -425,7 +425,7 @@ def _specs2key(*specs) -> str: return sep.join(specs) -def _read_o_file(cfg_path: str, name: str, needed_keys: List[str], intern: Dict[str, Dict[str, Union[bool, Dict[str, Dict[str, Dict[str, Dict[str, Dict[str, Union[int, str]]]]]], int]]], version: str, im: int) -> Dict[str, List[float]]: +def _read_o_file(cfg_path: str, name: str, needed_keys: list[str], intern: dict[str, dict[str, Union[bool, dict[str, dict[str, dict[str, dict[str, dict[str, Union[int, str]]]]]], int]]], version: str, im: int) -> dict[str, list[float]]: return_vals = {} for key in needed_keys: file = cfg_path + '/' + name @@ -450,7 +450,7 @@ def _read_o_file(cfg_path: str, name: str, needed_keys: List[str], intern: Dict[ return return_vals -def _extract_corr_type(corr_type: str) -> Tuple[bool, bool]: +def _extract_corr_type(corr_type: str) -> tuple[bool, bool]: if corr_type == 'bb': b2b = True single = True @@ -463,7 +463,7 @@ def _extract_corr_type(corr_type: str) -> Tuple[bool, bool]: return b2b, single -def _find_files(rep_path: str, prefix: str, compact: bool, files: List[Union[range, str, Any]]=[]) -> List[str]: +def _find_files(rep_path: str, prefix: str, compact: bool, files: list[Union[range, str, Any]]=[]) -> list[str]: sub_ls = [] if not files == []: files.sort(key=lambda x: int(re.findall(r'\d+', x)[-1])) @@ -504,7 +504,7 @@ def _make_pattern(version: str, name: str, noffset: str, wf: str, wf2: Union[str return pattern -def _find_correlator(file_name: str, version: str, pattern: str, b2b: bool, silent: bool=False) -> Tuple[int, int]: +def _find_correlator(file_name: str, version: str, pattern: str, b2b: bool, silent: bool=False) -> tuple[int, int]: T = 0 with open(file_name, "r") as my_file: @@ -530,7 +530,7 @@ def _find_correlator(file_name: str, version: str, pattern: str, b2b: bool, sile return start_read, T -def _read_compact_file(rep_path: str, cfg_file: str, intern: Dict[str, Dict[str, Union[bool, Dict[str, Dict[str, Dict[str, Dict[str, Dict[str, Union[int, str]]]]]], int]]], needed_keys: List[str], im: int) -> Dict[str, List[float]]: +def _read_compact_file(rep_path: str, cfg_file: str, intern: dict[str, dict[str, Union[bool, dict[str, dict[str, dict[str, dict[str, dict[str, Union[int, str]]]]]], int]]], needed_keys: list[str], im: int) -> dict[str, list[float]]: return_vals = {} with open(rep_path + cfg_file) as fp: lines = fp.readlines() @@ -561,7 +561,7 @@ def _read_compact_file(rep_path: str, cfg_file: str, intern: Dict[str, Dict[str, return return_vals -def _read_compact_rep(path: str, rep: str, sub_ls: List[str], intern: Dict[str, Dict[str, Union[bool, Dict[str, Dict[str, Dict[str, Dict[str, Dict[str, Union[int, str]]]]]], int]]], needed_keys: List[str], im: int) -> Dict[str, List[ndarray]]: +def _read_compact_rep(path: str, rep: str, sub_ls: list[str], intern: dict[str, dict[str, Union[bool, dict[str, dict[str, dict[str, dict[str, dict[str, Union[int, str]]]]]], int]]], needed_keys: list[str], im: int) -> dict[str, list[ndarray]]: rep_path = path + '/' + rep + '/' no_cfg = len(sub_ls) @@ -583,7 +583,7 @@ def _read_compact_rep(path: str, rep: str, sub_ls: List[str], intern: Dict[str, return return_vals -def _read_chunk(chunk: List[str], gauge_line: int, cfg_sep: str, start_read: int, T: int, corr_line: int, b2b: bool, pattern: str, im: int, single: bool) -> Tuple[int, List[float]]: +def _read_chunk(chunk: list[str], gauge_line: int, cfg_sep: str, start_read: int, T: int, corr_line: int, b2b: bool, pattern: str, im: int, single: bool) -> tuple[int, list[float]]: try: idl = int(chunk[gauge_line].split(cfg_sep)[-1]) except Exception: @@ -600,7 +600,7 @@ def _read_chunk(chunk: List[str], gauge_line: int, cfg_sep: str, start_read: int return idl, data -def _read_append_rep(filename: str, pattern: str, b2b: bool, cfg_separator: str, im: int, single: bool) -> Tuple[int, List[int], List[List[float]]]: +def _read_append_rep(filename: str, pattern: str, b2b: bool, cfg_separator: str, im: int, single: bool) -> tuple[int, list[int], list[list[float]]]: with open(filename, 'r') as fp: content = fp.readlines() data_starts = [] @@ -649,7 +649,7 @@ def _read_append_rep(filename: str, pattern: str, b2b: bool, cfg_separator: str, return T, rep_idl, data -def _get_rep_names(ls: List[str], ens_name: None=None) -> List[str]: +def _get_rep_names(ls: list[str], ens_name: None=None) -> list[str]: new_names = [] for entry in ls: try: @@ -664,7 +664,7 @@ def _get_rep_names(ls: List[str], ens_name: None=None) -> List[str]: return new_names -def _get_appended_rep_names(ls: List[str], prefix: str, name: str, ens_name: None=None) -> List[str]: +def _get_appended_rep_names(ls: list[str], prefix: str, name: str, ens_name: None=None) -> list[str]: new_names = [] for exc in ls: if not fnmatch.fnmatch(exc, prefix + '*.' + name): diff --git a/pyerrors/input/utils.py b/pyerrors/input/utils.py index 5ac00ba8..015b2fbe 100644 --- a/pyerrors/input/utils.py +++ b/pyerrors/input/utils.py @@ -4,10 +4,9 @@ import re import fnmatch import os -from typing import List -def sort_names(ll: List[str]) -> List[str]: +def sort_names(ll: list[str]) -> list[str]: """Sorts a list of names of replika with searches for `r` and `id` in the replikum string. If this search fails, a fallback method is used, where the strings are simply compared and the first diffeing numeral is used for differentiation. diff --git a/pyerrors/integrate.py b/pyerrors/integrate.py index 74e0e4a5..fcc59ec6 100644 --- a/pyerrors/integrate.py +++ b/pyerrors/integrate.py @@ -4,10 +4,10 @@ from autograd import jacobian from scipy.integrate import quad as squad from numpy import ndarray -from typing import Callable, Dict, List, Tuple, Union +from typing import Callable, Union -def quad(func: Callable, p: Union[List[Union[float, Obs]], List[float], ndarray], a: Union[Obs, float, int], b: Union[Obs, float, int], **kwargs) -> Union[Tuple[Obs, float], Tuple[float, float], Tuple[Obs, float, Dict[str, Union[int, ndarray]]]]: +def quad(func: Callable, p: Union[list[Union[float, Obs]], list[float], ndarray], a: Union[Obs, float, int], b: Union[Obs, float, int], **kwargs) -> Union[tuple[Obs, float], tuple[float, float], tuple[Obs, float, dict[str, Union[int, ndarray]]]]: '''Performs a (one-dimensional) numeric integration of f(p, x) from a to b. The integration is performed using scipy.integrate.quad(). diff --git a/pyerrors/linalg.py b/pyerrors/linalg.py index 8b9f9e7b..cb0b3119 100644 --- a/pyerrors/linalg.py +++ b/pyerrors/linalg.py @@ -3,7 +3,7 @@ import autograd.numpy as anp # Thinly-wrapped numpy from .obs import derived_observable, CObs, Obs, import_jackknife from numpy import ndarray -from typing import Callable, Tuple, Union +from typing import Callable, Union def matmul(*operands) -> ndarray: @@ -262,7 +262,7 @@ def _mat_mat_op(op: Callable, obs: ndarray, **kwargs) -> ndarray: return derived_observable(lambda x, **kwargs: op(x), [obs], array_mode=True)[0] -def eigh(obs: ndarray, **kwargs) -> Tuple[ndarray, ndarray]: +def eigh(obs: ndarray, **kwargs) -> tuple[ndarray, ndarray]: """Computes the eigenvalues and eigenvectors of a given hermitian matrix of Obs according to np.linalg.eigh.""" w = derived_observable(lambda x, **kwargs: anp.linalg.eigh(x)[0], obs) v = derived_observable(lambda x, **kwargs: anp.linalg.eigh(x)[1], obs) @@ -286,7 +286,7 @@ def pinv(obs: ndarray, **kwargs) -> ndarray: return derived_observable(lambda x, **kwargs: anp.linalg.pinv(x), obs) -def svd(obs: ndarray, **kwargs) -> Tuple[ndarray, ndarray, ndarray]: +def svd(obs: ndarray, **kwargs) -> tuple[ndarray, ndarray, ndarray]: """Computes the singular value decomposition of a matrix of Obs.""" u = derived_observable(lambda x, **kwargs: anp.linalg.svd(x, full_matrices=False)[0], obs) s = derived_observable(lambda x, **kwargs: anp.linalg.svd(x, full_matrices=False)[1], obs) diff --git a/pyerrors/roots.py b/pyerrors/roots.py index ece0e183..79ac94d1 100644 --- a/pyerrors/roots.py +++ b/pyerrors/roots.py @@ -3,10 +3,10 @@ import scipy.optimize from autograd import jacobian from .obs import Obs, derived_observable -from typing import Callable, List, Union +from typing import Callable, Union -def find_root(d: Union[Obs, List[Obs]], func: Callable, guess: float=1.0, **kwargs) -> Obs: +def find_root(d: Union[Obs, list[Obs]], func: Callable, guess: float=1.0, **kwargs) -> Obs: r'''Finds the root of the function func(x, d) where d is an `Obs`. Parameters From b8700ef96285e1121c1923927319fcd0e800ba65 Mon Sep 17 00:00:00 2001 From: Fabian Joswig Date: Fri, 3 Jan 2025 22:57:29 +0100 Subject: [PATCH 14/57] [Fix] Fix type annotations in json.py --- pyerrors/input/json.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/pyerrors/input/json.py b/pyerrors/input/json.py index 2768a68b..8dc59ea2 100644 --- a/pyerrors/input/json.py +++ b/pyerrors/input/json.py @@ -17,7 +17,7 @@ from typing import Any, Optional, Union -def create_json_string(ol: Any, description: Union[str, dict[str, Union[str, dict[str, Union[dict[str, Union[str, dict[str, Union[int, str]]]], dict[str, Optional[Union[str, list[str], float]]]]]]], dict[str, Union[str, dict[Optional[Union[int, bool]], str], float]], dict[str, dict[str, dict[str, str]]], dict[str, Union[str, dict[Optional[Union[int, bool]], str], dict[int, float]]]]='', indent: int=1) -> str: +def create_json_string(ol: list, description: Union[str, dict]='', indent: int=1) -> str: """Generate the string for the export of a list of Obs or structures containing Obs to a .json(.gz) file @@ -168,7 +168,7 @@ def write_Corr_to_dict(my_corr): if not isinstance(ol, list): ol = [ol] - d = {} + d: dict[str, Any] = {} d['program'] = 'pyerrors %s' % (pyerrorsversion.__version__) d['version'] = '1.1' d['who'] = getpass.getuser() @@ -219,7 +219,7 @@ def _jsonifier(obj): return json.dumps(d, indent=indent, ensure_ascii=False, default=_jsonifier, write_mode=json.WM_COMPACT) -def dump_to_json(ol: Union[Corr, list[Union[Obs, list[Obs], Corr, ndarray]], ndarray, list[Union[Obs, list[Obs], ndarray]], list[Obs]], fname: str, description: Union[str, dict[str, Union[str, dict[str, Union[dict[str, Union[str, dict[str, Union[int, str]]]], dict[str, Optional[Union[str, list[str], float]]]]]]], dict[str, Union[str, dict[Optional[Union[int, bool]], str], float]], dict[str, dict[str, dict[str, str]]], dict[str, Union[str, dict[Optional[Union[int, bool]], str], dict[int, float]]]]='', indent: int=1, gz: bool=True): +def dump_to_json(ol: list, fname: str, description: Union[str, dict]='', indent: int=1, gz: bool=True): """Export a list of Obs or structures containing Obs to a .json(.gz) file. Dict keys that are not JSON-serializable such as floats are converted to strings. @@ -253,12 +253,13 @@ def dump_to_json(ol: Union[Corr, list[Union[Obs, list[Obs], Corr, ndarray]], nda if not fname.endswith('.gz'): fname += '.gz' - fp = gzip.open(fname, 'wb') - fp.write(jsonstring.encode('utf-8')) + gp = gzip.open(fname, 'wb') + gp.write(jsonstring.encode('utf-8')) + gp.close() else: fp = open(fname, 'w', encoding='utf-8') fp.write(jsonstring) - fp.close() + fp.close() def _parse_json_dict(json_dict: dict[str, Any], verbose: bool=True, full_output: bool=False) -> Any: @@ -473,7 +474,7 @@ def get_Corr_from_dict(o): return ol -def import_json_string(json_string: str, verbose: bool=True, full_output: bool=False) -> Union[Obs, List[Obs], Corr]: +def import_json_string(json_string: str, verbose: bool=True, full_output: bool=False) -> Union[Obs, list[Obs], Corr]: """Reconstruct a list of Obs or structures containing Obs from a json string. The following structures are supported: Obs, list, numpy.ndarray, Corr @@ -548,7 +549,7 @@ def load_json(fname: str, verbose: bool=True, gz: bool=True, full_output: bool=F return _parse_json_dict(d, verbose, full_output) -def _ol_from_dict(ind: Union[dict[Optional[Union[int, bool]], str], dict[str, Union[dict[str, Union[Obs, list[Obs], dict[str, Union[int, Obs, Corr, ndarray]]]], dict[str, Optional[Union[str, list[str], Obs, float]]], str]], dict[str, Union[dict[str, Union[Obs, list[Obs], dict[str, Union[int, Obs, Corr, ndarray]]]], dict[str, Optional[Union[str, list[str], Obs, float]]], list[str]]], dict[str, Union[dict[str, Union[Obs, list[Obs], dict[str, Union[int, Obs, Corr, ndarray]]]], dict[str, Optional[Union[str, list[str], Obs, float]]]]]], reps: str='DICTOBS') -> Union[tuple[list[Any], dict[Optional[Union[int, bool]], str]], tuple[list[Union[Obs, list[Obs], Corr, ndarray]], dict[str, Union[dict[str, Union[str, dict[str, Union[int, str]]]], dict[str, Optional[Union[str, list[str], float]]]]]]]: +def _ol_from_dict(ind: dict, reps: str='DICTOBS') -> tuple[list, dict]: """Convert a dictionary of Obs objects to a list and a dictionary that contains placeholders instead of the Obs objects. @@ -628,7 +629,7 @@ def obslist_replace_obs(li): return ol, nd -def dump_dict_to_json(od: Union[dict[str, Union[dict[str, Union[Obs, list[Obs], dict[str, Union[int, Obs, Corr, ndarray]]]], dict[str, Optional[Union[str, list[str], Obs, float]]], str]], dict[str, Union[dict[str, Union[Obs, list[Obs], dict[str, Union[int, Obs, Corr, ndarray]]]], dict[str, Optional[Union[str, list[str], Obs, float]]], list[str]]], list[Union[Obs, list[Obs], Corr, ndarray]], dict[Optional[Union[int, bool]], str], dict[str, Union[dict[str, Union[Obs, list[Obs], dict[str, Union[int, Obs, Corr, ndarray]]]], dict[str, Optional[Union[str, list[str], Obs, float]]]]]], fname: str, description: Union[str, float, dict[int, float]]='', indent: int=1, reps: str='DICTOBS', gz: bool=True): +def dump_dict_to_json(od: dict, fname: str, description: Union[str, dict]='', indent: int=1, reps: str='DICTOBS', gz: bool=True): """Export a dict of Obs or structures containing Obs to a .json(.gz) file Parameters @@ -668,7 +669,7 @@ def dump_dict_to_json(od: Union[dict[str, Union[dict[str, Union[Obs, list[Obs], dump_to_json(ol, fname, description=desc_dict, indent=indent, gz=gz) -def _od_from_list_and_dict(ol: list[Union[Obs, list[Obs], Corr, ndarray]], ind: dict[str, dict[str, Optional[Union[str, dict[str, Union[int, str]], list[str], float]]]], reps: str='DICTOBS') -> dict[str, dict[str, Any]]: +def _od_from_list_and_dict(ol: list, ind: dict, reps: str='DICTOBS') -> dict[str, dict[str, Any]]: """Parse a list of Obs or structures containing Obs and an accompanying dict, where the structures have been replaced by placeholders to a dict that contains the structures. @@ -731,7 +732,7 @@ def list_replace_string(li): return nd -def load_json_dict(fname: str, verbose: bool=True, gz: bool=True, full_output: bool=False, reps: str='DICTOBS') -> dict[str, Union[dict[str, Union[Obs, list[Obs], dict[str, Union[int, Obs, Corr, ndarray]]]], dict[str, Optional[Union[str, list[str], Obs, float]]], str, dict[str, Union[dict[str, Union[Obs, list[Obs], dict[str, Union[int, Obs, Corr, ndarray]]]], dict[str, Optional[Union[str, list[str], Obs, float]]]]]]]: +def load_json_dict(fname: str, verbose: bool=True, gz: bool=True, full_output: bool=False, reps: str='DICTOBS') -> dict: """Import a dict of Obs or structures containing Obs from a .json(.gz) file. The following structures are supported: Obs, list, numpy.ndarray, Corr From 6d5a9b9d837fa32bb1ee171ce29b5f3bbc0ff96b Mon Sep 17 00:00:00 2001 From: Fabian Joswig Date: Fri, 3 Jan 2025 23:15:40 +0100 Subject: [PATCH 15/57] [Fix] Simplify type annotations in input modules --- pyerrors/fits.py | 2 +- pyerrors/input/dobs.py | 33 +++++++++++++++++---------------- pyerrors/input/sfcf.py | 12 +++++++----- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/pyerrors/fits.py b/pyerrors/fits.py index 2a2950ae..fe96713a 100644 --- a/pyerrors/fits.py +++ b/pyerrors/fits.py @@ -710,7 +710,7 @@ def odr_chisquare_compact_y(d): return output -def fit_lin(x: list[Union[Obs, int, float]], y: list[Obs], **kwargs) -> list[Obs]: +def fit_lin(x: Sequence[Union[Obs, int, float]], y: Sequence[Obs], **kwargs) -> list[Obs]: """Performs a linear fit to y = n + m * x and returns two Obs n, m. Parameters diff --git a/pyerrors/input/dobs.py b/pyerrors/input/dobs.py index d4ac638c..af60eb9c 100644 --- a/pyerrors/input/dobs.py +++ b/pyerrors/input/dobs.py @@ -18,9 +18,9 @@ # Based on https://stackoverflow.com/a/10076823 -def _etree_to_dict(t: _Element) -> dict[str, Union[str, dict[str, str], dict[str, Union[str, dict[str, str]]]]]: +def _etree_to_dict(t: _Element) -> dict: """ Convert the content of an XML file to a python dict""" - d = {t.tag: {} if t.attrib else None} + d: dict = {t.tag: {} if t.attrib else None} children = list(t) if children: dd = defaultdict(list) @@ -70,7 +70,7 @@ def _dict_to_xmlstring(d: dict[str, Any]) -> str: return iters -def _dict_to_xmlstring_spaces(d: dict[str, dict[str, dict[str, Union[str, dict[str, str], list[dict[str, str]]]]]], space: str=' ') -> str: +def _dict_to_xmlstring_spaces(d: dict, space: str=' ') -> str: s = _dict_to_xmlstring(d) o = '' c = 0 @@ -89,7 +89,7 @@ def _dict_to_xmlstring_spaces(d: dict[str, dict[str, dict[str, Union[str, dict[s return o -def create_pobs_string(obsl: list[Obs], name: str, spec: str='', origin: str='', symbol: Optional[list[Union[str, Any]]]=None, enstag: None=None) -> str: +def create_pobs_string(obsl: list[Obs], name: str, spec: str='', origin: str='', symbol: Optional[list[Union[str, Any]]]=None, enstag: Optional[str]=None) -> str: """Export a list of Obs or structures containing Obs to an xml string according to the Zeuthen pobs format. @@ -119,7 +119,7 @@ def create_pobs_string(obsl: list[Obs], name: str, spec: str='', origin: str='', if symbol is None: symbol = [] - od = {} + od: dict[str, Any] = {} ename = obsl[0].e_names[0] names = list(obsl[0].deltas.keys()) nr = len(names) @@ -182,7 +182,7 @@ def create_pobs_string(obsl: list[Obs], name: str, spec: str='', origin: str='', return rs -def write_pobs(obsl: list[Obs], fname: str, name: str, spec: str='', origin: str='', symbol: Optional[list[Union[str, Any]]]=None, enstag: None=None, gz: bool=True): +def write_pobs(obsl: list[Obs], fname: str, name: str, spec: str='', origin: str='', symbol: Optional[list[Union[str, Any]]]=None, enstag: Optional[str]=None, gz: bool=True): """Export a list of Obs or structures containing Obs to a .xml.gz file according to the Zeuthen pobs format. @@ -223,12 +223,13 @@ def write_pobs(obsl: list[Obs], fname: str, name: str, spec: str='', origin: str if not fname.endswith('.gz'): fname += '.gz' - fp = gzip.open(fname, 'wb') - fp.write(pobsstring.encode('utf-8')) + gp = gzip.open(fname, 'wb') + gp.write(pobsstring.encode('utf-8')) + gp.close() else: fp = open(fname, 'w', encoding='utf-8') fp.write(pobsstring) - fp.close() + fp.close() def _import_data(string: str) -> list[Union[int, float]]: @@ -305,7 +306,7 @@ def _import_cdata(cd: _Element) -> tuple[str, ndarray, ndarray]: return cd[0].text.strip(), cov, grad -def read_pobs(fname: str, full_output: bool=False, gz: bool=True, separator_insertion: None=None) -> Union[dict[str, Union[str, dict[str, str], list[Obs]]], list[Obs]]: +def read_pobs(fname: str, full_output: bool=False, gz: bool=True, separator_insertion: None=None) -> Union[dict, list[Obs]]: """Import a list of Obs from an xml.gz file in the Zeuthen pobs format. Tags are not written or recovered automatically. @@ -358,7 +359,7 @@ def read_pobs(fname: str, full_output: bool=False, gz: bool=True, separator_inse deltas = [] names = [] - idl = [] + idl: list[list[int]] = [] for i in range(5, len(pobs)): delta, name, idx = _import_rdata(pobs[i]) deltas.append(delta) @@ -405,7 +406,7 @@ def read_pobs(fname: str, full_output: bool=False, gz: bool=True, separator_inse # this is based on Mattia Bruno's implementation at https://github.com/mbruno46/pyobs/blob/master/pyobs/IO/xml.py -def import_dobs_string(content: bytes, full_output: bool=False, separator_insertion: bool=True) -> Union[dict[str, Union[str, dict[str, str], list[Obs]]], list[Obs]]: +def import_dobs_string(content: bytes, full_output: bool=False, separator_insertion: bool=True) -> Union[dict, list[Obs]]: """Import a list of Obs from a string in the Zeuthen dobs format. Tags are not written or recovered automatically. @@ -579,7 +580,7 @@ def import_dobs_string(content: bytes, full_output: bool=False, separator_insert return res -def read_dobs(fname: str, full_output: bool=False, gz: bool=True, separator_insertion: bool=True) -> Union[dict[str, Union[str, dict[str, str], list[Obs]]], list[Obs]]: +def read_dobs(fname: str, full_output: bool=False, gz: bool=True, separator_insertion: bool=True) -> Union[dict, list[Obs]]: """Import a list of Obs from an xml.gz file in the Zeuthen dobs format. Tags are not written or recovered automatically. @@ -666,7 +667,7 @@ def _dobsdict_to_xmlstring(d: dict[str, Any]) -> str: return iters -def _dobsdict_to_xmlstring_spaces(d: dict[str, Union[dict[str, Union[dict[str, str], dict[str, Union[str, dict[str, str]]], dict[str, Union[str, dict[str, Union[str, list[str]]], list[dict[str, Union[str, int, list[dict[str, str]]]]]]]]], dict[str, Union[dict[str, str], dict[str, Union[str, dict[str, str]]], dict[str, Union[str, dict[str, Union[str, list[str]]], list[dict[str, Union[str, int, list[dict[str, str]]]]], list[dict[str, Union[str, list[dict[str, str]]]]]]]]]]], space: str=' ') -> str: +def _dobsdict_to_xmlstring_spaces(d: dict, space: str=' ') -> str: s = _dobsdict_to_xmlstring(d) o = '' c = 0 @@ -685,7 +686,7 @@ def _dobsdict_to_xmlstring_spaces(d: dict[str, Union[dict[str, Union[dict[str, s return o -def create_dobs_string(obsl: list[Obs], name: str, spec: str='dobs v1.0', origin: str='', symbol: Optional[list[Union[str, Any]]]=None, who: None=None, enstags: Optional[dict[Any, Any]]=None) -> str: +def create_dobs_string(obsl: list[Obs], name: str, spec: str='dobs v1.0', origin: str='', symbol: Optional[list[Union[str, Any]]]=None, who: Optional[str]=None, enstags: Optional[dict]=None) -> str: """Generate the string for the export of a list of Obs or structures containing Obs to a .xml.gz file according to the Zeuthen dobs format. @@ -876,7 +877,7 @@ def create_dobs_string(obsl: list[Obs], name: str, spec: str='dobs v1.0', origin return rs -def write_dobs(obsl: list[Obs], fname: str, name: str, spec: str='dobs v1.0', origin: str='', symbol: Optional[list[Union[str, Any]]]=None, who: None=None, enstags: None=None, gz: bool=True): +def write_dobs(obsl: list[Obs], fname: str, name: str, spec: str='dobs v1.0', origin: str='', symbol: Optional[list[Union[str, Any]]]=None, who: Optional[str]=None, enstags: Optional[dict]=None, gz: bool=True): """Export a list of Obs or structures containing Obs to a .xml.gz file according to the Zeuthen dobs format. diff --git a/pyerrors/input/sfcf.py b/pyerrors/input/sfcf.py index 46cfa5d4..c47f81d2 100644 --- a/pyerrors/input/sfcf.py +++ b/pyerrors/input/sfcf.py @@ -78,7 +78,7 @@ def read_sfcf(path: str, prefix: str, name: str, quarks: str='.*', corr_type: st return ret[name][quarks][str(noffset)][str(wf)][str(wf2)] -def read_sfcf_multi(path: str, prefix: str, name_list: list[str], quarks_list: list[str]=['.*'], corr_type_list: list[str]=['bi'], noffset_list: list[int]=[0], wf_list: list[int]=[0], wf2_list: list[int]=[0], version: str="1.0c", cfg_separator: str="n", silent: bool=False, keyed_out: bool=False, **kwargs) -> dict[str, dict[str, dict[str, dict[str, dict[str, list[Obs]]]]]]: +def read_sfcf_multi(path: str, prefix: str, name_list: list[str], quarks_list: list[str]=['.*'], corr_type_list: list[str]=['bi'], noffset_list: list[int]=[0], wf_list: list[int]=[0], wf2_list: list[int]=[0], version: str="1.0c", cfg_separator: str="n", silent: bool=False, keyed_out: bool=False, **kwargs) -> dict: """Read sfcf files from given folder structure. Parameters @@ -425,7 +425,7 @@ def _specs2key(*specs) -> str: return sep.join(specs) -def _read_o_file(cfg_path: str, name: str, needed_keys: list[str], intern: dict[str, dict[str, Union[bool, dict[str, dict[str, dict[str, dict[str, dict[str, Union[int, str]]]]]], int]]], version: str, im: int) -> dict[str, list[float]]: +def _read_o_file(cfg_path: str, name: str, needed_keys: list[str], intern: dict[str, dict], version: str, im: int) -> dict[str, list[float]]: return_vals = {} for key in needed_keys: file = cfg_path + '/' + name @@ -463,7 +463,9 @@ def _extract_corr_type(corr_type: str) -> tuple[bool, bool]: return b2b, single -def _find_files(rep_path: str, prefix: str, compact: bool, files: list[Union[range, str, Any]]=[]) -> list[str]: +def _find_files(rep_path: str, prefix: str, compact: bool, files: Optional[list]=None) -> list[str]: + if files is None: + files = [] sub_ls = [] if not files == []: files.sort(key=lambda x: int(re.findall(r'\d+', x)[-1])) @@ -530,7 +532,7 @@ def _find_correlator(file_name: str, version: str, pattern: str, b2b: bool, sile return start_read, T -def _read_compact_file(rep_path: str, cfg_file: str, intern: dict[str, dict[str, Union[bool, dict[str, dict[str, dict[str, dict[str, dict[str, Union[int, str]]]]]], int]]], needed_keys: list[str], im: int) -> dict[str, list[float]]: +def _read_compact_file(rep_path: str, cfg_file: str, intern: dict[str, dict], needed_keys: list[str], im: int) -> dict[str, list[float]]: return_vals = {} with open(rep_path + cfg_file) as fp: lines = fp.readlines() @@ -561,7 +563,7 @@ def _read_compact_file(rep_path: str, cfg_file: str, intern: dict[str, dict[str, return return_vals -def _read_compact_rep(path: str, rep: str, sub_ls: list[str], intern: dict[str, dict[str, Union[bool, dict[str, dict[str, dict[str, dict[str, dict[str, Union[int, str]]]]]], int]]], needed_keys: list[str], im: int) -> dict[str, list[ndarray]]: +def _read_compact_rep(path: str, rep: str, sub_ls: list[str], intern: dict[str, dict], needed_keys: list[str], im: int) -> dict[str, list[ndarray]]: rep_path = path + '/' + rep + '/' no_cfg = len(sub_ls) From 6a990c147b0c33a85917fda9949d982177903643 Mon Sep 17 00:00:00 2001 From: Fabian Joswig Date: Fri, 3 Jan 2025 23:17:06 +0100 Subject: [PATCH 16/57] [Fix] Fix ruff --- pyerrors/input/json.py | 3 +-- pyerrors/input/sfcf.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pyerrors/input/json.py b/pyerrors/input/json.py index 8dc59ea2..e5b56a96 100644 --- a/pyerrors/input/json.py +++ b/pyerrors/input/json.py @@ -13,8 +13,7 @@ from ..correlators import Corr from ..misc import _assert_equal_properties from .. import version as pyerrorsversion -from numpy import ndarray -from typing import Any, Optional, Union +from typing import Any, Union def create_json_string(ol: list, description: Union[str, dict]='', indent: int=1) -> str: diff --git a/pyerrors/input/sfcf.py b/pyerrors/input/sfcf.py index c47f81d2..898c3241 100644 --- a/pyerrors/input/sfcf.py +++ b/pyerrors/input/sfcf.py @@ -7,7 +7,7 @@ from .utils import sort_names, check_idl import itertools from numpy import ndarray -from typing import Any, Union +from typing import Union, Optional sep = "/" From 9c960ae24cea752d41188a27c09daa22a37909fc Mon Sep 17 00:00:00 2001 From: Fabian Joswig Date: Sun, 5 Jan 2025 16:30:42 +0100 Subject: [PATCH 17/57] [Fix] Correct type hints in fits.py --- pyerrors/fits.py | 57 +++++++++++++++++++++++++++++++++--------------- pyerrors/obs.py | 4 ++-- 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/pyerrors/fits.py b/pyerrors/fits.py index fe96713a..28f53f1c 100644 --- a/pyerrors/fits.py +++ b/pyerrors/fits.py @@ -36,13 +36,31 @@ class Fit_result(Sequence): Hotelling t-squared p-value for correlated fits. """ - def __init__(self): - self.fit_parameters = None + def __init__(self) -> None: + self.fit_parameters: Optional[list] = None + self.fit_function: Optional[Union[Callable, dict[str, Callable]]] = None + self.priors: Optional[Union[list[Obs], dict[int, Obs]]] = None + self.method: Optional[str] = None + self.iterations: Optional[int] = None + self.chisquare: Optional[float] = None + self.odr_chisquare: Optional[float] = None + self.dof: Optional[int] = None + self.p_value: Optional[float] = None + self.message: Optional[str] = None + self.t2_p_value: Optional[float] = None + self.chisquare_by_dof: Optional[float] = None + self.chisquare_by_expected_chisquare: Optional[float] = None + self.residual_variance: Optional[float] = None + self.xplus: Optional[float] = None def __getitem__(self, idx: int) -> Obs: + if self.fit_parameters is None: + raise ValueError('No fit parameters available.') return self.fit_parameters[idx] def __len__(self) -> int: + if self.fit_parameters is None: + raise ValueError('No fit parameters available.') return len(self.fit_parameters) def gamma_method(self, **kwargs): @@ -64,6 +82,8 @@ def __str__(self) -> str: if hasattr(self, 't2_p_value'): my_str += 't\u00B2p-value = ' + f'{self.t2_p_value:2.4f}' + '\n' my_str += 'Fit parameters:\n' + if self.fit_parameters is None: + raise ValueError('No fit parameters available.') for i_par, par in enumerate(self.fit_parameters): my_str += str(i_par) + '\t' + ' ' * int(par >= 0) + str(par).rjust(int(par < 0.0)) + '\n' return my_str @@ -338,9 +358,8 @@ def func_b(a, x): p_f = dp_f = np.array([]) prior_mask = [] loc_priors = [] - - if 'initial_guess' in kwargs: - x0 = kwargs.get('initial_guess') + x0 = kwargs.get('initial_guess') + if x0 is not None: if len(x0) != n_parms: raise ValueError('Initial guess does not have the correct length: %d vs. %d' % (len(x0), n_parms)) else: @@ -359,8 +378,8 @@ def chisqfunc_uncorr(p): return anp.sum(general_chisqfunc_uncorr(p, y_f, p_f) ** 2) if kwargs.get('correlated_fit') is True: - if 'inv_chol_cov_matrix' in kwargs: - chol_inv = kwargs.get('inv_chol_cov_matrix') + chol_inv = kwargs.get('inv_chol_cov_matrix') + if chol_inv is not None: if (chol_inv[0].shape[0] != len(dy_f)): raise TypeError('The number of columns of the inverse covariance matrix handed over needs to be equal to the number of y errors.') if (chol_inv[0].shape[0] != chol_inv[0].shape[1]): @@ -389,17 +408,17 @@ def chisqfunc(p): if output.method != 'Levenberg-Marquardt': if output.method == 'migrad': - tolerance = 1e-4 # default value of 1e-1 set by iminuit can be problematic - if 'tol' in kwargs: - tolerance = kwargs.get('tol') + tolerance = kwargs.get('tol') + if tolerance is None: + tolerance = 1e-4 # default value of 1e-1 set by iminuit can be problematic fit_result = iminuit.minimize(chisqfunc_uncorr, x0, tol=tolerance) # Stopping criterion 0.002 * tol * errordef if kwargs.get('correlated_fit') is True: fit_result = iminuit.minimize(chisqfunc, fit_result.x, tol=tolerance) output.iterations = fit_result.nfev else: - tolerance = 1e-12 - if 'tol' in kwargs: - tolerance = kwargs.get('tol') + tolerance = kwargs.get('tol') + if tolerance is None: + tolerance = 1e-12 fit_result = scipy.optimize.minimize(chisqfunc_uncorr, x0, method=kwargs.get('method'), tol=tolerance) if kwargs.get('correlated_fit') is True: fit_result = scipy.optimize.minimize(chisqfunc, fit_result.x, method=kwargs.get('method'), tol=tolerance) @@ -429,8 +448,8 @@ def chisqfunc_residuals(p): if not fit_result.success: raise Exception('The minimization procedure did not converge.') - output.chisquare = chisquare - output.dof = y_all.shape[-1] - n_parms + len(loc_priors) + output.chisquare = float(chisquare) + output.dof = int(y_all.shape[-1] - n_parms + len(loc_priors)) output.p_value = 1 - scipy.stats.chi2.cdf(output.chisquare, output.dof) if output.dof > 0: output.chisquare_by_dof = output.chisquare / output.dof @@ -603,8 +622,8 @@ def func(a, x): if np.any(np.asarray(dy_f) <= 0.0): raise Exception('No y errors available, run the gamma method first.') - if 'initial_guess' in kwargs: - x0 = kwargs.get('initial_guess') + x0 = kwargs.get('initial_guess') + if x0 is not None: if len(x0) != n_parms: raise Exception('Initial guess does not have the correct length: %d vs. %d' % (len(x0), n_parms)) else: @@ -890,6 +909,8 @@ def _construct_prior_obs(i_prior: Union[Obs, str], i_n: int) -> Obs: return i_prior elif isinstance(i_prior, str): loc_val, loc_dval = _extract_val_and_dval(i_prior) - return cov_Obs(loc_val, loc_dval ** 2, '#prior' + str(i_n) + f"_{np.random.randint(2147483647):010d}") + prior_obs = cov_Obs(loc_val, loc_dval ** 2, '#prior' + str(i_n) + f"_{np.random.randint(2147483647):010d}") + assert isinstance(prior_obs, Obs) + return prior_obs else: raise TypeError("Prior entries need to be 'Obs' or 'str'.") diff --git a/pyerrors/obs.py b/pyerrors/obs.py index 5cf44fc8..cb8d97c7 100644 --- a/pyerrors/obs.py +++ b/pyerrors/obs.py @@ -1435,8 +1435,8 @@ def reweight(weight: Obs, obs: Union[ndarray, list[Obs]], **kwargs) -> list[Obs] for name in obs[i].names: if not set(obs[i].idl[name]).issubset(weight.idl[name]): raise ValueError('obs[%d] has to be defined on a subset of the configs in weight.idl[%s]!' % (i, name)) - new_samples = [] - w_deltas = {} + new_samples: list = [] + w_deltas: dict[str, ndarray] = {} for name in sorted(obs[i].names): w_deltas[name] = _reduce_deltas(weight.deltas[name], weight.idl[name], obs[i].idl[name]) new_samples.append((w_deltas[name] + weight.r_values[name]) * (obs[i].deltas[name] + obs[i].r_values[name])) From 5376a8ad44b2d06e078a164e986bc13c2f42c281 Mon Sep 17 00:00:00 2001 From: Fabian Joswig Date: Sun, 5 Jan 2025 16:54:22 +0100 Subject: [PATCH 18/57] [Fix] Further type fixes in fits and sfcf --- pyerrors/fits.py | 16 +++++----- pyerrors/input/sfcf.py | 69 ++++++++++++++++++++---------------------- 2 files changed, 41 insertions(+), 44 deletions(-) diff --git a/pyerrors/fits.py b/pyerrors/fits.py index 28f53f1c..f1b22e76 100644 --- a/pyerrors/fits.py +++ b/pyerrors/fits.py @@ -55,12 +55,12 @@ def __init__(self) -> None: def __getitem__(self, idx: int) -> Obs: if self.fit_parameters is None: - raise ValueError('No fit parameters available.') + raise TypeError('No fit parameters available.') return self.fit_parameters[idx] def __len__(self) -> int: if self.fit_parameters is None: - raise ValueError('No fit parameters available.') + raise TypeError('No fit parameters available.') return len(self.fit_parameters) def gamma_method(self, **kwargs): @@ -71,19 +71,19 @@ def gamma_method(self, **kwargs): def __str__(self) -> str: my_str = 'Goodness of fit:\n' - if hasattr(self, 'chisquare_by_dof'): + if self.chisquare_by_dof is not None: my_str += '\u03C7\u00b2/d.o.f. = ' + f'{self.chisquare_by_dof:2.6f}' + '\n' - elif hasattr(self, 'residual_variance'): + elif self.residual_variance is not None: my_str += 'residual variance = ' + f'{self.residual_variance:2.6f}' + '\n' - if hasattr(self, 'chisquare_by_expected_chisquare'): + if self.chisquare_by_expected_chisquare is not None: my_str += '\u03C7\u00b2/\u03C7\u00b2exp = ' + f'{self.chisquare_by_expected_chisquare:2.6f}' + '\n' - if hasattr(self, 'p_value'): + if self.p_value is not None: my_str += 'p-value = ' + f'{self.p_value:2.4f}' + '\n' - if hasattr(self, 't2_p_value'): + if self.t2_p_value is not None: my_str += 't\u00B2p-value = ' + f'{self.t2_p_value:2.4f}' + '\n' my_str += 'Fit parameters:\n' if self.fit_parameters is None: - raise ValueError('No fit parameters available.') + raise TypeError('No fit parameters available.') for i_par, par in enumerate(self.fit_parameters): my_str += str(i_par) + '\t' + ' ' * int(par >= 0) + str(par).rjust(int(par < 0.0)) + '\n' return my_str diff --git a/pyerrors/input/sfcf.py b/pyerrors/input/sfcf.py index 898c3241..d9352785 100644 --- a/pyerrors/input/sfcf.py +++ b/pyerrors/input/sfcf.py @@ -7,7 +7,7 @@ from .utils import sort_names, check_idl import itertools from numpy import ndarray -from typing import Union, Optional +from typing import Any, Union, Optional sep = "/" @@ -164,10 +164,9 @@ def read_sfcf_multi(path: str, prefix: str, name_list: list[str], quarks_list: l else: compact = False appended = False - ls = [] - if "replica" in kwargs: - ls = kwargs.get("replica") - else: + ls = kwargs.get("replica") + if ls is None: + ls = [] for (dirpath, dirnames, filenames) in os.walk(path): if not appended: ls.extend(dirnames) @@ -192,13 +191,12 @@ def read_sfcf_multi(path: str, prefix: str, name_list: list[str], quarks_list: l if not silent: print('Read', part, 'part of', name_list, 'from', prefix[:-1], ',', replica, 'replica') - if 'names' in kwargs: - new_names = kwargs.get('names') + new_names = kwargs.get('names') + if new_names is not None: if len(new_names) != len(set(new_names)): raise Exception("names are not unique!") if len(new_names) != replica: raise Exception('names should have the length', replica) - else: ens_name = kwargs.get("ens_name") if not appended: @@ -207,14 +205,14 @@ def read_sfcf_multi(path: str, prefix: str, name_list: list[str], quarks_list: l new_names = _get_appended_rep_names(ls, prefix, name_list[0], ens_name) new_names = sort_names(new_names) - idl = [] + idl: list[list[int]] = [] - noffset_list = [str(x) for x in noffset_list] - wf_list = [str(x) for x in wf_list] - wf2_list = [str(x) for x in wf2_list] + noffset_strings: list[str] = [str(x) for x in noffset_list] + wf_strings: list[str] = [str(x) for x in wf_list] + wf2_strings: list[str] = [str(x) for x in wf2_list] # setup dict structures - intern = {} + intern: dict[str, Any] = {} for name, corr_type in zip(name_list, corr_type_list): intern[name] = {} b2b, single = _extract_corr_type(corr_type) @@ -223,26 +221,26 @@ def read_sfcf_multi(path: str, prefix: str, name_list: list[str], quarks_list: l intern[name]["spec"] = {} for quarks in quarks_list: intern[name]["spec"][quarks] = {} - for off in noffset_list: + for off in noffset_strings: intern[name]["spec"][quarks][off] = {} - for w in wf_list: + for w in wf_strings: intern[name]["spec"][quarks][off][w] = {} if b2b: - for w2 in wf2_list: + for w2 in wf2_strings: intern[name]["spec"][quarks][off][w][w2] = {} intern[name]["spec"][quarks][off][w][w2]["pattern"] = _make_pattern(version, name, off, w, w2, intern[name]['b2b'], quarks) else: intern[name]["spec"][quarks][off][w]["0"] = {} intern[name]["spec"][quarks][off][w]["0"]["pattern"] = _make_pattern(version, name, off, w, 0, intern[name]['b2b'], quarks) - internal_ret_dict = {} + internal_ret_dict: dict[str, list] = {} needed_keys = [] for name, corr_type in zip(name_list, corr_type_list): b2b, single = _extract_corr_type(corr_type) if b2b: - needed_keys.extend(_lists2key([name], quarks_list, noffset_list, wf_list, wf2_list)) + needed_keys.extend(_lists2key([name], quarks_list, noffset_strings, wf_strings, wf2_strings)) else: - needed_keys.extend(_lists2key([name], quarks_list, noffset_list, wf_list, ["0"])) + needed_keys.extend(_lists2key([name], quarks_list, noffset_strings, wf_strings, ["0"])) for key in needed_keys: internal_ret_dict[key] = [] @@ -288,9 +286,9 @@ def read_sfcf_multi(path: str, prefix: str, name_list: list[str], quarks_list: l if version == "0.0" or not compact: file = path + '/' + item + '/' + sub_ls[0] + '/' + name if corr_type_list[name_index] == 'bi': - name_keys = _lists2key(quarks_list, noffset_list, wf_list, ["0"]) + name_keys = _lists2key(quarks_list, noffset_strings, wf_strings, ["0"]) else: - name_keys = _lists2key(quarks_list, noffset_list, wf_list, wf2_list) + name_keys = _lists2key(quarks_list, noffset_strings, wf_strings, wf2_strings) for key in name_keys: specs = _key2specs(key) quarks = specs[0] @@ -306,7 +304,7 @@ def read_sfcf_multi(path: str, prefix: str, name_list: list[str], quarks_list: l intern[name]["T"] = T # preparing the datastructure # the correlators get parsed into... - deltas = [] + deltas: list[list] = [] for j in range(intern[name]["T"]): deltas.append([]) internal_ret_dict[sep.join([name, key])] = deltas @@ -327,8 +325,8 @@ def read_sfcf_multi(path: str, prefix: str, name_list: list[str], quarks_list: l rep_data.append(file_data) for t in range(intern[name]["T"]): internal_ret_dict[key][t].append([]) - for cfg in range(no_cfg): - internal_ret_dict[key][t][i].append(rep_data[cfg][key][t]) + for cfg_number in range(no_cfg): + internal_ret_dict[key][t][i].append(rep_data[cfg_number][key][t]) else: for key in needed_keys: specs = _key2specs(key) @@ -337,10 +335,9 @@ def read_sfcf_multi(path: str, prefix: str, name_list: list[str], quarks_list: l off = specs[2] w = specs[3] w2 = specs[4] - if "files" in kwargs: - if isinstance(kwargs.get("files"), list) and all(isinstance(f, str) for f in kwargs.get("files")): - name_ls = kwargs.get("files") - else: + name_ls = kwargs.get("files") + if name_ls is not None: + if not (isinstance(name_ls, list) and all(isinstance(f, str) for f in name_ls)): raise TypeError("In append mode, files has to be of type list[str]!") else: name_ls = ls @@ -377,7 +374,7 @@ def read_sfcf_multi(path: str, prefix: str, name_list: list[str], quarks_list: l if not silent: print("Done") - result_dict = {} + result_dict: dict[str, Any] = {} if keyed_out: for key in needed_keys: name = _key2specs(key)[0] @@ -390,12 +387,12 @@ def read_sfcf_multi(path: str, prefix: str, name_list: list[str], quarks_list: l result_dict[name] = {} for quarks in quarks_list: result_dict[name][quarks] = {} - for off in noffset_list: + for off in noffset_strings: result_dict[name][quarks][off] = {} - for w in wf_list: + for w in wf_strings: result_dict[name][quarks][off][w] = {} if corr_type != 'bi': - for w2 in wf2_list: + for w2 in wf2_strings: key = _specs2key(name, quarks, off, w, w2) result = [] for t in range(intern[name]["T"]): @@ -642,13 +639,13 @@ def _read_append_rep(filename: str, pattern: str, b2b: bool, cfg_separator: str, rep_idl.append(idl) rep_data.append(data) - data = [] + final_data: list[list[float]] = [] for t in range(T): - data.append([]) + final_data.append([]) for c in range(len(rep_data)): - data[t].append(rep_data[c][t]) - return T, rep_idl, data + final_data[t].append(rep_data[c][t]) + return T, rep_idl, final_data def _get_rep_names(ls: list[str], ens_name: None=None) -> list[str]: From 336117c1cfe70a046edc694b603e023bf7f808be Mon Sep 17 00:00:00 2001 From: Fabian Joswig Date: Sun, 5 Jan 2025 17:12:50 +0100 Subject: [PATCH 19/57] [Fix] More type hint fixing --- pyerrors/correlators.py | 4 ++-- pyerrors/input/dobs.py | 15 ++++++++------- pyerrors/input/sfcf.py | 1 + pyerrors/obs.py | 2 +- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/pyerrors/correlators.py b/pyerrors/correlators.py index 729cf560..f5ca283d 100644 --- a/pyerrors/correlators.py +++ b/pyerrors/correlators.py @@ -209,7 +209,7 @@ def item(self, i: int, j: int) -> "Corr": newcontent = [None if (item is None) else item[i, j] for item in self.content] return Corr(newcontent) - def plottable(self) -> tuple[list[int], list[float]]: + def plottable(self) -> tuple[list, list, list]: """Outputs the correlator in a plotable format. Outputs three lists containing the timeslice index, the value on each @@ -1415,7 +1415,7 @@ def prune(self, Ntrunc: int, tproj: int=3, t0proj: int=2, basematrix: None=None) return Corr(newcontent) -def _sort_vectors(vec_set_in: list[Optional[ndarray]], ts: int) -> list[Optional[Union[ndarray, list[ndarray]]]]: +def _sort_vectors(vec_set_in: list[Optional[ndarray]], ts: int) -> list[Union[None, ndarray, list[ndarray]]]: """Helper function used to find a set of Eigenvectors consistent over all timeslices""" if isinstance(vec_set_in[ts][0][0], Obs): diff --git a/pyerrors/input/dobs.py b/pyerrors/input/dobs.py index af60eb9c..719773b6 100644 --- a/pyerrors/input/dobs.py +++ b/pyerrors/input/dobs.py @@ -338,8 +338,8 @@ def read_pobs(fname: str, full_output: bool=False, gz: bool=True, separator_inse if gz: if not fname.endswith('.gz'): fname += '.gz' - with gzip.open(fname, 'r') as fin: - content = fin.read() + with gzip.open(fname, 'r') as gin: + content = gin.read() else: if fname.endswith('.gz'): warnings.warn("Trying to read from %s without unzipping!" % fname, UserWarning) @@ -721,7 +721,7 @@ def create_dobs_string(obsl: list[Obs], name: str, spec: str='dobs v1.0', origin symbol = [] if enstags is None: enstags = {} - od = {} + od: dict[str, Any] = {} r_names = [] for o in obsl: r_names += [name for name in o.names if name.split('|')[0] in o.mc_names] @@ -821,7 +821,7 @@ def create_dobs_string(obsl: list[Obs], name: str, spec: str='dobs v1.0', origin ed[''].append(ad) pd['edata'].append(ed) - allcov = {} + allcov: dict[str, ndarray] = {} for o in obsl: for cname in o.cov_names: if cname in allcov: @@ -925,9 +925,10 @@ def write_dobs(obsl: list[Obs], fname: str, name: str, spec: str='dobs v1.0', or if not fname.endswith('.gz'): fname += '.gz' - fp = gzip.open(fname, 'wb') - fp.write(dobsstring.encode('utf-8')) + gp = gzip.open(fname, 'wb') + gp.write(dobsstring.encode('utf-8')) + gp.close() else: fp = open(fname, 'w', encoding='utf-8') fp.write(dobsstring) - fp.close() + fp.close() diff --git a/pyerrors/input/sfcf.py b/pyerrors/input/sfcf.py index d9352785..8feb8989 100644 --- a/pyerrors/input/sfcf.py +++ b/pyerrors/input/sfcf.py @@ -517,6 +517,7 @@ def _find_correlator(file_name: str, version: str, pattern: str, b2b: bool, sile else: start_read = content.count('\n', 0, match.start()) + 5 + b2b end_match = re.search(r'\n\s*\n', content[match.start():]) + assert end_match is not None T = content[match.start():].count('\n', 0, end_match.start()) - 4 - b2b if not T > 0: raise ValueError("Correlator with pattern\n" + pattern + "\nis empty!") diff --git a/pyerrors/obs.py b/pyerrors/obs.py index cb8d97c7..4ee81d10 100644 --- a/pyerrors/obs.py +++ b/pyerrors/obs.py @@ -69,7 +69,7 @@ class Obs: N_sigma_global = 1.0 N_sigma_dict: dict[str, int] = {} - def __init__(self, samples: list[Union[ndarray, list[Any]]], names: list[str], idl: Optional[list[Union[list[int], range]]]=None, **kwargs): + def __init__(self, samples: list[Union[ndarray, list[Any]]], names: list[str], idl: Optional[Union[list[list[int]], list[Union[list[int], range]], list[range]]]=None, **kwargs): """ Initialize Obs object. Parameters From 52b91d83d836c1b855834564065e52a186ed568a Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Thu, 9 Jan 2025 09:25:57 +0000 Subject: [PATCH 20/57] add typehints for other util functions --- pyerrors/input/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyerrors/input/utils.py b/pyerrors/input/utils.py index 015b2fbe..0ab6b324 100644 --- a/pyerrors/input/utils.py +++ b/pyerrors/input/utils.py @@ -53,7 +53,7 @@ def sort_names(ll: list[str]) -> list[str]: return ll -def check_idl(idl, che): +def check_idl(idl: list, che: list) -> str: """Checks if list of configurations is contained in an idl Parameters @@ -83,7 +83,7 @@ def check_idl(idl, che): return miss_str -def check_params(path, param_hash, prefix, param_prefix="parameters_"): +def check_params(path: str, param_hash: str, prefix: str, param_prefix: str ="parameters_") -> dict[str, list]: """ Check if, for sfcf, the parameter hashes at the end of the parameter files are in fact the expected one. From cb9d942208ca2ffeb7123d7c74698246bde74157 Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Thu, 9 Jan 2025 09:44:32 +0000 Subject: [PATCH 21/57] make deriv structure like second_deriv --- pyerrors/correlators.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pyerrors/correlators.py b/pyerrors/correlators.py index f5ca283d..09fe9845 100644 --- a/pyerrors/correlators.py +++ b/pyerrors/correlators.py @@ -588,8 +588,8 @@ def deriv(self, variant: Optional[str]="symmetric") -> "Corr": """ if self.N != 1: raise ValueError("deriv only implemented for one-dimensional correlators.") + newcontent: list[Union[None, ndarray, Obs]] = [] if variant == "symmetric": - newcontent = [] for t in range(1, self.T - 1): if (self.content[t - 1] is None) or (self.content[t + 1] is None): newcontent.append(None) @@ -599,7 +599,6 @@ def deriv(self, variant: Optional[str]="symmetric") -> "Corr": raise ValueError('Derivative is undefined at all timeslices') return Corr(newcontent, padding=[1, 1]) elif variant == "forward": - newcontent = [] for t in range(self.T - 1): if (self.content[t] is None) or (self.content[t + 1] is None): newcontent.append(None) @@ -609,7 +608,6 @@ def deriv(self, variant: Optional[str]="symmetric") -> "Corr": raise ValueError("Derivative is undefined at all timeslices") return Corr(newcontent, padding=[0, 1]) elif variant == "backward": - newcontent = [] for t in range(1, self.T): if (self.content[t - 1] is None) or (self.content[t] is None): newcontent.append(None) @@ -619,7 +617,6 @@ def deriv(self, variant: Optional[str]="symmetric") -> "Corr": raise ValueError("Derivative is undefined at all timeslices") return Corr(newcontent, padding=[1, 0]) elif variant == "improved": - newcontent = [] for t in range(2, self.T - 2): if (self.content[t - 2] is None) or (self.content[t - 1] is None) or (self.content[t + 1] is None) or (self.content[t + 2] is None): newcontent.append(None) @@ -629,7 +626,6 @@ def deriv(self, variant: Optional[str]="symmetric") -> "Corr": raise ValueError('Derivative is undefined at all timeslices') return Corr(newcontent, padding=[2, 2]) elif variant == 'log': - newcontent = [] for t in range(self.T): if (self.content[t] is None) or (self.content[t] <= 0): newcontent.append(None) From 2f40ff8ce96e15957ea3f4b72906945d8517e772 Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Thu, 9 Jan 2025 12:32:19 +0000 Subject: [PATCH 22/57] add typing for read_pbp --- pyerrors/input/misc.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyerrors/input/misc.py b/pyerrors/input/misc.py index 252d7249..9aced78e 100644 --- a/pyerrors/input/misc.py +++ b/pyerrors/input/misc.py @@ -99,11 +99,15 @@ def fit_t0(t2E_dict: dict[float, Obs], fit_range: int, plot_fit: Optional[bool]= return -fit_result[0] / fit_result[1] -def read_pbp(path, prefix, **kwargs): +def read_pbp(path: str, prefix: str, **kwargs): """Read pbp format from given folder structure. Parameters ---------- + path : str + Directory to read pbp from + prefix : str + Prefix of the files to be read r_start : list list which contains the first config to be read for each replicum r_stop : list From bbf0b689a1a07c8a5abe84c611298913c2e7ee07 Mon Sep 17 00:00:00 2001 From: Fabian Joswig Date: Tue, 7 Jan 2025 10:22:58 +0100 Subject: [PATCH 23/57] [Fix] Additional typehints --- pyerrors/input/sfcf.py | 4 ++-- pyerrors/obs.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyerrors/input/sfcf.py b/pyerrors/input/sfcf.py index 89cda7e1..383bac56 100644 --- a/pyerrors/input/sfcf.py +++ b/pyerrors/input/sfcf.py @@ -650,7 +650,7 @@ def _read_append_rep(filename: str, pattern: str, b2b: bool, cfg_separator: str, return T, rep_idl, final_data -def _get_rep_names(ls: list[str], ens_name: None=None, rep_sep: str ='r') -> list[str]: +def _get_rep_names(ls: list[str], ens_name: Optional[str]=None, rep_sep: str ='r') -> list[str]: new_names = [] for entry in ls: try: @@ -665,7 +665,7 @@ def _get_rep_names(ls: list[str], ens_name: None=None, rep_sep: str ='r') -> lis return new_names -def _get_appended_rep_names(ls: list[str], prefix: str, name: str, ens_name: None=None, rep_sep: str ='r') -> list[str]: +def _get_appended_rep_names(ls: list[str], prefix: str, name: str, ens_name: Optional[str]=None, rep_sep: str ='r') -> list[str]: new_names = [] for exc in ls: if not fnmatch.fnmatch(exc, prefix + '*.' + name): diff --git a/pyerrors/obs.py b/pyerrors/obs.py index 4ee81d10..45337053 100644 --- a/pyerrors/obs.py +++ b/pyerrors/obs.py @@ -233,7 +233,7 @@ def gamma_method(self, **kwargs): else: fft = True - def _parse_kwarg(kwarg_name): + def _parse_kwarg(kwarg_name: str): if kwarg_name in kwargs: tmp = kwargs.get(kwarg_name) if isinstance(tmp, (int, float)): From 7a3a28dad00960f2761bb9f0cabc670bd87b5ae7 Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Mon, 13 Jan 2025 17:33:50 +0000 Subject: [PATCH 24/57] add typehints for check_params --- pyerrors/input/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyerrors/input/utils.py b/pyerrors/input/utils.py index 0ab6b324..9d3126a6 100644 --- a/pyerrors/input/utils.py +++ b/pyerrors/input/utils.py @@ -83,7 +83,7 @@ def check_idl(idl: list, che: list) -> str: return miss_str -def check_params(path: str, param_hash: str, prefix: str, param_prefix: str ="parameters_") -> dict[str, list]: +def check_params(path: str, param_hash: str, prefix: str, param_prefix: str ="parameters_") -> dict[str, str]: """ Check if, for sfcf, the parameter hashes at the end of the parameter files are in fact the expected one. From 4814675ff665d12ce11b33eac48324337d5b3523 Mon Sep 17 00:00:00 2001 From: Simon Kuberski Date: Mon, 17 Feb 2025 15:24:18 +0100 Subject: [PATCH 25/57] [Fix] more work on typehints --- pyerrors/correlators.py | 37 +++++++++++++++++-------------------- pyerrors/covobs.py | 6 +++--- pyerrors/fits.py | 13 ++++++++++++- pyerrors/misc.py | 2 +- pyerrors/obs.py | 36 +++++++++++++++++++----------------- tests/correlators_test.py | 4 +++- tests/obs_test.py | 1 + 7 files changed, 56 insertions(+), 43 deletions(-) diff --git a/pyerrors/correlators.py b/pyerrors/correlators.py index 09fe9845..8b436143 100644 --- a/pyerrors/correlators.py +++ b/pyerrors/correlators.py @@ -45,7 +45,7 @@ class Corr: __slots__ = ["content", "N", "T", "tag", "prange"] - def __init__(self, data_input: Any, padding: list[int]=[0, 0], prange: Optional[list[int]]=None): + def __init__(self, data_input: list[Obs, CObs], padding: list[int]=[0, 0], prange: Optional[list[int]]=None): """ Initialize a Corr object. Parameters @@ -285,7 +285,7 @@ def trace(self) -> "Corr": """Calculates the per-timeslice trace of a correlator matrix.""" if self.N == 1: raise ValueError("Only works for correlator matrices.") - newcontent: list[Union[None, float]] = [] + newcontent: list[Union[None, Obs, CObs]] = [] for t in range(self.T): if _check_for_none(self, self.content[t]): newcontent.append(None) @@ -715,8 +715,8 @@ def m_eff(self, variant: str='log', guess: float=1.0) -> "Corr": """ if self.N != 1: raise Exception('Correlator must be projected before getting m_eff') + newcontent: list[Union[None, Obs]] = [] if variant == 'log': - newcontent = [] for t in range(self.T - 1): if ((self.content[t] is None) or (self.content[t + 1] is None)) or (self.content[t + 1][0].value == 0): newcontent.append(None) @@ -730,7 +730,6 @@ def m_eff(self, variant: str='log', guess: float=1.0) -> "Corr": return np.log(Corr(newcontent, padding=[0, 1])) elif variant == 'logsym': - newcontent = [] for t in range(1, self.T - 1): if ((self.content[t - 1] is None) or (self.content[t + 1] is None)) or (self.content[t + 1][0].value == 0): newcontent.append(None) @@ -752,7 +751,6 @@ def m_eff(self, variant: str='log', guess: float=1.0) -> "Corr": def root_function(x, d): return func(x * (t - self.T / 2)) / func(x * (t + 1 - self.T / 2)) - d - newcontent = [] for t in range(self.T - 1): if (self.content[t] is None) or (self.content[t + 1] is None) or (self.content[t + 1][0].value == 0): newcontent.append(None) @@ -769,7 +767,6 @@ def root_function(x, d): return Corr(newcontent, padding=[0, 1]) elif variant == 'arccosh': - newcontent = [] for t in range(1, self.T - 1): if (self.content[t] is None) or (self.content[t + 1] is None) or (self.content[t - 1] is None) or (self.content[t][0].value == 0): newcontent.append(None) @@ -782,7 +779,7 @@ def root_function(x, d): else: raise ValueError('Unknown variant.') - def fit(self, function: Callable, fitrange: Optional[Union[str, list[int]]]=None, silent: bool=False, **kwargs) -> Fit_result: + def fit(self, function: Callable, fitrange: Optional[list[int]]=None, silent: bool=False, **kwargs) -> Fit_result: r'''Fits function to the data Parameters @@ -865,7 +862,7 @@ def set_prange(self, prange: list[int]): self.prange = prange return - def show(self, x_range: Optional[list[int]]=None, comp: Optional[Corr]=None, y_range: None=None, logscale: bool=False, plateau: None=None, fit_res: Optional[Fit_result]=None, fit_key: Optional[str]=None, ylabel: None=None, save: None=None, auto_gamma: bool=False, hide_sigma: None=None, references: None=None, title: None=None): + def show(self, x_range: Optional[list[int]]=None, comp: Optional[Corr]=None, y_range: Optional[list[int, float]]=None, logscale: bool=False, plateau: Optional[Obs, float, int]=None, fit_res: Optional[Fit_result]=None, fit_key: Optional[str]=None, ylabel: Optional[str]=None, save: Optional[str]=None, auto_gamma: bool=False, hide_sigma: Optional[int, float]=None, references: Optional[list[float]]=None, title: Optional[str]=None): """Plots the correlator using the tag of the correlator as label if available. Parameters @@ -1081,14 +1078,14 @@ def __str__(self) -> str: __array_priority__ = 10000 - def __eq__(self, y: Union[Corr, Obs, int]) -> ndarray: + def __eq__(self, y: Any) -> ndarray: if isinstance(y, Corr): comp = np.asarray(y.content, dtype=object) else: comp = np.asarray(y) return np.asarray(self.content, dtype=object) == comp - def __add__(self, y: Any) -> "Corr": + def __add__(self, y: Union[Corr, Obs, CObs, int, float, complex, ndarray]) -> "Corr": if isinstance(y, Corr): if ((self.N != y.N) or (self.T != y.T)): raise ValueError("Addition of Corrs with different shape") @@ -1116,7 +1113,7 @@ def __add__(self, y: Any) -> "Corr": else: raise TypeError("Corr + wrong type") - def __mul__(self, y: Any) -> "Corr": + def __mul__(self, y: Union[Corr, Obs, CObs, int, float, complex, ndarray]) -> "Corr": if isinstance(y, Corr): if not ((self.N == 1 or y.N == 1 or self.N == y.N) and self.T == y.T): raise ValueError("Multiplication of Corr object requires N=N or N=1 and T=T") @@ -1187,7 +1184,7 @@ def __rmatmul__(self, y: ndarray) -> "Corr": else: return NotImplemented - def __truediv__(self, y: Union[Corr, float, ndarray, int]) -> "Corr": + def __truediv__(self, y: Union[Corr, Obs, CObs, int, float, ndarray]) -> "Corr": if isinstance(y, Corr): if not ((self.N == 1 or y.N == 1 or self.N == y.N) and self.T == y.T): raise ValueError("Multiplication of Corr object requires N=N or N=1 and T=T") @@ -1245,10 +1242,10 @@ def __neg__(self) -> "Corr": newcontent = [None if _check_for_none(self, item) else -1. * item for item in self.content] return Corr(newcontent, prange=self.prange) - def __sub__(self, y: Union[Corr, float, ndarray, int]) -> "Corr": + def __sub__(self, y: Union[Corr, Obs, CObs, int, float, complex, ndarray]) -> "Corr": return self + (-y) - def __pow__(self, y: Union[float, int]) -> "Corr": + def __pow__(self, y: Union[Obs, CObs, float, int]) -> "Corr": if isinstance(y, (Obs, int, float, CObs)): newcontent = [None if _check_for_none(self, item) else item**y for item in self.content] return Corr(newcontent, prange=self.prange) @@ -1321,16 +1318,16 @@ def arctanh(self) -> "Corr": return self._apply_func_to_corr(np.arctanh) # Right hand side operations (require tweak in main module to work) - def __radd__(self, y): + def __radd__(self, y: Union[Corr, Obs, CObs, int, float, complex, ndarray]) -> "Corr": return self + y - def __rsub__(self, y: int) -> "Corr": + def __rsub__(self, y: Union[Corr, Obs, CObs, int, float, complex, ndarray]) -> "Corr": return -self + y - def __rmul__(self, y: Union[float, int]) -> "Corr": + def __rmul__(self, y: Union[Corr, Obs, CObs, int, float, complex, ndarray]) -> "Corr": return self * y - def __rtruediv__(self, y: int) -> "Corr": + def __rtruediv__(self, y: Union[Corr, Obs, CObs, int, float, ndarray]) -> "Corr": return (self / y) ** (-1) @property @@ -1353,7 +1350,7 @@ def return_imag(obs_OR_cobs): return self._apply_func_to_corr(return_imag) - def prune(self, Ntrunc: int, tproj: int=3, t0proj: int=2, basematrix: None=None) -> "Corr": + def prune(self, Ntrunc: int, tproj: int=3, t0proj: int=2, basematrix: Optional[ndarray]=None) -> "Corr": r''' Project large correlation matrix to lowest states This method can be used to reduce the size of an (N x N) correlation matrix @@ -1448,7 +1445,7 @@ def _check_for_none(corr: Corr, entry: Optional[ndarray]) -> bool: return len(list(filter(None, np.asarray(entry).flatten()))) < corr.N ** 2 -def _GEVP_solver(Gt: Optional[ndarray], G0: ndarray, method: str='eigh', chol_inv: Optional[ndarray]=None) -> ndarray: +def _GEVP_solver(Gt: ndarray, G0: ndarray, method: str='eigh', chol_inv: Optional[ndarray]=None) -> ndarray: r"""Helper function for solving the GEVP and sorting the eigenvectors. Solves $G(t)v_i=\lambda_i G(t_0)v_i$ and returns the eigenvectors v_i diff --git a/pyerrors/covobs.py b/pyerrors/covobs.py index 2c654ee0..c1cdd013 100644 --- a/pyerrors/covobs.py +++ b/pyerrors/covobs.py @@ -1,12 +1,12 @@ from __future__ import annotations import numpy as np from numpy import ndarray -from typing import Any, Optional, Union +from typing import Optional, Union class Covobs: - def __init__(self, mean: Optional[Union[float, int]], cov: Any, name: str, pos: Optional[int]=None, grad: Optional[Union[ndarray, list[float]]]=None): + def __init__(self, mean: Union[float, int], cov: Union[list, ndarray], name: str, pos: Optional[int]=None, grad: Optional[Union[ndarray, list[float]]]=None): """ Initialize Covobs object. Parameters @@ -47,7 +47,7 @@ def errsq(self) -> float: """ return np.dot(np.transpose(self.grad), np.dot(self.cov, self.grad)).item() - def _set_cov(self, cov: Any): + def _set_cov(self, cov: Union[list, ndarray]): """ Set the covariance matrix of the covobs Parameters diff --git a/pyerrors/fits.py b/pyerrors/fits.py index f1b22e76..4d72ae5e 100644 --- a/pyerrors/fits.py +++ b/pyerrors/fits.py @@ -827,9 +827,20 @@ def residual_plot(x: ndarray, y: list[Obs], func: Callable, fit_res: list[Obs], plt.draw() -def error_band(x: list[int], func: Callable, beta: list[Obs]) -> ndarray: +def error_band(x: list[int], func: Callable, beta: Union[Fit_result, list[Obs]]) -> ndarray: """Calculate the error band for an array of sample values x, for given fit function func with optimized parameters beta. + Parameters + ---------- + x : list[int] + A list of sample points where the error band is evaluated. + + func : Callable + The function representing the fit model. + + beta : Union[Fit_result, list[Obs]] + Optimized fit parameters. + Returns ------- err : np.array(Obs) diff --git a/pyerrors/misc.py b/pyerrors/misc.py index c4baff53..036ddbb1 100644 --- a/pyerrors/misc.py +++ b/pyerrors/misc.py @@ -29,7 +29,7 @@ def print_config(): print(f"{key: <10}\t {value}") -def errorbar(x, y, axes=plt, **kwargs): +def errorbar(x: Union[ndarray[int, float, Obs], list[int, float, Obs]], y: Union[ndarray[int, float, Obs], list[int, float, Obs]], axes=plt, **kwargs): """pyerrors wrapper for the errorbars method of matplotlib Parameters diff --git a/pyerrors/obs.py b/pyerrors/obs.py index 45337053..f8dc5229 100644 --- a/pyerrors/obs.py +++ b/pyerrors/obs.py @@ -12,7 +12,7 @@ import numdifftools as nd from itertools import groupby from .covobs import Covobs -from numpy import bool, float64, int64, ndarray +from numpy import float64, int64, ndarray from typing import Any, Callable, Optional, Union, Sequence, TYPE_CHECKING if sys.version_info >= (3, 10): @@ -501,7 +501,7 @@ def is_zero(self, atol: float=1e-10) -> Union[bool, bool]: """ return np.isclose(0.0, self.value, 1e-14, atol) and all(np.allclose(0.0, delta, 1e-14, atol) for delta in self.deltas.values()) and all(np.allclose(0.0, delta.errsq(), 1e-14, atol) for delta in self.covobs.values()) - def plot_tauint(self, save: None=None): + def plot_tauint(self, save: Optional[str]=None): """Plot integrated autocorrelation time for each ensemble. Parameters @@ -541,7 +541,7 @@ def plot_tauint(self, save: None=None): if save: fig.savefig(save + "_" + str(e)) - def plot_rho(self, save: None=None): + def plot_rho(self, save: Optional[str]=None): """Plot normalized autocorrelation function time for each ensemble. Parameters @@ -626,7 +626,7 @@ def plot_history(self, expand: bool=True): plt.title(e_name + f'\nskew: {skew(y_test):.3f} (p={skewtest(y_test).pvalue:.3f}), kurtosis: {kurtosis(y_test):.3f} (p={kurtosistest(y_test).pvalue:.3f})') plt.draw() - def plot_piechart(self, save: None=None) -> dict[str, float64]: + def plot_piechart(self, save: Optional[str]=None) -> dict[str, float64]: """Plot piechart which shows the fractional contribution of each ensemble to the error and returns a dictionary containing the fractions. @@ -708,7 +708,7 @@ def export_jackknife(self) -> ndarray: tmp_jacks[1:] = (n * mean - full_data) / (n - 1) return tmp_jacks - def export_bootstrap(self, samples: int=500, random_numbers: Optional[ndarray]=None, save_rng: None=None) -> ndarray: + def export_bootstrap(self, samples: int=500, random_numbers: Optional[ndarray]=None, save_rng: Optional[str]=None) -> ndarray: """Export bootstrap samples from the Obs Parameters @@ -784,19 +784,19 @@ def __hash__(self) -> int: return int(m.hexdigest(), 16) & 0xFFFFFFFF # Overload comparisons - def __lt__(self, other: Union[Obs, float, float64]) -> Union[bool, bool]: + def __lt__(self, other: Union[Obs, float, float64, int]) -> bool: return self.value < other - def __le__(self, other: Union[Obs, float, int]) -> bool: + def __le__(self, other: Union[Obs, float, float64, int]) -> bool: return self.value <= other - def __gt__(self, other: Union[Obs, float]) -> Union[bool, bool]: + def __gt__(self, other: Union[Obs, float, float64, int]) -> bool: return self.value > other - def __ge__(self, other: Union[Obs, float, int]) -> Union[bool, bool]: + def __ge__(self, other: Union[Obs, float, float64, int]) -> bool: return self.value >= other - def __eq__(self, other: Optional[Union[Obs, float64, int, float]]) -> Union[bool, bool]: + def __eq__(self, other: Optional[Union[Obs, float, float64, int]]) -> bool: if other is None: return False return (self - other).is_zero() @@ -815,10 +815,10 @@ def __add__(self, y: Any) -> Union[Obs, NotImplementedType, CObs, ndarray]: else: return derived_observable(lambda x, **kwargs: x[0] + y, [self], man_grad=[1]) - def __radd__(self, y: Union[float, int]) -> Union[Obs, NotImplementedType, CObs, ndarray]: + def __radd__(self, y: Any) -> Union[Obs, NotImplementedType, CObs, ndarray]: return self + y - def __mul__(self, y: Any) -> Union[Obs, ndarray, CObs, NotImplementedType]: + def __mul__(self, y: Any) -> Union[Obs, NotImplementedType, CObs, ndarray]: if isinstance(y, Obs): return derived_observable(lambda x, **kwargs: x[0] * x[1], [self, y], man_grad=[y.value, self.value]) else: @@ -831,10 +831,10 @@ def __mul__(self, y: Any) -> Union[Obs, ndarray, CObs, NotImplementedType]: else: return derived_observable(lambda x, **kwargs: x[0] * y, [self], man_grad=[y]) - def __rmul__(self, y: Union[float, int]) -> Union[Obs, NotImplementedType, CObs, ndarray]: + def __rmul__(self, y: Any) -> Union[Obs, NotImplementedType, CObs, ndarray]: return self * y - def __sub__(self, y: Any) -> Union[Obs, NotImplementedType, ndarray]: + def __sub__(self, y: Any) -> Union[Obs, NotImplementedType, CObs, ndarray]: if isinstance(y, Obs): return derived_observable(lambda x, **kwargs: x[0] - x[1], [self, y], man_grad=[1, -1]) else: @@ -845,7 +845,7 @@ def __sub__(self, y: Any) -> Union[Obs, NotImplementedType, ndarray]: else: return derived_observable(lambda x, **kwargs: x[0] - y, [self], man_grad=[1]) - def __rsub__(self, y: Union[float, int]) -> Union[Obs, NotImplementedType, CObs, ndarray]: + def __rsub__(self, y: Any) -> Union[Obs, NotImplementedType, CObs, ndarray]: return -1 * (self - y) def __pos__(self) -> Obs: @@ -959,6 +959,8 @@ def gamma_method(self, **kwargs): if isinstance(self.imag, Obs): self.imag.gamma_method(**kwargs) + gm = gamma_method + def is_zero(self) -> bool: """Checks whether both real and imaginary part are zero within machine precision.""" return self.real == 0.0 and self.imag == 0.0 @@ -1057,7 +1059,7 @@ def __format__(self, format_type: str) -> str: return f"({self.real:{format_type}}{self.imag:+{significance}}j)" -def gamma_method(x: Union[Corr, Obs, ndarray, list[Obs]], **kwargs) -> ndarray: +def gamma_method(x: Union[Corr, Obs, CObs, ndarray, list[Obs, CObs]], **kwargs) -> ndarray: """Vectorized version of the gamma_method applicable to lists or arrays of Obs. See docstring of pe.Obs.gamma_method for details. @@ -1192,7 +1194,7 @@ def _expand_deltas_for_merge(deltas: ndarray, idx: Union[range, list[int]], shap return np.array([ret[new_idx[i] - new_idx[0]] for i in range(len(new_idx))]) * len(new_idx) / len(idx) * scalefactor -def derived_observable(func: Callable, data: Any, array_mode: bool=False, **kwargs) -> Union[Obs, ndarray]: +def derived_observable(func: Callable, data: Union[list[Obs], ndarray], array_mode: bool=False, **kwargs) -> Union[Obs, ndarray]: """Construct a derived Obs according to func(data, **kwargs) using automatic differentiation. Parameters diff --git a/tests/correlators_test.py b/tests/correlators_test.py index fc3528d2..052fd40d 100644 --- a/tests/correlators_test.py +++ b/tests/correlators_test.py @@ -181,6 +181,8 @@ def f(a, x): with pytest.raises(ValueError): my_corr.fit(f, [0, 2, 3]) + fit_res = my_corr.fit(f, fitrange=[0, 1]) + def test_plateau(): my_corr = pe.correlators.Corr([pe.pseudo_Obs(1.01324, 0.05, 't'), pe.pseudo_Obs(1.042345, 0.008, 't')]) @@ -226,7 +228,7 @@ def test_utility(): corr.print() corr.print([2, 4]) corr.show() - corr.show(comp=corr) + corr.show(comp=corr, x_range=[2, 5.], y_range=[2, 3.], hide_sigma=0.5, references=[.1, .2, .6], title='TEST') corr.dump('test_dump', datatype="pickle", path='.') corr.dump('test_dump', datatype="pickle") diff --git a/tests/obs_test.py b/tests/obs_test.py index 2c642ad4..72da2dfd 100644 --- a/tests/obs_test.py +++ b/tests/obs_test.py @@ -407,6 +407,7 @@ def test_cobs(): obs2 = pe.pseudo_Obs(-0.2, 0.03, 't') my_cobs = pe.CObs(obs1, obs2) + my_cobs.gm() assert +my_cobs == my_cobs assert -my_cobs == 0 - my_cobs my_cobs == my_cobs From f44b19c9d1fb189572740612bf0ba5b0a4996f80 Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Sat, 29 Mar 2025 11:10:26 +0000 Subject: [PATCH 26/57] clean up sfcf input types --- pyerrors/input/sfcf.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/pyerrors/input/sfcf.py b/pyerrors/input/sfcf.py index 89cda7e1..9cb714ef 100644 --- a/pyerrors/input/sfcf.py +++ b/pyerrors/input/sfcf.py @@ -7,7 +7,7 @@ from .utils import sort_names, check_idl import itertools from numpy import ndarray -from typing import Any, Union, Optional +from typing import Any, Union, Optional, Literal sep = "/" @@ -143,14 +143,14 @@ def read_sfcf_multi(path: str, prefix: str, name_list: list[str], quarks_list: l dict[name][quarks][offset][wf][wf2] = list[Obs] """ - if kwargs.get('im'): + im: Literal[1, 0] = 0 + part: str = 'real' + + if kwargs.get('im', False): im = 1 part = 'imaginary' - else: - im = 0 - part = 'real' - known_versions = ["0.0", "1.0", "2.0", "1.0c", "2.0c", "1.0a", "2.0a"] + known_versions: list = ["0.0", "1.0", "2.0", "1.0c", "2.0c", "1.0a", "2.0a"] if version not in known_versions: raise Exception("This version is not known!") @@ -165,9 +165,9 @@ def read_sfcf_multi(path: str, prefix: str, name_list: list[str], quarks_list: l else: compact = False appended = False - ls = kwargs.get("replica") - if ls is None: - ls = [] + ls: list = kwargs.get("replica", []) + if ls == []: + for (dirpath, dirnames, filenames) in os.walk(path): if not appended: ls.extend(dirnames) @@ -180,7 +180,7 @@ def read_sfcf_multi(path: str, prefix: str, name_list: list[str], quarks_list: l for exc in ls: if not fnmatch.fnmatch(exc, prefix + '*'): ls = list(set(ls) - set([exc])) - + replica: int = 0 if not appended: ls = sort_names(ls) replica = len(ls) @@ -246,6 +246,7 @@ def read_sfcf_multi(path: str, prefix: str, name_list: list[str], quarks_list: l for key in needed_keys: internal_ret_dict[key] = [] + rep_idl: list = [] if not appended: for i, item in enumerate(ls): rep_path = path + '/' + item @@ -318,7 +319,7 @@ def read_sfcf_multi(path: str, prefix: str, name_list: list[str], quarks_list: l internal_ret_dict[key][t].append(rep_deltas[key][t]) else: for key in needed_keys: - rep_data = [] + rep_data: list = [] name = _key2specs(key)[0] for subitem in sub_ls: cfg_path = path + '/' + item + '/' + subitem @@ -350,6 +351,7 @@ def read_sfcf_multi(path: str, prefix: str, name_list: list[str], quarks_list: l deltas = [] for rep, file in enumerate(name_ls): rep_idl = [] + rep_data = [] filename = path + '/' + file T, rep_idl, rep_data = _read_append_rep(filename, pattern, intern[name]['b2b'], cfg_separator, im, intern[name]['single']) if rep == 0: @@ -362,12 +364,12 @@ def read_sfcf_multi(path: str, prefix: str, name_list: list[str], quarks_list: l if name == name_list[0]: idl.append(rep_idl) - if kwargs.get("check_configs") is True: + che: Union[list[list[int]], None] = kwargs.get("check_configs", None) + if che is not None: if not silent: print("Checking for missing configs...") - che = kwargs.get("check_configs") if not (len(che) == len(idl)): - raise Exception("check_configs has to be the same length as replica!") + raise Exception("check_configs has to have an entry for each replicum!") for r in range(len(idl)): if not silent: print("checking " + new_names[r]) From 96cdec46e236583ab8e73563d22d53380aa8f720 Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Sat, 29 Mar 2025 14:04:52 +0000 Subject: [PATCH 27/57] annotate read_ms5_xsf --- pyerrors/input/openQCD.py | 171 ++++++++++++++++++-------------------- 1 file changed, 79 insertions(+), 92 deletions(-) diff --git a/pyerrors/input/openQCD.py b/pyerrors/input/openQCD.py index 339c8bfd..98ae202e 100644 --- a/pyerrors/input/openQCD.py +++ b/pyerrors/input/openQCD.py @@ -10,10 +10,19 @@ from .misc import fit_t0 from .utils import sort_names from io import BufferedReader -from typing import Optional, Union +from typing import Optional, Union, TypedDict, Unpack -def read_rwms(path: str, prefix: str, version: str='2.0', names: Optional[list[str]]=None, **kwargs) -> list[Obs]: +class rwms_kwargs(TypedDict): + files: list[str] + postfix: str + r_start: list[Union[int]] + r_stop: list[Union[int]] + r_step: int + + + +def read_rwms(path: str, prefix: str, version: str='2.0', names: Optional[list[str]]=None, **kwargs: Unpack[rwms_kwargs]) -> list[Obs]: """Read rwms format from given folder structure. Returns a list of length nrw Parameters @@ -27,7 +36,7 @@ def read_rwms(path: str, prefix: str, version: str='2.0', names: Optional[list[s version : str version of openQCD, default 2.0 names : list - list of names that is assigned to the data according according + list of names that is assigned to the data according to the order in the file list. Use careful, if you do not provide file names! r_start : list list which contains the first config to be read for each replicum @@ -53,39 +62,24 @@ def read_rwms(path: str, prefix: str, version: str='2.0', names: Optional[list[s if version not in known_oqcd_versions: raise Exception('Unknown openQCD version defined!') print("Working with openQCD version " + version) - if 'postfix' in kwargs: - postfix = kwargs.get('postfix') - else: - postfix = '' + postfix: str = kwargs.get('postfix', '') - if 'files' in kwargs: - known_files = kwargs.get('files') - else: - known_files = [] + known_files: list[str] = kwargs.get('files', []) + ls = _find_files(path, prefix, postfix, 'dat', known_files=known_files) replica = len(ls) - if 'r_start' in kwargs: - r_start = kwargs.get('r_start') - if len(r_start) != replica: - raise Exception('r_start does not match number of replicas') - r_start = [o if o else None for o in r_start] - else: - r_start = [None] * replica + r_start: list[Union[int, None]] = kwargs.get('r_start', [None] * replica) + if len(r_start) != replica: + raise Exception('r_start does not match number of replicas') - if 'r_stop' in kwargs: - r_stop = kwargs.get('r_stop') - if len(r_stop) != replica: - raise Exception('r_stop does not match number of replicas') - else: - r_stop = [None] * replica + r_stop: list[Union[int, None]] = kwargs.get('r_stop', [None] * replica) + if len(r_stop) != replica: + raise Exception('r_stop does not match number of replicas') - if 'r_step' in kwargs: - r_step = kwargs.get('r_step') - else: - r_step = 1 + r_step: int = kwargs.get('r_step', 1) print('Read reweighting factors from', prefix[:-1], ',', replica, 'replica', end='') @@ -110,14 +104,14 @@ def read_rwms(path: str, prefix: str, version: str='2.0', names: Optional[list[s print_err = 1 print() - deltas = [] + deltas: list[list[float]] = [] - configlist = [] + configlist: list[list[int]] = [] r_start_index = [] r_stop_index = [] for rep in range(replica): - tmp_array = [] + tmp_array: list[list] = [] with open(path + '/' + ls[rep], 'rb') as fp: t = fp.read(4) # number of reweighting factors @@ -144,7 +138,7 @@ def read_rwms(path: str, prefix: str, version: str='2.0', names: Optional[list[s for i in range(nrw): nfct.append(1) - nsrc = [] + nsrc: list[int] = [] for i in range(nrw): t = fp.read(4) nsrc.append(struct.unpack('i', t)[0]) @@ -161,11 +155,12 @@ def read_rwms(path: str, prefix: str, version: str='2.0', names: Optional[list[s configlist[-1].append(config_no) for i in range(nrw): if (version == '2.0'): + tmpd: dict = _read_array_openQCD2(fp) tmpd = _read_array_openQCD2(fp) - tmpd = _read_array_openQCD2(fp) - tmp_rw = tmpd['arr'] + tmp_rw: list[float] = tmpd['arr'] + tmp_n: list[int] = tmpd['n'] tmp_nfct = 1.0 - for j in range(tmpd['n'][0]): + for j in range(tmp_n[0]): tmp_nfct *= np.mean(np.exp(-np.asarray(tmp_rw[j]))) if print_err: print(config_no, i, j, @@ -179,7 +174,7 @@ def read_rwms(path: str, prefix: str, version: str='2.0', names: Optional[list[s for j in range(nfct[i]): t = fp.read(8 * nsrc[i]) t = fp.read(8 * nsrc[i]) - tmp_rw = struct.unpack('d' * nsrc[i], t) + tmp_rw: list[float] = struct.unpack('d' * nsrc[i], t) tmp_nfct *= np.mean(np.exp(-np.asarray(tmp_rw))) if print_err: print(config_no, i, j, @@ -232,7 +227,7 @@ def read_rwms(path: str, prefix: str, version: str='2.0', names: Optional[list[s return result -def _extract_flowed_energy_density(path: str, prefix: str, dtr_read: int, xmin: int, spatial_extent: int, postfix: str='ms', **kwargs) -> dict[float, Obs]: +def _extract_flowed_energy_density(path: str, prefix: str, dtr_read: int, xmin: int, spatial_extent: int, postfix: str='ms', **kwargs: Unpack[rwms_kwargs]) -> dict[float, Obs]: """Extract a dictionary with the flowed Yang-Mills action density from given .ms.dat files. Returns a dictionary with Obs as values and flow times as keys. @@ -319,18 +314,18 @@ def _extract_flowed_energy_density(path: str, prefix: str, dtr_read: int, xmin: print('Extract flowed Yang-Mills action density from', prefix, ',', replica, 'replica') - if 'names' in kwargs: - rep_names = kwargs.get('names') - else: + + rep_names: list[str] = kwargs.get('names', []) + if len(rep_names) == 0: rep_names = [] for entry in ls: truncated_entry = entry.split('.')[0] idx = truncated_entry.index('r') rep_names.append(truncated_entry[:idx] + '|' + truncated_entry[idx:]) - Ysum = [] + Ysum: list = [] - configlist = [] + configlist: list[list[int]] = [] r_start_index = [] r_stop_index = [] @@ -413,7 +408,7 @@ def _extract_flowed_energy_density(path: str, prefix: str, dtr_read: int, xmin: idl = [range(configlist[rep][r_start_index[rep]], configlist[rep][r_stop_index[rep]] + 1, r_step) for rep in range(replica)] E_dict = {} for n in range(nn + 1): - samples = [] + samples: list[list[float]] = [] for nrep, rep in enumerate(Ysum): samples.append([]) for cnfg in rep: @@ -599,7 +594,7 @@ def _parse_array_openQCD2(d: int, n: tuple[int, int], size: int, wa: Union[tuple return arr -def _find_files(path: str, prefix: str, postfix: str, ext: str, known_files: Union[str, list[str]]=[]) -> list[str]: +def _find_files(path: str, prefix: str, postfix: str, ext: str, known_files: list[str]=[]) -> list[str]: found = [] files = [] @@ -1146,7 +1141,7 @@ def read_qtop_sector(path: str, prefix: str, c: float, target: int=0, **kwargs) return qtop_projection(qtop, target=target) -def read_ms5_xsf(path: str, prefix: str, qc: str, corr: str, sep: str="r", **kwargs) -> Corr: +def read_ms5_xsf(path: str, prefix: str, qc: str, corr: str, sep: str="r", **kwargs) -> Union[Corr, CObs]: """ Read data from files in the specified directory with the specified prefix and quark combination extension, and return a `Corr` object containing the data. @@ -1188,9 +1183,7 @@ def read_ms5_xsf(path: str, prefix: str, qc: str, corr: str, sep: str="r", **kwa If there is an error unpacking binary data. """ - # found = [] files = [] - names = [] # test if the input is correct if qc not in ['dd', 'ud', 'du', 'uu']: @@ -1199,15 +1192,13 @@ def read_ms5_xsf(path: str, prefix: str, qc: str, corr: str, sep: str="r", **kwa if corr not in ["gS", "gP", "gA", "gV", "gVt", "lA", "lV", "lVt", "lT", "lTt", "g1", "l1"]: raise Exception("Unknown correlator!") - if "files" in kwargs: - known_files = kwargs.get("files") - else: - known_files = [] + known_files: list[str] = kwargs.get("files", []) + expected_idl = kwargs.get('idl', []) + files = _find_files(path, prefix, "ms5_xsf_" + qc, "dat", known_files=known_files) - if "names" in kwargs: - names = kwargs.get("names") - else: + names: list[str] = kwargs.get("names", []) + if len(names) == 0: for f in files: if not sep == "": se = f.split(".")[0] @@ -1216,31 +1207,30 @@ def read_ms5_xsf(path: str, prefix: str, qc: str, corr: str, sep: str="r", **kwa names.append(se.split(sep)[0] + "|r" + se.split(sep)[1]) else: names.append(prefix) - if 'idl' in kwargs: - expected_idl = kwargs.get('idl') + names = sorted(names) files = sorted(files) - cnfgs = [] - realsamples = [] - imagsamples = [] + cnfgs: list[list[int]] = [] + realsamples: list[list[list[float]]] = [] + imagsamples: list[list[list[float]]] = [] repnum = 0 for file in files: with open(path + "/" + file, "rb") as fp: - t = fp.read(8) - kappa = struct.unpack('d', t)[0] - t = fp.read(8) - csw = struct.unpack('d', t)[0] - t = fp.read(8) - dF = struct.unpack('d', t)[0] - t = fp.read(8) - zF = struct.unpack('d', t)[0] + tmp_bytes = fp.read(8) + kappa: float = struct.unpack('d', tmp_bytes)[0] + tmp_bytes = fp.read(8) + csw: float = struct.unpack('d', tmp_bytes)[0] + tmp_bytes = fp.read(8) + dF: float = struct.unpack('d', tmp_bytes)[0] + tmp_bytes = fp.read(8) + zF: float = struct.unpack('d', tmp_bytes)[0] - t = fp.read(4) - tmax = struct.unpack('i', t)[0] - t = fp.read(4) - bnd = struct.unpack('i', t)[0] + tmp_bytes = fp.read(4) + tmax: int = struct.unpack('i', tmp_bytes)[0] + tmp_bytes = fp.read(4) + bnd: int = struct.unpack('i', tmp_bytes)[0] placesBI = ["gS", "gP", "gA", "gV", @@ -1252,22 +1242,22 @@ def read_ms5_xsf(path: str, prefix: str, qc: str, corr: str, sep: str="r", **kwa # the chunks have the following structure: # confignumber, 10x timedependent complex correlators as doubles, 2x timeindependent complex correlators as doubles - chunksize = 4 + (8 * 2 * tmax * 10) + (8 * 2 * 2) packstr = '=i' + ('d' * 2 * tmax * 10) + ('d' * 2 * 2) + chunksize = struct.calcsize(packstr) cnfgs.append([]) realsamples.append([]) imagsamples.append([]) - for t in range(tmax): + for time in range(tmax): realsamples[repnum].append([]) imagsamples[repnum].append([]) if 'idl' in kwargs: left_idl = set(expected_idl[repnum]) while True: - cnfgt = fp.read(chunksize) - if not cnfgt: + cnfg_bytes = fp.read(chunksize) + if not cnfg_bytes: break - asascii = struct.unpack(packstr, cnfgt) - cnfg = asascii[0] + asascii = struct.unpack(packstr, cnfg_bytes) + cnfg: int = asascii[0] idl_wanted = True if 'idl' in kwargs: idl_wanted = (cnfg in expected_idl[repnum]) @@ -1280,24 +1270,21 @@ def read_ms5_xsf(path: str, prefix: str, qc: str, corr: str, sep: str="r", **kwa else: tmpcorr = asascii[1 + 2 * tmax * len(placesBI) + 2 * placesBB.index(corr):1 + 2 * tmax * len(placesBI) + 2 * placesBB.index(corr) + 2] - corrres = [[], []] + corrres: list[list[float]] = [[], []] for i in range(len(tmpcorr)): corrres[i % 2].append(tmpcorr[i]) - for t in range(int(len(tmpcorr) / 2)): - realsamples[repnum][t].append(corrres[0][t]) - for t in range(int(len(tmpcorr) / 2)): - imagsamples[repnum][t].append(corrres[1][t]) - if 'idl' in kwargs: - left_idl = list(left_idl) - if expected_idl[repnum] == left_idl: - raise ValueError("None of the idls searched for were found in replikum of file " + file) - elif len(left_idl) > 0: - warnings.warn('Could not find idls ' + str(left_idl) + ' in replikum of file ' + file, UserWarning) + for time in range(int(len(tmpcorr) / 2)): + realsamples[repnum][time].append(corrres[0][time]) + for time in range(int(len(tmpcorr) / 2)): + imagsamples[repnum][time].append(corrres[1][time]) + if len(expected_idl) > 0: + left_idl_list = list(left_idl) + if expected_idl[repnum] == left_idl_list: + raise ValueError("None of the idls searched for were found in replicum of file " + file) + elif len(left_idl_list) > 0: + warnings.warn('Could not find idls ' + str(left_idl) + ' in replicum of file ' + file, UserWarning) repnum += 1 - s = "Read correlator " + corr + " from " + str(repnum) + " replika with idls" + str(realsamples[0][t]) - for rep in range(1, repnum): - s += ", " + str(realsamples[rep][t]) - print(s) + print("Read correlator " + corr + " from " + str(repnum) + " replica with idls") print("Asserted run parameters:\n T:", tmax, "kappa:", kappa, "csw:", csw, "dF:", dF, "zF:", zF, "bnd:", bnd) # we have the data now... but we need to re format the whole thing and put it into Corr objects. From b3b4126a87b4b4acf8812e496f3a67fd51e3d4de Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Sat, 29 Mar 2025 14:30:31 +0000 Subject: [PATCH 28/57] some more quick fixes --- pyerrors/input/openQCD.py | 38 +++++++++++++------------------------- 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/pyerrors/input/openQCD.py b/pyerrors/input/openQCD.py index 98ae202e..1f36f2fd 100644 --- a/pyerrors/input/openQCD.py +++ b/pyerrors/input/openQCD.py @@ -16,8 +16,8 @@ class rwms_kwargs(TypedDict): files: list[str] postfix: str - r_start: list[Union[int]] - r_stop: list[Union[int]] + r_start: list[int] + r_stop: list[int] r_step: int @@ -71,11 +71,11 @@ def read_rwms(path: str, prefix: str, version: str='2.0', names: Optional[list[s replica = len(ls) - r_start: list[Union[int, None]] = kwargs.get('r_start', [None] * replica) + r_start: list[int] = kwargs.get('r_start', [-1] * replica) if len(r_start) != replica: raise Exception('r_start does not match number of replicas') - r_stop: list[Union[int, None]] = kwargs.get('r_stop', [None] * replica) + r_stop: list[int] = kwargs.get('r_stop', [-1] * replica) if len(r_stop) != replica: raise Exception('r_stop does not match number of replicas') @@ -283,34 +283,22 @@ def _extract_flowed_energy_density(path: str, prefix: str, dtr_read: int, xmin: Dictionary with the flowed action density at flow times t """ - if 'files' in kwargs: - known_files = kwargs.get('files') - else: - known_files = [] + known_files = kwargs.get('files', []) ls = _find_files(path, prefix, postfix, 'dat', known_files=known_files) replica = len(ls) - if 'r_start' in kwargs: - r_start = kwargs.get('r_start') - if len(r_start) != replica: - raise Exception('r_start does not match number of replicas') - r_start = [o if o else None for o in r_start] - else: - r_start = [None] * replica + + r_start: list[int] = kwargs.get('r_start', [-1] * replica) + if len(r_start) != replica: + raise Exception('r_start does not match number of replicas') - if 'r_stop' in kwargs: - r_stop = kwargs.get('r_stop') - if len(r_stop) != replica: - raise Exception('r_stop does not match number of replicas') - else: - r_stop = [None] * replica + r_stop: list[int] = kwargs.get('r_start', [-1] * replica) + if len(r_stop) != replica: + raise Exception('r_start does not match number of replicas') - if 'r_step' in kwargs: - r_step = kwargs.get('r_step') - else: - r_step = 1 + r_step = kwargs.get('r_step', 1) print('Extract flowed Yang-Mills action density from', prefix, ',', replica, 'replica') From f594e0632a90bc59682bf43da9a4ebf986a046c4 Mon Sep 17 00:00:00 2001 From: Fabian Joswig Date: Mon, 5 May 2025 19:14:33 +0200 Subject: [PATCH 29/57] [Fix] Clean up merge conflict --- pyerrors/obs.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyerrors/obs.py b/pyerrors/obs.py index e76ae597..ccf0cc58 100644 --- a/pyerrors/obs.py +++ b/pyerrors/obs.py @@ -1788,7 +1788,6 @@ def import_bootstrap(boots: ndarray, name: str, random_numbers: ndarray) -> Obs: return ret -<<<<<<< HEAD def merge_obs(list_of_obs: list[Obs]) -> Obs: """Combine all observables in list_of_obs into one new observable. This allows to merge Obs that have been computed on multiple replica From ab8ca3ac0efc2194697fa43823621c8b1d5855ad Mon Sep 17 00:00:00 2001 From: Fabian Joswig Date: Mon, 5 May 2025 19:16:35 +0200 Subject: [PATCH 30/57] [Fix] Clean up whitespaces --- pyerrors/input/openQCD.py | 4 ---- pyerrors/input/sfcf.py | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/pyerrors/input/openQCD.py b/pyerrors/input/openQCD.py index 1f36f2fd..18b6e99d 100644 --- a/pyerrors/input/openQCD.py +++ b/pyerrors/input/openQCD.py @@ -19,7 +19,6 @@ class rwms_kwargs(TypedDict): r_start: list[int] r_stop: list[int] r_step: int - def read_rwms(path: str, prefix: str, version: str='2.0', names: Optional[list[str]]=None, **kwargs: Unpack[rwms_kwargs]) -> list[Obs]: @@ -65,7 +64,6 @@ def read_rwms(path: str, prefix: str, version: str='2.0', names: Optional[list[s postfix: str = kwargs.get('postfix', '') known_files: list[str] = kwargs.get('files', []) - ls = _find_files(path, prefix, postfix, 'dat', known_files=known_files) @@ -289,7 +287,6 @@ def _extract_flowed_energy_density(path: str, prefix: str, dtr_read: int, xmin: replica = len(ls) - r_start: list[int] = kwargs.get('r_start', [-1] * replica) if len(r_start) != replica: raise Exception('r_start does not match number of replicas') @@ -302,7 +299,6 @@ def _extract_flowed_energy_density(path: str, prefix: str, dtr_read: int, xmin: print('Extract flowed Yang-Mills action density from', prefix, ',', replica, 'replica') - rep_names: list[str] = kwargs.get('names', []) if len(rep_names) == 0: rep_names = [] diff --git a/pyerrors/input/sfcf.py b/pyerrors/input/sfcf.py index a9a2723e..1294b833 100644 --- a/pyerrors/input/sfcf.py +++ b/pyerrors/input/sfcf.py @@ -167,7 +167,7 @@ def read_sfcf_multi(path: str, prefix: str, name_list: list[str], quarks_list: l appended = False ls: list = kwargs.get("replica", []) if ls == []: - + for (dirpath, dirnames, filenames) in os.walk(path): if not appended: ls.extend(dirnames) From f3e7cdec2824dbc7ab4ebe73ea9cf5b274ce4e24 Mon Sep 17 00:00:00 2001 From: Fabian Joswig Date: Mon, 5 May 2025 19:22:54 +0200 Subject: [PATCH 31/57] [Fix] Remove Unpack for compatibility with older python versions. --- pyerrors/input/openQCD.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyerrors/input/openQCD.py b/pyerrors/input/openQCD.py index 18b6e99d..14f76847 100644 --- a/pyerrors/input/openQCD.py +++ b/pyerrors/input/openQCD.py @@ -10,7 +10,7 @@ from .misc import fit_t0 from .utils import sort_names from io import BufferedReader -from typing import Optional, Union, TypedDict, Unpack +from typing import Optional, Union, TypedDict class rwms_kwargs(TypedDict): @@ -21,7 +21,7 @@ class rwms_kwargs(TypedDict): r_step: int -def read_rwms(path: str, prefix: str, version: str='2.0', names: Optional[list[str]]=None, **kwargs: Unpack[rwms_kwargs]) -> list[Obs]: +def read_rwms(path: str, prefix: str, version: str='2.0', names: Optional[list[str]]=None, **kwargs: rwms_kwargs) -> list[Obs]: """Read rwms format from given folder structure. Returns a list of length nrw Parameters @@ -225,7 +225,7 @@ def read_rwms(path: str, prefix: str, version: str='2.0', names: Optional[list[s return result -def _extract_flowed_energy_density(path: str, prefix: str, dtr_read: int, xmin: int, spatial_extent: int, postfix: str='ms', **kwargs: Unpack[rwms_kwargs]) -> dict[float, Obs]: +def _extract_flowed_energy_density(path: str, prefix: str, dtr_read: int, xmin: int, spatial_extent: int, postfix: str='ms', **kwargs: rwms_kwargs) -> dict[float, Obs]: """Extract a dictionary with the flowed Yang-Mills action density from given .ms.dat files. Returns a dictionary with Obs as values and flow times as keys. From ac7e98d1afd3ad836bef30d9d92f5455e8a1de08 Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Wed, 7 May 2025 07:32:29 +0000 Subject: [PATCH 32/57] simple fix for openQCD in test fails --- pyerrors/input/openQCD.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pyerrors/input/openQCD.py b/pyerrors/input/openQCD.py index 14f76847..8ba0ac6c 100644 --- a/pyerrors/input/openQCD.py +++ b/pyerrors/input/openQCD.py @@ -69,11 +69,11 @@ def read_rwms(path: str, prefix: str, version: str='2.0', names: Optional[list[s replica = len(ls) - r_start: list[int] = kwargs.get('r_start', [-1] * replica) + r_start: list[Union[int, None]] = kwargs.get('r_start', [None] * replica) if len(r_start) != replica: raise Exception('r_start does not match number of replicas') - r_stop: list[int] = kwargs.get('r_stop', [-1] * replica) + r_stop: list[Union[int, None]] = kwargs.get('r_stop', [None] * replica) if len(r_stop) != replica: raise Exception('r_stop does not match number of replicas') @@ -287,13 +287,13 @@ def _extract_flowed_energy_density(path: str, prefix: str, dtr_read: int, xmin: replica = len(ls) - r_start: list[int] = kwargs.get('r_start', [-1] * replica) + r_start: list[Union[int, None]] = kwargs.get('r_start', [None] * replica) if len(r_start) != replica: raise Exception('r_start does not match number of replicas') - r_stop: list[int] = kwargs.get('r_start', [-1] * replica) + r_stop: list[Union[int, None]] = kwargs.get('r_stop', [None] * replica) if len(r_stop) != replica: - raise Exception('r_start does not match number of replicas') + raise Exception('r_stop does not match number of replicas') r_step = kwargs.get('r_step', 1) From 356a3967fd2a515a42d25ffc56840ccde7160f88 Mon Sep 17 00:00:00 2001 From: Fabian Joswig Date: Mon, 23 Jun 2025 21:36:32 +0200 Subject: [PATCH 33/57] [Chore] Simplify flaot typehints --- pyerrors/obs.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/pyerrors/obs.py b/pyerrors/obs.py index ccf0cc58..21014bbb 100644 --- a/pyerrors/obs.py +++ b/pyerrors/obs.py @@ -159,11 +159,11 @@ def __init__(self, samples: list[Union[ndarray, list[Any]]], names: list[str], i self.tag = None @property - def value(self) -> Union[float, int64, float64, int]: + def value(self) -> float: return self._value @property - def dvalue(self) -> Union[float, float64]: + def dvalue(self) -> float: return self._dvalue @property @@ -481,7 +481,7 @@ def reweight(self, weight: "Obs") -> "Obs": """ return reweight(weight, [self])[0] - def is_zero_within_error(self, sigma: Union[float, int]=1) -> Union[bool, bool]: + def is_zero_within_error(self, sigma: float=1) -> bool: """Checks whether the observable is zero within 'sigma' standard errors. Parameters @@ -493,7 +493,7 @@ def is_zero_within_error(self, sigma: Union[float, int]=1) -> Union[bool, bool]: """ return self.is_zero() or np.abs(self.value) <= sigma * self._dvalue - def is_zero(self, atol: float=1e-10) -> Union[bool, bool]: + def is_zero(self, atol: float=1e-10) -> bool: """Checks whether the observable is zero within a given tolerance. Parameters @@ -867,7 +867,7 @@ def __truediv__(self, y: Any) -> Union[Obs, NotImplementedType, ndarray]: else: return derived_observable(lambda x, **kwargs: x[0] / y, [self], man_grad=[1 / y]) - def __rtruediv__(self, y: Union[float, int]) -> Obs: + def __rtruediv__(self, y: float) -> Obs: if isinstance(y, Obs): return derived_observable(lambda x, **kwargs: x[0] / x[1], [y, self], man_grad=[1 / self.value, - y.value / self.value ** 2]) else: @@ -878,13 +878,13 @@ def __rtruediv__(self, y: Union[float, int]) -> Obs: else: return derived_observable(lambda x, **kwargs: y / x[0], [self], man_grad=[-y / self.value ** 2]) - def __pow__(self, y: Union[Obs, float, int]) -> Obs: + def __pow__(self, y: Union[Obs, float]) -> Obs: if isinstance(y, Obs): return derived_observable(lambda x, **kwargs: x[0] ** x[1], [self, y], man_grad=[y.value * self.value ** (y.value - 1), self.value ** y.value * np.log(self.value)]) else: return derived_observable(lambda x, **kwargs: x[0] ** y, [self], man_grad=[y * self.value ** (y - 1)]) - def __rpow__(self, y: Union[float, int]) -> Obs: + def __rpow__(self, y: float) -> Obs: return derived_observable(lambda x, **kwargs: y ** x[0], [self], man_grad=[y ** self.value * np.log(y)]) def __abs__(self) -> Obs: @@ -941,7 +941,7 @@ class CObs: """Class for a complex valued observable.""" __slots__ = ['_real', '_imag', 'tag'] - def __init__(self, real: Obs, imag: Union[Obs, float, int]=0.0): + def __init__(self, real: Obs, imag: Union[Obs, float]=0.0): self._real = real self._imag = imag self.tag = None @@ -951,7 +951,7 @@ def real(self) -> Obs: return self._real @property - def imag(self) -> Union[Obs, float, int]: + def imag(self) -> Union[Obs, float]: return self._imag def gamma_method(self, **kwargs): @@ -979,7 +979,7 @@ def __add__(self, other: Any) -> Union[CObs, ndarray]: else: return CObs(self.real + other, self.imag) - def __radd__(self, y: Union[complex, float, Obs, int]) -> "CObs": + def __radd__(self, y: Union[complex, Obs]) -> "CObs": return self + y def __sub__(self, other: Any) -> Union[CObs, ndarray]: @@ -990,7 +990,7 @@ def __sub__(self, other: Any) -> Union[CObs, ndarray]: else: return CObs(self.real - other, self.imag) - def __rsub__(self, other: Union[complex, float, Obs, int]) -> "CObs": + def __rsub__(self, other: Union[complex, Obs]) -> "CObs": return -1 * (self - other) def __mul__(self, other: Any) -> Union[CObs, ndarray]: @@ -1012,7 +1012,7 @@ def __mul__(self, other: Any) -> Union[CObs, ndarray]: else: return CObs(self.real * other, self.imag * other) - def __rmul__(self, other: Union[complex, Obs, CObs, float, int]) -> "CObs": + def __rmul__(self, other: Union[complex, Obs, CObs]) -> "CObs": return self * other def __truediv__(self, other: Any) -> Union[CObs, ndarray]: @@ -1024,7 +1024,7 @@ def __truediv__(self, other: Any) -> Union[CObs, ndarray]: else: return CObs(self.real / other, self.imag / other) - def __rtruediv__(self, other: Union[complex, float, Obs, CObs, int]) -> CObs: + def __rtruediv__(self, other: Union[complex, Obs, CObs]) -> CObs: r = self.real ** 2 + self.imag ** 2 if hasattr(other, 'real') and hasattr(other, 'imag'): return CObs((self.real * other.real + self.imag * other.imag) / r, (self.real * other.imag - self.imag * other.real) / r) @@ -1164,7 +1164,7 @@ def _intersection_idx(idl: list[Union[range, list[int]]]) -> Union[range, list[i return idinter -def _expand_deltas_for_merge(deltas: ndarray, idx: Union[range, list[int]], shape: int, new_idx: Union[range, list[int]], scalefactor: Union[float, int]) -> ndarray: +def _expand_deltas_for_merge(deltas: ndarray, idx: Union[range, list[int]], shape: int, new_idx: Union[range, list[int]], scalefactor: float) -> ndarray: """Expand deltas defined on idx to the list of configs that is defined by new_idx. New, empty entries are filled by 0. If idx and new_idx are of type range, the smallest common divisor of the step sizes is used as new step size. From a4525bef31db8c9637d7e127d97c4b2007f47a7c Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Mon, 3 Nov 2025 11:24:46 +0000 Subject: [PATCH 34/57] correct typo --- pyerrors/input/sfcf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyerrors/input/sfcf.py b/pyerrors/input/sfcf.py index c6b0d976..2e44f87e 100644 --- a/pyerrors/input/sfcf.py +++ b/pyerrors/input/sfcf.py @@ -8,7 +8,7 @@ import itertools from numpy import ndarray from typing import Any, Union, Optional, Literal -from Collections.abc import Callable +from collections.abc import Callable sep = "/" From 1a771226ace67c37904a5c4ec48b311bbafd4b4a Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Mon, 3 Nov 2025 13:42:35 +0000 Subject: [PATCH 35/57] small type corrections --- pyerrors/input/sfcf.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pyerrors/input/sfcf.py b/pyerrors/input/sfcf.py index 2e44f87e..9076f239 100644 --- a/pyerrors/input/sfcf.py +++ b/pyerrors/input/sfcf.py @@ -14,7 +14,7 @@ sep = "/" -def read_sfcf(path: str, prefix: str, name: str, quarks: str='.*', corr_type: str="bi", noffset: int=0, wf: int=0, wf2: int=0, version: str="1.0c", cfg_separator: str="n", cfg_func: Callable | None = None, silent: bool=False, **kwargs) -> list[Obs]: +def read_sfcf(path: str, prefix: str, name: str, quarks: str='.*', corr_type: str="bi", noffset: int=0, wf: int=0, wf2: int=0, version: str="1.0c", cfg_separator: str="n", cfg_func: Optional[Callable] = None, silent: bool=False, **kwargs) -> list[Obs]: """Read sfcf files from given folder structure. Parameters @@ -79,7 +79,7 @@ def read_sfcf(path: str, prefix: str, name: str, quarks: str='.*', corr_type: st return ret[name][quarks][str(noffset)][str(wf)][str(wf2)] -def read_sfcf_multi(path: str, prefix: str, name_list: list[str], quarks_list: list[str]=['.*'], corr_type_list: list[str]=['bi'], noffset_list: list[int]=[0], wf_list: list[int]=[0], wf2_list: list[int]=[0], version: str="1.0c", cfg_separator: str="n", cfg_func: Callable | None = None, silent: bool=False, keyed_out: bool=False, **kwargs) -> dict: +def read_sfcf_multi(path: str, prefix: str, name_list: list[str], quarks_list: list[str]=['.*'], corr_type_list: list[str]=['bi'], noffset_list: list[int]=[0], wf_list: list[int]=[0], wf2_list: list[int]=[0], version: str="1.0c", cfg_separator: str="n", cfg_func: Optional[Callable] = None, silent: bool=False, keyed_out: bool=False, **kwargs) -> dict: """Read sfcf files from given folder structure. Parameters @@ -247,7 +247,7 @@ def read_sfcf_multi(path: str, prefix: str, name_list: list[str], quarks_list: l for key in needed_keys: internal_ret_dict[key] = [] - def _default_idl_func(cfg_string, cfg_sep): + def _default_idl_func(cfg_string: str, cfg_sep: str): return int(cfg_string.split(cfg_sep)[-1]) if cfg_func is None: @@ -596,7 +596,7 @@ def _read_compact_rep(path: str, rep: str, sub_ls: list[str], intern: dict[str, return return_vals -def _read_chunk_data(chunk: list[str], start_read: int, T: int, corr_line: int, b2b: bool, pattern: str, im: int, single: bool) -> tuple[int, list[float]]: +def _read_chunk_data(chunk: list[str], start_read: int, T: int, corr_line: int, b2b: bool, pattern: str, im: int, single: bool) -> list[float]: found_pat = "" data = [] for li in chunk[corr_line + 1:corr_line + 6 + b2b]: @@ -608,7 +608,7 @@ def _read_chunk_data(chunk: list[str], start_read: int, T: int, corr_line: int, return data -def _read_append_rep(filename: str, pattern: str, b2b: bool, im: int, single: bool, idl_func: Callable[str, list], cfg_func_args: list) -> tuple[int, list[int], list[list[float]]]: +def _read_append_rep(filename: str, pattern: str, b2b: bool, im: int, single: bool, idl_func: Callable, cfg_func_args: list) -> tuple[int, list[int], list[list[float]]]: with open(filename, 'r') as fp: content = fp.readlines() data_starts = [] From 33be7c2ecb1087eb40dc84fd7f649e1031639a37 Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Mon, 3 Nov 2025 13:45:56 +0000 Subject: [PATCH 36/57] simplify internal method for read_rwms --- pyerrors/input/openQCD.py | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/pyerrors/input/openQCD.py b/pyerrors/input/openQCD.py index 8ba0ac6c..ee6d0e30 100644 --- a/pyerrors/input/openQCD.py +++ b/pyerrors/input/openQCD.py @@ -13,15 +13,7 @@ from typing import Optional, Union, TypedDict -class rwms_kwargs(TypedDict): - files: list[str] - postfix: str - r_start: list[int] - r_stop: list[int] - r_step: int - - -def read_rwms(path: str, prefix: str, version: str='2.0', names: Optional[list[str]]=None, **kwargs: rwms_kwargs) -> list[Obs]: +def read_rwms(path: str, prefix: str, version: str='2.0', names: Optional[list[str]]=None, **kwargs) -> list[Obs]: """Read rwms format from given folder structure. Returns a list of length nrw Parameters @@ -69,11 +61,11 @@ def read_rwms(path: str, prefix: str, version: str='2.0', names: Optional[list[s replica = len(ls) - r_start: list[Union[int, None]] = kwargs.get('r_start', [None] * replica) + r_start: list[int] = kwargs.get('r_start', [0] * replica) if len(r_start) != replica: raise Exception('r_start does not match number of replicas') - r_stop: list[Union[int, None]] = kwargs.get('r_stop', [None] * replica) + r_stop: list[int] = kwargs.get('r_stop', [-1] * replica) if len(r_stop) != replica: raise Exception('r_stop does not match number of replicas') @@ -153,10 +145,8 @@ def read_rwms(path: str, prefix: str, version: str='2.0', names: Optional[list[s configlist[-1].append(config_no) for i in range(nrw): if (version == '2.0'): - tmpd: dict = _read_array_openQCD2(fp) - tmpd = _read_array_openQCD2(fp) - tmp_rw: list[float] = tmpd['arr'] - tmp_n: list[int] = tmpd['n'] + tmp_n, tmp_rw = _read_array_openQCD2(fp) + tmp_n, tmp_rw = _read_array_openQCD2(fp) tmp_nfct = 1.0 for j in range(tmp_n[0]): tmp_nfct *= np.mean(np.exp(-np.asarray(tmp_rw[j]))) @@ -189,7 +179,7 @@ def read_rwms(path: str, prefix: str, version: str='2.0', names: Optional[list[s offset = configlist[-1][0] - 1 configlist[-1] = [item - offset for item in configlist[-1]] - if r_start[rep] is None: + if r_start[rep] == 0: r_start_index.append(0) else: try: @@ -198,7 +188,7 @@ def read_rwms(path: str, prefix: str, version: str='2.0', names: Optional[list[s raise Exception('Config %d not in file with range [%d, %d]' % ( r_start[rep], configlist[-1][0], configlist[-1][-1])) from None - if r_stop[rep] is None: + if r_stop[rep] == -1: r_stop_index.append(len(configlist[-1]) - 1) else: try: @@ -618,7 +608,7 @@ def _find_files(path: str, prefix: str, postfix: str, ext: str, known_files: lis return files -def _read_array_openQCD2(fp: BufferedReader) -> dict[str, Union[int, tuple[int, int], list[list[float]]]]: +def _read_array_openQCD2(fp: BufferedReader) -> tuple[tuple[int, int], list[list[float]]]: t = fp.read(4) d = struct.unpack('i', t)[0] t = fp.read(4 * d) @@ -641,7 +631,7 @@ def _read_array_openQCD2(fp: BufferedReader) -> dict[str, Union[int, tuple[int, tmp = struct.unpack('%d%s' % (m, types), t) arr = _parse_array_openQCD2(d, n, size, tmp, quadrupel=True) - return {'d': d, 'n': n, 'size': size, 'arr': arr} + return n, arr def read_qtop(path: str, prefix: str, c: float, dtr_cnfg: int=1, version: str="openQCD", **kwargs) -> Obs: From c822811e44bc0f7438d2efccbd0aeca67c6cddc0 Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Mon, 3 Nov 2025 14:26:42 +0000 Subject: [PATCH 37/57] fix types for r_start and r_stop, also for postfix --- pyerrors/input/openQCD.py | 55 +++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/pyerrors/input/openQCD.py b/pyerrors/input/openQCD.py index ee6d0e30..7ea86c97 100644 --- a/pyerrors/input/openQCD.py +++ b/pyerrors/input/openQCD.py @@ -210,8 +210,8 @@ def read_rwms(path: str, prefix: str, version: str='2.0', names: Optional[list[s result = [] idl = [range(configlist[rep][r_start_index[rep]], configlist[rep][r_stop_index[rep]] + 1, r_step) for rep in range(replica)] - for t in range(nrw): - result.append(Obs(deltas[t], rep_names, idl=idl)) + for k in range(nrw): + result.append(Obs(deltas[k], rep_names, idl=idl)) return result @@ -277,11 +277,11 @@ def _extract_flowed_energy_density(path: str, prefix: str, dtr_read: int, xmin: replica = len(ls) - r_start: list[Union[int, None]] = kwargs.get('r_start', [None] * replica) + r_start: list[int] = kwargs.get('r_start', [0] * replica) if len(r_start) != replica: raise Exception('r_start does not match number of replicas') - r_stop: list[Union[int, None]] = kwargs.get('r_stop', [None] * replica) + r_stop: list[int] = kwargs.get('r_stop', [-1] * replica) if len(r_stop) != replica: raise Exception('r_stop does not match number of replicas') @@ -355,7 +355,7 @@ def _extract_flowed_energy_density(path: str, prefix: str, dtr_read: int, xmin: offset = configlist[-1][0] - 1 configlist[-1] = [item - offset for item in configlist[-1]] - if r_start[rep] is None: + if r_start[rep] == 0: r_start_index.append(0) else: try: @@ -364,7 +364,7 @@ def _extract_flowed_energy_density(path: str, prefix: str, dtr_read: int, xmin: raise Exception('Config %d not in file with range [%d, %d]' % ( r_start[rep], configlist[-1][0], configlist[-1][-1])) from None - if r_stop[rep] is None: + if r_stop[rep] == -1: r_stop_index.append(len(configlist[-1]) - 1) else: try: @@ -818,43 +818,36 @@ def _read_flow_obs(path: str, prefix: str, c: float, dtr_cnfg: int=1, version: s raise Exception("Unknown openQCD version.") if "steps" in kwargs: steps = kwargs.get("steps") + + postfix = kwargs.get("postfix", "") if version == "sfqcd": if "L" in kwargs: supposed_L = kwargs.get("L") else: supposed_L = None - postfix = "gfms" + if postfix == "": + postfix = "gfms" else: if "L" not in kwargs: raise Exception("This version of openQCD needs you to provide the spatial length of the lattice as parameter 'L'.") else: L = kwargs.get("L") - postfix = "ms" + if postfix == "": + postfix = "ms" - if "postfix" in kwargs: - postfix = kwargs.get("postfix") - - if "files" in kwargs: - known_files = kwargs.get("files") - else: - known_files = [] + known_files = kwargs.get("files", []) files = _find_files(path, prefix, postfix, "dat", known_files=known_files) + replica = len(files) - if 'r_start' in kwargs: - r_start = kwargs.get('r_start') - if len(r_start) != len(files): - raise Exception('r_start does not match number of replicas') - r_start = [o if o else None for o in r_start] - else: - r_start = [None] * len(files) + r_start = kwargs.get('r_start', [0] * replica) + if len(r_start) != replica: + raise ValueError('r_start does not match number of replicas') + + r_stop = kwargs.get('r_stop', [-1] * replica) + if len(r_stop) != replica: + raise ValueError('r_stop does not match number of replicas') - if 'r_stop' in kwargs: - r_stop = kwargs.get('r_stop') - if len(r_stop) != len(files): - raise Exception('r_stop does not match number of replicas') - else: - r_stop = [None] * len(files) rep_names = [] zeuthen = kwargs.get('Zeuthen_flow', False) @@ -960,7 +953,7 @@ def _read_flow_obs(path: str, prefix: str, c: float, dtr_cnfg: int=1, version: s offset, offset * steps)) configlist[-1] = [item - offset for item in configlist[-1]] - if r_start[rep] is None: + if r_start[rep] == 0: r_start_index.append(0) else: try: @@ -969,7 +962,7 @@ def _read_flow_obs(path: str, prefix: str, c: float, dtr_cnfg: int=1, version: s raise Exception('Config %d not in file with range [%d, %d]' % ( r_start[rep], configlist[-1][0], configlist[-1][-1])) from None - if r_stop[rep] is None: + if r_stop[rep] == -1: r_stop_index.append(len(configlist[-1]) - 1) else: try: @@ -1161,7 +1154,7 @@ def read_ms5_xsf(path: str, prefix: str, qc: str, corr: str, sep: str="r", **kwa # test if the input is correct if qc not in ['dd', 'ud', 'du', 'uu']: - raise Exception("Unknown quark conbination!") + raise Exception("Unknown quark combination!") if corr not in ["gS", "gP", "gA", "gV", "gVt", "lA", "lV", "lVt", "lT", "lTt", "g1", "l1"]: raise Exception("Unknown correlator!") From b8ee9eca5c779c3f98c1ab28c5b0c426476a9974 Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Mon, 3 Nov 2025 14:28:25 +0000 Subject: [PATCH 38/57] lint --- pyerrors/input/openQCD.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyerrors/input/openQCD.py b/pyerrors/input/openQCD.py index 7ea86c97..89235dda 100644 --- a/pyerrors/input/openQCD.py +++ b/pyerrors/input/openQCD.py @@ -10,7 +10,7 @@ from .misc import fit_t0 from .utils import sort_names from io import BufferedReader -from typing import Optional, Union, TypedDict +from typing import Optional, Union def read_rwms(path: str, prefix: str, version: str='2.0', names: Optional[list[str]]=None, **kwargs) -> list[Obs]: @@ -215,7 +215,7 @@ def read_rwms(path: str, prefix: str, version: str='2.0', names: Optional[list[s return result -def _extract_flowed_energy_density(path: str, prefix: str, dtr_read: int, xmin: int, spatial_extent: int, postfix: str='ms', **kwargs: rwms_kwargs) -> dict[float, Obs]: +def _extract_flowed_energy_density(path: str, prefix: str, dtr_read: int, xmin: int, spatial_extent: int, postfix: str='ms', **kwargs) -> dict[float, Obs]: """Extract a dictionary with the flowed Yang-Mills action density from given .ms.dat files. Returns a dictionary with Obs as values and flow times as keys. @@ -843,7 +843,7 @@ def _read_flow_obs(path: str, prefix: str, c: float, dtr_cnfg: int=1, version: s r_start = kwargs.get('r_start', [0] * replica) if len(r_start) != replica: raise ValueError('r_start does not match number of replicas') - + r_stop = kwargs.get('r_stop', [-1] * replica) if len(r_stop) != replica: raise ValueError('r_stop does not match number of replicas') From eb942601165f610738ca3f4ba7426326072ae9d1 Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Mon, 3 Nov 2025 14:37:10 +0000 Subject: [PATCH 39/57] allow all as tag --- pyerrors/obs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyerrors/obs.py b/pyerrors/obs.py index 21014bbb..45311257 100644 --- a/pyerrors/obs.py +++ b/pyerrors/obs.py @@ -156,7 +156,7 @@ def __init__(self, samples: list[Union[ndarray, list[Any]]], names: list[str], i self.ddvalue: float = 0.0 self.reweighted = False - self.tag = None + self.tag: Any = None @property def value(self) -> float: From 5bad13f91ef38fad7bd62ef9c3ce8f34d6c25dbc Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Mon, 3 Nov 2025 14:52:13 +0000 Subject: [PATCH 40/57] simplify read_flow_obs names handling --- pyerrors/input/openQCD.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/pyerrors/input/openQCD.py b/pyerrors/input/openQCD.py index 89235dda..3cc5204b 100644 --- a/pyerrors/input/openQCD.py +++ b/pyerrors/input/openQCD.py @@ -1000,7 +1000,8 @@ def _read_flow_obs(path: str, prefix: str, c: float, dtr_cnfg: int=1, version: s truncated_file = file[:-len(postfix)] - if "names" not in kwargs: + rep_names = kwargs.get("names", []) + if len(rep_names) == 0: try: idx = truncated_file.index('r') except Exception: @@ -1008,9 +1009,6 @@ def _read_flow_obs(path: str, prefix: str, c: float, dtr_cnfg: int=1, version: s raise Exception("Automatic recognition of replicum failed, please enter the key word 'names'.") ens_name = truncated_file[:idx] rep_names.append(ens_name + '|' + truncated_file[idx:].split(".")[0]) - else: - names = kwargs.get("names") - rep_names = names deltas.append(Q_top) @@ -1046,8 +1044,8 @@ def qtop_projection(qtop: Obs, target: int=0) -> Obs: for n in qtop.deltas: proj_qtop.append(np.array([1 if round(qtop.r_values[n] + q) == target else 0 for q in qtop.deltas[n]])) - reto = Obs(proj_qtop, qtop.names, idl=[qtop.idl[name] for name in qtop.names]) - return reto + qtop_projected = Obs(proj_qtop, qtop.names, idl=[qtop.idl[name] for name in qtop.names]) + return qtop_projected def read_qtop_sector(path: str, prefix: str, c: float, target: int=0, **kwargs) -> Obs: From e2649b30746b2a47493ddba36468e989a158e108 Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Mon, 3 Nov 2025 15:42:39 +0000 Subject: [PATCH 41/57] small renamings --- pyerrors/input/openQCD.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pyerrors/input/openQCD.py b/pyerrors/input/openQCD.py index 3cc5204b..760e0be1 100644 --- a/pyerrors/input/openQCD.py +++ b/pyerrors/input/openQCD.py @@ -101,7 +101,7 @@ def read_rwms(path: str, prefix: str, version: str='2.0', names: Optional[list[s r_stop_index = [] for rep in range(replica): - tmp_array: list[list] = [] + tmp_array: list[list[float]] = [] with open(path + '/' + ls[rep], 'rb') as fp: t = fp.read(4) # number of reweighting factors @@ -162,8 +162,8 @@ def read_rwms(path: str, prefix: str, version: str='2.0', names: Optional[list[s for j in range(nfct[i]): t = fp.read(8 * nsrc[i]) t = fp.read(8 * nsrc[i]) - tmp_rw: list[float] = struct.unpack('d' * nsrc[i], t) - tmp_nfct *= np.mean(np.exp(-np.asarray(tmp_rw))) + tmp_rw_unpacked = struct.unpack('d' * nsrc[i], t) + tmp_nfct *= np.mean(np.exp(-np.asarray(tmp_rw_unpacked))) if print_err: print(config_no, i, j, np.mean(np.exp(-np.asarray(tmp_rw))), @@ -383,9 +383,9 @@ def _extract_flowed_energy_density(path: str, prefix: str, dtr_read: int, xmin: E_dict = {} for n in range(nn + 1): samples: list[list[float]] = [] - for nrep, rep in enumerate(Ysum): + for nrep, rep_data in enumerate(Ysum): samples.append([]) - for cnfg in rep: + for cnfg in rep_data: samples[-1].append(cnfg[n]) samples[-1] = samples[-1][r_start_index[nrep]:r_stop_index[nrep] + 1][::r_step] new_obs = Obs(samples, rep_names, idl=idl) From 4f09fc7d1fb737cbc7cc4a1745c9a2c4c483a522 Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Mon, 3 Nov 2025 15:51:07 +0000 Subject: [PATCH 42/57] better steps from kwargs in read flow obs --- pyerrors/input/openQCD.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyerrors/input/openQCD.py b/pyerrors/input/openQCD.py index 760e0be1..9f72f948 100644 --- a/pyerrors/input/openQCD.py +++ b/pyerrors/input/openQCD.py @@ -816,8 +816,8 @@ def _read_flow_obs(path: str, prefix: str, c: float, dtr_cnfg: int=1, version: s if version not in known_versions: raise Exception("Unknown openQCD version.") - if "steps" in kwargs: - steps = kwargs.get("steps") + + steps = kwargs.get("steps", 0) postfix = kwargs.get("postfix", "") if version == "sfqcd": @@ -940,12 +940,12 @@ def _read_flow_obs(path: str, prefix: str, c: float, dtr_cnfg: int=1, version: s if len(np.unique(np.diff(traj_list))) != 1: raise Exception("Irregularities in stepsize found") else: - if 'steps' in kwargs: + if steps == 0: + steps = traj_list[1] - traj_list[0] + else: if steps != traj_list[1] - traj_list[0]: raise Exception("steps and the found stepsize are not the same") - else: - steps = traj_list[1] - traj_list[0] - + configlist.append([tr // steps // dtr_cnfg for tr in traj_list]) if configlist[-1][0] > 1: offset = configlist[-1][0] - 1 From 1e00caf3c11a1543e0f82ae59e912b27a825244d Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Mon, 3 Nov 2025 16:04:59 +0000 Subject: [PATCH 43/57] small switch of lines --- pyerrors/input/misc.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pyerrors/input/misc.py b/pyerrors/input/misc.py index 9aced78e..15fec0df 100644 --- a/pyerrors/input/misc.py +++ b/pyerrors/input/misc.py @@ -60,7 +60,10 @@ def fit_t0(t2E_dict: dict[float, Obs], fit_range: int, plot_fit: Optional[bool]= fit_result = fit_lin(x, y) + retval = (-fit_result[0] / fit_result[1]) + if plot_fit is True: + retval.gamma_method() plt.figure() gs = gridspec.GridSpec(2, 1, height_ratios=[3, 1], wspace=0.0, hspace=0.0) ax0 = plt.subplot(gs[0]) @@ -72,8 +75,6 @@ def fit_t0(t2E_dict: dict[float, Obs], fit_range: int, plot_fit: Optional[bool]= yplot = [fit_result[0] + fit_result[1] * xi for xi in xplot] [yi.gamma_method() for yi in yplot] ax0.fill_between(xplot, y1=[yi.value - yi.dvalue for yi in yplot], y2=[yi.value + yi.dvalue for yi in yplot]) - retval = (-fit_result[0] / fit_result[1]) - retval.gamma_method() ylim = ax0.get_ylim() ax0.fill_betweenx(ylim, x1=retval.value - retval.dvalue, x2=retval.value + retval.dvalue, color='gray', alpha=0.4) ax0.set_ylim(ylim) @@ -96,7 +97,7 @@ def fit_t0(t2E_dict: dict[float, Obs], fit_range: int, plot_fit: Optional[bool]= ax1.set_xlabel(r'$t/a^2$') plt.draw() - return -fit_result[0] / fit_result[1] + return retval def read_pbp(path: str, prefix: str, **kwargs): From 4f8567a172b7577fb1198090b2df056f8c66b099 Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Mon, 3 Nov 2025 16:07:07 +0000 Subject: [PATCH 44/57] lint --- pyerrors/input/openQCD.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyerrors/input/openQCD.py b/pyerrors/input/openQCD.py index 9f72f948..a274514c 100644 --- a/pyerrors/input/openQCD.py +++ b/pyerrors/input/openQCD.py @@ -945,7 +945,7 @@ def _read_flow_obs(path: str, prefix: str, c: float, dtr_cnfg: int=1, version: s else: if steps != traj_list[1] - traj_list[0]: raise Exception("steps and the found stepsize are not the same") - + configlist.append([tr // steps // dtr_cnfg for tr in traj_list]) if configlist[-1][0] > 1: offset = configlist[-1][0] - 1 From fb7ba0cbc3c0d293147bbcca54fcd7c5d6505b1f Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Mon, 3 Nov 2025 16:17:57 +0000 Subject: [PATCH 45/57] simplify r_start and r_stop in read_pbp --- pyerrors/input/misc.py | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/pyerrors/input/misc.py b/pyerrors/input/misc.py index 15fec0df..12a7451f 100644 --- a/pyerrors/input/misc.py +++ b/pyerrors/input/misc.py @@ -136,21 +136,15 @@ def read_pbp(path: str, prefix: str, **kwargs): ls.sort(key=lambda x: int(re.findall(r'\d+', x[len(prefix):])[0])) replica = len(ls) - if 'r_start' in kwargs: - r_start = kwargs.get('r_start') - if len(r_start) != replica: - raise Exception('r_start does not match number of replicas') - # Adjust Configuration numbering to python index - r_start = [o - 1 if o else None for o in r_start] - else: - r_start = [None] * replica - - if 'r_stop' in kwargs: - r_stop = kwargs.get('r_stop') - if len(r_stop) != replica: - raise Exception('r_stop does not match number of replicas') - else: - r_stop = [None] * replica + r_start = kwargs.get('r_start', [1] * replica) + if len(r_start) != replica: + raise Exception('r_start does not match number of replicas') + # Adjust Configuration numbering to python index + r_start = [o - 1 if o else None for o in r_start] + + r_stop = kwargs.get('r_stop', [-1] * replica) + if len(r_stop) != replica: + raise Exception('r_stop does not match number of replicas') print(r'Read from', prefix[:-1], ',', replica, 'replica', end='') @@ -159,10 +153,10 @@ def read_pbp(path: str, prefix: str, **kwargs): print_err = 1 print() - deltas = [] + deltas: list[list[float]] = [] for rep in range(replica): - tmp_array = [] + tmp_array: list[list[float]] = [] with open(path + '/' + ls[rep], 'rb') as fp: t = fp.read(4) # number of reweighting factors From dddaaa48980cdb6e746e760ded824ee76d2b4724 Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Mon, 3 Nov 2025 16:34:02 +0000 Subject: [PATCH 46/57] renameing in read_hd5 for less confisuion --- pyerrors/input/hadrons.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/pyerrors/input/hadrons.py b/pyerrors/input/hadrons.py index 525f564a..f7ccdb1c 100644 --- a/pyerrors/input/hadrons.py +++ b/pyerrors/input/hadrons.py @@ -7,9 +7,10 @@ from ..correlators import Corr from ..dirac import epsilon_tensor_rank4 from .misc import fit_t0 +from typing import Union, Optional -def _get_files(path, filestem, idl): +def _get_files(path: str, filestem: str, idl: list[int]): ls = os.listdir(path) # Clean up file list @@ -45,7 +46,7 @@ def get_cnfg_number(n): if np.any(dc < 0): raise Exception("Unsorted files") if len(dc) == 1: - idx = range(cnfg_numbers[0], cnfg_numbers[-1] + dc[0], dc[0]) + idx = list(range(cnfg_numbers[0], cnfg_numbers[-1] + dc[0], dc[0])) elif idl: idx = idl else: @@ -54,7 +55,7 @@ def get_cnfg_number(n): return filtered_files, idx -def read_hd5(filestem, ens_id, group, attrs=None, idl=None, part="real"): +def read_hd5(filestem: str, ens_id: str, group: str, attrs: Optional[Union[dict, int]]=None, idl=None, part="real") -> Corr: r'''Read hadrons hdf5 file and extract entry based on attributes. Parameters @@ -122,20 +123,21 @@ def read_hd5(filestem, ens_id, group, attrs=None, idl=None, part="real"): for k, i in h5file[group + '/' + entry].attrs.items(): infos.append(k + ': ' + i[0].decode()) h5file.close() - corr_data = np.array(corr_data) + corr_data_array = np.array(corr_data) if part == "complex": l_obs = [] - for c in corr_data.T: + for c in corr_data_array.T: l_obs.append(CObs(Obs([c.real], [ens_id], idl=[idx]), Obs([c.imag], [ens_id], idl=[idx]))) + corr = Corr(l_obs) else: corr_data = getattr(corr_data, part) - l_obs = [] - for c in corr_data.T: - l_obs.append(Obs([c], [ens_id], idl=[idx])) - - corr = Corr(l_obs) + p_obs = [] + for c in corr_data_array.T: + p_obs.append(Obs([c], [ens_id], idl=[idx])) + corr = Corr(p_obs) + corr.tag = r", ".join(infos) return corr From 53192d31a973a85a60c81fadff0cd22a2e364c4a Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Mon, 3 Nov 2025 16:43:44 +0000 Subject: [PATCH 47/57] typehints for hadron read functions --- pyerrors/input/hadrons.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pyerrors/input/hadrons.py b/pyerrors/input/hadrons.py index f7ccdb1c..a5bfeef8 100644 --- a/pyerrors/input/hadrons.py +++ b/pyerrors/input/hadrons.py @@ -142,7 +142,7 @@ def read_hd5(filestem: str, ens_id: str, group: str, attrs: Optional[Union[dict, return corr -def read_meson_hd5(path, filestem, ens_id, meson='meson_0', idl=None, gammas=None): +def read_meson_hd5(path: str, filestem: str, ens_id: str, meson: str='meson_0', idl: Optional[range]=None, gammas: Optional[tuple[str, ...]]=None): r'''Read hadrons meson hdf5 file and extract the meson labeled 'meson' Parameters @@ -156,7 +156,7 @@ def read_meson_hd5(path, filestem, ens_id, meson='meson_0', idl=None, gammas=Non meson : str label of the meson to be extracted, standard value meson_0 which corresponds to the pseudoscalar pseudoscalar two-point function. - gammas : tuple of strings + gammas : tuple[str] Instrad of a meson label one can also provide a tuple of two strings indicating the gamma matrices at sink and source (gamma_snk, gamma_src). ("Gamma5", "Gamma5") corresponds to the pseudoscalar pseudoscalar @@ -181,7 +181,7 @@ def read_meson_hd5(path, filestem, ens_id, meson='meson_0', idl=None, gammas=Non part="real") -def _extract_real_arrays(path, files, tree, keys): +def _extract_real_arrays(path: str, files: list[str], tree: str, keys: list[str]): corr_data = {} for key in keys: corr_data[key] = [] @@ -199,7 +199,7 @@ def _extract_real_arrays(path, files, tree, keys): return corr_data -def extract_t0_hd5(path, filestem, ens_id, obs='Clover energy density', fit_range=5, idl=None, **kwargs): +def extract_t0_hd5(path: str, filestem: str, ens_id: str, obs='Clover energy density', fit_range: int=5, idl: Optional[range]=None, **kwargs): r'''Read hadrons FlowObservables hdf5 file and extract t0 Parameters @@ -247,7 +247,7 @@ def extract_t0_hd5(path, filestem, ens_id, obs='Clover energy density', fit_rang return fit_t0(t2E_dict, fit_range, plot_fit=kwargs.get('plot_fit')) -def read_DistillationContraction_hd5(path, ens_id, diagrams=["direct"], idl=None): +def read_DistillationContraction_hd5(path: str, ens_id: str, diagrams: list[str]=["direct"], idl: Optional[range]=None): """Read hadrons DistillationContraction hdf5 files in given directory structure Parameters @@ -256,7 +256,7 @@ def read_DistillationContraction_hd5(path, ens_id, diagrams=["direct"], idl=None path to the directories to read ens_id : str name of the ensemble, required for internal bookkeeping - diagrams : list + diagrams : list[str] List of strings of the diagrams to extract, e.g. ["direct", "box", "cross"]. idl : range If specified only configurations in the given range are read in. @@ -384,7 +384,7 @@ def __array_finalize__(self, obj): self.mom_out = getattr(obj, 'mom_out', None) -def read_ExternalLeg_hd5(path, filestem, ens_id, idl=None): +def read_ExternalLeg_hd5(path: str, filestem: str, ens_id: str, idl: Optional[range]=None): """Read hadrons ExternalLeg hdf5 file and output an array of CObs Parameters @@ -429,7 +429,7 @@ def read_ExternalLeg_hd5(path, filestem, ens_id, idl=None): return Npr_matrix(matrix, mom_in=mom) -def read_Bilinear_hd5(path, filestem, ens_id, idl=None): +def read_Bilinear_hd5(path: str, filestem: str, ens_id: str, idl: Optional[range]=None): """Read hadrons Bilinear hdf5 file and output an array of CObs Parameters @@ -488,7 +488,7 @@ def read_Bilinear_hd5(path, filestem, ens_id, idl=None): return result_dict -def read_Fourquark_hd5(path, filestem, ens_id, idl=None, vertices=["VA", "AV"]): +def read_Fourquark_hd5(path: str, filestem: str, ens_id: str, idl: Optional[range]=None, vertices: list[str]=["VA", "AV"]): """Read hadrons FourquarkFullyConnected hdf5 file and output an array of CObs Parameters @@ -574,7 +574,7 @@ def read_Fourquark_hd5(path, filestem, ens_id, idl=None, vertices=["VA", "AV"]): return result_dict -def _get_lorentz_names(name): +def _get_lorentz_names(name: str): lorentz_index = ['X', 'Y', 'Z', 'T'] res = [] From 982789907c871bc77bdb2ee1f6f408e4d97985a9 Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Mon, 3 Nov 2025 16:46:26 +0000 Subject: [PATCH 48/57] lint --- pyerrors/input/hadrons.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyerrors/input/hadrons.py b/pyerrors/input/hadrons.py index a5bfeef8..af493d9e 100644 --- a/pyerrors/input/hadrons.py +++ b/pyerrors/input/hadrons.py @@ -137,7 +137,7 @@ def read_hd5(filestem: str, ens_id: str, group: str, attrs: Optional[Union[dict, for c in corr_data_array.T: p_obs.append(Obs([c], [ens_id], idl=[idx])) corr = Corr(p_obs) - + corr.tag = r", ".join(infos) return corr From 38692d23434a7ebe5ac581fd55a8328bdf818da7 Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Mon, 3 Nov 2025 17:09:14 +0000 Subject: [PATCH 49/57] add typehints for bdio --- pyerrors/input/bdio.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/pyerrors/input/bdio.py b/pyerrors/input/bdio.py index 11da18e8..37da1a47 100644 --- a/pyerrors/input/bdio.py +++ b/pyerrors/input/bdio.py @@ -4,7 +4,7 @@ from ..obs import Obs -def read_ADerrors(file_path, bdio_path='./libbdio.so', **kwargs): +def read_ADerrors(file_path: str, bdio_path: str='./libbdio.so'): """ Extract generic MCMC data from a bdio file read_ADerrors requires bdio to be compiled into a shared library. This can be achieved by @@ -160,7 +160,7 @@ def read_c_size_t(): return return_list -def write_ADerrors(obs_list, file_path, bdio_path='./libbdio.so', **kwargs): +def write_ADerrors(obs_list: list, file_path: str, bdio_path: str='./libbdio.so'): """ Write Obs to a bdio file according to ADerrors conventions read_mesons requires bdio to be compiled into a shared library. This can be achieved by @@ -172,8 +172,10 @@ def write_ADerrors(obs_list, file_path, bdio_path='./libbdio.so', **kwargs): Parameters ---------- - file_path -- path to the bdio file - bdio_path -- path to the shared bdio library libbdio.so (default ./libbdio.so) + file_path : str + path to the bdio file + bdio_path : str + path to the shared bdio library libbdio.so (default ./libbdio.so) Returns ------- @@ -290,15 +292,15 @@ def write_c_size_t(int32): return 0 -def _get_kwd(string, key): +def _get_kwd(string: str, key: str): return (string.split(key, 1)[1]).split(" ", 1)[0] -def _get_corr_name(string, key): +def _get_corr_name(string: str, key: str): return (string.split(key, 1)[1]).split(' NDIM=', 1)[0] -def read_mesons(file_path, bdio_path='./libbdio.so', **kwargs): +def read_mesons(file_path: str, bdio_path: str='./libbdio.so', **kwargs): """ Extract mesons data from a bdio file and return it as a dictionary The dictionary can be accessed with a tuple consisting of (type, source_position, kappa1, kappa2) @@ -513,7 +515,7 @@ def read_mesons(file_path, bdio_path='./libbdio.so', **kwargs): return result -def read_dSdm(file_path, bdio_path='./libbdio.so', **kwargs): +def read_dSdm(file_path: str, bdio_path: str='./libbdio.so', **kwargs): """ Extract dSdm data from a bdio file and return it as a dictionary The dictionary can be accessed with a tuple consisting of (type, kappa) From dcf6a1f8ad1745fe1d0525598a54f7ca8b9b9147 Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Tue, 4 Nov 2025 08:05:11 +0000 Subject: [PATCH 50/57] being a bit more concrete with literals --- pyerrors/correlators.py | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/pyerrors/correlators.py b/pyerrors/correlators.py index d14166a3..27282986 100644 --- a/pyerrors/correlators.py +++ b/pyerrors/correlators.py @@ -10,8 +10,9 @@ from .fits import least_squares, Fit_result from .roots import find_root from . import linalg +from .input.json import dump_to_json from numpy import ndarray, ufunc -from typing import Any, Callable, Optional, Union +from typing import Any, Callable, Optional, Union, Literal class Corr: @@ -45,7 +46,7 @@ class Corr: __slots__ = ["content", "N", "T", "tag", "prange"] - def __init__(self, data_input: list[Obs, CObs], padding: list[int]=[0, 0], prange: Optional[list[int]]=None): + def __init__(self, data_input: Union[list[Obs, CObs], list[ndarray[ndarray[Obs, CObs]]], ndarray[ndarray[Corr]]], padding: list[int]=[0, 0], prange: Optional[list[int]]=None): """ Initialize a Corr object. Parameters @@ -303,7 +304,7 @@ def matrix_symmetric(self) -> "Corr": transposed = [None if _check_for_none(self, G) else G.T for G in self.content] return 0.5 * (Corr(transposed) + self) - def GEVP(self, t0: int, ts: Optional[int]=None, sort: Optional[str]="Eigenvalue", vector_obs: bool=False, **kwargs) -> Union[list[list[Optional[ndarray]]], ndarray, list[Optional[ndarray]]]: + def GEVP(self, t0: int, ts: Optional[int]=None, sort: Optional[Literal["Eigenvalue", "Eigenvector"]]="Eigenvalue", vector_obs: bool=False, **kwargs) -> Union[list[list[Optional[ndarray]]], ndarray, list[Optional[ndarray]]]: r'''Solve the generalized eigenvalue problem on the correlator matrix and returns the corresponding eigenvectors. The eigenvectors are sorted according to the descending eigenvalues, the zeroth eigenvector(s) correspond to the @@ -409,7 +410,7 @@ def _get_mat_at_t(t, vector_obs=vector_obs): else: return reordered_vecs - def Eigenvalue(self, t0: int, ts: None=None, state: int=0, sort: str="Eigenvalue", **kwargs) -> "Corr": + def Eigenvalue(self, t0: int, ts: Optional[int]=None, state: int=0, sort: Optional[Literal["Eigenvalue", "Eigenvector"]]="Eigenvalue", **kwargs) -> "Corr": """Determines the eigenvalue of the GEVP by solving and projecting the correlator Parameters @@ -495,7 +496,7 @@ def thin(self, spacing: int=2, offset: int=0) -> "Corr": new_content.append(self.content[t]) return Corr(new_content) - def correlate(self, partner: Union[Corr, float, Obs]) -> "Corr": + def correlate(self, partner: Union[Corr, Obs]) -> "Corr": """Correlate the correlator with another correlator or Obs Parameters @@ -577,14 +578,14 @@ def T_symmetry(self, partner: "Corr", parity: int=+1) -> "Corr": return (self + T_partner) / 2 - def deriv(self, variant: Optional[str]="symmetric") -> "Corr": + def deriv(self, variant: Literal["symmetric", "forward", "backward", "improved", "log"]="symmetric") -> "Corr": """Return the first derivative of the correlator with respect to x0. Parameters ---------- variant : str decides which definition of the finite differences derivative is used. - Available choice: symmetric, forward, backward, improved, log, default: symmetric + Available choices: symmetric, forward, backward, improved, log, default: symmetric """ if self.N != 1: raise ValueError("deriv only implemented for one-dimensional correlators.") @@ -638,7 +639,7 @@ def deriv(self, variant: Optional[str]="symmetric") -> "Corr": else: raise ValueError("Unknown variant.") - def second_deriv(self, variant: Optional[str]="symmetric") -> "Corr": + def second_deriv(self, variant: Literal["symmetric", "big_symmetric", "improved", "log"]="symmetric") -> "Corr": r"""Return the second derivative of the correlator with respect to x0. Parameters @@ -698,7 +699,7 @@ def second_deriv(self, variant: Optional[str]="symmetric") -> "Corr": else: raise ValueError("Unknown variant.") - def m_eff(self, variant: str='log', guess: float=1.0) -> "Corr": + def m_eff(self, variant: Literal["log", "cosh", "periodic", "sinh", "arccosh", "logsym"]='log', guess: float=1.0) -> "Corr": """Returns the effective mass of the correlator as correlator object Parameters @@ -813,7 +814,7 @@ def fit(self, function: Callable, fitrange: Optional[list[int]]=None, silent: bo result = least_squares(xs, ys, function, silent=silent, **kwargs) return result - def plateau(self, plateau_range: Optional[list[int]]=None, method: str="fit", auto_gamma: bool=False) -> Obs: + def plateau(self, plateau_range: Optional[list[int]]=None, method: Literal['fit', 'avg']="fit", auto_gamma: bool=False) -> Obs: """ Extract a plateau value from a Corr object Parameters @@ -862,7 +863,7 @@ def set_prange(self, prange: list[int]): self.prange = prange return - def show(self, x_range: Optional[list[int]]=None, comp: Optional[Corr]=None, y_range: Optional[list[int, float]]=None, logscale: bool=False, plateau: Optional[Obs, float, int]=None, fit_res: Optional[Fit_result]=None, fit_key: Optional[str]=None, ylabel: Optional[str]=None, save: Optional[str]=None, auto_gamma: bool=False, hide_sigma: Optional[int, float]=None, references: Optional[list[float]]=None, title: Optional[str]=None): + def show(self, x_range: Optional[list[int]]=None, comp: Optional[Corr]=None, y_range: Optional[list[int, float]]=None, logscale: bool=False, plateau: Union[Obs, float, int, None]=None, fit_res: Optional[Fit_result]=None, fit_key: Optional[str]=None, ylabel: Optional[str]=None, save: Optional[str]=None, auto_gamma: bool=False, hide_sigma: Union[int, float, None]=None, references: Optional[list[float]]=None, title: Optional[str]=None): """Plots the correlator using the tag of the correlator as label if available. Parameters @@ -1029,11 +1030,8 @@ def dump(self, filename: str, datatype: str="json.gz", **kwargs): specifies a custom path for the file (default '.') """ if datatype == "json.gz": - from .input.json import dump_to_json - if 'path' in kwargs: - file_name = kwargs.get('path') + '/' + filename - else: - file_name = filename + path = kwargs.get("path", ".") + file_name = path + '/' + filename dump_to_json(self, file_name) elif datatype == "pickle": dump_object(self, filename, **kwargs) @@ -1078,7 +1076,7 @@ def __str__(self) -> str: __array_priority__ = 10000 - def __eq__(self, y: Any) -> ndarray: + def __eq__(self, y: Any) -> ndarray[bool, None]: if isinstance(y, Corr): comp = np.asarray(y.content, dtype=object) else: From dd6a2dc6a47107866636726b4c5a14dd785b67d6 Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Tue, 4 Nov 2025 08:28:09 +0000 Subject: [PATCH 51/57] cast epsilon tensors to int --- pyerrors/dirac.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyerrors/dirac.py b/pyerrors/dirac.py index 9d4244aa..6fa3e15c 100644 --- a/pyerrors/dirac.py +++ b/pyerrors/dirac.py @@ -24,7 +24,7 @@ dtype=complex) -def epsilon_tensor(i: int, j: int, k: int) -> float: +def epsilon_tensor(i: int, j: int, k: int) -> int: """Rank-3 epsilon tensor Based on https://codegolf.stackexchange.com/a/160375 @@ -38,10 +38,10 @@ def epsilon_tensor(i: int, j: int, k: int) -> float: if not (test_set <= set((1, 2, 3)) or test_set <= set((0, 1, 2))): raise ValueError("Unexpected input", i, j, k) - return (i - j) * (j - k) * (k - i) / 2 + return int((i - j) * (j - k) * (k - i) / 2) -def epsilon_tensor_rank4(i: int, j: int, k: int, o: int) -> float: +def epsilon_tensor_rank4(i: int, j: int, k: int, o: int) -> int: """Rank-4 epsilon tensor Extension of https://codegolf.stackexchange.com/a/160375 @@ -56,7 +56,7 @@ def epsilon_tensor_rank4(i: int, j: int, k: int, o: int) -> float: if not (test_set <= set((1, 2, 3, 4)) or test_set <= set((0, 1, 2, 3))): raise ValueError("Unexpected input", i, j, k, o) - return (i - j) * (j - k) * (k - i) * (i - o) * (j - o) * (o - k) / 12 + return int((i - j) * (j - k) * (k - i) * (i - o) * (j - o) * (o - k) / 12) def Grid_gamma(gamma_tag: str) -> ndarray: From e116e742575cbd7dd3722dcdf1a9f989636eb975 Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Tue, 4 Nov 2025 09:03:53 +0000 Subject: [PATCH 52/57] undo import change --- pyerrors/correlators.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyerrors/correlators.py b/pyerrors/correlators.py index 27282986..1437cb97 100644 --- a/pyerrors/correlators.py +++ b/pyerrors/correlators.py @@ -10,7 +10,6 @@ from .fits import least_squares, Fit_result from .roots import find_root from . import linalg -from .input.json import dump_to_json from numpy import ndarray, ufunc from typing import Any, Callable, Optional, Union, Literal @@ -46,7 +45,7 @@ class Corr: __slots__ = ["content", "N", "T", "tag", "prange"] - def __init__(self, data_input: Union[list[Obs, CObs], list[ndarray[ndarray[Obs, CObs]]], ndarray[ndarray[Corr]]], padding: list[int]=[0, 0], prange: Optional[list[int]]=None): + def __init__(self, data_input: Union[list[Obs, CObs], list[ndarray[ndarray[Obs, CObs]]], ndarray[ndarray]], padding: list[int]=[0, 0], prange: Optional[list[int]]=None): """ Initialize a Corr object. Parameters @@ -1030,6 +1029,7 @@ def dump(self, filename: str, datatype: str="json.gz", **kwargs): specifies a custom path for the file (default '.') """ if datatype == "json.gz": + from .input.json import dump_to_json path = kwargs.get("path", ".") file_name = path + '/' + filename dump_to_json(self, file_name) From c9cef8ff5f5b2da39807a761ecf8b30c9964a09f Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Tue, 4 Nov 2025 09:04:39 +0000 Subject: [PATCH 53/57] add some typecasts --- pyerrors/fits.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/pyerrors/fits.py b/pyerrors/fits.py index a33c8091..00a9510d 100644 --- a/pyerrors/fits.py +++ b/pyerrors/fits.py @@ -776,7 +776,7 @@ def fit_lin(x: Sequence[Union[Obs, int, float]], y: Sequence[Obs], **kwargs) -> Returns ------- fit_parameters : list[Obs] - LIist of fitted observables. + List of fitted observables. """ def f(a, x): @@ -879,7 +879,10 @@ def error_band(x: list[int], func: Callable, beta: Union[Fit_result, list[Obs]]) err : np.array(Obs) Error band for an array of sample values x """ - cov = covariance(beta) + if isinstance(beta, Fit_result): + cov = covariance(np.array(beta.fit_parameters)) + else: + cov = covariance(beta) if np.any(np.abs(cov - cov.T) > 1000 * np.finfo(np.float64).eps): warnings.warn("Covariance matrix is not symmetric within floating point precision", RuntimeWarning) @@ -890,9 +893,9 @@ def error_band(x: list[int], func: Callable, beta: Union[Fit_result, list[Obs]]) err = [] for i, item in enumerate(x): err.append(np.sqrt(deriv[i] @ cov @ deriv[i])) - err = np.array(err) + err_array = np.array(err) - return err + return err_array def ks_test(objects: Optional[list[Fit_result]]=None): @@ -916,7 +919,7 @@ def ks_test(objects: Optional[list[Fit_result]]=None): else: obs_list = objects - p_values = [o.p_value for o in obs_list] + p_values = np.asarray([o.p_value for o in obs_list]) bins = len(p_values) x = np.arange(0, 1.001, 0.001) @@ -928,7 +931,7 @@ def ks_test(objects: Optional[list[Fit_result]]=None): plt.title(str(bins) + ' p-values') n = np.arange(1, bins + 1) / np.float64(bins) - Xs = np.sort(p_values) + Xs: ndarray[float] = np.sort(p_values) plt.step(Xs, n) diffs = n - Xs loc_max_diff = np.argmax(np.abs(diffs)) From 898551d3f981c041a2fc0c0e1a347f3a941a420f Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Tue, 4 Nov 2025 09:18:30 +0000 Subject: [PATCH 54/57] rename some internal functions --- pyerrors/linalg.py | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/pyerrors/linalg.py b/pyerrors/linalg.py index cb0b3119..1393ae3e 100644 --- a/pyerrors/linalg.py +++ b/pyerrors/linalg.py @@ -3,7 +3,7 @@ import autograd.numpy as anp # Thinly-wrapped numpy from .obs import derived_observable, CObs, Obs, import_jackknife from numpy import ndarray -from typing import Callable, Union +from typing import Callable, Union, Literal def matmul(*operands) -> ndarray: @@ -24,7 +24,7 @@ def matmul(*operands) -> ndarray: extended_operands.append(tmp[0]) extended_operands.append(tmp[1]) - def multi_dot(operands, part): + def multi_dot(operands, part: Literal["Real", "Imag"]): stack_r = operands[0] stack_i = operands[1] for op_r, op_i in zip(operands[2::2], operands[3::2]): @@ -55,7 +55,7 @@ def multi_dot_i(operands): return res else: - def multi_dot(operands): + def multi_dot(operands, part: Literal["Real", "Imag"]): stack = operands[0] for op in operands[1:]: stack = stack @ op @@ -75,25 +75,25 @@ def jack_matmul(*operands) -> ndarray: For large matrices this is considerably faster compared to matmul. """ - def _exp_to_jack(matrix): + def _export_to_jack(matrix): base_matrix = np.empty_like(matrix) for index, entry in np.ndenumerate(matrix): base_matrix[index] = entry.export_jackknife() return base_matrix - def _imp_from_jack(matrix, name, idl): + def _import_from_jack(matrix, name, idl): base_matrix = np.empty_like(matrix) for index, entry in np.ndenumerate(matrix): base_matrix[index] = import_jackknife(entry, name, [idl]) return base_matrix - def _exp_to_jack_c(matrix): + def _export_to_jack_c(matrix): base_matrix = np.empty_like(matrix) for index, entry in np.ndenumerate(matrix): base_matrix[index] = entry.real.export_jackknife() + 1j * entry.imag.export_jackknife() return base_matrix - def _imp_from_jack_c(matrix, name, idl): + def _import_from_jack_c(matrix, name, idl): base_matrix = np.empty_like(matrix) for index, entry in np.ndenumerate(matrix): base_matrix[index] = CObs(import_jackknife(entry.real, name, [idl]), @@ -104,24 +104,24 @@ def _imp_from_jack_c(matrix, name, idl): name = operands[0].flat[0].real.names[0] idl = operands[0].flat[0].real.idl[name] - r = _exp_to_jack_c(operands[0]) + r = _export_to_jack_c(operands[0]) for op in operands[1:]: if isinstance(op.flat[0], CObs): - r = r @ _exp_to_jack_c(op) + r = r @ _export_to_jack_c(op) else: r = r @ op - return _imp_from_jack_c(r, name, idl) + return _import_from_jack_c(r, name, idl) else: name = operands[0].flat[0].names[0] idl = operands[0].flat[0].idl[name] - r = _exp_to_jack(operands[0]) + r = _export_to_jack(operands[0]) for op in operands[1:]: if isinstance(op.flat[0], Obs): - r = r @ _exp_to_jack(op) + r = r @ _export_to_jack(op) else: r = r @ op - return _imp_from_jack(r, name, idl) + return _import_from_jack(r, name, idl) def einsum(subscripts: str, *operands) -> Union[CObs, Obs, ndarray]: @@ -136,25 +136,25 @@ def einsum(subscripts: str, *operands) -> Union[CObs, Obs, ndarray]: Obs valued. """ - def _exp_to_jack(matrix): + def _export_to_jack(matrix): base_matrix = [] for index, entry in np.ndenumerate(matrix): base_matrix.append(entry.export_jackknife()) return np.asarray(base_matrix).reshape(matrix.shape + base_matrix[0].shape) - def _exp_to_jack_c(matrix): + def _export_to_jack_c(matrix): base_matrix = [] for index, entry in np.ndenumerate(matrix): base_matrix.append(entry.real.export_jackknife() + 1j * entry.imag.export_jackknife()) return np.asarray(base_matrix).reshape(matrix.shape + base_matrix[0].shape) - def _imp_from_jack(matrix, name, idl): + def _import_from_jack(matrix, name, idl): base_matrix = np.empty(shape=matrix.shape[:-1], dtype=object) for index in np.ndindex(matrix.shape[:-1]): base_matrix[index] = import_jackknife(matrix[index], name, [idl]) return base_matrix - def _imp_from_jack_c(matrix, name, idl): + def _import_from_jack_c(matrix, name, idl): base_matrix = np.empty(shape=matrix.shape[:-1], dtype=object) for index in np.ndindex(matrix.shape[:-1]): base_matrix[index] = CObs(import_jackknife(matrix[index].real, name, [idl]), @@ -174,9 +174,9 @@ def _imp_from_jack_c(matrix, name, idl): conv_operands = [] for op in operands: if isinstance(op.flat[0], CObs): - conv_operands.append(_exp_to_jack_c(op)) + conv_operands.append(_export_to_jack_c(op)) elif isinstance(op.flat[0], Obs): - conv_operands.append(_exp_to_jack(op)) + conv_operands.append(_export_to_jack(op)) else: conv_operands.append(op) @@ -186,9 +186,9 @@ def _imp_from_jack_c(matrix, name, idl): jack_einsum = np.einsum(extended_subscripts, *conv_operands, optimize=einsum_path) if jack_einsum.dtype == complex: - result = _imp_from_jack_c(jack_einsum, name, idl) + result = _import_from_jack_c(jack_einsum, name, idl) elif jack_einsum.dtype == float: - result = _imp_from_jack(jack_einsum, name, idl) + result = _import_from_jack(jack_einsum, name, idl) else: raise Exception("Result has unexpected datatype") From 38df83344ba3496364e6a5246f88093294d50440 Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Tue, 4 Nov 2025 09:36:10 +0000 Subject: [PATCH 55/57] small renaming for type consistency --- pyerrors/mpm.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyerrors/mpm.py b/pyerrors/mpm.py index 7a85ace3..dd3a1bc4 100644 --- a/pyerrors/mpm.py +++ b/pyerrors/mpm.py @@ -52,10 +52,10 @@ def matrix_pencil_method(corrs: list[Obs], k: int=1, p: Optional[int]=None, **kw matrix = [] for n in range(data_sets): matrix.append(scipy.linalg.hankel(data[n][:n_data - p], data[n][n_data - p - 1:])) - matrix = np.array(matrix) + matrix_array = np.array(matrix) # Construct y1 and y2 - y1 = np.concatenate(matrix[:, :, :p]) - y2 = np.concatenate(matrix[:, :, 1:]) + y1 = np.concatenate(matrix_array[:, :, :p]) + y2 = np.concatenate(matrix_array[:, :, 1:]) # Apply SVD to y2 u, s, vh = svd(y2, **kwargs) # Construct z from y1 and SVD of y2, setting all singular values beyond the kth to zero From 51b9d2fb11979d4713012087a74ce02176199b65 Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Tue, 4 Nov 2025 09:58:34 +0000 Subject: [PATCH 56/57] undo signature change in linalg --- pyerrors/linalg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyerrors/linalg.py b/pyerrors/linalg.py index 1393ae3e..324c4007 100644 --- a/pyerrors/linalg.py +++ b/pyerrors/linalg.py @@ -55,7 +55,7 @@ def multi_dot_i(operands): return res else: - def multi_dot(operands, part: Literal["Real", "Imag"]): + def multi_dot(operands): stack = operands[0] for op in operands[1:]: stack = stack @ op From 8d9a136cd8e77ffb2f52e88212cc437e9a9fe246 Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Tue, 4 Nov 2025 10:22:39 +0000 Subject: [PATCH 57/57] slight typechange in cov_Obs --- pyerrors/obs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyerrors/obs.py b/pyerrors/obs.py index 45311257..04c4df79 100644 --- a/pyerrors/obs.py +++ b/pyerrors/obs.py @@ -653,7 +653,7 @@ def plot_piechart(self, save: Optional[str]=None) -> dict[str, float64]: return dict(zip(labels, sizes)) def dump(self, filename: str, datatype: str="json.gz", description: str="", **kwargs): - """Dump the Obs to a file 'name' of chosen format. + """Dump the Obs to a file 'filename' of chosen datatype. Parameters ---------- @@ -1822,7 +1822,7 @@ def merge_obs(list_of_obs: list[Obs]) -> Obs: return o -def cov_Obs(means: Union[int, list[float], float, list[int]], cov: Any, name: str, grad: None=None) -> Union[Obs, list[Obs]]: +def cov_Obs(means: Union[int, list[float], float, list[int]], cov: Any, name: str, grad: Optional[Union[list, ndarray]]=None) -> Union[Obs, list[Obs]]: """Create an Obs based on mean(s) and a covariance matrix Parameters