From 3db8eb2989db390cfd91c8d9708acb70e864e68a Mon Sep 17 00:00:00 2001 From: Fabian Joswig Date: Wed, 25 Dec 2024 11:09:58 +0100 Subject: [PATCH 01/32] [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 .misc import dump_object, _assert_equal_properties 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 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: 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 @@ class 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, 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 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: """ 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 @@ class Corr: 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 @@ class Corr: return Corr(new_content) - def reweight(self, weight, **kwargs): + def reweight(self, weight: Obs, **kwargs) -> "Corr": """Reweight the correlator. Parameters @@ -543,7 +546,7 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: __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 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: 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 @@ class Covobs: 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 @@ class Covobs: 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 @@ class Covobs: 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 @@ identity = np.array( 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 autograd import elementwise_grad as egrad 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 @@ class Fit_result(Sequence): 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 @@ class Fit_result(Sequence): 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 least_squares(x, y, func, priors=None, silent=False, **kwargs): 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 total_least_squares(x, y, func, silent=False, **kwargs): 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 fit_lin(x, y, **kwargs): 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 matmul(*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 jack_matmul(*operands): 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 einsum(subscripts, *operands): 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 _scalar_mat_op(op, obs, **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 pandas as pd 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 @@ from scipy.stats import skew, skewtest, kurtosis, kurtosistest 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 @@ class Obs: 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 @@ class Obs: return res @property - def covobs(self): + def covobs(self) -> Dict[str, Covobs]: return self._covobs def gamma_method(self, **kwargs): @@ -341,7 +344,7 @@ class Obs: 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 @@ class Obs: 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 @@ class Obs: 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 @@ class Obs: """ 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 @@ class Obs: """ 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 @@ class Obs: """ 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 @@ class Obs: 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 @@ class Obs: 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 @@ class Obs: 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 @@ class Obs: 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 @@ class Obs: 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 @@ class Obs: 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 @@ class Obs: 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 @@ class Obs: 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 @@ class Obs: 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 @@ class Obs: 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 @@ class Obs: 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 @@ class Obs: 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 @@ class Obs: 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 @@ class Obs: 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 @@ class CObs: 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 @@ class CObs: 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 @@ class CObs: 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 @@ class CObs: 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 @@ class CObs: 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 @@ class CObs: 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 derived_observable(func, data, array_mode=False, **kwargs): 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 _covariance_element(obs1, obs2): 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 cov_Obs(means, cov, name, grad=None): 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/32] [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 Obs 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 ..covobs import Covobs 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 create_json_string(ol, description='', indent=1): 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 _parse_json_dict(json_dict, verbose=True, full_output=False): 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 _ol_from_dict(ind, reps='DICTOBS'): 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 _od_from_list_and_dict(ol, ind, reps='DICTOBS'): 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 @@ import matplotlib.pyplot as plt 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 ..obs import CObs 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 ..obs import Obs 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 @@ import numpy as np # Thinly-wrapped numpy 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/32] [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 @@ class Obs: 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 @@ class Obs: 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 @@ class Obs: 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 @@ class Obs: 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 @@ class 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 @@ class CObs: """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 @@ class CObs: 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 @@ class CObs: 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/32] [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 matplotlib.pyplot as plt 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 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 numpy as np 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 @@ 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 +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 @@ from __future__ import annotations 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/32] [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 @@ class Obs: 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 @@ class Obs: 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 @@ class Obs: 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 @@ class Obs: 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/32] [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 @@ 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, 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 @@ class Obs: 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 @@ class Obs: 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 @@ class Obs: 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 @@ class Obs: 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 @@ class Obs: 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 @@ class Obs: 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 @@ class Obs: 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 @@ class Obs: 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 @@ class Obs: 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 @@ class Obs: 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 @@ class Obs: 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 @@ class Obs: 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 @@ class Obs: 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 @@ class CObs: 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 @@ class 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 @@ class CObs: 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 derived_observable(func: Callable, data: Any, array_mode: bool=False, **kwar 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 derived_observable(func: Callable, data: Any, array_mode: bool=False, **kwar 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 _covariance_element(obs1: Obs, obs2: Obs) -> Union[float, float64]: 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 cov_Obs(means: Union[float64, int, List[float], float, List[int]], cov: Any, 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/32] [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/32] [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 @@ class Obs: 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 @@ class Obs: 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 @@ class Obs: 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/32] [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 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: 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 @@ class 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 @@ class 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 @@ class 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 @@ class 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 @@ class 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 @@ class 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 @@ class 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 @@ class 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 @@ class 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 @@ class Corr: 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 @@ class 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 @@ class 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 @@ class 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 @@ class 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 @@ class 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 @@ class 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 @@ class 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 matmul(*operands) -> ndarray: 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 numpy as np 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/32] [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 pandas as pd 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/32] [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/32] [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 @@ from typing import Union, TYPE_CHECKING 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 _covariance_element(obs1: Obs, obs2: Obs) -> Union[float, float64]: 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 cov_Obs(means: Union[int, list[float], float, list[int]], cov: Any, name: st 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/32] [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 .misc import dump_object, _assert_equal_properties 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 @@ class Corr: 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 @@ class Corr: 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 @@ class 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 @@ class 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 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: 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 @@ class Covobs: 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 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 +from typing import Any, Callable, Optional, Union class Fit_result(Sequence): @@ -73,7 +73,7 @@ class Fit_result(Sequence): 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 least_squares(x: Any, y: Union[Dict[str, ndarray], List[Obs], ndarray, Dict[ 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 total_least_squares(x: List[Obs], y: List[Obs], func: Callable, silent: bool 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 fit_lin(x: List[Union[Obs, int, float]], y: List[Obs], **kwargs) -> List[Obs 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 ..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 +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 ..covobs import Covobs 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 create_json_string(ol: Any, description: Union[str, Dict[str, Union[str, Dic 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 _ol_from_dict(ind: Union[Dict[Optional[Union[int, bool]], str], Dict[str, Un 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 _od_from_list_and_dict(ol: List[Union[Obs, List[Obs], Corr, ndarray]], ind: 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 @@ import matplotlib.pyplot as plt 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 ..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 +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 ..obs import Obs 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 @@ from __future__ import annotations 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 .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 +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 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 +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 numpy as np 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/32] [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 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], 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 create_json_string(ol: Any, description: Union[str, dict[str, Union[str, dic 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 create_json_string(ol: Any, description: Union[str, dict[str, Union[str, dic 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 _parse_json_dict(json_dict: dict[str, Any], verbose: bool=True, full_output: 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 _ol_from_dict(ind: Union[dict[Optional[Union[int, bool]], str], dict[str, Un 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 _od_from_list_and_dict(ol: list[Union[Obs, list[Obs], Corr, ndarray]], ind: 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/32] [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 total_least_squares(x: list[Obs], y: list[Obs], func: Callable, silent: bool 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 @@ 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: """ 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/32] [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 ..covobs import Covobs 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 ..obs import Obs 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/32] [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 @@ class Fit_result(Sequence): 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 least_squares(x: Any, y: Union[dict[str, ndarray], list[Obs], ndarray, dict[ 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 least_squares(x: Any, y: Union[dict[str, ndarray], list[Obs], ndarray, dict[ 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 least_squares(x: Any, y: Union[dict[str, ndarray], list[Obs], ndarray, dict[ 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 least_squares(x: Any, y: Union[dict[str, ndarray], list[Obs], ndarray, dict[ 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 total_least_squares(x: list[Obs], y: list[Obs], func: Callable, silent: bool 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/32] [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 @@ class Fit_result(Sequence): 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 @@ class Fit_result(Sequence): 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 ..obs import Obs 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/32] [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 @@ class 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 @@ class Corr: 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/32] 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/32] 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 @@ class 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 @@ class 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 @@ class 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 @@ class 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 @@ class 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/32] 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/32] [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 @@ class Obs: 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/32] 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/32] [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 @@ class 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 @@ class 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 @@ class 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 @@ class 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 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: 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 @@ class Corr: __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 @@ class 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 @@ class 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 @@ class 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 @@ class 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 @@ class Corr: 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 @@ class Covobs: """ 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 @@ from scipy.stats import skew, skewtest, kurtosis, kurtosistest 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 @@ class Obs: """ 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 @@ class Obs: 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 @@ class Obs: 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 @@ class Obs: 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 @@ class Obs: 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 @@ class Obs: 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 @@ class Obs: 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 @@ class Obs: 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 @@ class CObs: 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 @@ class CObs: 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 test_fit_correlator(): 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/32] 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 ..obs import Obs 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/32] 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 ..correlators import Corr 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/32] 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 @@ from typing import Optional, Union, TypedDict, Unpack 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/32] [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/32] [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/32] [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 ..correlators import Corr 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/32] 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)