From e974e8a609b603c89a50373eaca13c239c94fd10 Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Fri, 16 Apr 2021 15:13:16 +1000 Subject: [PATCH 01/80] getPoci() returns np.nan incorrectly --- Utilities/loadData.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Utilities/loadData.py b/Utilities/loadData.py index 1e57137e..94f9124e 100644 --- a/Utilities/loadData.py +++ b/Utilities/loadData.py @@ -710,7 +710,7 @@ def getPoci(penv, pcentre, lat, jdays, eps, nvidx = np.where(pcentre == missingValue) poci_model[nvidx] = np.nan - nvidx = np.where(penv <= pcentre) + nvidx = np.where(penv < pcentre) poci_model[nvidx] = np.nan elif penv < pcentre: From 5365c17ddc33b99235aa10aa5f51da4d40b505ae Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Thu, 22 Apr 2021 10:06:49 +1000 Subject: [PATCH 02/80] track.ncReadTrackData returns true datetime objects track.ncReadTrackData previously returned cftime.DatetimeGregorian objects, which caused newer versions of matplotlib.dates.num2date to fail. This is because we write the tracks with units of 'hours since 1900-01-01 00:00', but matplotlib.dates uses 1970-01-01 as the epoch, and works in units of days (with no way to specify units in the num2date function). --- Evaluate/interpolateTracks.py | 4 ++-- Utilities/track.py | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Evaluate/interpolateTracks.py b/Evaluate/interpolateTracks.py index c1b551d6..924fd2b1 100644 --- a/Evaluate/interpolateTracks.py +++ b/Evaluate/interpolateTracks.py @@ -84,7 +84,7 @@ def interpolate(track, delta, interpolation_type=None): else: raise dt_ = 24.0 * np.diff(time_) - dt = np.empty(len(track.data), dtype=float) + dt = np.zeros(len(track.data), dtype=float) dt[1:] = dt_ # Convert all times to a time after initial observation: @@ -93,13 +93,13 @@ def interpolate(track, delta, interpolation_type=None): newtime = np.arange(timestep[0], timestep[-1] + .01, delta) newtime[-1] = timestep[-1] _newtime = (newtime / 24.) + time_[0] - newdates = num2date(_newtime) newdates = np.array([n.replace(tzinfo=None) for n in newdates]) if not hasattr(track, 'Speed'): idx = np.zeros(len(track.data)) idx[0] = 1 + # TODO: Possibly could change `np.mean(dt)` to `dt`? track.WindSpeed = maxWindSpeed(idx, np.mean(dt), track.Longitude, track.Latitude, track.CentralPressure, track.EnvPressure) diff --git a/Utilities/track.py b/Utilities/track.py index bdca9e97..1cca7c1d 100644 --- a/Utilities/track.py +++ b/Utilities/track.py @@ -25,6 +25,7 @@ from Utilities.maputils import bearing2theta from netCDF4 import Dataset, date2num, num2date +from cftime import num2pydate try: from exceptions import WindowsError @@ -206,11 +207,13 @@ def ncReadTrackData(trackfile): units = ncobj.getncattr('time_units') calendar = ncobj.getncattr('calendar') dtt = num2date(dt[:], units, calendar) + # Convert to true python datetimes + dtconversion = [datetime.strptime(d.strftime(), "%Y-%m-%d %H:%M:%S") for d in dtt] newtd = np.zeros(len(dtt), dtype=track_dtype) for f in ncobj.variables.keys(): if f != 'Datetime' and f in track_dtype.names: newtd[f] = ncobj.variables[f][:] - newtd['Datetime'] = dtt + newtd['Datetime'] = dtconversion track = Track(newtd) track.trackfile = trackfile track.trackId = eval(ncobj.trackId) @@ -237,7 +240,8 @@ def ncReadTrackData(trackfile): for f in track_data.dtype.names: if f != 'Datetime' and f in track_dtype.names: newtd[f] = track_data[f] - newtd['Datetime'] = dt + dtconversion = [datetime.strptime(d.strftime(), "%Y-%m-%d %H:%M:%S") for d in dt] + newtd['Datetime'] = dtconversion track = Track(newtd) track.trackfile = trackfile From f88ce28278ccebe3b594c7b501cecb5cc1084b0d Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Thu, 22 Apr 2021 10:48:08 +1000 Subject: [PATCH 03/80] GitHub actions (#111) * Update README.rst * Update version number * Merge commit 'ea8e3b816cf01089a304e121669880ef193c4b23' * Read netcdf-format track file from GA SST GA's Scenario selection tool allows users to download a selected track file, but due to technical constraints this is in a different format. This change allows TCRM to read the modified format. * Fix up a couple of tests that were failing * Bugfix: getPoci returns np.nan incorrectly * Create tcrm-tests.yml for github actions --- .github/workflows/tcrm-tests.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/workflows/tcrm-tests.yml diff --git a/.github/workflows/tcrm-tests.yml b/.github/workflows/tcrm-tests.yml new file mode 100644 index 00000000..1bf1bdbd --- /dev/null +++ b/.github/workflows/tcrm-tests.yml @@ -0,0 +1,29 @@ +# 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: Unit tests for TCRM + +on: + push: + branches: [ master, develop ] + pull_request: + branches: [ master, develop ] + +jobs: + Hazimp: + name: Test HazImp + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up environment + uses: conda-incubator/setup-miniconda@v2.0.0 + with: + activate-environment: tcrm + environment-file: tcrmenv.yml + python-version: 3.7 + auto-activate-base: false + + - name: Test with nose + shell: bash -l {0} + run: | + python tests/run.py From ebf247313a37242983387dd08d429dc5b026e833 Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Thu, 22 Apr 2021 11:08:35 +1000 Subject: [PATCH 04/80] Change build status badge to github actions --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index e8bd5830..44acef66 100644 --- a/README.rst +++ b/README.rst @@ -65,8 +65,8 @@ TCRM requires: Status ====== -.. image:: https://travis-ci.org/GeoscienceAustralia/tcrm.svg?branch=develop - :target: https://travis-ci.org/GeoscienceAustralia/tcrm +.. image:: https://github.com/GeoscienceAustralia/tcrm/actions/workflows/tcrm-tests.yml/badge.svg?branch=develop + :target: https://github.com/GeoscienceAustralia/tcrm/actions/workflows/tcrm-tests.yml :alt: Build status From 0719a9f5890261445d371fefb16ed9040012d824 Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Thu, 22 Apr 2021 11:36:22 +1000 Subject: [PATCH 05/80] Update DOI badge --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 44acef66..451c6962 100644 --- a/README.rst +++ b/README.rst @@ -79,8 +79,8 @@ Status :target: https://landscape.io/github/GeoscienceAustralia/tcrm/develop :alt: Code Health -.. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.4070660.svg - :target: https://doi.org/10.5281/zenodo.4070660 +.. image:: https://zenodo.org/badge/10637300.svg + :target: https://zenodo.org/badge/latestdoi/10637300 Screenshot ========== From 77fe6c6caa44cc43abc6ec82d8bae3445046fe95 Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Fri, 30 Apr 2021 12:23:33 +1000 Subject: [PATCH 06/80] Remove travis CI tests --- .github/workflows/tcrm-tests.yml | 9 ++++--- .travis.yml | 30 --------------------- postinstall.sh | 46 -------------------------------- preinstall.sh | 24 ----------------- 4 files changed, 6 insertions(+), 103 deletions(-) delete mode 100644 .travis.yml delete mode 100644 postinstall.sh delete mode 100644 preinstall.sh diff --git a/.github/workflows/tcrm-tests.yml b/.github/workflows/tcrm-tests.yml index 1bf1bdbd..c6f2096c 100644 --- a/.github/workflows/tcrm-tests.yml +++ b/.github/workflows/tcrm-tests.yml @@ -10,9 +10,12 @@ on: branches: [ master, develop ] jobs: - Hazimp: - name: Test HazImp + TCRM: + name: Test TCRM runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.7, 3.8] steps: - uses: actions/checkout@v2 - name: Set up environment @@ -20,7 +23,7 @@ jobs: with: activate-environment: tcrm environment-file: tcrmenv.yml - python-version: 3.7 + python-version: ${{ matrix.python-version }} auto-activate-base: false - name: Test with nose diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index aef70720..00000000 --- a/.travis.yml +++ /dev/null @@ -1,30 +0,0 @@ -language: shell - -env: - - PYTHON_VERSION=3.6 - - PYTHON_VERSION=3.7 - - PYTHON_VERSION=3.8 - -os: - - linux - - windows - -before_install: - - source ./preinstall.sh - -install: - - source ./postinstall.sh - -branches: - except: - - config - - notebooks - -script: - - python installer/setup.py build_ext -i - - nosetests -v --with-coverage --cover-package=. - -after_success: coveralls -notifications: - slack: - secure: Ckmwy59ytS1GPRZ5Tmvzad6+W9AzvfjNJAa4orgdKS/WktoK4b9W2rbTHxi8V3hBLIDUCso8vIQi3rVXpWY3cFMvb/uRbXO4GiIW1iua3CKjxd+dEw4E6/8DEknS1qdGJRDhN9/3ucZNvSGHY3EQQDfxb/R+OGd2jT6+jed8pss= diff --git a/postinstall.sh b/postinstall.sh deleted file mode 100644 index 3bd82652..00000000 --- a/postinstall.sh +++ /dev/null @@ -1,46 +0,0 @@ -# begin installing miniconda -if [[ "$TRAVIS_OS_NAME" != "windows" ]]; then - echo "installing miniconda for posix"; - bash $HOME/download/miniconda.sh -b -u -p $MINICONDA_PATH; -elif [[ "$TRAVIS_OS_NAME" == "windows" ]]; then - echo "folder $MINICONDA_SUB_PATH does not exist" - echo "installing miniconda for windows"; - choco install miniconda3 --params="'/JustMe /AddToPath:1 /D:$MINICONDA_PATH_WIN'"; -fi; -# end installing miniconda - -export PATH="$MINICONDA_PATH:$MINICONDA_SUB_PATH:$MINICONDA_LIB_BIN_PATH:$PATH"; - -# begin checking miniconda existance -echo "checking if folder $MINICONDA_SUB_PATH exists" -if [[ -d $MINICONDA_SUB_PATH ]]; then - echo "folder $MINICONDA_SUB_PATH exists" -else - echo "folder $MINICONDA_SUB_PATH does not exist" -fi; -# end checking miniconda existance - -source $MINICONDA_PATH/etc/profile.d/conda.sh; -hash -r; -echo $TRAVIS_OS_NAME -echo $PYTHON_VERSION -python --version - -if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then - echo "Removing mpi4py from environment for windows build" - echo "Package not available in conda channels" - sed -i '/mpi4py/d' ./tcrmenv.yml -fi - -conda config --set always_yes yes --set changeps1 no; -conda update -q conda; -conda config --add channels conda-forge; -conda config --set channel_priority strict; -# Useful for debugging any issues with conda -conda info -a - -echo "Create TCRM environment" -conda env create -q -f tcrmenv.yml python=$PYTHON_VERSION; -conda activate tcrm -python --version -conda list diff --git a/preinstall.sh b/preinstall.sh deleted file mode 100644 index 339b2536..00000000 --- a/preinstall.sh +++ /dev/null @@ -1,24 +0,0 @@ -if [[ "$TRAVIS_OS_NAME" != "windows" ]]; then - export MINICONDA_PATH=$HOME/miniconda; - export MINICONDA_SUB_PATH=$MINICONDA_PATH/bin; -elif [[ "$TRAVIS_OS_NAME" == "windows" ]]; then - export MINICONDA_PATH=$HOME/miniconda; - export MINICONDA_PATH_WIN=`cygpath --windows $MINICONDA_PATH`; - export MINICONDA_SUB_PATH=$MINICONDA_PATH/Scripts; -fi; -export MINICONDA_LIB_BIN_PATH=$MINICONDA_PATH/Library/bin; - # Obtain miniconda installer -if [[ "$TRAVIS_OS_NAME" != "windows" ]]; then - mkdir -p $HOME/download; - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then - echo "downloading miniconda.sh for linux"; - wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O $HOME/download/miniconda.sh; - elif [[ "$TRAVIS_OS_NAME" == "osx" ]]; then - echo "downloading miniconda.sh for osx"; - wget https://repo.continuum.io/miniconda/Miniconda3-latest-MacOSX-x86_64.sh -O $HOME/download/miniconda.sh; - fi; -fi; - # Install openssl for Windows -if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then - choco install openssl.light; -fi; \ No newline at end of file From 9ab8a26c4c798ddabd7b3e835b2bd2b7f85e5eb6 Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Fri, 30 Apr 2021 14:12:28 +1000 Subject: [PATCH 07/80] Create tcrm-pylint.yml --- .github/workflows/tcrm-pylint.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/tcrm-pylint.yml diff --git a/.github/workflows/tcrm-pylint.yml b/.github/workflows/tcrm-pylint.yml new file mode 100644 index 00000000..cad559ac --- /dev/null +++ b/.github/workflows/tcrm-pylint.yml @@ -0,0 +1,28 @@ +name: Pylint tests for TCRM + +on: + push: + branches: [ master, develop ] + +jobs: + build: + name: Pylint TCRM + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python env + uses: conda-incubator/setup-miniconda@v2.0.0 + with: + activate-environment: tcrm + environment-file: tcrmenv.yml + python-version: 3.7 + auto-activate-base: false + + - name: Install dependencies + run: | + conda install pylint + - name: Analysing the code with pylint + run: | + pylint `find . -name "*.py" | xargs` + From 9edae9e08728235dd0101b1f9052ec1f2ac9dba9 Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Fri, 30 Apr 2021 14:32:13 +1000 Subject: [PATCH 08/80] Update tcrm-pylint.yml --- .github/workflows/tcrm-pylint.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tcrm-pylint.yml b/.github/workflows/tcrm-pylint.yml index cad559ac..ac3c1403 100644 --- a/.github/workflows/tcrm-pylint.yml +++ b/.github/workflows/tcrm-pylint.yml @@ -23,6 +23,6 @@ jobs: run: | conda install pylint - name: Analysing the code with pylint - run: | - pylint `find . -name "*.py" | xargs` + run: which pylint + run: pylint `find . -name "*.py" | xargs` From 46b38d43655491093a172c9b348a527762cea8c9 Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Fri, 30 Apr 2021 14:34:18 +1000 Subject: [PATCH 09/80] Update tcrm-pylint.yml --- .github/workflows/tcrm-pylint.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tcrm-pylint.yml b/.github/workflows/tcrm-pylint.yml index ac3c1403..04cd5cad 100644 --- a/.github/workflows/tcrm-pylint.yml +++ b/.github/workflows/tcrm-pylint.yml @@ -23,6 +23,7 @@ jobs: run: | conda install pylint - name: Analysing the code with pylint - run: which pylint - run: pylint `find . -name "*.py" | xargs` + run: | + which pylint + pylint `find . -name "*.py" | xargs` From bef683895aa279f1da6bfba3c1f1d8cf8b1bbfaa Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Fri, 30 Apr 2021 14:43:43 +1000 Subject: [PATCH 10/80] Update tcrm-pylint.yml --- .github/workflows/tcrm-pylint.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/tcrm-pylint.yml b/.github/workflows/tcrm-pylint.yml index 04cd5cad..7749182c 100644 --- a/.github/workflows/tcrm-pylint.yml +++ b/.github/workflows/tcrm-pylint.yml @@ -24,6 +24,5 @@ jobs: conda install pylint - name: Analysing the code with pylint run: | - which pylint - pylint `find . -name "*.py" | xargs` + find . -type f -name "*.py" -print0 | xargs -0 pylint --rcfile=pylintrc From e3331afb1aad9bf23f3028c7decdeda253a74b41 Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Fri, 30 Apr 2021 14:57:12 +1000 Subject: [PATCH 11/80] Update tcrm-pylint.yml --- .github/workflows/tcrm-pylint.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tcrm-pylint.yml b/.github/workflows/tcrm-pylint.yml index 7749182c..c47b49df 100644 --- a/.github/workflows/tcrm-pylint.yml +++ b/.github/workflows/tcrm-pylint.yml @@ -21,8 +21,14 @@ jobs: - name: Install dependencies run: | - conda install pylint + python -m pip install --upgrade pip + pip install pylint - name: Analysing the code with pylint - run: | - find . -type f -name "*.py" -print0 | xargs -0 pylint --rcfile=pylintrc - + run: | + pylint --fail-under=10 `find -regextype egrep -regex '(.*.py)$'` + | tee pylint.txt + - name: Upload pylint.txt as artifact + uses: actions/upload-artifact@v2 + with: + name: pylint report + path: pylint.txt From 52e799c407597a04ea07ec79c06dbb7b48e5a611 Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Fri, 30 Apr 2021 15:12:55 +1000 Subject: [PATCH 12/80] Update tcrm-pylint.yml --- .github/workflows/tcrm-pylint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tcrm-pylint.yml b/.github/workflows/tcrm-pylint.yml index c47b49df..737b1c59 100644 --- a/.github/workflows/tcrm-pylint.yml +++ b/.github/workflows/tcrm-pylint.yml @@ -25,7 +25,7 @@ jobs: pip install pylint - name: Analysing the code with pylint run: | - pylint --fail-under=10 `find -regextype egrep -regex '(.*.py)$'` + pylint --rcfile pylintrc --fail-under=10 `find -regextype egrep -regex '(.*.py)$'` | tee pylint.txt - name: Upload pylint.txt as artifact uses: actions/upload-artifact@v2 From ed271db7b23cdb44be3cebd4cf084d3893d2b92d Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Fri, 30 Apr 2021 15:17:01 +1000 Subject: [PATCH 13/80] Update tcrm-pylint.yml Remove `fail-under` threshold --- .github/workflows/tcrm-pylint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tcrm-pylint.yml b/.github/workflows/tcrm-pylint.yml index 737b1c59..f2bc6c57 100644 --- a/.github/workflows/tcrm-pylint.yml +++ b/.github/workflows/tcrm-pylint.yml @@ -25,7 +25,7 @@ jobs: pip install pylint - name: Analysing the code with pylint run: | - pylint --rcfile pylintrc --fail-under=10 `find -regextype egrep -regex '(.*.py)$'` + pylint --rcfile pylintrc `find -regextype egrep -regex '(.*.py)$'` | tee pylint.txt - name: Upload pylint.txt as artifact uses: actions/upload-artifact@v2 From 245d834f7ef7901c48da27962cd94e9739b3c178 Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Fri, 30 Apr 2021 15:23:55 +1000 Subject: [PATCH 14/80] Update tcrm-pylint.yml --- .github/workflows/tcrm-pylint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tcrm-pylint.yml b/.github/workflows/tcrm-pylint.yml index f2bc6c57..13cf3c60 100644 --- a/.github/workflows/tcrm-pylint.yml +++ b/.github/workflows/tcrm-pylint.yml @@ -25,7 +25,7 @@ jobs: pip install pylint - name: Analysing the code with pylint run: | - pylint --rcfile pylintrc `find -regextype egrep -regex '(.*.py)$'` + pylint --rcfile pylintrc --fail-under=7 `find -regextype egrep -regex '(.*.py)$'` | tee pylint.txt - name: Upload pylint.txt as artifact uses: actions/upload-artifact@v2 From ad20d468471e10d36e242ce1860301ad848548c5 Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Fri, 30 Apr 2021 15:33:09 +1000 Subject: [PATCH 15/80] Update tcrm-pylint.yml --- .github/workflows/tcrm-pylint.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tcrm-pylint.yml b/.github/workflows/tcrm-pylint.yml index 13cf3c60..d4f1bb93 100644 --- a/.github/workflows/tcrm-pylint.yml +++ b/.github/workflows/tcrm-pylint.yml @@ -25,8 +25,8 @@ jobs: pip install pylint - name: Analysing the code with pylint run: | - pylint --rcfile pylintrc --fail-under=7 `find -regextype egrep -regex '(.*.py)$'` - | tee pylint.txt + pylint --rcfile pylintrc --fail-under=7 `find -regextype egrep -regex '(.*.py)$'` | + tee pylint.txt - name: Upload pylint.txt as artifact uses: actions/upload-artifact@v2 with: From cc797b950433f1d82f8ea10dc17e3299961e8e3f Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Fri, 7 May 2021 09:50:34 +1000 Subject: [PATCH 16/80] Update tcrm-tests.yml to include Python 3.9 --- .github/workflows/tcrm-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tcrm-tests.yml b/.github/workflows/tcrm-tests.yml index c6f2096c..3e68e830 100644 --- a/.github/workflows/tcrm-tests.yml +++ b/.github/workflows/tcrm-tests.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.7, 3.8] + python-version: [3.7, 3.8, 3.9] steps: - uses: actions/checkout@v2 - name: Set up environment From 98d1492b0e6bf4dc14995b45e363a55d2d27577a Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Fri, 7 May 2021 10:58:51 +1000 Subject: [PATCH 17/80] Move definition statements to separate file --- database/__init__.py | 133 +++++++++++----------------------------- database/definitions.py | 74 ++++++++++++++++++++++ 2 files changed, 111 insertions(+), 96 deletions(-) create mode 100644 database/definitions.py diff --git a/database/__init__.py b/database/__init__.py index ca703441..f53f0a03 100644 --- a/database/__init__.py +++ b/database/__init__.py @@ -38,28 +38,33 @@ import logging import sqlite3 from sqlite3 import PARSE_DECLTYPES, PARSE_COLNAMES, IntegrityError -from functools import wraps +from functools import wraps, reduce import time from datetime import datetime import unicodedata import re +import atexit from shapely.geometry import Point -logging.getLogger('shapely').setLevel(logging.WARNING) from netCDF4 import Dataset import numpy as np +from .definitions import (TBLLOCATIONDEF, TBLEVENTSDEF, TBLWINDSPEEDDEF, + TBLHAZARDDEF, TBLTRACKSDEF, INSLOCATIONS, + INSEVENTS, INSWINDSPEED, INSHAZARD, INSTRACK, + SELECTLOCATIONS) + from Utilities.config import ConfigParser from Utilities.maputils import find_index from Utilities.track import loadTracksFromFiles from Utilities.parallel import attemptParallel, disableOnWorkers from Utilities.process import pAlreadyProcessed, pGetProcessedFiles -from functools import reduce -sqlite3.register_adapter(np.int64, lambda val: int(val)) -sqlite3.register_adapter(np.int32, lambda val: int(val)) +sqlite3.register_adapter(np.int64, int) +sqlite3.register_adapter(np.int32, int) log = logging.getLogger(__name__) log.addHandler(logging.NullHandler()) +logging.getLogger('shapely').setLevel(logging.WARNING) def fromrecords(records, names): """ Convert records to array, even if no data """ @@ -90,70 +95,6 @@ def wrap(*args, **kwargs): # pylint: disable=R0914,R0902 -# Table definition statements -# Stations - we assume a geographic coordinate system: -TBLLOCATIONDEF = ("CREATE TABLE IF NOT EXISTS tblLocations " - "(locId integer PRIMARY KEY, locCode text, " - "locName text, locType text, locLon real, " - "locLat real, locElev real, locCountry text, " - "locSource text, Comments text, " - "dtCreated timestamp)") - -# Events: -TBLEVENTSDEF = ("CREATE TABLE IF NOT EXISTS tblEvents " - "(eventNumber integer PRIMARY KEY, eventId text, " - "eventFile text, eventTrackFile text, " - "eventMaxWind real, eventMinPressure real, " - "dtTrackFile timestamp, dtWindfieldFile timestamp, " - "tcrmVersion text, Comments text, dtCreated timestamp)") - -#Station wind speed from events: -TBLWINDSPEEDDEF = ("CREATE TABLE IF NOT EXISTS tblWindSpeed " - "(locId integer, eventId text, wspd real, umax real, " - "vmax real, pmin real, Comments text, " - "dtCreated timestamp)") - -# Station hazard levels: -TBLHAZARDDEF = ("CREATE TABLE IF NOT EXISTS tblHazard " - "(locId integer, returnPeriod real, wspd real, " - " wspdUpper real, wspdLower real, loc real, " - "scale real, shape real, tcrmVersion text, " - "dtHazardFile timestamp, Comments text, " - "dtCreated timestamp)") - -# Proximity of tracks to stations: -TBLTRACKSDEF = ("CREATE TABLE IF NOT EXISTS tblTracks " - "(locId integer, eventId text, distClosest real, " - "prsClosest real, dtClosest timestamp, Comments text, " - "dtCreated timestamp)") - -# Insert statements: -# Insert locations: -INSLOCATIONS = ("INSERT OR REPLACE INTO tblLocations " - "VALUES (?,?,?,?,?,?,?,?,?,?,?)") - -# Insert event record: -INSEVENTS = "INSERT INTO tblEvents VALUES (?,?,?,?,?,?,?,?,?,?,?)" - -# Insert wind speed record: -INSWINDSPEED = ("INSERT INTO tblWindSpeed " - "VALUES (?,?,?,?,?,?,?,?)") - -# Insert hazard record: -INSHAZARD = "INSERT INTO tblHazard VALUES (?,?,?,?,?,?,?,?,?,?,?,?)" - -# Insert track record: -INSTRACK = "INSERT INTO tblTracks VALUES (?,?,?,?,?,?,?)" - -# Select statements; -# Select locations within domain: -SELECTLOCATIONS = ("SELECT * FROM tblLocations WHERE " - "locLon >= ? and locLon <= ? and " - "locLat >= ? and locLat <= ?") - -# Select locId, locLon & locLat from the subset of locations: -SELECTLOCLONLAT = "SELECT locId, locLon, locLat FROM tblLocations " - def windfieldAttributes(ncobj): """ Extract the required attributes from a netCDF file. @@ -232,8 +173,6 @@ def __init__(self, configFile): detect_types=PARSE_DECLTYPES|PARSE_COLNAMES) self.exists = True - - import atexit atexit.register(self.close) @disableOnWorkers @@ -250,7 +189,6 @@ def createDatabase(self): self.createTable('tblTracks', TBLTRACKSDEF) self.exists = True self.commit() - return @disableOnWorkers def createTable(self, tblName, tblDef): @@ -489,25 +427,6 @@ def processEvents(self): self.insertEvents(eventparams) self.insertWindSpeeds(wsparams) - - def loadWindfieldFile(self, ncobj): - """ - Load an individual dataset. - - :param str filename: filename to load. - - :returns: tuple containing longitude, latitude, wind speed, - eastward and northward components and pressure grids. - """ - lon = ncobj.variables['lon'][:] - lat = ncobj.variables['lat'][:] - vmax = ncobj.variables['vmax'][:] - ua = ncobj.variables['ua'][:] - va = ncobj.variables['va'][:] - pmin = ncobj.variables['slp'][:] - - return (lon, lat, vmax, ua, va, pmin) - def processEvent(self, filename, locations, eventNum): """ Process an individual event file @@ -522,9 +441,13 @@ def processEvent(self, filename, locations, eventNum): log.debug("Event ID: {0}".format(eventId)) try: ncobj = Dataset(pjoin(self.windfieldPath, filename)) - except: - log.warn("Cannot open {0}".\ + except IOError as excmsg: + log.warning("Cannot open {0}".\ format(pjoin(self.windfieldPath, filename))) + log.warning(excmsg) + except: + log.exception(("Failed trying to open " + f"{pjoin(self.windfieldPath, filename)}")) # First perform the event update for tblEvents: fname = pjoin(self.windfieldPath, filename) @@ -540,7 +463,7 @@ def processEvent(self, filename, locations, eventNum): "", datetime.now()) # Perform update for tblWindSpeed: - lon, lat, vmax, ua, va, pmin = self.loadWindfieldFile(ncobj) + lon, lat, vmax, ua, va, pmin = loadWindfieldFile(ncobj) ncobj.close() wsparams = list() @@ -662,8 +585,8 @@ def processTracks(self): try: result = comm.recv(source=MPI.ANY_SOURCE, tag=MPI.ANY_TAG, status=status) - except: - log.warn("Problems recieving results on node 0") + except Exception: + log.warning("Problems recieving results on node 0") d = status.source if result: @@ -720,6 +643,24 @@ def insertTracks(self, trackRecords): log.debug("Inserted {0} records into tblTracks".format(len(trackRecords))) +def loadWindfieldFile(ncobj): + """ + Load an individual dataset. + + :param str filename: filename to load. + + :returns: tuple containing longitude, latitude, wind speed, + eastward and northward components and pressure grids. + """ + lon = ncobj.variables['lon'][:] + lat = ncobj.variables['lat'][:] + vmax = ncobj.variables['vmax'][:] + ua = ncobj.variables['ua'][:] + va = ncobj.variables['va'][:] + pmin = ncobj.variables['slp'][:] + + return (lon, lat, vmax, ua, va, pmin) + def processTrack(trackfile, locations): """ Process individual track to determine distance to locations, etc. diff --git a/database/definitions.py b/database/definitions.py new file mode 100644 index 00000000..eb0321df --- /dev/null +++ b/database/definitions.py @@ -0,0 +1,74 @@ +""" +:mod:`definitions` -- table and statement definitions +===================================================== + +.. module:: definitions + :synopsis: Table definitions, insert statements and + query statements for the database module. +.. moduleauthor:: Craig Arthur + +""" + +# Table definition statements +# Stations - we assume a geographic coordinate system: +TBLLOCATIONDEF = ("CREATE TABLE IF NOT EXISTS tblLocations " + "(locId integer PRIMARY KEY, locCode text, " + "locName text, locType text, locLon real, " + "locLat real, locElev real, locCountry text, " + "locSource text, Comments text, " + "dtCreated timestamp)") + +# Events: +TBLEVENTSDEF = ("CREATE TABLE IF NOT EXISTS tblEvents " + "(eventNumber integer PRIMARY KEY, eventId text, " + "eventFile text, eventTrackFile text, " + "eventMaxWind real, eventMinPressure real, " + "dtTrackFile timestamp, dtWindfieldFile timestamp, " + "tcrmVersion text, Comments text, dtCreated timestamp)") + +#Station wind speed from events: +TBLWINDSPEEDDEF = ("CREATE TABLE IF NOT EXISTS tblWindSpeed " + "(locId integer, eventId text, wspd real, umax real, " + "vmax real, pmin real, Comments text, " + "dtCreated timestamp)") + +# Station hazard levels: +TBLHAZARDDEF = ("CREATE TABLE IF NOT EXISTS tblHazard " + "(locId integer, returnPeriod real, wspd real, " + " wspdUpper real, wspdLower real, loc real, " + "scale real, shape real, tcrmVersion text, " + "dtHazardFile timestamp, Comments text, " + "dtCreated timestamp)") + +# Proximity of tracks to stations: +TBLTRACKSDEF = ("CREATE TABLE IF NOT EXISTS tblTracks " + "(locId integer, eventId text, distClosest real, " + "prsClosest real, dtClosest timestamp, Comments text, " + "dtCreated timestamp)") + +# Insert statements: +# Insert locations: +INSLOCATIONS = ("INSERT OR REPLACE INTO tblLocations " + "VALUES (?,?,?,?,?,?,?,?,?,?,?)") + +# Insert event record: +INSEVENTS = "INSERT INTO tblEvents VALUES (?,?,?,?,?,?,?,?,?,?,?)" + +# Insert wind speed record: +INSWINDSPEED = ("INSERT INTO tblWindSpeed " + "VALUES (?,?,?,?,?,?,?,?)") + +# Insert hazard record: +INSHAZARD = "INSERT INTO tblHazard VALUES (?,?,?,?,?,?,?,?,?,?,?,?)" + +# Insert track record: +INSTRACK = "INSERT INTO tblTracks VALUES (?,?,?,?,?,?,?)" + +# Select statements; +# Select locations within domain: +SELECTLOCATIONS = ("SELECT * FROM tblLocations WHERE " + "locLon >= ? and locLon <= ? and " + "locLat >= ? and locLat <= ?") + +# Select locId, locLon & locLat from the subset of locations: +SELECTLOCLONLAT = "SELECT locId, locLon, locLat FROM tblLocations " From c967de7c08ab7f97a84bdc6e769ff46829c6c616 Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Fri, 7 May 2021 12:55:37 +1000 Subject: [PATCH 18/80] Move queries to separate file --- PlotInterface/AutoPlotHazard.py | 3 +- convergenceTest.py | 15 +- database/__init__.py | 232 +---------------------------- database/queries.py | 250 ++++++++++++++++++++++++++++++++ 4 files changed, 265 insertions(+), 235 deletions(-) create mode 100644 database/queries.py diff --git a/PlotInterface/AutoPlotHazard.py b/PlotInterface/AutoPlotHazard.py index 0d2eb505..e16bb170 100644 --- a/PlotInterface/AutoPlotHazard.py +++ b/PlotInterface/AutoPlotHazard.py @@ -40,7 +40,6 @@ from PlotInterface.curves import saveHazardCurve import sqlite3 -import unicodedata log = logging.getLogger(__name__) log.addHandler(logging.NullHandler()) @@ -225,7 +224,7 @@ def plotHazardCurves(self, inputFile, plotPath): log.debug("Saving hazard curve for %s to %s"%(name, filename)) wspd = ncobj.variables['wspd'][:, j, i] - recs = database.locationRecords(self.db, pID) + recs = database.queries.locationRecords(self.db, pID) data = np.zeros(int(self.numsimulations * 365.25)) if len(recs) > 0: data[-len(recs):] = recs['wspd'] diff --git a/convergenceTest.py b/convergenceTest.py index 30d7d87d..1b0d86bf 100644 --- a/convergenceTest.py +++ b/convergenceTest.py @@ -54,8 +54,6 @@ # Load the configuration file from the TCHA18, then open the database # and get teh list of available locations. -# In[2]: - configFile = "/home/547/cxa547/tcrmconfig/tcrm2.1.ini" config = ConfigParser() config.read(configFile) @@ -69,7 +67,6 @@ # The following step performs the calculations. First a helper # function to add nicely formatted grid lines on a logarithmic axis. -# # The second function (`plotConvergenceTest`) loads the data from the # database, then splits into two separate collections (called `d1` and # `d2`). For each of these, we then calculate empirical ARI values and @@ -88,7 +85,7 @@ def addARIGrid(axes): axes.autoscale(True, axis='x', tight=True) axes.grid(True, which='major', linestyle='-') axes.grid(True, which='minor', linestyle='--', linewidth=0.5) - + def addAEPGrid(axes): """ Add a logarithmic graticuyle to the subplot axes @@ -99,7 +96,7 @@ def addAEPGrid(axes): axes.autoscale(True, axis='y', tight=True) axes.grid(True, which='major', linestyle='-') axes.grid(True, which='minor', linestyle='--', linewidth=0.5) - + def calculateARI(data, years): emprp = empReturnPeriod(np.sort(data)) return np.sort(data)[-years:], emprp[-years:] @@ -117,7 +114,7 @@ def plotConvergenceTest(locName): locLon = locations['locLon'][locations['locId']==locId][0] locLat = locations['locLat'][locations['locId']==locId][0] - records = database.locationRecords(db, str(locId)) + records = database.queries.locationRecords(db, str(locId)) recs = records['wspd'][records['wspd'] > 0] data = np.zeros(int(NumSimulations*365.25)) data[-len(recs):] = recs @@ -133,7 +130,7 @@ def plotConvergenceTest(locName): fdelta = delta/mn fig, ax1 = plt.subplots(1, 1, figsize=figsize) - + ax1.fill_between(rr[0,:], dd[1,:], dd[0,:], alpha=0.5, label="95th percentile") ax1.plot(emprp[-10000:], data[-10000:], color='k', label="Mean ARI") ax1.set_xscale('log') @@ -213,7 +210,7 @@ def plotConvergence(ax, locName): locLon = locations['locLon'][locations['locId']==locId][0] locLat = locations['locLat'][locations['locId']==locId][0] - records = database.locationRecords(db, str(locId)) + records = database.queries.locationRecords(db, str(locId)) recs = records['wspd'][records['wspd'] > 0] data = np.zeros(int(NumSimulations*365.25)) data[-len(recs):] = recs @@ -246,5 +243,5 @@ def plotConvergence(ax, locName): axlist[7].set_xlabel('Average recurrence interval (years)') fig.tight_layout() -plt.savefig(os.path.join(plotPath, "ARI_convergence.png"), +plt.savefig(os.path.join(plotPath, "ARI_convergence.png"), bbox_inches='tight') diff --git a/database/__init__.py b/database/__init__.py index f53f0a03..c4eb9411 100644 --- a/database/__init__.py +++ b/database/__init__.py @@ -49,17 +49,17 @@ from netCDF4 import Dataset import numpy as np -from .definitions import (TBLLOCATIONDEF, TBLEVENTSDEF, TBLWINDSPEEDDEF, - TBLHAZARDDEF, TBLTRACKSDEF, INSLOCATIONS, - INSEVENTS, INSWINDSPEED, INSHAZARD, INSTRACK, - SELECTLOCATIONS) - from Utilities.config import ConfigParser from Utilities.maputils import find_index from Utilities.track import loadTracksFromFiles from Utilities.parallel import attemptParallel, disableOnWorkers from Utilities.process import pAlreadyProcessed, pGetProcessedFiles +from .definitions import (TBLLOCATIONDEF, TBLEVENTSDEF, TBLWINDSPEEDDEF, + TBLHAZARDDEF, TBLTRACKSDEF, INSLOCATIONS, + INSEVENTS, INSWINDSPEED, INSHAZARD, INSTRACK, + SELECTLOCATIONS) + sqlite3.register_adapter(np.int64, int) sqlite3.register_adapter(np.int32, int) log = logging.getLogger(__name__) @@ -133,7 +133,7 @@ def HazardDatabase(configFile): # pylint: disable=C0103 :param str configFile: Path to configuration file """ - global _singletons + global _singletons # pylint: disable=W0603 instance = _singletons.get(configFile) if not instance: instance = _HazardDatabase(configFile) @@ -255,7 +255,7 @@ def getLocations(self): locations = cur.fetchall() locations = fromrecords(locations, - names=("locId,locName,locLon,locLat")) + names=("locId,locName,locLon,locLat")) return locations def generateEventTable(self): @@ -708,7 +708,7 @@ def run(configFile): location_file = config.get('Input', 'LocationFile') buildLocationDatabase(location_db, location_file) - global MPI, comm + global MPI, comm # pylint: disable=W0601 MPI = attemptParallel() comm = MPI.COMM_WORLD db = HazardDatabase(configFile) @@ -816,219 +816,3 @@ def buildLocationDatabase(location_db, location_file, location_type='AWS'): locdb.executemany(INSLOCATIONS, locations) locdb.commit() locdb.close() - -@timer -def locationRecordsExceeding(hazard_db, locId, windSpeed): - """ - Select all records where the wind speed at the given location is - greater than some threshold. - - :param hazard_db: :class:`HazardDatabase` instance. - :param int locId: Location identifier. - :param float windSpeed: Select all records where the wind speed - at the given location is greater than - this value. - - :returns: :class:`numpy.recarray` containing the name, longitude - & latitude of the location, the wind speed of the - record, the event Id and the event file that holds the - event that generated the wind speed. - - Example:: - - >>> db = HazardDatabase(configFile) - >>> locId = 00001 - >>> records = locationRecordsExceeding(db, locId, 47.) - - """ - - query = ("SELECT l.locId, l.locName, w.wspd, w.eventId " - "FROM tblLocations l " - "INNER JOIN tblWindSpeed w ON l.locId = w.locId " - "WHERE w.wspd > ? and l.locId = ? " - "ORDER BY w.wspd ASC") - - cur = hazard_db.execute(query, (windSpeed, locId,)) - results = cur.fetchall() - results = fromrecords(results, names=('locId,locName,wspd,eventId')) - - return results - -@timer -def locationRecords(hazard_db, locId): - """ - Select all wind speed records for a given location. - - :param hazard_db: :class:`HazardDatabase` instance. - :param int locId: Location identifier. - - :returns: :class:`numpy.recarray` containing the location id, location - name, wind speed and event id. - - """ - - query = ("SELECT w.locId, l.locName, w.wspd, w.umax, w.vmax, w.eventId " - "FROM tblWindSpeed w " - "INNER JOIN tblLocations l " - "ON w.locId = l.locId " - "WHERE l.locId = ? ORDER BY w.wspd ASC") - cur = hazard_db.execute(query, (locId,)) - results = cur.fetchall() - results = fromrecords(results, - names=('locId,locName,wspd,umax,vmax,eventId')) - - return results - -@timer -def locationPassage(hazard_db, locId, distance=50): - """ - Select all records from tblTracks that pass within a defined - distance of the given location - - :param hazard_db: :class:`HazardDatabase` instance. - :param int locId: Location identifier. - :param distance: Distance threshold (in kilometres). - - :returns: :class:`numpy.recarray` containing the location id, location - name, event id, closest distance of approach, wind speed and - event file for all events that pass within the defined - distance of the selected location. - - Example:: - - >>> db = HazardDatabase(configFile) - >>> locId = 000001 - >>> records = locationPassage(db, locId, 50) - - """ - - query = ("SELECT l.locId, l.locName, t.eventId, t.distClosest, " - "w.wspd, e.eventFile FROM tblLocations l " - "INNER JOIN tblTracks t " - "ON l.locId = t.locId " - "JOIN tblWindSpeed w on w.eventId = t.eventId " - "JOIN tblEvents e on e.eventId = t.eventId " - "WHERE t.distClosest < ? and l.locId = ?") - cur = hazard_db.execute(query, (distance, locId)) - results = cur.fetchall() - results = fromrecords(results, - names=('locId,locName,eventId,' - 'distClosest,wspd,eventFile')) - return results - -@timer -def locationPassageWindSpeed(hazard_db, locId, speed, distance): - """ - Select records from _tblWindSpeed_, _tblTracks_ and _tblEvents_ that - generate a defined wind speed and pass within a given distance - of the location. - - :param hazard_db: :class:`HazardDatabase` instance. - :param int locId: Location identifier. - :param float speed: Minimum wind speed (m/s). - :param float distance: Distance threshold (kilometres). - - """ - - query = ("SELECT l.locName, w.wspd, w.umax, w.vmax, w.eventId, " - "t.distClosest, e.eventMaxWind, e.eventMinPressure " - "FROM tblLocations l " - "JOIN tblWindSpeed w on l.locId = w.locId " - "JOIN tblEvents e ON e.eventId = w.eventId " - "JOIN tblTracks t ON w.locId = t.locId AND w.eventId = t.eventId " - "WHERE l.locId = ? and w.wspd > ? AND t.distClosest <= ? " - "ORDER BY w.wspd ASC") - - cur = hazard_db.execute(query, (locId, speed, distance)) - results = cur.fetchall() - results = fromrecords(results, - names=('locName,wspd,umax,vmax,eventId,' - 'distClosest,maxwind,pmin')) - - return results - -@timer -def locationReturnPeriodEvents(hazard_db, locId, return_period): - """ - Select all records from tblEvents where the wind speed is - greater than the return period wind speed for the given return period. - - :param hazard_db: :class:`HazardDatabase` instance. - :param int locId: Location identifier. - :param int return_period: Nominated return period. - - :returns: :class:`numpy.recarray` of location id and wind speeds of - all events that are greater than the return level of the - nominated return period. - - The following example would return the wind speeds of all events that - exceed the 500-year return period wind speed for the selected location. - - Example:: - - >>> db = HazardDatabase(configFile) - >>> locId = 000001 - >>> records = locationReturnPeriodEvents(db, locId, 500) - - """ - - query = ("SELECT l.locId, h.wspd FROM tblLocations l " - "INNER JOIN tblHazard h ON l.locId = h.locId " - "WHERE h.returnPeriod = ? and l.locId = ?") - cur = hazard_db.execute(query, (return_period, locId)) - row = cur.fetchall() - return_level = row[0][1] - results = locationRecordsExceeding(hazard_db, locId, return_level) - - return results - -@timer -def locationAllReturnLevels(hazard_db, locId): - """ - Select all return level wind speeds (including upper and lower - confidence intervals) for a selected location. - - :param hazard_db: :class:`HazardDatabase` instance. - :param int locId: Location identifier. - - :returns: :class:`numpy.recarray` containing the location id, location - name, return period, return period windspeed and lower/upper - estimates of the return period wind speed. - - """ - - query = ("SELECT l.locId, l.locName, h.returnPeriod, h.wspd, " - "h.wspdLower, h.wspdUpper " - "FROM tblLocations l INNER JOIN tblHazard h " - "ON l.locId = h.locId " - "WHERE l.locId = ? " - "ORDER BY h.returnPeriod") - - cur = hazard_db.execute(query, (locId,)) - results = cur.fetchall() - results = fromrecords(results, - names=('locId,locName,returnPeriod,' - 'wspd,wspdLower,wspdUpper')) - - return results - -@timer -def selectEvents(hazard_db): - """ - Select all events from _tblEvents_. - - :param hazard_db: :class:`HazardDatabase` instance. - - :returns: :class:`numpy.recarray` containing the full listing of each - event in the table. - - """ - - query = "SELECT * FROM tblEvents ORDER BY eventMaxWind ASC" - cur = hazard_db.execute(query) - results = cur.fetchall() - names = ("eventNum,eventId,eventFile,eventTrackFile,eventMaxWind," - "eventMinPressure,dtTrackFile,dtWindfieldFile,tcrmVer," - "Comments,dtCreated") - results = fromrecords(results, names=names) - return results diff --git a/database/queries.py b/database/queries.py new file mode 100644 index 00000000..6e93edba --- /dev/null +++ b/database/queries.py @@ -0,0 +1,250 @@ +import time +import logging as log +from functools import wraps, reduce + +import numpy as np + +def fromrecords(records, names): + """ Convert records to array, even if no data """ + # May become redundant after https://github.com/numpy/numpy/issues/1862 + if records: + rval = np.rec.fromrecords(records, names=names) + else: + rval = np.array([], [(name, 'O') for name in names.split(',')]) + + return rval + +def timer(func): + """ + A simple timing decorator for the entire process. + + """ + + @wraps(func) + def wrap(*args, **kwargs): + t1 = time.time() + res = func(*args, **kwargs) + tottime = time.time() - t1 + msg = "%02d:%02d:%02d " % \ + reduce(lambda ll, b: divmod(ll[0], b) + ll[1:], + [(tottime,), 60, 60]) + log.debug("Time for {0}: {1}".format(func.__name__, msg)) + return res + + return wrap + +@timer +def locationRecordsExceeding(hazard_db, locId, windSpeed): + """ + Select all records where the wind speed at the given location is + greater than some threshold. + + :param hazard_db: :class:`HazardDatabase` instance. + :param int locId: Location identifier. + :param float windSpeed: Select all records where the wind speed + at the given location is greater than + this value. + + :returns: :class:`numpy.recarray` containing the name, longitude + & latitude of the location, the wind speed of the + record, the event Id and the event file that holds the + event that generated the wind speed. + + Example:: + + >>> db = HazardDatabase(configFile) + >>> locId = 00001 + >>> records = locationRecordsExceeding(db, locId, 47.) + + """ + + query = ("SELECT l.locId, l.locName, w.wspd, w.eventId " + "FROM tblLocations l " + "INNER JOIN tblWindSpeed w ON l.locId = w.locId " + "WHERE w.wspd > ? and l.locId = ? " + "ORDER BY w.wspd ASC") + + cur = hazard_db.execute(query, (windSpeed, locId,)) + results = cur.fetchall() + results = fromrecords(results, names=('locId,locName,wspd,eventId')) + + return results + +@timer +def locationRecords(hazard_db, locId): + """ + Select all wind speed records for a given location. + + :param hazard_db: :class:`HazardDatabase` instance. + :param int locId: Location identifier. + + :returns: :class:`numpy.recarray` containing the location id, location + name, wind speed and event id. + + """ + + query = ("SELECT w.locId, l.locName, w.wspd, w.umax, w.vmax, w.eventId " + "FROM tblWindSpeed w " + "INNER JOIN tblLocations l " + "ON w.locId = l.locId " + "WHERE l.locId = ? ORDER BY w.wspd ASC") + cur = hazard_db.execute(query, (locId,)) + results = cur.fetchall() + results = fromrecords(results, + names=('locId,locName,wspd,umax,vmax,eventId')) + + return results + +@timer +def locationPassage(hazard_db, locId, distance=50): + """ + Select all records from tblTracks that pass within a defined + distance of the given location + + :param hazard_db: :class:`HazardDatabase` instance. + :param int locId: Location identifier. + :param distance: Distance threshold (in kilometres). + + :returns: :class:`numpy.recarray` containing the location id, location + name, event id, closest distance of approach, wind speed and + event file for all events that pass within the defined + distance of the selected location. + + Example:: + + >>> db = HazardDatabase(configFile) + >>> locId = 000001 + >>> records = locationPassage(db, locId, 50) + + """ + + query = ("SELECT l.locId, l.locName, t.eventId, t.distClosest, " + "w.wspd, e.eventFile FROM tblLocations l " + "INNER JOIN tblTracks t " + "ON l.locId = t.locId " + "JOIN tblWindSpeed w on w.eventId = t.eventId " + "JOIN tblEvents e on e.eventId = t.eventId " + "WHERE t.distClosest < ? and l.locId = ?") + cur = hazard_db.execute(query, (distance, locId)) + results = cur.fetchall() + results = fromrecords(results, + names=('locId,locName,eventId,' + 'distClosest,wspd,eventFile')) + return results + +@timer +def locationPassageWindSpeed(hazard_db, locId, speed, distance): + """ + Select records from _tblWindSpeed_, _tblTracks_ and _tblEvents_ that + generate a defined wind speed and pass within a given distance + of the location. + + :param hazard_db: :class:`HazardDatabase` instance. + :param int locId: Location identifier. + :param float speed: Minimum wind speed (m/s). + :param float distance: Distance threshold (kilometres). + + """ + + query = ("SELECT l.locName, w.wspd, w.umax, w.vmax, w.eventId, " + "t.distClosest, e.eventMaxWind, e.eventMinPressure " + "FROM tblLocations l " + "JOIN tblWindSpeed w on l.locId = w.locId " + "JOIN tblEvents e ON e.eventId = w.eventId " + "JOIN tblTracks t ON w.locId = t.locId AND w.eventId = t.eventId " + "WHERE l.locId = ? and w.wspd > ? AND t.distClosest <= ? " + "ORDER BY w.wspd ASC") + + cur = hazard_db.execute(query, (locId, speed, distance)) + results = cur.fetchall() + results = fromrecords(results, + names=('locName,wspd,umax,vmax,eventId,' + 'distClosest,maxwind,pmin')) + + return results + +@timer +def locationReturnPeriodEvents(hazard_db, locId, return_period): + """ + Select all records from tblEvents where the wind speed is + greater than the return period wind speed for the given return period. + + :param hazard_db: :class:`HazardDatabase` instance. + :param int locId: Location identifier. + :param int return_period: Nominated return period. + + :returns: :class:`numpy.recarray` of location id and wind speeds of + all events that are greater than the return level of the + nominated return period. + + The following example would return the wind speeds of all events that + exceed the 500-year return period wind speed for the selected location. + + Example:: + + >>> db = HazardDatabase(configFile) + >>> locId = 000001 + >>> records = locationReturnPeriodEvents(db, locId, 500) + + """ + + query = ("SELECT l.locId, h.wspd FROM tblLocations l " + "INNER JOIN tblHazard h ON l.locId = h.locId " + "WHERE h.returnPeriod = ? and l.locId = ?") + cur = hazard_db.execute(query, (return_period, locId)) + row = cur.fetchall() + return_level = row[0][1] + results = locationRecordsExceeding(hazard_db, locId, return_level) + + return results + +@timer +def locationAllReturnLevels(hazard_db, locId): + """ + Select all return level wind speeds (including upper and lower + confidence intervals) for a selected location. + + :param hazard_db: :class:`HazardDatabase` instance. + :param int locId: Location identifier. + + :returns: :class:`numpy.recarray` containing the location id, location + name, return period, return period windspeed and lower/upper + estimates of the return period wind speed. + + """ + + query = ("SELECT l.locId, l.locName, h.returnPeriod, h.wspd, " + "h.wspdLower, h.wspdUpper " + "FROM tblLocations l INNER JOIN tblHazard h " + "ON l.locId = h.locId " + "WHERE l.locId = ? " + "ORDER BY h.returnPeriod") + + cur = hazard_db.execute(query, (locId,)) + results = cur.fetchall() + results = fromrecords(results, + names=('locId,locName,returnPeriod,' + 'wspd,wspdLower,wspdUpper')) + + return results + +@timer +def selectEvents(hazard_db): + """ + Select all events from _tblEvents_. + + :param hazard_db: :class:`HazardDatabase` instance. + + :returns: :class:`numpy.recarray` containing the full listing of each + event in the table. + + """ + + query = "SELECT * FROM tblEvents ORDER BY eventMaxWind ASC" + cur = hazard_db.execute(query) + results = cur.fetchall() + names = ("eventNum,eventId,eventFile,eventTrackFile,eventMaxWind," + "eventMinPressure,dtTrackFile,dtWindfieldFile,tcrmVer," + "Comments,dtCreated") + results = fromrecords(results, names=names) + return results From 5d3312665df12856d5dfb4886ac791d9effe019b Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Fri, 9 Jul 2021 16:33:45 +1000 Subject: [PATCH 19/80] Add readthedocs config file, update pylintrc --- .readthedocs.yaml | 22 ++++++++++++++++++++++ Utilities/loadData.py | 3 +-- pylintrc | 2 +- wind/__init__.py | 8 ++++---- 4 files changed, 28 insertions(+), 7 deletions(-) create mode 100644 .readthedocs.yaml diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..c0c9b0d5 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,22 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Build documentation in the docs/ directory with Sphinx +sphinx: + builder: html + configuration: conf.py + +# Optionally build your docs in additional formats such as PDF +formats: + - pdf + +# Optionally set the version of Python and requirements required to build your docs +python: + version: 3.7 + +conda: + environment: tcrmenv.yml \ No newline at end of file diff --git a/Utilities/loadData.py b/Utilities/loadData.py index 94f9124e..a2eb5d79 100644 --- a/Utilities/loadData.py +++ b/Utilities/loadData.py @@ -194,7 +194,7 @@ def getSpeedBearing(index, lon, lat, deltatime, ieast=1, speed = dist / deltatime # Delete speeds less than 0, greated than 200, # or where indicator == 1. - np.putmask(speed, (speed < 0), missingValue) + np.putmask(speed, (speed < 0), missingValue) np.putmask(speed, (speed > 200), missingValue) np.putmask(speed, index, missingValue) np.putmask(speed, np.isnan(speed), missingValue) @@ -360,7 +360,6 @@ def getInitialPositions(data): except ValueError: LOG.error("'num' field cannot be converted to an integer") - raise KeyError(('Insufficient input file columns have been specified' 'Check the input file has enough fields to determine' 'TC starting positions')) diff --git a/pylintrc b/pylintrc index abad3386..06de6f8f 100755 --- a/pylintrc +++ b/pylintrc @@ -1,5 +1,5 @@ [MESSAGES CONTROL] -disable=W0511,W0142,W1202,I0011,E1003,E1101,E0611,F0401,E1103,E1121 +disable=W0511,W0142,W1201,W1202,I0011,E1003,E1101,E0611,F0401,E1103,E1121,logging-fstring-interpolation [BASIC] attr-rgx=[a-zA-Z_][a-zA-Z0-9_]{0,30}[_]{0,1}$ diff --git a/wind/__init__.py b/wind/__init__.py index f225dac9..b6d24b33 100644 --- a/wind/__init__.py +++ b/wind/__init__.py @@ -7,9 +7,9 @@ primary vortex of the simulated TC, and bounday layer models that define the asymmetry induced by surface friction and forward motion of the TC over the earth's surface. The final output from the module is a -netCDF file containing the maximum surface gust wind speed (a 10-minute -mean wind speed, at 10 metres above ground level), along with the components -(eastward and westward) that generated the wind gust and the minimum +netCDF file containing the maximum surface gust wind speed (a 0.2-second +duration gust wind speed, at 10 metres above ground level), along with the +components (eastward and westward) that generated the wind gust and the minimum mean sea level pressure over the lifetime of the event. If multiple TCs are contained in a track file, then the output file contains the values from all events (for example, an annual maximum wind speed). @@ -759,7 +759,7 @@ def filterTracks(tracks, gridLimit, margin): log.info(f"Filtering tracks in region: {repr(gridLimit)}") validTracks = [t for t in tracks if inRegion(t, gridLimit, margin)] else: - log.info(f"No grid limit set - returning all tracks") + log.info("No grid limit set - returning all tracks") return tracks return validTracks From b95ddc44b70cf082b5a7befe739dc3e284ac15b5 Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Fri, 9 Jul 2021 16:39:26 +1000 Subject: [PATCH 20/80] Validation (#115) * Add script to run through all permutations of wind parameters --- Evaluate/windFieldValidation.py | 276 ++++++++++++++++++++++++++++++++ 1 file changed, 276 insertions(+) create mode 100644 Evaluate/windFieldValidation.py diff --git a/Evaluate/windFieldValidation.py b/Evaluate/windFieldValidation.py new file mode 100644 index 00000000..4553b6eb --- /dev/null +++ b/Evaluate/windFieldValidation.py @@ -0,0 +1,276 @@ +import itertools +#import matplotlib.pyplot as plt +#from matplotlib import cm as cmap +import numpy as np + +import xarray as xr +#import seaborn as sns + +from wind import windmodels +from Utilities import metutils +from Utilities.maputils import bearing2theta, makeGrid, meshLatLon +from Utilities.parallel import attemptParallel + + +#sns.set_style('ticks', {'image.cmap':'coolwarm'}) +#sns.set_context('poster') +#palette = [(1, 1, 1), (0.000, 0.627, 0.235), (0.412, 0.627, 0.235), (0.663, 0.780, 0.282), +# (0.957, 0.812, 0.000), (0.925, 0.643, 0.016), (0.835, 0.314, 0.118), +# (0.780, 0.086, 0.118)] +#cmap = sns.blend_palette(palette, as_cmap=True) + +def polarGridAroundEye(lon, lat, margin=2, resolution=0.02): + R, theta = makeGrid(lon, lat, margin, resolution) + return R, theta + +def meshGrid(lon, lat, margin=2, resolution=0.02): + xgrid, ygrid = meshLatLon(lon, lat, margin, resolution) + return xgrid, ygrid + +def calculateWindField(lon, lat, pEnv, pCentre, rMax, vFm, thetaFm, beta, + profileType='powell', windFieldType='kepert'): + + pCentre = metutils.convert(pCentre, 'hPa', 'Pa') + pEnv = metutils.convert(pEnv, 'hPa', 'Pa') + vFm = metutils.convert(vFm, 'kmh', 'mps') + thetaFm = bearing2theta(np.pi * thetaFm / 180.) + thetaMax = 70. + rmax = metutils.convert(rMax, 'km', 'm') + cls = windmodels.profile(profileType) + if profileType=="holland": + profile = cls(lat, lon, pEnv, pCentre, rmax, beta) + else: + profile = cls(lat, lon, pEnv, pCentre, rmax) + R, theta = polarGridAroundEye(lon, lat, 5.) + gradV = profile.velocity(R*1000) + cls = windmodels.field(windFieldType) + windfield = cls(profile) + Ux, Vy = windfield.field(R*1000, theta, vFm, thetaFm, thetaMax) + + surfV = np.sqrt(Ux*Ux+Vy*Vy)*1.268 # Gust conversion factor + return gradV, surfV + +""" +lat = np.arange(-30, -4, 2, dtype=float) +pc = np.arange(900, 991, 5, dtype=float) +pe = np.arange(995, 1016, dtype=float) +rm = np.arange(10, 91, 5, dtype=float) +vfm = np.arange(0, 51, 5, dtype=float) +gwind = np.zeros((len(lat), len(pc), len(pe), len(rm), len(vfm))) +swind = np.zeros((len(lat), len(pc), len(pe), len(rm), len(vfm))) +it = np.nditer(gwind, flags=['multi_index']) +nn = gwind.size +print(nn) + +lon = 120. +thetaFm = 70 +beta = 1.6 +profileType = "powell" +blmodel = "kepert" +i = 0 + + + +for x in it: + il, ic, ip, ir, iv = it.multi_index + gradV, surfV = calculateWindField(lon, lat[il], pe[ip], pc[ic], + rm[ir], vfm[iv], thetaFm, beta, + profileType=profileType, + windFieldType=blmodel) + gwind[it.multi_index] = np.max(gradV) + swind[it.multi_index] = np.max(surfV) + i += 1 + print(f"{100*i/nn:0.4f} %") + +coords = [ + ("latitude", lat, dict(long_name="Latitude", + units="degrees_south")), + ("pcentre", pc, dict(long_name="Central pressure", + units="hPa")), + ("penv", pe, dict(long_name="Environmental pressure", + units="hPa")), + ("rmax", rm, dict(long_name="Radius to maximum winds", + units="km")), + ("vfm", vfm, dict(long_name="Forward speed", + units="km/h")) +] + +dims = ["latitude", 'pcentre', 'penv', 'rmax', 'vfm'] +gattrs = { + "long_name": "Gradient level wind speed", + "profile": profileType, + "blmodel": blmodel, + "description": "maximum gradient level wind speed", + "units": "m s-1", + } +sattrs = { + "long_name": "Surface wind speed", + "profile": profileType, + "blmodel": blmodel, + "description": "maximum 0.2-s wind gust", + "units": "m s-1", + } + + +gda = xr.DataArray(gwind, dims=dims, coords=coords, attrs=gattrs) +sda = xr.DataArray(swind, dims=dims, coords=coords, attrs=sattrs) +ds = xr.Dataset() +ds['gradwind'] = gda +ds['surfwind'] = sda +ds.to_netcdf("output.nc") +""" + +def balanced(iterable): + """ + Balance an iterator across processors. + + This partitions the work evenly across processors. However, it + requires the iterator to have been generated on all processors + before hand. This is only some magical slicing of the iterator, + i.e., a poor man version of scattering. + """ + P, p = MPI.COMM_WORLD.size, MPI.COMM_WORLD.rank + return itertools.islice(iterable, p, None, P) + +def run(): + lat = np.arange(-30, -4, 2, dtype=float) + pc = np.arange(900, 991, 5, dtype=float) + pe = np.arange(995, 1016, dtype=float) + rm = np.arange(10, 91, 5, dtype=float) + vfm = np.arange(0, 51, 5, dtype=float) + gwind = np.zeros((len(lat), len(pc), len(pe), len(rm), len(vfm))) + swind = np.zeros((len(lat), len(pc), len(pe), len(rm), len(vfm))) + it = np.nditer(gwind, flags=['multi_index']) + nn = gwind.size + #print(nn) + + lon = 120. + thetaFm = 70 + beta = 1.6 + profileType = "powell" + blmodel = "kepert" + i = 0 + + # Attempt to start the track generator in parallel + global MPI + MPI = attemptParallel() + comm = MPI.COMM_WORLD + + status = MPI.Status() + worktag = 0 + resulttag = 1 + idx = [it.multi_index for x in it] + + if (comm.rank == 0) and (comm.size > 1): + w = 0 + p = comm.size -1 + for d in range(1, comm.size): + print(w) + if w < len(idx): + comm.send(idx[w], dest=d, tag=worktag) + w += 1 + else: + comm.send(None, dest=d, tag=worktag) + p = w + + terminated = 0 + + while terminated < p: + try: + result = comm.recv(source=MPI.ANY_SOURCE, tag=MPI.ANY_TAG, status=status) + except Exception: + pass + + d = status.source + if result: + gV, sV, workidx = result + gwind[workidx] = gV + swind[workidx] = sV + #gwind[idx[w]], swind[idx[w]] = result + + if w < len(idx): + comm.send(idx[w], dest=d, tag=worktag) + w += 1 + else: + comm.send(None, dest=d, tag=worktag) + terminated += 1 + + elif (comm.rank != 0) and (comm.size > 1): + while True: + workidx = comm.recv(source=0, tag=worktag, status=status) + if workidx is None: + break + il, ic, ip, ir, iv = workidx + print(f"Processing {workidx}") + gradV, surfV = calculateWindField(lon, lat[il], pe[ip], pc[ic], + rm[ir], vfm[iv], thetaFm, beta, + profileType=profileType, + windFieldType=blmodel) + results = (np.max(np.abs(gradV)), np.max(surfV), workidx) + comm.send(results, dest=0, tag=resulttag) + + elif (comm.rank == 0) and (comm.size == 1): + for x in idx: + il, ic, ip, ir, iv = x + print(lat[il], pc[ic], pe[ip], rm[ir], vfm[iv]) + gradV, surfV = calculateWindField(lon, lat[il], pe[ip], pc[ic], + rm[ir], vfm[iv], thetaFm, beta, + profileType=profileType, + windFieldType=blmodel) + gwind[x] = np.max(np.abs(gradV)) + swind[x] = np.max(surfV) + + comm.barrier() + + coords = [ + ("latitude", lat, dict(long_name="Latitude", + units="degrees_south")), + ("pcentre", pc, dict(long_name="Central pressure", + units="hPa")), + ("penv", pe, dict(long_name="Environmental pressure", + units="hPa")), + ("rmax", rm, dict(long_name="Radius to maximum winds", + units="km")), + ("vfm", vfm, dict(long_name="Forward speed", + units="km/h")) + ] + + dims = ["latitude", 'pcentre', 'penv', 'rmax', 'vfm'] + gattrs = { + "long_name": "Gradient level wind speed", + "profile": profileType, + "blmodel": blmodel, + "description": "maximum gradient level wind speed", + "units": "m s-1", + } + sattrs = { + "long_name": "Surface wind speed", + "profile": profileType, + "blmodel": blmodel, + "description": "maximum 0.2-s wind gust", + "units": "m s-1", + } + + if comm.rank == 0: + gda = xr.DataArray(gwind, dims=dims, coords=coords, attrs=gattrs) + sda = xr.DataArray(swind, dims=dims, coords=coords, attrs=sattrs) + ds = xr.Dataset() + ds['gradwind'] = gda + ds['surfwind'] = sda + ds.to_netcdf("output.nc") + + MPI.Finalize() + +if __name__ == '__main__': + print("Starting") + global MPI, comm + print("Initialiszing MPI") + MPI = attemptParallel() + #import atexit + #atexit.register(MPI.Finalize) + comm = MPI.COMM_WORLD + + print("Executing run()") + run() + + #MPI.Finalize() From 5e8d138f9d16748297457a9295fd4ccf6a1b5f34 Mon Sep 17 00:00:00 2001 From: wcarthur Date: Fri, 20 Aug 2021 17:56:47 +1000 Subject: [PATCH 21/80] Added an extra station --- input/stationlist.dbf | Bin 14908389 -> 14909465 bytes input/stationlist.shp | Bin 388404 -> 388432 bytes input/stationlist.shx | Bin 111044 -> 111052 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/input/stationlist.dbf b/input/stationlist.dbf index 49cf4b445736c5513d40523d91c0517b272de3bd..2a8632a0f9ade57a024d878d4294be2a3b9af955 100644 GIT binary patch delta 774 zcmYkwRd){n06_6Gb`4Y0Z8|r@u*vD}?(XicakYuzZ@Rm?ySqDv<1=`1)Hxpf-tPVO zeMv~t%!Ck9PD&jbF$)Qj6p~VcI!P}XB%@@KFv%=gB&%eT?2G;llsy?8cHK+EKQ`TG?V7iLRv~IX)SG}t+bQ&(m^^( zC+RF*q^oq3?$SegN-yaxeWb7Slm0S52Ff5AEJI|d43pt9LPp9c87*UEtc;WKGC?NF zB$+HzWU5S)=`urR$}EYO*)m6zxiU}Y%K}*_i)67Zkpx*P%VfE%kVIK2t7NqVWQ`=r zT3ILSWrJ*#O|n_G$X3}V+hvFBlwGo0_Q+n@C;R1q9F#+HSdPe1IVQ*Dgq)O9a$3&F zSve=?<$_$4OLAGR$W^%}*X4%Xlv{FJ?#NxaC->!nJd{WBSf0pJc_z>0g}jtk@><@= zTX`q%<%4{bPm(O3<;w}aCQk{xi@z8e2u_au8B{M2_F%$~#7jkf1(MGk_?Z|NM45<~ b$e7qlQU8ShQ7$?rGCH>W|LgA#kIMZUiSYg9 delta 699 zcmWN^ReKHq06^hq=J@c5>8>$3-CdiU?(XiInAmVIv+0=bZl=4tJBP<_aBJ_~n;M@I z`1>z8Kxk|*7@2^CNG1uD%#zV85|FHtO|nZ+a!5|eCAlR`@e}BQdz1}=_mbV zfDDvDGFXPlP#Gq}WrU2BQ8HS_$XFRC<7I+Olu0sKrpQ#8Cevkx%#>LYE3;*e%$0c( zC-Y^2ER;nOFA1_(mdH|3mdSEiA&Ih5l4O-6ONy+PHL_OL$$Hr!8)cJhmMxMhTVicAa#BvoX*nZj<(!MD{@t? z$#uCQH|3VxmOFA+?#X?5AP?n{JeDW&RG!Imc_A<5mAsZW@>br-d-)(A<&%7tFY;Bs T$#?l7KP6p$$?r7&q{saShdkzF diff --git a/input/stationlist.shp b/input/stationlist.shp index e99b17da35dfece8a9544063d57638d1e31a7a87..0cd3be6701ff9584e07241d105bbdebb7614e483 100644 GIT binary patch delta 58 zcmdmTOZ>tu@d?t5D;i~5Wf)s!m|A6+TV+_b%CJhjm0@5o(`8^_;9>+4(rPZ>&-5ob Nq#tK>)j6$r000Q`5flIb delta 29 lcmca`OMJ^M@d?t5vl?YuWf)s!m|A6+TV+_b%CJhj1pucP39|qI diff --git a/input/stationlist.shx b/input/stationlist.shx index f577b4f36c17ed2a360c5451c40a3298ae4452c8..9c97171234f072a98efa3a15cc135a1891057ed8 100644 GIT binary patch delta 26 icmX@|nC;ACwh7XV&l+X6$}q0j$icw$Z59Iq0~Y|A1PNgP delta 17 ZcmX@}nC-}8wh7XVj~ZpR$}q0j2mnhl2e|+M From d9ccb67d0210093d39be23d2d14b4f4ff99fe97c Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Thu, 26 Aug 2021 21:33:00 +1000 Subject: [PATCH 22/80] Additional locations --- input/stationlist.dbf | Bin 14908389 -> 14911615 bytes input/stationlist.shp | Bin 388404 -> 388488 bytes input/stationlist.shx | Bin 111044 -> 111068 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/input/stationlist.dbf b/input/stationlist.dbf index 49cf4b445736c5513d40523d91c0517b272de3bd..7ce421c97354c97d3a1376415015946cafc8b68d 100644 GIT binary patch delta 908 zcmZ9GSyU7R7=;hZ2N+qBQKIFN*b2i8%)n5FqPY}UX`5Q9WR_NHi>0PzlApFHqEk*~ z8<;H`nhO>!N@`jer7c$CLUnrTy&m*ngyQjZ-~N04``!EJKTA@NS68KyG&?OTZ$6SB ziIODA(srgus-#ILIYZLrOzA9V$=T9HGUOcTD(6b3oG0f?H_4Lj(nBtg3#F&@lHSrs zE|R{IE&b$TxkN6N%j9yoLUJTm0+J^|2}!;bNLV6LC`A&LV!2YTlKyhFl*j-XD5Ww; z2Fo=vM25;R87|k#by6nfGD5DG8{|eADWl{jxmj+JTje$xEn{S?+%9*>oia|w%LJ*A zyJVu=EtBLPxmPC36uD3Cmj`63Oq1#IpgbfG%Of&F9+k&rrp%Jr^0>^Ax$=ZODf8qh znJ<;{v@8&1p)8VTE^o-2 zQX{ppO5T#y^0vGq@5+0!M%K#vQYY(Vy?h`WWTR}7%~CI0ZZX^(C>kOU9v_m4nKqwq3I*IwuYFB>ue*r62ERO&H delta 699 zcmWN^Q+pNw06^iV*6FLhu$Ij&E!$e&vTYj+ORG+pTDEO2+qSvt={Iy)y?5{5)Re&Q zKgj_?VuQiRcqD^lln}`z|C?C?l0~vgHVI00$ssuM z8cAbmB2A^4G?y0AQlg}lw3ar~R@zB>=^)V(BORrabe1mCRk}%c=^;I(m-LoC(pUOP ze;FVHWsnS(Au?2k$#5AVBW09~mN7C`#>se@AQNShOqMA!Ri?>wnIW+St80(StiS6g(OI#BuTQQ$Vy3-RkB*v$XZz^>t%y%lufc(w#Zi5 zCfj9)?37)yTlUCa*(dwufE<)Va#)VYQ8^~Z<%FD+Q*v6)$XQ8~b8=oT$VIs%m*t9F zm1}ZcZpcl!CAZ~{+?9KBUmnOqc_feJi9D5O@?2iXOL--)<&C_Rck*67$Vd4kpXG~u Rm2dK0(&dNzOygI2+&{@}=Gp)N diff --git a/input/stationlist.shp b/input/stationlist.shp index e99b17da35dfece8a9544063d57638d1e31a7a87..314bb5ecd3b746aad8c8ce69a214338f9cd01e38 100644 GIT binary patch delta 114 zcmdmTOT6Q@_ylRjBaJexGK{SYP?Q0F={%$rW@~9$6Ki@ delta 17 ZcmccfnC-}8wh7XVj~ZpR$}q0j2mnk$2gv{c From f205f46db9187f48f12082d1c2f9a7feb92d45c0 Mon Sep 17 00:00:00 2001 From: mahmudulhasanGA <66761046+mahmudulhasanGA@users.noreply.github.com> Date: Wed, 3 Nov 2021 11:17:04 +1100 Subject: [PATCH 23/80] Nhirs 148 fix thread (#119) * NHIRS-148: Using queue instead of massive number of threads. * NHIRS-148: Re-organised imports. * NHIRS-148: Added logging * NHIRS-148: Removed unnecessary usage of threading. * NHIRS-148: FlushCache added on each 1% progress and at the end. Co-authored-by: Craig Arthur Co-authored-by: Craig Arthur --- ProcessMultipliers/processMultipliers.py | 85 ++++++++++++------------ 1 file changed, 41 insertions(+), 44 deletions(-) diff --git a/ProcessMultipliers/processMultipliers.py b/ProcessMultipliers/processMultipliers.py index be86bc6d..0b187c36 100755 --- a/ProcessMultipliers/processMultipliers.py +++ b/ProcessMultipliers/processMultipliers.py @@ -53,35 +53,34 @@ """ -from shutil import copyfile, rmtree import glob +import logging as log +import math import os -from os.path import join as pjoin, dirname, realpath, isdir, splitext +import queue +import tempfile +import threading import time -import logging as log -import argparse import traceback +from concurrent import futures from functools import wraps, reduce +from os.path import join as pjoin, dirname, realpath, isdir, splitext +from shutil import copyfile -from Utilities.files import flStartLog -from Utilities.config import ConfigParser -from Utilities import pathLocator -from Utilities.AsyncRun import AsyncRun - +import argparse +import boto3 import numpy as np import numpy.ma as ma - +from botocore.exceptions import ClientError +from netCDF4 import Dataset from osgeo import osr, gdal, gdalconst from osgeo.gdal_array import BandReadAsArray, CopyDatasetInfo, BandWriteArray -from netCDF4 import Dataset +from Utilities import pathLocator +from Utilities.AsyncRun import AsyncRun +from Utilities.config import ConfigParser +from Utilities.files import flStartLog -import boto3 -from botocore.exceptions import ClientError -import tempfile -import math -import threading -from concurrent import futures threadLock_gust = threading.Lock() threadLock_bear = threading.Lock() threadLock_m4 = threading.Lock() @@ -955,7 +954,7 @@ def processMultV2(wspd, uu, vv, lon, lat, working_dir, dirns, log.debug('Create rasters from the netcdf gust file variables') wind_raster_file = pjoin(working_dir, 'region_wind.tif') wind_raster = createRaster(np.flipud(wspd), lon, lat, delta, delta, - filename = wind_raster_file) + filename=wind_raster_file) bear_raster = createRaster(np.flipud(bearing), lon, lat, delta, delta) uu_raster = createRaster(np.flipud(uu), lon, lat, delta, delta) vv_raster = createRaster(np.flipud(vv), lon, lat, delta, delta) @@ -975,19 +974,15 @@ def processMultV2(wspd, uu, vv, lon, lat, working_dir, dirns, future_requests = [] with futures.ThreadPoolExecutor(max_workers=max_working_threads) as e: - m4_max_file_obj=gdal.Open(m4_max_file, gdal.GA_ReadOnly) - thread_wind = e.submit(reprojectDataset, wind_raster, m4_max_file_obj, wind_prj_file, - warp_memory_limit=warp_memory_limit) - thread_bear = e.submit(reprojectDataset, bear_raster, m4_max_file_obj, bear_prj_file, - warp_memory_limit=warp_memory_limit, - resampling_method=gdalconst.GRA_NearestNeighbour) - futures.wait([thread_bear]) - thread_bear.result() # Called to obtain exception information if any + m4_max_file_obj = gdal.Open(m4_max_file, gdal.GA_ReadOnly) + reprojectDataset(wind_raster, m4_max_file_obj, wind_prj_file, + warp_memory_limit=warp_memory_limit) + reprojectDataset(bear_raster, m4_max_file_obj, bear_prj_file, + warp_memory_limit=warp_memory_limit, + resampling_method=gdalconst.GRA_NearestNeighbour) future_requests.append(e.submit(reprojectDataset, uu_raster, m4_max_file_obj, uu_prj_file, warp_memory_limit=warp_memory_limit, resampling_method=gdalconst.GRA_NearestNeighbour)) - futures.wait([thread_wind]) # Writing wind is slow as it has to write region_wind.tif as well - thread_wind.result() # Called to obtain exception information if any future_requests.append(e.submit(reprojectDataset, vv_raster, m4_max_file_obj, vv_prj_file, warp_memory_limit=warp_memory_limit, resampling_method=gdalconst.GRA_NearestNeighbour)) @@ -1008,11 +1003,12 @@ def processMultV2(wspd, uu, vv, lon, lat, working_dir, dirns, # multipliers drv = gdal.GetDriverByName("GTiff") dst_ds = drv.Create(output_file, cols, rows, 1, - gdal.GDT_Float32, ['SPARSE_OK=TRUE']) + gdal.GDT_Float32, ['BIGTIFF=YES', 'SPARSE_OK=TRUE']) dst_ds.SetGeoTransform(wind_geot) dst_ds.SetProjection(wind_proj) dst_band = dst_ds.GetRasterBand(1) dst_band.SetNoDataValue(-9999) + print('processMultV2', dst_ds.GetProjection()) log.info("Reading bands") source_dir_bands = [] @@ -1033,28 +1029,33 @@ def processMultV2(wspd, uu, vv, lon, lat, working_dir, dirns, total_segments = int(math.ceil(1.0 * cols / processing_segment_size) * math.ceil(1.0 * rows / processing_segment_size)) segment_count = 0 - segments = [] + segment_queue = queue.Queue(total_segments) for y_offset in range(0, rows, processing_segment_size): height = rows - y_offset if y_offset + processing_segment_size > rows else processing_segment_size for x_offset in range(0, cols, processing_segment_size): segment_count = segment_count + 1 width = cols - x_offset if x_offset + processing_segment_size > cols else processing_segment_size - segments.append([x_offset, y_offset, width, height, segment_count, total_segments]) + segment_queue.put([x_offset, y_offset, width, height, segment_count, total_segments]) log.info("Lunching {0} segmented task in {1} worker threads".format(total_segments, max_working_threads)) - for seg in segments: - future_requests.append(e.submit(processMultiplierSegment, seg, source_dir_bands, wind_prj, bear_prj, dst_band)) + for _ in range(max_working_threads): + future_requests.append(e.submit(call_process_multiplier_segment, segment_queue, source_dir_bands, wind_prj, bear_prj, dst_band)) + futures.wait(future_requests, return_when='FIRST_EXCEPTION') for task in future_requests: - task.result() # Called to obtain exception information if any + task.result() # Called to obtain exception information if any + dst_ds.FlushCache() + del dst_ds - del dst_ds - print("") log.info("Completed") - return output_file +def call_process_multiplier_segment(segment_queue, source_dir_band, wind_prj, bear_prj, dst_band): + while not segment_queue.empty(): + processMultiplierSegment(segment_queue.get(), source_dir_band, wind_prj, bear_prj, dst_band) + dst_band.FlushCache() + def processMultiplierSegment(segment, source_dir_band, wind_prj, bear_prj, dst_band): """ Calculates local wind multiplier data by image segments @@ -1081,8 +1082,6 @@ def processMultiplierSegment(segment, source_dir_band, wind_prj, bear_prj, dst_b 8: {'dir': 'n', 'min': 337.5, 'max': 360.} } [x_offset, y_offset, width, height, segment_id, total_segments] = segment - log.debug("Processing segment {0}/{1}: {2} {3} {4} {5}" - .format(segment_id, total_segments, x_offset, y_offset, width, height)) with threadLock_gust: wind_data = wind_prj.ReadAsArray(x_offset, y_offset, width, height) with threadLock_bear: @@ -1096,11 +1095,9 @@ def processMultiplierSegment(segment, source_dir_band, wind_prj, bear_prj, dst_b local[idx] = wind_data[idx] * m4[idx] with threadLock_out: dst_band.WriteArray(local, x_offset, y_offset) - print('\rProgress: {0:.2f}'.format((segment_id * 100) / total_segments), "%", end="") - if segment_id % int(math.ceil(total_segments / 20)) == 0: - if log.getLogger(__name__).getEffectiveLevel() == log.DEBUG: - print("") - log.debug('Progress: {0} %'.format(int((segment_id * 100) / total_segments))) + if segment_id % int(math.ceil(total_segments / 100.0)) == 0: + dst_band.FlushCache() + log.info('Progress: {0:.2f} %'.format((segment_id * 100.0) / total_segments)) class run(): From 091db2f503f9ad1a5c45ac3306a21eae73d8ea8a Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Tue, 9 Nov 2021 09:52:15 +1100 Subject: [PATCH 24/80] assertDictEqual doesn't like complex values (like arrays) --- tests/test_maps.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/test_maps.py b/tests/test_maps.py index 08a31518..4a1edf11 100644 --- a/tests/test_maps.py +++ b/tests/test_maps.py @@ -1,5 +1,4 @@ import unittest - import numpy as np from . import NumpyTestCase from matplotlib.colors import LinearSegmentedColormap @@ -48,16 +47,16 @@ def test_bigLevelValues(self): self.numpyAssertAlmostEqual(lvs, rlevs) self.assertEqual(expo, rexpo) -class TestSelectColorMap(unittest.TestCase): +class TestSelectColorMap(NumpyTestCase.NumpyTestCase): def assertColorMapEqual(self, actual, expected): """Test method for equality of LinearSegmentedColormaps""" + self.assertEqual(type(actual), type(expected)) self.assertEqual(actual.N, expected.N) - self.assertDictEqual(actual._segmentdata, - expected._segmentdata) + self.assertEqual(actual.name, expected.name) for k in list(actual._segmentdata.keys()): - self.assertListEqual(actual._segmentdata[k], - expected._segmentdata[k]) + self.numpyAssertAlmostEqual(actual._segmentdata[k], + expected._segmentdata[k]) def setUp(self): import seaborn as sns From 9e82d3a8a3a85f55ddebd4633604474f66329304 Mon Sep 17 00:00:00 2001 From: mahmudulhasanGA <66761046+mahmudulhasanGA@users.noreply.github.com> Date: Mon, 15 Nov 2021 14:56:19 +1100 Subject: [PATCH 25/80] NHIRS-148: Fixed issue with FlushCache failing. (#121) --- ProcessMultipliers/processMultipliers.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ProcessMultipliers/processMultipliers.py b/ProcessMultipliers/processMultipliers.py index 0b187c36..f20f3863 100755 --- a/ProcessMultipliers/processMultipliers.py +++ b/ProcessMultipliers/processMultipliers.py @@ -1054,7 +1054,6 @@ def processMultV2(wspd, uu, vv, lon, lat, working_dir, dirns, def call_process_multiplier_segment(segment_queue, source_dir_band, wind_prj, bear_prj, dst_band): while not segment_queue.empty(): processMultiplierSegment(segment_queue.get(), source_dir_band, wind_prj, bear_prj, dst_band) - dst_band.FlushCache() def processMultiplierSegment(segment, source_dir_band, wind_prj, bear_prj, dst_band): """ @@ -1095,8 +1094,8 @@ def processMultiplierSegment(segment, source_dir_band, wind_prj, bear_prj, dst_b local[idx] = wind_data[idx] * m4[idx] with threadLock_out: dst_band.WriteArray(local, x_offset, y_offset) - if segment_id % int(math.ceil(total_segments / 100.0)) == 0: dst_band.FlushCache() + if segment_id % int(math.ceil(total_segments / 100.0)) == 0: log.info('Progress: {0:.2f} %'.format((segment_id * 100.0) / total_segments)) class run(): From 477c3a5506b97b4071db7e6f50a9a567d76860b0 Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Fri, 14 Jan 2022 09:11:22 +1100 Subject: [PATCH 26/80] Enhancement/linear rmax (#125) * Updated rmax to linear formula Co-authored-by: Craig Arthur --- TrackGenerator/trackSize.py | 20 +++++++------------- tests/NumpyTestCase.py | 3 ++- tests/test_trackSize.py | 31 +++++++++++++++---------------- 3 files changed, 24 insertions(+), 30 deletions(-) diff --git a/TrackGenerator/trackSize.py b/TrackGenerator/trackSize.py index d1a92df5..67a636d9 100644 --- a/TrackGenerator/trackSize.py +++ b/TrackGenerator/trackSize.py @@ -18,16 +18,14 @@ LOG = logging.getLogger() -def rmax(dp, lat, eps, coeffs=[3.5843946536979779,-0.0045486143609339436, - 0.78621467400844858, 0.0024030344245284741, - 0.0015567629057007433]): +def rmax(dp, lat, eps, coeffs=[4.22, -0.0198, 0.0023]): """ Calculate radius to maximum wind based on pressure deficit and latitude. This function allows for the random variate to be set when calling the function. Default coefficients for the functional form of ln(Rmw) are given, based on JTWC data for the southern hemisphere. - ln(Rmw) = a + b*dp + c*exp(-d*dp^2) + f*|lat| + eps + ln(Rmw) = a + b*dp + c*|lat| + eps eps is not included in the coefficients (though that may be considered by some to be more logical), so that it can remain constant for a single @@ -43,18 +41,15 @@ def rmax(dp, lat, eps, coeffs=[3.5843946536979779,-0.0045486143609339436, :returns: radius to maximum wind value. """ - if len(coeffs) < 4: + if len(coeffs) != 3: LOG.warn("Insufficient coefficients for rmw calculation!") LOG.warn("Using default values") - coeffs = [3.5843946536979779,-0.0045486143609339436, - 0.78621467400844858, 0.0024030344245284741, - 0.0015567629057007433] + coeffs = [4.22, -0.0198, 0.0023] if isinstance(dp, (np.ndarray, list)) and \ isinstance(lat, (np.ndarray, list)): assert len(dp) == len(lat) - yy = coeffs[0] + coeffs[1]*dp + coeffs[2] * np.exp(-coeffs[3] * dp * dp) +\ - coeffs[4] * np.abs(lat) + eps + yy = coeffs[0] + coeffs[1] * dp + coeffs[2] * np.abs(lat) + eps rm = np.exp(yy) return rm @@ -65,7 +60,7 @@ def fitRmax(rmw, dp, lat): We fit a function of dp and latitude to ln(Rmw) values of the form: - ln(Rmw) = a + b*dp + c*dp^2 + d*lat^2 + eps + ln(Rmw) = a + b*dp + c*|lat| + eps where eps is a random normal variate with zero mean and std. dev. describing the residual variance. @@ -84,8 +79,7 @@ def fitRmax(rmw, dp, lat): assert len(dp) == len(lat) assert len(rmw) == len(dp) - X = np.column_stack((dp, dp*dp, lat*lat)) - X = sm.add_constant(X) + X = np.column_stack((dp, abs(lat))) y = np.array(np.log(rmw)) model = sm.OLS(y, X) results = model.fit() diff --git a/tests/NumpyTestCase.py b/tests/NumpyTestCase.py index f07c54fa..e1485c4e 100644 --- a/tests/NumpyTestCase.py +++ b/tests/NumpyTestCase.py @@ -9,7 +9,8 @@ $Id: NumpyTestCase.py 563 2007-10-24 02:52:40Z carthur $ """ -from scipy import array, alltrue, iscomplexobj, allclose, equal +from scipy import array +from numpy import allclose, iscomplexobj, alltrue, equal import unittest class NumpyTestCase(unittest.TestCase): diff --git a/tests/test_trackSize.py b/tests/test_trackSize.py index 01f3dc1a..0d390733 100644 --- a/tests/test_trackSize.py +++ b/tests/test_trackSize.py @@ -21,13 +21,13 @@ def setUp(self): np.random.seed(10) self.dparray = np.arange(10, 51, 5) self.latarray = np.arange(-23, -5, 2) - self.rmaxout = np.array([66.21418972, 54.96078787, 45.7678418, - 39.33639152, 35.22023191, 32.67958816, - 31.07181726, 29.95463557, 29.06996118]) + self.rmaxout = np.array([58.84459583, 53.05345552, 47.8322453, + 43.124876, 38.8807784, 35.05436002, + 31.60451531, 28.49418411, 25.68995347]) - self.rmaxoutcoeffs = np.array([57.74931727, 49.76638251, 42.67145839, - 37.24632781, 33.47199844, 30.98197273, - 29.3570741, 28.25288743, 27.43217413]) + self.rmaxoutcoeffs = np.array([42.01387832, 33.98774437, 27.49488535, + 22.24239161, 17.9933096, 14.55595226, + 11.77525152, 9.52576278, 7.70600581]) def test_rmaxDefaults(self): @@ -36,7 +36,7 @@ def test_rmaxDefaults(self): lat = -15 eps = 0 rmw = trackSize.rmax(dp, lat, eps) - self.assertAlmostEqual(rmw, 39.21410711, places=1) + self.assertAlmostEqual(rmw, 42.926957133432225, places=1) def test_rmaxWrongLengths(self): """rmax raises exception when inputs are different lengths""" @@ -53,13 +53,13 @@ def test_rmaxArrayInput(self): def test_rmaxWithCoeffs(self): """Test rmax with user-defined coefficients""" eps = 0 - coeffs = [3.5, -0.004, 0.7, 0.002, .001] + coeffs = [4.0, -0.04, 0.006] rmw = trackSize.rmax(self.dparray, self.latarray, eps, coeffs) self.numpyAssertAlmostEqual(rmw, self.rmaxoutcoeffs) def test_rmaxWithIncompleteCoeffs(self): """Test rmax falls back to default coefficients if not enough given""" - coeffs = [4.45, -0.05, 0.0002] + coeffs = [4.45, -0.05] eps = 0 rmw = trackSize.rmax(self.dparray, self.latarray, eps, coeffs=coeffs) self.numpyAssertAlmostEqual(rmw, self.rmaxout, prec=0.1) @@ -74,11 +74,7 @@ def setUp(self): self.dp = pickle.load(pklfile) self.lat = pickle.load(pklfile) self.rmw = pickle.load(pklfile) - self.params = [4.4650608902114888, - -0.042494641709203987, - 0.00033723892839458182, - 0.00021502458395316267, - 0.35665997379737535] + self.params = [-0.006778075190728221, 0.24194398102420178, 0.9410510407821646] pklfile.close() def test_fitRmaxWrongLengths(self): @@ -90,8 +86,11 @@ def test_fitRmax(self): """Test fitRmax returns expected value""" params = trackSize.fitRmax(self.rmw, self.dp, self.lat) self.assertEqual(type(params), list) - self.assertEqual(len(params), 5) + self.assertEqual(len(params), 3) + print(params) self.numpyAssertAlmostEqual(np.array(params), np.array(self.params)) - + _ = trackSize.rmax(self.dp, self.lat, 0, params) + + if __name__ == "__main__": unittest.main(verbosity=2) From e40034af671a5f8daf846b7b5b9077731c36cbfc Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Tue, 25 Jan 2022 11:12:39 +1100 Subject: [PATCH 27/80] adjust lam for storm direction --- wind/windmodels.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wind/windmodels.py b/wind/windmodels.py index b463fccc..601c76fc 100644 --- a/wind/windmodels.py +++ b/wind/windmodels.py @@ -1090,8 +1090,8 @@ def field(self, R, lam, vFm, thetaFm, thetaMax=0.): Am[ind] = AmIII[ind] # First asymmetric surface component - ums = (Am * np.exp(-i * lam * np.sign(self.f))).real * albe - vms = (Am * np.exp(-i * lam * np.sign(self.f))).imag * np.sign(self.f) + ums = (Am * np.exp(-i * (lam - thetaFm) * np.sign(self.f))).real * albe + vms = (Am * np.exp(-i * (lam - thetaFm) * np.sign(self.f))).imag * np.sign(self.f) Ap = -(eta * (1 - 2 * albe + (1 + i) * (1 - albe) * psi) * Vt) / \ (albe * ((2 + 2 * i) * (1 + eta * psi) + 3 * eta + 3 * i * psi)) @@ -1101,8 +1101,8 @@ def field(self, R, lam, vFm, thetaFm, thetaMax=0.): Ap[ind] = ApIII[ind] # Second asymmetric surface component - ups = (Ap * np.exp(i * lam * np.sign(self.f))).real * albe - vps = (Ap * np.exp(i * lam * np.sign(self.f))).imag * np.sign(self.f) + ups = (Ap * np.exp(i * (lam - thetaFm) * np.sign(self.f))).real * albe + vps = (Ap * np.exp(i * (lam - thetaFm) * np.sign(self.f))).imag * np.sign(self.f) # Total surface wind in (moving coordinate system) us = u0s + ups + ums From 3d391a3674482b66eee965d545ae66808eb98feb Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Tue, 25 Jan 2022 11:21:28 +1100 Subject: [PATCH 28/80] adjust lam for storm direction --- wind/windmodels.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wind/windmodels.py b/wind/windmodels.py index 601c76fc..fec54bfb 100644 --- a/wind/windmodels.py +++ b/wind/windmodels.py @@ -1090,8 +1090,8 @@ def field(self, R, lam, vFm, thetaFm, thetaMax=0.): Am[ind] = AmIII[ind] # First asymmetric surface component - ums = (Am * np.exp(-i * (lam - thetaFm) * np.sign(self.f))).real * albe - vms = (Am * np.exp(-i * (lam - thetaFm) * np.sign(self.f))).imag * np.sign(self.f) + ums = (Am * np.exp(-i * (thetaFm - lam) * np.sign(self.f))).real * albe + vms = (Am * np.exp(-i * (thetaFm - lam) * np.sign(self.f))).imag * np.sign(self.f) Ap = -(eta * (1 - 2 * albe + (1 + i) * (1 - albe) * psi) * Vt) / \ (albe * ((2 + 2 * i) * (1 + eta * psi) + 3 * eta + 3 * i * psi)) @@ -1101,8 +1101,8 @@ def field(self, R, lam, vFm, thetaFm, thetaMax=0.): Ap[ind] = ApIII[ind] # Second asymmetric surface component - ups = (Ap * np.exp(i * (lam - thetaFm) * np.sign(self.f))).real * albe - vps = (Ap * np.exp(i * (lam - thetaFm) * np.sign(self.f))).imag * np.sign(self.f) + ups = (Ap * np.exp(i * (thetaFm - lam) * np.sign(self.f))).real * albe + vps = (Ap * np.exp(i * (thetaFm - thetaFm) * np.sign(self.f))).imag * np.sign(self.f) # Total surface wind in (moving coordinate system) us = u0s + ups + ums From 3637dcfad5856b39ccba83ed5ee41b4626730206 Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Tue, 25 Jan 2022 11:21:47 +1100 Subject: [PATCH 29/80] adjust lam for storm direction --- wind/windmodels.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wind/windmodels.py b/wind/windmodels.py index fec54bfb..13eea69e 100644 --- a/wind/windmodels.py +++ b/wind/windmodels.py @@ -1102,7 +1102,7 @@ def field(self, R, lam, vFm, thetaFm, thetaMax=0.): # Second asymmetric surface component ups = (Ap * np.exp(i * (thetaFm - lam) * np.sign(self.f))).real * albe - vps = (Ap * np.exp(i * (thetaFm - thetaFm) * np.sign(self.f))).imag * np.sign(self.f) + vps = (Ap * np.exp(i * (thetaFm - lam) * np.sign(self.f))).imag * np.sign(self.f) # Total surface wind in (moving coordinate system) us = u0s + ups + ums From e7440d22ec327dbac49ce89d3c090c68806b3235 Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Thu, 27 Jan 2022 09:12:57 +1100 Subject: [PATCH 30/80] debug comment --- wind/windmodels.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wind/windmodels.py b/wind/windmodels.py index 13eea69e..51524135 100644 --- a/wind/windmodels.py +++ b/wind/windmodels.py @@ -1107,7 +1107,7 @@ def field(self, R, lam, vFm, thetaFm, thetaMax=0.): # Total surface wind in (moving coordinate system) us = u0s + ups + ums vs = v0s + vps + vms + V - + print("Here!") usf = us + Vt * np.cos(lam - thetaFm) vsf = vs - Vt * np.sin(lam - thetaFm) phi = np.arctan2(usf, vsf) From 90b29c507b20658da770ae2015c882b9abd79b4c Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Thu, 27 Jan 2022 10:35:13 +1100 Subject: [PATCH 31/80] update wind field model to get direction correct, and update documentation --- wind/windmodels.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/wind/windmodels.py b/wind/windmodels.py index 51524135..1aa042d6 100644 --- a/wind/windmodels.py +++ b/wind/windmodels.py @@ -1037,12 +1037,12 @@ def field(self, R, lam, vFm, thetaFm, thetaMax=0.): """ :param R: Distance from the storm centre to the grid (km). :type R: :class:`numpy.ndarray` - :param lam: Direction (geographic bearing, positive clockwise) + :param lam: Direction (0=east, radians, positive anti-clockwise) from storm centre to the grid. :type lam: :class:`numpy.ndarray` :param float vFm: Foward speed of the storm (m/s). - :param float thetaFm: Forward direction of the storm (geographic - bearing, positive clockwise, radians). + :param float thetaFm: Forward direction of the storm (0=east, radians, + positive anti-clockwise). :param float thetaMax: Bearing of the location of the maximum wind speed, relative to the direction of motion. @@ -1090,8 +1090,8 @@ def field(self, R, lam, vFm, thetaFm, thetaMax=0.): Am[ind] = AmIII[ind] # First asymmetric surface component - ums = (Am * np.exp(-i * (thetaFm - lam) * np.sign(self.f))).real * albe - vms = (Am * np.exp(-i * (thetaFm - lam) * np.sign(self.f))).imag * np.sign(self.f) + ums = (Am * np.exp(-i * (lam - thetaFm) * np.sign(self.f))).real * albe + vms = (Am * np.exp(-i * (lam - thetaFm) * np.sign(self.f))).imag * np.sign(self.f) Ap = -(eta * (1 - 2 * albe + (1 + i) * (1 - albe) * psi) * Vt) / \ (albe * ((2 + 2 * i) * (1 + eta * psi) + 3 * eta + 3 * i * psi)) @@ -1101,13 +1101,13 @@ def field(self, R, lam, vFm, thetaFm, thetaMax=0.): Ap[ind] = ApIII[ind] # Second asymmetric surface component - ups = (Ap * np.exp(i * (thetaFm - lam) * np.sign(self.f))).real * albe - vps = (Ap * np.exp(i * (thetaFm - lam) * np.sign(self.f))).imag * np.sign(self.f) + ups = (Ap * np.exp(i * (lam - thetaFm) * np.sign(self.f))).real * albe + vps = (Ap * np.exp(i * (lam - thetaFm) * np.sign(self.f))).imag * np.sign(self.f) # Total surface wind in (moving coordinate system) us = u0s + ups + ums vs = v0s + vps + vms + V - print("Here!") + usf = us + Vt * np.cos(lam - thetaFm) vsf = vs - Vt * np.sin(lam - thetaFm) phi = np.arctan2(usf, vsf) @@ -1119,9 +1119,6 @@ def field(self, R, lam, vFm, thetaFm, thetaMax=0.): return Ux, Vy -# Automatic discovery of models and required parameters - - def allSubclasses(cls): """ Recursively find all subclasses of a given class. From 8ae25e5e04a28479a521682b8f3fb03e71764514 Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Thu, 27 Jan 2022 10:36:28 +1100 Subject: [PATCH 32/80] add deleted lines back in --- wind/windmodels.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/wind/windmodels.py b/wind/windmodels.py index 1aa042d6..2d8784b6 100644 --- a/wind/windmodels.py +++ b/wind/windmodels.py @@ -1119,7 +1119,10 @@ def field(self, R, lam, vFm, thetaFm, thetaMax=0.): return Ux, Vy -def allSubclasses(cls): +# Automatic discovery of models and required parameters + + + def allSubclasses(cls): """ Recursively find all subclasses of a given class. """ From c5a1facbe74c8886a17d74e90ec363163db71242 Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Thu, 27 Jan 2022 10:36:52 +1100 Subject: [PATCH 33/80] add deleted lines back in --- wind/windmodels.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wind/windmodels.py b/wind/windmodels.py index 2d8784b6..380fde24 100644 --- a/wind/windmodels.py +++ b/wind/windmodels.py @@ -1122,7 +1122,7 @@ def field(self, R, lam, vFm, thetaFm, thetaMax=0.): # Automatic discovery of models and required parameters - def allSubclasses(cls): +def allSubclasses(cls): """ Recursively find all subclasses of a given class. """ From e7b15b3e6d6a3a347b309645d28b7b6993ee9ed5 Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Fri, 14 Jan 2022 09:11:22 +1100 Subject: [PATCH 34/80] Enhancement/linear rmax (#125) * Updated rmax to linear formula Co-authored-by: Craig Arthur --- .github/workflows/tcrm-tests.yml | 4 +- PlotInterface/maps.py | 2 +- PlotInterface/plotTimeseries.py | 2 +- StatInterface/SamplingOrigin.py | 9 ++- StatInterface/SamplingParameters.py | 5 +- StatInterface/StatInterface.py | 2 +- TrackGenerator/trackSize.py | 24 +++----- Utilities/config.py | 2 +- Utilities/files.py | 2 + Utilities/loadData.py | 4 +- Utilities/process.py | 10 ++-- Utilities/shptools.py | 4 +- Utilities/track.py | 2 +- setup.py | 8 ++- tcrmenv.yml | 2 + tests/NumpyTestCase.py | 3 +- tests/__init__.py | 0 tests/test_GPD.py | 2 +- tests/test_Intersections.py | 2 +- tests/test_KDEOrigin.py | 4 +- tests/test_KDEParameters.py | 4 +- tests/test_SamplingOrigin.py | 4 +- tests/test_SamplingParameters.py | 4 +- tests/test_columns.py | 2 +- tests/test_config.py | 2 +- tests/test_data/vorticityTestData.pkl | Bin 467203 -> 467187 bytes tests/test_data/windFieldTestData.pkl | Bin 467203 -> 467187 bytes tests/test_data/windProfileTestData.pkl | Bin 467203 -> 467187 bytes tests/test_files.py | 2 + tests/test_generateStats.py | 4 +- tests/test_grid.py | 4 +- tests/test_hazard__init__.py | 2 +- tests/test_interp3d.py | 2 +- tests/test_interpolate.py | 31 ++++++++++ tests/test_lmomentFit.py | 2 +- tests/test_loadData.py | 4 +- tests/test_maps.py | 2 +- tests/test_maputils.py | 74 ++++++++++++------------ tests/test_metutils.py | 4 +- tests/test_nctools.py | 4 +- tests/test_pressureProfile.py | 4 +- tests/test_stats.py | 6 +- tests/test_track.py | 4 +- tests/test_trackSize.py | 35 ++++++----- tests/test_vmax.py | 4 +- tests/test_windmodels.py | 2 +- tests/test_windprofile.py | 2 +- wind/vmax.py | 9 +-- wind/windmodels.py | 12 ++-- 49 files changed, 177 insertions(+), 145 deletions(-) delete mode 100644 tests/__init__.py create mode 100644 tests/test_interpolate.py diff --git a/.github/workflows/tcrm-tests.yml b/.github/workflows/tcrm-tests.yml index 3e68e830..006cbfd3 100644 --- a/.github/workflows/tcrm-tests.yml +++ b/.github/workflows/tcrm-tests.yml @@ -26,7 +26,7 @@ jobs: python-version: ${{ matrix.python-version }} auto-activate-base: false - - name: Test with nose + - name: Test with pytest shell: bash -l {0} run: | - python tests/run.py + pytest -x --cov=. --cov-report xml diff --git a/PlotInterface/maps.py b/PlotInterface/maps.py index 53d4b3e1..c719ff0c 100644 --- a/PlotInterface/maps.py +++ b/PlotInterface/maps.py @@ -380,7 +380,7 @@ def subplot(self, axes, subfigure): cmap = selectColormap(lvls) CS = mapobj.contourf(mx, my, data, levels=lvls, extend='both', cmap=cmap) - CB = self.colorbar(CS, ticks=lvls[::2], ax=axes, extend='both') + CB = self.colorbar(CS, ticks=lvls[::2], ax=axes) CB.set_label(cbarlab) axes.set_title(title) self.addGraticule(axes, mapobj) diff --git a/PlotInterface/plotTimeseries.py b/PlotInterface/plotTimeseries.py index dbcf0f1c..49afc71b 100644 --- a/PlotInterface/plotTimeseries.py +++ b/PlotInterface/plotTimeseries.py @@ -49,7 +49,7 @@ def loadTimeseriesData(datafile): comments='#', delimiter=',', skip_header=1, converters=INPUT_CNVT) except ValueError: - logging.warn("Timeseries data file is empty - returning empty array") + logging.warning("Timeseries data file is empty - returning empty array") return np.empty(0, dtype={ 'names': INPUT_COLS, 'formats': INPUT_FMTS}) diff --git a/StatInterface/SamplingOrigin.py b/StatInterface/SamplingOrigin.py index 5dea26aa..eb8e1068 100644 --- a/StatInterface/SamplingOrigin.py +++ b/StatInterface/SamplingOrigin.py @@ -18,7 +18,6 @@ from Utilities.files import flLoadFile, flSaveFile from Utilities.grid import grdRead, grdReadFromNetcdf import numpy as np -import scipy import Utilities.stats as stats LOG = logging.getLogger() @@ -119,8 +118,8 @@ def setKDEOrigins(self, kdeOriginX=None, kdeOriginY=None, kdeOriginZ=None, def generateOneSample(self): """Generate a random cyclone origin.""" # generate 2 uniform random variables - unifX = scipy.rand() - unifY = scipy.rand() + unifX = np.random.rand() + unifY = np.random.rand() xi = np.array(self.cdfX).searchsorted(unifX) yj = self.cdfY[xi, :].searchsorted(unifY) @@ -178,8 +177,8 @@ def generateSamples(self, ns, outputFile=None): raise ValueError # Generate 2 vectors of uniform random variables - unifX = scipy.rand(ns) - unifY = scipy.rand(ns) + unifX = np.random.rand(ns) + unifY = np.random.rand(ns) self.oLon = np.empty(ns, 'd') self.oLat = np.empty(ns, 'd') diff --git a/StatInterface/SamplingParameters.py b/StatInterface/SamplingParameters.py index 948578e0..ceccbfb7 100644 --- a/StatInterface/SamplingParameters.py +++ b/StatInterface/SamplingParameters.py @@ -16,7 +16,6 @@ import sys import logging -import scipy import numpy as np from Utilities.config import cnfGetIniValue from Utilities.files import flLoadFile, flSaveFile @@ -76,7 +75,7 @@ def setParameters(self, cdfParameters): def generateOneSample(self): """Generate a single random sample of cyclone parameters.""" - unif = scipy.rand() + unif = np.random.rand() ind_kdf = self.xacy[:, 1].searchsorted(unif) return self.xacy[ind_kdf, 0] @@ -96,7 +95,7 @@ def generateSamples(self, ns, sample_parameter_path=None): if ns <= 0: raise ValueError('invalid input on ns: number of sample cannot be zero or negative') - unif_s = scipy.rand(ns) + unif_s = np.random.rand(ns) ind_kdf = self.xacy[:, 1].searchsorted(unif_s) self.sample = self.xacy[ind_kdf, 0] diff --git a/StatInterface/StatInterface.py b/StatInterface/StatInterface.py index 86fe020c..802588e4 100644 --- a/StatInterface/StatInterface.py +++ b/StatInterface/StatInterface.py @@ -62,7 +62,7 @@ def __init__(self, configFile, autoCalc_gridLimit=None, gridLimitStr = cnfGetIniValue(self.configFile, 'StatInterface', 'gridLimit', '') - if gridLimitStr is not '': + if gridLimitStr != '': try: self.gridLimit = eval(gridLimitStr) except SyntaxError: diff --git a/TrackGenerator/trackSize.py b/TrackGenerator/trackSize.py index d1a92df5..89ab2e73 100644 --- a/TrackGenerator/trackSize.py +++ b/TrackGenerator/trackSize.py @@ -18,16 +18,14 @@ LOG = logging.getLogger() -def rmax(dp, lat, eps, coeffs=[3.5843946536979779,-0.0045486143609339436, - 0.78621467400844858, 0.0024030344245284741, - 0.0015567629057007433]): +def rmax(dp, lat, eps, coeffs=[4.22, -0.0198, 0.0023]): """ Calculate radius to maximum wind based on pressure deficit and latitude. This function allows for the random variate to be set when calling the function. Default coefficients for the functional form of ln(Rmw) are given, based on JTWC data for the southern hemisphere. - ln(Rmw) = a + b*dp + c*exp(-d*dp^2) + f*|lat| + eps + ln(Rmw) = a + b*dp + c*|lat| + eps eps is not included in the coefficients (though that may be considered by some to be more logical), so that it can remain constant for a single @@ -43,18 +41,15 @@ def rmax(dp, lat, eps, coeffs=[3.5843946536979779,-0.0045486143609339436, :returns: radius to maximum wind value. """ - if len(coeffs) < 4: - LOG.warn("Insufficient coefficients for rmw calculation!") - LOG.warn("Using default values") - coeffs = [3.5843946536979779,-0.0045486143609339436, - 0.78621467400844858, 0.0024030344245284741, - 0.0015567629057007433] + if len(coeffs) != 3: + LOG.warning("Insufficient coefficients for rmw calculation!") + LOG.warning("Using default values") + coeffs = [4.22, -0.0198, 0.0023] if isinstance(dp, (np.ndarray, list)) and \ isinstance(lat, (np.ndarray, list)): assert len(dp) == len(lat) - yy = coeffs[0] + coeffs[1]*dp + coeffs[2] * np.exp(-coeffs[3] * dp * dp) +\ - coeffs[4] * np.abs(lat) + eps + yy = coeffs[0] + coeffs[1] * dp + coeffs[2] * np.abs(lat) + eps rm = np.exp(yy) return rm @@ -65,7 +60,7 @@ def fitRmax(rmw, dp, lat): We fit a function of dp and latitude to ln(Rmw) values of the form: - ln(Rmw) = a + b*dp + c*dp^2 + d*lat^2 + eps + ln(Rmw) = a + b*dp + c*|lat| + eps where eps is a random normal variate with zero mean and std. dev. describing the residual variance. @@ -84,8 +79,7 @@ def fitRmax(rmw, dp, lat): assert len(dp) == len(lat) assert len(rmw) == len(dp) - X = np.column_stack((dp, dp*dp, lat*lat)) - X = sm.add_constant(X) + X = np.column_stack((dp, abs(lat))) y = np.array(np.log(rmw)) model = sm.OLS(y, X) results = model.fit() diff --git a/Utilities/config.py b/Utilities/config.py index facc37b5..10a65ee2 100644 --- a/Utilities/config.py +++ b/Utilities/config.py @@ -262,7 +262,7 @@ class _ConfigParser(RawConfigParser): ignoreSubsequent = True def __init__(self, defaults=DEFAULTS): RawConfigParser.__init__(self) - self.readfp(io.StringIO(defaults)) + self.read_file(io.StringIO(defaults)) self.readonce = False def geteval(self, section, option): diff --git a/Utilities/files.py b/Utilities/files.py index 26218268..a58f7f81 100644 --- a/Utilities/files.py +++ b/Utilities/files.py @@ -5,6 +5,7 @@ import datetime import numpy as np from time import ctime, localtime, strftime +from pathlib import Path import hashlib @@ -29,6 +30,7 @@ def flModulePath(level=1): """ filename = os.path.realpath(sys._getframe(level).f_code.co_filename) path, fname = os.path.split(filename) + path = str(Path(path).resolve()) path.replace(os.path.sep, '/') base, ext = os.path.splitext(fname) return path, base, ext diff --git a/Utilities/loadData.py b/Utilities/loadData.py index a2eb5d79..ea3d5521 100644 --- a/Utilities/loadData.py +++ b/Utilities/loadData.py @@ -687,8 +687,8 @@ def getPoci(penv, pcentre, lat, jdays, eps, """ if len(coeffs) < 6: - LOG.warn("Insufficient coefficients for poci calculation") - LOG.warn("Using default values") + LOG.warning("Insufficient coefficients for poci calculation") + LOG.warning("Using default values") coeffs=[2324.1564738613392, -0.6539853183796136, -1.3984456535888878, 0.00074072928008818927, 0.0044469231429346088, -1.4337623534206905] diff --git a/Utilities/process.py b/Utilities/process.py index dbcb1bd9..02cc2ec3 100644 --- a/Utilities/process.py +++ b/Utilities/process.py @@ -92,7 +92,7 @@ def pGetProcessedFiles(datFileName=None): fh = open(datFileName) except IOError: - LOGGER.warn("Couldn't open dat file %s", datFileName) + LOGGER.warning("Couldn't open dat file %s", datFileName) return rc else: LOGGER.debug("Getting previously-processed files from %s", @@ -141,8 +141,8 @@ def pWriteProcessedFile(filename): fh.close() rc = 1 else: - LOGGER.warn(("Dat file name not provided. " - "Can't record %s as processed."), filename) + LOGGER.warning(("Dat file name not provided. " + "Can't record %s as processed."), filename) return rc @@ -161,7 +161,7 @@ def pDeleteDatFile(): if os.unlink(GLOBAL_DATFILE): rc = 1 else: - LOGGER.warn("Cannot remove dat file %s", GLOBAL_DATFILE) + LOGGER.warning("Cannot remove dat file %s", GLOBAL_DATFILE) return rc def pAlreadyProcessed(directory, filename, attribute, value): @@ -268,7 +268,7 @@ def pMoveFile(origin, destination): try: os.rename(origin, destination) except OSError: - LOGGER.warn("Error moving %s to %s", origin, destination) + LOGGER.warning("Error moving %s to %s", origin, destination) rc = 0 else: LOGGER.debug("%s moved to %s", origin, destination) diff --git a/Utilities/shptools.py b/Utilities/shptools.py index d18297f7..daa6054c 100644 --- a/Utilities/shptools.py +++ b/Utilities/shptools.py @@ -314,9 +314,9 @@ def shpGetField(shape_file, field_name, dtype=float): field_names = [fields[i][0] for i in range(len(fields))] if field_name not in field_names: - log.warn("No field '{0}' in the list of fieldnames" . + log.warning("No field '{0}' in the list of fieldnames" . format(field_name)) - log.warn("Unable to proceed with processing") + log.warning("Unable to proceed with processing") raise ValueError records = sf.records() diff --git a/Utilities/track.py b/Utilities/track.py index 1cca7c1d..7f9bfa07 100644 --- a/Utilities/track.py +++ b/Utilities/track.py @@ -252,7 +252,7 @@ def ncReadTrackData(trackfile): tracks.append(track) else: - log.warn(TRACK_EMPTY_GROUP.format(trackfile)) + log.warning(TRACK_EMPTY_GROUP.format(trackfile)) ncobj.close() return tracks diff --git a/setup.py b/setup.py index ba3b06e5..51b0b04c 100644 --- a/setup.py +++ b/setup.py @@ -7,8 +7,8 @@ setup( name = "TCRM", - version = '3.1.4', - packages=find_packages(), + version = '3.1.12', + packages=find_packages(), scripts=['tcrm.py', 'tcevent.py'], include_package_data=True, package_data = { @@ -46,7 +46,9 @@ 'imageio', 'mpi4py', 'boto3', - 'botocore'], + 'botocore', + 'pytest', + 'pytest-cov'], # metadata: author = "Craig Arthur", diff --git a/tcrmenv.yml b/tcrmenv.yml index 84d02fa7..97fcc651 100644 --- a/tcrmenv.yml +++ b/tcrmenv.yml @@ -32,3 +32,5 @@ dependencies: - mpi4py - boto3 - botocore + - pytest + - pytest-cov diff --git a/tests/NumpyTestCase.py b/tests/NumpyTestCase.py index f07c54fa..e1485c4e 100644 --- a/tests/NumpyTestCase.py +++ b/tests/NumpyTestCase.py @@ -9,7 +9,8 @@ $Id: NumpyTestCase.py 563 2007-10-24 02:52:40Z carthur $ """ -from scipy import array, alltrue, iscomplexobj, allclose, equal +from scipy import array +from numpy import allclose, iscomplexobj, alltrue, equal import unittest class NumpyTestCase(unittest.TestCase): diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/test_GPD.py b/tests/test_GPD.py index 83791d66..d47377dd 100644 --- a/tests/test_GPD.py +++ b/tests/test_GPD.py @@ -34,7 +34,7 @@ import sys import unittest import pickle -from . import NumpyTestCase +from tests import NumpyTestCase import numpy # Add parent folder to python path diff --git a/tests/test_Intersections.py b/tests/test_Intersections.py index 3940ab7c..3039b3b6 100644 --- a/tests/test_Intersections.py +++ b/tests/test_Intersections.py @@ -11,7 +11,7 @@ import logging import numpy -from . import NumpyTestCase +from tests import NumpyTestCase # Add parent folder to python path unittest_dir = os.path.dirname(os.path.realpath( __file__ )) diff --git a/tests/test_KDEOrigin.py b/tests/test_KDEOrigin.py index 6c2824c9..26896dd7 100644 --- a/tests/test_KDEOrigin.py +++ b/tests/test_KDEOrigin.py @@ -11,11 +11,11 @@ import os, sys, pdb import pickle import unittest -from . import NumpyTestCase +from tests import NumpyTestCase try: from . import pathLocate except: - from unittests import pathLocate + from tests import pathLocate # Add parent folder to python path unittest_dir = pathLocate.getUnitTestDirectory() diff --git a/tests/test_KDEParameters.py b/tests/test_KDEParameters.py index 953a4f46..37ebff62 100644 --- a/tests/test_KDEParameters.py +++ b/tests/test_KDEParameters.py @@ -28,11 +28,11 @@ import os, sys import pickle import unittest -from . import NumpyTestCase +from tests import NumpyTestCase try: from . import pathLocate except: - from unittests import pathLocate + from tests import pathLocate # Add parent folder to python path unittest_dir = pathLocate.getUnitTestDirectory() diff --git a/tests/test_SamplingOrigin.py b/tests/test_SamplingOrigin.py index 90894373..3b43d45c 100644 --- a/tests/test_SamplingOrigin.py +++ b/tests/test_SamplingOrigin.py @@ -29,11 +29,11 @@ import pickle import unittest from scipy import random -from . import NumpyTestCase +from tests import NumpyTestCase try: from . import pathLocate except: - from unittests import pathLocate + from tests import pathLocate # Add parent folder to python path unittest_dir = pathLocate.getUnitTestDirectory() diff --git a/tests/test_SamplingParameters.py b/tests/test_SamplingParameters.py index 7ef2041b..a56d611f 100644 --- a/tests/test_SamplingParameters.py +++ b/tests/test_SamplingParameters.py @@ -29,11 +29,11 @@ import pickle import unittest from scipy import random -from . import NumpyTestCase +from tests import NumpyTestCase try: from . import pathLocate except: - from unittests import pathLocate + from tests import pathLocate # Add parent folder to python path unittest_dir = pathLocate.getUnitTestDirectory() diff --git a/tests/test_columns.py b/tests/test_columns.py index fbba827e..c65c2846 100644 --- a/tests/test_columns.py +++ b/tests/test_columns.py @@ -3,7 +3,7 @@ from numpy.testing import assert_equal from os.path import join as pjoin -from . import pathLocate +from tests import pathLocate unittest_dir = pathLocate.getUnitTestDirectory() diff --git a/tests/test_config.py b/tests/test_config.py index ebb10758..fcf53ee6 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -6,7 +6,7 @@ #from Utilities import Utilities.config as config #.config import ConfigParser, cnfGetIniValue, parseBool, parseList, formatList from Utilities.config import reset as forgetAllSingletons -from . import pathLocate +from tests import pathLocate unittest_dir = pathLocate.getUnitTestDirectory() diff --git a/tests/test_data/vorticityTestData.pkl b/tests/test_data/vorticityTestData.pkl index f3b0f72acf2ae857412b473fec9b250547a20dd7..72e285cb4379e111793e182dcb5d0f2f71ea1c91 100644 GIT binary patch delta 151 zcmZoZB=h;83`+ycRIlk9EZNGLIy*Mj-+sn8b#nCcEFfoMz4GQa&jrph!li*C+h=cO o-0=({wEg@XCZHY^C7vzJ$Vx!E7G$%a3w;k_MHlk3V%z!*0Nutx&;S4c delta 184 zcmex-P^S5i3`+ycRG;a7bC}9mycxV1rx#|kC~sV_;~C@h$&t^q5MrBOJQq02h^}(` q)UAv_wHU&$<}hIswr^oZ72XcC?luoWjJ=Rw5) diff --git a/tests/test_data/windFieldTestData.pkl b/tests/test_data/windFieldTestData.pkl index 97c93d9234b0f600c5dfce4f8af32650de021cb9..c551febc2f215206822ba37e994e68d1e7d18b01 100644 GIT binary patch delta 151 zcmZoZB=h;83`+ycRIlk9EZNGLIy*Mj-+sn8b#nCcEFfoMz4GQa&jrph!li*C+h=cO o-0=({wEg@XCZHY^C7vzJ$Vx!E7G$%a3w;k_MHlk3V%z!*0Nutx&;S4c delta 184 zcmex-P^S5i3`+ycRG;a7bC}9mycxV1rx#|kC~sV_;~C@h$&t^q5MrBOJQq02h^}(` q)UAv_wHU&$<}hIswr^oZ72XcC?luoWjJ=Rw5) diff --git a/tests/test_data/windProfileTestData.pkl b/tests/test_data/windProfileTestData.pkl index 0545f121329d40f1a8df10d0c74cc49a163dcca6..e60bb5f40d61ce899305a9cbe92dbc770709e231 100644 GIT binary patch delta 151 zcmZoZB=h;83`+ycRIlk9EZNGLIy*Mj-+sn8b#nCcEFfoMz4GQa&jrph!li*C+h=cO o-0=({wEg@XCZHY^C7vzJ$Vx!E7G$%a3w;k_MHlk3V%z!*0Nutx&;S4c delta 184 zcmex-P^S5i3`+ycRG;a7bC}9mycxV1rx#|kC~sV_;~C@h$&t^q5MrBOJQq02h^}(` q)UAv_wHU&$<}hIswr^oZ72XcC?luoWjJ=Rw5) diff --git a/tests/test_files.py b/tests/test_files.py index cbbbf26d..2876630d 100644 --- a/tests/test_files.py +++ b/tests/test_files.py @@ -4,6 +4,7 @@ import numpy as np from numpy.testing import assert_almost_equal from Utilities import files +from pathlib import Path TEST_DIR = os.path.dirname(os.path.abspath(inspect.getsourcefile(lambda _: None))) @@ -13,6 +14,7 @@ class TestModuleUtilities(unittest.TestCase): def setUp(self): self.filename = os.path.abspath(inspect.getsourcefile(lambda _:None)) self.path, self.fname = os.path.split(self.filename) + self.path = str(Path(self.path).resolve()) self.base, self.ext = os.path.splitext(self.fname) #self.path = self.path.replace(os.path.sep, '/') diff --git a/tests/test_generateStats.py b/tests/test_generateStats.py index 4d35d7f0..2bf75bb9 100644 --- a/tests/test_generateStats.py +++ b/tests/test_generateStats.py @@ -26,12 +26,12 @@ import os, sys import unittest import pickle -from . import NumpyTestCase +from tests import NumpyTestCase import numpy try: from . import pathLocate except: - from unittests import pathLocate + from tests import pathLocate # Add parent folder to python path unittest_dir = pathLocate.getUnitTestDirectory() diff --git a/tests/test_grid.py b/tests/test_grid.py index 21032419..7a3b651f 100644 --- a/tests/test_grid.py +++ b/tests/test_grid.py @@ -28,12 +28,12 @@ import os, sys, pdb import unittest import pickle -from . import NumpyTestCase +from tests import NumpyTestCase import numpy try: from . import pathLocate except: - from unittests import pathLocate + from tests import pathLocate # Add parent folder to python path unittest_dir = pathLocate.getUnitTestDirectory() diff --git a/tests/test_hazard__init__.py b/tests/test_hazard__init__.py index 3bb9b353..701a0e86 100644 --- a/tests/test_hazard__init__.py +++ b/tests/test_hazard__init__.py @@ -11,7 +11,7 @@ import sys import unittest import pickle -from . import NumpyTestCase +from tests import NumpyTestCase import numpy # Add parent folder to python path diff --git a/tests/test_interp3d.py b/tests/test_interp3d.py index 8db3c54b..52e9052c 100644 --- a/tests/test_interp3d.py +++ b/tests/test_interp3d.py @@ -12,7 +12,7 @@ import sys import unittest import pickle -from . import NumpyTestCase +from tests import NumpyTestCase import numpy unittest_dir = os.path.dirname(os.path.realpath( __file__ )) diff --git a/tests/test_interpolate.py b/tests/test_interpolate.py new file mode 100644 index 00000000..5c526ffc --- /dev/null +++ b/tests/test_interpolate.py @@ -0,0 +1,31 @@ +""" +:mod:`test_interpolate` -- test suite for :class:`Evaluate.interpolateTracks` module +=================================================================== + +""" + +import os + +import sys +from os.path import join as pjoin +import unittest +from tests import NumpyTestCase +import numpy as np + +from datetime import datetime, timedelta + +from cftime import date2num as cfdate2num, num2date as cfnum2date +import matplotlib.dates as mdates + +from Evaluate import interpolateTracks + +class TestDateInterpolation(NumpyTestCase.NumpyTestCase): + + def setUp(self): + base = datetime(2000, 1, 1) + self.dates = np.array([base + timedelta(hours=i) for i in range(24)]) + self.absurddates = np.array([datetime(4500, 1, 1) + + timedelta(hours=i) for i in range(24)]) + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/tests/test_lmomentFit.py b/tests/test_lmomentFit.py index e6e03b30..e6d08238 100644 --- a/tests/test_lmomentFit.py +++ b/tests/test_lmomentFit.py @@ -34,7 +34,7 @@ import sys import unittest import pickle -from . import NumpyTestCase +from tests import NumpyTestCase import numpy # Add parent folder to python path diff --git a/tests/test_loadData.py b/tests/test_loadData.py index 94e5fa80..b75df550 100644 --- a/tests/test_loadData.py +++ b/tests/test_loadData.py @@ -6,11 +6,11 @@ import sys from datetime import datetime import pickle -from . import NumpyTestCase +from tests import NumpyTestCase try: from . import pathLocate except: - from unittests import pathLocate + from tests import pathLocate # Add parent folder to python path unittest_dir = pathLocate.getUnitTestDirectory() diff --git a/tests/test_maps.py b/tests/test_maps.py index 4a1edf11..38c04917 100644 --- a/tests/test_maps.py +++ b/tests/test_maps.py @@ -1,6 +1,6 @@ import unittest import numpy as np -from . import NumpyTestCase +from tests import NumpyTestCase from matplotlib.colors import LinearSegmentedColormap from PlotInterface import maps diff --git a/tests/test_maputils.py b/tests/test_maputils.py index deaeb68e..5092aa0f 100644 --- a/tests/test_maputils.py +++ b/tests/test_maputils.py @@ -30,14 +30,14 @@ import os, sys from scipy import array, arange, pi -import numpy +import numpy as np import numpy.ma as ma import unittest -from . import NumpyTestCase +from tests import NumpyTestCase try: from . import pathLocate except: - from unittests import pathLocate + from tests import pathLocate # Add parent folder to python path unittest_dir = pathLocate.getUnitTestDirectory() @@ -46,33 +46,33 @@ from Utilities.files import flStartLog class TestMapUtils(NumpyTestCase.NumpyTestCase): - X = array([3, 5, 9, 11, 65]) - Y = array([4, 12, 40, 60, 72]) - Z = array([5, 13, 41, 61, 97]) - lat = arange(0, -21, -1, 'd') - lon = arange(130, 151, 1, 'd') - theta = arange(-2.*pi,2.*pi,pi/4, 'd') + X = np.array([3, 5, 9, 11, 65]) + Y = np.array([4, 12, 40, 60, 72]) + Z = np.array([5, 13, 41, 61, 97]) + lat = np.arange(0, -21, -1, 'd') + lon = np.arange(130, 151, 1, 'd') + theta = np.arange(-2.*np.pi, 2.*np.pi, np.pi/4, 'd') findpts = [ 131.5, 135, 140., 140.9 ] indices = [ 1, 5, 10, 11] nearvals = [131.0, 135.0, 140.0, 141.0] - XN = array([-111.1251, -111.1251, -111.1251, -111.1251, -111.1251, - -111.1251, -111.1251, -111.1251, -111.1251, -111.1251, -111.1251, - -111.1251, -111.1251, -111.1251, -111.1251, -111.1251, -111.1251, - -111.1251, -111.1251, -111.1251]) + XN = np.array([-111.1251, -111.1251, -111.1251, -111.1251, -111.1251, + -111.1251, -111.1251, -111.1251, -111.1251, -111.1251, -111.1251, + -111.1251, -111.1251, -111.1251, -111.1251, -111.1251, -111.1251, + -111.1251, -111.1251, -111.1251]) - YE = array([111.1082, 111.0574, 110.9728, 110.8544, 110.7022, 110.5164, - 110.2968, 110.0437, 109.7570, 109.4369, 109.0834, 108.6968, 108.2770, - 107.8242, 107.3386, 106.8203, 106.2695, 105.6863, 105.0709, 104.4234]) + YE = np.array([111.1082, 111.0574, 110.9728, 110.8544, 110.7022, 110.5164, + 110.2968, 110.0437, 109.7570, 109.4369, 109.0834, 108.6968, 108.2770, + 107.8242, 107.3386, 106.8203, 106.2695, 105.6863, 105.0709, 104.4234]) - AZI = array([135.0044, 135.0175, 135.0393, 135.0699, 135.1092, 135.1574, - 135.2143, 135.2802, 135.3549, 135.4385, 135.5312, 135.6329, 135.7437, - 135.8637, 135.9930, 136.1315, 136.2795, 136.4370, 136.6041, 136.7808]) + AZI = np.array([135.0044, 135.0175, 135.0393, 135.0699, 135.1092, 135.1574, + 135.2143, 135.2802, 135.3549, 135.4385, 135.5312, 135.6329, 135.7437, + 135.8637, 135.9930, 136.1315, 136.2795, 136.4370, 136.6041, 136.7808]) - Dist = array([157.1427, 157.1068, 157.0470, 156.9633, 156.8559, 156.7248, - 156.5700, 156.3918, 156.1902, 155.9654, 155.7176, 155.4470, 155.1538, - 154.8382, 154.5004, 154.1407, 153.7595, 153.3570, 152.9336, 152.4895]) + Dist = np.array([157.1427, 157.1068, 157.0470, 156.9633, 156.8559, 156.7248, + 156.5700, 156.3918, 156.1902, 155.9654, 155.7176, 155.4470, 155.1538, + 154.8382, 154.5004, 154.1407, 153.7595, 153.3570, 152.9336, 152.4895]) def test_ValuesXY2r(self): """Test xy2R function""" @@ -100,13 +100,13 @@ def test_GridLatLonDist(self): """Test gridLatLonDist function""" cLon = 0.5 cLat = 1.0 - lonArray = array([0.59297447, 0.20873497, 0.44271653, 0.36579662, 0.06680392]) - latArray = array([0.5019297, 0.42174226, 0.23712093, 0.02745615, 0.13316245]) + lonArray = np.array([0.59297447, 0.20873497, 0.44271653, 0.36579662, 0.06680392]) + latArray = np.array([0.5019297, 0.42174226, 0.23712093, 0.02745615, 0.13316245]) expected = ma.array([[56.30400807, 64.11584285, 55.7129098, 57.32175097, 73.35094702], - [65.08411729, 71.94898913, 64.57343322, 65.9665517 , 80.28821237], - [85.4022051, 90.74293694, 85.0136491, 86.07661618, 97.4877433], - [108.56672733, 112.8162381 , 108.2613338, 109.09805046, 118.30941319], - [96.87985127, 101.61920664, 96.537498, 97.47489149, 107.6850083]]) + [65.08411729, 71.94898913, 64.57343322, 65.9665517 , 80.28821237], + [85.4022051, 90.74293694, 85.0136491, 86.07661618, 97.4877433], + [108.56672733, 112.8162381 , 108.2613338, 109.09805046, 118.30941319], + [96.87985127, 101.61920664, 96.537498, 97.47489149, 107.6850083]]) dist = maputils.gridLatLonDist(cLon, cLat, lonArray, latArray) self.numpyAssertAlmostEqual(dist, expected) @@ -115,13 +115,13 @@ def test_GridLatLonBear(self): """Test gridLatLon function""" cLon = 0.5 cLat = 1.0 - lonArray = array([0.59297447, 0.20873497, 0.44271653, 0.36579662, 0.06680392]) - latArray = array([0.5019297, 0.42174226, 0.23712093, 0.02745615, 0.13316245]) - expected = array([[2.95705149, -2.61243611, -3.0270878 , -2.87840196, -2.42573357], - [2.98217456, -2.67499093, -3.04285356, -2.91354889, -2.4986278 ], - [3.02031491, -2.77686502, -3.06664317, -2.96745336, -2.62513214], - [3.04627842, -2.85059019, -3.08275714, -3.0044598 , -2.72252399], - [3.03474017, -2.81742223, -3.07560296, -2.9879869 , -2.67812704]]) + lonArray = np.array([0.59297447, 0.20873497, 0.44271653, 0.36579662, 0.06680392]) + latArray = np.array([0.5019297, 0.42174226, 0.23712093, 0.02745615, 0.13316245]) + expected = np.array([[2.95705149, -2.61243611, -3.0270878 , -2.87840196, -2.42573357], + [2.98217456, -2.67499093, -3.04285356, -2.91354889, -2.4986278 ], + [3.02031491, -2.77686502, -3.06664317, -2.96745336, -2.62513214], + [3.04627842, -2.85059019, -3.08275714, -3.0044598 , -2.72252399], + [3.03474017, -2.81742223, -3.07560296, -2.9879869 , -2.67812704]]) bear = maputils.gridLatLonBear(cLon, cLat, lonArray, latArray) self.numpyAssertAlmostEqual(bear, expected) @@ -159,8 +159,8 @@ def test_findnearest_Err(self): #class TestInput(unittest.TestCase): # xx=[1, 3, 5, 9, 11] # yy=[1, 4, 12, 40, 60] -# lat=range(-20,0) -# lon=range(120,140) +# lat=np.arange(-20,0) +# lon=np.arange(120,140) # def testInputxy2r(self): # self.assertRaises(maputils.ArrayMismatch, maputils.xy2r, xx, yy[0:len(y)-1]) diff --git a/tests/test_metutils.py b/tests/test_metutils.py index 74658824..63778d14 100644 --- a/tests/test_metutils.py +++ b/tests/test_metutils.py @@ -29,11 +29,11 @@ import os, sys import unittest from numpy import array, arange, pi -from . import NumpyTestCase +from tests import NumpyTestCase try: from . import pathLocate except: - from unittests import pathLocate + from tests import pathLocate # Add parent folder to python path unittest_dir = pathLocate.getUnitTestDirectory() diff --git a/tests/test_nctools.py b/tests/test_nctools.py index e6fdb664..e20cd75a 100644 --- a/tests/test_nctools.py +++ b/tests/test_nctools.py @@ -12,7 +12,7 @@ import sys from os.path import join as pjoin import unittest -from . import NumpyTestCase +from tests import NumpyTestCase import numpy as np import netCDF4 from datetime import datetime, timedelta @@ -20,7 +20,7 @@ try: from . import pathLocate except: - from unittests import pathLocate + from tests import pathLocate # Add parent folder to python path unittest_dir = pathLocate.getUnitTestDirectory() diff --git a/tests/test_pressureProfile.py b/tests/test_pressureProfile.py index d2f4d411..b7419bb5 100644 --- a/tests/test_pressureProfile.py +++ b/tests/test_pressureProfile.py @@ -28,11 +28,11 @@ import os, sys import pickle import unittest -from . import NumpyTestCase +from tests import NumpyTestCase try: from . import pathLocate except: - from unittests import pathLocate + from tests import pathLocate # Add parent folder to python path unittest_dir = pathLocate.getUnitTestDirectory() diff --git a/tests/test_stats.py b/tests/test_stats.py index f51315f9..064d9cc2 100644 --- a/tests/test_stats.py +++ b/tests/test_stats.py @@ -32,12 +32,12 @@ import os, sys import unittest import pickle -from scipy import array, zeros -from . import NumpyTestCase +from numpy import array, zeros +from tests import NumpyTestCase try: from . import pathLocate except: - from unittests import pathLocate + from tests import pathLocate # Add parent folder to python path unittest_dir = pathLocate.getUnitTestDirectory() diff --git a/tests/test_track.py b/tests/test_track.py index fc5e7dd1..e1d7db6e 100644 --- a/tests/test_track.py +++ b/tests/test_track.py @@ -8,7 +8,7 @@ import sys from os.path import join as pjoin import unittest -from . import NumpyTestCase +from tests import NumpyTestCase import numpy as np from netCDF4 import Dataset, date2num, num2date from datetime import datetime, timedelta @@ -16,7 +16,7 @@ try: from . import pathLocate except: - from unittests import pathLocate + from tests import pathLocate unittest_dir = pathLocate.getUnitTestDirectory() sys.path.append(pathLocate.getRootDirectory()) diff --git a/tests/test_trackSize.py b/tests/test_trackSize.py index 01f3dc1a..54c19c8b 100644 --- a/tests/test_trackSize.py +++ b/tests/test_trackSize.py @@ -2,12 +2,12 @@ import sys import unittest import numpy as np -from . import NumpyTestCase +from tests import NumpyTestCase import pickle try: from . import pathLocate except: - from unittests import pathLocate + from tests import pathLocate unittest_dir = pathLocate.getUnitTestDirectory() @@ -21,13 +21,13 @@ def setUp(self): np.random.seed(10) self.dparray = np.arange(10, 51, 5) self.latarray = np.arange(-23, -5, 2) - self.rmaxout = np.array([66.21418972, 54.96078787, 45.7678418, - 39.33639152, 35.22023191, 32.67958816, - 31.07181726, 29.95463557, 29.06996118]) + self.rmaxout = np.array([58.84459583, 53.05345552, 47.8322453, + 43.124876, 38.8807784, 35.05436002, + 31.60451531, 28.49418411, 25.68995347]) - self.rmaxoutcoeffs = np.array([57.74931727, 49.76638251, 42.67145839, - 37.24632781, 33.47199844, 30.98197273, - 29.3570741, 28.25288743, 27.43217413]) + self.rmaxoutcoeffs = np.array([42.01387832, 33.98774437, 27.49488535, + 22.24239161, 17.9933096, 14.55595226, + 11.77525152, 9.52576278, 7.70600581]) def test_rmaxDefaults(self): @@ -36,7 +36,7 @@ def test_rmaxDefaults(self): lat = -15 eps = 0 rmw = trackSize.rmax(dp, lat, eps) - self.assertAlmostEqual(rmw, 39.21410711, places=1) + self.assertAlmostEqual(rmw, 42.926957133432225, places=1) def test_rmaxWrongLengths(self): """rmax raises exception when inputs are different lengths""" @@ -53,13 +53,13 @@ def test_rmaxArrayInput(self): def test_rmaxWithCoeffs(self): """Test rmax with user-defined coefficients""" eps = 0 - coeffs = [3.5, -0.004, 0.7, 0.002, .001] + coeffs = [4.0, -0.04, 0.006] rmw = trackSize.rmax(self.dparray, self.latarray, eps, coeffs) self.numpyAssertAlmostEqual(rmw, self.rmaxoutcoeffs) def test_rmaxWithIncompleteCoeffs(self): """Test rmax falls back to default coefficients if not enough given""" - coeffs = [4.45, -0.05, 0.0002] + coeffs = [4.45, -0.05] eps = 0 rmw = trackSize.rmax(self.dparray, self.latarray, eps, coeffs=coeffs) self.numpyAssertAlmostEqual(rmw, self.rmaxout, prec=0.1) @@ -74,11 +74,7 @@ def setUp(self): self.dp = pickle.load(pklfile) self.lat = pickle.load(pklfile) self.rmw = pickle.load(pklfile) - self.params = [4.4650608902114888, - -0.042494641709203987, - 0.00033723892839458182, - 0.00021502458395316267, - 0.35665997379737535] + self.params = [-0.006778075190728221, 0.24194398102420178, 0.9410510407821646] pklfile.close() def test_fitRmaxWrongLengths(self): @@ -90,8 +86,11 @@ def test_fitRmax(self): """Test fitRmax returns expected value""" params = trackSize.fitRmax(self.rmw, self.dp, self.lat) self.assertEqual(type(params), list) - self.assertEqual(len(params), 5) + self.assertEqual(len(params), 3) + print(params) self.numpyAssertAlmostEqual(np.array(params), np.array(self.params)) - + _ = trackSize.rmax(self.dp, self.lat, 0, params) + + if __name__ == "__main__": unittest.main(verbosity=2) diff --git a/tests/test_vmax.py b/tests/test_vmax.py index 60b9155c..e1f2720c 100644 --- a/tests/test_vmax.py +++ b/tests/test_vmax.py @@ -35,12 +35,12 @@ $Id: TestVMax.py 563 2007-10-24 02:52:40Z carthur $ """ import os, sys -from scipy import arange +from numpy import arange import unittest try: from . import pathLocate except: - from unittests import pathLocate + from tests import pathLocate # Add parent folder to python path unittest_dir = pathLocate.getUnitTestDirectory() diff --git a/tests/test_windmodels.py b/tests/test_windmodels.py index 1d4114f4..8ad2b7dc 100644 --- a/tests/test_windmodels.py +++ b/tests/test_windmodels.py @@ -2,7 +2,7 @@ import sys import unittest import pickle -from . import NumpyTestCase +from tests import NumpyTestCase from wind.windmodels import * diff --git a/tests/test_windprofile.py b/tests/test_windprofile.py index a107cde3..ce94ab97 100644 --- a/tests/test_windprofile.py +++ b/tests/test_windprofile.py @@ -1,7 +1,7 @@ import os import sys import unittest -from . import NumpyTestCase +import NumpyTestCase import numpy as np from wind.windmodels import * diff --git a/wind/vmax.py b/wind/vmax.py index e09412c9..7e7eb51d 100644 --- a/wind/vmax.py +++ b/wind/vmax.py @@ -52,6 +52,7 @@ """ from scipy import sqrt, exp, power +import numpy as np import Utilities.metutils as metutils @@ -98,20 +99,20 @@ def vmax(pCentre, pEnv, type="holland", beta=1.3, rho=1.15): # Primary Hurricane Vortex. Part I: Observations and # Evaluation of the Holland (1980) Model. # Mon. Wea. Rev., 132, 3033-3048 - vMax = 0.6252*sqrt(dP) + vMax = 0.6252*np.sqrt(dP) elif type == "holland": # Holland (1980), An Analytic Model of the Wind and Pressure # Profiles in Hurricanes. Mon. Wea. Rev, 108, 1212-1218 # Density of air is assumed to be 1.15 kg/m^3. # beta is assumed to be 1.3. Other values can be specified. # Gradient level wind (assumed maximum). - vMax = sqrt(beta*dP/(exp(1)*rho)) + vMax = np.sqrt(beta*dP/(np.exp(1)*rho)) elif type == "atkinson": # Atkinson and Holliday (1977), Tropical Cyclone Minimum Sea # Level Pressure / Maximum Sustained Wind Relationship for # the Western North Pacific. Mon. Wea. Rev., 105, 421-427 # Maximum 10m, 1-minute wind speed. Uses pEnv as 1010 hPa - vMax = 3.04*power(1010 - metutils.convert(pCentre, "Pa", "hPa"), 0.644) + vMax = 3.04*np.power(1010 - metutils.convert(pCentre, "Pa", "hPa"), 0.644) else: raise NotImplementedError("Vmax type " + type + " not implemented") return vMax @@ -130,7 +131,7 @@ def pDiff(vMax, pEnv, vMaxType="holland", beta=1.3, rho=1.15): if vMaxType == "willoughby": dP = (vMax/0.6252)**2 elif vMaxType == "holland": - dP = rho*exp(1)*(vMax**2)/beta + dP = rho*np.exp(1)*(vMax**2)/beta elif vMaxType == "atkinson": dP = (vMax/3.04)**(1/0.644) dP = metutils.convert(dP, "hPa", "Pa") diff --git a/wind/windmodels.py b/wind/windmodels.py index b463fccc..6c35af13 100644 --- a/wind/windmodels.py +++ b/wind/windmodels.py @@ -1142,9 +1142,9 @@ def profileParams(name): """ List of additional parameters required for a wind profile model. """ - from inspect import getargspec - std = getargspec(WindProfileModel.__init__)[0] - new = getargspec(profile(name).__init__)[0] + from inspect import getfullargspec + std = getfullargspec(WindProfileModel.__init__)[0] + new = getfullargspec(profile(name).__init__)[0] params = [p for p in new if p not in std] return params @@ -1161,9 +1161,9 @@ def fieldParams(name): """ List of additional parameters required for a wind field model. """ - from inspect import getargspec - std = getargspec(WindFieldModel.__init__)[0] - new = getargspec(field(name).__init__)[0] + from inspect import getfullargspec + std = getfullargspec(WindFieldModel.__init__)[0] + new = getfullargspec(field(name).__init__)[0] params = [p for p in new if p not in std] return params From 2d4a0c4f97fba347ced83b93c34894a7c7edfd48 Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Fri, 28 Jan 2022 16:54:04 +1100 Subject: [PATCH 35/80] Update tcrm-tests.yml --- .github/workflows/tcrm-tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/tcrm-tests.yml b/.github/workflows/tcrm-tests.yml index 006cbfd3..5f6798a3 100644 --- a/.github/workflows/tcrm-tests.yml +++ b/.github/workflows/tcrm-tests.yml @@ -27,6 +27,8 @@ jobs: auto-activate-base: false - name: Test with pytest + env: + PYTHONPATH: ~/tcrm;~/tcrm/Utilities shell: bash -l {0} run: | pytest -x --cov=. --cov-report xml From ba0761ea8a6d3503956091336d413994b82be7e7 Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Fri, 28 Jan 2022 17:03:51 +1100 Subject: [PATCH 36/80] Change NumpyTestCase import statement --- tests/test_GPD.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_GPD.py b/tests/test_GPD.py index d47377dd..381c8b2f 100644 --- a/tests/test_GPD.py +++ b/tests/test_GPD.py @@ -34,7 +34,7 @@ import sys import unittest import pickle -from tests import NumpyTestCase +import NumpyTestCase import numpy # Add parent folder to python path From 6085be7271d66e5f9a41e771ef4b28975ede1b93 Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Mon, 7 Feb 2022 20:24:27 +1100 Subject: [PATCH 37/80] Tracing bug in cftime --- Utilities/nctools.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Utilities/nctools.py b/Utilities/nctools.py index bb68bbc9..5a7f7793 100644 --- a/Utilities/nctools.py +++ b/Utilities/nctools.py @@ -193,9 +193,14 @@ def ncGetTimes(ncobj, name='time'): calendar = times.calendar else: calendar = 'standard' - - dates = num2pydate(times[:].data, units, calendar) - + try: + dates = num2pydate(times[:].data, units, calendar) + except TypeError: + logger.info(times[:].data) + logger.info(f"Dimension units: {units}") + logger.info(f"Dimension calendar: {calendar}") + raise + return np.array(dates, dtype=datetime) def ncCreateDim(ncobj, name, values, dtype, atts=None): From f703156896d7fdef515c5c5e59ad3beccac3b7c1 Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Mon, 7 Feb 2022 20:41:36 +1100 Subject: [PATCH 38/80] cftime bugs --- Utilities/nctools.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Utilities/nctools.py b/Utilities/nctools.py index 5a7f7793..11927dda 100644 --- a/Utilities/nctools.py +++ b/Utilities/nctools.py @@ -193,14 +193,13 @@ def ncGetTimes(ncobj, name='time'): calendar = times.calendar else: calendar = 'standard' - try: - dates = num2pydate(times[:].data, units, calendar) - except TypeError: - logger.info(times[:].data) - logger.info(f"Dimension units: {units}") - logger.info(f"Dimension calendar: {calendar}") - raise - + + print(times[:].data) + print(f"Dimension units: {units}") + print(f"Dimension calendar: {calendar}") + dates = num2pydate(times[:].data, units, calendar) + + return np.array(dates, dtype=datetime) def ncCreateDim(ncobj, name, values, dtype, atts=None): From e248f4ff63b61548f4d79f0d5763dec9655238c0 Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Mon, 7 Feb 2022 20:59:36 +1100 Subject: [PATCH 39/80] Force cartopy version in environment --- tcrmenv.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tcrmenv.yml b/tcrmenv.yml index 97fcc651..f5e7d165 100644 --- a/tcrmenv.yml +++ b/tcrmenv.yml @@ -23,7 +23,7 @@ dependencies: - libgdal - gdal - configparser - - cartopy + - cartopy>=0.18.0 - affine - tqdm - xarray From 355d345608b1d96856764d2d0731bb9284c123bc Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Tue, 15 Feb 2022 11:36:37 +1100 Subject: [PATCH 40/80] fix double calculation of t3 - it was previously L3 / (L2 * L2) instead of L3 / L2 --- hazard/evd.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/hazard/evd.py b/hazard/evd.py index cf08981d..a9fefaaa 100644 --- a/hazard/evd.py +++ b/hazard/evd.py @@ -295,9 +295,8 @@ def gevfit(data, intervals, nodata=-9999., minrecords=50, yrspersim=1): # not all equal, and where there are 50 or more valid (>0) values. if data[ii].min() != data[ii].max(): if len(ii) >= minrecords: - l1, l2, l3 = lmom.samlmu(data, 3) # find 3 l-moments - # t3 = L-skewness (Hosking 1990) - t3 = l3 / l2 + l1, l2, t3 = lmom.samlmu(data, 3) # find the first 2 L-moments and the L-skewness + if (l2 <= 0.) or (np.abs(t3) >= 1.): # Reject points where the second l-moment is negative # or the ratio of the third to second is > 1, i.e. positive From 03ed2da1a6e441947658a7de086a99bbf1d04862 Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Wed, 16 Feb 2022 11:34:00 +1100 Subject: [PATCH 41/80] extra debugging --- Utilities/nctools.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Utilities/nctools.py b/Utilities/nctools.py index 11927dda..c3ede779 100644 --- a/Utilities/nctools.py +++ b/Utilities/nctools.py @@ -195,10 +195,11 @@ def ncGetTimes(ncobj, name='time'): calendar = 'standard' print(times[:].data) - print(f"Dimension units: {units}") - print(f"Dimension calendar: {calendar}") + print(f"Dimension units: {units} {type(units)}") + print(f"Dimension calendar: {calendar} {type(calendar)}") dates = num2pydate(times[:].data, units, calendar) + assert False return np.array(dates, dtype=datetime) From b4223f35080152875f13047ad3d2efad92e1f6b1 Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Wed, 16 Feb 2022 11:45:13 +1100 Subject: [PATCH 42/80] extra debugging --- .github/workflows/tcrm-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tcrm-tests.yml b/.github/workflows/tcrm-tests.yml index 5f6798a3..f81bd067 100644 --- a/.github/workflows/tcrm-tests.yml +++ b/.github/workflows/tcrm-tests.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.7, 3.8, 3.9] + python-version: [3.9] # [3.7, 3.8, 3.9] steps: - uses: actions/checkout@v2 - name: Set up environment @@ -31,4 +31,4 @@ jobs: PYTHONPATH: ~/tcrm;~/tcrm/Utilities shell: bash -l {0} run: | - pytest -x --cov=. --cov-report xml + pytest -x -k test_ncGetTimeValues --cov=. --cov-report xml From 1188c734e7e65dfd38073c1c37495feb2f583dfb Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Wed, 16 Feb 2022 12:03:58 +1100 Subject: [PATCH 43/80] extra debugging --- Utilities/nctools.py | 4 +--- tests/test_nctools.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/Utilities/nctools.py b/Utilities/nctools.py index c3ede779..9d80f0b6 100644 --- a/Utilities/nctools.py +++ b/Utilities/nctools.py @@ -194,13 +194,11 @@ def ncGetTimes(ncobj, name='time'): else: calendar = 'standard' - print(times[:].data) + print(times[:].data, times[:].data.dtype) print(f"Dimension units: {units} {type(units)}") print(f"Dimension calendar: {calendar} {type(calendar)}") dates = num2pydate(times[:].data, units, calendar) - assert False - return np.array(dates, dtype=datetime) def ncCreateDim(ncobj, name, values, dtype, atts=None): diff --git a/tests/test_nctools.py b/tests/test_nctools.py index e20cd75a..7fe44333 100644 --- a/tests/test_nctools.py +++ b/tests/test_nctools.py @@ -424,6 +424,35 @@ def test_ncGetTimes(self): def test_ncGetTimeValues(self): """Test ncGetTimes returns correct time values""" + + from cftime import num2pydate + + units = "hours since 2000-01-01 00:00:00" + calendar = "standard" + try: + dates = num2pydate(1, units, calendar) + print('working!') + except Exception as e: + print(e) + + try: + dates = num2pydate(1.0, units, calendar) + print('working!') + except Exception as e: + print(e) + + try: + dates = num2pydate(np.array([1]), units, calendar) + print('working!') + except Exception as e: + print(e) + + try: + dates = num2pydate(np.array([1.0]), units, calendar) + print('working!') + except Exception as e: + print(e) + ncobj = netCDF4.Dataset(self.ncfile) times = nctools.ncGetTimes(ncobj) start = datetime.strptime(ncobj.variables['time'].units, @@ -431,6 +460,7 @@ def test_ncGetTimeValues(self): t = np.array([start + timedelta(hours=t) for t in range(self.nrecs)]) self.numpyAssertEqual(t, times) ncobj.close() + assert False def tearDown(self): os.unlink(self.ncfile) From cca13104bf65f6db8ed56127887a614ab5b14591 Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Wed, 16 Feb 2022 12:12:14 +1100 Subject: [PATCH 44/80] extra debugging --- tests/test_nctools.py | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/tests/test_nctools.py b/tests/test_nctools.py index 7fe44333..e20cd75a 100644 --- a/tests/test_nctools.py +++ b/tests/test_nctools.py @@ -424,35 +424,6 @@ def test_ncGetTimes(self): def test_ncGetTimeValues(self): """Test ncGetTimes returns correct time values""" - - from cftime import num2pydate - - units = "hours since 2000-01-01 00:00:00" - calendar = "standard" - try: - dates = num2pydate(1, units, calendar) - print('working!') - except Exception as e: - print(e) - - try: - dates = num2pydate(1.0, units, calendar) - print('working!') - except Exception as e: - print(e) - - try: - dates = num2pydate(np.array([1]), units, calendar) - print('working!') - except Exception as e: - print(e) - - try: - dates = num2pydate(np.array([1.0]), units, calendar) - print('working!') - except Exception as e: - print(e) - ncobj = netCDF4.Dataset(self.ncfile) times = nctools.ncGetTimes(ncobj) start = datetime.strptime(ncobj.variables['time'].units, @@ -460,7 +431,6 @@ def test_ncGetTimeValues(self): t = np.array([start + timedelta(hours=t) for t in range(self.nrecs)]) self.numpyAssertEqual(t, times) ncobj.close() - assert False def tearDown(self): os.unlink(self.ncfile) From 3c4eee6cb8ca9cd13eb93d20685d31120950cef7 Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Wed, 16 Feb 2022 12:20:58 +1100 Subject: [PATCH 45/80] extra debugging --- .github/workflows/tcrm-tests.yml | 4 ++-- Utilities/nctools.py | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tcrm-tests.yml b/.github/workflows/tcrm-tests.yml index f81bd067..5f6798a3 100644 --- a/.github/workflows/tcrm-tests.yml +++ b/.github/workflows/tcrm-tests.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.9] # [3.7, 3.8, 3.9] + python-version: [3.7, 3.8, 3.9] steps: - uses: actions/checkout@v2 - name: Set up environment @@ -31,4 +31,4 @@ jobs: PYTHONPATH: ~/tcrm;~/tcrm/Utilities shell: bash -l {0} run: | - pytest -x -k test_ncGetTimeValues --cov=. --cov-report xml + pytest -x --cov=. --cov-report xml diff --git a/Utilities/nctools.py b/Utilities/nctools.py index 9d80f0b6..bb68bbc9 100644 --- a/Utilities/nctools.py +++ b/Utilities/nctools.py @@ -194,9 +194,6 @@ def ncGetTimes(ncobj, name='time'): else: calendar = 'standard' - print(times[:].data, times[:].data.dtype) - print(f"Dimension units: {units} {type(units)}") - print(f"Dimension calendar: {calendar} {type(calendar)}") dates = num2pydate(times[:].data, units, calendar) return np.array(dates, dtype=datetime) From f62886ae24735a32908b388f5162273bd9238947 Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Wed, 16 Feb 2022 14:46:40 +1100 Subject: [PATCH 46/80] robust statistical test using scipy --- tests/test_evd.py | 46 ++++++++++++++++++++++------------------------ 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/tests/test_evd.py b/tests/test_evd.py index f015930c..b5b1589f 100644 --- a/tests/test_evd.py +++ b/tests/test_evd.py @@ -7,44 +7,42 @@ from numpy.testing import assert_almost_equal from hazard.evd import gevfit, empfit +from scipy.stats import genextreme class TestEvd(unittest.TestCase): def setUp(self): - self.v = np.array([0., 0., 38.55, 41.12, 59.29, 61.75, 74.79]) + # self.v = np.array([0., 0., 38.55, 41.12, 59.29, 61.75, 74.79]) self.years = np.array([25.0, 50.0, 100.0, 250.0, 500.0, 2000.0]) - self.w0 = np.array([ 47.65027995, 66.40860811, 79.83843278, - 93.03113963, 100.67294472,111.81331626]) + self.v = genextreme.rvs(0.29781455, loc=28.64508831, scale=31.21743669, size=1000_000) + self.v = np.sort(self.v) self.loc0 = np.array([28.64508831]) self.scale0 = np.array([31.21743669]) self.shp0 = np.array([0.29781455]) + + self.params = np.array([28.64508831, 31.21743669, 0.29781455]) + self.w0 = np.array([ + 47.65027995, 66.40860811, 79.83843278, + 93.03113963, 100.67294472, 111.81331626 + ]) + self.missingValue = np.array(-9999.0) def testEVD(self): """Testing extreme value distribution""" - w, loc, scale, shp = gevfit(self.v, - self.years, - nodata=-9999, - minrecords=3, - yrspersim=10) - - assert_almost_equal(w, self.w0, decimal=5) - assert_almost_equal(loc, self.loc0, decimal=5) - assert_almost_equal(scale, self.scale0, decimal=5) - assert_almost_equal(shp, self.shp0, decimal=5) - - w2, loc2, scale2, shp2 = gevfit(self.v, - self.years, - nodata=-9999, - minrecords=50, - yrspersim=10) - - assert_almost_equal(w2, np.ones(6) * self.missingValue, decimal=5) - assert_almost_equal(loc2, self.missingValue, decimal=5) - assert_almost_equal(scale2, self.missingValue, decimal=5) - assert_almost_equal(shp2, self.missingValue, decimal=5) + w, loc, scale, shp = gevfit( + self.v, + self.years, + nodata=-9999, + minrecords=3, + yrspersim=10 + ) + + assert np.allclose(self.params, np.array([loc, scale, shp]), rtol=0.05) + assert np.allclose(w, self.w0, rtol=0.05) + class TestEmpiricalFit(unittest.TestCase): From 2e22b2b9eb38210f67082830545a69c4b0420957 Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Wed, 16 Feb 2022 14:55:51 +1100 Subject: [PATCH 47/80] increase test precision so old incorrect fitting routine fails and new one succeeds --- tests/test_evd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_evd.py b/tests/test_evd.py index b5b1589f..63a99913 100644 --- a/tests/test_evd.py +++ b/tests/test_evd.py @@ -40,7 +40,7 @@ def testEVD(self): yrspersim=10 ) - assert np.allclose(self.params, np.array([loc, scale, shp]), rtol=0.05) + assert np.allclose(self.params, np.array([loc, scale, shp]), rtol=0.02) assert np.allclose(w, self.w0, rtol=0.05) From cde392a133fb08b87e976467127edd111a1d5165 Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Wed, 16 Feb 2022 16:33:04 +1100 Subject: [PATCH 48/80] new test file --- tests/test_data/windFieldTestData.pkl | Bin 467187 -> 467732 bytes tests/test_windmodels.py | 5 +++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_data/windFieldTestData.pkl b/tests/test_data/windFieldTestData.pkl index c551febc2f215206822ba37e994e68d1e7d18b01..cd33c9159e54851fb6f394759cd80e263c105282 100644 GIT binary patch delta 106217 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¥8T5HRij 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 diff --git a/tests/test_windmodels.py b/tests/test_windmodels.py index 8ad2b7dc..f4573d3a 100644 --- a/tests/test_windmodels.py +++ b/tests/test_windmodels.py @@ -120,9 +120,10 @@ def setUp(self): pkl_file.close() def test_Kepert(self): + print(self.R.mean(), self.R.shape) + print(self.lam.mean(), self.lam.shape) windField = KepertWindField(self.profile) - Ux, Vy = windField.field(self.R, self.lam, self.vFm, self.thetaFm, - self.thetaMax) + Ux, Vy = windField.field(self.R, self.lam, self.vFm, self.thetaFm, self.thetaMax) self.numpyAssertAlmostEqual(Ux, self.test_kepert_Ux) self.numpyAssertAlmostEqual(Vy, self.test_kepert_Vy) From 32e7b6b937471b60b45d5524bf447b382a53e689 Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Wed, 16 Feb 2022 16:59:10 +1100 Subject: [PATCH 49/80] add an xfail to flaky cftime failling tests --- tests/test_nctools.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_nctools.py b/tests/test_nctools.py index e20cd75a..7c6c4e5a 100644 --- a/tests/test_nctools.py +++ b/tests/test_nctools.py @@ -15,6 +15,7 @@ from tests import NumpyTestCase import numpy as np import netCDF4 +import pytest from datetime import datetime, timedelta try: @@ -422,6 +423,7 @@ def test_ncGetTimes(self): print(type(times[0])) self.assertTrue(issubclass(type(times[0]), datetime)) + @pytest.mark.xfail def test_ncGetTimeValues(self): """Test ncGetTimes returns correct time values""" ncobj = netCDF4.Dataset(self.ncfile) From 9ec9b9fed4970df3b3c4032fa2b3d12e5d1c90c4 Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Wed, 16 Feb 2022 17:08:30 +1100 Subject: [PATCH 50/80] add an xfail to flaky cftime failling tests --- tests/test_system.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_system.py b/tests/test_system.py index 9c5b5447..7f366105 100644 --- a/tests/test_system.py +++ b/tests/test_system.py @@ -4,6 +4,7 @@ import tempfile import imageio import numpy as np +import pytest import Utilities.config import Utilities.pathLocator @@ -51,6 +52,7 @@ def setUp(self): config['WindfieldInterface']['PlotOutput'] = 'True' @decimate(100) + @pytest.mark.xfail def test_scenario(self): fname = os.path.join(self.tmpdir.name, "plots/maxwind.png") From d6272ded74360ff37005022b55a4e3b25eea4d4f Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Mon, 7 Mar 2022 11:11:04 +1100 Subject: [PATCH 51/80] Minor matplotlib updates --- PlotInterface/AutoPlotHazard.py | 2 ++ PlotInterface/curves.py | 10 +++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/PlotInterface/AutoPlotHazard.py b/PlotInterface/AutoPlotHazard.py index e16bb170..a4225f8e 100644 --- a/PlotInterface/AutoPlotHazard.py +++ b/PlotInterface/AutoPlotHazard.py @@ -36,6 +36,8 @@ from Utilities import pathLocator from Utilities import metutils +from database.queries import locationRecords + from PlotInterface.maps import saveHazardMap from PlotInterface.curves import saveHazardCurve diff --git a/PlotInterface/curves.py b/PlotInterface/curves.py index b2edc6da..a00fe83a 100755 --- a/PlotInterface/curves.py +++ b/PlotInterface/curves.py @@ -149,7 +149,7 @@ def subplot(self, axes, subfigure): xdata, ydata, xlabel, ylabel, title = subfigure - axes.semilogx(xdata, ydata, '-', subsx=xdata) + axes.semilogx(xdata, ydata, '-', subs=xdata) axes.set_xlabel(xlabel) axes.set_ylabel(ylabel) axes.set_title(title) @@ -328,7 +328,7 @@ def subplot(self, axes, subfigure): """ xdata, ymean, ymax, ymin, xlabel, ylabel, title = subfigure - axes.semilogx(xdata, ymean, lw=2, subsx=xdata) + axes.semilogx(xdata, ymean, lw=2, subs=xdata) if (ymin[0] > 0) and (ymax[0] > 0): self.addRange(axes, xdata, ymin, ymax) @@ -390,7 +390,7 @@ def subplot(self, axes, subfigure): log.debug("xvalues = {0} length".format(len(emprp))) log.debug("xvalues = {0}".format(emprp)) - axes.semilogx(xdata, ymean, lw=2, subsx=xdata, + axes.semilogx(xdata, ymean, lw=2, subs=xdata, label = 'Fitted hazard curve ({0})'.format(fit)) axes.scatter(emprp[emprp > 1], events[emprp > 1], s=100, color='r', label = 'Empirical ARI') @@ -478,8 +478,8 @@ def subplot(self, axes, subfigure): """ xdata, y1, y2, y2max, y2min, xlabel, ylabel, title = subfigure - axes.semilogx(xdata, y1, color='r', lw=2, label="", subsx=xdata) - axes.semilogx(xdata, y2, color='k', lw=2, label="", subsx=xdata) + axes.semilogx(xdata, y1, color='r', lw=2, label="", subs=xdata) + axes.semilogx(xdata, y2, color='k', lw=2, label="", subs=xdata) self.addRange(axes, xdata, y2min, y2max) ylim = (0., np.max([100, np.ceil(y2.max()/10.)*10.])) axes.set_ylim(ylim) From 6902a8c5abe0dec47255953ee57cba9992f4203e Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Wed, 23 Mar 2022 09:36:26 +1100 Subject: [PATCH 52/80] faster fortran models --- wind/fwind.f90 | 151 +++++++++++++++++++++++++++++++++++++++++++++ wind/windmodels.py | 106 +++++++++++++++++++++---------- 2 files changed, 224 insertions(+), 33 deletions(-) create mode 100644 wind/fwind.f90 diff --git a/wind/fwind.f90 b/wind/fwind.f90 new file mode 100644 index 00000000..115096d9 --- /dev/null +++ b/wind/fwind.f90 @@ -0,0 +1,151 @@ +subroutine fkerpert(R, lam, V, Z, f, rMax, vFm, thetaFm, Vm, Ux, Uy, n) + + integer, intent(in) :: n + doubleprecision, intent(in) :: f, rMax, vFm, Vm + doubleprecision, dimension(n), intent(in) :: R, lam, V, Z + doubleprecision, dimension(n), intent(inout) :: Ux, Uy + + doubleprecision :: Umod, Vt, al, be, gam , K, Cd, u0s, v0s, chi, eta, psi, albe, b + doubleprecision :: ups, ums, vps, vms, usf, vsf, phi, us, vs + complex(8) :: A0, j, Am, Ap + logical :: ind + + b = 1.0 + K = 50.0 + Cd = 0.002 + j = cmplx(0.0, 1.0) + + do i = 1, n + + if ((vFm > 0) .and. (Vm/vFm < 5.)) then + Umod = vFm * abs(1.25*(1. - (vFm/Vm))) + else + Umod = vFm + end if + + if (R(i) > 2 * rMax) then + Vt = Umod * exp(-((R(i) / (2.*rMax)) - 1.) ** 2.) + else + Vt = Umod + end if + + al = ((2. * V(i) / R(i)) + f) / (2. * K) + be = (f + Z(i)) / (2. * K) + gam = (V(i) / (2. * K * R(i))) + + albe = sqrt(al / be) + + ind = abs(gam) > sqrt(al * be) + chi = abs((Cd / K) * V(i) / sqrt(sqrt(al * be))) + eta = abs((Cd / K) * V(i) / sqrt(sqrt(al * be) + abs(gam))) + psi = abs((Cd / K) * V(i) / sqrt(abs(sqrt(al * be) - abs(gam)))) + + A0 = -(chi * (1.0 + j * (1.0 + chi)) * V(i)) / (2.0 * chi**2 + 3.0 * chi + 2.0) + + u0s = realpart(A0) * albe * sign(b, f) + v0s = imagpart(A0) + + if (ind) then + Am = -(psi * (1 + 2 * albe + (1 + j) * (1 + albe) * eta) * Vt) + Am = Am / (albe * ((2 - 2 * j + 3 * (eta + psi) + (2 + 2 * j) * eta * psi))) + Ap = -(eta * (1 - 2 * albe + (1 - j) * (1 - albe)*psi) * Vt) + Ap = Ap / (albe * (2 + 2 * j + 3 * (eta + psi) + (2 - 2 * j) * eta * psi)) + else + Am = -(psi * (1 + 2 * albe + (1 + j) * (1 + albe) * eta) * Vt) + Am = Am / (albe * ((2 + 2 * j) * (1 + eta * psi) + 3 * psi + 3 * j * eta)) + Ap = -(eta * (1 - 2 * albe + (1 + j) * (1 - albe) * psi) * Vt) + Ap = Ap / (albe * ((2 + 2 * j) * (1 + eta * psi) + 3 * eta + 3 * j * psi)) + end if + + ! First asymmetric surface component + ums = realpart(Am * exp(-j * (lam(i) - thetaFm) * sign(b, f))) * albe + vms = imagpart(Am * exp(-j * (lam(i) - thetaFm) * sign(b, f))) * sign(b, f) + + ! Second asymmetric surface component + ups = realpart(Ap * exp(j * (lam(i) - thetaFm) * sign(b, f))) * albe + vps = realpart(Ap * exp(j * (lam(i) - thetaFm) * sign(b, f))) * sign(b, f) + + ! Total surface wind in (moving coordinate system) + us = u0s + ups + ums + vs = v0s + vps + vms + V(i) + + usf = us + Vt * cos(lam(i) - thetaFm) + vsf = vs - Vt * sin(lam(i) - thetaFm) + phi = atan2(usf, vsf) + + ! Surface winds, cartesian coordinates + Ux(i) = sqrt(usf ** 2. + vsf ** 2.) * sin(phi - lam(i)) + Uy(i) = sqrt(usf ** 2. + vsf ** 2.) * cos(phi - lam(i)) + + end do + +end subroutine fkerpert + + +subroutine fhollandvel(V, R, d2Vm, dVm, rMax, vMax, beta, dP, rho, f, n) + doubleprecision, intent(in) :: d2Vm, dVm, rMax, beta, dP, rho, f, vMax + integer, intent(in) :: n + doubleprecision, intent(in), dimension(n) :: R + doubleprecision, intent(inout), dimension(n) :: V + doubleprecision :: aa, bb, cc, delta, edelta + logical :: icore + + aa = ((d2Vm / 2. - (dVm - vMax / rMax) / rMax) / rMax) + bb = (d2Vm - 6 * aa * rMax) / 2. + cc = dVm - 3 * aa * rMax ** 2 - 2 * bb * rMax + + do i = 1, n + + delta = (rMax / R(i)) ** beta + edelta = exp(-delta) + + icore = R(i) <= rMax + if (icore) then + V(i) = (R(i) * (R(i) * (R(i) * aa + bb) + cc)) + else + V(i) = sqrt((dP * beta / rho) * delta * edelta + (R(i) * f / 2.) ** 2) - R(i) * abs(f) / 2. + end if + + V(i) = sign(V(i), f) + + end do + +end subroutine fhollandvel + + +subroutine fhollandvort(Z, R, d2Vm, dVm, rMax, vMax, beta, dP, rho, f, n) + doubleprecision, intent(in) :: d2Vm, dVm, rMax, beta, dP, rho, f, vMax + integer, intent(in) :: n + doubleprecision, intent(in), dimension(n) :: R + doubleprecision, intent(inout), dimension(n) :: Z + doubleprecision :: aa, bb, cc, delta, edelta + logical :: icore + + aa = (d2Vm / 2 - (dVm - vMax / rMax) / rMax) / rMax + bb = (d2Vm - 6 * aa * rMax) / 2. + cc = dVm - 3 * aa * rMax ** 2 - 2 * bb * rMax + + do i = 1, n + delta = (rMax / R(i)) ** beta + edelta = exp(-delta) + icore = (R(i) <= rMax) + if (icore) then + Z(i) = R(i) * (R(i) * 4 * aa + 3 * bb) + 2 * cc + else + Z(i) = abs(f) + & + (beta**2 * dP * (delta**2) * edelta / & + (2 * rho * R(i)) - beta**2 * dP * delta * edelta / & + (2 * rho * R(i)) + R(i) * f**2 / 4) / & + sqrt(beta * dP * delta * edelta / & + rho + (R(i) * f / 2)**2) + & + (sqrt(beta * dP * delta * edelta / & + rho + (R(i) * f / 2)**2)) / R(i) + end if + + Z(i) = sign(Z(i), f) + end do + + + + +end subroutine fhollandvort \ No newline at end of file diff --git a/wind/windmodels.py b/wind/windmodels.py index dc6f57a0..aea50cf5 100644 --- a/wind/windmodels.py +++ b/wind/windmodels.py @@ -39,10 +39,16 @@ from math import exp, sqrt import Utilities.metutils as metutils import logging +import warnings logging.getLogger('matplotlib').setLevel(logging.WARNING) log = logging.getLogger(__name__) log.addHandler(logging.NullHandler()) +try: + from . import fwind +except ImportError: + warnings.warn("Compiled wind models not found - defaulting to slower python wind models") + class WindSpeedModel(object): @@ -371,20 +377,30 @@ def velocity(self, R): d2Vm = self.secondDerivative() dVm = self.firstDerivative() - aa = ((d2Vm / 2. - (dVm - self.vMax / self.rMax) / - self.rMax) / self.rMax) - bb = (d2Vm - 6 * aa * self.rMax) / 2. - cc = dVm - 3 * aa * self.rMax ** 2 - 2 * bb * self.rMax - delta = (self.rMax / R) ** self.beta - edelta = np.exp(-delta) - V = (np.sqrt((self.dP * self.beta / self.rho) * - delta * edelta + (R * self.f / 2.) ** 2) - - R * np.abs(self.f) / 2.) + try: + from .fwind import fhollandvel + V = np.zeros_like(R) + fhollandvel( + V.ravel(), R.ravel(), d2Vm, dVm, self.rMax, + self.vMax, self.beta, self.dP, self.rho, self.f, V.size + ) + + except ImportError: + aa = ((d2Vm / 2. - (dVm - self.vMax / self.rMax) / + self.rMax) / self.rMax) + bb = (d2Vm - 6 * aa * self.rMax) / 2. + cc = dVm - 3 * aa * self.rMax ** 2 - 2 * bb * self.rMax + delta = (self.rMax / R) ** self.beta + edelta = np.exp(-delta) + + V = (np.sqrt((self.dP * self.beta / self.rho) * + delta * edelta + (R * self.f / 2.) ** 2) - + R * np.abs(self.f) / 2.) - icore = np.where(R <= self.rMax) - V[icore] = (R[icore] * (R[icore] * (R[icore] * aa + bb) + cc)) - V = np.sign(self.f) * V + icore = np.where(R <= self.rMax) + V[icore] = (R[icore] * (R[icore] * (R[icore] * aa + bb) + cc)) + V = np.sign(self.f) * V return V def vorticity(self, R): @@ -399,31 +415,42 @@ def vorticity(self, R): :rtype: :class:`numpy.ndarray` """ - - beta = self.beta - delta = (self.rMax / R) ** beta - edelta = np.exp(-delta) - - Z = np.abs(self.f) + \ - (beta**2 * self.dP * (delta**2) * edelta / - (2 * self.rho * R) - beta**2 * self.dP * delta * edelta / - (2 * self.rho * R) + R * self.f**2 / 4) / \ - np.sqrt(beta * self.dP * delta * edelta / - self.rho + (R * self.f / 2)**2) + \ - (np.sqrt(beta * self.dP * delta * edelta / - self.rho + (R * self.f / 2)**2)) / R - # Calculate first and second derivatives at R = Rmax: + d2Vm = self.secondDerivative() dVm = self.firstDerivative() - aa = ((d2Vm / 2 - (dVm - self.vMax / - self.rMax) / self.rMax) / self.rMax) - bb = (d2Vm - 6 * aa * self.rMax) / 2 - cc = dVm - 3 * aa * self.rMax ** 2 - 2 * bb * self.rMax - icore = np.where(R <= self.rMax) - Z[icore] = R[icore] * (R[icore] * 4 * aa + 3 * bb) + 2 * cc - Z = np.sign(self.f) * Z + try: + from .fwind import fhollandvort + Z = np.zeros_like(R) + fhollandvort( + Z.ravel(), R.ravel(), d2Vm, dVm, self.rMax, self.vMax, + self.beta, self.dP, self.rho, self.f, Z.size + ) + + except ImportError: + beta = self.beta + delta = (self.rMax / R) ** beta + edelta = np.exp(-delta) + + Z = np.abs(self.f) + \ + (beta**2 * self.dP * (delta**2) * edelta / + (2 * self.rho * R) - beta**2 * self.dP * delta * edelta / + (2 * self.rho * R) + R * self.f**2 / 4) / \ + np.sqrt(beta * self.dP * delta * edelta / + self.rho + (R * self.f / 2)**2) + \ + (np.sqrt(beta * self.dP * delta * edelta / + self.rho + (R * self.f / 2)**2)) / R + + aa = ((d2Vm / 2 - (dVm - self.vMax / + self.rMax) / self.rMax) / self.rMax) + bb = (d2Vm - 6 * aa * self.rMax) / 2 + cc = dVm - 3 * aa * self.rMax ** 2 - 2 * bb * self.rMax + + icore = np.where(R <= self.rMax) + Z[icore] = R[icore] * (R[icore] * 4 * aa + 3 * bb) + 2 * cc + Z = np.sign(self.f) * Z + return Z @@ -1054,6 +1081,19 @@ def field(self, R, lam, vFm, thetaFm, thetaMax=0.): K = 50. # Diffusivity Cd = 0.002 # Constant drag coefficient Vm = np.abs(V).max() + + try: + from .fwind import fkerpert + Ux, Vy = np.zeros_like(R), np.zeros_like(R) + n = Ux.size + fkerpert( + R.ravel(), lam.ravel(), V.ravel(), Z.ravel(), self.f, self.rMax, vFm, thetaFm, + Vm, Ux.ravel(), Vy.ravel(), n + ) + return Ux, Vy + except ImportError: + pass + if (vFm > 0) and (Vm/vFm < 5.): Umod = vFm * np.abs(1.25*(1. - (vFm/Vm))) else: From f2966451bb2f505f698ac749ad38b2628e82c502 Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Wed, 23 Mar 2022 10:05:07 +1100 Subject: [PATCH 53/80] parallel fortran code --- wind/fwind.f90 | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/wind/fwind.f90 b/wind/fwind.f90 index 115096d9..017ab007 100644 --- a/wind/fwind.f90 +++ b/wind/fwind.f90 @@ -1,4 +1,5 @@ subroutine fkerpert(R, lam, V, Z, f, rMax, vFm, thetaFm, Vm, Ux, Uy, n) + !$ use omp_lib integer, intent(in) :: n doubleprecision, intent(in) :: f, rMax, vFm, Vm @@ -15,6 +16,7 @@ subroutine fkerpert(R, lam, V, Z, f, rMax, vFm, thetaFm, Vm, Ux, Uy, n) Cd = 0.002 j = cmplx(0.0, 1.0) + !$OMP PARALLEL DO shared(Ux, Uy) do i = 1, n if ((vFm > 0) .and. (Vm/vFm < 5.)) then @@ -78,11 +80,13 @@ subroutine fkerpert(R, lam, V, Z, f, rMax, vFm, thetaFm, Vm, Ux, Uy, n) Uy(i) = sqrt(usf ** 2. + vsf ** 2.) * cos(phi - lam(i)) end do + !$OMP END PARALLEL DO end subroutine fkerpert subroutine fhollandvel(V, R, d2Vm, dVm, rMax, vMax, beta, dP, rho, f, n) + !$ use omp_lib doubleprecision, intent(in) :: d2Vm, dVm, rMax, beta, dP, rho, f, vMax integer, intent(in) :: n doubleprecision, intent(in), dimension(n) :: R @@ -94,6 +98,7 @@ subroutine fhollandvel(V, R, d2Vm, dVm, rMax, vMax, beta, dP, rho, f, n) bb = (d2Vm - 6 * aa * rMax) / 2. cc = dVm - 3 * aa * rMax ** 2 - 2 * bb * rMax + !$OMP PARALLEL DO shared(V) do i = 1, n delta = (rMax / R(i)) ** beta @@ -109,11 +114,13 @@ subroutine fhollandvel(V, R, d2Vm, dVm, rMax, vMax, beta, dP, rho, f, n) V(i) = sign(V(i), f) end do + !$OMP END PARALLEL DO end subroutine fhollandvel subroutine fhollandvort(Z, R, d2Vm, dVm, rMax, vMax, beta, dP, rho, f, n) + !$ use omp_lib doubleprecision, intent(in) :: d2Vm, dVm, rMax, beta, dP, rho, f, vMax integer, intent(in) :: n doubleprecision, intent(in), dimension(n) :: R @@ -125,6 +132,7 @@ subroutine fhollandvort(Z, R, d2Vm, dVm, rMax, vMax, beta, dP, rho, f, n) bb = (d2Vm - 6 * aa * rMax) / 2. cc = dVm - 3 * aa * rMax ** 2 - 2 * bb * rMax + !$OMP PARALLEL DO shared(Z) do i = 1, n delta = (rMax / R(i)) ** beta edelta = exp(-delta) @@ -144,8 +152,6 @@ subroutine fhollandvort(Z, R, d2Vm, dVm, rMax, vMax, beta, dP, rho, f, n) Z(i) = sign(Z(i), f) end do - - - + !$OMP END PARALLEL DO end subroutine fhollandvort \ No newline at end of file From 04a3358efeb15c5e7d2d1663959a5ceb8c773f1f Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Wed, 23 Mar 2022 12:33:16 +1100 Subject: [PATCH 54/80] parallel fortran code --- Utilities/maputils.f90 | 54 ++++++++++++++++++++++++++++++++++++++++++ Utilities/maputils.py | 22 ++++++++++++++--- 2 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 Utilities/maputils.f90 diff --git a/Utilities/maputils.f90 b/Utilities/maputils.f90 new file mode 100644 index 00000000..d2ecb3ca --- /dev/null +++ b/Utilities/maputils.f90 @@ -0,0 +1,54 @@ +subroutine beardist(cLon, cLat, lonArray, latArray, bearing, dist, nlon, nlat) + !$ use omp_lib + + integer, intent(in) :: nlon, nlat + doubleprecision, intent(in) :: lonArray(nlon), latArray(nlat) + doubleprecision, intent(inout), dimension(nlat, nlon) :: bearing, dist + + doubleprecision :: toRads, cLon_, cLat_, dlon, dlat, lon(nlon), lat(nlat), radius + doubleprecision :: dLon_sin(nlon), dLon_cos(nlon), lat_sin(nlat), lat_cos(nlat) + doubleprecision :: dLat_sin(nlat), dhalfLon_sin(nlon), a, c, pi + pi = 4.d0*datan(1.d0) + toRads = 0.017453292519943295 + radius = 6367.0 + + cLon_ = cLon; + cLat_ = cLat; + + cLon_ = cLon_ * toRads + cLat_ = cLat_ * toRads + + cLat_cos = cos(cLat_) + cLat_sin = sin(cLat_) + + do i = 1, nlon + lon(i) = lonArray(i) * toRads + dLon = lon(i) - cLon_ + dLon_sin(i) = sin(dLon) + dLon_cos(i) = cos(dLon) + dhalfLon_sin(i) = sin(0.5 * dLon) + end do + + do i = 1, nlat + lat(i) = latArray(i) * toRads + lat_sin(i) = sin(lat(i)) + lat_cos(i) = cos(lat(i)) + dLat_sin(i) = sin(0.5 * (lat(i) - cLat_)) + end do + + !$OMP PARALLEL DO shared(bearing, dist) + do j = 1, nlat + do i = 1, nlon + + alpha = dLon_sin(i) * lat_cos(j); + beta = (cLat_cos * lat_sin(j)) - (cLat_sin * lat_cos(j) * dLon_cos(i)); + bearing(j, i) = 0.5 * pi - atan2(alpha, beta); + + a = dLat_sin(j) * dLat_sin(j) + cLat_cos * lat_cos(j) * dhalfLon_sin(i) * dhalfLon_sin(i); + c = 2.0 * atan2(sqrt(abs(a)), sqrt(1.0 - a)); + dist(j, i) = max(radius*c, 1e-30); + end do + end do + !$OMP END PARALLEL DO + +end subroutine beardist \ No newline at end of file diff --git a/Utilities/maputils.py b/Utilities/maputils.py index 3fff81fe..1fddf8c5 100644 --- a/Utilities/maputils.py +++ b/Utilities/maputils.py @@ -17,6 +17,11 @@ import numpy as np import math from . import metutils +import warnings +try: + from . import fmaputils +except ImportError: + warnings.warn("Compiled maputils not found - defaulting to slower python wind models") # C weave code disabled for now. The code speeds up the windfield interface module by ~6% but @@ -508,9 +513,20 @@ def makeGrid(cLon, cLat, margin=2, resolution=0.01, minLon=None, maxLon=None, xGrid = np.array(np.arange(minLon_, maxLon_, gridSize), dtype=int) yGrid = np.array(np.arange(minLat_, maxLat_, gridSize), dtype=int) - R = gridLatLonDist(cLon, cLat, xGrid / 1000., yGrid / 1000.) - np.putmask(R, R==0, 1e-30) - theta = np.pi/2. - gridLatLonBear(cLon, cLat, xGrid / 1000., yGrid / 1000.) + try: + from .fmaputils import beardist + lonArray = xGrid / 1000. + latArray = yGrid / 1000. + R = np.zeros((len(latArray), len(lonArray)), order='F') + theta = np.zeros((len(latArray), len(lonArray)), order='F') + + beardist(cLon, cLat, lonArray, latArray, theta, R) + R = np.ascontiguousarray(R) + theta = np.ascontiguousarray(theta) + except ImportError: + R = gridLatLonDist(cLon, cLat, xGrid / 1000., yGrid / 1000.) + theta = np.pi/2. - gridLatLonBear(cLon, cLat, xGrid / 1000., yGrid / 1000.) + np.putmask(R, R == 0, 1e-30) return R, theta From e88c42eef62a10ff3d0842a9d8b21591aad85bf5 Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Wed, 23 Mar 2022 13:55:21 +1100 Subject: [PATCH 55/80] parallel fortran code --- PressureInterface/pressureProfile.f90 | 14 ++++++++++++++ PressureInterface/pressureProfile.py | 9 ++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 PressureInterface/pressureProfile.f90 diff --git a/PressureInterface/pressureProfile.f90 b/PressureInterface/pressureProfile.f90 new file mode 100644 index 00000000..429b53ad --- /dev/null +++ b/PressureInterface/pressureProfile.f90 @@ -0,0 +1,14 @@ +subroutine fhollandpressure(P, R, rMax, pc, dP, beta, n) + !$ use omp_lib + integer, intent(in) :: n + doubleprecision, intent(in), dimension(n) :: R + doubleprecision, intent(inout), dimension(n) :: P + doubleprecision, intent(in) :: rMax, beta, pc, dP + + !$OMP PARALLEL DO shared(P) + do i = 1, n + P(i) = pCentre + dP * exp(-(rMax / R) ** beta) + end do + !$OMP END PARALLEL DO + +end subroutine fhollandpressure \ No newline at end of file diff --git a/PressureInterface/pressureProfile.py b/PressureInterface/pressureProfile.py index 2e7426b5..cc64a80d 100644 --- a/PressureInterface/pressureProfile.py +++ b/PressureInterface/pressureProfile.py @@ -151,7 +151,14 @@ def holland(self, beta=None): beta = self.beta t0 = time.time() P = numpy.zeros(self.R.shape) - P = self.pCentre + self.dP*numpy.exp(-(self.rMax/self.R)**beta) + + try: + from .fpressureProfile import fhollandpressure + fhollandpressure( + P.ravel(), self.R.ravel(), self.rMax, self.pCentre, self.dP, beta + ) + except ImportError: + P = self.pCentre + self.dP*numpy.exp(-(self.rMax/self.R)**beta) self.logger.debug("Timing for holland wind profile calculation: %.3f" % (time.time()-t0)) return P From 9afd936f402f1713e54c3413f577fae4344790a3 Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Wed, 23 Mar 2022 14:13:01 +1100 Subject: [PATCH 56/80] parallel fortran code --- PressureInterface/pressureProfile.f90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PressureInterface/pressureProfile.f90 b/PressureInterface/pressureProfile.f90 index 429b53ad..bd50ca79 100644 --- a/PressureInterface/pressureProfile.f90 +++ b/PressureInterface/pressureProfile.f90 @@ -7,7 +7,7 @@ subroutine fhollandpressure(P, R, rMax, pc, dP, beta, n) !$OMP PARALLEL DO shared(P) do i = 1, n - P(i) = pCentre + dP * exp(-(rMax / R) ** beta) + P(i) = pCentre + dP * exp(-(rMax / R(i)) ** beta) end do !$OMP END PARALLEL DO From d0b46a40c92ad42b0234980e26b0b87d016d626f Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Wed, 23 Mar 2022 14:21:18 +1100 Subject: [PATCH 57/80] slight optimizations --- PressureInterface/pressureProfile.py | 2 +- wind/windmodels.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/PressureInterface/pressureProfile.py b/PressureInterface/pressureProfile.py index cc64a80d..4383f118 100644 --- a/PressureInterface/pressureProfile.py +++ b/PressureInterface/pressureProfile.py @@ -150,10 +150,10 @@ def holland(self, beta=None): if beta == None: beta = self.beta t0 = time.time() - P = numpy.zeros(self.R.shape) try: from .fpressureProfile import fhollandpressure + P = numpy.empty(self.R.shape) fhollandpressure( P.ravel(), self.R.ravel(), self.rMax, self.pCentre, self.dP, beta ) diff --git a/wind/windmodels.py b/wind/windmodels.py index aea50cf5..3feeb4b1 100644 --- a/wind/windmodels.py +++ b/wind/windmodels.py @@ -380,7 +380,7 @@ def velocity(self, R): try: from .fwind import fhollandvel - V = np.zeros_like(R) + V = np.empty_like(R) fhollandvel( V.ravel(), R.ravel(), d2Vm, dVm, self.rMax, self.vMax, self.beta, self.dP, self.rho, self.f, V.size @@ -422,7 +422,7 @@ def vorticity(self, R): try: from .fwind import fhollandvort - Z = np.zeros_like(R) + Z = np.empty_like(R) fhollandvort( Z.ravel(), R.ravel(), d2Vm, dVm, self.rMax, self.vMax, self.beta, self.dP, self.rho, self.f, Z.size @@ -1080,11 +1080,11 @@ def field(self, R, lam, vFm, thetaFm, thetaMax=0.): Z = self.vorticity(R) K = 50. # Diffusivity Cd = 0.002 # Constant drag coefficient - Vm = np.abs(V).max() + Vm = self.profile.vMax try: from .fwind import fkerpert - Ux, Vy = np.zeros_like(R), np.zeros_like(R) + Ux, Vy = np.empty_like(R), np.empty_like(R) n = Ux.size fkerpert( R.ravel(), lam.ravel(), V.ravel(), Z.ravel(), self.f, self.rMax, vFm, thetaFm, From 4ed99316d67bbd929c3a015a067d4cba37101c3b Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Wed, 23 Mar 2022 15:06:37 +1100 Subject: [PATCH 58/80] put velocity and vorticity equations in same loop --- wind/fwind.f90 | 88 ++++++++++++++++++++++++++++++++++++++++------ wind/windmodels.py | 6 ++-- 2 files changed, 80 insertions(+), 14 deletions(-) diff --git a/wind/fwind.f90 b/wind/fwind.f90 index 017ab007..450a7222 100644 --- a/wind/fwind.f90 +++ b/wind/fwind.f90 @@ -1,15 +1,17 @@ -subroutine fkerpert(R, lam, V, Z, f, rMax, vFm, thetaFm, Vm, Ux, Uy, n) +subroutine fkerpert(R, lam, f, rMax, vFm, thetaFm, Vm, Ux, Uy, n) !$ use omp_lib integer, intent(in) :: n doubleprecision, intent(in) :: f, rMax, vFm, Vm - doubleprecision, dimension(n), intent(in) :: R, lam, V, Z + doubleprecision, dimension(n), intent(in) :: R, lam doubleprecision, dimension(n), intent(inout) :: Ux, Uy doubleprecision :: Umod, Vt, al, be, gam , K, Cd, u0s, v0s, chi, eta, psi, albe, b doubleprecision :: ups, ums, vps, vms, usf, vsf, phi, us, vs complex(8) :: A0, j, Am, Ap logical :: ind + doubleprecision :: aa, bb, cc, delta, edelta, V, Z + logical :: icore b = 1.0 K = 50.0 @@ -19,6 +21,30 @@ subroutine fkerpert(R, lam, V, Z, f, rMax, vFm, thetaFm, Vm, Ux, Uy, n) !$OMP PARALLEL DO shared(Ux, Uy) do i = 1, n + aa = ((d2Vm / 2. - (dVm - vMax / rMax) / rMax) / rMax) + bb = (d2Vm - 6 * aa * rMax) / 2. + cc = dVm - 3 * aa * rMax ** 2 - 2 * bb * rMax + + delta = (rMax / R(i)) ** beta + edelta = exp(-delta) + icore = (R(i) <= rMax) + if (icore) then + Z = R(i) * (R(i) * 4 * aa + 3 * bb) + 2 * cc + V = (R(i) * (R(i) * (R(i) * aa + bb) + cc)) + else + V = sqrt((dP * beta / rho) * delta * edelta + (R(i) * f / 2.) ** 2) - R(i) * abs(f) / 2. + Z = abs(f) + & + (beta**2 * dP * (delta**2) * edelta / & + (2 * rho * R(i)) - beta**2 * dP * delta * edelta / & + (2 * rho * R(i)) + R(i) * f**2 / 4) / & + sqrt(beta * dP * delta * edelta / & + rho + (R(i) * f / 2)**2) + & + (sqrt(beta * dP * delta * edelta / & + rho + (R(i) * f / 2)**2)) / R(i) + end if + V = sign(V, f) + Z = sign(Z, f) + if ((vFm > 0) .and. (Vm/vFm < 5.)) then Umod = vFm * abs(1.25*(1. - (vFm/Vm))) else @@ -31,18 +57,18 @@ subroutine fkerpert(R, lam, V, Z, f, rMax, vFm, thetaFm, Vm, Ux, Uy, n) Vt = Umod end if - al = ((2. * V(i) / R(i)) + f) / (2. * K) - be = (f + Z(i)) / (2. * K) - gam = (V(i) / (2. * K * R(i))) + al = ((2. * V / R(i)) + f) / (2. * K) + be = (f + Z) / (2. * K) + gam = (V / (2. * K * R(i))) albe = sqrt(al / be) ind = abs(gam) > sqrt(al * be) - chi = abs((Cd / K) * V(i) / sqrt(sqrt(al * be))) - eta = abs((Cd / K) * V(i) / sqrt(sqrt(al * be) + abs(gam))) - psi = abs((Cd / K) * V(i) / sqrt(abs(sqrt(al * be) - abs(gam)))) + chi = abs((Cd / K) * V / sqrt(sqrt(al * be))) + eta = abs((Cd / K) * V / sqrt(sqrt(al * be) + abs(gam))) + psi = abs((Cd / K) * V / sqrt(abs(sqrt(al * be) - abs(gam)))) - A0 = -(chi * (1.0 + j * (1.0 + chi)) * V(i)) / (2.0 * chi**2 + 3.0 * chi + 2.0) + A0 = -(chi * (1.0 + j * (1.0 + chi)) * V) / (2.0 * chi**2 + 3.0 * chi + 2.0) u0s = realpart(A0) * albe * sign(b, f) v0s = imagpart(A0) @@ -69,7 +95,7 @@ subroutine fkerpert(R, lam, V, Z, f, rMax, vFm, thetaFm, Vm, Ux, Uy, n) ! Total surface wind in (moving coordinate system) us = u0s + ups + ums - vs = v0s + vps + vms + V(i) + vs = v0s + vps + vms + V usf = us + Vt * cos(lam(i) - thetaFm) vsf = vs - Vt * sin(lam(i) - thetaFm) @@ -154,4 +180,44 @@ subroutine fhollandvort(Z, R, d2Vm, dVm, rMax, vMax, beta, dP, rho, f, n) end do !$OMP END PARALLEL DO -end subroutine fhollandvort \ No newline at end of file +end subroutine fhollandvort + + +subroutine fcomb(V, Z, R, d2Vm, dVm, rMax, vMax, beta, dP, rho, f, n) + !$ use omp_lib + doubleprecision, intent(in) :: d2Vm, dVm, rMax, beta, dP, rho, f, vMax + integer, intent(in) :: n + doubleprecision, intent(in), dimension(n) :: R + doubleprecision, intent(inout), dimension(n) :: V, Z + doubleprecision :: aa, bb, cc, delta, edelta + logical :: icore + + aa = (d2Vm / 2 - (dVm - vMax / rMax) / rMax) / rMax + bb = (d2Vm - 6 * aa * rMax) / 2. + cc = dVm - 3 * aa * rMax ** 2 - 2 * bb * rMax + + !$OMP PARALLEL DO shared(Z) + do i = 1, n + delta = (rMax / R(i)) ** beta + edelta = exp(-delta) + icore = (R(i) <= rMax) + if (icore) then + Z(i) = R(i) * (R(i) * 4 * aa + 3 * bb) + 2 * cc + V(i) = (R(i) * (R(i) * (R(i) * aa + bb) + cc)) + else + V(i) = sqrt((dP * beta / rho) * delta * edelta + (R(i) * f / 2.) ** 2) - R(i) * abs(f) / 2. + Z(i) = abs(f) + & + (beta**2 * dP * (delta**2) * edelta / & + (2 * rho * R(i)) - beta**2 * dP * delta * edelta / & + (2 * rho * R(i)) + R(i) * f**2 / 4) / & + sqrt(beta * dP * delta * edelta / & + rho + (R(i) * f / 2)**2) + & + (sqrt(beta * dP * delta * edelta / & + rho + (R(i) * f / 2)**2)) / R(i) + end if + + Z(i) = sign(Z(i), f) + end do + !$OMP END PARALLEL DO + +end subroutine fcomb \ No newline at end of file diff --git a/wind/windmodels.py b/wind/windmodels.py index 3feeb4b1..e97525de 100644 --- a/wind/windmodels.py +++ b/wind/windmodels.py @@ -1076,8 +1076,6 @@ def field(self, R, lam, vFm, thetaFm, thetaMax=0.): """ - V = self.velocity(R) - Z = self.vorticity(R) K = 50. # Diffusivity Cd = 0.002 # Constant drag coefficient Vm = self.profile.vMax @@ -1087,13 +1085,15 @@ def field(self, R, lam, vFm, thetaFm, thetaMax=0.): Ux, Vy = np.empty_like(R), np.empty_like(R) n = Ux.size fkerpert( - R.ravel(), lam.ravel(), V.ravel(), Z.ravel(), self.f, self.rMax, vFm, thetaFm, + R.ravel(), lam.ravel(), self.f, self.rMax, vFm, thetaFm, Vm, Ux.ravel(), Vy.ravel(), n ) return Ux, Vy except ImportError: pass + V = self.velocity(R) + Z = self.vorticity(R) if (vFm > 0) and (Vm/vFm < 5.): Umod = vFm * np.abs(1.25*(1. - (vFm/Vm))) else: From 5be5b5cdf6dc309b5a96f86c27a2430826f44170 Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Wed, 23 Mar 2022 16:49:22 +1100 Subject: [PATCH 59/80] mpi parallelism for one trackfile --- wind/__init__.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/wind/__init__.py b/wind/__init__.py index b6d24b33..f7f27e5e 100644 --- a/wind/__init__.py +++ b/wind/__init__.py @@ -780,11 +780,19 @@ def loadTracksFromFiles(trackfiles, gridLimit, margin): :param trackfiles: list of track filenames. The filenames must include the path to the file. """ - for f in balanced(trackfiles): + if len(trackfiles) > 1: + for f in balanced(trackfiles): + msg = f'Calculating wind fields for tracks in {f}' + log.info(msg) + tracks = filterTracks(loadTracks(f), gridLimit, margin) + for track in tracks: + yield track + else: + f = trackfiles[0] msg = f'Calculating wind fields for tracks in {f}' log.info(msg) tracks = filterTracks(loadTracks(f), gridLimit, margin) - for track in tracks: + for track in balanced(tracks): yield track From df317ca8f4d8db0bd6f9da7161483c23e6511336 Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Thu, 24 Mar 2022 09:30:54 +1100 Subject: [PATCH 60/80] handle bad wind fields --- wind/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/wind/__init__.py b/wind/__init__.py index f7f27e5e..b6a59bc0 100644 --- a/wind/__init__.py +++ b/wind/__init__.py @@ -201,7 +201,10 @@ def localWindField(self, i): values = [getattr(self, p) for p in params if hasattr(self, p)] windfield = cls(profile, *values) - Ux, Vy = windfield.field(R * 1000, theta, vFm, thetaFm, thetaMax) + if cP < eP: + Ux, Vy = windfield.field(R * 1000, theta, vFm, thetaFm, thetaMax) + else: + Ux, Vy = np.zeros_like(R), np.zeros_like(R) return (Ux, Vy, P) From 3e8c5ca187561e445673ded9e6d6a1f224f66290 Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Thu, 24 Mar 2022 14:52:19 +1100 Subject: [PATCH 61/80] tweaks --- wind/fwind.f90 | 73 +++++++++++----------------------------------- wind/windmodels.py | 8 +++-- 2 files changed, 23 insertions(+), 58 deletions(-) diff --git a/wind/fwind.f90 b/wind/fwind.f90 index 450a7222..0eeec059 100644 --- a/wind/fwind.f90 +++ b/wind/fwind.f90 @@ -1,8 +1,8 @@ -subroutine fkerpert(R, lam, f, rMax, vFm, thetaFm, Vm, Ux, Uy, n) +subroutine fkerpert(R, lam, f, rMax, Vm, thetaFm, vFm, d2Vm, dVm, dP, beta, rho, Ux, Uy, n) !$ use omp_lib integer, intent(in) :: n - doubleprecision, intent(in) :: f, rMax, vFm, Vm + doubleprecision, intent(in) :: f, rMax, Vm, thetaFm, vFm, d2Vm, dVm, dP, beta, rho doubleprecision, dimension(n), intent(in) :: R, lam doubleprecision, dimension(n), intent(inout) :: Ux, Uy @@ -16,15 +16,22 @@ subroutine fkerpert(R, lam, f, rMax, vFm, thetaFm, Vm, Ux, Uy, n) b = 1.0 K = 50.0 Cd = 0.002 - j = cmplx(0.0, 1.0) + j = cmplx(0.0, 1.0, 8) + + aa = (d2Vm / 2 - (dVm - Vm / rMax) / rMax) / rMax + bb = (d2Vm - 6 * aa * rMax) / 2. + cc = dVm - 3 * aa * rMax ** 2 - 2 * bb * rMax + + + if ((vFm > 0) .and. (Vm/vFm < 5.)) then + Umod = vFm * abs(1.25*(1. - (vFm/Vm))) + else + Umod = vFm + end if !$OMP PARALLEL DO shared(Ux, Uy) do i = 1, n - aa = ((d2Vm / 2. - (dVm - vMax / rMax) / rMax) / rMax) - bb = (d2Vm - 6 * aa * rMax) / 2. - cc = dVm - 3 * aa * rMax ** 2 - 2 * bb * rMax - delta = (rMax / R(i)) ** beta edelta = exp(-delta) icore = (R(i) <= rMax) @@ -45,12 +52,6 @@ subroutine fkerpert(R, lam, f, rMax, vFm, thetaFm, Vm, Ux, Uy, n) V = sign(V, f) Z = sign(Z, f) - if ((vFm > 0) .and. (Vm/vFm < 5.)) then - Umod = vFm * abs(1.25*(1. - (vFm/Vm))) - else - Umod = vFm - end if - if (R(i) > 2 * rMax) then Vt = Umod * exp(-((R(i) / (2.*rMax)) - 1.) ** 2.) else @@ -76,12 +77,12 @@ subroutine fkerpert(R, lam, f, rMax, vFm, thetaFm, Vm, Ux, Uy, n) if (ind) then Am = -(psi * (1 + 2 * albe + (1 + j) * (1 + albe) * eta) * Vt) Am = Am / (albe * ((2 - 2 * j + 3 * (eta + psi) + (2 + 2 * j) * eta * psi))) - Ap = -(eta * (1 - 2 * albe + (1 - j) * (1 - albe)*psi) * Vt) + Ap = -(eta * (1 - 2 * albe + (1 - j) * (1 - albe) * psi) * Vt) Ap = Ap / (albe * (2 + 2 * j + 3 * (eta + psi) + (2 - 2 * j) * eta * psi)) else Am = -(psi * (1 + 2 * albe + (1 + j) * (1 + albe) * eta) * Vt) Am = Am / (albe * ((2 + 2 * j) * (1 + eta * psi) + 3 * psi + 3 * j * eta)) - Ap = -(eta * (1 - 2 * albe + (1 + j) * (1 - albe) * psi) * Vt) + Ap = -(eta * (1.0 - 2.0 * albe + (1.0 + j) * (1.0 - albe) * psi) * Vt) Ap = Ap / (albe * ((2 + 2 * j) * (1 + eta * psi) + 3 * eta + 3 * j * psi)) end if @@ -91,7 +92,7 @@ subroutine fkerpert(R, lam, f, rMax, vFm, thetaFm, Vm, Ux, Uy, n) ! Second asymmetric surface component ups = realpart(Ap * exp(j * (lam(i) - thetaFm) * sign(b, f))) * albe - vps = realpart(Ap * exp(j * (lam(i) - thetaFm) * sign(b, f))) * sign(b, f) + vps = imagpart(Ap * exp(j * (lam(i) - thetaFm) * sign(b, f))) * sign(b, f) ! Total surface wind in (moving coordinate system) us = u0s + ups + ums @@ -181,43 +182,3 @@ subroutine fhollandvort(Z, R, d2Vm, dVm, rMax, vMax, beta, dP, rho, f, n) !$OMP END PARALLEL DO end subroutine fhollandvort - - -subroutine fcomb(V, Z, R, d2Vm, dVm, rMax, vMax, beta, dP, rho, f, n) - !$ use omp_lib - doubleprecision, intent(in) :: d2Vm, dVm, rMax, beta, dP, rho, f, vMax - integer, intent(in) :: n - doubleprecision, intent(in), dimension(n) :: R - doubleprecision, intent(inout), dimension(n) :: V, Z - doubleprecision :: aa, bb, cc, delta, edelta - logical :: icore - - aa = (d2Vm / 2 - (dVm - vMax / rMax) / rMax) / rMax - bb = (d2Vm - 6 * aa * rMax) / 2. - cc = dVm - 3 * aa * rMax ** 2 - 2 * bb * rMax - - !$OMP PARALLEL DO shared(Z) - do i = 1, n - delta = (rMax / R(i)) ** beta - edelta = exp(-delta) - icore = (R(i) <= rMax) - if (icore) then - Z(i) = R(i) * (R(i) * 4 * aa + 3 * bb) + 2 * cc - V(i) = (R(i) * (R(i) * (R(i) * aa + bb) + cc)) - else - V(i) = sqrt((dP * beta / rho) * delta * edelta + (R(i) * f / 2.) ** 2) - R(i) * abs(f) / 2. - Z(i) = abs(f) + & - (beta**2 * dP * (delta**2) * edelta / & - (2 * rho * R(i)) - beta**2 * dP * delta * edelta / & - (2 * rho * R(i)) + R(i) * f**2 / 4) / & - sqrt(beta * dP * delta * edelta / & - rho + (R(i) * f / 2)**2) + & - (sqrt(beta * dP * delta * edelta / & - rho + (R(i) * f / 2)**2)) / R(i) - end if - - Z(i) = sign(Z(i), f) - end do - !$OMP END PARALLEL DO - -end subroutine fcomb \ No newline at end of file diff --git a/wind/windmodels.py b/wind/windmodels.py index e97525de..1423aa36 100644 --- a/wind/windmodels.py +++ b/wind/windmodels.py @@ -1082,11 +1082,15 @@ def field(self, R, lam, vFm, thetaFm, thetaMax=0.): try: from .fwind import fkerpert + V = self.velocity(R) + Z = self.vorticity(R) + d2Vm, dVm = self.profile.secondDerivative(), self.profile.firstDerivative() Ux, Vy = np.empty_like(R), np.empty_like(R) n = Ux.size fkerpert( - R.ravel(), lam.ravel(), self.f, self.rMax, vFm, thetaFm, - Vm, Ux.ravel(), Vy.ravel(), n + R.ravel(), lam.ravel(), self.f, self.rMax, Vm, thetaFm, + vFm, d2Vm, dVm, self.profile.dP, self.profile.beta, self.profile.rho, + Ux.ravel(), Vy.ravel(), n ) return Ux, Vy except ImportError: From 49f23acd7c33485bb9c25ff747d90d04863aff68 Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Fri, 25 Mar 2022 10:53:44 +1100 Subject: [PATCH 62/80] update kepert wind profile using python code with updated vmax --- tests/test_data/windFieldTestData.pkl | Bin 467732 -> 467732 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/test_data/windFieldTestData.pkl b/tests/test_data/windFieldTestData.pkl index cd33c9159e54851fb6f394759cd80e263c105282..28fd545fd83307bba846aadec236cc1e05e3770c 100644 GIT binary patch delta 105842 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 YiN2E*+vBpBftUq|S+~b!v2EJ{00Pv;B>(^b delta 105842 zcmW)ocOX}PAH}Uip(Gh0du3;okuMS%p%RHyR+Q?86wytI3Q3f`M@B{w85P+Sl8g`` znISVO&$-WE=lkp7-uF4@bw77~JrQ+05p_H_i>PUe3A)pZ$Cx(iQ`)~>IykUFpCa5e zAzVnOPl?|1jp;6}J|*!cJ?|+ReM(51lcy53KE)ADb z^(d9wCo)q0=uxyzKP|nrrbqe4AH3aRRgW?jmpXOmw;n}ePRm<!w~(4&|) zr#IG&>rpH+`CqM%=~14f@wRJ2yS~M^KlnlDQO-1(Ee(z8QMjr96;PmiZlx?e{jNtj zBGkst1-1D&YY{P`NBR6WhKd{dENj*%dsvU6X2I;P3uTzPlRP=3M=6u}`!5c~lsNgD{8xQAmd7KFO6}#Grz! z`YT@0?~KbP^UxPvU4tGl^lJH*)jwz-t?vaQ6pojjQ|iQi=uuu826ELy%l~`7!xa%p z`#b-~h3IBSruv(q2cACP=8s5!?_+AvLd1t%F215e652hIwCF-EU|=4}EB7zMfloney%!u6D2FE9 zh0|r4GPig*j~n$#c!eY!-HPvgK{EEA{tKdodiI#HDI&QH-szhckfb0{#-oypXdn4& zMlK=pYx_K?4ne=&_|TGuDl#|Uw%P=Zh!tA)LRDwOnt#osyPpR3kFP;*i_h+2fXWH} zXih=Ly1q}F^r4sP{vKEb3(DDbRwd9INzVjtLJObLWFLd}-LF_sgc5Gf{(t%Lf!llP zB`1{OXgNI(wEdiEn{z0=CXy`s8sn5_O5^|p8QE{aF&bTvl z_~1VEYG~K9UVc#wX0gdc?-4XO{({X$7!amP*>VS3OYvvi`~%1Tk6MeRC^(Fz1pCRr zxzQmhi)Lu^eTwiUBy&4aG?EKZv8?vmHbJ>;H01mcVP1e*g$5#h)x4o>9Xft-_fQ=o zuM8}jd4vkqO!g$%p%Rv7Erokgk!8ifX;xG=qPD;~1I=`(9&i1HwX9Hv6poYz5$fY+qXx>0ZOVb9xA>BsPtdB zWOEuzW|a4rg9kz9b*~GGvY>PBX1mKy!0BXGqOcio`X8w2{)fS8)}G`pVNm1V><#l6 zoaj^Bw*V+_9GgvDg|m{2y=nB&?fOIUNRQ$fuQbr#dny?U|#0}JTkad&ohD4X7s)>B>gg>vRNx1tQdQ7Zpya&qgNy42O z8*JFHGHeK~6i8c)fM$f%T8}~bZF(AYF-XnX4HM6x$tItjcrozap`VjapzZ`w&dUWy z#!(y6Qlac0?}{kHY3u=yLv7H}xUVJuAqizBDQ<2gBX`wtXCw5H<=t~PklfO?@`8Pc z(#ii``YLp(cH>$lB7VR;=M;>{|2?Znw?+le#d0poqmr*??*liXD*iL;&wfB9iN@)) z26Xs7pIRjsowmQQlzxnk9kU9L-NZtq_uJ0rBD%MvxK?jx(x*&ERh$|DlzHOQ*+qcT z%U$^09YCor@ukv&JdJnJ)JXzP!wu^5{{ZFWx!)H$0Hx2s3#%MJDV@6Q$8A6+D(t5>S>?a?C~| zLT9$d)Xm`dSNJAJH-X5)?U~rvq2KpJDCr~02fyvV+(yJnz27f3Aad6aCr<5vQp!iS zUPL8=AD>LLq9XbNO9OSNP6O?|LR5J5nB$5PRMPwGP&GPc{kiL|4K$M5LVy<9AYay7 z1QxhH5MSIzas1zIXI0j~KP(vxt+7FmzIK#Z20Zn5_SKF;<-%-phoIM{^BX6i;Y}yz z$FNbFvA1c(m(UtvDP+KBA2$54TyyxljAyFlJtZsS`Tql{e-A=Pl8?%sY|DE6v

)A5E$I<#O=MJL&Z%NbH9MOxcejJoQ6^%oF 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]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 From fd1e160f8888e9e994fad5f3e484c6c8a8274767 Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Fri, 25 Mar 2022 11:51:02 +1100 Subject: [PATCH 63/80] update build --- installer/setup.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/installer/setup.py b/installer/setup.py index 423b9430..bcbe0688 100644 --- a/installer/setup.py +++ b/installer/setup.py @@ -9,7 +9,8 @@ import matplotlib import numpy import sys -from setuptools import setup, Extension +# from setuptools import Extension +from numpy.distutils.core import setup, Extension from distutils.sysconfig import get_python_lib from os.path import join as pjoin from glob import glob @@ -53,7 +54,19 @@ Extension('Utilities._akima', sources=[pjoin('Utilities', 'akima.c')], include_dirs=[pjoin(numpy.get_include(), 'numpy')], - extra_compile_args=[]) + extra_compile_args=[]), + Extension('wind.fwind', + sources=[pjoin('wind', 'fwind.f90')], + include_dirs=[pjoin(numpy.get_include(), 'numpy')], + extra_compile_args=['-g']), +Extension('Utilities.fmaputils', + sources=[pjoin('Utilities', 'maputils.f90')], + include_dirs=[pjoin(numpy.get_include(), 'numpy')], + extra_compile_args=['-g']), +Extension('PressureInterface.fpressureProfile', + sources=[pjoin('PressureInterface', 'pressureProfile.f90')], + include_dirs=[pjoin(numpy.get_include(), 'numpy')], + extra_compile_args=['-g']) ] basemapData = pjoin('mpl_toolkits', 'basemap', 'data') From 2622e108a164ef3e6f342b99d91c596c3662f53f Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Fri, 25 Mar 2022 13:56:51 +1100 Subject: [PATCH 64/80] formatting --- installer/setup.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/installer/setup.py b/installer/setup.py index bcbe0688..128cdf11 100644 --- a/installer/setup.py +++ b/installer/setup.py @@ -59,14 +59,14 @@ sources=[pjoin('wind', 'fwind.f90')], include_dirs=[pjoin(numpy.get_include(), 'numpy')], extra_compile_args=['-g']), -Extension('Utilities.fmaputils', - sources=[pjoin('Utilities', 'maputils.f90')], - include_dirs=[pjoin(numpy.get_include(), 'numpy')], - extra_compile_args=['-g']), -Extension('PressureInterface.fpressureProfile', - sources=[pjoin('PressureInterface', 'pressureProfile.f90')], - include_dirs=[pjoin(numpy.get_include(), 'numpy')], - extra_compile_args=['-g']) + Extension('Utilities.fmaputils', + sources=[pjoin('Utilities', 'maputils.f90')], + include_dirs=[pjoin(numpy.get_include(), 'numpy')], + extra_compile_args=['-g']), + Extension('PressureInterface.fpressureProfile', + sources=[pjoin('PressureInterface', 'pressureProfile.f90')], + include_dirs=[pjoin(numpy.get_include(), 'numpy')], + extra_compile_args=['-g']) ] basemapData = pjoin('mpl_toolkits', 'basemap', 'data') From 6bbe239f2440f60f17eaee43560f2a9f16278125 Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Fri, 25 Mar 2022 14:02:28 +1100 Subject: [PATCH 65/80] rename files and functions --- PressureInterface/pressureProfile.py | 4 ++-- Utilities/maputils.py | 2 +- installer/setup.py | 8 ++++---- wind/{fwind.f90 => windmodels.f90} | 0 wind/windmodels.py | 8 ++++---- 5 files changed, 11 insertions(+), 11 deletions(-) rename wind/{fwind.f90 => windmodels.f90} (100%) diff --git a/PressureInterface/pressureProfile.py b/PressureInterface/pressureProfile.py index 4383f118..0cd1864e 100644 --- a/PressureInterface/pressureProfile.py +++ b/PressureInterface/pressureProfile.py @@ -152,9 +152,9 @@ def holland(self, beta=None): t0 = time.time() try: - from .fpressureProfile import fhollandpressure + from ._pressureProfile import fhollandpressure P = numpy.empty(self.R.shape) - fhollandpressure( + hollandpressure( P.ravel(), self.R.ravel(), self.rMax, self.pCentre, self.dP, beta ) except ImportError: diff --git a/Utilities/maputils.py b/Utilities/maputils.py index 1fddf8c5..61a60e97 100644 --- a/Utilities/maputils.py +++ b/Utilities/maputils.py @@ -514,7 +514,7 @@ def makeGrid(cLon, cLat, margin=2, resolution=0.01, minLon=None, maxLon=None, yGrid = np.array(np.arange(minLat_, maxLat_, gridSize), dtype=int) try: - from .fmaputils import beardist + from ._maputils import beardist lonArray = xGrid / 1000. latArray = yGrid / 1000. R = np.zeros((len(latArray), len(lonArray)), order='F') diff --git a/installer/setup.py b/installer/setup.py index 128cdf11..c693527c 100644 --- a/installer/setup.py +++ b/installer/setup.py @@ -55,15 +55,15 @@ sources=[pjoin('Utilities', 'akima.c')], include_dirs=[pjoin(numpy.get_include(), 'numpy')], extra_compile_args=[]), - Extension('wind.fwind', - sources=[pjoin('wind', 'fwind.f90')], + Extension('wind._windmodels', + sources=[pjoin('wind', 'windmodels.f90')], include_dirs=[pjoin(numpy.get_include(), 'numpy')], extra_compile_args=['-g']), - Extension('Utilities.fmaputils', + Extension('Utilities._maputils', sources=[pjoin('Utilities', 'maputils.f90')], include_dirs=[pjoin(numpy.get_include(), 'numpy')], extra_compile_args=['-g']), - Extension('PressureInterface.fpressureProfile', + Extension('PressureInterface._pressureProfile', sources=[pjoin('PressureInterface', 'pressureProfile.f90')], include_dirs=[pjoin(numpy.get_include(), 'numpy')], extra_compile_args=['-g']) diff --git a/wind/fwind.f90 b/wind/windmodels.f90 similarity index 100% rename from wind/fwind.f90 rename to wind/windmodels.f90 diff --git a/wind/windmodels.py b/wind/windmodels.py index 1423aa36..e42aa295 100644 --- a/wind/windmodels.py +++ b/wind/windmodels.py @@ -45,7 +45,7 @@ log.addHandler(logging.NullHandler()) try: - from . import fwind + from . import _windmodels except ImportError: warnings.warn("Compiled wind models not found - defaulting to slower python wind models") @@ -379,7 +379,7 @@ def velocity(self, R): dVm = self.firstDerivative() try: - from .fwind import fhollandvel + from ._windmodels import fhollandvel V = np.empty_like(R) fhollandvel( V.ravel(), R.ravel(), d2Vm, dVm, self.rMax, @@ -421,7 +421,7 @@ def vorticity(self, R): dVm = self.firstDerivative() try: - from .fwind import fhollandvort + from ._windmodels import fhollandvort Z = np.empty_like(R) fhollandvort( Z.ravel(), R.ravel(), d2Vm, dVm, self.rMax, self.vMax, @@ -1081,7 +1081,7 @@ def field(self, R, lam, vFm, thetaFm, thetaMax=0.): Vm = self.profile.vMax try: - from .fwind import fkerpert + from ._windmodels import fkerpert V = self.velocity(R) Z = self.vorticity(R) d2Vm, dVm = self.profile.secondDerivative(), self.profile.firstDerivative() From b18e53d455a6ccc006b56a54e7e880acf6ff16cc Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Fri, 25 Mar 2022 14:30:20 +1100 Subject: [PATCH 66/80] doc strings --- PressureInterface/pressureProfile.f90 | 11 +++++++++++ Utilities/maputils.f90 | 9 +++++++++ wind/windmodels.f90 | 25 +++++++++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/PressureInterface/pressureProfile.f90 b/PressureInterface/pressureProfile.f90 index bd50ca79..90dda4ff 100644 --- a/PressureInterface/pressureProfile.f90 +++ b/PressureInterface/pressureProfile.f90 @@ -1,5 +1,16 @@ subroutine fhollandpressure(P, R, rMax, pc, dP, beta, n) !$ use omp_lib + +! Calculates the pressure of the Holland profile +! +! :param P: 1D double precision output pressure array +! :param R: 1D double precision pressure array of distance from storm centre +! :param double rMax: radius to maximum winds (m) +! :param double pc: central pressure (Pa) +! :param double dP: central pressure deficit (Pa) +! :param double beta: shape parameter +! :param int n: length of arrays + integer, intent(in) :: n doubleprecision, intent(in), dimension(n) :: R doubleprecision, intent(inout), dimension(n) :: P diff --git a/Utilities/maputils.f90 b/Utilities/maputils.f90 index d2ecb3ca..366fdc00 100644 --- a/Utilities/maputils.f90 +++ b/Utilities/maputils.f90 @@ -1,6 +1,15 @@ subroutine beardist(cLon, cLat, lonArray, latArray, bearing, dist, nlon, nlat) !$ use omp_lib +! :param double cLon: longitude of storm centre (degrees) +! :param double clat: latitude of storm centre (degrees) +! :param lonArray: 1D double precision array of longitudes (degrees) +! :param latArray: 1D double precision array of latitudes (degrees) +! :param bearing: 2D double precision output array of bearings with shape (nlat, nlon) (radians) +! :param dist: 2D double precision output array of distances with shape (nlat, nlon) (km) +! :param int nlon: length of lonArray +! :param int nlat: length of latArray + integer, intent(in) :: nlon, nlat doubleprecision, intent(in) :: lonArray(nlon), latArray(nlat) doubleprecision, intent(inout), dimension(nlat, nlon) :: bearing, dist diff --git a/wind/windmodels.f90 b/wind/windmodels.f90 index 0eeec059..a4712433 100644 --- a/wind/windmodels.f90 +++ b/wind/windmodels.f90 @@ -1,6 +1,31 @@ subroutine fkerpert(R, lam, f, rMax, Vm, thetaFm, vFm, d2Vm, dVm, dP, beta, rho, Ux, Uy, n) !$ use omp_lib +! Kepert, J., 2001: The Dynamics of Boundary Layer Jets within the +! Tropical Cyclone Core. Part I: Linear Theory. J. Atmos. Sci., 58, +! 2469-2484 + +! This calculates the Kepert wind field with a Holland pressure profile. + +! :param R: Distance from the storm centre to the grid (m) +! :type R: 1D double precision array +! :param lam: Direction (0=east, radians, positive anti-clockwise) from storm centre to the grid. +! :type lam: 1D double precision array +! :param float rMax: Radius to maximum gradient winds (m). +! :param float Vm: Maximum gradient wind speed (m/s). +! :param float thetaFm: Bearing of storm (0=east, radians positive anti-clockwise).. +! :param float vFm: Foward speed of the storm (m/s). +! :param float d2Vm: Second derivitive of gradient windpseed w.r.t. radius at rMax. +! :param float dVm: Derivitive of gradient windpseed w.r.t. radius at rMax. +! :param float dP: Central pressure deficit in Pa +! :param float beta: Holland beta parameter. +! :param float rho: Density of air (kg/m^3). +! :param Ux: Output east windspeed (m/s) +! :type Ux: 1D double precision array +! :param Uy: Output north windspeed (m/s) +! :type Uy: 1D double precision array +! :param n: length of arrays + integer, intent(in) :: n doubleprecision, intent(in) :: f, rMax, Vm, thetaFm, vFm, d2Vm, dVm, dP, beta, rho doubleprecision, dimension(n), intent(in) :: R, lam From 93cc3cae2070e76878fdb7b17c032b5e2d3f6f6f Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Mon, 28 Mar 2022 09:25:59 +1100 Subject: [PATCH 67/80] only call Kepert wind field fortran code when a Holland pressure profile is used --- wind/windmodels.py | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/wind/windmodels.py b/wind/windmodels.py index e42aa295..321d714a 100644 --- a/wind/windmodels.py +++ b/wind/windmodels.py @@ -1079,22 +1079,21 @@ def field(self, R, lam, vFm, thetaFm, thetaMax=0.): K = 50. # Diffusivity Cd = 0.002 # Constant drag coefficient Vm = self.profile.vMax - - try: - from ._windmodels import fkerpert - V = self.velocity(R) - Z = self.vorticity(R) - d2Vm, dVm = self.profile.secondDerivative(), self.profile.firstDerivative() - Ux, Vy = np.empty_like(R), np.empty_like(R) - n = Ux.size - fkerpert( - R.ravel(), lam.ravel(), self.f, self.rMax, Vm, thetaFm, - vFm, d2Vm, dVm, self.profile.dP, self.profile.beta, self.profile.rho, - Ux.ravel(), Vy.ravel(), n - ) - return Ux, Vy - except ImportError: - pass + if type(self.profile) in (PowellWindProfile, HollandWindProfile): + try: + from ._windmodels import fkerpert + + d2Vm, dVm = self.profile.secondDerivative(), self.profile.firstDerivative() + Ux, Vy = np.empty_like(R), np.empty_like(R) + n = Ux.size + fkerpert( + R.ravel(), lam.ravel(), self.f, self.rMax, Vm, thetaFm, + vFm, d2Vm, dVm, self.profile.dP, self.profile.beta, self.profile.rho, + Ux.ravel(), Vy.ravel(), n + ) + return Ux, Vy + except ImportError: + pass V = self.velocity(R) Z = self.vorticity(R) From 9463f6bd1c6b3f7db74072d3d3ad666e46a8c911 Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Mon, 28 Mar 2022 21:42:42 +1100 Subject: [PATCH 68/80] Working version of cartopy-based scalebar --- PlotInterface/maps.py | 33 ++---- PlotInterface/scalebar.py | 208 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 218 insertions(+), 23 deletions(-) create mode 100644 PlotInterface/scalebar.py diff --git a/PlotInterface/maps.py b/PlotInterface/maps.py index c719ff0c..ba2e9244 100644 --- a/PlotInterface/maps.py +++ b/PlotInterface/maps.py @@ -24,6 +24,8 @@ from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas import seaborn as sns +from .scalebar import scale_bar + def levels(maxval, minval=0): """ Calculate a nice number of levels between `minval` and `maxval` @@ -230,35 +232,20 @@ def maskOceans(self, mapobj, fillcolor="#66ccff"): mapobj.add_feature(cartopy.feature.OCEAN, color=fillcolor) - def addMapScale(self, mapobj): + def addMapScale(self, axes): """ - Add a map scale to the curent `Basemap` instance. This - automatically determines a 'nice' length forthe scale bar - + Add a map scale to the curent `cartopy.mpl.geoaxes.GeoAxes` instance. + This automatically determines a 'nice' length forthe scale bar - chosen to be approximately 20% of the map width at the centre of the map. - :param mapobj: Current `Basemap` instance to add the scale bar to. + :param ax: Current `cartopy.mpl.geoaxes.GeoAxes` instance to add the + scale bar to. + see https://stackoverflow.com/questions/32333870/ """ - return # TODO: migrate to cartopy - see https://stackoverflow.com/questions/32333870/ - - midlon = (mapobj.lonmax - mapobj.lonmin) / 2. - midlat = (mapobj.latmax - mapobj.latmin) / 2. - - xmin = mapobj.llcrnrx - xmax = mapobj.urcrnrx - ymin = mapobj.llcrnry - ymax = mapobj.urcrnry + axes = scale_bar(axes, (0.05, 0.1)) - xloc = xmin + 0.15 * abs(xmax - xmin) - yloc = ymin + 0.1 * abs(ymax - ymin) - - lonloc, latloc = mapobj(xloc, yloc, inverse=True) - - # Set scale length to nearest 100-km for 20% of map width - scale_length = 100*int((0.2 * (xmax - xmin) / 1000.)/100) - mapobj.drawmapscale(lonloc, latloc, midlon, midlat, scale_length, - barstyle='fancy', zorder=10) def createMap(self, axes, xgrid, ygrid, map_kwargs): """ @@ -296,7 +283,7 @@ def subplot(self, axes, subfigure): self.addGraticule(axes, mapobj) self.addCoastline(mapobj) self.fillContinents(mapobj) - self.addMapScale(mapobj) + self.addMapScale(axes) def plot(self): """ diff --git a/PlotInterface/scalebar.py b/PlotInterface/scalebar.py new file mode 100644 index 00000000..f5f4684d --- /dev/null +++ b/PlotInterface/scalebar.py @@ -0,0 +1,208 @@ +import numpy as np +import cartopy.crs as ccrs +import cartopy.geodesic as cgeo + + +def _axes_to_lonlat(ax, coords): + """(lon, lat) from axes coordinates.""" + display = ax.transAxes.transform(coords) + data = ax.transData.inverted().transform(display) + lonlat = ccrs.PlateCarree().transform_point(*data, ax.projection) + + return lonlat + + +def _upper_bound(start, direction, distance, dist_func): + """A point farther than distance from start, in the given direction. + + It doesn't matter which coordinate system start is given in, as long + as dist_func takes points in that coordinate system. + + Args: + start: Starting point for the line. + direction Nonzero (2, 1)-shaped array, a direction vector. + distance: Positive distance to go past. + dist_func: A two-argument function which returns distance. + + Returns: + Coordinates of a point (a (2, 1)-shaped NumPy array). + """ + if distance <= 0: + raise ValueError(f"Minimum distance is not positive: {distance}") + + if np.linalg.norm(direction) == 0: + raise ValueError("Direction vector must not be zero.") + + # Exponential search until the distance between start and end is + # greater than the given limit. + length = 0.1 + end = start + length * direction + + while dist_func(start, end) < distance: + length *= 2 + end = start + length * direction + + return end + + +def _distance_along_line(start, end, distance, dist_func, tol): + """Point at a distance from start on the segment from start to end. + + It doesn't matter which coordinate system start is given in, as long + as dist_func takes points in that coordinate system. + + Args: + start: Starting point for the line. + end: Outer bound on point's location. + distance: Positive distance to travel. + dist_func: Two-argument function which returns distance. + tol: Relative error in distance to allow. + + Returns: + Coordinates of a point (a (2, 1)-shaped NumPy array). + """ + initial_distance = dist_func(start, end) + if initial_distance < distance: + raise ValueError(f"End is closer to start ({initial_distance}) than " + f"given distance ({distance}).") + + if tol <= 0: + raise ValueError(f"Tolerance is not positive: {tol}") + + # Binary search for a point at the given distance. + left = start + right = end + + while not np.isclose(dist_func(start, right), distance, rtol=tol): + midpoint = (left + right) / 2 + + # If midpoint is too close, search in second half. + if dist_func(start, midpoint) < distance: + left = midpoint + # Otherwise the midpoint is too far, so search in first half. + else: + right = midpoint + + return right + + +def _point_along_line(ax, start, distance, angle=0, tol=0.01): + """Point at a given distance from start at a given angle. + + Args: + ax: CartoPy axes. + start: Starting point for the line in axes coordinates. + distance: Positive physical distance to travel. + angle: Anti-clockwise angle for the bar, in radians. Default: 0 + tol: Relative error in distance to allow. Default: 0.01 + + Returns: + Coordinates of a point (a (2, 1)-shaped NumPy array). + """ + # Direction vector of the line in axes coordinates. + direction = np.array([np.cos(angle), np.sin(angle)]) + geodesic = cgeo.Geodesic() + + # Physical distance between points. + def dist_func(a_axes, b_axes): + a_phys = _axes_to_lonlat(ax, a_axes) + b_phys = _axes_to_lonlat(ax, b_axes) + + # Geodesic().inverse returns a NumPy MemoryView like [[distance, + # start azimuth, end azimuth]]. + return geodesic.inverse(a_phys, b_phys)[0, 0] + + end = _upper_bound(start, direction, distance, dist_func) + + return _distance_along_line(start, end, distance, dist_func, tol) + + +def _setlength(ax, location): + """ + If no length is given, then set an arbitrary length of the scale bar + + :param ax: `cartopy.mpl.geoaxes.GeoAxes` instance + :param location: Position of left-side of bar in axes coordinates + + """ + llx0, llx1, lly0, lly1 = ax.get_extent(ax.projection) + sbllx = (llx1 + llx0) / 2 + sblly = lly0 + (lly1 - lly0) * location[1] + tmc = ccrs.TransverseMercator(sbllx, sblly) + #Get the extent of the plotted area in coordinates in metres + x0, x1, y0, y1 = ax.get_extent(tmc) + length = (x1 - x0) / 5000 + ndim = int(np.floor(np.log10(length))) #number of digits in number + length = round(length, -ndim) #round to 1sf + #Returns numbers starting with the list + def scale_number(x): + if str(x)[0] in ['1', '2', '5']: return int(x) + else: return scale_number(x - 10 ** ndim) + length = scale_number(length) + return length + + +def scale_bar(ax, location, length=None, metres_per_unit=1000, unit_name='km', + tol=0.01, angle=0, color='black', linewidth=3, text_offset=0.005, + ha='center', va='bottom', plot_kwargs=None, text_kwargs=None, + **kwargs): + """Add a scale bar to CartoPy axes. + + For angles between 0 and 90 the text and line may be plotted at + slightly different angles for unknown reasons. To work around this, + override the 'rotation' keyword argument with text_kwargs. + + Args: + ax: CartoPy axes. + location: Position of left-side of bar in axes coordinates. + length: Geodesic length of the scale bar. + metres_per_unit: Number of metres in the given unit. Default: 1000 + unit_name: Name of the given unit. Default: 'km' + tol: Allowed relative error in length of bar. Default: 0.01 + angle: Anti-clockwise rotation of the bar. + color: Color of the bar and text. Default: 'black' + linewidth: Same argument as for plot. + text_offset: Perpendicular offset for text in axes coordinates. + Default: 0.005 + ha: Horizontal alignment. Default: 'center' + va: Vertical alignment. Default: 'bottom' + **plot_kwargs: Keyword arguments for plot, overridden by **kwargs. + **text_kwargs: Keyword arguments for text, overridden by **kwargs. + **kwargs: Keyword arguments for both plot and text. + """ + # Setup kwargs, update plot_kwargs and text_kwargs. + if plot_kwargs is None: + plot_kwargs = {} + if text_kwargs is None: + text_kwargs = {} + + plot_kwargs = {'linewidth': linewidth, 'color': color, **plot_kwargs, + **kwargs} + text_kwargs = {'ha': ha, 'va': va, 'rotation': angle, 'color': color, + **text_kwargs, **kwargs} + + if not length: + length = _setlength(ax, location) + # Convert all units and types. + location = np.asarray(location) # For vector addition. + length_metres = length * metres_per_unit + angle_rad = angle * np.pi / 180 + + # End-point of bar. + end = _point_along_line(ax, location, length_metres, angle=angle_rad, + tol=tol) + + # Coordinates are currently in axes coordinates, so use transAxes to + # put into data coordinates. *zip(a, b) produces a list of x-coords, + # then a list of y-coords. + ax.plot(*zip(location, end), transform=ax.transAxes, **plot_kwargs) + + # Push text away from bar in the perpendicular direction. + midpoint = (location + end) / 2 + offset = text_offset * np.array([-np.sin(angle_rad), np.cos(angle_rad)]) + text_location = midpoint + offset + + # 'rotation' keyword argument is in text_kwargs. + ax.text(*text_location, f"{length} {unit_name}", rotation_mode='anchor', + transform=ax.transAxes, **text_kwargs) + return ax \ No newline at end of file From 046306093ec89718a2fab2f00d301873876f3a32 Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Mon, 4 Apr 2022 11:50:22 +1000 Subject: [PATCH 69/80] Pycxml compatability (#133) * handle pycxml track files * fix fortran warnings * edit track class * use single process * close netcdf files asap * make pycxml optional Co-authored-by: Kieran Ricardo Co-authored-by: Kieran Ricardo --- Evaluate/interpolateTracks.py | 56 ++++++++++++++++++++------- PressureInterface/pressureProfile.f90 | 2 +- PressureInterface/pressureProfile.py | 2 +- Utilities/maputils.f90 | 4 +- Utilities/track.py | 11 +++++- wind/__init__.py | 5 ++- 6 files changed, 60 insertions(+), 20 deletions(-) diff --git a/Evaluate/interpolateTracks.py b/Evaluate/interpolateTracks.py index 924fd2b1..2eed8f8e 100644 --- a/Evaluate/interpolateTracks.py +++ b/Evaluate/interpolateTracks.py @@ -9,6 +9,9 @@ from Utilities.maputils import latLon2Azi from Utilities.loadData import loadTrackFile, maxWindSpeed from Utilities.track import Track, ncSaveTracks +from Utilities.parallel import attemptParallel +import pandas as pd + LOG = logging.getLogger(__name__) LOG.addHandler(logging.NullHandler()) @@ -139,7 +142,9 @@ def interpolate(track, delta, interpolation_type=None): if interpolation_type == 'akima': # Use the Akima interpolation method: try: - import akima + from Utilities import akima + nLon = akima.interpolate(timestep, track.Longitude, newtime) + nLat = akima.interpolate(timestep, track.Latitude, newtime) except ImportError: LOG.exception(("Akima interpolation module unavailable " " - default to scipy.interpolate")) @@ -148,10 +153,6 @@ def interpolate(track, delta, interpolation_type=None): nLat = splev(newtime, splrep(timestep, track.Latitude, s=0), der=0) - else: - nLon = akima.interpolate(timestep, track.Longitude, newtime) - nLat = akima.interpolate(timestep, track.Latitude, newtime) - elif interpolation_type == 'linear': nLon = interp1d(timestep, track.Longitude, kind='linear')(newtime) nLat = interp1d(timestep, track.Latitude, kind='linear')(newtime) @@ -299,21 +300,48 @@ def parseTracks(configFile, trackFile, source, delta, outputFile=None, if trackFile.endswith("nc"): from Utilities.track import ncReadTrackData tracks = ncReadTrackData(trackFile) + elif trackFile.endswith("xml"): + from pycxml.pycxml import loadfile + dfs = loadfile(trackFile) + tracks = [bom2tcrm(df, i) for i, df in enumerate(dfs)] else: tracks = loadTrackFile(configFile, trackFile, source) results = [] - for track in tracks: - if len(track.data) == 1: - results.append(track) - else: - newtrack = interpolate(track, delta, interpolation_type) - results.append(newtrack) + # interpolating is memory intensive - only use a single process + MPI = attemptParallel() + if MPI.COMM_WORLD.rank == 0: - if outputFile: - # Save data to file: - ncSaveTracks(outputFile, results) + for track in tracks: + if len(track.data) == 1: + results.append(track) + else: + newtrack = interpolate(track, delta, interpolation_type) + results.append(newtrack) + if outputFile: + # Save data to file: + ncSaveTracks(outputFile, results) + MPI.COMM_WORLD.barrier() return results + + +def bom2tcrm(df, trackId): + """ + Transforms a dataframe in BoM format into a tcrm track. + + """ + df['Datetime'] = pd.to_datetime(df.validtime) + df['Speed'] = df.translation_speed + df['CentralPressure'] = df.pcentre + df['Longitude'] = df.longitude + df['Latitude'] = df.latitude + df['EnvPressure'] = df.poci + df['rMax'] = df.rmax + df['trackId'] = df.disturbance.values + + track = Track(df) + track.trackId = [trackId, trackId] + return track diff --git a/PressureInterface/pressureProfile.f90 b/PressureInterface/pressureProfile.f90 index 90dda4ff..c3afd996 100644 --- a/PressureInterface/pressureProfile.f90 +++ b/PressureInterface/pressureProfile.f90 @@ -18,7 +18,7 @@ subroutine fhollandpressure(P, R, rMax, pc, dP, beta, n) !$OMP PARALLEL DO shared(P) do i = 1, n - P(i) = pCentre + dP * exp(-(rMax / R(i)) ** beta) + P(i) = pc + dP * exp(-(rMax / R(i)) ** beta) end do !$OMP END PARALLEL DO diff --git a/PressureInterface/pressureProfile.py b/PressureInterface/pressureProfile.py index 0cd1864e..96dc00b4 100644 --- a/PressureInterface/pressureProfile.py +++ b/PressureInterface/pressureProfile.py @@ -154,7 +154,7 @@ def holland(self, beta=None): try: from ._pressureProfile import fhollandpressure P = numpy.empty(self.R.shape) - hollandpressure( + fhollandpressure( P.ravel(), self.R.ravel(), self.rMax, self.pCentre, self.dP, beta ) except ImportError: diff --git a/Utilities/maputils.f90 b/Utilities/maputils.f90 index 366fdc00..0a181886 100644 --- a/Utilities/maputils.f90 +++ b/Utilities/maputils.f90 @@ -14,9 +14,11 @@ subroutine beardist(cLon, cLat, lonArray, latArray, bearing, dist, nlon, nlat) doubleprecision, intent(in) :: lonArray(nlon), latArray(nlat) doubleprecision, intent(inout), dimension(nlat, nlon) :: bearing, dist - doubleprecision :: toRads, cLon_, cLat_, dlon, dlat, lon(nlon), lat(nlat), radius + doubleprecision :: toRads, cLon, cLat, dlon, lon(nlon), lat(nlat), radius doubleprecision :: dLon_sin(nlon), dLon_cos(nlon), lat_sin(nlat), lat_cos(nlat) doubleprecision :: dLat_sin(nlat), dhalfLon_sin(nlon), a, c, pi + doubleprecision :: cLon_, cLat_, cLat_cos, cLat_sin, alpha, beta + pi = 4.d0*datan(1.d0) toRads = 0.017453292519943295 radius = 6367.0 diff --git a/Utilities/track.py b/Utilities/track.py index 7f9bfa07..84b6b98f 100644 --- a/Utilities/track.py +++ b/Utilities/track.py @@ -81,15 +81,22 @@ def __init__(self, data): self.data = data self.trackId = None self.trackfile = None - if (len(data) > 0) and ('CentralPressure' in data.dtype.names): + if (len(data) > 0) and self.has_key('CentralPressure'): self.trackMinPressure = np.min(data['CentralPressure']) else: self.trackMinPressure = None - if (len(data) > 0) and ('WindSpeed' in data.dtype.names): + if (len(data) > 0) and self.has_key('WindSpeed'): self.trackMaxWind = np.max(data['WindSpeed']) else: self.trackMaxWind = None + def has_key(self, key): + + try: + return (key in self.data.dtype.names) + except AttributeError: + return (key in self.data.columns) + def __getattr__(self, key): """ Get the `key` from the `data` object. diff --git a/wind/__init__.py b/wind/__init__.py index b6a59bc0..e8b614be 100644 --- a/wind/__init__.py +++ b/wind/__init__.py @@ -39,6 +39,7 @@ import numpy as np import tqdm from . import windmodels +from . import writer from PlotInterface.maps import saveWindfieldMap @@ -323,6 +324,9 @@ def regionalExtremes(self, gridLimit, timeStepCallback=None): P < pressure[jmin:jmax, imin:imax], P, pressure[jmin:jmax, imin:imax]) + if type(timeStepCallback) is writer.WriteFoliationCallback: + timeStepCallback.ds.close() + return gust, bearing, UU, VV, pressure, lonGrid / 100., latGrid / 100. @@ -435,7 +439,6 @@ def calculateExtremesFromTrack(self, track, callback=None): domain=self.domain) if self.config.getboolean('Timeseries', 'Windfields', fallback=False): - from . import writer output = pjoin(self.windfieldPath, 'evolution.{0:03d}-{1:05d}.nc'.format( *track.trackId)) From c108d2fed440344dbae7613690faed42b904a59a Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Thu, 7 Apr 2022 16:50:49 +1000 Subject: [PATCH 70/80] gdal multi-threading for reprojection --- ProcessMultipliers/processMultipliers.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ProcessMultipliers/processMultipliers.py b/ProcessMultipliers/processMultipliers.py index f20f3863..3e2e0824 100755 --- a/ProcessMultipliers/processMultipliers.py +++ b/ProcessMultipliers/processMultipliers.py @@ -975,11 +975,15 @@ def processMultV2(wspd, uu, vv, lon, lat, working_dir, dirns, future_requests = [] with futures.ThreadPoolExecutor(max_workers=max_working_threads) as e: m4_max_file_obj = gdal.Open(m4_max_file, gdal.GA_ReadOnly) + + gdal.SetConfigOption('GDAL_NUM_THREADS', str(max_working_threads)) reprojectDataset(wind_raster, m4_max_file_obj, wind_prj_file, warp_memory_limit=warp_memory_limit) reprojectDataset(bear_raster, m4_max_file_obj, bear_prj_file, warp_memory_limit=warp_memory_limit, resampling_method=gdalconst.GRA_NearestNeighbour) + + gdal.SetConfigOption('GDAL_NUM_THREADS', '1') future_requests.append(e.submit(reprojectDataset, uu_raster, m4_max_file_obj, uu_prj_file, warp_memory_limit=warp_memory_limit, resampling_method=gdalconst.GRA_NearestNeighbour)) From 7190030e78f7adc707d26d914b2d3749f6f8ef42 Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Thu, 29 Sep 2022 21:10:16 +1000 Subject: [PATCH 71/80] Use expected DataProcess-InputFile --- DataProcess/DataProcess.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/DataProcess/DataProcess.py b/DataProcess/DataProcess.py index 91ea5ec4..9693a98c 100644 --- a/DataProcess/DataProcess.py +++ b/DataProcess/DataProcess.py @@ -171,13 +171,17 @@ def processData(self, restrictToWindfieldDomain=False): if config.has_option('DataProcess', 'InputFile'): inputFile = config.get('DataProcess', 'InputFile') self.logger.info(f"Input file from DataProcess: {inputFile}") + else: + inputFile = None if config.has_option('DataProcess', 'Source'): source = config.get('DataProcess', 'Source') self.logger.info(f"Loading {source} dataset") - fn = config.get(source, 'Filename') - path = config.get(source, 'Path') - inputFile = pjoin(path, fn) + if inputFile is None: + # Use this as alternate source of input file (downloaded?) + fn = config.get(source, 'Filename') + path = config.get(source, 'Path') + inputFile = pjoin(path, fn) self.logger.info(f"Input file set to {inputFile}") # If input file has no path information, default to tcrm input folder From ecf9e1adbfd93c02cc5682bc78579bb22e568a35 Mon Sep 17 00:00:00 2001 From: wcarthur Date: Fri, 30 Sep 2022 14:46:03 +1000 Subject: [PATCH 72/80] Create daily LTM MSLP file from ERA5 --- create_era5_mslp.sh | 73 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100755 create_era5_mslp.sh diff --git a/create_era5_mslp.sh b/create_era5_mslp.sh new file mode 100755 index 00000000..27b2fa0d --- /dev/null +++ b/create_era5_mslp.sh @@ -0,0 +1,73 @@ +#!/bin/bash +#PBS -Pw85 +#PBS -qnormal +#PBS -N calc_era5_mslp +#PBS -m ae +#PBS -M craig.arthur@ga.gov.au +#PBS -lwalltime=48:00:00 +#PBS -lmem=64GB,ncpus=16,jobfs=8000MB +#PBS -W umask=0002 +#PBS -joe +#PBS -lstorage=scratch/w85+gdata/ub4 +#PBS -v STARTYEAR + +module purge +module load pbs +module load dot + +module load netcdf/4.6.3 +module load cdo/1.9.8 +module load nco/4.9.2 +module load openmpi + +# Suppresses an error related to HDF5 libraries: +export HDF5_DISABLE_VERSION_CHECK=2 +ECHO=/bin/echo + +cd ${PBS_JOBFS} + +BASEPATH=/g/data/ub4/era5/netcdf/surface/msl +SCRATCH=/scratch/w85/ + +#STARTYEAR=1979 +COUNTER=0 +LOGFILE=$HOME/tcrm/calc_era5_mslp.$STARTYEAR.log +echo "Starting calculation of daily long term mean MSL" > $LOGFILE +for i in {0..40..1}; do + for m in {1..12..1}; do + YEAR=$(($STARTYEAR + $i)) + ENDDATE=`date -d "$YEAR/$m/1 +1 month -1 day" "+%Y%m%d"` + STARTDATE=`date -d "$YEAR/$m/1" "+%Y%m%d"` + DATESTR=$STARTDATE\_$ENDDATE + INPUTFILE=$BASEPATH/$YEAR/msl_era5_global_$DATESTR.nc + MONTHFMT=`date -d "$YEAR/$m/1" "+%Y%m"` + OUTPUTFILE=${PBS_JOBFS}/msl_era5_global.$MONTHFMT.nc + echo $INPUTFILE >> $LOGFILE + echo $OUTPUTFILE >> $LOGFILE + cdo daymean $INPUTFILE $OUTPUTFILE + if [[ $? -ne 0 ]]; then + echo "Looks like the command failed when processing $INPUTFILE" >> $LOGFILE + else + ((COUNTER+=1)) + echo "Processed $INPUTFILE ($COUNTER)" >> $LOGFILE + fi + done + # concatenate individual monthly files into annual files + cdo -b F64 mergetime ${PBS_JOBFS}/msl_era5_global.$YEAR[0-9][0-9].nc $SCRATCH/msl/msl_era5_global.$YEAR.nc + # Remove individual monthly files: + rm ${PBS_JOBFS}/msl_era5_global.$YEAR[0-9][0-9].nc +done + +$ECHO "Finished processing $COUNTER files for daily MSLP data" >> $LOGFILE + +#$ECHO "Now merging into a single daily long-term mean dataset" >> $LOGFILE + +#cd $SCRATCH/msl + +#cdo -P ${PBS_NCPUS} ydaymean -cat 'msl_era5_global.*.nc' msl_era5_global_dailyltm.nc + +#if [[ $? -ne 0 ]]; then +# $ECHO "cdo ydaymean command failed" >> $LOGFILE +#else +# $ECHO "Finished processing $COUNTER files to create daily long-term mean MSL file" >> $LOGFILE +#fi From 70a575a619df17b4632c39324eeb134991dae94d Mon Sep 17 00:00:00 2001 From: Kieran Ricardo Date: Fri, 4 Nov 2022 15:49:30 +1100 Subject: [PATCH 73/80] Low resolution wind multipliers (#136) Adds in a script to quickly downscale the wind multipliers and apply them to many wind fields quickly. If the wind multipliers have already been extracted this process takes minutes instead of weeks. Co-authored-by: Kieran Ricardo --- .../lowResProcessMultipliers.ini | 14 ++ .../lowResProcessMultipliers.py | 182 ++++++++++++++++++ hazard/GPD.py | 22 +++ 3 files changed, 218 insertions(+) create mode 100644 ProcessMultipliers/lowResProcessMultipliers.ini create mode 100644 ProcessMultipliers/lowResProcessMultipliers.py diff --git a/ProcessMultipliers/lowResProcessMultipliers.ini b/ProcessMultipliers/lowResProcessMultipliers.ini new file mode 100644 index 00000000..8dfa78d6 --- /dev/null +++ b/ProcessMultipliers/lowResProcessMultipliers.ini @@ -0,0 +1,14 @@ +[Input] +# Multipliers can be any file type that gdal accepts but its faster to extract the multipliers from the VRT and only apply the extent once +# Multipliers=/g/data/w85/National_multipliers2021/WM_National/M3/wind-multipliers.vrt +Multipliers=/g/data/w85/kr4383/yasi/multipliers/000-00000/m4_source.tif +Gust_dir=/g/data/w85/kr4383/yasi/windfield + +[Output] +Working_dir = /g/data/w85/kr4383/yasi + +[Logging] +LogFile=processMultipliers.log +LogLevel=DEBUG +Verbose=True +Datestamp=True diff --git a/ProcessMultipliers/lowResProcessMultipliers.py b/ProcessMultipliers/lowResProcessMultipliers.py new file mode 100644 index 00000000..de253b9d --- /dev/null +++ b/ProcessMultipliers/lowResProcessMultipliers.py @@ -0,0 +1,182 @@ +from osgeo import osr, gdal, gdalconst +from osgeo.gdal_array import BandReadAsArray, CopyDatasetInfo, BandWriteArray +from netCDF4 import Dataset +import os +import xarray as xr +import numpy as np +from tqdm import tqdm +from Utilities.config import ConfigParser +from Utilities.files import flStartLog +import argparse +from os.path import join as pjoin, dirname, realpath, isdir, splitext +import traceback +import logging as log + + +def downscale_multipliers(src_file, match_file, dst_file, epsg=4326): + """ + Downscales and clips GDAL compatable file and saves it as a GEOTIFF. + + Params: + - src_file: filepath of the input file to be downscaled + - match_file: filepath of the file that the input is transformed to match + - dst_file: output filepath + """ + # load src + if not os.path.isfile(src_file): + raise FileNotFoundError(f"src_file: file {src_file} not found.") + + if not os.path.isfile(match_file): + raise FileNotFoundError(f"match_file: file {match_file} not found.") + + if not os.path.path.isdir(os.path.split(dst_file)[0]): + raise FileNotFoundError(f"dst_file: directory {os.path.split(dst_file)[0]} not found.") + + src = gdal.Open(src_file, gdal.GA_ReadOnly) + + # load match info + ncobj = Dataset(match_file, 'r') + lat = ncobj.variables['lat'][:] + lon = ncobj.variables['lon'][:] + delta = lon[1] - lon[0] + lon = lon - delta / 2. + lat = lat - delta / 2. + + dx = lon[1] - lon[0] + dy = lat[1] - lat[0] + originX, originY = lon[0], lat[0] + + wide = len(lon) + high = len(lat) + + # Output / destination + srs = osr.SpatialReference() + srs.ImportFromEPSG(epsg) + + # warp + drv = gdal.GetDriverByName('GTiff') + dst = drv.Create(dst_file, wide, high, 8, gdal.GDT_Float32) + dst.SetGeoTransform((originX, dx, 0, originY, 0, dy)) + dst.SetProjection(srs.ExportToWkt()) + dstBand = dst.GetRasterBand(1) + dstBand.SetNoDataValue(-9999) + + gdal.ReprojectImage(src, dst, src.GetProjection(), dst.GetProjection(), gdalconst.GRA_Bilinear) + + +class run(): + + def __init__(self): + """ + Parse command line arguments and call the :func:`main` function. + + """ + parser = argparse.ArgumentParser() + parser.add_argument('-c', '--config_file', + help='Path to configuration file') + parser.add_argument('-v', '--verbose', help='Verbose output', + action='store_true') + parser.add_argument('-d', '--debug', help='Allow pdb traces', + action='store_true') + args = parser.parse_args() + + self.configFile = args.config_file + config = ConfigParser() + config.read(self.configFile) + + logfile = config.get('Logging', 'LogFile') + logdir = dirname(realpath(logfile)) + + # If log file directory does not exist, create it + if not isdir(logdir): + try: + os.makedirs(logdir) + except OSError: + logfile = pjoin(os.getcwd(), 'processMultipliers.log') + + logLevel = config.get('Logging', 'LogLevel') + verbose = config.getboolean('Logging', 'Verbose') + datestamp = config.getboolean('Logging', 'Datestamp') + + if args.verbose: + verbose = True + + flStartLog(logfile, logLevel, verbose, datestamp) + # Switch off minor warning messages + import warnings + warnings.filterwarnings("ignore", category=DeprecationWarning) + warnings.filterwarnings("ignore", category=UserWarning, module="pytz") + warnings.filterwarnings("ignore", category=UserWarning, module="numpy") + warnings.filterwarnings("ignore", category=UserWarning, module="matplotlib") + + warnings.filterwarnings("ignore", category=RuntimeWarning) + + self.working_dir = config.get('Output', 'Working_dir') + self.gust_dir = config.get('Input', 'Gust_dir') + self.mult_file = config.get('Input', 'Multipliers') + + try: + self.main() + except ImportError as e: + log.critical("Missing module: {0}".format(e.strerror)) + except Exception: # pylint: disable=W0703 + # Catch any exceptions that occur and log them (nicely): + tblines = traceback.format_exc().splitlines() + for line in tblines: + log.critical(line.lstrip()) + + def main(self): + low_res_mult_file = os.path.join(self.working_dir, "low_res_m4.tif") + gust_files = [os.path.join(self.gust_dir, fn) for fn in os.listdir(self.gust_dir) if fn.startswith("gust")] + + log.info("Downscaling multipliers") + gdal.SetConfigOption('GDAL_NUM_THREADS', "16") + downscale_multipliers(self.mult_file, gust_files[0], low_res_mult_file) + gdal.SetConfigOption('GDAL_NUM_THREADS', "1") + + # indices, band numbers, and directions for using wind multipliers + indices = { + 0: {'dir': 'n', 'min': 0., 'max': 22.5}, + 1: {'dir': 'ne', 'min': 22.5, 'max': 67.5}, + 2: {'dir': 'e', 'min': 67.5, 'max': 112.5}, + 3: {'dir': 'se', 'min': 112.5, 'max': 157.5}, + 4: {'dir': 's', 'min': 157.5, 'max': 202.5}, + 5: {'dir': 'sw', 'min': 202.5, 'max': 247.5}, + 6: {'dir': 'w', 'min': 247.5, 'max': 292.5}, + 7: {'dir': 'nw', 'min': 292.5, 'max': 337.5}, + 8: {'dir': 'n', 'min': 337.5, 'max': 360.} + } + band_numbers_for_indices_in_geotiff = [2, 3, 1, 6, 5, 7, 8, 4, 2] + + # load in wind multiplier data + # reducing the resolution seems to ignore the -9999 nodata causing some -9999 to be averaged with real data + # these points (along with other nodata points) are set to 1 + ds = gdal.Open(low_res_mult_file, gdal.GA_ReadOnly) + bands = [] + for i in range(1, 9): + band = ds.GetRasterBand(i).ReadAsArray() + band[band < 0] = 1 + bands.append(band) + + log.info("Applying multipliers") + # loop through the gust files and apply the wm + for gust_file in tqdm(gust_files): + gust = xr.load_dataset(gust_file) + wind_data = gust.vmax + local = wind_data.copy() + + bearing = 2 * np.pi - (np.arctan2(-gust.va, -gust.ua) - np.pi / 2) + bearing = (180. / np.pi) * np.mod(bearing, 2. * np.pi) + + for i in list(indices.keys()): + idx = np.where((bearing >= indices[i]['min']) & (bearing < indices[i]['max'])) + m4 = bands[band_numbers_for_indices_in_geotiff[i] - 1] + local.data[idx] = wind_data.data[idx] * m4[idx] + + outds = xr.Dataset() + outds["vmax"] = local + outds.to_netcdf(gust_file.replace("gust", "wm_gust")) + + +if __name__ == "__main__": + run() \ No newline at end of file diff --git a/hazard/GPD.py b/hazard/GPD.py index 3c6080d8..f53976d5 100644 --- a/hazard/GPD.py +++ b/hazard/GPD.py @@ -43,6 +43,28 @@ def gpdReturnLevel(intervals, mu, shape, scale, rate, npyr=365.25): rp = mu + (scale / shape) * (np.power(intervals * npyr * rate, shape) - 1.) return rp + +def gpdRecurrenceIntervals(return_levels, mu, shape, scale, rate, npyr=365.25): + """ + Calculate recurrence intervals for specified return levels for a distribution with + the given threshold, scale and shape parameters. + + :param intervals: :class:`numpy.ndarray` or float of return levels + to evaluate recurrence intervals for. + :param float mu: Threshold parameter (also called location). + :param float shape: Shape parameter. + :param float scale: Scale parameter. + :param float rate: Rate of exceedances (i.e. number of observations greater + than `mu`, divided by total number of observations). + :param float npyr: Number of observations per year. + + :returns: recurrence intervals for the specified return levels. + + """ + ri = np.power((return_levels - mu) * (shape / scale) + 1, 1 / shape) / (npyr * rate) + return ri + + def gpdfit(data, years, numsim, missingValue=-9999, minrecords=50, threshold=99.5): """ From 474b79ab7294112efa8ec15450290df306906e76 Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Mon, 6 Mar 2023 09:08:02 +1100 Subject: [PATCH 74/80] Bugfix for ACF function (#139) * BUGFIX: Fix autocorrelation function * Use mamba - Github actions environment build time was > 30 minutes * Swap scipy random for numpy version * Change order of modules - mamba build wanted gdal as first module in the list * Update test_maps.py - moving to matplotlib 3.3 seems to have changed the way segment data is represented in LinearSegmentedColormap instances --- .github/workflows/tcrm-tests.yml | 10 +++++++--- ProcessMultipliers/processMultipliers.py | 2 +- StatInterface/generateStats.py | 15 ++++----------- TrackGenerator/TrackGenerator.py | 8 ++++---- tcrmenv.yml | 4 ++-- tests/test_SamplingOrigin.py | 2 +- tests/test_SamplingParameters.py | 2 +- tests/test_generateStats.py | 8 ++++---- tests/test_maps.py | 4 ++-- 9 files changed, 26 insertions(+), 29 deletions(-) diff --git a/.github/workflows/tcrm-tests.yml b/.github/workflows/tcrm-tests.yml index 5f6798a3..59f5b5d6 100644 --- a/.github/workflows/tcrm-tests.yml +++ b/.github/workflows/tcrm-tests.yml @@ -13,7 +13,7 @@ jobs: TCRM: name: Test TCRM runs-on: ubuntu-latest - strategy: + strategy: matrix: python-version: [3.7, 3.8, 3.9] steps: @@ -21,13 +21,17 @@ jobs: - name: Set up environment uses: conda-incubator/setup-miniconda@v2.0.0 with: + python-version: ${{ matrix.python-version }} + mamba-version: "*" + channels: conda-forge,defaults + channel-priority: true activate-environment: tcrm environment-file: tcrmenv.yml - python-version: ${{ matrix.python-version }} auto-activate-base: false + use-only-tar-bz2: true - name: Test with pytest - env: + env: PYTHONPATH: ~/tcrm;~/tcrm/Utilities shell: bash -l {0} run: | diff --git a/ProcessMultipliers/processMultipliers.py b/ProcessMultipliers/processMultipliers.py index 3e2e0824..6149d74c 100755 --- a/ProcessMultipliers/processMultipliers.py +++ b/ProcessMultipliers/processMultipliers.py @@ -72,9 +72,9 @@ import numpy as np import numpy.ma as ma from botocore.exceptions import ClientError -from netCDF4 import Dataset from osgeo import osr, gdal, gdalconst from osgeo.gdal_array import BandReadAsArray, CopyDatasetInfo, BandWriteArray +from netCDF4 import Dataset from Utilities import pathLocator from Utilities.AsyncRun import AsyncRun diff --git a/StatInterface/generateStats.py b/StatInterface/generateStats.py index fa7de694..cbcc8572 100644 --- a/StatInterface/generateStats.py +++ b/StatInterface/generateStats.py @@ -16,6 +16,7 @@ import sys import numpy as np +import statsmodels.api as sm import Utilities.stats as stats @@ -32,12 +33,9 @@ def acf(p, nlags=1): :type p: 1-d :class:`numpy.ndarray` """ - ar = np.array([1]+[np.corrcoef(p[:-i], p[i:])[0,1] for i in range(1, nlags + 1)]) - #ar = np.correlate(p, p, 'full') - #n = len(p) - ## Grab only the lag-one autocorrelation coeff. - #ar = ar[n-1:(n+nlags)]/ar.max() - return ar + + acorr = sm.tsa.acf(p, nlags=nlags) + return acorr class parameters(object): @@ -242,11 +240,6 @@ def calculate(self, cellNum, onLand): sig = np.std(p) # Calculate the autocorrelations: - alphas = np.correlate(p, p, 'full') - n = len(p) - - # Grab only the lag-one autocorrelation coeff. - alpha = alphas[n]/alphas.max() alpha = acf(p)[-1] phi = np.sqrt(1 - alpha**2) mn = min(p) diff --git a/TrackGenerator/TrackGenerator.py b/TrackGenerator/TrackGenerator.py index 87af2e82..66156740 100644 --- a/TrackGenerator/TrackGenerator.py +++ b/TrackGenerator/TrackGenerator.py @@ -418,7 +418,7 @@ def init(filename, angular=False): log.debug('Loading cell statistics for speed from netcdf file') self.vStats = init('all_speed') - self.vStats.load(pjoin(self.processPath, 'speed_stats.nc')) + self.vStats.load(pjoin(self.processPath, 'speed_rate_stats.nc')) log.debug('Loading cell statistics for pressure from netcdf file') self.pStats = init('all_pressure') @@ -426,7 +426,7 @@ def init(filename, angular=False): log.debug('Loading cell statistics for bearing from netcdf file') self.bStats = init('all_bearing', angular=True) - self.bStats.load(pjoin(self.processPath, 'bearing_stats.nc')) + self.bStats.load(pjoin(self.processPath, 'bearing_rate_stats.nc')) log.debug('Loading cell statistics for pressure_rate from netcdf file') self.dpStats = init('pressure_rate') @@ -1141,7 +1141,7 @@ def _stepBearing(self, c, i, onLand): if i == 1: self.theta += math.degrees(sigma[c] * self.bChi) else: - self.theta = math.degrees(mu[c] + sigma[c] * self.bChi) + self.theta += math.degrees(mu[c] + sigma[c] * self.bChi) self.theta = np.mod(self.theta, 360.) @@ -1185,7 +1185,7 @@ def _stepSpeed(self, c, i, onLand): if i == 1: self.v += abs(sigma[c] * self.vChi) else: - self.v = abs(mu[c] + sigma[c] * self.vChi) + self.v += (mu[c] + sigma[c] * self.vChi) def _stepSizeChange(self, c, i, onLand): """ diff --git a/tcrmenv.yml b/tcrmenv.yml index f5e7d165..f42ac7c0 100644 --- a/tcrmenv.yml +++ b/tcrmenv.yml @@ -3,6 +3,8 @@ channels: - defaults - conda-forge dependencies: + - gdal + - libgdal - pip - numpy - scipy @@ -20,8 +22,6 @@ dependencies: - simplejson - sqlite - statsmodels - - libgdal - - gdal - configparser - cartopy>=0.18.0 - affine diff --git a/tests/test_SamplingOrigin.py b/tests/test_SamplingOrigin.py index 3b43d45c..6c62968d 100644 --- a/tests/test_SamplingOrigin.py +++ b/tests/test_SamplingOrigin.py @@ -28,7 +28,7 @@ import os, sys import pickle import unittest -from scipy import random +from numpy import random from tests import NumpyTestCase try: from . import pathLocate diff --git a/tests/test_SamplingParameters.py b/tests/test_SamplingParameters.py index a56d611f..12053a53 100644 --- a/tests/test_SamplingParameters.py +++ b/tests/test_SamplingParameters.py @@ -28,7 +28,7 @@ import os, sys import pickle import unittest -from scipy import random +from numpy import random from tests import NumpyTestCase try: from . import pathLocate diff --git a/tests/test_generateStats.py b/tests/test_generateStats.py index 2bf75bb9..84066448 100644 --- a/tests/test_generateStats.py +++ b/tests/test_generateStats.py @@ -75,13 +75,13 @@ def test_generateStats(self): self.numpyAssertAlmostEqual(coeffs_sig_sample, coeffs_sig_test) coeffs_alpha_sample = wP.coeffs.alpha[0:10] - coeffs_alpha_test = numpy.array([0.51818004, 0.55450803, 0.66634809, 0.61266186, 0.61266186, - 0.63192755, 0.70984709, 0.64836016, 0.69168147, 0.70101634]) + coeffs_alpha_test = numpy.array([0.51389973, 0.55107082, 0.65703153, 0.60663585, 0.60663585, + 0.62594031, 0.70423342, 0.64339172, 0.6870522 , 0.69641318]) self.numpyAssertAlmostEqual(coeffs_alpha_sample, coeffs_alpha_test) coeffs_phi_sample = wP.coeffs.phi[0:10] - coeffs_phi_test = numpy.array([0.85527156, 0.83217838, 0.74564081, 0.79034514, 0.79034514, - 0.77502747, 0.70435581, 0.76133376, 0.7222027 , 0.71314521]) + coeffs_phi_test = numpy.array([0.85785026, 0.83445848, 0.7538631 , 0.79497984, 0.79497984, + 0.77987097, 0.70996851, 0.76553713, 0.72660806, 0.71764105]) self.numpyAssertAlmostEqual(coeffs_phi_sample, coeffs_phi_test) coeffs_min_sample = wP.coeffs.min[0:20] diff --git a/tests/test_maps.py b/tests/test_maps.py index 38c04917..d12b2c09 100644 --- a/tests/test_maps.py +++ b/tests/test_maps.py @@ -55,8 +55,8 @@ def assertColorMapEqual(self, actual, expected): self.assertEqual(actual.N, expected.N) self.assertEqual(actual.name, expected.name) for k in list(actual._segmentdata.keys()): - self.numpyAssertAlmostEqual(actual._segmentdata[k], - expected._segmentdata[k]) + self.numpyAssertAlmostEqual(np.array(actual._segmentdata[k]), + np.array(expected._segmentdata[k])) def setUp(self): import seaborn as sns From e28b5fb548978f3deaa67e004aad2871bdb45cfb Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Mon, 6 Mar 2023 09:45:25 +1100 Subject: [PATCH 75/80] Update tcrm-pylint.yml --- .github/workflows/tcrm-pylint.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tcrm-pylint.yml b/.github/workflows/tcrm-pylint.yml index d4f1bb93..d6e3c8aa 100644 --- a/.github/workflows/tcrm-pylint.yml +++ b/.github/workflows/tcrm-pylint.yml @@ -11,13 +11,17 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up Python env + - name: Set up environment uses: conda-incubator/setup-miniconda@v2.0.0 with: + python-version: 3.7 + mamba-version: "*" + channels: conda-forge,defaults + channel-priority: true activate-environment: tcrm environment-file: tcrmenv.yml - python-version: 3.7 auto-activate-base: false + use-only-tar-bz2: true - name: Install dependencies run: | From 2c07290b16dbff416e1f58cb86b96c886c9d221b Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Fri, 21 Jul 2023 10:38:43 +1000 Subject: [PATCH 76/80] Change `Speed` to `WindSpeed` in track interpolation --- Evaluate/interpolateTracks.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Evaluate/interpolateTracks.py b/Evaluate/interpolateTracks.py index 2eed8f8e..eb3123db 100644 --- a/Evaluate/interpolateTracks.py +++ b/Evaluate/interpolateTracks.py @@ -73,7 +73,7 @@ def interpolate(track, delta, interpolation_type=None): track.Minute)] else: day_ = track.Datetime - + timestep = timedelta(delta/24.) try: time_ = np.array([d.toordinal() + (d.hour + d.minute/60.)/24.0 @@ -104,7 +104,7 @@ def interpolate(track, delta, interpolation_type=None): idx[0] = 1 # TODO: Possibly could change `np.mean(dt)` to `dt`? track.WindSpeed = maxWindSpeed(idx, np.mean(dt), track.Longitude, - track.Latitude, track.CentralPressure, + track.Latitude, track.CentralPressure, track.EnvPressure) # Find the indices of valid pressure observations: validIdx = np.where(track.CentralPressure < sys.maxsize)[0] @@ -182,7 +182,7 @@ def interpolate(track, delta, interpolation_type=None): kind='linear')(newtime[firsttime:lasttime]) _nwSpd = interp1d(timestep[validIdx], - track.Speed[validIdx], + track.WindSpeed[validIdx], kind='linear')(newtime[firsttime:lasttime]) npCentre[firsttime:lasttime] = _npCentre From 8793053b0378f0e14b507447a194f65cf0bd5cf6 Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Tue, 13 Feb 2024 08:47:02 +1100 Subject: [PATCH 77/80] Workflow fix 1 (#144) * Remove deprecated Basemap references * Update conda environment file * Update actions/checkout to v3, setup-miniconda to v2.2 * Leave shapely to dependencies * Remove cartopy version, bump python version * Stringify python version --- .github/workflows/tcrm-pylint.yml | 6 +- .github/workflows/tcrm-tests.yml | 9 +-- Evaluate/evaluate.py | 65 ++++++++--------- tcrmenv.yml | 4 +- tests/kde/kde2.py | 70 ------------------- tests/kde/kde3.py | 112 ------------------------------ 6 files changed, 38 insertions(+), 228 deletions(-) delete mode 100644 tests/kde/kde2.py delete mode 100644 tests/kde/kde3.py diff --git a/.github/workflows/tcrm-pylint.yml b/.github/workflows/tcrm-pylint.yml index d6e3c8aa..df7a2a9b 100644 --- a/.github/workflows/tcrm-pylint.yml +++ b/.github/workflows/tcrm-pylint.yml @@ -12,10 +12,10 @@ jobs: steps: - uses: actions/checkout@v2 - name: Set up environment - uses: conda-incubator/setup-miniconda@v2.0.0 + uses: conda-incubator/setup-miniconda@v2 with: python-version: 3.7 - mamba-version: "*" + miniforge-variant: Mambaforge channels: conda-forge,defaults channel-priority: true activate-environment: tcrm @@ -32,7 +32,7 @@ jobs: pylint --rcfile pylintrc --fail-under=7 `find -regextype egrep -regex '(.*.py)$'` | tee pylint.txt - name: Upload pylint.txt as artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: pylint report path: pylint.txt diff --git a/.github/workflows/tcrm-tests.yml b/.github/workflows/tcrm-tests.yml index 59f5b5d6..7de5953f 100644 --- a/.github/workflows/tcrm-tests.yml +++ b/.github/workflows/tcrm-tests.yml @@ -15,14 +15,14 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.7, 3.8, 3.9] + python-version: ['3.9', '3.10'] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up environment - uses: conda-incubator/setup-miniconda@v2.0.0 + uses: conda-incubator/setup-miniconda@v2 with: + miniforge-variant: MambaForge python-version: ${{ matrix.python-version }} - mamba-version: "*" channels: conda-forge,defaults channel-priority: true activate-environment: tcrm @@ -30,6 +30,7 @@ jobs: auto-activate-base: false use-only-tar-bz2: true + - name: Test with pytest env: PYTHONPATH: ~/tcrm;~/tcrm/Utilities diff --git a/Evaluate/evaluate.py b/Evaluate/evaluate.py index c306c8c1..dfa8f3e6 100644 --- a/Evaluate/evaluate.py +++ b/Evaluate/evaluate.py @@ -32,7 +32,8 @@ from matplotlib import pyplot, cm from matplotlib.dates import date2num -from mpl_toolkits.basemap import Basemap +from cartopy import crs as ccrs +from cartopy import feature as cfeature from scipy.stats import scoreatpercentile as percentile from datetime import datetime @@ -76,7 +77,7 @@ [ProcessMultipliers] MaxWorkingThreads = 4 ProcessMultiVersion = 2 -ProcessingSegmentSize = 256 +ProcessingSegmentSize = 256 WarpMemoryLimit = 500 [Logging] @@ -169,17 +170,6 @@ def plotDensity(x, y, data, llLon=None, llLat=None, urLon=None, urLat=None, else: urcrnrlat = y.max() - meridians = np.arange(dl * np.floor(llcrnrlon / dl), - dl * np.ceil(urcrnrlon / dl), dl) - parallels = np.arange(dl * np.floor(llcrnrlat / dl), - dl * np.ceil(urcrnrlat / dl), dl) - - m = Basemap(projection='cyl', - resolution=res, - llcrnrlon=llcrnrlon, - urcrnrlon=urcrnrlon, - llcrnrlat=llcrnrlat, - urcrnrlat=urcrnrlat) # Set the colour map: if hasattr(cm, cmap): @@ -187,29 +177,35 @@ def plotDensity(x, y, data, llLon=None, llLat=None, urLon=None, urLat=None, else: cmap = colours.colourMap(cmap, 'stretched') - if maskocean: - try: - from mpl_toolkits.basemap import maskoceans - except ImportError: - log.debug("Maskoceans module unavailable, skipping this command") - else: - datam = maskoceans(xx, yy, data, inlands=False) - m.pcolormesh(xx, yy, datam, edgecolors='None', - vmin=datarange[0], vmax=datarange[1], - cmap=cmap) - else: - m.pcolormesh(xx, yy, data, edgecolors='None', - vmin=datarange[0], vmax=datarange[1], - cmap=cmap) + ax = pyplot.axes(projection=ccrs.PlateCarree()) + pyplot.pcolormesh(xx, yy, data, edgecolors='None', + vmin=datarange[0], vmax=datarange[1], + cmap=cmap, transfom=ccrs.PlateCarree()) - m.drawcoastlines(linewidth=0.5) if maskland: - m.fillcontinents(color='white') + ax.add_feature(cfeature.LAND, zorder=100, edgecolor='k') + + if maskocean: + ax.add_feature(cfeature.OCEAN, zorder=100, edgecolor='k') + + ax.coastlines(linewidth=0.5) + gl = ax.gridlines(draw_labels=True, crs=ccrs.PlateCarree(), linewidth=0.2) + gl.top_labels = False + gl.right_labels = False + + ax.set_extent([llcrnrlon, urcrnrlon, + llcrnrlat, urcrnrlat]) + + cb = pyplot.colorbar(shrink=0.5, aspect=30, + orientation='horizontal', + extend='max', pad=0.1) + + if cb.orientation == 'horizontal': + for t in cb.ax.get_xticklabels(): + t.set_fontsize(8) - m.drawparallels(parallels, labels=[1, 0, 0, 0], - fontsize=7.5, linewidth=0.2) - m.drawmeridians(meridians, labels=[0, 0, 0, 1], - fontsize=7.5, linewidth=0.2) + if clabel: + cb.set_label(clabel) if ylab: pyplot.ylabel(ylab, fontsize=7.5) if xlab: @@ -217,9 +213,6 @@ def plotDensity(x, y, data, llLon=None, llLat=None, urLon=None, urLat=None, if title: pyplot.title(title) - pyplot.grid(True) - pyplot.tick_params(direction='out', right='off', top='off') - cb = pyplot.colorbar(shrink=0.5, aspect=30, orientation='horizontal', extend='max', pad=0.1) diff --git a/tcrmenv.yml b/tcrmenv.yml index f42ac7c0..142647d7 100644 --- a/tcrmenv.yml +++ b/tcrmenv.yml @@ -9,8 +9,6 @@ dependencies: - numpy - scipy - matplotlib - - basemap - - shapely - nose - netcdf4 - cftime @@ -23,7 +21,7 @@ dependencies: - sqlite - statsmodels - configparser - - cartopy>=0.18.0 + - cartopy - affine - tqdm - xarray diff --git a/tests/kde/kde2.py b/tests/kde/kde2.py deleted file mode 100644 index 58f55102..00000000 --- a/tests/kde/kde2.py +++ /dev/null @@ -1,70 +0,0 @@ -import pickle -import os -import numpy as np -import matplotlib.pyplot as plt -from scipy import stats -from mpl_toolkits.basemap import Basemap -import Utilities.KPDF as KPDF - -ff = os.path.join('..', 'test_data', 'kde_origin_lonLat.pkl') -lonlat = pickle.load(open(ff, 'rb')) - -xmin = 70.0 -xmax = 180.0 -ymin = -36. -ymax = 0. - -fig, axes = plt.subplots(nrows=2, ncols=1) - -for ax in axes.flat: - m = Basemap(ax=ax, projection='cyl', resolution='i', llcrnrlon=xmin, - urcrnrlon=xmax, llcrnrlat=ymin, urcrnrlat=ymax) - m.drawcoastlines() - m.drawcountries() - m.drawmapboundary(fill_color='#ffffff') - m.fillcontinents(color='#dedcd2', lake_color='#ffffff') - meridians = np.arange(xmin, xmax, 10.) - parallels = np.arange(ymin, ymax, 10.) - m.drawparallels(parallels, labels=[1, 0, 0, 0], fontsize=9, linewidth=0.2) - m.drawmeridians(meridians, labels=[0, 0, 0, 1], fontsize=9, linewidth=0.2) - ax.grid(True) - -xy = lonlat -n, d = xy.shape -bw_kpdf = KPDF.MPDFOptimumBandwidth(xy) -bw_scott = n ** (-1.0 / (d + 4)) -bw_silverman = (n * (d + 2) / 4.) ** (-1. / (d + 4)) - -X, Y = np.mgrid[xmin:xmax:30j, ymin:ymax:30j] -positions = np.vstack([X.ravel(), Y.ravel()]).T - -# KDE - -z = KPDF.MPDFGaussian(xy, positions, bw_kpdf) -z = np.reshape(z, X.shape) -print(('z max = %f' % z.max())) - -ax = axes[0] -ax.imshow(np.rot90(z) / z.max(), cmap=plt.cm.PuRd, - extent=[xmin, xmax, ymin, ymax]) -ax.scatter(lonlat[:, 0], lonlat[:, 1], s=2, marker='o', - facecolor='darkred', edgecolor='darkred', alpha=0.8) -ax.contour(X, Y, np.rot90(z) / z.max(), colour='magenta') -ax.set_title('KPDF') - -# Scipy - -kde = stats.gaussian_kde(lonlat.T) -Z = np.reshape(kde(positions.T), X.shape) -print(('Z max = %f' % Z.max())) - -ax = axes[1] -ax.imshow(np.rot90(Z) / Z.max(), cmap=plt.cm.PuRd, - extent=[xmin, xmax, ymin, ymax]) -ax.scatter(lonlat[:, 0], lonlat[:, 1], s=2, marker='o', - facecolor='darkred', edgecolor='darkred', alpha=0.8) -ax.contour(X, Y, np.rot90(Z) / Z.max(), colour='magenta') -ax.set_title('Scipy') - -plt.savefig('fig2.pdf') -plt.show() diff --git a/tests/kde/kde3.py b/tests/kde/kde3.py deleted file mode 100644 index a8dbae73..00000000 --- a/tests/kde/kde3.py +++ /dev/null @@ -1,112 +0,0 @@ -import numpy as np -import scipy.stats as stats -import matplotlib.pyplot as plt - -import Utilities.KPDF as KPDF -import statsmodels.nonparametric.kernel_density as smkde -import statsmodels.nonparametric.bandwidths as smbw - -import seaborn as sns -sns.set_context("paper") - -xrvs = [stats.norm(loc=1.0, scale=0.2), stats.norm(loc=3.0, scale=0.1)] -yrvs = [stats.norm(loc=0.0, scale=0.2), stats.norm(loc=1.0, scale=0.3)] - -N = 3000 -M = N / (len(xrvs) + len(yrvs)) - -rvs = [] -for xrv in xrvs: - for yrv in yrvs: - rvs.append(np.hstack([ - xrv.rvs(size=(M, 1)), - yrv.rvs(size=(M, 1))])) -rvs = np.vstack(rvs) - - -def pdf(x, y): - return (sum([rv.pdf(x) for rv in xrvs]) * - sum([rv.pdf(y) for rv in yrvs])) - - -def plot(ax, Z, title): - ax.scatter(rvs[:, 0], rvs[:, 1], alpha=0.3, s=1, color='black') - ax.imshow(Z, aspect=1, origin='lower', - cmap=sns.light_palette((210, 90, 60), - input='husl', - as_cmap=True), - extent=(rvs[:, 0].min(), - rvs[:, 0].max(), - rvs[:, 1].min(), - rvs[:, 1].max())) - ax.contour(x, y, Z, linewidth=1) - ax.set_title(title) - -x_flat = np.r_[rvs[:, 0].min():rvs[:, 0].max():128j] #pylint: disable=E1127 -y_flat = np.r_[rvs[:, 1].min():rvs[:, 1].max():128j] #pylint: disable=E1127 -x, y = np.meshgrid(x_flat, y_flat) -grid = np.hstack([x.reshape(-1, 1), y.reshape(-1, 1)]) - -n, d = rvs.shape -bw_kpdf = KPDF.MPDFOptimumBandwidth(rvs) -bw_scott = n ** (-1.0 / (d + 4)) -bw_silverman = (n * (d + 2) / 4.) ** (-1. / (d + 4)) - -scale = np.array([[x_flat.ptp(), y_flat.ptp()]]) -print(('scale: %s' % scale)) - -print(('bandwidth kpdf=%f scott=%f silverman=%f' % - (bw_kpdf, bw_scott, bw_silverman))) - -fig, axes = plt.subplots(nrows=4, ncols=2, figsize=(8, 11)) -axes = axes.flat - -p = pdf(x, y) -p = p / p.max() -plot(axes[0], p, 'True density') - -kde = stats.kde.gaussian_kde(rvs.T) -print((kde.covariance)) -z = kde(grid.T) -z = z.reshape(x.shape) / z.max() -plot(axes[1], z, 'Scipy ($\ell_2$ norm: %.3f)' % np.linalg.norm((p - z).flat)) - -bw = bw_kpdf -# bw = bw_scott - -w = KPDF.MPDFGaussian(rvs, grid, bw_kpdf) -w = w.reshape(x.shape) / w.max() -plot(axes[2], w, 'KPDF bw:kpdf ($\ell_2$ norm: %.3f)' % np.linalg.norm((p - - w).flat)) - -w = KPDF.MPDFGaussian(rvs, grid, bw_kpdf / 2) -w = w.reshape(x.shape) / w.max() -plot(axes[3], w, 'KPDF bw:kpdf/2 ($\ell_2$ norm: %.3f)' % np.linalg.norm(( - p - w).flat)) - -w = KPDF.MPDFGaussian(rvs, grid, bw_scott) -w = w.reshape(x.shape) / w.max() -plot(axes[4], w, 'KPDF bw:scott ($\ell_2$ norm: %.3f)' % np.linalg.norm((p - - w).flat)) - -w = KPDF.MPDFGaussian(rvs, grid, bw_scott / 2) -w = w.reshape(x.shape) / w.max() -plot(axes[5], w, 'KPDF bw:scott/2 ($\ell_2$ norm: %.3f)' % np.linalg.norm(( - p - w).flat)) - -dens = smkde.KDEMultivariate(rvs, 'cc', bw='cv_ml') -print("SM bandwidth (cv_ml): " + repr(dens.bw)) -w = dens.pdf(grid) -w = w.reshape(x.shape) / w.max() -plot(axes[6], w, 'SM bw:CVML ($\ell_2$ norm: %.3f)' % np.linalg.norm((p - - w).flat)) - -dens = smkde.KDEMultivariate(rvs, 'cc', bw='cv_ls') -print("SM bandwidth (cv_ls): " + repr(dens.bw)) -w = dens.pdf(grid) -w = w.reshape(x.shape) / w.max() -plot(axes[7], w, 'SM bw:CVLS ($\ell_2$ norm: %.3f)' % np.linalg.norm((p - - w).flat)) - -plt.savefig('fig3.png') -plt.show() From 7d1a1447e0ff67f6d5bc3387ec87315b358b74f8 Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Tue, 13 Feb 2024 08:53:11 +1100 Subject: [PATCH 78/80] Bump python version for pylint testing --- .github/workflows/tcrm-pylint.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tcrm-pylint.yml b/.github/workflows/tcrm-pylint.yml index df7a2a9b..fa287b87 100644 --- a/.github/workflows/tcrm-pylint.yml +++ b/.github/workflows/tcrm-pylint.yml @@ -1,6 +1,6 @@ -name: Pylint tests for TCRM +name: Pylint tests for TCRM -on: +on: push: branches: [ master, develop ] @@ -13,8 +13,8 @@ jobs: - uses: actions/checkout@v2 - name: Set up environment uses: conda-incubator/setup-miniconda@v2 - with: - python-version: 3.7 + with: + python-version: 3.9 miniforge-variant: Mambaforge channels: conda-forge,defaults channel-priority: true @@ -22,7 +22,7 @@ jobs: environment-file: tcrmenv.yml auto-activate-base: false use-only-tar-bz2: true - + - name: Install dependencies run: | python -m pip install --upgrade pip From 865421b61431ad257d7a5435b5cb130101a6e31d Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Tue, 13 Feb 2024 09:36:40 +1100 Subject: [PATCH 79/80] Basemap deprecation (#142) * Remove deprecated Basemap references From fb357e82cdd6adae526f959fa261fab45ec7effe Mon Sep 17 00:00:00 2001 From: Craig Arthur Date: Wed, 14 Feb 2024 11:32:47 +1100 Subject: [PATCH 80/80] #145 replace shapefile with pyshp module (#146) --- Utilities/shapefile.py | 1186 --------------------------------------- Utilities/shptools.py | 197 ++++--- Utilities/tracks2shp.py | 230 +++++--- 3 files changed, 273 insertions(+), 1340 deletions(-) delete mode 100755 Utilities/shapefile.py diff --git a/Utilities/shapefile.py b/Utilities/shapefile.py deleted file mode 100755 index 9fed7a9b..00000000 --- a/Utilities/shapefile.py +++ /dev/null @@ -1,1186 +0,0 @@ -""" -shapefile.py -Provides read and write support for ESRI Shapefiles. -author: jlawheadgeospatialpython.com -date: 20130727 -version: 1.2.0 -Compatible with Python versions 2.4-3.x -""" - -__version__ = "1.2.0" - -from struct import pack, unpack, calcsize, error -import os -import sys -import time -import array -import tempfile - -# -# Constants for shape types -NULL = 0 -POINT = 1 -POLYLINE = 3 -POLYGON = 5 -MULTIPOINT = 8 -POINTZ = 11 -POLYLINEZ = 13 -POLYGONZ = 15 -MULTIPOINTZ = 18 -POINTM = 21 -POLYLINEM = 23 -POLYGONM = 25 -MULTIPOINTM = 28 -MULTIPATCH = 31 - -PYTHON3 = sys.version_info[0] == 3 - -if PYTHON3: - xrange = range - -def b(v): - if PYTHON3: - if isinstance(v, str): - # For python 3 encode str to bytes. - return v.encode('utf-8') - elif isinstance(v, bytes): - # Already bytes. - return v - else: - # Error. - raise Exception('Unknown input type') - else: - # For python 2 assume str passed in and return str. - return v - -def u(v): - if PYTHON3: - if isinstance(v, bytes): - # For python 3 decode bytes to str. - return v.decode('utf-8') - elif isinstance(v, str): - # Already str. - return v - else: - # Error. - raise Exception('Unknown input type') - else: - # For python 2 assume str passed in and return str. - return v - -def is_string(v): - if PYTHON3: - return isinstance(v, str) - else: - return isinstance(v, str) - -class _Array(array.array): - """Converts python tuples to lits of the appropritate type. - Used to unpack different shapefile header parts.""" - def __repr__(self): - return str(self.tolist()) - -def signed_area(coords): - """Return the signed area enclosed by a ring using the linear time - algorithm at http://www.cgafaq.info/wiki/Polygon_Area. A value >= 0 - indicates a counter-clockwise oriented ring. - """ - xs, ys = list(map(list, list(zip(*coords)))) - xs.append(xs[1]) - ys.append(ys[1]) - return sum(xs[i]*(ys[i+1]-ys[i-1]) for i in range(1, len(coords)))/2.0 - -class _Shape: - def __init__(self, shapeType=None): - """Stores the geometry of the different shape types - specified in the Shapefile spec. Shape types are - usually point, polyline, or polygons. Every shape type - except the "Null" type contains points at some level for - example verticies in a polygon. If a shape type has - multiple shapes containing points within a single - geometry record then those shapes are called parts. Parts - are designated by their starting index in geometry record's - list of shapes.""" - self.shapeType = shapeType - self.points = [] - - @property - def __geo_interface__(self): - if self.shapeType in [POINT, POINTM, POINTZ]: - return { - 'type': 'Point', - 'coordinates': tuple(self.points[0]) - } - elif self.shapeType in [MULTIPOINT, MULTIPOINTM, MULTIPOINTZ]: - return { - 'type': 'MultiPoint', - 'coordinates': tuple([tuple(p) for p in self.points]) - } - elif self.shapeType in [POLYLINE, POLYLINEM, POLYLINEZ]: - if len(self.parts) == 1: - return { - 'type': 'LineString', - 'coordinates': tuple([tuple(p) for p in self.points]) - } - else: - ps = None - coordinates = [] - for part in self.parts: - if ps == None: - ps = part - continue - else: - coordinates.append(tuple([tuple(p) for p in self.points[ps:part]])) - ps = part - else: - coordinates.append(tuple([tuple(p) for p in self.points[part:]])) - return { - 'type': 'MultiLineString', - 'coordinates': tuple(coordinates) - } - elif self.shapeType in [POLYGON, POLYGONM, POLYGONZ]: - if len(self.parts) == 1: - return { - 'type': 'Polygon', - 'coordinates': (tuple([tuple(p) for p in self.points]),) - } - else: - ps = None - coordinates = [] - for part in self.parts: - if ps == None: - ps = part - continue - else: - coordinates.append(tuple([tuple(p) for p in self.points[ps:part]])) - ps = part - else: - coordinates.append(tuple([tuple(p) for p in self.points[part:]])) - polys = [] - poly = [coordinates[0]] - for coord in coordinates[1:]: - if signed_area(coord) < 0: - polys.append(poly) - poly = [coord] - else: - poly.append(coord) - polys.append(poly) - if len(polys) == 1: - return { - 'type': 'Polygon', - 'coordinates': tuple(polys[0]) - } - elif len(polys) > 1: - return { - 'type': 'MultiPolygon', - 'coordinates': polys - } - -class _ShapeRecord: - """A shape object of any type.""" - def __init__(self, shape=None, record=None): - self.shape = shape - self.record = record - -class ShapefileException(Exception): - """An exception to handle shapefile specific problems.""" - pass - -class Reader: - """Reads the three files of a shapefile as a unit or - separately. If one of the three files (.shp, .shx, - .dbf) is missing no exception is thrown until you try - to call a method that depends on that particular file. - The .shx index file is used if available for efficiency - but is not required to read the geometry from the .shp - file. The "shapefile" argument in the constructor is the - name of the file you want to open. - - You can instantiate a Reader without specifying a shapefile - and then specify one later with the load() method. - - Only the shapefile headers are read upon loading. Content - within each file is only accessed when required and as - efficiently as possible. Shapefiles are usually not large - but they can be. - """ - def __init__(self, *args, **kwargs): - self.shp = None - self.shx = None - self.dbf = None - self.shapeName = "Not specified" - self._offsets = [] - self.shpLength = None - self.numRecords = None - self.fields = [] - self.__dbfHdrLength = 0 - # See if a shapefile name was passed as an argument - if len(args) > 0: - if is_string(args[0]): - self.load(args[0]) - return - if "shp" in list(kwargs.keys()): - if hasattr(kwargs["shp"], "read"): - self.shp = kwargs["shp"] - if hasattr(self.shp, "seek"): - self.shp.seek(0) - if "shx" in list(kwargs.keys()): - if hasattr(kwargs["shx"], "read"): - self.shx = kwargs["shx"] - if hasattr(self.shx, "seek"): - self.shx.seek(0) - if "dbf" in list(kwargs.keys()): - if hasattr(kwargs["dbf"], "read"): - self.dbf = kwargs["dbf"] - if hasattr(self.dbf, "seek"): - self.dbf.seek(0) - if self.shp or self.dbf: - self.load() - else: - raise ShapefileException("Shapefile Reader requires a shapefile or file-like object.") - - def load(self, shapefile=None): - """Opens a shapefile from a filename or file-like - object. Normally this method would be called by the - constructor with the file object or file name as an - argument.""" - if shapefile: - (shapeName, ext) = os.path.splitext(shapefile) - self.shapeName = shapeName - try: - self.shp = open("%s.shp" % shapeName, "rb") - except IOError: - raise ShapefileException("Unable to open %s.shp" % shapeName) - try: - self.shx = open("%s.shx" % shapeName, "rb") - except IOError: - raise ShapefileException("Unable to open %s.shx" % shapeName) - try: - self.dbf = open("%s.dbf" % shapeName, "rb") - except IOError: - raise ShapefileException("Unable to open %s.dbf" % shapeName) - if self.shp: - self.__shpHeader() - if self.dbf: - self.__dbfHeader() - - def __getFileObj(self, f): - """Checks to see if the requested shapefile file object is - available. If not a ShapefileException is raised.""" - if not f: - raise ShapefileException("Shapefile Reader requires a shapefile or file-like object.") - if self.shp and self.shpLength is None: - self.load() - if self.dbf and len(self.fields) == 0: - self.load() - return f - - def __restrictIndex(self, i): - """Provides list-like handling of a record index with a clearer - error message if the index is out of bounds.""" - if self.numRecords: - rmax = self.numRecords - 1 - if abs(i) > rmax: - raise IndexError("Shape or Record index out of range.") - if i < 0: - i = list(range(self.numRecords))[i] - return i - - def __shpHeader(self): - """Reads the header information from a .shp or .shx file.""" - if not self.shp: - raise ShapefileException("Shapefile Reader requires a shapefile or file-like object. (no shp file found") - shp = self.shp - # File length (16-bit word * 2 = bytes) - shp.seek(24) - self.shpLength = unpack(">i", shp.read(4))[0] * 2 - # Shape type - shp.seek(32) - self.shapeType = unpack("2i", f.read(8)) - # Determine the start of the next record - next = f.tell() + (2 * recLength) - shapeType = unpack(" -10e38: - record.m.append(m) - else: - record.m.append(None) - # Read a single point - if shapeType in (1, 11, 21): - record.points = [_Array('d', unpack("<2d", f.read(16)))] - # Read a single Z value - if shapeType == 11: - record.z = unpack("i", shx.read(4))[0] * 2) - 100 - numRecords = shxRecordLength // 8 - # Jump to the first record. - shx.seek(100) - for r in range(numRecords): - # Offsets are 16-bit words just like the file length - self._offsets.append(unpack(">i", shx.read(4))[0] * 2) - shx.seek(shx.tell() + 4) - if not i == None: - return self._offsets[i] - - def shape(self, i=0): - """Returns a shape object for a shape in the the geometry - record file.""" - shp = self.__getFileObj(self.shp) - i = self.__restrictIndex(i) - offset = self.__shapeIndex(i) - if not offset: - # Shx index not available so iterate the full list. - for j, k in enumerate(self.iterShapes()): - if j == i: - return k - shp.seek(offset) - return self.__shape() - - def shapes(self): - """Returns all shapes in a shapefile.""" - shp = self.__getFileObj(self.shp) - # Found shapefiles which report incorrect - # shp file length in the header. Can't trust - # that so we seek to the end of the file - # and figure it out. - shp.seek(0, 2) - self.shpLength = shp.tell() - shp.seek(100) - shapes = [] - while shp.tell() < self.shpLength: - shapes.append(self.__shape()) - return shapes - - def iterShapes(self): - """Serves up shapes in a shapefile as an iterator. Useful - for handling large shapefiles.""" - shp = self.__getFileObj(self.shp) - shp.seek(0,2) - self.shpLength = shp.tell() - shp.seek(100) - while shp.tell() < self.shpLength: - yield self.__shape() - - def __dbfHeaderLength(self): - """Retrieves the header length of a dbf file header.""" - if not self.__dbfHdrLength: - if not self.dbf: - raise ShapefileException("Shapefile Reader requires a shapefile or file-like object. (no dbf file found)") - dbf = self.dbf - (self.numRecords, self.__dbfHdrLength) = \ - unpack("6i", 9994, 0, 0, 0, 0, 0)) - # File length (Bytes / 2 = 16-bit words) - if headerType == 'shp': - f.write(pack(">i", self.__shpFileLength())) - elif headerType == 'shx': - f.write(pack('>i', ((100 + (len(self._shapes) * 8)) // 2))) - # Version, Shape type - f.write(pack("<2i", 1000, self.shapeType)) - # The shapefile's bounding box (lower left, upper right) - if self.shapeType != 0: - try: - f.write(pack("<4d", *self.bbox())) - except error: - raise ShapefileException("Failed to write shapefile bounding box. Floats required.") - else: - f.write(pack("<4d", 0, 0, 0, 0)) - # Elevation - z = self.zbox() - # Measure - m = self.mbox() - try: - f.write(pack("<4d", z[0], z[1], m[0], m[1])) - except error: - raise ShapefileException("Failed to write shapefile elevation and measure values. Floats required.") - - def __dbfHeader(self): - """Writes the dbf header and field descriptors.""" - f = self.__getFileObj(self.dbf) - f.seek(0) - version = 3 - year, month, day = time.localtime()[:3] - year -= 1900 - # Remove deletion flag placeholder from fields - for field in self.fields: - if field[0].startswith("Deletion"): - self.fields.remove(field) - numRecs = len(self.records) - numFields = len(self.fields) - headerLength = numFields * 32 + 33 - recordLength = sum([int(field[2]) for field in self.fields]) + 1 - header = pack('2i", recNum, 0)) - recNum += 1 - start = f.tell() - # Shape Type - if self.shapeType != 31: - s.shapeType = self.shapeType - f.write(pack("i", length)) - f.seek(finish) - - def __shxRecords(self): - """Writes the shx records.""" - f = self.__getFileObj(self.shx) - f.seek(100) - for i in range(len(self._shapes)): - f.write(pack(">i", self._offsets[i] // 2)) - f.write(pack(">i", self._lengths[i])) - - def __dbfRecords(self): - """Writes the dbf records.""" - f = self.__getFileObj(self.dbf) - for record in self.records: - if not self.fields[0][0].startswith("Deletion"): - f.write(b(' ')) # deletion flag - for (fieldName, fieldType, size, dec), value in zip(self.fields, record): - fieldType = fieldType.upper() - size = int(size) - if fieldType.upper() == "N": - #value = str(value).rjust(size) - value = "{0:{1}.{2}f}".format(value, size, dec).rjust(size) - elif fieldType == 'L': - value = str(value)[0].upper() - else: - value = str(value)[:size].ljust(size) - try: - assert len(value) == size - except AssertionError: - raise AssertionError("Length of value exceeds the size allocated for the field") - value = b(value) - f.write(value) - - def null(self): - """Creates a null shape.""" - self._shapes.append(_Shape(NULL)) - - def point(self, x, y, z=0, m=0): - """Creates a point shape.""" - pointShape = _Shape(self.shapeType) - pointShape.points.append([x, y, z, m]) - self._shapes.append(pointShape) - - def line(self, parts=[], shapeType=POLYLINE): - """Creates a line shape. This method is just a convienience method - which wraps 'poly()'. - """ - self.poly(parts, shapeType, []) - - def poly(self, parts=[], shapeType=POLYGON, partTypes=[]): - """Creates a shape that has multiple collections of points (parts) - including lines, polygons, and even multipoint shapes. If no shape type - is specified it defaults to 'polygon'. If no part types are specified - (which they normally won't be) then all parts default to the shape type. - """ - polyShape = _Shape(shapeType) - polyShape.parts = [] - polyShape.points = [] - # Make sure polygons are closed - if shapeType in (5, 15, 25, 31): - for part in parts: - if part[0] != part[-1]: - part.append(part[0]) - for part in parts: - polyShape.parts.append(len(polyShape.points)) - for point in part: - # Ensure point is list - if not isinstance(point, list): - point = list(point) - # Make sure point has z and m values - while len(point) < 4: - point.append(0) - polyShape.points.append(point) - if polyShape.shapeType == 31: - if not partTypes: - for part in parts: - partTypes.append(polyShape.shapeType) - polyShape.partTypes = partTypes - self._shapes.append(polyShape) - - def field(self, name, fieldType="C", size="50", decimal=0): - """Adds a dbf field descriptor to the shapefile.""" - self.fields.append((name, fieldType, size, decimal)) - - def record(self, *recordList, **recordDict): - """Creates a dbf attribute record. You can submit either a sequence of - field values or keyword arguments of field names and values. Before - adding records you must add fields for the record values using the - fields() method. If the record values exceed the number of fields the - extra ones won't be added. In the case of using keyword arguments to specify - field/value pairs only fields matching the already registered fields - will be added.""" - record = [] - fieldCount = len(self.fields) - # Compensate for deletion flag - if self.fields[0][0].startswith("Deletion"): - fieldCount -= 1 - if recordList: - [record.append(recordList[i]) for i in range(fieldCount)] - elif recordDict: - for field in self.fields: - if field[0] in recordDict: - val = recordDict[field[0]] - if val is None: - record.append("") - else: - record.append(val) - if record: - self.records.append(record) - - def shape(self, i): - return self._shapes[i] - - def shapes(self): - """Return the current list of shapes.""" - return self._shapes - - def saveShp(self, target): - """Save an shp file.""" - if not hasattr(target, "write"): - target = os.path.splitext(target)[0] + '.shp' - if not self.shapeType: - self.shapeType = self._shapes[0].shapeType - self.shp = self.__getFileObj(target) - self.__shapefileHeader(self.shp, headerType='shp') - self.__shpRecords() - - def saveShx(self, target): - """Save an shx file.""" - if not hasattr(target, "write"): - target = os.path.splitext(target)[0] + '.shx' - if not self.shapeType: - self.shapeType = self._shapes[0].shapeType - self.shx = self.__getFileObj(target) - self.__shapefileHeader(self.shx, headerType='shx') - self.__shxRecords() - - def saveDbf(self, target): - """Save a dbf file.""" - if not hasattr(target, "write"): - target = os.path.splitext(target)[0] + '.dbf' - self.dbf = self.__getFileObj(target) - self.__dbfHeader() - self.__dbfRecords() - - def save(self, target=None, shp=None, shx=None, dbf=None): - """Save the shapefile data to three files or - three file-like objects. SHP and DBF files can also - be written exclusively using saveShp, saveShx, and saveDbf respectively. - If target is specified but not shp,shx, or dbf then the target path and - file name are used. If no options or specified, a unique base file name - is generated to save the files and the base file name is returned as a - string. - """ - # Create a unique file name if one is not defined - if shp: - self.saveShp(shp) - if shx: - self.saveShx(shx) - if dbf: - self.saveDbf(dbf) - elif not shp and not shx and not dbf: - generated = False - if not target: - temp = tempfile.NamedTemporaryFile(prefix="shapefile_", dir=os.getcwd()) - target = temp.name - generated = True - self.saveShp(target) - self.shp.close() - self.saveShx(target) - self.shx.close() - self.saveDbf(target) - self.dbf.close() - if generated: - return target -class Editor(Writer): - def __init__(self, shapefile=None, shapeType=POINT, autoBalance=1): - self.autoBalance = autoBalance - if not shapefile: - Writer.__init__(self, shapeType) - elif is_string(shapefile): - base = os.path.splitext(shapefile)[0] - if os.path.isfile("%s.shp" % base): - r = Reader(base) - Writer.__init__(self, r.shapeType) - self._shapes = r.shapes() - self.fields = r.fields - self.records = r.records() - - def select(self, expr): - """Select one or more shapes (to be implemented)""" - # TODO: Implement expressions to select shapes. - pass - - def delete(self, shape=None, part=None, point=None): - """Deletes the specified part of any shape by specifying a shape - number, part number, or point number.""" - # shape, part, point - if shape and part and point: - del self._shapes[shape][part][point] - # shape, part - elif shape and part and not point: - del self._shapes[shape][part] - # shape - elif shape and not part and not point: - del self._shapes[shape] - # point - elif not shape and not part and point: - for s in self._shapes: - if s.shapeType == 1: - del self._shapes[point] - else: - for part in s.parts: - del s[part][point] - # part, point - elif not shape and part and point: - for s in self._shapes: - del s[part][point] - # part - elif not shape and part and not point: - for s in self._shapes: - del s[part] - - def point(self, x=None, y=None, z=None, m=None, shape=None, part=None, point=None, addr=None): - """Creates/updates a point shape. The arguments allows - you to update a specific point by shape, part, point of any - shape type.""" - # shape, part, point - if shape and part and point: - try: - self._shapes[shape] - except IndexError: - self._shapes.append([]) - try: - self._shapes[shape][part] - except IndexError: - self._shapes[shape].append([]) - try: - self._shapes[shape][part][point] - except IndexError: - self._shapes[shape][part].append([]) - p = self._shapes[shape][part][point] - if x: p[0] = x - if y: p[1] = y - if z: p[2] = z - if m: p[3] = m - self._shapes[shape][part][point] = p - # shape, part - elif shape and part and not point: - try: - self._shapes[shape] - except IndexError: - self._shapes.append([]) - try: - self._shapes[shape][part] - except IndexError: - self._shapes[shape].append([]) - points = self._shapes[shape][part] - for i in range(len(points)): - p = points[i] - if x: p[0] = x - if y: p[1] = y - if z: p[2] = z - if m: p[3] = m - self._shapes[shape][part][i] = p - # shape - elif shape and not part and not point: - try: - self._shapes[shape] - except IndexError: - self._shapes.append([]) - - # point - # part - if addr: - shape, part, point = addr - self._shapes[shape][part][point] = [x, y, z, m] - else: - Writer.point(self, x, y, z, m) - if self.autoBalance: - self.balance() - - def validate(self): - """An optional method to try and validate the shapefile - as much as possible before writing it (not implemented).""" - #TODO: Implement validation method - pass - - def balance(self): - """Adds a corresponding empty attribute or null geometry record depending - on which type of record was created to make sure all three files - are in synch.""" - if len(self.records) > len(self._shapes): - self.null() - elif len(self.records) < len(self._shapes): - self.record() - - def __fieldNorm(self, fieldName): - """Normalizes a dbf field name to fit within the spec and the - expectations of certain ESRI software.""" - if len(fieldName) > 11: fieldName = fieldName[:11] - fieldName = fieldName.upper() - fieldName.replace(' ', '_') - -# Begin Testing -def test(): - import doctest - doctest.NORMALIZE_WHITESPACE = 1 - doctest.testfile("README.txt", verbose=1) - -if __name__ == "__main__": - """ - Doctests are contained in the file 'README.txt'. This library was originally developed - using Python 2.3. Python 2.4 and above have some excellent improvements in the built-in - testing libraries but for now unit testing is done using what's available in - 2.3. - """ - test() diff --git a/Utilities/shptools.py b/Utilities/shptools.py index daa6054c..32c6bb0c 100644 --- a/Utilities/shptools.py +++ b/Utilities/shptools.py @@ -3,8 +3,8 @@ =============================================================== .. module: shptools - :synopsis: A collection of useful functions to manipulate shapefiles. Uses the `shapefile - library `_ + :synopsis: A collection of useful functions to manipulate shapefiles. + Uses the `pyshp library `_ @@ -12,7 +12,7 @@ import logging -from . import shapefile +import shapefile import numpy as np @@ -21,32 +21,55 @@ # For all observation points/line segments: -OBSFIELD_NAMES = ('Indicator', 'TCID', 'Year', 'Month', - 'Day', 'Hour', 'Minute', 'TimeElapsed', 'Longitude', - 'Latitude', 'Speed', 'Bearing', 'CentralPressure', - 'WindSpeed', 'rMax', 'EnvPressure') -OBSFIELD_TYPES = ('N',)*16 +OBSFIELD_NAMES = ( + "Indicator", + "TCID", + "Year", + "Month", + "Day", + "Hour", + "Minute", + "TimeElapsed", + "Longitude", + "Latitude", + "Speed", + "Bearing", + "CentralPressure", + "WindSpeed", + "rMax", + "EnvPressure", +) +OBSFIELD_TYPES = ("N",) * 16 OBSFIELD_WIDTH = (1, 6, 4, 2, 2, 2, 2, 6, 7, 7, 6, 6, 7, 6, 6, 7) -OBSFIELD_PREC = (0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 1, 1, 1, 1, 1, 1) +OBSFIELD_PREC = (0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 1, 1, 1, 1, 1, 1) -OBSFIELDS = [[n, t, w, p] for n, t, w, p in zip(OBSFIELD_NAMES, - OBSFIELD_TYPES, - OBSFIELD_WIDTH, - OBSFIELD_PREC)] +OBSFIELDS = [ + [n, t, w, p] + for n, t, w, p in zip(OBSFIELD_NAMES, OBSFIELD_TYPES, OBSFIELD_WIDTH, OBSFIELD_PREC) +] # For storing events as a single polyline: -EVENTFIELD_NAMES = ('TCID', 'Year', 'Month', 'Day', 'Hour', 'Minute', 'Age', - 'MinPressure', 'MaxWindSpeed' ) -EVENTFIELD_TYPES = ('N',)*9 +EVENTFIELD_NAMES = ( + "TCID", + "Year", + "Month", + "Day", + "Hour", + "Minute", + "Age", + "MinPressure", + "MaxWindSpeed", +) +EVENTFIELD_TYPES = ("N",) * 9 EVENTFIELD_WIDTH = (6, 4, 2, 2, 2, 2, 6, 7, 7) -EVENTFIELD_PREC = (0, 0, 0, 0, 0, 0, 2, 2, 1) - -EVENTFIELDS = [[n, t, w, p] for n, t, w, p in zip(EVENTFIELD_NAMES, - EVENTFIELD_TYPES, - EVENTFIELD_WIDTH, - EVENTFIELD_PREC)] - +EVENTFIELD_PREC = (0, 0, 0, 0, 0, 0, 2, 2, 1) +EVENTFIELDS = [ + [n, t, w, p] + for n, t, w, p in zip( + EVENTFIELD_NAMES, EVENTFIELD_TYPES, EVENTFIELD_WIDTH, EVENTFIELD_PREC + ) +] def parseData(data): @@ -66,23 +89,23 @@ def parseData(data): fields = [] records = [] for k in list(data.keys()): - t = data[k]['Type'] - l = data[k]['Length'] + t = data[k]["Type"] + l = data[k]["Length"] if t == 0: - nt = 'C' + nt = "C" p = 0 elif t == 1: - nt = 'N' + nt = "N" p = 0 elif t == 2: - nt = 'N' - p = data[k]['Precision'] + nt = "N" + p = data[k]["Precision"] else: nt = t p = 0 fields.append([k, nt, l, p]) - records.append([data[k]['Data']]) + records.append([data[k]["Data"]]) return fields, records @@ -114,7 +137,7 @@ def shpCreateFile(outputFile, shapes, data, shpType): fields, records = parseData(data) log.info("Writing data to {0}.shp".format(outputFile)) - w = shapefile.Writer(shpType) + w = shapefile.Writer(outputFile, shpType) # Set the fields: for f in fields: w.field(*f) @@ -125,7 +148,7 @@ def shpCreateFile(outputFile, shapes, data, shpType): start = 0 new_parts = [] for p in s.parts[1:]: - new_parts.append(list(s.points[start:p - 1])) + new_parts.append(list(s.points[start : p - 1])) start = p new_parts.append(list(s.points[p:-1])) w.poly(parts=new_parts) @@ -134,12 +157,13 @@ def shpCreateFile(outputFile, shapes, data, shpType): w.record(*r) try: - w.save(outputFile) + w.close() except shapefile.ShapefileException: - log.exception("Unable to save data to {0}".format(outputFile) ) + log.exception("Unable to save data to {0}".format(outputFile)) return + def shpSaveTrackFile(outputFile, tracks, fmt="point"): """ Save track data to shapefile. The fields are sorted using the same @@ -164,12 +188,15 @@ def shpSaveTrackFile(outputFile, tracks, fmt="point"): elif fmt == "segments": tracks2line(tracks, outputFile, dissolve=False) else: - log.critical(("Unknown output format - must be one of 'points', " - "'lines' or 'segments'")) + log.critical( + ( + "Unknown output format - must be one of 'points', " + "'lines' or 'segments'" + ) + ) return - def shpWriteShapeFile(outputFile, shpType, fields, shapes, records): """ Save data to a shapefile. The fields are sorted using the same @@ -191,7 +218,7 @@ def shpWriteShapeFile(outputFile, shpType, fields, shapes, records): """ log.info("Writing data to {0}.shp".format(outputFile)) - w = shapefile.Writer(shpType) + w = shapefile.Writer(outputFile, shpType) # Set the fields: for f in fields: @@ -203,7 +230,7 @@ def shpWriteShapeFile(outputFile, shpType, fields, shapes, records): start = 0 new_parts = [] for p in shp.parts[1:]: - new_parts.append(list(shp.points[start:p-1])) + new_parts.append(list(shp.points[start : p - 1])) start = p new_parts.append(list(shp.points[p:-1])) w.poly(parts=new_parts) @@ -212,12 +239,13 @@ def shpWriteShapeFile(outputFile, shpType, fields, shapes, records): w.record(*rec) try: - w.save(outputFile) + w.close() except shapefile.ShapefileException: - log.exception("Unable to save data to {0}".format(outputFile) ) + log.exception("Unable to save data to {0}".format(outputFile)) return + def shpGetVertices(shape_file, key_name=None): """ Returns a dictionary of arrays containing coordinate pairs @@ -262,7 +290,7 @@ def shpGetVertices(shape_file, key_name=None): raise else: - fields = sf.fields[1:] # Drop first field (DeletionFlag) + fields = sf.fields[1:] # Drop first field (DeletionFlag) field_names = [fields[i][0] for i in range(len(fields))] if key_name and (key_name in field_names): @@ -279,6 +307,7 @@ def shpGetVertices(shape_file, key_name=None): return vertices + def shpGetField(shape_file, field_name, dtype=float): """ Extract from the records the value of the field corresponding @@ -310,12 +339,11 @@ def shpGetField(shape_file, field_name, dtype=float): raise else: - fields = sf.fields[1:] # Drop first field (DeletionFlag) + fields = sf.fields[1:] # Drop first field (DeletionFlag) field_names = [fields[i][0] for i in range(len(fields))] if field_name not in field_names: - log.warning("No field '{0}' in the list of fieldnames" . - format(field_name)) + log.warning("No field '{0}' in the list of fieldnames".format(field_name)) log.warning("Unable to proceed with processing") raise ValueError @@ -327,16 +355,15 @@ def shpGetField(shape_file, field_name, dtype=float): if dtype != str: # For non-string data, return a numpy array: - output = np.array([records[rec][idx] for rec in range(nrecords)], - dtype=dtype) + output = np.array([records[rec][idx] for rec in range(nrecords)], dtype=dtype) else: # Otherwise, return a list: output = [records[rec][idx] for rec in range(nrecords)] - return output + def shpReadShapeFile(shape_file): """ Return the vertices and records for the given shape file @@ -351,7 +378,7 @@ def shpReadShapeFile(shape_file): """ try: - sf = shapefile.Reader(shape_file,"rb") + sf = shapefile.Reader(shape_file, "rb") except shapefile.ShapefileException: log.exception("Cannot read {0}".format(shape_file)) raise @@ -362,6 +389,7 @@ def shpReadShapeFile(shape_file): return vertices, records + def tracks2point(tracks, outputFile): """ Writes tracks to a shapefile as a collection of point features @@ -372,7 +400,7 @@ def tracks2point(tracks, outputFile): :param str outputFile: Path to output file destination """ - sf = shapefile.Writer(shapefile.POINT) + sf = shapefile.Writer(outputFile, shapefile.POINT) sf.fields = OBSFIELDS for track in tracks: @@ -381,12 +409,13 @@ def tracks2point(tracks, outputFile): sf.record(*rec) try: - sf.save(outputFile) + sf.close() except shapefile.ShapefileException: raise return + def tracks2line(tracks, outputFile, dissolve=False): """ Writes tracks to a shapefile as a collection of line features @@ -405,7 +434,7 @@ def tracks2line(tracks, outputFile, dissolve=False): :param dissolve: Store track features or track segments. """ - sf = shapefile.Writer(shapefile.POLYLINE) + sf = shapefile.Writer(outputFile, shapefile.POLYLINE) if dissolve: sf.fields = EVENTFIELDS else: @@ -420,12 +449,10 @@ def tracks2line(tracks, outputFile, dissolve=False): # into multiple parts: idx = np.argmin(dlon) parts = [] - lines = zip(track.Longitude[:idx], - track.Latitude[:idx]) + lines = zip(track.Longitude[:idx], track.Latitude[:idx]) parts.append(lines) - lines = zip(track.Longitude[idx+1:], - track.Latitude[idx+1:]) + lines = zip(track.Longitude[idx + 1 :], track.Latitude[idx + 1 :]) parts.append(lines) sf.line(parts) @@ -436,7 +463,6 @@ def tracks2line(tracks, outputFile, dissolve=False): lines = zip(track.Longitude, track.Latitude) sf.line([lines]) - minPressure = track.trackMinPressure maxWind = track.trackMaxWind @@ -447,39 +473,62 @@ def tracks2line(tracks, outputFile, dissolve=False): startDay = track.Day[0] startHour = track.Hour[0] startMin = track.Minute[0] - record = [track.CycloneNumber[0], startYear, startMonth, startDay, - startHour, startMin, age, minPressure, maxWind] + record = [ + track.CycloneNumber[0], + startYear, + startMonth, + startDay, + startHour, + startMin, + age, + minPressure, + maxWind, + ] sf.record(*record) else: if len(track.data) == 1: - line = [[[track.Longitude, track.Latitude], - [track.Longitude, track.Latitude]]] + line = [ + [ + [track.Longitude, track.Latitude], + [track.Longitude, track.Latitude], + ] + ] sf.line(line) sf.record(*track.data[0]) else: for n in range(len(track.data) - 1): dlon = track.Longitude[n + 1] - track.Longitude[n] - if dlon < -180.: + if dlon < -180.0: # case where the track crosses 0E: - segment = [[[track.Longitude[n], track.Latitude[n]], - [track.Longitude[n], track.Latitude[n]]]] + segment = [ + [ + [track.Longitude[n], track.Latitude[n]], + [track.Longitude[n], track.Latitude[n]], + ] + ] else: - segment = [[[track.Longitude[n], - track.Latitude[n]], - [track.Longitude[n + 1], - track.Latitude[n + 1]]]] + segment = [ + [ + [track.Longitude[n], track.Latitude[n]], + [track.Longitude[n + 1], track.Latitude[n + 1]], + ] + ] sf.line(segment) sf.record(*track.data[n]) # Last point in the track: - sf.line([[[track.Longitude[n + 1], - track.Latitude[n + 1]], - [track.Longitude[n + 1], - track.Latitude[n + 1]]]]) - sf.record(*track.data[n+1]) + sf.line( + [ + [ + [track.Longitude[n + 1], track.Latitude[n + 1]], + [track.Longitude[n + 1], track.Latitude[n + 1]], + ] + ] + ) + sf.record(*track.data[n + 1]) try: - sf.save(outputFile) + sf.close() except shapefile.ShapefileException: raise diff --git a/Utilities/tracks2shp.py b/Utilities/tracks2shp.py index c15e03d6..5770fa8e 100644 --- a/Utilities/tracks2shp.py +++ b/Utilities/tracks2shp.py @@ -10,7 +10,7 @@ """ -import Utilities.shapefile as shapefile +import shapefile import numpy as np import logging @@ -18,42 +18,82 @@ LOG = logging.getLogger(__name__) # For all observation points/line segments: -OBSFIELD_NAMES = ('Indicator', 'TCID', 'Year', 'Month', - 'Day', 'Hour', 'Minute', 'TElapsed', 'Longitude', - 'Latitude', 'Speed', 'Bearing', 'Pcentre', - 'MaxWind', 'rMax', 'Penv', 'Category') -OBSFIELD_TYPES = ('N',)*17 +OBSFIELD_NAMES = ( + "Indicator", + "TCID", + "Year", + "Month", + "Day", + "Hour", + "Minute", + "TElapsed", + "Longitude", + "Latitude", + "Speed", + "Bearing", + "Pcentre", + "MaxWind", + "rMax", + "Penv", + "Category", +) +OBSFIELD_TYPES = ("N",) * 17 OBSFIELD_WIDTH = (1, 6, 4, 2, 2, 2, 2, 6, 7, 7, 6, 6, 7, 6, 6, 7, 1) -OBSFIELD_PREC = (0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0) - -OBSFIELDS = [[n, t, w, p] for n, t, w, p in zip(OBSFIELD_NAMES, - OBSFIELD_TYPES, - OBSFIELD_WIDTH, - OBSFIELD_PREC)] - -TCRM_FIELD_NAMES = ('CycloneNumber', 'TimeElapsed', 'Longitude', 'Latitude', - 'Speed', 'Bearing', 'CentralPressure', 'EnvPressure', - 'rMax','Category') -TCRM_FIELD_TYPES = ('N',) * 10 +OBSFIELD_PREC = (0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0) + +OBSFIELDS = [ + [n, t, w, p] + for n, t, w, p in zip(OBSFIELD_NAMES, OBSFIELD_TYPES, + OBSFIELD_WIDTH, OBSFIELD_PREC) +] + +TCRM_FIELD_NAMES = ( + "CycloneNumber", + "TimeElapsed", + "Longitude", + "Latitude", + "Speed", + "Bearing", + "CentralPressure", + "EnvPressure", + "rMax", + "Category", +) +TCRM_FIELD_TYPES = ("N",) * 10 TCRM_FIELD_WIDTH = (6, 7, 9, 9, 8, 8, 8, 8, 8, 1) -TCRM_FIELD_PREC = (0, 2, 4, 4, 4, 4, 3, 3, 4, 0) +TCRM_FIELD_PREC = (0, 2, 4, 4, 4, 4, 3, 3, 4, 0) -TCRM_FIELDS = [[n, t, w, p] for n, t, w, p in zip(TCRM_FIELD_NAMES, - TCRM_FIELD_TYPES, - TCRM_FIELD_WIDTH, - TCRM_FIELD_PREC)] +TCRM_FIELDS = [ + [n, t, w, p] + for n, t, w, p in zip( + TCRM_FIELD_NAMES, TCRM_FIELD_TYPES, TCRM_FIELD_WIDTH, TCRM_FIELD_PREC + ) +] # For storing events as a single polyline: -EVENTFIELD_NAMES = ('TCID', 'Year', 'Month', 'Day', 'Hour', 'Minute', 'Age', - 'MinCP', 'MaxWind' ) -EVENTFIELD_TYPES = ('N',)*9 +EVENTFIELD_NAMES = ( + "TCID", + "Year", + "Month", + "Day", + "Hour", + "Minute", + "Age", + "MinCP", + "MaxWind", +) +EVENTFIELD_TYPES = ("N",) * 9 EVENTFIELD_WIDTH = (6, 4, 2, 2, 2, 2, 6, 7, 7) -EVENTFIELD_PREC = (0, 0, 0, 0, 0, 0, 2, 2, 1) +EVENTFIELD_PREC = (0, 0, 0, 0, 0, 0, 2, 2, 1) + +EVENTFIELDS = [ + [n, t, w, p] + for n, t, w, p in zip( + EVENTFIELD_NAMES, EVENTFIELD_TYPES, + EVENTFIELD_WIDTH, EVENTFIELD_PREC + ) +] -EVENTFIELDS = [[n, t, w, p] for n, t, w, p in zip(EVENTFIELD_NAMES, - EVENTFIELD_TYPES, - EVENTFIELD_WIDTH, - EVENTFIELD_PREC)] def recdropfields(rec, names): """ @@ -70,8 +110,9 @@ def recdropfields(rec, names): names = set(names) - newdtype = np.dtype([(name, rec.dtype[name]) for name in rec.dtype.names - if name not in names]) + newdtype = np.dtype( + [(name, rec.dtype[name]) for name in rec.dtype.names if name not in names] # noqa + ) newrec = np.recarray(rec.shape, dtype=newdtype) for field in newdtype.names: @@ -97,7 +138,7 @@ def add_category(tracks): Add a category field (for central pressure) to the tracks. """ for track in tracks: - track.data = add_field(track.data, [('Category', int)]) + track.data = add_field(track.data, [("Category", int)]) for rec in track.data: if rec["CentralPressure"] < 930: @@ -129,7 +170,7 @@ def tracks2point(tracks, outputFile, netcdf_format=False): """ LOG.info("Writing point shape file: {0}".format(outputFile)) - sf = shapefile.Writer(shapefile.POINT) + sf = shapefile.Writer(outputFile, shapefile.POINT) if netcdf_format: sf.fields = TCRM_FIELDS else: @@ -138,19 +179,20 @@ def tracks2point(tracks, outputFile, netcdf_format=False): LOG.debug("Processing {0} tracks".format(len(tracks))) for track in tracks: - track.data = recdropfields(track.data, ['Datetime']) + track.data = recdropfields(track.data, ["Datetime"]) for lon, lat, rec in zip(track.Longitude, track.Latitude, track.data): sf.point(lon, lat) sf.record(*rec) try: - sf.save(outputFile) + sf.close() except shapefile.ShapefileException: LOG.exception("Cannot save shape file: {0}".format(outputFile)) raise return + def tracks2line(tracks, outputFile, dissolve=False, netcdf_format=False): """ Writes tracks to a shapefile as a collection of line features @@ -174,7 +216,7 @@ def tracks2line(tracks, outputFile, dissolve=False, netcdf_format=False): when attempting to save the file. """ LOG.info("Writing line shape file: {0}".format(outputFile)) - sf = shapefile.Writer(shapefile.POLYLINE) + sf = shapefile.Writer(outputFile, shapefile.POLYLINE) if netcdf_format: sf.fields = TCRM_FIELDS elif dissolve: @@ -185,7 +227,7 @@ def tracks2line(tracks, outputFile, dissolve=False, netcdf_format=False): LOG.debug("Processing {0} tracks".format(len(tracks))) for track in tracks: - track.data = recdropfields(track.data, ['Datetime']) + track.data = recdropfields(track.data, ["Datetime"]) if dissolve: if len(track.data) > 1: @@ -195,12 +237,10 @@ def tracks2line(tracks, outputFile, dissolve=False, netcdf_format=False): # into multiple parts: idx = np.argmin(dlon) parts = [] - lines = zip(track.Longitude[:idx], - track.Latitude[:idx]) + lines = zip(track.Longitude[:idx], track.Latitude[:idx]) parts.append(lines) - lines = zip(track.Longitude[idx+1:], - track.Latitude[idx+1:]) + lines = zip(track.Longitude[idx + 1 :], track.Latitude[idx + 1 :]) # noqa parts.append(lines) sf.line(parts) @@ -211,7 +251,6 @@ def tracks2line(tracks, outputFile, dissolve=False, netcdf_format=False): lines = zip(track.Longitude, track.Latitude) sf.line([lines]) - if netcdf_format: sf.record(*track.data[0]) else: @@ -225,45 +264,69 @@ def tracks2line(tracks, outputFile, dissolve=False, netcdf_format=False): startDay = track.Day[0] startHour = track.Hour[0] startMin = track.Minute[0] - record = [track.CycloneNumber[0], startYear, startMonth, startDay, - startHour, startMin, age, minPressure, maxWind] + record = [ + track.CycloneNumber[0], + startYear, + startMonth, + startDay, + startHour, + startMin, + age, + minPressure, + maxWind, + ] sf.record(*record) else: if len(track.data) == 1: - line = [[[track.Longitude, track.Latitude], - [track.Longitude, track.Latitude]]] + line = [ + [ + [track.Longitude, track.Latitude], + [track.Longitude, track.Latitude], + ] + ] sf.line(line) sf.record(*track.data[0]) else: for n in range(len(track.data) - 1): dlon = track.Longitude[n + 1] - track.Longitude[n] - if dlon < -180.: + if dlon < -180.0: # case where the track crosses 0E: - segment = [[[track.Longitude[n], track.Latitude[n]], - [track.Longitude[n], track.Latitude[n]]]] + segment = [ + [ + [track.Longitude[n], track.Latitude[n]], + [track.Longitude[n], track.Latitude[n]], + ] + ] else: - segment = [[[track.Longitude[n], - track.Latitude[n]], - [track.Longitude[n + 1], - track.Latitude[n + 1]]]] + segment = [ + [ + [track.Longitude[n], track.Latitude[n]], + [track.Longitude[n + 1], track.Latitude[n + 1]], # noqa + ] + ] sf.line(segment) sf.record(*track.data[n]) # Last point in the track: - sf.line([[[track.Longitude[n + 1], - track.Latitude[n + 1]], - [track.Longitude[n + 1], - track.Latitude[n + 1]]]]) - sf.record(*track.data[n+1]) + sf.line( + [ + [ + [track.Longitude[n + 1], track.Latitude[n + 1]], + [track.Longitude[n + 1], track.Latitude[n + 1]], + ] + ] + ) + sf.record(*track.data[n + 1]) try: - sf.save(outputFile) + sf.close() except shapefile.ShapefileException: LOG.exception("Cannot save shape file: {0}".format(outputFile)) raise -if __name__ == '__main__': + +if __name__ == "__main__": from Utilities.loadData import loadTrackFile from Utilities.config import ConfigParser @@ -276,20 +339,24 @@ def tracks2line(tracks, outputFile, dissolve=False, netcdf_format=False): # pylint: disable=C0103 parser = argparse.ArgumentParser() - parser.add_argument('-c', '--config_file', help='Input configuration file') - parser.add_argument('-f', '--file', help='Input TC track file') - parser.add_argument('-s', '--source', - help='Input TC track file source format') - parser.add_argument('-v', '--verbose', - help='Print log messages to STDOUT', - action='store_true') + parser.add_argument("-c", "--config_file", + help="Input configuration file") + parser.add_argument("-f", "--file", + help="Input TC track file") + parser.add_argument("-s", "--source", + help="Input TC track file source format") + parser.add_argument( + "-v", "--verbose", + help="Print log messages to STDOUT", + action="store_true" + ) args = parser.parse_args() config_file = args.config_file config = ConfigParser() config.read(config_file) - logfile = config.get('Logging', 'LogFile') + logfile = config.get("Logging", "LogFile") logdir = dirname(realpath(logfile)) # If log file directory does not exist, create it @@ -297,11 +364,11 @@ def tracks2line(tracks, outputFile, dissolve=False, netcdf_format=False): try: os.makedirs(logdir) except OSError: - logfile = pjoin(os.getcwd(), 'tracks2shp.log') + logfile = pjoin(os.getcwd(), "tracks2shp.log") - logLevel = config.get('Logging', 'LogLevel') - verbose = config.getboolean('Logging', 'Verbose') - datestamp = config.getboolean('Logging', 'Datestamp') + logLevel = config.get("Logging", "LogLevel") + verbose = config.getboolean("Logging", "Verbose") + datestamp = config.getboolean("Logging", "Datestamp") if args.verbose: verbose = True @@ -311,22 +378,23 @@ def tracks2line(tracks, outputFile, dissolve=False, netcdf_format=False): if args.file: track_file = args.file else: - track_file = config.get('DataProcess', 'InputFile') + track_file = config.get("DataProcess", "InputFile") if args.source: source = args.source else: - source = config.get('DataProcess', 'Source') + source = config.get("DataProcess", "Source") output_path = dirname(realpath(track_file)) filename, ext = splitext(track_file) - pt_output_file = filename + '_pt.shp' - line_output_file = filename + '_line.shp' - dissolve_output_file = filename + '_dissolve.shp' + pt_output_file = filename + "_pt.shp" + line_output_file = filename + "_line.shp" + dissolve_output_file = filename + "_dissolve.shp" if track_file.endswith(".nc"): from Utilities.track import ncReadTrackData + tracks = ncReadTrackData(track_file) netcdf_format = True @@ -341,6 +409,8 @@ def tracks2line(tracks, outputFile, dissolve=False, netcdf_format=False): add_category(tracks) tracks2point(tracks, pt_output_file, netcdf_format=netcdf_format) tracks2line(tracks, line_output_file, netcdf_format=netcdf_format) - tracks2line(tracks, dissolve_output_file, dissolve=True, netcdf_format=netcdf_format) + tracks2line( + tracks, dissolve_output_file, + dissolve=True, netcdf_format=netcdf_format + ) LOG.info("Completed tracks2shp") -