diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..74b25cb --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# SPDX-FileCopyrightText: © 2023 The ehist authors +# +# SPDX-License-Identifier: BSD-2-Clause + +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.py] +indent_size = 4 diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 8d98574..01a41cc 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -10,8 +10,11 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] - os: [ubuntu-20.04, ubuntu-22.04, macos-11, macos-12] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + os: [ubuntu-22.04] + include: + - python-version: 3.12 + os: macos-12 steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4832127..a60a9cf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,36 +7,41 @@ ci: autoupdate_schedule: quarterly repos: - repo: https://github.com/pre-commit/mirrors-prettier - rev: v3.0.0-alpha.6 + rev: v3.0.3 hooks: - id: prettier - repo: https://github.com/psf/black - rev: 23.3.0 + rev: 23.9.1 hooks: - id: black - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.261 + rev: v0.0.292 hooks: - id: ruff args: [--fix] - repo: https://github.com/pycqa/pylint - rev: v3.0.0a6 + rev: v3.0.0 hooks: - id: pylint files: ehist additional_dependencies: [numpy, matplotlib, scipy] - repo: https://github.com/fsfe/reuse-tool - rev: v1.1.2 + rev: v2.1.0 hooks: - id: reuse - repo: https://github.com/codespell-project/codespell - rev: v2.2.4 + rev: v2.2.6 hooks: - id: codespell additional_dependencies: - tomli - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.5.1 + rev: v1.5.4 hooks: - id: forbid-crlf - id: forbid-tabs + - repo: https://github.com/editorconfig-checker/editorconfig-checker.python + rev: "2.7.2" + hooks: + - id: editorconfig-checker + alias: ec diff --git a/COPYING b/COPYING index 13beafe..0f459f9 100644 --- a/COPYING +++ b/COPYING @@ -6,11 +6,11 @@ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. + list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE diff --git a/LICENSES/BSD-2-Clause.txt b/LICENSES/BSD-2-Clause.txt index 5f662b3..eb3c575 100644 --- a/LICENSES/BSD-2-Clause.txt +++ b/LICENSES/BSD-2-Clause.txt @@ -1,4 +1,4 @@ -Copyright (c) +Copyright (c) Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/ehist/axis.py b/ehist/axis.py index 5472ce2..05a0644 100644 --- a/ehist/axis.py +++ b/ehist/axis.py @@ -242,7 +242,7 @@ def auto_axis(points, bins=None, span=None, t=None): def print_axis(axis): - print( + return ( f"{axis.__class__.__name__}\n\tb: {axis.bins}\n\tw: {axis.widths}\n\te: {axis.edges}" - f"\n\tc: {axis.pcenters}\n\te: {axis.pedges}", + f"\n\tc: {axis.pcenters}\n\te: {axis.pedges}" ) diff --git a/ehist/bayesian_blocks.py b/ehist/bayesian_blocks.py index a4aa691..17cdbed 100644 --- a/ehist/bayesian_blocks.py +++ b/ehist/bayesian_blocks.py @@ -46,7 +46,7 @@ import numpy as np -# TODO: implement other fitness functions from appendix B of Scargle 2012 +# TODO: implement other fitness functions from appendix B of Scargle 2012 # noqa: FIX002, TD002, TD003 # __all__ = ['FitnessFunc', 'Events', 'RegularEvents', 'PointMeasures', # 'bayesian_blocks'] @@ -71,19 +71,19 @@ def bayesian_blocks(t, x=None, sigma=None, fitness="events", **kwargs): If a string, the following options are supported: - 'events' : binned or unbinned event data. Arguments are ``gamma``, - which gives the slope of the prior on the number of bins, or - ``ncp_prior``, which is :math:`-\ln({\tt gamma})`. + which gives the slope of the prior on the number of bins, or + ``ncp_prior``, which is :math:`-\ln({\tt gamma})`. - 'regular_events' : non-overlapping events measured at multiples of a - fundamental tick rate, ``dt``, which must be specified as an - additional argument. Extra arguments are ``p0``, which gives the - false alarm probability to compute the prior, or ``gamma``, which - gives the slope of the prior on the number of bins, or ``ncp_prior``, - which is :math:`-\ln({\tt gamma})`. + fundamental tick rate, ``dt``, which must be specified as an + additional argument. Extra arguments are ``p0``, which gives the + false alarm probability to compute the prior, or ``gamma``, which + gives the slope of the prior on the number of bins, or ``ncp_prior``, + which is :math:`-\ln({\tt gamma})`. - 'measures' : fitness for a measured sequence with Gaussian errors. - Extra arguments are ``p0``, which gives the false alarm probability - to compute the prior, or ``gamma``, which gives the slope of the - prior on the number of bins, or ``ncp_prior``, which is - :math:`-\ln({\tt gamma})`. + Extra arguments are ``p0``, which gives the false alarm probability + to compute the prior, or ``gamma``, which gives the slope of the + prior on the number of bins, or ``ncp_prior``, which is + :math:`-\ln({\tt gamma})`. In all three cases, if more than one of ``p0``, ``gamma``, and ``ncp_prior`` is chosen, ``ncp_prior`` takes precedence over ``gamma`` @@ -137,7 +137,7 @@ def bayesian_blocks(t, x=None, sigma=None, fitness="events", **kwargs): References ---------- .. [1] Scargle, J et al. (2012) - https://ui.adsabs.harvard.edu/abs/2013ApJ...764..167S + https://ui.adsabs.harvard.edu/abs/2013ApJ...764..167S See Also -------- @@ -166,27 +166,27 @@ class FitnessFunc: Derived classes should overload the following method: ``fitness(self, **kwargs)``: - Compute the fitness given a set of named arguments. - Arguments accepted by fitness must be among ``[T_k, N_k, a_k, b_k, c_k]`` - (See [1]_ for details on the meaning of these parameters). + Compute the fitness given a set of named arguments. + Arguments accepted by fitness must be among ``[T_k, N_k, a_k, b_k, c_k]`` + (See [1]_ for details on the meaning of these parameters). Additionally, other methods may be overloaded as well: ``__init__(self, **kwargs)``: - Initialize the fitness function with any parameters beyond the normal - ``p0`` and ``gamma``. + Initialize the fitness function with any parameters beyond the normal + ``p0`` and ``gamma``. ``validate_input(self, t, x, sigma)``: - Enable specific checks of the input data (``t``, ``x``, ``sigma``) - to be performed prior to the fit. + Enable specific checks of the input data (``t``, ``x``, ``sigma``) + to be performed prior to the fit. ``compute_ncp_prior(self, N)``: If ``ncp_prior`` is not defined explicitly, - this function is called in order to define it before fitting. This may be - calculated from ``gamma``, ``p0``, or whatever method you choose. + this function is called in order to define it before fitting. This may be + calculated from ``gamma``, ``p0``, or whatever method you choose. ``p0_prior(self, N)``: - Specify the form of the prior given the false-alarm probability ``p0`` - (See [1]_ for details). + Specify the form of the prior given the false-alarm probability ``p0`` + (See [1]_ for details). For examples of implemented fitness functions, see :class:`Events`, :class:`RegularEvents`, and :class:`PointMeasures`. @@ -194,7 +194,7 @@ class FitnessFunc: References ---------- .. [1] Scargle, J et al. (2012) - https://ui.adsabs.harvard.edu/abs/2013ApJ...764..167S + https://ui.adsabs.harvard.edu/abs/2013ApJ...764..167S """ def __init__(self, p0=0.05, gamma=None, ncp_prior=None) -> None: @@ -241,7 +241,7 @@ def validate_input(self, t, x=None, sigma=None): # if x is specified, then we need to simultaneously sort t and x else: - # TODO: allow broadcasted x? + # TODO: allow broadcasted x? # noqa: FIX002, TD002, TD003 x = np.asarray(x, dtype=float) if x.shape not in [(), (1,), (t.size,)]: @@ -533,7 +533,7 @@ def scott_bin_width(data, return_bins=False): References ---------- .. [1] Scott, David W. (1979). "On optimal and data-based histograms". - Biometricka 66 (3): 605-610 + Biometricka 66 (3): 605-610 See Also -------- knuth_bin_width @@ -589,8 +589,8 @@ def freedman_bin_width(data, return_bins=False): References ---------- .. [1] D. Freedman & P. Diaconis (1981) - "On the histogram as a density estimator: L2 theory". - Probability Theory and Related Fields 57 (4): 453-476 + "On the histogram as a density estimator: L2 theory". + Probability Theory and Related Fields 57 (4): 453-476 See Also -------- knuth_bin_width @@ -618,9 +618,9 @@ def freedman_bin_width(data, return_bins=False): if "Maximum allowed size exceeded" in str(e): raise ValueError( "The inter-quartile range of the data is too small: " - "failed to construct histogram with {} bins. " + f"failed to construct histogram with {Nbins + 1} bins. " "Please use another bin method, such as " - 'bins="scott"'.format(Nbins + 1), + 'bins="scott"', ) else: # Something else # pragma: no cover raise @@ -664,7 +664,7 @@ def knuth_bin_width(data, return_bins=False, quiet=True): References ---------- .. [1] Knuth, K.H. "Optimal Data-Based Binning for Histograms". - arXiv:0605197, 2006 + arXiv:0605197, 2006 See Also -------- freedman_bin_width diff --git a/ehist/hist1d.py b/ehist/hist1d.py index d44294b..cd85ac5 100644 --- a/ehist/hist1d.py +++ b/ehist/hist1d.py @@ -22,7 +22,7 @@ def __init__( self, points, bins=None, - range=None, + range=None, # noqa: A002 norm=None, logx=False, t=None, @@ -195,8 +195,6 @@ def vtext(self, logy=False, width=80): def htext(self, show_area=False, show_count=True, logy=False, width=80): y, min_y, max_y = self._get_yaxis_text(logy=logy) - print("YYY", y) - lowedges = self.x.bins.copy()[:-1] highedges = self.x.bins.copy()[1:] if type(self.x) in [LogIntAxis, IntAxis]: @@ -382,7 +380,7 @@ def function_fit(self, func, p0, **kwargs): def __str__(self) -> str: return ( f"<{self.__class__.__name__} {self.x.__class__.__name__} " - f"bins={len(self.x.pcenters),} " + f"bins={len(self.x.pcenters)} " f"range=[{self.x.edges[0]:0.2f},{self.x.edges[-1]:0.2f}]" f"entries={self.entries}>" ) diff --git a/ehist/hist2d.py b/ehist/hist2d.py index c4c552a..37123da 100644 --- a/ehist/hist2d.py +++ b/ehist/hist2d.py @@ -10,7 +10,15 @@ class Hist2D: - def __init__(self, x, y, bins=(60, 50), range=None, log=(False, False), weights=1.0) -> None: + def __init__( + self, + x, + y, + bins=(60, 50), + range=None, # noqa: A002 + log=(False, False), + weights=1.0, + ) -> None: assert len(x) == len( y, ), f"x and y must have the same dimensions: len(x) = {len(x)}, len(y) = {len(y)}" @@ -46,7 +54,7 @@ def __init__(self, x, y, bins=(60, 50), range=None, log=(False, False), weights= rangey = np.log10(rangey) if range is not None: - range = (rangex, rangey) + range = (rangex, rangey) # noqa: A001 nancut = np.array(np.isfinite(x) & np.isfinite(y), dtype=bool) self.entries = nancut.sum() @@ -99,7 +107,7 @@ def plot_text(self): def __str__(self) -> str: return ( - f"<{self.__class__.__name__,} " + f"<{self.__class__.__name__} " f"bins=({len(self.xedges) - 1},{len(self.yedges) - 1})" f"range=[[{self.xedges[0]:0.2f},{self.xedges[-1]:0.2f}]," f"[{self.yedges[0]:0.2f},{self.yedges[-1]:0.2f}]]" diff --git a/ehist/util.py b/ehist/util.py index f0cdb62..1c42692 100644 --- a/ehist/util.py +++ b/ehist/util.py @@ -183,7 +183,6 @@ def __init__(self, width=80, height=25) -> None: self.height = height def get_plot(self, y, min_y, max_y): - print(min_y, max_y) rep = max(1, self.width // len(y)) bars = [] for yy in y: diff --git a/examples/plot_ehist.py b/examples/plot_ehist.py index 7433404..b74a4ae 100755 --- a/examples/plot_ehist.py +++ b/examples/plot_ehist.py @@ -31,49 +31,49 @@ def main(d): plt.show() -def Triangle(): +def triangle(): x = stats.triang.ppf(np.linspace(0, 1, 1000, 1), 1) return {"points": x, "bins": 120}, {"s": "steps,err"} -def PowerLaw(): +def power_law(): e = np.geomspace(1e3, 1e6, 1000) return {"points": e, "bins": 30, "t": "log"}, {"s": "err,marker,steps", "logy": True} -def Zenith(): +def zenith(): cz = np.arccos(np.linspace(-1, 1, 1000)) return {"points": cz, "bins": 20, "t": np.cos}, {"s": "err,marker,steps"} -def Int(): - X = np.linspace(0, 21, 106)[:-1].astype(int) - return {"points": X, "bins": 20, "t": int}, {"s": "err,marker,steps"} +def integer(): + x = np.linspace(0, 21, 106)[:-1].astype(int) + return {"points": x, "bins": 20, "t": int}, {"s": "err,marker,steps"} -def LogInt(): - N = 1001 - x = [int(N / i) * [i] for i in range(1, N)] +def log_int(): + n = 1001 + x = [int(n / i) * [i] for i in range(1, n)] p = [val for sublist in x for val in sublist] return {"points": p, "t": "logint", "bins": 20}, {"s": "steps,marker,err", "logy": True} -def Freedman(): +def freedman(): x = stats.norm.ppf(np.linspace(0, 1, 1002)[1:-1]) return {"points": x, "bins": "freedman"}, {} -def Scott(): +def scott(): x = stats.norm.ppf(np.linspace(0, 1, 1002)[1:-1]) return {"points": x, "bins": "scott"}, {} -def Knuth(): +def knuth(): x = stats.norm.ppf(np.linspace(0, 1, 1002)[1:-1]) return {"points": x, "bins": "knuth"}, {} -def Blocks(): +def blocks(): x = stats.norm.ppf(np.linspace(0, 1, 1002)[1:-1]) return {"points": x, "bins": "blocks"}, {} diff --git a/pyproject.toml b/pyproject.toml index b007d06..7502e55 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,17 +51,14 @@ max-line-length = "108" [tool.ruff] select = ["ALL"] ignore = [ - "D", # pydocstyle - "ANN", # flake8-annotations - "T20", # flake8-print - "S101", # assert-used - "PLR0913", - "FBT002", - "A001", - "A002", - "C901", - "PLR0912", - "ERA001", + "D", # pydocstyle + "ANN", # flake8-annotations + "S101", # assert-used + "PLR0913", # Too many arguments to function call + "FBT002", # Boolean default positional argument + "C901", # is too complex + "PLR0912", # Too many branches + "ERA001", # Found commented-out code ] line-length = 108 target-version = "py37" @@ -73,15 +70,14 @@ fixable = ["I"] "N","A","RET","EM","TRY","PLR","ISC","B","FBT" ] "tests/*" = [ - "N", # pep8-naming "PT", # flake8-pytest-style - "PLR", # max-statements + "PLR0915", # max-statements "INP001" ] "examples/*"=[ - "N", - "INP", - + "N813", + "INP001", + "T201", # flake8-print ] [tool.codespell] diff --git a/tests/test_axes.py b/tests/test_axes.py index 162761f..4601612 100644 --- a/tests/test_axes.py +++ b/tests/test_axes.py @@ -44,9 +44,9 @@ def test_integer(self): a6 = auto_axis(range(5, 23), 10) assert_allclose(a6.widths, [1] + 8 * [2] + [1]) - assert_allclose(a6.edges, [5, *list(range(6, 23, 2))] + [23]) - assert_allclose(a6.pcenters, [5.5, *list(range(7, 22, 2))] + [22.5]) - assert_allclose(a6.pedges, [5, *list(range(6, 23, 2))] + [23]) + assert_allclose(a6.edges, [5, *list(range(6, 23, 2)), 23]) + assert_allclose(a6.pcenters, [5.5, *list(range(7, 22, 2)), 22.5]) + assert_allclose(a6.pedges, [5, *list(range(6, 23, 2)), 23]) a7 = auto_axis(range(5, 23), span=(10, 15)) assert_allclose(a7.widths, 1) @@ -61,7 +61,7 @@ def test_integer(self): assert_allclose(a8.pcenters, range(15, 92, 8)) assert_allclose(a8.pedges, range(11, 92, 8)) - def test_LogIntAxis(self): + def test_log_int_axis(self): b1 = np.arange(1, 11) a1 = auto_axis(b1, 10, t="logint") assert_allclose(a1.bins, b1) @@ -113,7 +113,7 @@ def test_linear(self): assert_allclose(a1.pedges, b1) assert_allclose(a1.pcenters, b1[:-1] + 0.5) - def test_LogAxis(self): + def test_log_axis(self): b1 = np.geomspace(1, 10, 11) a1 = auto_axis(np.geomspace(1, 10, 100), 10, t="log") self.assertTrue(isinstance(a1, LogAxis))