From 311e1ca0b22f5df840d1c470ad5ef82e03402916 Mon Sep 17 00:00:00 2001 From: Alex Drlica-Wagner Date: Wed, 14 Jul 2021 09:23:36 -0500 Subject: [PATCH] GitHub Actions (#89) Closes #83. * setup github action * convert imf steps to int * yaml safe_load * testing actions --- .github/workflows/python-package.yml | 67 ++++++++++++++++++++++++++++ README.md | 2 +- ugali/analysis/imf.py | 8 ++-- ugali/utils/config.py | 2 +- ugali/utils/parabola.py | 16 +++---- 5 files changed, 82 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/python-package.yml diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml new file mode 100644 index 0000000..fa862a1 --- /dev/null +++ b/.github/workflows/python-package.yml @@ -0,0 +1,67 @@ +# 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 +# For more information on conda actions see: https://autobencoder.com/2020-08-24-conda-actions/ +# Starter workflow for conda: https://github.com/actions/starter-workflows/blob/main/ci/python-package-conda.yml + +name: build + +on: + push: + branches: [ master, actions ] + pull_request: + branches: [ master ] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + max-parallel: 3 + matrix: + python-version: [2.7, 3.8] + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Setup conda + run: | + # Add $CONDA/bin to built-in path + echo $CONDA/bin >> $GITHUB_PATH + conda update -q conda + conda info + - name: Create conda environment + run: | + conda create -q -n env python=${{ matrix.python-version }} numpy scipy matplotlib astropy healpy pyyaml emcee fitsio corner -c conda-forge -c kadrlica + - name: Install package + run: | + source activate env + export UGALIDIR="$HOME/.ugali" + python setup.py -q install --isochrones --catalogs + - name: Install test data + run: | + wget https://github.com/DarkEnergySurvey/ugali/releases/download/v1.7.0/ugali-test-data.tar.gz -O ugali-test-data.tar.gz + tar -xzf ugali-test-data.tar.gz + - name: Lint with flake8 + if: ${{ false }} + run: | + source activate env + conda install -q flake8 -c conda-forge + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with nose + run: | + source activate env + conda install -q nose -c conda-forge + export MPLBACKEND=Agg + # Exclude notebook tests + nosetests -v -I test_notebook.py; + - name: Test notebooks + if: ${{ matrix.python-version == '2.7' }} + run: | + source activate env + conda install -q jupyter nbconvert -c conda-forge + # Run notebook tests + nosetests -v tests/test_notebook.py diff --git a/README.md b/README.md index c118d84..44b5315 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Build](https://img.shields.io/travis/DarkEnergySurvey/ugali.svg)](https://travis-ci.org/DarkEnergySurvey/ugali) +[![Build](https://github.com/DarkEnergySurvey/ugali/actions/workflows/python-package.yml/badge.svg)](https://github.com/DarkEnergySurvey/ugali/actions/workflows/python-package.yml) [![PyPI](https://img.shields.io/pypi/v/ugali.svg)](https://pypi.python.org/pypi/ugali) [![Release](https://img.shields.io/github/release/DarkEnergySurvey/ugali.svg)](../../releases) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](../../) diff --git a/ugali/analysis/imf.py b/ugali/analysis/imf.py index 81c391b..07b4304 100644 --- a/ugali/analysis/imf.py +++ b/ugali/analysis/imf.py @@ -20,7 +20,7 @@ def __call__(self, mass, **kwargs): """ Call the pdf of the mass function """ return self.pdf(mass,**kwargs) - def integrate(self, mass_min, mass_max, log_mode=True, weight=False, steps=1e4): + def integrate(self, mass_min, mass_max, log_mode=True, weight=False, steps=10000): """ Numerical Riemannn integral of the IMF (stupid simple). Parameters: @@ -28,13 +28,14 @@ def integrate(self, mass_min, mass_max, log_mode=True, weight=False, steps=1e4): mass_min: minimum mass bound for integration (solar masses) mass_max: maximum mass bound for integration (solar masses) log_mode[True]: use logarithmic steps in stellar mass as oppose to linear - weight[False]: weight the integral by stellar mass - steps: number of numerical integration steps + weight[False]: weight the integral by stellar mass + steps: number of numerical integration steps Returns: -------- result of integral """ + steps = int(steps) if log_mode: d_log_mass = (np.log10(mass_max) - np.log10(mass_min)) / float(steps) log_mass = np.linspace(np.log10(mass_min), np.log10(mass_max), steps) @@ -73,6 +74,7 @@ def sample(self, n, mass_min=0.1, mass_max=10., steps=10000, seed=None): mass : array of randomly sampled mass values """ if seed is not None: np.random.seed(seed) + steps = int(steps) d_mass = (mass_max - mass_min) / float(steps) mass = np.linspace(mass_min, mass_max, steps) cdf = np.insert(np.cumsum(d_mass * self.pdf(mass[1:], log_mode=False)), 0, 0.) diff --git a/ugali/utils/config.py b/ugali/utils/config.py index 70927b1..95be93c 100644 --- a/ugali/utils/config.py +++ b/ugali/utils/config.py @@ -74,7 +74,7 @@ def _load(self, config): """ if isstring(config): self.filename = config - params = yaml.load(open(config)) + params = yaml.safe_load(open(config)) elif isinstance(config, Config): # This is the copy constructor... self.filename = config.filename diff --git a/ugali/utils/parabola.py b/ugali/utils/parabola.py index 24a9c65..e80a973 100644 --- a/ugali/utils/parabola.py +++ b/ugali/utils/parabola.py @@ -122,7 +122,7 @@ def profileUpperLimit(self, delta = 2.71): return max((np.sqrt(b**2 - 4. * a * c) - b) / (2. * a), (-1. * np.sqrt(b**2 - 4. * a * c) - b) / (2. * a)) - def bayesianUpperLimit(self, alpha, steps=1.e5, plot=False): + def bayesianUpperLimit(self, alpha, steps=1e5, plot=False): """ Compute one-sided upper limit using Bayesian Method of Helene. Several methods of increasing numerical stability have been implemented. @@ -130,7 +130,7 @@ def bayesianUpperLimit(self, alpha, steps=1.e5, plot=False): x_dense, y_dense = self.densify() y_dense -= np.max(y_dense) # Numeric stability f = scipy.interpolate.interp1d(x_dense, y_dense, kind='linear') - x = np.linspace(0., np.max(x_dense), steps) + x = np.linspace(0., np.max(x_dense), int(steps)) pdf = np.exp(f(x) / 2.) cut = (pdf / np.max(pdf)) > 1.e-10 x = x[cut] @@ -147,7 +147,7 @@ def bayesianUpperLimit(self, alpha, steps=1.e5, plot=False): return cdf_reflect(alpha) - def bayesianUpperLimit2(self, alpha, steps=1.e5, plot=False): + def bayesianUpperLimit2(self, alpha, steps=1e5, plot=False): """ Compute one-sided upper limit using Bayesian Method of Helene. """ @@ -156,7 +156,7 @@ def bayesianUpperLimit2(self, alpha, steps=1.e5, plot=False): f = scipy.interpolate.interp1d(self.x[cut], self.y[cut], kind='cubic') except: f = scipy.interpolate.interp1d(self.x[cut], self.y[cut], kind='linear') - x = np.linspace(0., np.max(self.x[cut]), steps) + x = np.linspace(0., np.max(self.x[cut]), int(steps)) y = np.exp(f(x) / 2.) #forbidden = np.nonzero((y / np.exp(self.vertex_y / 2.)) < 1.e-10)[0] forbidden = np.nonzero((y / self.vertex_y) < 1.e-10)[0] @@ -171,17 +171,17 @@ def bayesianUpperLimit2(self, alpha, steps=1.e5, plot=False): return cdf_reflect(alpha) - def confidenceInterval(self, alpha=0.6827, steps=1.e5, plot=False): + def confidenceInterval(self, alpha=0.6827, steps=1e5, plot=False): """ Compute two-sided confidence interval by taking x-values corresponding to the largest PDF-values first. """ x_dense, y_dense = self.densify() y_dense -= np.max(y_dense) # Numeric stability f = scipy.interpolate.interp1d(x_dense, y_dense, kind='linear') - x = np.linspace(0., np.max(x_dense), steps) + x = np.linspace(0., np.max(x_dense), int(steps)) # ADW: Why does this start at 0, which often outside the input range? # Wouldn't starting at xmin be better: - #x = np.linspace(np.min(x_dense), np.max(x_dense), steps) + #x = np.linspace(np.min(x_dense), np.max(x_dense), int(steps)) pdf = np.exp(f(x) / 2.) cut = (pdf / np.max(pdf)) > 1.e-10 x = x[cut] @@ -206,7 +206,7 @@ def upperLimitsDeltaTS(confidence_level, one_sided=True, degrees_of_freedom=1): ts_min = 0 # TS = Test Statistic ts_max = 5 ts_steps = 1000 - x = np.linspace(ts_min, ts_max, ts_steps) + x = np.linspace(ts_min, ts_max, int(ts_steps)) y = (0.5 * scipy.stats.chi2.sf(x, degrees_of_freedom) - (1. - confidence_level))**2 return x[np.argmin(y)]