From 4e9f375d2d039828b85402807baff104210e3dbf Mon Sep 17 00:00:00 2001 From: Johannes Buchner Date: Sun, 26 May 2024 16:41:54 +0200 Subject: [PATCH] try to fix cython int type error; add github CI for quick checks closes issue https://github.com/JohannesBuchner/UltraNest/issues/134 (hopefully) --- .circleci/config.yml | 7 ++++- .github/workflows/tests.yml | 44 ++++++++++++++++++++++++++++++++ ultranest/integrator.py | 6 +++-- ultranest/mlfriends.pyx | 10 ++++---- ultranest/popstepsampler.py | 8 +++--- ultranest/stepfuncs.pyx | 4 +-- ultranest/stepsampler.py | 51 ++++++++++++++++++++----------------- 7 files changed, 93 insertions(+), 37 deletions(-) create mode 100644 .github/workflows/tests.yml diff --git a/.circleci/config.yml b/.circleci/config.yml index 3f78a630..1cf5aebf 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -17,7 +17,8 @@ jobs: - run: sudo ln -s /usr/lib/python3/dist-packages/numpy/core/include/numpy/ /usr/include/numpy - - run: sudo python3 -m pip install -r pip-requirements.txt pytest-html coveralls pyyaml mpi4py + - run: sudo python3 -m pip install -r pip-requirements.txt pytest-html coveralls pyyaml mpi4py pydocstyle pycodestyle flake8 + - run: mkdir -p test-reports - run: python3 -m pip install -e . @@ -35,6 +36,10 @@ jobs: - run: coverage3 report --include="$PWD/*" --omit="$PWD/.eggs/*" - run: coverage3 html --include="$PWD/*" --omit="$PWD/.eggs/*" && mv htmlcov test-reports + - run: flake8 $(ls ultranest/*.py | grep -Ev '^ultranest/(flatnuts|dychmc|dyhmc).py') + - run: pycodestyle $(ls ultranest/*.py | grep -Ev '^ultranest/(flatnuts|dychmc|dyhmc).py') + - run: pydocstyle $(ls ultranest/*.py | grep -Ev '^ultranest/(flatnuts|dychmc|dyhmc).py') + - run: coveralls - store_test_results: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..2c00f278 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,44 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: build + +on: + push: + pull_request: + schedule: + - cron: '42 4 5,20 * *' + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: [3.7, 3.8, 3.9, "3.10", 3.11, 3.12] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: python -m pip install -r pip-requirements.txt + + - name: Lint with flake8 + run: flake8 $(ls ultranest/*.py | grep -Ev '^ultranest/(flatnuts|dychmc|dyhmc).py') + + - name: Check code style + run: pycodestyle $(ls ultranest/*.py | grep -Ev '^ultranest/(flatnuts|dychmc|dyhmc).py') + + - name: Check doc style + run: pydocstyle $(ls ultranest/*.py | grep -Ev '^ultranest/(flatnuts|dychmc|dyhmc).py') + + - name: Install package + run: python -m pip install -e . + + - name: Test with pytest + run: pytest -n -x diff --git a/ultranest/integrator.py b/ultranest/integrator.py index b3930987..3c4fb252 100644 --- a/ultranest/integrator.py +++ b/ultranest/integrator.py @@ -37,6 +37,8 @@ __all__ = ['ReactiveNestedSampler', 'NestedSampler', 'read_file', 'warmstart_from_similar_file'] +int_t = int + def _get_cumsum_range(pi, dp): """Compute quantile indices from probabilities. @@ -2015,8 +2017,8 @@ def _update_region( # instead, track the clusters from before by matching manually oldt = self.transformLayer.transform(oldu) - clusterids = np.zeros(len(active_u), dtype=np.int_) - nnearby = np.empty(len(self.region.unormed), dtype=np.int_) + clusterids = np.zeros(len(active_u), dtype=int_t) + nnearby = np.empty(len(self.region.unormed), dtype=int_t) for ci in np.unique(self.transformLayer.clusterids): if ci == 0: continue diff --git a/ultranest/mlfriends.pyx b/ultranest/mlfriends.pyx index df2ad7b6..eb1247b3 100644 --- a/ultranest/mlfriends.pyx +++ b/ultranest/mlfriends.pyx @@ -27,7 +27,7 @@ cdef count_nearby( np.ndarray[np.float_t, ndim=2] apts, np.ndarray[np.float_t, ndim=2] bpts, np.float_t radiussq, - np.ndarray[np.int_, ndim=1] nnearby + np.ndarray[np.int_t, ndim=1] nnearby ): """Count the number of points in ``apts`` within square radius ``radiussq`` for each point ``b`` in `bpts``. @@ -139,7 +139,7 @@ def find_nearby( np.ndarray[np.float_t, ndim=2] apts, np.ndarray[np.float_t, ndim=2] bpts, np.float_t radiussq, - np.ndarray[np.int_, ndim=1] nnearby + np.ndarray[np.int_t, ndim=1] nnearby ): """Gets the index of a point in `a` within square radius `radiussq`, for each point `b` in `bpts`. @@ -223,7 +223,7 @@ cdef float compute_maxradiussq(np.ndarray[np.float_t, ndim=2] apts, np.ndarray[n @cython.wraparound(False) def compute_mean_pair_distance( np.ndarray[np.float_t, ndim=2] pts, - np.ndarray[np.int_, ndim=1] clusterids + np.ndarray[np.int_t, ndim=1] clusterids ): """Compute the average distance between pairs of points. Pairs from different clusters are excluded in the computation. @@ -271,7 +271,7 @@ cdef _update_clusters( np.ndarray[np.float_t, ndim=2] upoints, np.ndarray[np.float_t, ndim=2] tpoints, np.float_t maxradiussq, - np.ndarray[np.int_, ndim=1] clusterids, + np.ndarray[np.int_t, ndim=1] clusterids, ): """same signature as ``update_clusters()``, see there.""" assert upoints.shape[0] == tpoints.shape[0], ('different number of points', upoints.shape[0], tpoints.shape[0]) @@ -845,7 +845,7 @@ class LocalAffineLayer(AffineLayer): return s -def vol_prefactor(np.int_ n): +def vol_prefactor(np.int_t n): """Volume constant for an ``n``-dimensional sphere. for ``n`` even: $$ (2pi)^(n /2) / (2 * 4 * ... * n)$$ diff --git a/ultranest/popstepsampler.py b/ultranest/popstepsampler.py index e32c00c8..48c9d4d3 100644 --- a/ultranest/popstepsampler.py +++ b/ultranest/popstepsampler.py @@ -17,6 +17,8 @@ from ultranest.stepfuncs import generate_differential_direction, generate_mixture_random_direction import scipy.stats +int_t = int + def unitcube_line_intersection(ray_origin, ray_direction): r"""Compute intersection of a line (ray) and a unit box (0:1 in all axes). @@ -425,7 +427,7 @@ def _setup(self, ndim): self.allL = np.zeros((self.popsize, self.nsteps + 1)) + np.nan self.currentt = np.zeros(self.popsize) + np.nan self.currentv = np.zeros((self.popsize, ndim)) + np.nan - self.generation = np.zeros(self.popsize, dtype=np.int_) - 1 + self.generation = np.zeros(self.popsize, dtype=int_t) - 1 self.current_left = np.zeros(self.popsize) self.current_right = np.zeros(self.popsize) self.searching_left = np.zeros(self.popsize, dtype=bool) @@ -923,9 +925,9 @@ def __next__( # Slice bounds for each points tleft, tright = self.slice_limit(tleft_unitcube,tright_unitcube) # Index of the workers working concurrently - worker_running = np.arange(0, self.popsize, 1, dtype=np.int_) + worker_running = np.arange(0, self.popsize, 1, dtype=int_t) # Status indicating if a points has already find its next position - status = np.zeros(self.popsize, dtype=np.int_) # one for success, zero for running + status = np.zeros(self.popsize, dtype=int_t) # one for success, zero for running # Loop until each points has found its next position or we reached 100 iterations for it in range(self.max_it): diff --git a/ultranest/stepfuncs.pyx b/ultranest/stepfuncs.pyx index fd4c0111..44e7f2d5 100644 --- a/ultranest/stepfuncs.pyx +++ b/ultranest/stepfuncs.pyx @@ -331,7 +331,7 @@ def step_back(Lmin, allL, generation, currentt, log=False): cdef _fill_directions( np.ndarray[np.float_t, ndim=2] v, - np.ndarray[np.int_, ndim=1] indices, + np.ndarray[np.int_t, ndim=1] indices, float scale ): cdef size_t nsamples = v.shape[0] @@ -533,7 +533,7 @@ cpdef tuple update_vectorised_slice_sampler(\ np.ndarray[np.float_t, ndim=1] t, np.ndarray[np.float_t, ndim=1] tleft,\ np.ndarray[np.float_t, ndim=1] tright, np.ndarray[np.float_t, ndim=1] proposed_L,\ np.ndarray[np.float_t, ndim=2] proposed_u, np.ndarray[np.float_t, ndim=2] proposed_p,\ - np.ndarray[np.int_, ndim=1] worker_running, np.ndarray[np.int_, ndim=1] status,\ + np.ndarray[np.int_t, ndim=1] worker_running, np.ndarray[np.int_t, ndim=1] status,\ np.float_t Likelihood_threshold,np.float_t shrink_factor, np.ndarray[np.float_t, ndim=2] allu,\ np.ndarray[np.float_t, ndim=1] allL, np.ndarray[np.float_t, ndim=2] allp, int popsize): diff --git a/ultranest/stepsampler.py b/ultranest/stepsampler.py index e161c03e..09dec8e1 100644 --- a/ultranest/stepsampler.py +++ b/ultranest/stepsampler.py @@ -466,33 +466,36 @@ def select_random_livepoint(us, Ls, Lmin): class IslandPopulationRandomLivepointSelector(object): + """Mutually isolated live point subsets. + + To replace dead points, chains are only started from the same + island as the dead point. Island refers to chunks of + live point indices (0,1,2,3 as stored, not sorted). + Each chunk has size ´island_size´. + + If ´island_size´ is large, for example, the total number of live points, + then clumping can occur more easily. This is the observed behaviour + that a limited random walk is run from one live point, giving + two similar points, then the next dead point replacement is + likely run again from these, giving more and more similar live points. + This gives a run-away process leading to clumps of similar, + highly correlated points. + + If ´island_size´ is small, for example, 1, then each dead point + is replaced by a chain started from it. This is a problem because + modes can never die out. Nested sampling can then not complete. + + In a multi-modal run, within a given number of live points, + the number of live points per mode is proportional to the mode's + prior volume, but can fluctuate. If the number of live points + is small, a fluctuation can lead to mode die-out, which cannot + be reversed. Therefore, the number of island members should be + large enough to represent each mode. + """ + def __init__(self, island_size, exchange_probability=0): """Set up multiple isolated islands. - To replace dead points, chains are only started from the same - island as the dead point. Island refers to chunks of - live point indices (0,1,2,3 as stored, not sorted). - Each chunk has size ´island_size´. - - If ´island_size´ is large, for example, the total number of live points, - then clumping can occur more easily. This is the observed behaviour - that a limited random walk is run from one live point, giving - two similar points, then the next dead point replacement is - likely run again from these, giving more and more similar live points. - This gives a run-away process leading to clumps of similar, - highly correlated points. - - If ´island_size´ is small, for example, 1, then each dead point - is replaced by a chain started from it. This is a problem because - modes can never die out. Nested sampling can then not complete. - - In a multi-modal run, within a given number of live points, - the number of live points per mode is proportional to the mode's - prior volume, but can fluctuate. If the number of live points - is small, a fluctuation can lead to mode die-out, which cannot - be reversed. Therefore, the number of island members should be - large enough to represent each mode. - Parameters ----------- island_size: int