diff --git a/.gitignore b/.gitignore index 868dff8..042b9bd 100644 --- a/.gitignore +++ b/.gitignore @@ -46,4 +46,10 @@ doc/build doc/source/internal_api/generated doc/source/user_api/generated test/ipynb/.ipynb_checkpoints +fortran/omp.f90 +fortran/wrffortran.pyf +fortran/*.mod +build +.settings +src/wrf_python.egg-info diff --git a/MANIFEST.in b/MANIFEST.in index d83672c..6654fc5 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,6 +4,8 @@ include requirements.txt include fortran/*.f90 include fortran/*.pyf +include build_scripts/*.sh +include build_scripts/*.bat include setup.py recursive-include doc/source * diff --git a/README.md b/README.md index 178fc8b..34ec870 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,25 @@ -# wrf-python +wrf-python +============== A collection of diagnostic and interpolation routines for use with output from the Weather Research and Forecasting (WRF-ARW) Model. This package provides over 30 diagnostic calculations, several interpolation routines, and utilities to help with plotting via cartopy, basemap, or PyNGL. The functionality is similar to what is provided by the NCL WRF package. -Installation via conda: +Installation +---------------------------- conda install -c conda-forge wrf-python -Documentation is available at: +Documentation +---------------------------------- http://wrf-python.rtfd.org + +Citation +------------------ + + Ladwig, W. (2017). wrf-python (Version x.x.x) [Software]. Boulder, Colorado: UCAR/NCAR. https://doi.org/10.5065/D6W094P1 + +Note: The version number x.x.x should be set to the version of wrf-python that you are using. diff --git a/build_scripts/gnu_no_omp.sh b/build_scripts/gnu_no_omp.sh new file mode 100755 index 0000000..b87b529 --- /dev/null +++ b/build_scripts/gnu_no_omp.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +cd ../fortran +gfortran -E ompgen.F90 -cpp -o omp.f90 +f2py *.f90 -m _wrffortran -h wrffortran.pyf --overwrite-signature +cd .. + +python setup.py clean --all +python setup.py config_fc --f90flags="-mtune=generic" build_ext build +pip install . diff --git a/build_scripts/gnu_omp.sh b/build_scripts/gnu_omp.sh new file mode 100755 index 0000000..09644ad --- /dev/null +++ b/build_scripts/gnu_omp.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +cd ../fortran +gfortran -E ompgen.F90 -fopenmp -cpp -o omp.f90 +f2py *.f90 -m _wrffortran -h wrffortran.pyf --overwrite-signature +cd .. + +python setup.py clean --all +python setup.py config_fc --f90flags="-mtune=generic -fopenmp" build_ext --libraries="gomp" build +pip install . + diff --git a/build_scripts/win_mingw_no_omp.bat b/build_scripts/win_mingw_no_omp.bat new file mode 100644 index 0000000..9e1223c --- /dev/null +++ b/build_scripts/win_mingw_no_omp.bat @@ -0,0 +1,15 @@ +cd ../fortran +CALL gfortran -E ompgen.F90 -cpp -o omp.f90 +REM Wildcards not working on Windows for some reason +CALL f2py -m _wrffortran -h wrffortran.pyf --overwrite-signature wrf_constants.f90 wrf_testfunc.f90 wrf_user.f90 rip_cape.f90 wrf_cloud_fracf.f90 wrf_fctt.f90 wrf_user_dbz.f90 wrf_relhl.f90 calc_uh.f90 wrf_user_latlon_routines.f90 wrf_pvo.f90 eqthecalc.f90 wrf_rip_phys_routines.f90 wrf_pw.f90 wrf_vinterp.f90 wrf_wind.f90 omp.f90 +cd .. + +CALL python setup.py clean --all + +IF %PROCESSOR_ARCHITECTURE% == AMD64 ( + CALL python setup.py config_fc --f90flags="-O2 -mtune=generic" build --compiler=mingw32 --fcompiler=gnu95 +) ELSE ( + CALL python setup.py config_fc --f90flags="-O2 -mtune=generic -mincoming-stack-boundary=2" build --compiler=mingw32 --fcompiler=gnu95 +) + +CALL pip install . diff --git a/build_scripts/win_mingw_omp.bat b/build_scripts/win_mingw_omp.bat new file mode 100644 index 0000000..b412e96 --- /dev/null +++ b/build_scripts/win_mingw_omp.bat @@ -0,0 +1,17 @@ +cd ../fortran +CALL gfortran -E ompgen.F90 -cpp -fopenmp -o omp.f90 +REM Wildcards not working on Windows for some reason +CALLf2py -m _wrffortran -h wrffortran.pyf --overwrite-signature wrf_constants.f90 wrf_testfunc.f90 wrf_user.f90 rip_cape.f90 wrf_cloud_fracf.f90 wrf_fctt.f90 wrf_user_dbz.f90 wrf_relhl.f90 calc_uh.f90 wrf_user_latlon_routines.f90 wrf_pvo.f90 eqthecalc.f90 wrf_rip_phys_routines.f90 wrf_pw.f90 wrf_vinterp.f90 wrf_wind.f90 omp.f90 +cd .. + +CALL python setup.py clean --all + +IF %PROCESSOR_ARCHITECTURE% == AMD64 ( + CALL python setup.py config_fc --f90flags="-O2 -mtune=generic -fopenmp" build_ext --libraries="gomp" build --compiler=mingw32 --fcompiler=gnu95 +) ELSE ( + CALL python setup.py config_fc --f90flags="-O2 -mtune=generic -fopenmp -mincoming-stack-boundary=2" build_ext --libraries="gomp" build --compiler=mingw32 --fcompiler=gnu95 +) + +CALL pip install . + + diff --git a/build_scripts/win_msvc_mingw_no_omp.bat b/build_scripts/win_msvc_mingw_no_omp.bat new file mode 100644 index 0000000..6fa132b --- /dev/null +++ b/build_scripts/win_msvc_mingw_no_omp.bat @@ -0,0 +1,14 @@ +cd ../fortran +CALL gfortran -E ompgen.F90 -cpp -o omp.f90 +REM Wildcards not working on Windows for some reason +CALL f2py -m _wrffortran -h wrffortran.pyf --overwrite-signature wrf_constants.f90 wrf_testfunc.f90 wrf_user.f90 rip_cape.f90 wrf_cloud_fracf.f90 wrf_fctt.f90 wrf_user_dbz.f90 wrf_relhl.f90 calc_uh.f90 wrf_user_latlon_routines.f90 wrf_pvo.f90 eqthecalc.f90 wrf_rip_phys_routines.f90 wrf_pw.f90 wrf_vinterp.f90 wrf_wind.f90 omp.f90 +cd .. + +CALL python setup.py clean --all + +IF %PROCESSOR_ARCHITECTURE% == AMD64 ( + CALL python setup.py config_fc --f90flags="-O2 -mtune=generic" build --compiler=mingw32 --fcompiler=gnu95 +) ELSE ( + CALL python setup.py config_fc --f90flags="-O2 -mtune=generic -mincoming-stack-boundary=2" build --compiler=msvc --fcompiler=gnu95 +) + CALL pip install . diff --git a/build_scripts/win_msvc_mingw_omp.bat b/build_scripts/win_msvc_mingw_omp.bat new file mode 100644 index 0000000..95a13a7 --- /dev/null +++ b/build_scripts/win_msvc_mingw_omp.bat @@ -0,0 +1,16 @@ +cd ../fortran +CALL gfortran -E ompgen.F90 -cpp -fopenmp -o omp.f90 +REM Wildcards not working on Windows for some reason +CALL f2py -m _wrffortran -h wrffortran.pyf --overwrite-signature wrf_constants.f90 wrf_testfunc.f90 wrf_user.f90 rip_cape.f90 wrf_cloud_fracf.f90 wrf_fctt.f90 wrf_user_dbz.f90 wrf_relhl.f90 calc_uh.f90 wrf_user_latlon_routines.f90 wrf_pvo.f90 eqthecalc.f90 wrf_rip_phys_routines.f90 wrf_pw.f90 wrf_vinterp.f90 wrf_wind.f90 omp.f90 +cd .. + +CALL python setup.py clean --all + +IF %PROCESSOR_ARCHITECTURE% == AMD64 ( + CALL python setup.py config_fc --f90flags="-O2 -mtune=generic -fopenmp" build_ext --libraries="gomp" build --compiler=msvc --fcompiler=gnu95 +) ELSE ( + CALL python setup.py config_fc --f90flags="-O2 -mtune=generic -fopenmp -mincoming-stack-boundary=2" build_ext --libraries="gomp" build --compiler=msvc --fcompiler=gnu95 +) + +CALL pip install . + diff --git a/conda_recipe/bld.bat b/conda_recipe/bld.bat index cedde2c..d1bb7f0 100644 --- a/conda_recipe/bld.bat +++ b/conda_recipe/bld.bat @@ -1,8 +1,15 @@ +cd ./fortran +CALL gfortran -E ompgen.F90 -cpp -fopenmp -o omp.f90 +REM Wildcards not working on Windows for some reason +CALL f2py -m _wrffortran -h wrffortran.pyf --overwrite-signature wrf_constants.f90 wrf_testfunc.f90 wrf_user.f90 rip_cape.f90 wrf_cloud_fracf.f90 wrf_fctt.f90 wrf_user_dbz.f90 wrf_relhl.f90 calc_uh.f90 wrf_user_latlon_routines.f90 wrf_pvo.f90 eqthecalc.f90 wrf_rip_phys_routines.f90 wrf_pw.f90 wrf_vinterp.f90 wrf_wind.f90 omp.f90 +cd .. -%ARCH% == 64 ( - %PYTHON% setup.py config_fc --f90flags="-O2 -mtune=generic -mfpmath=sse -msse2" build --compiler=mingw32 --fcompiler=gnu95 -) else ( - %PYTHON% setup.py config_fc --f90flags="-O2 -mtune=generic -mfpmath=sse -msse2 -mincoming-stack-boundary=2" build --compiler=mingw32 --fcompiler=gnu95 +IF %ARCH% == 64 ( + CALL %PYTHON% setup.py config_fc --f90flags="-O2 -mtune=generic -fopenmp" build_ext --libraries="gomp" build --compiler=msvc --fcompiler=gnu95 +) ELSE ( + CALL %PYTHON% setup.py config_fc --f90flags="-O2 -mtune=generic -fopenmp -mincoming-stack-boundary=2" build_ext --libraries="gomp" build --compiler=msvc --fcompiler=gnu95 ) -%PYTHON% setup.py install --single-version-externally-managed --record=record.txt + +CALL %PYTHON% setup.py install --single-version-externally-managed --record=record.txt + diff --git a/conda_recipe/build.sh b/conda_recipe/build.sh index 9ae7b95..ec01ebb 100644 --- a/conda_recipe/build.sh +++ b/conda_recipe/build.sh @@ -1,10 +1,16 @@ #!/bin/bash +cd ./fortran +gfortran -E ompgen.F90 -fopenmp -cpp -o omp.f90 +f2py *.f90 -m _wrffortran -h wrffortran.pyf --overwrite-signature +cd .. + if [ `uname` == Darwin ]; then LDFLAGS="$LDFLAGS -undefined dynamic_lookup -bundle" fi -$PYTHON setup.py config_fc --f90flags="-mtune=generic -mfpmath=sse" build +$PYTHON setup.py config_fc --f90flags="-mtune=generic -fopenmp" build_ext --libraries="gomp" build + $PYTHON setup.py install --single-version-externally-managed --record=record.txt diff --git a/conda_recipe/meta.yaml b/conda_recipe/meta.yaml index 7563c19..b912d3a 100644 --- a/conda_recipe/meta.yaml +++ b/conda_recipe/meta.yaml @@ -1,38 +1,40 @@ -{% set version = "1.0.4" %} +{% set version = "1.1.0" %} package: name: wrf-python version: {{ version }} source: - fn: wrf-python-{{ version }}.tar.gz - url: https://github.com/NCAR/wrf-python/archive/{{ version }}.tar.gz - sha256: 303da6607ac66f49b6d011c32c0bf3af9b5ea5cb6563448daafafb8ec9a078d1 + git_url: https://github.com/NCAR/wrf-python + git_branch: develop + build: number: 0 detect_binary_files_with_prefix: true - skip: True # [(win and py>=35)] requirements: build: - setuptools - - numpy x.x - - wrapt - - mingwpy # [win] - - gcc # [unix] - python + - numpy 1.11.* # [unix] + - numpy 1.14.* # [win] + - wrapt + - m2w64-toolchain # [win] + - gcc # [unix] run: - - numpy x.x + - setuptools + - numpy >=1.11 - python - wrapt - - mingwpy # [win] + - m2w64-gcc-libs # [win] - libgfortran # [unix] - libgcc # [unix] - xarray test: requires: + - setuptools - netcdf4 - xarray source_files: diff --git a/conda_recipe/meta.yaml.develop b/conda_recipe/meta.yaml.develop new file mode 100644 index 0000000..ee75ff1 --- /dev/null +++ b/conda_recipe/meta.yaml.develop @@ -0,0 +1,58 @@ +{% set version = "1.1.0" %} + +package: + name: wrf-python + version: {{ version }} + +source: + git_url: https://github.com/NCAR/wrf-python + git_branch: develop + + +build: + number: 0 + detect_binary_files_with_prefix: true + +requirements: + build: + - setuptools + - numpy 1.11.* # [unix] + - numpy 1.14.* # [win] + - wrapt + - m2w64-toolchain # [win] + - gcc # [unix] + - python + run: + - setuptools + - numpy >=1.11 + - python + - wrapt + - m2w64-gcc-libs # [win] + - libgfortran # [unix] + - libgcc # [unix] + - xarray + +test: + requires: + - setuptools + - netcdf4 + - xarray + source_files: + - test/ci_tests + imports: + - wrf + commands: + - cd test/ci_tests && python utests.py + +about: + home: https://github.com/NCAR/wrf-python + license: "UCAR" + license_file: '{{ environ["RECIPE_DIR"] }}/LICENSE' + summary: "Diagnostic and interpolation routines for WRF-ARW data." + +extra: + recipe-maintainers: + - bladwig1 + - marylhaley + - david-ian-brown + - khallock diff --git a/conda_recipe/meta.yaml.release b/conda_recipe/meta.yaml.release new file mode 100644 index 0000000..51c2ee0 --- /dev/null +++ b/conda_recipe/meta.yaml.release @@ -0,0 +1,58 @@ +{% set version = "1.1.0" %} + +package: + name: wrf-python + version: {{ version }} + +source: + fn: wrf-python-{{ version }}.tar.gz + url: https://github.com/NCAR/wrf-python/archive/{{ version }}.tar.gz + sha256: ea2202e1d8237c65b9d77a91f00da0f2e7a37ed6214ddd976872b619b9647b33 + +build: + number: 0 + detect_binary_files_with_prefix: true + +requirements: + build: + - setuptools + - numpy 1.11.* # [unix] + - numpy 1.14.* # [win] + - wrapt + - m2w64-toolchain # [win] + - gcc # [unix] + - python + run: + - setuptools + - numpy >=1.11 + - python + - wrapt + - m2w64-gcc-libs # [win] + - libgfortran # [unix] + - libgcc # [unix] + - xarray + +test: + requires: + - setuptools + - netcdf4 + - xarray + source_files: + - test/ci_tests + imports: + - wrf + commands: + - cd test/ci_tests && python utests.py + +about: + home: https://github.com/NCAR/wrf-python + license: "UCAR" + license_file: '{{ environ["RECIPE_DIR"] }}/LICENSE' + summary: "Diagnostic and interpolation routines for WRF-ARW data." + +extra: + recipe-maintainers: + - bladwig1 + - marylhaley + - david-ian-brown + - khallock diff --git a/doc/source/_templates/product_table.txt b/doc/source/_templates/product_table.txt index 8e92724..c49c3ac 100644 --- a/doc/source/_templates/product_table.txt +++ b/doc/source/_templates/product_table.txt @@ -1,240 +1,256 @@ -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| Variable Name | Description | Available Units | Additional Keyword Arguments | -+====================+===============================================================+=============================+===============================================================================================+ -| avo | Absolute Vorticity | 10-5 s-1 | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| eth/theta_e | Equivalent Potential Temperature | K | **units** (str) : Set to desired units. Default is *'K'*. | -| | | | | -| | | degC | | -| | | | | -| | | degF | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| cape_2d | 2D cape (mcape/mcin/lcl/lfc) | J kg-1 ; J kg-1 ; m ; m | **missing** (float): Fill value for output only | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| cape_3d | 3D cape and cin | J kg-1 | **missing** (float): Fill value for output only | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| ctt | Cloud Top Temperature | degC | **units** (str) : Set to desired units. Default is *'degC'*. | -| | | | | -| | | K | | -| | | | | -| | | degF | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| cloudfrac | Cloud Fraction | % | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| dbz | Reflectivity | dBZ | **do_variant** (boolean): Set to True to enable variant calculation. Default is *False*. | -| | | | | -| | | | **do_liqskin** (boolean): Set to True to enable liquid skin calculation. Default is *False*. | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| mdbz | Maximum Reflectivity | dBZ | **do_variant** (boolean): Set to True to enable variant calculation. Default is *False*. | -| | | | | -| | | | **do_liqskin** (boolean): Set to True to enable liquid skin calculation. Default is *False*. | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| geopt/geopotential | Full Model Geopotential | m2 s-2 | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| helicity | Storm Relative Helicity | m2 s-2 | **top** (float): The top level for the calculation in meters. Default is *3000.0*. | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| lat | Latitude | decimal degrees | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| lon | Longitude | decimal degrees | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| omg/omega | Omega | Pa s-1 | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| p/pres | Full Model Pressure | Pa | **units** (str) : Set to desired units. Default is *'Pa'*. | -| | | | | -| | (in specified units) | hPa | | -| | | | | -| | | mb | | -| | | | | -| | | torr | | -| | | | | -| | | mmhg | | -| | | | | -| | | atm | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| pressure | Full Model Pressure (hPa) | hPa | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| pvo | Potential Vorticity | PVU | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| pw | Precipitable Water | kg m-2 | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| rh | Relative Humidity | % | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| rh2 | 2m Relative Humidity | % | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| slp | Sea Level Pressure | hPa | **units** (str) : Set to desired units. Default is *'hPa'*. | -| | | | | -| | | hPa | | -| | | | | -| | | mb | | -| | | | | -| | | torr | | -| | | | | -| | | mmhg | | -| | | | | -| | | atm | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| ter | Model Terrain Height | m | **units** (str) : Set to desired units. Default is *'m'*. | -| | | | | -| | | km | | -| | | | | -| | | dm | | -| | | | | -| | | ft | | -| | | | | -| | | mi | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| td2 | 2m Dew Point Temperature | degC | **units** (str) : Set to desired units. Default is *'degC'*. | -| | | | | -| | | K | | -| | | | | -| | | degF | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| td | Dew Point Temperature | degC | **units** (str) : Set to desired units. Default is *'degC'*. | -| | | | | -| | | K | | -| | | | | -| | | degF | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| tc | Temperature in Celsius | degC | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| th/theta | Potential Temperature | K | **units** (str) : Set to desired units. Default is *'K'*. | -| | | | | -| | | degC | | -| | | | | -| | | degF | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| temp | Temperature (in specified units) | K | **units** (str) : Set to desired units. Default is *'K'*. | -| | | | | -| | | degC | | -| | | | | -| | | degF | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| tk | Temperature in Kelvin | K | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| times | Times in the File or Sequence | | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| xtimes | XTIME Coordinate | minutes since | | -| | | | | -| | (if applicable) | start of | | -| | | | | -| | | model run | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| tv | Virtual Temperature | K | **units** (str) : Set to desired units. Default is *'K'*. | -| | | | | -| | | degC | | -| | | | | -| | | degF | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| twb | Wet Bulb Temperature | K | **units** (str) : Set to desired units. Default is *'K'*. | -| | | | | -| | | degC | | -| | | | | -| | | degF | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| updraft_helicity | Updraft Helicity | m2 s-2 | **bottom** (float): The bottom level for the calculation in meters. Default is *2000.0*. | -| | | | | -| | | | **top** (float): The top level for the calculation in meters. Default is *5000.0*. | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| ua | U-component of Wind on Mass Points | m s-1 | **units** (str) : Set to desired units. Default is *'m s-1'*. | -| | | | | -| | | km h-1 | | -| | | | | -| | | mi h-1 | | -| | | | | -| | | kt | | -| | | | | -| | | ft s-1 | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| va | V-component of Wind on Mass Points | m s-1 | **units** (str) : Set to desired units. Default is *'m s-1'*. | -| | | | | -| | | km h-1 | | -| | | | | -| | | mi h-1 | | -| | | | | -| | | kt | | -| | | | | -| | | ft s-1 | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| wa | W-component of Wind on Mass Points | m s-1 | **units** (str) : Set to desired units. Default is *'m s-1'*. | -| | | | | -| | | km h-1 | | -| | | | | -| | | mi h-1 | | -| | | | | -| | | kt | | -| | | | | -| | | ft s-1 | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| uvmet10 | 10 m U and V Components of Wind | m s-1 | **units** (str) : Set to desired units. Default is *'m s-1'*. | -| | | | | -| | Rotated to Earth Coordinates | km h-1 | | -| | | | | -| | | mi h-1 | | -| | | | | -| | | kt | | -| | | | | -| | | ft s-1 | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| uvmet | U and V Components of Wind | m s-1 | **units** (str) : Set to desired units. Default is *'m s-1'*. | -| | | | | -| | Rotated to Earth Coordinates | km h-1 | | -| | | | | -| | | mi h-1 | | -| | | | | -| | | kt | | -| | | | | -| | | ft s-1 | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| wspd_wdir | Wind Speed and Direction (wind_from_direction) | m s-1 | **units** (str) : Set to desired units. Default is *'m s-1'*. | -| | | | | -| | in Grid Coordinates | km h-1 | | -| | | | | -| | | mi h-1 | | -| | | | | -| | | kt | | -| | | | | -| | | ft s-1 | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| wspd_wdir10 | 10m Wind Speed and Direction (wind_from_direction) | m s-1 | **units** (str) : Set to desired units. Default is *'m s-1'*. | -| | | | | -| | in Grid Coordinates | km h-1 | | -| | | | | -| | | mi h-1 | | -| | | | | -| | | kt | | -| | | | | -| | | ft s-1 | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| uvmet_wspd_wdir | Wind Speed and Direction (wind_from_direction) | m s-1 | **units** (str) : Set to desired units. Default is *'m s-1'*. | -| | | | | -| | Rotated to Earth Coordinates | km h-1 | | -| | | | | -| | | mi h-1 | | -| | | | | -| | | kt | | -| | | | | -| | | ft s-1 | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| uvmet10_wspd_wdir | 10m Wind Speed and Direction (wind_from_direction) | m s-1 | **units** (str) : Set to desired units. Default is *'m s-1'*. | -| | | | | -| | Rotated to Earth Coordinates | km h-1 | | -| | | | | -| | | mi h-1 | | -| | | | | -| | | kt | | -| | | | | -| | | ft s-1 | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ -| z/height | Full Model Height on Mass Levels | m | **msl** (boolean): Set to False to return AGL values. True is for MSL. Default is *True*. | -| | | | | -| | | km | **units** (str) : Set to desired units. Default is *'m'*. | -| | | | | -| | | dm | | -| | | | | -| | | ft | | -| | | | | -| | | mi | | -+--------------------+---------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------------+ - - ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Variable Name | Description | Available Units | Additional Keyword Arguments | ++====================+===============================================================+=============================+=========================================================================================================================================================+ +| avo | Absolute Vorticity | 10-5 s-1 | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| eth/theta_e | Equivalent Potential Temperature | K | **units** (str) : Set to desired units. Default is *'K'*. | +| | | | | +| | | degC | | +| | | | | +| | | degF | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| cape_2d | 2D cape (mcape/mcin/lcl/lfc) | J kg-1 ; J kg-1 ; m ; m | **missing** (float): Fill value for output only | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| cape_3d | 3D cape and cin | J kg-1 | **missing** (float): Fill value for output only | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| ctt | Cloud Top Temperature | degC | **units** (str) : Set to desired units. Default is *'degC'*. | +| | | | | +| | | K | | +| | | | | +| | | degF | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| cloudfrac | Cloud Fraction | % | **vert_type** (str): The vertical coordinate type for the cloud thresholds. Must be 'height_agl', 'height_msl', or 'pres'. Default is 'height_agl'. | +| | | | | +| | | | **low_thresh** (float): The low cloud threshold (meters for 'height_agl' and 'height_msl', pascals for 'pres'). Default is 300 m (97000 Pa) | +| | | | | +| | | | **mid_thresh** (float): The mid cloud threshold (meters for 'height_agl' and 'height_msl', pascals for 'pres'). Default is 2000 m (80000 Pa) | +| | | | | +| | | | **high_thresh** (float): The high cloud threshold (meters for 'height_agl' and 'height_msl', pascals for 'pres'). Default is 6000 m (45000 Pa) | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| dbz | Reflectivity | dBZ | **do_variant** (boolean): Set to True to enable variant calculation. Default is *False*. | +| | | | | +| | | | **do_liqskin** (boolean): Set to True to enable liquid skin calculation. Default is *False*. | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| mdbz | Maximum Reflectivity | dBZ | **do_variant** (boolean): Set to True to enable variant calculation. Default is *False*. | +| | | | | +| | | | **do_liqskin** (boolean): Set to True to enable liquid skin calculation. Default is *False*. | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| geopt/geopotential | Geopotential for the Mass Grid | m2 s-2 | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| geopt_stag | Geopotential for the Vertically Staggered Grid | m2 s-2 | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| helicity | Storm Relative Helicity | m2 s-2 | **top** (float): The top level for the calculation in meters. Default is *3000.0*. | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| lat | Latitude | decimal degrees | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| lon | Longitude | decimal degrees | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| omg/omega | Omega | Pa s-1 | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| p/pres | Full Model Pressure | Pa | **units** (str) : Set to desired units. Default is *'Pa'*. | +| | | | | +| | (in specified units) | hPa | | +| | | | | +| | | mb | | +| | | | | +| | | torr | | +| | | | | +| | | mmhg | | +| | | | | +| | | atm | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| pressure | Full Model Pressure (hPa) | hPa | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| pvo | Potential Vorticity | PVU | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| pw | Precipitable Water | kg m-2 | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| rh | Relative Humidity | % | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| rh2 | 2m Relative Humidity | % | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| slp | Sea Level Pressure | hPa | **units** (str) : Set to desired units. Default is *'hPa'*. | +| | | | | +| | | hPa | | +| | | | | +| | | mb | | +| | | | | +| | | torr | | +| | | | | +| | | mmhg | | +| | | | | +| | | atm | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| ter | Model Terrain Height | m | **units** (str) : Set to desired units. Default is *'m'*. | +| | | | | +| | | km | | +| | | | | +| | | dm | | +| | | | | +| | | ft | | +| | | | | +| | | mi | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| td2 | 2m Dew Point Temperature | degC | **units** (str) : Set to desired units. Default is *'degC'*. | +| | | | | +| | | K | | +| | | | | +| | | degF | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| td | Dew Point Temperature | degC | **units** (str) : Set to desired units. Default is *'degC'*. | +| | | | | +| | | K | | +| | | | | +| | | degF | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| tc | Temperature in Celsius | degC | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| th/theta | Potential Temperature | K | **units** (str) : Set to desired units. Default is *'K'*. | +| | | | | +| | | degC | | +| | | | | +| | | degF | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| temp | Temperature (in specified units) | K | **units** (str) : Set to desired units. Default is *'K'*. | +| | | | | +| | | degC | | +| | | | | +| | | degF | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| tk | Temperature in Kelvin | K | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| times | Times in the File or Sequence | | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| xtimes | XTIME Coordinate | minutes since | | +| | | | | +| | (if applicable) | start of | | +| | | | | +| | | model run | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| tv | Virtual Temperature | K | **units** (str) : Set to desired units. Default is *'K'*. | +| | | | | +| | | degC | | +| | | | | +| | | degF | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| twb | Wet Bulb Temperature | K | **units** (str) : Set to desired units. Default is *'K'*. | +| | | | | +| | | degC | | +| | | | | +| | | degF | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| updraft_helicity | Updraft Helicity | m2 s-2 | **bottom** (float): The bottom level for the calculation in meters. Default is *2000.0*. | +| | | | | +| | | | **top** (float): The top level for the calculation in meters. Default is *5000.0*. | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| ua | U-component of Wind on Mass Points | m s-1 | **units** (str) : Set to desired units. Default is *'m s-1'*. | +| | | | | +| | | km h-1 | | +| | | | | +| | | mi h-1 | | +| | | | | +| | | kt | | +| | | | | +| | | ft s-1 | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| va | V-component of Wind on Mass Points | m s-1 | **units** (str) : Set to desired units. Default is *'m s-1'*. | +| | | | | +| | | km h-1 | | +| | | | | +| | | mi h-1 | | +| | | | | +| | | kt | | +| | | | | +| | | ft s-1 | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| wa | W-component of Wind on Mass Points | m s-1 | **units** (str) : Set to desired units. Default is *'m s-1'*. | +| | | | | +| | | km h-1 | | +| | | | | +| | | mi h-1 | | +| | | | | +| | | kt | | +| | | | | +| | | ft s-1 | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| uvmet10 | 10 m U and V Components of Wind | m s-1 | **units** (str) : Set to desired units. Default is *'m s-1'*. | +| | | | | +| | Rotated to Earth Coordinates | km h-1 | | +| | | | | +| | | mi h-1 | | +| | | | | +| | | kt | | +| | | | | +| | | ft s-1 | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| uvmet | U and V Components of Wind | m s-1 | **units** (str) : Set to desired units. Default is *'m s-1'*. | +| | | | | +| | Rotated to Earth Coordinates | km h-1 | | +| | | | | +| | | mi h-1 | | +| | | | | +| | | kt | | +| | | | | +| | | ft s-1 | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| wspd_wdir | Wind Speed and Direction (wind_from_direction) | m s-1 | **units** (str) : Set to desired units. Default is *'m s-1'*. | +| | | | | +| | in Grid Coordinates | km h-1 | | +| | | | | +| | | mi h-1 | | +| | | | | +| | | kt | | +| | | | | +| | | ft s-1 | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| wspd_wdir10 | 10m Wind Speed and Direction (wind_from_direction) | m s-1 | **units** (str) : Set to desired units. Default is *'m s-1'*. | +| | | | | +| | in Grid Coordinates | km h-1 | | +| | | | | +| | | mi h-1 | | +| | | | | +| | | kt | | +| | | | | +| | | ft s-1 | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| uvmet_wspd_wdir | Wind Speed and Direction (wind_from_direction) | m s-1 | **units** (str) : Set to desired units. Default is *'m s-1'*. | +| | | | | +| | Rotated to Earth Coordinates | km h-1 | | +| | | | | +| | | mi h-1 | | +| | | | | +| | | kt | | +| | | | | +| | | ft s-1 | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| uvmet10_wspd_wdir | 10m Wind Speed and Direction (wind_from_direction) | m s-1 | **units** (str) : Set to desired units. Default is *'m s-1'*. | +| | | | | +| | Rotated to Earth Coordinates | km h-1 | | +| | | | | +| | | mi h-1 | | +| | | | | +| | | kt | | +| | | | | +| | | ft s-1 | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| z/height | Model Height for Mass Grid | m | **msl** (boolean): Set to False to return AGL values. True is for MSL. Default is *True*. | +| | | | | +| | | km | **units** (str) : Set to desired units. Default is *'m'*. | +| | | | | +| | | dm | | +| | | | | +| | | ft | | +| | | | | +| | | mi | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ +| zstag | Model Height for Vertically Staggered Grid | m | **msl** (boolean): Set to False to return AGL values. True is for MSL. Default is *True*. | +| | | | | +| | | km | **units** (str) : Set to desired units. Default is *'m'*. | +| | | | | +| | | dm | | +| | | | | +| | | ft | | +| | | | | +| | | mi | | ++--------------------+---------------------------------------------------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+ diff --git a/doc/source/basic_usage.rst b/doc/source/basic_usage.rst index 3bd5eba..c29f03f 100644 --- a/doc/source/basic_usage.rst +++ b/doc/source/basic_usage.rst @@ -1,6 +1,36 @@ How To Use ============ +Introduction +--------------- + +The API for wrf-python can be summarized as a variable computation/extraction +routine, several interpolation routines, and a few plotting helper utilities. +The API is kept as simple as possible to help minimize the +learning curve for new programmers, students, and scientists. In the future, +we plan to extend xarray for programmers desiring a more object oriented API, +but this remains a work in progress. + +The five most commonly used routines can be summarized as: + +- :meth:`wrf.getvar` - Extracts WRF-ARW NetCDF variables and + computes diagnostic variables that WRF does not compute (e.g. storm + relative helicity). This is the routine that you will use most often. + +- :meth:`wrf.interplevel` - Interpolates a three-dimensional field to a + horizontal plane at a specified level using simple (fast) linear + interpolation (e.g. 850 hPa temperature). + +- :meth:`wrf.vertcross` - Interpolates a three-dimensional field to a vertical + plane through a user-specified horizontal line (i.e. a cross section). + +- :meth:`wrf.interpline` - Interpolates a two-dimensional field to a + user-specified line. + +- :meth:`wrf.vinterp` - Interpolates a three-dimensional field to + user-specified 'surface' levels (e.g. theta-e levels). This is a smarter, + albeit slower, version of :meth:`wrf.interplevel`. + Basic Usage ---------------- @@ -10,8 +40,8 @@ Computing Diagnostic Variables ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The primary use for the :meth:`wrf.getvar` function is to return diagnostic -variables that require a calculation, since WRF does not produce these variables -natively. These diagnostics include CAPE, storm relative helicity, +variables that require a calculation, since WRF does not produce these +variables natively. These diagnostics include CAPE, storm relative helicity, omega, sea level pressure, etc. A table of all available diagnostics can be found here: :ref:`diagnostic-table`. @@ -355,9 +385,9 @@ Result: pole_lon=0.0) -Note how the 'Time' dimension was replaced with the 'file' dimension, due to the -numpy's automatic squeezing of the single 'Time' dimension. To maintain the -'Time' dimension, set the *squeeze* parameter to False. +Note how the 'Time' dimension was replaced with the 'file' dimension, due to +numpy's automatic squeezing of the single element 'Time' dimension. To maintain +the 'Time' dimension, set the *squeeze* parameter to False. .. code-block:: python @@ -706,8 +736,9 @@ Example Using Lat/Lon Coordinates start_point = CoordPair(lat=start_lat, lon=start_lon) end_point = CoordPair(lat=end_lat, lon=end_lon) - # When using lat/lon coordinates, you must supply a netcdf file object, or a - # projection object. + # When using lat/lon coordinates, you must supply a WRF netcdf file object, + # or a projection object with the lower left latitude and lower left + # longitude points. p_vert = vertcross(p, z, wrfin=ncfile, start_point=start_point, end_point=end_point, latlon=True) print(p_vert) @@ -953,8 +984,11 @@ Example Using Lat/Lon Coordinates start_point = CoordPair(lat=start_lat, lon=start_lon) end_point = CoordPair(lat=end_lat, lon=end_lon) - # Calculate the vertical cross section. By setting latlon to True, this - # also calculates the latitude and longitude coordinates along the line + # Calculate the interpolated line. To use latitude and longitude points, + # you must supply a WRF NetCDF file object, or a projection object along + # with the lower left latitude and lower left longitude points. + # Also, by setting latlon to True, this routine will + # also calculate the latitude and longitude coordinates along the line # and adds them to the metadata to help with plotting labels. t2_line = interpline(t2, wrfin=ncfile, start_point=start_point, end_point=end_point, latlon=True) @@ -1614,3 +1648,460 @@ Result: ] +.. _using_omp: + +Using OpenMP +------------------------- + +Beginning in version 1.1, the Fortran computational routines in wrf-python make +use of OpenMP directives. OpenMP enables the calculations to use multiple CPU +cores, which can improve performance. In order to use OpenMP features, +wrf-python has to be compiled with OpenMP enabled (most pre-built binary +installations will have this enabled). + +The Fortran computational routines have all been built using runtime +scheduling, instead of compile time scheduling, so that the user can choose the +scheduler type within their Python application. By default, the scheduling +type is set to :data:`wrf.OMP_SCHED_STATIC` using only 1 CPU core, so +wrf-python will behave similarly to the non-OpenMP built versions. For the most +part, the difference between the scheduling types is minimal, with the exception +being the :data:`wrf.OMP_SCHED_DYNAMIC` scheduler that is much slower due to +the additional overhead associated with it. For new users, using the default +scheduler should be sufficient. + + +Verifying that OpenMP is Enabled +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To take advantage of the performance improvements offered by OpenMP, wrf-python +needs to have been compiled with OpenMP features enabled. The example below +shows how you can determine if OpenMP is enabled in your build of wrf-python. + +.. code-block:: python + + from __future__ import print_function + + from wrf import omp_enabled + + print(omp_enabled()) + + +Result: + +.. code-block:: none + + True + + +Determining the Number of Available Processors +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The example below shows how you can get the maximum number of processors +that are available on your system. + +.. code-block:: python + + from __future__ import print_function + + from wrf import omp_get_num_procs + + print(omp_get_num_procs()) + + +Result: + +.. code-block:: none + + 8 + + +Specifying the Number of Threads +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To enable multicore support via OpenMP, specifying the maximum number +of OpenMP threads (i.e. CPU cores) is the only step that you need to take. + +In the example below, :meth:`wrf.omp_set_num_threads` is used to set the +maximum number of threads to use, and :meth:`wrf.omp_get_max_threads` is used +to retrieve (and print) the maximum number of threads used. + +.. note:: + + Although there is an OpenMP routine named :meth:`wrf.omp_get_num_threads`, + this routine will always return 1 when called from the sequential part of + the program. Use :meth:`wrf.omp_get_max_threads` to return the value set by + :meth:`wrf.omp_set_num_threads`. + +.. code-block:: python + + from __future__ import print_function + + from wrf import omp_set_num_threads, omp_get_max_threads + + omp_set_num_threads(4) + + print (omp_get_max_threads()) + + +Result: + +.. code-block:: none + + 4 + +Setting a Different Scheduler Type +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When an OpenMP directive is encountered in the Fortran code, a scheduler is +used to determine how the work is divided among the threads. All of the +Fortran routines are compiled to use a 'runtime' scheduler, which indicates +that the scheduler type (from the four listed below) is to be chosen at +runtime (i.e. inside a Python script) + +By default, the scheduler chosen is the :data:`wrf.OMP_SCHED_STATIC` scheduler, +which should be sufficient for most users. However, OpenMP and wrf-python +include the following options for the scheduler type: + +- :data:`wrf.OMP_SCHED_STATIC` +- :data:`wrf.OMP_SCHED_DYNAMIC` +- :data:`wrf.OMP_SCHED_GUIDED` +- :data:`wrf.OMP_SCHED_AUTO` + +Refer to the +`OpenMP Specification `_. +for more information about these scheduler types. In local testing, +:data:`wrf.OMP_SCHED_GUIDED` produced the best results, but +differences between :data:`wrf.OMP_SCHED_STATIC`, +:data:`wrf.OMP_SCHED_GUIDED`, and +:data:`wrf.OMP_SCHED_AUTO` were minor. However, +:data:`wrf.OMP_SCHED_DYNAMIC` produced noticeably slower results +due to the overhead of using a dynamic scheduler. + +When setting a scheduler type, the :meth:`wrf.omp_set_schedule` takes two +arguments. The first is the scheduler type (one from the list above), and the +second optional argument is a modifier, which is usually referred as the chunk +size. If the modifier/chunk_size is set to 0, then the OpenMP default +implementation is used. For :data:`wrf.OMP_SCHED_AUTO`, the +modifier is ignored. + +If you are new to OpenMP and all this sounds confusing, don't worry about +setting a scheduler type. The default static scheduler will be good enough. + +In the example below, the scheduler type is set to +:data:`wrf.OMP_SCHED_GUIDED` and uses the default chunk size of 0. The +scheduler type is then read back using :meth:`wrf.omp_get_schedule` +and printed. + +.. code-block:: python + + from __future__ import print_function + + from wrf import omp_set_schedule, omp_get_schedule, OMP_SCHED_GUIDED + + omp_set_schedule(OMP_SCHED_GUIDED, 0) + + sched, modifier = omp_get_schedule() + + print(sched, modifier) + + +Result: + +.. code-block:: none + + 3 1 + +Notice that the printed scheduler type (*sched* variable) is set to a +value of 3, which is the actual integer constant value for the +:data:`wrf.OMP_SCHED_GUIDED` scheduler type. The *modifier* is returned as a +value of 1, which is different than the 0 that was supplied to the +:meth:`wrf.omp_set_schedule` routine. This is because the 0 tells OpenMP to use +its own default value for the scheduler, which is 1 for this type of scheduler. + +.. _performance: + +Performance Tips +-------------------- + +Memory Issues with :data:`wrf.ALL_TIMES` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The use of :data:`wrf.ALL_TIMES` for the *timeidx* parameter to +:meth:`wrf.getvar` is convenient for computing diagnostic variables across +multiple files/times, but there is something that users should be aware of. +When :data:`wrf.ALL_TIMES` is set as the *timeidx* argument, all arrays used +in the computation are extracted for all times before the computation +is started. This can cause serious memory issues on smaller hardware systems +like laptops. + +In this example, the user wants to use a data set that is 289 x 39 x 300 x 300 +and compute z for the entire data set. The user is using a laptop with +8 GB of memory. + +.. code-block:: python + + from netCDF4 import Dataset + from wrf import getvar, ALL_TIMES + + file_list = [Dataset("/path/to/file1"), Dataset("/path/to/file2"),...] + z = getvar(file_list, "z", ALL_TIMES) + +Five hours later, the computation finished. What happened? + +In wrf-python, all of the computational routines use 8-byte REAL variables so +that both the 4-byte and 8-byte version of WRF output can be used. The +calculation for z extracts three variables (P, PHB, and HGT) and returns a +fourth array (RESULT). The RESULT will get cut in half to 4-byte REALs +after the computation, but needs an 8-byte REAL when the result is computed. + +Let's look at the approximate amount memory needed: + +**P**: 289 x 39 x 300 x 300 x 8 = 8,115,120,000 bytes (~8 GB!) + +**PHB**: 289 x 39 x 300 x 300 x 8 = 8,115,120,000 bytes (~8 GB!) + +**HGT**: 289 x 300 x 300 x 8 = 208,080,000 (~208 MB) + +**RESULT**: 289 x 39 x 300 x 300 x 8 = 8,115,120,000 bytes (~8 GB!) + +Yikes! So, in order to do this calculation using :data:`wrf.ALL_TIMES` as +the *timeidx*, over 24.2 GB are needed for this one calculation. When the +laptop runs out of memory, it begins using the hard drive for swap memory, +which runs hundreds of times slower than real memory. + +To fix this situation, it is better to allocate the output array yourself and +run the calculation for each time step in a loop +("loop-and-fill"). The required memory requirements change to: + +(Note: only need to store the result in a 4-byte REAL) + +**FINAL_RESULT**: 289 x 39 x 300 x 300 x 4 = 4,057560,000 bytes (~4 GB) + +(Note: the numbers below are for each loop iteration) + +**P**: 39 x 300 x 300 x 8 = 28,080,000 bytes (~28 MB) + +**PHB**: 39 x 300 x 300 x 8 = 28,080,000 bytes (~28 MB) + +**HGT**: 300 x 300 x 8 = 720,000 bytes (720 KB) + +**RESULT**: 39 x 300 x 300 x 8 = 28,080,000 bytes (~28 MB) + +Since the memory for the computation is deleted after each +loop iteration, the total memory usage drops to approximately 4.1 GB. + +The moral of the story is that you need to make sure that your system has +enough memory to extract everything it needs up front if you want to use +:data:`wrf.ALL_TIMES`, otherwise it is better to "loop-and-fill" yourself. + +Here is an example of the "loop-and-fill" technique: + +.. code-block:: python + + from __future__ import print_function, division + + import numpy as np + from netCDF4 import Dataset + from wrf import getvar, ALL_TIMES + + filename_list = ["/path/to/file1", "/path/to/file2",...] + + # Result shape (hard coded for this example) + result_shape = (289, 39, 300, 300) + + # Only need 4-byte floats + z_final = np.empty(result_shape, np.float32) + + # Modify this number if using more than 1 time per file + times_per_file = 1 + + for timeidx in range(result_shape[0]): + # Compute the file index and the time index inside the file + fileidx = timeidx // times_per_file + file_timeidx = timeidx % times_per_file + + f = Dataset(filename_list[fileidx]) + z = getvar(f, "z", file_timeidx) + + z_final[timeidx,:] = z[:] + f.close() + + +The *cache* Argument for :meth:`wrf.getvar` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you have read through the documentation, you may have noticed that the +:meth:`wrf.getvar` routine contains a *cache* argument. What is this for? + +Internally, if metadata is turned on, a variable is extracted from the NetCDF +file and its metadata is copied to form the result's metadata. Often this +variable is one of the computation's function arguments, so rather than +spend time extracting the variable again for the computation, it is placed +in a cache (dictionary) and passed on to the computational function. + +What isn't widely known is that this cache argument can also be supplied by +end users wishing to speed up their application. This can be useful in +situations where numerous calculations are being performed on the same +data set. For many algorithms, the time needed to extract the arrays from the +NetCDF file is on par with the time needed to perform the calculation. If you +are computing numerous diagnostics, extracting the variables up front allows +you to only pay this extraction penalty once, rather than inside of each call +to :meth:`wrf.getvar`. + +The cache is nothing more than a dictionary where each key is the variable +name (e.g. "P") and the value is the :class:`xarray.DataArray` or +:class:`numpy.ndarray` variable. Creating the cache dictionary is easy, +since the :meth:`wrf.extract_vars` routine returns a dictionary for a +sequence of variables. + +.. note:: + + The *timeidx* parameter supplied to :meth:`extract_vars` + must be the same *timeidx* parameter that you plan to use for + :meth:`wrf.getvar`. Otherwise, it will crash with dimension mismatch errors. + +Some common variables that you can use to create an effective cache are: P, PB, +PH, PHB, T, QVAPOR, HGT, PSFC, U, V, W. + +Below is an example showing the same computation done with and without the +cache. The execution time is printed. The hardware used is a 2.8 GHz Intel Core +i7, which contains 4 CPU cores with 2 hyper threads (8 total threads). This +will be interpreted as 8 CPUs for OpenMP. + +.. code-block:: python + + from __future__ import print_function + + import time + from netCDF4 import Dataset + from wrf import getvar, ALL_TIMES, extract_vars + + # The first two files contain four times, the last file contains only one. + wrf_filenames = ["/path/to/wrfout_d02_2005-08-28_00:00:00", + "/path/to/wrfout_d02_2005-08-28_12:00:00", + "/path/to/wrfout_d02_2005-08-29_00:00:00"] + + wrfin = [Dataset(x) for x in wrf_filenames] + + start = time.time() + my_cache = extract_vars(wrfin, ALL_TIMES, ("P", "PSFC", "PB", "PH", "PHB", + "T", "QVAPOR", "HGT", "U", "V", + "W")) + end = time.time() + print ("Time taken to build cache: ", (end-start), "s") + + vars = ("avo", "eth", "cape_2d", "cape_3d", "ctt", "dbz", "mdbz", + "geopt", "helicity", "lat", "lon", "omg", "p", "pressure", + "pvo", "pw", "rh2", "rh", "slp", "ter", "td2", "td", "tc", + "theta", "tk", "tv", "twb", "updraft_helicity", "ua", "va", + "wa", "uvmet10", "uvmet", "z", "cfrac", "zstag", "geopt_stag") + + # No cache + start = time.time() + for var in vars: + v = getvar(wrfin, var, ALL_TIMES) + end = time.time() + no_cache_time = (end-start) + + print ("Time taken without variable cache: ", no_cache_time, "s") + + # With a cache + start = time.time() + for var in vars: + v = getvar(wrfin, var, ALL_TIMES, cache=my_cache) + end = time.time() + cache_time = (end-start) + + print ("Time taken with variable cache: ", cache_time, "s") + + improvement = ((no_cache_time-cache_time)/no_cache_time) * 100 + print ("The cache decreased computation time by: ", improvement, "%") + + +Result: + +.. code-block:: none + + Time taken to build cache: 0.28154706955 s + Time taken without variable cache: 11.0905270576 s + Time taken with variable cache: 8.25931215286 s + The cache decreased computation time by: 25.5282268378 % + +By removing the repeated extraction of common variables in the +:meth:`wrf.getvar` routine, for the single threaded case, the computation +time has been reduced by 25.5% in this particular example. + +Things get more interesting when OpenMP is turned on and set to use the +maximum number of processors (in this case 8 threads are used). + +.. code-block:: python + + from __future__ import print_function + + import time + from netCDF4 import Dataset + from wrf import (getvar, ALL_TIMES, extract_vars, + omp_set_num_threads, omp_get_num_procs) + + # The first two files contain four times, the last file contains only one. + wrf_filenames = ["/path/to/wrfout_d02_2005-08-28_00:00:00", + "/path/to/wrfout_d02_2005-08-28_12:00:00", + "/path/to/wrfout_d02_2005-08-29_00:00:00"] + + wrfin = [Dataset(x) for x in wrf_filenames] + + start = time.time() + my_cache = extract_vars(wrfin, ALL_TIMES, ("P", "PSFC", "PB", "PH", "PHB", + "T", "QVAPOR", "HGT", "U", "V", + "W")) + end = time.time() + print ("Time taken to build cache: ", (end-start), "s") + + omp_set_num_threads(omp_get_num_procs()) + + vars = ("avo", "eth", "cape_2d", "cape_3d", "ctt", "dbz", "mdbz", + "geopt", "helicity", "lat", "lon", "omg", "p", "pressure", + "pvo", "pw", "rh2", "rh", "slp", "ter", "td2", "td", "tc", + "theta", "tk", "tv", "twb", "updraft_helicity", "ua", "va", + "wa", "uvmet10", "uvmet", "z", "cfrac", "zstag", "geopt_stag") + + # No cache + start = time.time() + for var in vars: + v = getvar(wrfin, var, ALL_TIMES) + end = time.time() + no_cache_time = (end-start) + + print ("Time taken without variable cache: ", no_cache_time, "s") + + # With a cache + start = time.time() + for var in vars: + v = getvar(wrfin, var, ALL_TIMES, cache=my_cache) + end = time.time() + cache_time = (end-start) + + print ("Time taken with variable cache: ", cache_time, "s") + + improvement = ((no_cache_time-cache_time)/no_cache_time) * 100 + print ("The cache decreased computation time by: ", improvement, "%") + +Result: + +.. code-block:: none + + Time taken to build cache: 0.2700548172 s + Time taken without variable cache: 6.02652812004 s + Time taken with variable cache: 3.27777099609 s + The cache decreased computation time by: 45.6109565772 % + +In this example, 4 CPU cores (8 total threads) are used. When the cache is +used, the computation time drops by 45%, so almost half the time was spent +simply extracting variables from the NetCDF file. When compared to the +11.09 s needed to compute the single threaded case with no variable cache, the +computation time drops by roughly 70% (compared to 45% with 8 threads but +no cache). + +In summary, if you are computing a lot of diagnostic variables, consider using +the *cache* argument to improve performance, particularly if you want to +maximize your multithreaded performance with OpenMP. diff --git a/doc/source/citation.rst b/doc/source/citation.rst new file mode 100644 index 0000000..89c35c4 --- /dev/null +++ b/doc/source/citation.rst @@ -0,0 +1,26 @@ +.. _citation: + +Citation +================= + +WRF-Python has a Digital Object Identifier (DOI), which is a persistent +identifier for web-based resources. The wrf-python DOI, when used in URL form, +https://doi.org/10.5065/D6W094P1, provides a persistent link to the wrf-python +Github page. The benefit of DOIs is that they are widely accepted by academic +publishers as citable locators for scholarly objects. + +If you author a paper that involves data analysis with wrf-python, or +visualizations created with wrf-python, we would like to ask you to please +cite wrf-python. This helps us better understand the impact of the software on +the scientific community, which in turns helps us maintain support for the +effort. + +You can cite wrf-python using the following citation: + +.. code-block:: none + + Ladwig, W. (2017). wrf-python (Version x.x.x) [Software]. Boulder, Colorado: UCAR/NCAR. https://doi.org/10.5065/D6W094P1 + +.. note:: + + The version number x.x.x should be set to the version of wrf-python that you are using. \ No newline at end of file diff --git a/doc/source/conf.py b/doc/source/conf.py index 76aa4d0..b248434 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -23,14 +23,24 @@ except ImportError: class Mock(MagicMock): @classmethod def __getattr__(cls, name): - return Mock() + return MagicMock() MOCK_MODULES = ["numpy", "numpy.ma", "xarray", "cartopy", "pandas", "matplotlib", "netCDF4", "mpl_toolkits.basemap", "wrf._wrffortran"] sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES) -consts = {"DEFAULT_FILL" : 9.9692099683868690E36} +consts = {"DEFAULT_FILL" : 9.9692099683868690E36, + "DEFAULT_FILL_INT8" : -127, + "DEFAULT_FILL_INT16" : -32767, + "DEFAULT_FILL_INT32" : -2147483647, + "DEFAULT_FILL_INT64" : -9223372036854775806, + "DEFAULT_FILL_FLOAT" : 9.9692099683868690E36, + "DEFAULT_FILL_DOUBLE" : 9.9692099683868690E36, + "fomp_sched_static" : 1, + "fomp_sched_dynamic" : 2, + "fomp_sched_guided" : 3, + "fomp_sched_auto" : 4} class MockWrfConstants(object): def __init__(self): @@ -40,6 +50,8 @@ def mock_asscalar(val): return float(val) sys.modules["wrf._wrffortran"].wrf_constants = MockWrfConstants() +sys.modules["wrf._wrffortran"].omp_constants = MockWrfConstants() + sys.modules["numpy"].asscalar = mock_asscalar @@ -106,6 +118,7 @@ author = u'Bill Ladwig' # built documents. # # The short X.Y version. + import wrf version = wrf.__version__ # The full version, including alpha/beta/rc tags. diff --git a/doc/source/faq.rst b/doc/source/faq.rst index 74468d0..2c1cd28 100644 --- a/doc/source/faq.rst +++ b/doc/source/faq.rst @@ -61,6 +61,13 @@ In a future release of wrf-python, direct support for Dataset objects will be added and this will no longer be necessary. +Why is wrf-python taking hours to run? +--------------------------------------------- + +The most likely culprit is insufficient memory for the calculation you are +trying to perform. + +See :ref:`performance` for more information. diff --git a/doc/source/index.rst b/doc/source/index.rst index c0ae7c1..f43090a 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -45,8 +45,9 @@ Documentation ./api ./faq ./support + ./citation ./license - ./workshop + ./tutorial Indices and tables diff --git a/doc/source/installation.rst b/doc/source/installation.rst index 15b0cc4..bd5cb18 100644 --- a/doc/source/installation.rst +++ b/doc/source/installation.rst @@ -4,9 +4,10 @@ Installation Required Dependencies ---------------------- - - Python 2.7, 3.4, or 3.5 - - numpy (1.9 or later) + - Python 2.7, 3.4, or 3.5+ + - numpy (1.11 or later; 1.14 required to build on Windows) - wrapt (1.10 or later) + - setuptools (38.0 or later) Highly Recommended Packages @@ -81,13 +82,52 @@ The source code is available via github: https://github.com/NCAR/wrf-python -To install, change to the wrf-python directory and run:: +Or PyPI: + +https://pypi.python.org/pypi/wrf-python + +To install, if you do not need OpenMP support, change your working directory +to the wrf-python source directory and run:: $ pip install . + +Beginning with wrf-python 1.1, OpenMP is supported, but preprocessing the +ompgen.F90 file is required, which also requires running f2py to +build the .pyf file. To simplify this process, you can use the build scripts in +the *build_scripts* directory as a guide, or just call the script directly. + +Below is a sample from a build script for GNU compiler with OpenMP enabled: + +.. code-block:: none + + cd ../fortran + + gfortran -E ompgen.F90 -fopenmp -cpp -o omp.f90 + + f2py *.f90 -m _wrffortran -h wrffortran.pyf --overwrite-signature + + cd .. + + python setup.py clean --all + + python setup.py config_fc --f90flags="-mtune=generic -fopenmp" build_ext --libraries="gomp" build + + pip install . + +Beginning with numpy 1.14, f2py extensions can now be built using the MSVC +compiler and mingw gfortran compiler. Numpy 1.14 is required to build +wrf-python for Python 3.5+. + +.. note:: + + If you are building on a supercomputer and receiving linker related + errors (e.g. missing symbols, undefined references, etc), you probably + need to unset the LDFLAGS environment variable. System administrators on + supercomputing systems often define LDFLAGS for you so that you don't need + to worry about where libraries like NetCDF are installed. Unfortunately, + this can cause problems with the numpy.distutils build system. To fix, + using the build command from above:: + + $ unset LDFLAGS python setup.py config_fc --f90flags="-mtune=generic -fopenmp" build_ext --libraries="gomp" build + -Note that building on Win64 with Python 3.5+ and the mingw-64 compiler -is very difficult, due to incompatibilities with the runtime libraries and -lack of support from numpy's distutils. Improved support for these -configurations, along with numpy distutils support, should take place this -year. But for now, visual studio and the intel compiler may be required. -Otherwise, Python 2.7 or Python 3.4 is recommended. diff --git a/doc/source/internal_api/index.rst b/doc/source/internal_api/index.rst index 11fa312..a41775b 100644 --- a/doc/source/internal_api/index.rst +++ b/doc/source/internal_api/index.rst @@ -13,46 +13,46 @@ The routines below are called internally by :meth:`wrf.getvar`. :nosignatures: :toctree: ./generated/ - wrf.cape.get_2dcape - wrf.cape.get_3dcape - wrf.cloudfrac.get_cloudfrac - wrf.ctt.get_ctt - wrf.dbz.get_dbz - wrf.dbz.get_max_dbz - wrf.dewpoint.get_dp - wrf.dewpoint.get_dp_2m - wrf.geoht.get_geopt - wrf.geoht.get_height - wrf.helicity.get_srh - wrf.helicity.get_uh - wrf.omega.get_omega - wrf.pressure.get_pressure - wrf.pressure.get_pressure_hpa - wrf.pw.get_pw - wrf.rh.get_rh - wrf.rh.get_rh_2m - wrf.slp.get_slp - wrf.temp.get_theta - wrf.temp.get_temp - wrf.temp.get_eth - wrf.temp.get_tv - wrf.temp.get_tw - wrf.temp.get_tk - wrf.temp.get_tc - wrf.terrain.get_terrain - wrf.times.get_times - wrf.times.get_xtimes - wrf.uvmet.get_uvmet - wrf.uvmet.get_uvmet10 - wrf.uvmet.get_uvmet_wspd_wdir - wrf.uvmet.get_uvmet10_wspd_wdir - wrf.vorticity.get_avo - wrf.vorticity.get_pvo - wrf.wind.get_u_destag - wrf.wind.get_v_destag - wrf.wind.get_w_destag - wrf.wind.get_destag_wspd_wdir - wrf.wind.get_destag_wspd_wdir10 + wrf.g_cape.get_2dcape + wrf.g_cape.get_3dcape + wrf.g_cloudfrac.get_cloudfrac + wrf.g_ctt.get_ctt + wrf.g_dbz.get_dbz + wrf.g_dbz.get_max_dbz + wrf.g_dewpoint.get_dp + wrf.g_dewpoint.get_dp_2m + wrf.g_geoht.get_geopt + wrf.g_geoht.get_height + wrf.g_helicity.get_srh + wrf.g_helicity.get_uh + wrf.g_omega.get_omega + wrf.g_pressure.get_pressure + wrf.g_pressure.get_pressure_hpa + wrf.g_pw.get_pw + wrf.g_rh.get_rh + wrf.g_rh.get_rh_2m + wrf.g_slp.get_slp + wrf.g_temp.get_theta + wrf.g_temp.get_temp + wrf.g_temp.get_eth + wrf.g_temp.get_tv + wrf.g_temp.get_tw + wrf.g_temp.get_tk + wrf.g_temp.get_tc + wrf.g_terrain.get_terrain + wrf.g_times.get_times + wrf.g_times.get_xtimes + wrf.g_uvmet.get_uvmet + wrf.g_uvmet.get_uvmet10 + wrf.g_uvmet.get_uvmet_wspd_wdir + wrf.g_uvmet.get_uvmet10_wspd_wdir + wrf.g_vorticity.get_avo + wrf.g_vorticity.get_pvo + wrf.g_wind.get_u_destag + wrf.g_wind.get_v_destag + wrf.g_wind.get_w_destag + wrf.g_wind.get_destag_wspd_wdir + wrf.g_wind.get_destag_wspd_wdir10 ------------------------- diff --git a/doc/source/new.rst b/doc/source/new.rst index aec8cbe..e2ff9cc 100644 --- a/doc/source/new.rst +++ b/doc/source/new.rst @@ -4,6 +4,54 @@ What's New Releases ------------- +v1.1.0 +^^^^^^^^^^^^^^ + +- Release 1.1.0 +- Computational routines now support multiple cores using OpenMP. See + :ref:`using_omp` for details on how to use this new feature. +- The CAPE routines should be noticeably faster, even in the single threaded + case (thank you supreethms1809!). +- :meth:`wrf.getvar` now works correctly with non-gridded NetCDF variables +- The cloud fraction diagnostic has changed: + - Users can now select their own cloud threshold levels, and can choose + between a vertical coordinate defined as height (AGL), height (MSL), or + pressure. + - The default vertical coordinate type has been changed to be height (AGL). + This ensures that clouds appear over mountainous regions. If you need + the old behavior, set the *vert_type* argument to 'pressure'. + - Fixed a bug involving the cloud threshold search algorithm, where if the + surface was higher than the threshold for a cloud level, the algorithm + would use whatever was there before (uninitialized variable bug). This + caused some interesting visualization issues when plotted. Now, whenever + the surface is above a cloud level threshold, a fill value is used to + indicate that data is unavailable for that location. +- The cartopy object for LambertConformal should now work correctly in the + southern hemisphere. +- Fixed a bug with the PolarStereographic projection missing a geobounds + argument (thank you hanschen!). +- Renamed the modules containing the 'get_product' routines used + by :meth:`wrf.getvar` to avoid naming conflicts with the raw computational + routine names. Users should be using :meth:`wrf.getvar` instead of these + routines, but for those that imported the 'get_product' routines + directly, you will need to modify your code. +- Fixed a uniqueness issue with the internal coordinate cache that was causing + crashes when input data is changed to a different file in a jupyter notebook + cell. +- Added code to better support building wheels on Windows (thank you letmaik!) +- Improved support for scipy.io.netcdf objects. +- Added a new 'zstag' diagnostic that returns the height values for the + vertically staggered grid. +- A DOI is now available for wrf-python. Please cite wrf-python if you are + using it for your research. (See :ref:`citation`) +- Fixed issue with vertcross and interpline not working correctly when a + projection object is used. Users will now have to supply the lower left + latitude and longitude corner point. +- Beginning with numpy 1.14, wrf-python can be built using the MSVC + compiler with gfortran. WRF-Python can now be built for Python 3.5+ on + services like AppVeyor. + + v1.0.5 ^^^^^^^^^^^^^^ diff --git a/doc/source/tutorial.rst b/doc/source/tutorial.rst new file mode 100644 index 0000000..29b68b5 --- /dev/null +++ b/doc/source/tutorial.rst @@ -0,0 +1,25 @@ +Tutorials +============= + +NCAR occasionally provides tutorials for wrf-python at various times +throughout the year. + +Below are the links to the upcoming and past tutorials. + +Upcoming Tutorials +--------------------- + +.. toctree:: + :maxdepth: 1 + + tutorials/tutorial_03_2018.rst + + +Past Tutorials +------------------ + +.. toctree:: + :maxdepth: 1 + + tutorials/wrf_workshop_2017.rst + diff --git a/doc/source/tutorials/tutorial_03_2018.rst b/doc/source/tutorials/tutorial_03_2018.rst new file mode 100644 index 0000000..7fb0bb9 --- /dev/null +++ b/doc/source/tutorials/tutorial_03_2018.rst @@ -0,0 +1,81 @@ +WRF-Python Tutorial 2018 +========================= + +NCAR will be providing a four hour tutorial for wrf-python on Wednesday, March +7, 2018 from 8 AM to 12 PM. The tutorial is free, but seating is limited to +only 16 students, so registration is required. + +The tutorial will take place at NCAR's corporate training center in Boulder, +Colorado. + +`Corporate Technical Training Center `_ +3085 Center Green Drive, Building CG-2, Room #3024 +Boulder, Colorado + +Overview +-------------- + +This tutorial provides an introduction to wrf-python. The tutorial is beginner +friendly for new users of wrf-python, but this is not an introduction to the Python +programming language (see :ref:`prereq`). Due to limited seating, if you +do not have any previous experience with Python, please do not register +for this tutorial. + +Computers will be provided, but feel free to use your own laptop if you prefer. +We will be covering how to install wrf-python via conda as part of the +tutorial. + +Students are encouraged to bring their own data sets, but data will be provided +if this is not an option. Students will be provided a jupyter notebook workbook +which can be modified to accommodate their data. + +Topics include: + +- How to install wrf-python via conda +- A brief introduction to jupyter notebook +- Overview of WRF data files +- WRF-Python basics +- Plotting with cartopy +- Overview of OpenMP features and other performance tips +- Open lab for students + + +Registration +--------------- + +Please register prior to February 21, 2018. The registration form is here: + +`Registration Form `_ + +Registration consists of a brief survey, which will help give the instructors +a brief overview of your background and will help tailor the tutorial to +your expectations. + +.. _prereq: + +Prerequisites +--------------- + +This tutorial assumes that you have basic knowledge of how to type commands +in to a terminal window using your preferred operating system. You +should know some basic directory commands like *cd*, *mkdir*, *cp*, *mv*. + +This tutorial assumes that you have prior experience programming in Python. +Below is a list of some Python concepts that you will see in the examples, +but don't worry if you aren't familiar with everything. + +- Opening a Python interpreter and entering commands. +- Importing packages via the import statement. +- Familiarity with some of the basic Python types: str, list, tuple, dict, bool, float, int, None. +- Creating a list, tuple, or dict with "[ ]", "( )", "{ }" syntax (e.g. my_list = [1,2,3,4,5]). +- Accessing dict/list/tuple items with the "x[ ]" syntax (e.g. my_list_item = my_list[0]). +- Slicing str/list/tuple with the ":" syntax (e.g. my_slice = my_list[1:3]). +- Using object methods and attributes with the "x.y" syntax (e.g. my_list.append(6)). +- Calling functions (e.g. result = some_function(x, y)) +- Familiarity with numpy would be helpful, as only a very brief introduction + is provided. +- Familiarity with matplotlib would be helpful, as only a very brief + introduction is provided. + + + diff --git a/doc/source/workshop.rst b/doc/source/tutorials/wrf_workshop_2017.rst similarity index 100% rename from doc/source/workshop.rst rename to doc/source/tutorials/wrf_workshop_2017.rst diff --git a/doc/source/user_api/index.rst b/doc/source/user_api/index.rst index 17b72e0..5d5d77d 100644 --- a/doc/source/user_api/index.rst +++ b/doc/source/user_api/index.rst @@ -74,7 +74,6 @@ the array object to a compiled extension. :toctree: ./generated/ wrf.to_np - Variable Extraction Routines ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -110,7 +109,7 @@ The routines below are used to assist with plotting. wrf.get_pyngl wrf.cartopy_xlim wrf.cartopy_ylim - + Raw Diagnostic Routines ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -154,6 +153,82 @@ sure they are removed before calling these routines. wrf.omega wrf.pw +OpenMP Runtime Library Routines +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The routines below are the OpenMP runtime libraries that have been wrapped +for wrf-python. The entire library (OpenMP 3.x) has been wrapped, but many of +the routines are only useful inside of an OpenMP thread, so they aren't useful +from inside the Python interpreter. Also, the Fortran code in wrf-python is +fairly simple in terms of threading, so features like nested threads aren't +used. The documentation below is split in to the useful OpenMP functions and +the less useful functions. + +The documentation for each routine was taken directly from the +`OpenMP Specification `_. +Read the specification for more details about these routines. + +Useful OpenMP Routines +***************************** + +The routines below are useful when called from within a Python program. These +routines handle setting the number of threads, setting up the scheduler, +and timing. + +It is also important to note that the OpenMP directives within the Fortran +code all specify a runtime scheduler. This means that the user can control +the type of scheduling to use from within their Python application by using the +routines below. + +.. autosummary:: + :nosignatures: + :toctree: ./generated/ + + wrf.omp_enabled + wrf.omp_set_num_threads + wrf.omp_get_max_threads + wrf.omp_get_num_procs + wrf.omp_set_dynamic + wrf.omp_get_dynamic + wrf.omp_set_schedule + wrf.omp_get_schedule + wrf.omp_get_thread_limit + wrf.omp_get_wtime + wrf.omp_get_wtick + +Less Useful OpenMP Routines +******************************* + +The routines below are less useful because wrf-python does not use nested +parallelism and some of the routines are only applicable when called from +within an OpenMP thread. + +.. autosummary:: + :nosignatures: + :toctree: ./generated/ + + wrf.omp_get_num_threads + wrf.omp_get_thread_num + wrf.omp_in_parallel + wrf.omp_set_nested + wrf.omp_get_nested + wrf.omp_set_max_active_levels + wrf.omp_get_max_active_levels + wrf.omp_get_level + wrf.omp_get_ancestor_thread_num + wrf.omp_get_team_size + wrf.omp_get_active_level + wrf.omp_in_final + wrf.omp_init_lock + wrf.omp_init_nest_lock + wrf.omp_destroy_lock + wrf.omp_destroy_nest_lock + wrf.omp_set_lock + wrf.omp_set_nest_lock + wrf.omp_unset_lock + wrf.omp_unset_nest_lock + wrf.omp_test_lock + wrf.omp_test_nest_lock Configuration Routines ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -182,6 +257,7 @@ in one place. wrf.disable_pyngl wrf.set_cache_size wrf.get_cache_size + wrf.omp_enabled Miscellaneous Routines @@ -216,6 +292,8 @@ them helpful for other purposes. wrf.getproj wrf.cache_item wrf.get_cached_item + wrf.ll_points + wrf.pairs_to_latlon ------------------------ @@ -247,7 +325,7 @@ use a single point for an (x, y) or (lat, lon) location. wrf.CoordPair CoordPair Methods -~~~~~~~~~~~~~~~~~~~~~~~ +************************ .. autosummary:: :nosignatures: @@ -274,7 +352,7 @@ The classes below are used to hold the projection information in the 'projection' entry within a :attr:`xarray.DataArray.attrs` attribute. Projection Base Class -~~~~~~~~~~~~~~~~~~~~~~~~ +****************************** The base class for all map projection types. @@ -285,7 +363,7 @@ The base class for all map projection types. wrf.WrfProj Projection Base Class Methods -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*********************************** The class methods for all projection types. @@ -303,7 +381,7 @@ The class methods for all projection types. Projection Subclasses -~~~~~~~~~~~~~~~~~~~~~~~~ +***************************** See :class:`wrf.WrfProj` for methods and attributes. diff --git a/fortran/calc_uh.f90 b/fortran/calc_uh.f90 index a893189..72acfe6 100644 --- a/fortran/calc_uh.f90 +++ b/fortran/calc_uh.f90 @@ -47,7 +47,7 @@ SUBROUTINE DCALCUH(nx, ny, nz, nzp1, zp, mapfct, dx, dy, uhmnhgt, uhmxhgt, us, & ! Misc local variables INTEGER :: i, j, k, kbot, ktop - REAL(KIND=8) :: twodx, twody, wgtlw, sum, wmean, wsum, wavg + REAL(KIND=8) :: twodx, twody, wgtlw, sum, wmean, wsum !,wavg REAL(KIND=8) :: helbot, heltop, wbot, wtop REAL(KIND=8) :: zbot, ztop @@ -61,21 +61,29 @@ SUBROUTINE DCALCUH(nx, ny, nz, nzp1, zp, mapfct, dx, dy, uhmnhgt, uhmxhgt, us, & twodx = 2.0*dx twody = 2.0*dy + + !$OMP PARALLEL + + !$OMP DO COLLAPSE(3) SCHEDULE(runtime) DO k=2,nz-2 DO j=2,ny-1 DO i=2,nx-1 - wavg = 0.5*(w(i,j,k)+w(i,j,k+1)) - tem1(i,j,k) = wavg*((vs(i+1,j,k) - vs(i-1,j,k))/(twodx*mapfct(i,j)) - & - (us(i,j+1,k) - us(i,j-1,k))/(twody*mapfct(i,j))) + !wavg = 0.5*(w(i,j,k)+w(i,j,k+1)) + tem1(i,j,k) = (0.5*(w(i,j,k)+w(i,j,k+1)))*((vs(i+1,j,k) - & + vs(i-1,j,k))/(twodx*mapfct(i,j)) - & + (us(i,j+1,k) - us(i,j-1,k))/(twody*mapfct(i,j))) tem2(i,j,k) = 0.5*(zp(i,j,k) + zp(i,j,k+1)) END DO END DO END DO + !$OMP END DO ! Integrate over depth uhminhgt to uhmxhgt AGL ! ! WRITE(6,'(a,f12.1,a,f12.1,a)') & ! 'Calculating UH from ',uhmnhgt,' to ',uhmxhgt,' m AGL' + !$OMP DO COLLAPSE(2) PRIVATE(i, j, k, zbot, ztop, kbot, ktop, & + !$OMP wgtlw, wbot, wtop, wsum, wmean, sum, helbot, heltop) SCHEDULE(runtime) DO j=2,ny-2 DO i=2,nx-2 zbot = zp(i,j,2) + uhmnhgt @@ -142,6 +150,9 @@ SUBROUTINE DCALCUH(nx, ny, nz, nzp1, zp, mapfct, dx, dy, uhmnhgt, uhmxhgt, us, & END IF END DO END DO + !$OMP END DO + + !$OMP END PARALLEL uh = uh*1000. ! Scale according to Kain et al. (2008) diff --git a/fortran/eqthecalc.f90 b/fortran/eqthecalc.f90 index a4cb6ba..65c069d 100644 --- a/fortran/eqthecalc.f90 +++ b/fortran/eqthecalc.f90 @@ -32,6 +32,8 @@ SUBROUTINE DEQTHECALC(qvp, tmk, prs, eth, miy, mjx, mkzh) REAL(KIND=8) :: tlcl INTEGER :: i, j, k + !$OMP PARALLEL DO COLLAPSE(3) PRIVATE(i, j, k, q, t, p, e, tlcl) & + !$OMP SCHEDULE(runtime) DO k = 1,mkzh DO j = 1,mjx DO i = 1,miy @@ -40,11 +42,12 @@ SUBROUTINE DEQTHECALC(qvp, tmk, prs, eth, miy, mjx, mkzh) p = prs(i,j,k)/100. e = q*p/(EPS + q) tlcl = TLCLC1/(LOG(t**TLCLC2/e) - TLCLC3) + TLCLC4 - eth(i,j,k) = t*(1000.D0/p)**(GAMMA*(1.D0 + GAMMAMD*q))* & + eth(i,j,k) = tmk(i,j,k)*(1000.D0/p)**(GAMMA*(1.D0 + GAMMAMD*q))* & EXP((THTECON1/tlcl - THTECON2)*q*(1.D0 + THTECON3*q)) END DO END DO END DO + !$OMP END PARALLEL DO RETURN diff --git a/fortran/omp.f90 b/fortran/omp.f90 new file mode 100644 index 0000000..dce6b1d --- /dev/null +++ b/fortran/omp.f90 @@ -0,0 +1,465 @@ + +MODULE omp_constants + INTEGER, PARAMETER :: fomp_sched_kind = 4 + INTEGER, PARAMETER :: fomp_lock_kind = 4 + INTEGER, PARAMETER :: fomp_nest_lock_kind = 8 + INTEGER(KIND=fomp_sched_kind), PARAMETER :: fomp_sched_static = 1 + INTEGER(KIND=fomp_sched_kind), PARAMETER :: fomp_sched_dynamic = 2 + INTEGER(KIND=fomp_sched_kind), PARAMETER :: fomp_sched_guided = 3 + INTEGER(KIND=fomp_sched_kind), PARAMETER :: fomp_sched_auto = 4 +END MODULE omp_constants + + +FUNCTION fomp_enabled() + + IMPLICIT NONE + + !f2py threadsafe + + LOGICAL :: fomp_enabled + + fomp_enabled = .FALSE. + +END FUNCTION fomp_enabled + + +SUBROUTINE fomp_set_num_threads(num_threads) + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER, INTENT(IN) :: num_threads + IF (.FALSE.) PRINT *, num_threads + +END SUBROUTINE fomp_set_num_threads + + +FUNCTION fomp_get_num_threads() + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER :: fomp_get_num_threads + + fomp_get_num_threads = -1 + +END FUNCTION fomp_get_num_threads + + +FUNCTION fomp_get_max_threads() + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER :: fomp_get_max_threads + + fomp_get_max_threads = -1 + +END FUNCTION fomp_get_max_threads + + +FUNCTION fomp_get_thread_num() + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER :: fomp_get_thread_num + + fomp_get_thread_num = -1 + +END FUNCTION fomp_get_thread_num + + +FUNCTION fomp_get_num_procs() + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER :: fomp_get_num_procs + + fomp_get_num_procs = -1 + +END FUNCTION fomp_get_num_procs + + +FUNCTION fomp_in_parallel() + + IMPLICIT NONE + + !f2py threadsafe + + LOGICAL :: fomp_in_parallel + + fomp_in_parallel = .FALSE. + +END FUNCTION fomp_in_parallel + + +SUBROUTINE fomp_set_dynamic(dynamic_threads) + + IMPLICIT NONE + + !f2py threadsafe + + LOGICAL, INTENT(IN) :: dynamic_threads + IF (.FALSE.) PRINT *, dynamic_threads + +END SUBROUTINE fomp_set_dynamic + + +FUNCTION fomp_get_dynamic() + + IMPLICIT NONE + + !f2py threadsafe + + LOGICAL :: fomp_get_dynamic + + fomp_get_dynamic = .FALSE. + +END FUNCTION fomp_get_dynamic + + +SUBROUTINE fomp_set_nested(nested) + + IMPLICIT NONE + + !f2py threadsafe + + LOGICAL, INTENT(IN) :: nested + IF (.FALSE.) PRINT *, nested + +END SUBROUTINE fomp_set_nested + + +FUNCTION fomp_get_nested() + + IMPLICIT NONE + + !f2py threadsafe + + LOGICAL :: fomp_get_nested + + fomp_get_nested = .FALSE. + +END FUNCTION fomp_get_nested + + +SUBROUTINE fomp_set_schedule(kind, modifier) + + USE omp_constants, ONLY : fomp_sched_kind + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER(KIND=fomp_sched_kind), INTENT(IN) :: kind + INTEGER, INTENT(IN) :: modifier + IF (.FALSE.) PRINT *, kind, modifier + +END SUBROUTINE fomp_set_schedule + + +SUBROUTINE fomp_get_schedule(kind, modifier) + + USE omp_constants, ONLY : fomp_sched_kind + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER(KIND=fomp_sched_kind), INTENT(OUT) :: kind + INTEGER, INTENT(OUT) :: modifier + + kind = -1 + modifier = -1 + +END SUBROUTINE fomp_get_schedule + + +FUNCTION fomp_get_thread_limit() + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER :: fomp_get_thread_limit + + fomp_get_thread_limit = -1 + +END FUNCTION fomp_get_thread_limit + + +SUBROUTINE fomp_set_max_active_levels(max_levels) + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER, INTENT(IN) :: max_levels + IF (.FALSE.) PRINT *, max_levels + +END SUBROUTINE fomp_set_max_active_levels + + +FUNCTION fomp_get_max_active_levels() + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER :: fomp_get_max_active_levels + + fomp_get_max_active_levels = -1 + +END FUNCTION fomp_get_max_active_levels + + +FUNCTION fomp_get_level() + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER :: fomp_get_level + + fomp_get_level = -1 + +END FUNCTION fomp_get_level + + +FUNCTION fomp_get_ancestor_thread_num(level) + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER, INTENT(IN) :: level + INTEGER :: fomp_get_ancestor_thread_num + IF (.FALSE.) PRINT *, level + + fomp_get_ancestor_thread_num = -1 + +END FUNCTION fomp_get_ancestor_thread_num + + +FUNCTION fomp_get_team_size(level) + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER, INTENT(IN) :: level + INTEGER :: fomp_get_team_size + IF (.FALSE.) PRINT *, level + + fomp_get_team_size = -1 + +END FUNCTION fomp_get_team_size + + +FUNCTION fomp_get_active_level() + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER :: fomp_get_active_level + + fomp_get_active_level = -1 + +END FUNCTION fomp_get_active_level + + +FUNCTION fomp_in_final() + + IMPLICIT NONE + + !f2py threadsafe + + LOGICAL :: fomp_in_final + + fomp_in_final = .FALSE. + +END FUNCTION fomp_in_final + + +SUBROUTINE fomp_init_lock(svar) + + USE omp_constants, ONLY : fomp_lock_kind + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER(KIND=fomp_lock_kind), INTENT(OUT) :: svar + + svar = -1 + +END SUBROUTINE fomp_init_lock + + +SUBROUTINE fomp_init_nest_lock(nvar) + + USE omp_constants, ONLY : fomp_nest_lock_kind + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER(KIND=fomp_nest_lock_kind), INTENT(OUT) :: nvar + + nvar = -1 + +END SUBROUTINE fomp_init_nest_lock + + +SUBROUTINE fomp_destroy_lock(svar) + + USE omp_constants, ONLY : fomp_lock_kind + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER(KIND=fomp_lock_kind), INTENT(INOUT) :: svar + IF (.FALSE.) PRINT *, svar + +END SUBROUTINE fomp_destroy_lock + + +SUBROUTINE fomp_destroy_nest_lock(nvar) + + USE omp_constants, ONLY : fomp_nest_lock_kind + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER(KIND=fomp_nest_lock_kind), INTENT(INOUT) :: nvar + IF (.FALSE.) PRINT *, nvar + +END SUBROUTINE fomp_destroy_nest_lock + + +SUBROUTINE fomp_set_lock(svar) + + USE omp_constants, ONLY : fomp_lock_kind + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER(KIND=fomp_lock_kind), INTENT(INOUT) :: svar + IF (.FALSE.) PRINT *, svar + +END SUBROUTINE fomp_set_lock + + +SUBROUTINE fomp_set_nest_lock(nvar) + + USE omp_constants, ONLY : fomp_nest_lock_kind + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER(KIND=fomp_nest_lock_kind), INTENT(INOUT) :: nvar + IF (.FALSE.) PRINT *, nvar + +END SUBROUTINE fomp_set_nest_lock + + +SUBROUTINE fomp_unset_lock(svar) + + USE omp_constants, ONLY : fomp_lock_kind + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER(KIND=fomp_lock_kind), INTENT(INOUT) :: svar + IF (.FALSE.) PRINT *, svar + +END SUBROUTINE fomp_unset_lock + + +SUBROUTINE fomp_unset_nest_lock(nvar) + + USE omp_constants, ONLY : fomp_nest_lock_kind + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER(KIND=fomp_nest_lock_kind), INTENT(INOUT) :: nvar + IF (.FALSE.) PRINT *, nvar + +END SUBROUTINE fomp_unset_nest_lock + + +FUNCTION fomp_test_lock(svar) + + USE omp_constants, ONLY : fomp_lock_kind + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER(KIND=fomp_lock_kind), INTENT(INOUT) :: svar + LOGICAL :: fomp_test_lock + IF (.FALSE.) PRINT *, svar + + fomp_test_lock = .FALSE. + +END FUNCTION fomp_test_lock + + +FUNCTION fomp_test_nest_lock(nvar) + + USE omp_constants, ONLY : fomp_nest_lock_kind + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER(KIND=fomp_nest_lock_kind), INTENT(INOUT) :: nvar + INTEGER :: fomp_test_nest_lock + IF (.FALSE.) PRINT *, nvar + + fomp_test_nest_lock = -1 + +END FUNCTION fomp_test_nest_lock + + +FUNCTION fomp_get_wtime() + + IMPLICIT NONE + + !f2py threadsafe + + REAL (KIND=8) :: fomp_get_wtime + + fomp_get_wtime = -1 + +END FUNCTION fomp_get_wtime + + +FUNCTION fomp_get_wtick() + + IMPLICIT NONE + + !f2py threadsafe + + REAL (KIND=8) :: fomp_get_wtick + + fomp_get_wtick = -1 + +END FUNCTION fomp_get_wtick + + diff --git a/fortran/ompgen.F90 b/fortran/ompgen.F90 new file mode 100644 index 0000000..6ae48e1 --- /dev/null +++ b/fortran/ompgen.F90 @@ -0,0 +1,753 @@ +MODULE omp_constants +#ifdef _OPENMP + USE omp_lib + ! f2py can't figure this out without me making a map of these kinds to + ! c types, so we're going to have to hard code the kinds below. + !INTEGER, PARAMETER :: fomp_sched_kind = omp_sched_kind + !INTEGER, PARAMETER :: fomp_nest_lock_kind = omp_nest_lock_kind + !INTEGER, PARAMETER :: fomp_lock_kind = omp_lock_kind + + ! Note: Defining these specifically is the only way I can get f2py to + ! work without doing the same thing in a mapping file. The values below + ! are for GNU on 64bit. This may not be entirely correct for non-GNU + ! compilers. In particular, it will be the locks that segfault/crash if + ! this is wrong. +#if defined(__GFORTRAN__) +#if (__SIZEOF_POINTER__ == 8) + INTEGER, PARAMETER :: fomp_sched_kind = 4 + INTEGER, PARAMETER :: fomp_lock_kind = 8 + INTEGER, PARAMETER :: fomp_nest_lock_kind = 8 +#else + INTEGER, PARAMETER :: fomp_sched_kind = 4 + INTEGER, PARAMETER :: fomp_lock_kind = 4 + INTEGER, PARAMETER :: fomp_nest_lock_kind = 4 +#endif +#elif defined(__INTEL_COMPILER_BUILD_DATE) +#if defined(__x86_64__) || defined(_M_X64) + INTEGER, PARAMETER :: fomp_sched_kind = 4 + INTEGER, PARAMETER :: fomp_lock_kind = 8 + INTEGER, PARAMETER :: fomp_nest_lock_kind = 8 +#else + INTEGER, PARAMETER :: fomp_sched_kind = 4 + INTEGER, PARAMETER :: fomp_lock_kind = 4 + INTEGER, PARAMETER :: fomp_nest_lock_kind = 4 +#endif +#elif defined(__PGI) +#if defined(__x86_64__) + INTEGER, PARAMETER :: fomp_sched_kind = 4 + INTEGER, PARAMETER :: fomp_lock_kind = 8 + INTEGER, PARAMETER :: fomp_nest_lock_kind = 8 +#else + INTEGER, PARAMETER :: fomp_sched_kind = 4 + INTEGER, PARAMETER :: fomp_lock_kind = 4 + INTEGER, PARAMETER :: fomp_nest_lock_kind = 4 +#endif +#endif + + INTEGER(KIND=fomp_sched_kind), PARAMETER :: fomp_sched_static = omp_sched_static + INTEGER(KIND=fomp_sched_kind), PARAMETER :: fomp_sched_dynamic = omp_sched_dynamic + INTEGER(KIND=fomp_sched_kind), PARAMETER :: fomp_sched_guided = omp_sched_guided + INTEGER(KIND=fomp_sched_kind), PARAMETER :: fomp_sched_auto = omp_sched_auto +#else + INTEGER, PARAMETER :: fomp_sched_kind = 4 + INTEGER, PARAMETER :: fomp_lock_kind = 4 + INTEGER, PARAMETER :: fomp_nest_lock_kind = 8 + INTEGER(KIND=fomp_sched_kind), PARAMETER :: fomp_sched_static = 1 + INTEGER(KIND=fomp_sched_kind), PARAMETER :: fomp_sched_dynamic = 2 + INTEGER(KIND=fomp_sched_kind), PARAMETER :: fomp_sched_guided = 3 + INTEGER(KIND=fomp_sched_kind), PARAMETER :: fomp_sched_auto = 4 +#endif + +END MODULE omp_constants + + +FUNCTION fomp_enabled() + + IMPLICIT NONE + + !f2py threadsafe + + LOGICAL :: fomp_enabled + +#ifdef _OPENMP + fomp_enabled = .TRUE. +#else + fomp_enabled = .FALSE. +#endif + +END FUNCTION fomp_enabled + + +SUBROUTINE fomp_set_num_threads(num_threads) +#ifdef _OPENMP + USE omp_lib +#endif + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER, INTENT(IN) :: num_threads + +#ifdef _OPENMP + CALL omp_set_num_threads(num_threads) +#else + IF (.FALSE.) PRINT *, num_threads +#endif + + +END SUBROUTINE fomp_set_num_threads + + +FUNCTION fomp_get_num_threads() +#ifdef _OPENMP + USE omp_lib +#endif + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER :: fomp_get_num_threads + +#ifdef _OPENMP + fomp_get_num_threads = omp_get_num_threads() +#else + fomp_get_num_threads = -1 +#endif + +END FUNCTION fomp_get_num_threads + + +FUNCTION fomp_get_max_threads() +#ifdef _OPENMP + USE omp_lib +#endif + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER :: fomp_get_max_threads + +#ifdef _OPENMP + fomp_get_max_threads = omp_get_max_threads() +#else + fomp_get_max_threads = -1 +#endif + +END FUNCTION fomp_get_max_threads + + +FUNCTION fomp_get_thread_num() +#ifdef _OPENMP + USE omp_lib +#endif + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER :: fomp_get_thread_num + +#ifdef _OPENMP + fomp_get_thread_num = omp_get_thread_num() +#else + fomp_get_thread_num = -1 +#endif + +END FUNCTION fomp_get_thread_num + + +FUNCTION fomp_get_num_procs() +#ifdef _OPENMP + USE omp_lib +#endif + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER :: fomp_get_num_procs + +#ifdef _OPENMP + fomp_get_num_procs = omp_get_num_procs() +#else + fomp_get_num_procs = -1 +#endif + +END FUNCTION fomp_get_num_procs + + +FUNCTION fomp_in_parallel() +#ifdef _OPENMP + USE omp_lib +#endif + + IMPLICIT NONE + + !f2py threadsafe + + LOGICAL :: fomp_in_parallel + +#ifdef _OPENMP + fomp_in_parallel = omp_in_parallel() +#else + fomp_in_parallel = .FALSE. +#endif + +END FUNCTION fomp_in_parallel + + +SUBROUTINE fomp_set_dynamic(dynamic_threads) +#ifdef _OPENMP + USE omp_lib +#endif + + IMPLICIT NONE + + !f2py threadsafe + + LOGICAL, INTENT(IN) :: dynamic_threads + +#ifdef _OPENMP + CALL omp_set_dynamic(dynamic_threads) +#else + IF (.FALSE.) PRINT *, dynamic_threads +#endif + +END SUBROUTINE fomp_set_dynamic + + +FUNCTION fomp_get_dynamic() +#ifdef _OPENMP + USE omp_lib +#endif + + IMPLICIT NONE + + !f2py threadsafe + + LOGICAL :: fomp_get_dynamic + +#ifdef _OPENMP + fomp_get_dynamic = omp_get_dynamic() +#else + fomp_get_dynamic = .FALSE. +#endif + +END FUNCTION fomp_get_dynamic + + +SUBROUTINE fomp_set_nested(nested) +#ifdef _OPENMP + USE omp_lib +#endif + + IMPLICIT NONE + + !f2py threadsafe + + LOGICAL, INTENT(IN) :: nested + +#ifdef _OPENMP + CALL omp_set_nested(nested) +#else + IF (.FALSE.) PRINT *, nested +#endif + +END SUBROUTINE fomp_set_nested + + +FUNCTION fomp_get_nested() +#ifdef _OPENMP + USE omp_lib +#endif + + IMPLICIT NONE + + !f2py threadsafe + + LOGICAL :: fomp_get_nested + +#ifdef _OPENMP + fomp_get_nested = omp_get_nested() +#else + fomp_get_nested = .FALSE. +#endif + +END FUNCTION fomp_get_nested + + +SUBROUTINE fomp_set_schedule(kind, modifier) +#ifdef _OPENMP + USE omp_lib +#endif + USE omp_constants, ONLY : fomp_sched_kind + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER(KIND=fomp_sched_kind), INTENT(IN) :: kind + INTEGER, INTENT(IN) :: modifier + +#ifdef _OPENMP + CALL omp_set_schedule(kind, modifier) +#else + IF (.FALSE.) PRINT *, kind, modifier +#endif + +END SUBROUTINE fomp_set_schedule + + +SUBROUTINE fomp_get_schedule(kind, modifier) +#ifdef _OPENMP + USE omp_lib +#endif + USE omp_constants, ONLY : fomp_sched_kind + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER(KIND=fomp_sched_kind), INTENT(OUT) :: kind + INTEGER, INTENT(OUT) :: modifier + +#ifdef _OPENMP + CALL omp_get_schedule(kind, modifier) +#else + kind = -1 + modifier = -1 +#endif + +END SUBROUTINE fomp_get_schedule + + +FUNCTION fomp_get_thread_limit() +#ifdef _OPENMP + USE omp_lib +#endif + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER :: fomp_get_thread_limit + +#ifdef _OPENMP + fomp_get_thread_limit = omp_get_thread_limit() +#else + fomp_get_thread_limit = -1 +#endif + +END FUNCTION fomp_get_thread_limit + + +SUBROUTINE fomp_set_max_active_levels(max_levels) +#ifdef _OPENMP + USE omp_lib +#endif + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER, INTENT(IN) :: max_levels + +#ifdef _OPENMP + CALL omp_set_max_active_levels(max_levels) +#else + IF (.FALSE.) PRINT *, max_levels +#endif + +END SUBROUTINE fomp_set_max_active_levels + + +FUNCTION fomp_get_max_active_levels() +#ifdef _OPENMP + USE omp_lib +#endif + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER :: fomp_get_max_active_levels + +#ifdef _OPENMP + fomp_get_max_active_levels = omp_get_max_active_levels() +#else + fomp_get_max_active_levels = -1 +#endif + +END FUNCTION fomp_get_max_active_levels + + +FUNCTION fomp_get_level() +#ifdef _OPENMP + USE omp_lib +#endif + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER :: fomp_get_level + +#ifdef _OPENMP + fomp_get_level = omp_get_level() +#else + fomp_get_level = -1 +#endif + +END FUNCTION fomp_get_level + + +FUNCTION fomp_get_ancestor_thread_num(level) +#ifdef _OPENMP + USE omp_lib +#endif + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER, INTENT(IN) :: level + INTEGER :: fomp_get_ancestor_thread_num + +#ifdef _OPENMP + fomp_get_ancestor_thread_num = omp_get_ancestor_thread_num(level) +#else + IF (.FALSE.) PRINT *, level + fomp_get_ancestor_thread_num = -1 +#endif + +END FUNCTION fomp_get_ancestor_thread_num + + +FUNCTION fomp_get_team_size(level) +#ifdef _OPENMP + USE omp_lib +#endif + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER, INTENT(IN) :: level + INTEGER :: fomp_get_team_size + +#ifdef _OPENMP + fomp_get_team_size = omp_get_team_size(level) +#else + IF (.FALSE.) PRINT *, level + fomp_get_team_size = -1 +#endif + +END FUNCTION fomp_get_team_size + + +FUNCTION fomp_get_active_level() +#ifdef _OPENMP + USE omp_lib +#endif + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER :: fomp_get_active_level + +#ifdef _OPENMP + fomp_get_active_level = omp_get_active_level() +#else + fomp_get_active_level = -1 +#endif + +END FUNCTION fomp_get_active_level + + +FUNCTION fomp_in_final() +#ifdef _OPENMP + USE omp_lib +#endif + + IMPLICIT NONE + + !f2py threadsafe + + LOGICAL :: fomp_in_final + +#ifdef _OPENMP + fomp_in_final = omp_in_final() +#else + fomp_in_final = .FALSE. +#endif + +END FUNCTION fomp_in_final + + +SUBROUTINE fomp_init_lock(svar) +#ifdef _OPENMP + USE omp_lib +#endif + USE omp_constants, ONLY : fomp_lock_kind + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER(KIND=fomp_lock_kind), INTENT(OUT) :: svar + +#ifdef _OPENMP + CALL omp_init_lock(svar) +#else + svar = -1 +#endif + +END SUBROUTINE fomp_init_lock + + +SUBROUTINE fomp_init_nest_lock(nvar) +#ifdef _OPENMP + USE omp_lib +#endif + USE omp_constants, ONLY : fomp_nest_lock_kind + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER(KIND=fomp_nest_lock_kind), INTENT(OUT) :: nvar + +#ifdef _OPENMP + CALL omp_init_nest_lock(nvar) +#else + nvar = -1 +#endif + +END SUBROUTINE fomp_init_nest_lock + + +SUBROUTINE fomp_destroy_lock(svar) +#ifdef _OPENMP + USE omp_lib +#endif + USE omp_constants, ONLY : fomp_lock_kind + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER(KIND=fomp_lock_kind), INTENT(INOUT) :: svar + +#ifdef _OPENMP + CALL omp_destroy_lock(svar) +#else + IF (.FALSE.) PRINT *, svar +#endif + + +END SUBROUTINE fomp_destroy_lock + + +SUBROUTINE fomp_destroy_nest_lock(nvar) +#ifdef _OPENMP + USE omp_lib +#endif + USE omp_constants, ONLY : fomp_nest_lock_kind + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER(KIND=fomp_nest_lock_kind), INTENT(INOUT) :: nvar + +#ifdef _OPENMP + CALL omp_destroy_nest_lock(nvar) +#else + IF (.FALSE.) PRINT *, nvar +#endif + +END SUBROUTINE fomp_destroy_nest_lock + + +SUBROUTINE fomp_set_lock(svar) +#ifdef _OPENMP + USE omp_lib +#endif + USE omp_constants, ONLY : fomp_lock_kind + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER(KIND=fomp_lock_kind), INTENT(INOUT) :: svar + +#ifdef _OPENMP + CALL omp_set_lock(svar) +#else + IF (.FALSE.) PRINT *, svar +#endif + +END SUBROUTINE fomp_set_lock + + +SUBROUTINE fomp_set_nest_lock(nvar) +#ifdef _OPENMP + USE omp_lib +#endif + USE omp_constants, ONLY : fomp_nest_lock_kind + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER(KIND=fomp_nest_lock_kind), INTENT(INOUT) :: nvar + +#ifdef _OPENMP + CALL omp_set_nest_lock(nvar) +#else + IF (.FALSE.) PRINT *, nvar +#endif + +END SUBROUTINE fomp_set_nest_lock + + +SUBROUTINE fomp_unset_lock(svar) +#ifdef _OPENMP + USE omp_lib +#endif + USE omp_constants, ONLY : fomp_lock_kind + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER(KIND=fomp_lock_kind), INTENT(INOUT) :: svar + +#ifdef _OPENMP + CALL omp_unset_lock(svar) +#else + IF (.FALSE.) PRINT *, svar +#endif + +END SUBROUTINE fomp_unset_lock + + +SUBROUTINE fomp_unset_nest_lock(nvar) +#ifdef _OPENMP + USE omp_lib +#endif + USE omp_constants, ONLY : fomp_nest_lock_kind + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER(KIND=fomp_nest_lock_kind), INTENT(INOUT) :: nvar + +#ifdef _OPENMP + CALL omp_unset_nest_lock(nvar) +#else + IF (.FALSE.) PRINT *, nvar +#endif + +END SUBROUTINE fomp_unset_nest_lock + + +FUNCTION fomp_test_lock(svar) +#ifdef _OPENMP + USE omp_lib +#endif + USE omp_constants, ONLY : fomp_lock_kind + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER(KIND=fomp_lock_kind), INTENT(INOUT) :: svar + LOGICAL :: fomp_test_lock + +#ifdef _OPENMP + fomp_test_lock = omp_test_lock(svar) +#else + IF (.FALSE.) PRINT *, svar + fomp_test_lock = .FALSE. +#endif + + + +END FUNCTION fomp_test_lock + + +FUNCTION fomp_test_nest_lock(nvar) +#ifdef _OPENMP + USE omp_lib +#endif + USE omp_constants, ONLY : fomp_nest_lock_kind + + IMPLICIT NONE + + !f2py threadsafe + + INTEGER(KIND=fomp_nest_lock_kind), INTENT(INOUT) :: nvar + INTEGER :: fomp_test_nest_lock + +#ifdef _OPENMP + fomp_test_nest_lock = omp_test_nest_lock(nvar) +#else + IF (.FALSE.) PRINT *, nvar + fomp_test_nest_lock = -1 +#endif + + + +END FUNCTION fomp_test_nest_lock + + +FUNCTION fomp_get_wtime() +#ifdef _OPENMP + USE omp_lib +#endif + + IMPLICIT NONE + + !f2py threadsafe + + REAL (KIND=8) :: fomp_get_wtime + +#ifdef _OPENMP + fomp_get_wtime = omp_get_wtime() +#else + fomp_get_wtime = -1 +#endif + + + +END FUNCTION fomp_get_wtime + + +FUNCTION fomp_get_wtick() +#ifdef _OPENMP + USE omp_lib +#endif + + IMPLICIT NONE + + !f2py threadsafe + + REAL (KIND=8) :: fomp_get_wtick + +#ifdef _OPENMP + fomp_get_wtick = omp_get_wtick() +#else + fomp_get_wtick = -1 +#endif + + + +END FUNCTION fomp_get_wtick + + diff --git a/fortran/rip_cape.f90 b/fortran/rip_cape.f90 index 08cf9a0..c92be3f 100644 --- a/fortran/rip_cape.f90 +++ b/fortran/rip_cape.f90 @@ -37,9 +37,9 @@ END FUNCTION TVIRTUAL REAL(KIND=8) FUNCTION TONPSADIABAT(thte, prs, psadithte, psadiprs, psaditmk, gamma,& errstat, errmsg) USE wrf_constants, ONLY : ALGERR - +!!$OMP DECLARE SIMD (TONPSADIABAT) +!!uniform(thte,prs,psadithte,psadiprs,psaditmk) !f2py threadsafe - !f2py intent(in,out) :: cape, cin IMPLICIT NONE REAL(KIND=8), INTENT(IN) :: thte @@ -57,8 +57,9 @@ REAL(KIND=8) FUNCTION TONPSADIABAT(thte, prs, psadithte, psadiprs, psaditmk, gam REAL(KIND=8) :: fracjt2 REAL(KIND=8) :: fracip REAL(KIND=8) :: fracip2 - - INTEGER :: ip, ipch, jt, jtch + + INTEGER :: l1, h1, mid1, rang1, l2, h2, mid2, rang2 + INTEGER :: ip, jt ! This function gives the temperature (in K) on a moist adiabat ! (specified by thte in K) given pressure in hPa. It uses a @@ -78,22 +79,53 @@ REAL(KIND=8) FUNCTION TONPSADIABAT(thte, prs, psadithte, psadiprs, psaditmk, gam ! Otherwise, look for the given thte/prs point in the lookup table. jt = -1 - DO jtch = 1, 150-1 - IF (thte .GE. psadithte(jtch) .AND. thte .LT. psadithte(jtch+1)) THEN - jt = jtch - EXIT - !GO TO 213 + l1 = 1 + h1 = 149 + rang1 = h1 - l1 + mid1 = (h1 + l1) / 2 + DO WHILE(rang1 .GT. 1) + IF (thte .GE. psadithte(mid1)) THEN + l1 = mid1 + ELSE + h1 = mid1 END IF + rang1 = h1 - l1 + mid1 = (h1 + l1) / 2 END DO + jt = l1 + + ! DO jtch = 1, 150-1 + ! IF (thte .GE. psadithte(jtch) .AND. thte .LT. psadithte(jtch+1)) THEN + ! jt = jtch + ! EXIT + ! !GO TO 213 + ! END IF + ! END DO ip = -1 - DO ipch = 1, 150-1 - IF (prs .LE. psadiprs(ipch) .AND. prs .GT. psadiprs(ipch+1)) THEN - ip = ipch - EXIT - !GO TO 215 + l2 = 1 + h2 = 149 + rang2 = h2 - l2 + mid2 = (h2 + l2) / 2 + DO WHILE(rang2 .GT. 1) + IF (prs .LE. psadiprs(mid2)) THEN + l2 = mid2 + ELSE + h2 = mid2 END IF + rang2 = h2 - l2 + mid2 = (h2 + l2) / 2 END DO + ip = l2 + + ! ip = -1 + ! DO ipch = 1, 150-1 + ! IF (prs .LE. psadiprs(ipch) .AND. prs .GT. psadiprs(ipch+1)) THEN + ! ip = ipch + ! EXIT + ! !GO TO 215 + ! END IF + ! END DO IF (jt .EQ. -1 .OR. ip .EQ. -1) THEN ! Set the error and return @@ -185,35 +217,36 @@ END SUBROUTINE DLOOKUP_TABLE ! which case it assumes the lower bounding pressure level is as far ! below the lowest vertical level as the upper bounding pressure ! level is above. -SUBROUTINE DPFCALC(prs, sfp, pf, miy, mjx, mkzh, ter_follow) +SUBROUTINE DPFCALC(prs, sfp, pf, mix, mjy, mkzh, ter_follow) - REAL(KIND=8), DIMENSION(miy,mjx,mkzh), INTENT(IN) :: prs - REAL(KIND=8), DIMENSION(miy,mjx), INTENT(IN) :: sfp - REAL(KIND=8), DIMENSION(miy,mjx,mkzh), INTENT(OUT) :: pf - INTEGER, INTENT(IN) :: ter_follow,miy,mjx,mkzh + REAL(KIND=8), DIMENSION(mkzh,mix,mjy), INTENT(IN) :: prs + REAL(KIND=8), DIMENSION(mix,mjy), INTENT(IN) :: sfp + REAL(KIND=8), DIMENSION(mkzh,mix,mjy), INTENT(OUT) :: pf + INTEGER, INTENT(IN) :: ter_follow,mix,mjy,mkzh INTEGER :: i,j,k - - ! do j=1,mjx-1 Artifact of MM5 - DO j = 1,mjx - ! do i=1,miy-1 staggered grid - DO i = 1,miy - DO k = 1,mkzh - IF (k .EQ. mkzh) THEN - ! terrain-following data - IF (ter_follow .EQ. 1) THEN - pf(i,j,k) = sfp(i,j) - ! pressure-level data - ELSE - pf(i,j,k) = .5D0 * (3.D0*prs(i,j,k) - prs(i,j,k-1)) - END IF - ELSE - pf(i,j,k) = .5D0 * (prs(i,j,k+1) + prs(i,j,k)) - END IF - END DO - END DO + + !$OMP PARALLEL DO COLLAPSE(3) SCHEDULE(runtime) + DO j = 1,mjy + DO i = 1,mix + DO k = 1,mkzh + IF (k .EQ. mkzh) THEN + ! terrain-following data + IF (ter_follow .EQ. 1) THEN + pf(k,i,j) = sfp(i,j) + ! pressure-level data + ELSE + pf(k,i,j) = .5D0 * (3.D0*prs(k,i,j) - prs(k-1,i,j)) + END IF + ELSE + pf(k,i,j) = .5D0 * (prs(k+1,i,j) + prs(k,i,j)) + END IF + END DO + END DO END DO + !$OMP END PARALLEL DO + RETURN END SUBROUTINE DPFCALC @@ -224,50 +257,367 @@ END SUBROUTINE DPFCALC ! ! !DESCRIPTION: ! -! If i3dflag=1, this routine calculates CAPE and CIN (in m**2/s**2, +! This routine calculates CAPE and CIN (in m**2/s**2, ! or J/kg) for every grid point in the entire 3D domain (treating -! each grid point as a parcel). If i3dflag=0, then it -! calculates CAPE and CIN only for the parcel with max theta-e in +! each grid point as a parcel). +! + + +! Important! The z-indexes must be arranged so that mkzh (max z-index) is the +! surface pressure. So, pressure must be ordered in ascending order before +! calling this routine. Other variables must be ordered the same (p,tk,q,z). + +! Also, be advised that missing data values are not checked during the computation. +! Also also, Pressure must be hPa + +! NCLFORTSTART +SUBROUTINE DCAPECALC3D(prs,tmk,qvp,ght,ter,sfp,cape,cin,& + cmsg,mix,mjy,mkzh,ter_follow,& + psafile, errstat, errmsg) + USE wrf_constants, ONLY : CELKEL, G, EZERO, ESLCON1, ESLCON2, & + EPS, RD, CP, GAMMA, CPMD, RGASMD, GAMMAMD, TLCLC1, & + TLCLC2, TLCLC3, TLCLC4, THTECON1, THTECON2, THTECON3 + + !USE omp_lib + IMPLICIT NONE + + !f2py threadsafe + !f2py intent(in,out) :: cape, cin + + INTEGER, INTENT(IN) :: mix, mjy, mkzh, ter_follow + REAL(KIND=8), DIMENSION(mix,mjy,mkzh), INTENT(IN) :: prs + REAL(KIND=8), DIMENSION(mix,mjy,mkzh), INTENT(IN) :: tmk + REAL(KIND=8), DIMENSION(mix,mjy,mkzh), INTENT(IN) :: qvp + REAL(KIND=8), DIMENSION(mix,mjy,mkzh), INTENT(IN) :: ght + REAL(KIND=8), DIMENSION(mix,mjy), INTENT(IN) :: ter + REAL(KIND=8), DIMENSION(mix,mjy), INTENT(IN) ::sfp + REAL(KIND=8), DIMENSION(mix,mjy,mkzh), INTENT(OUT) :: cape + REAL(KIND=8), DIMENSION(mix,mjy,mkzh), INTENT(OUT) :: cin + REAL(KIND=8), INTENT(IN) :: cmsg + CHARACTER(LEN=*), INTENT(IN) :: psafile + INTEGER, INTENT(INOUT) :: errstat + CHARACTER(LEN=*), INTENT(INOUT) :: errmsg + +! NCLFORTEND + + ! local variables + INTEGER :: i, j, k, ilcl, kel, kk, klcl, klev, klfc, kmax, kpar + REAL(KIND=8) :: tlcl, zlcl + REAL(KIND=8) :: ethpari, qvppari, tmkpari + REAL(KIND=8) :: facden, qvplift, tmklift, tvenv, tvlift, ghtlift + REAL(KIND=8) :: eslift, tmkenv, qvpenv, tonpsadiabat + REAL(KIND=8) :: benamin, dz + REAL(KIND=8), DIMENSION(150) :: buoy, zrel, benaccum + REAL(KIND=8), DIMENSION(mkzh,mix,mjy) :: prsf + REAL(KIND=8), DIMENSION(150) :: psadithte, psadiprs + REAL(KIND=8), DIMENSION(150,150) :: psaditmk + LOGICAL :: elfound + + REAL(KIND=8), DIMENSION(mkzh,mix,mjy) :: prs_new + REAL(KIND=8), DIMENSION(mkzh,mix,mjy) :: tmk_new + REAL(KIND=8), DIMENSION(mkzh,mix,mjy) :: qvp_new + REAL(KIND=8), DIMENSION(mkzh,mix,mjy) :: ght_new + + ! To remove compiler warnings + tmkpari = 0 + qvppari = 0 + klev = 0 + klcl = 0 + kel = 0 + IF (.FALSE.) PRINT *,ter + + + ! the comments were taken from a mark stoelinga email, 23 apr 2007, + ! in response to a user getting the "outside of lookup table bounds" + ! error message. + + ! tmkpari - initial temperature of parcel, k + ! values of 300 okay. (not sure how much from this you can stray.) + + ! prspari - initial pressure of parcel, hpa + ! values of 980 okay. (not sure how much from this you can stray.) + + ! thtecon1, thtecon2, thtecon3 + ! these are all constants, the first in k and the other two have + ! no units. values of 3376, 2.54, and 0.81 were stated as being + ! okay. + + ! tlcl - the temperature at the parcel's lifted condensation level, k + ! should be a reasonable atmospheric temperature around 250-300 k + ! (398 is "way too high") + + ! qvppari - the initial water vapor mixing ratio of the parcel, + ! kg/kg (should range from 0.000 to 0.025) + ! + + ! calculated the pressure at full sigma levels (a set of pressure + ! levels that bound the layers represented by the vertical grid points) + + !$OMP PARALLEL DO COLLAPSE(3) SCHEDULE(runtime) + DO j = 1,mjy + DO i = 1,mix + DO k = 1,mkzh + prs_new(k,i,j) = prs(i,j,k) + tmk_new(k,i,j) = tmk(i,j,k) + qvp_new(k,i,j) = qvp(i,j,k) + ght_new(k,i,j) = ght(i,j,k) + END DO + END DO + END DO + !$OMP END PARALLEL DO + + CALL DPFCALC(prs_new, sfp, prsf, mix, mjy, mkzh, ter_follow) + + ! before looping, set lookup table for getting temperature on + ! a pseudoadiabat. + + CALL DLOOKUP_TABLE(psadithte, psadiprs, psaditmk, psafile, errstat, errmsg) + + IF (errstat .NE. 0) THEN + RETURN + END IF + + !$OMP PARALLEL DO COLLAPSE(2) PRIVATE(tlcl, ethpari, & + !$OMP zlcl, kk, ilcl, klcl, tmklift, tvenv, tvlift, ghtlift, & + !$OMP facden, tmkenv, qvpenv, eslift, qvplift, buoy, benamin, & + !$OMP benaccum, zrel, kmax, dz, elfound, & + !$OMP kel, klfc, & + !$OMP i, j, k, kpar) SCHEDULE(runtime) + DO j = 1,mjy + DO i = 1,mix + cape(i,j,1) = 0.D0 + cin(i,j,1) = 0.D0 + + !!$OMP SIMD + DO kpar = 2, mkzh + + ! Calculate temperature and moisture properties of parcel + ! (note, qvppari and tmkpari already calculated above for 2d case.) + + tlcl = TLCLC1/(LOG(tmk_new(kpar,i,j)**TLCLC2/(MAX(1.D-20,qvp_new(kpar,i,j)*prs_new(kpar,i,j)/ & + (EPS + qvp_new(kpar,i,j))))) - TLCLC3) + TLCLC4 + + ethpari = tmk_new(kpar,i,j)*(1000.D0/prs_new(kpar,i,j))**(GAMMA*(1.D0 + GAMMAMD*qvp_new(kpar,i,j)))* & + EXP((THTECON1/tlcl - THTECON2)*qvp_new(kpar,i,j)*(1.D0 + THTECON3*qvp_new(kpar,i,j))) + + zlcl = ght_new(kpar,i,j) + (tmk_new(kpar,i,j) - tlcl)/(G/CP * (1.D0 + CPMD*qvp_new(kpar,i,j))) + + ! Calculate buoyancy and relative height of lifted parcel at + ! all levels, and store in bottom up arrays. add a level at the lcl, + ! and at all points where buoyancy is zero. + ! + ! For arrays that go bottom to top + kk = 0 + ilcl = 0 + + IF (ght_new(kpar,i,j) .GE. zlcl) THEN + ! Initial parcel already saturated or supersaturated. + ilcl = 2 + klcl = 1 + END IF + + !!$OMP SIMD lastprivate(qvplift,tmklift,ghtlift,tvlift,tmkenv,qvpenv,tvenv,eslift,facden) + DO k = kpar,1,-1 + ! For arrays that go bottom to top + kk = kk + 1 + + ! Model level is below lcl + IF (ght_new(k,i,j) .LT. zlcl) THEN + tmklift = tmk_new(kpar,i,j) - G/(CP * (1.D0 + CPMD*qvp_new(kpar,i,j)))*& + (ght_new(k,i,j) - ght_new(kpar,i,j)) + tvenv = tmk_new(k,i,j)*(EPS + qvp_new(k,i,j))/(EPS*(1.D0 + qvp_new(k,i,j))) + tvlift = tmklift*(EPS + qvp_new(kpar,i,j))/(EPS*(1.D0 + qvp_new(kpar,i,j))) + ghtlift = ght_new(k,i,j) + ELSE IF (ght(i,j,k) .GE. zlcl .AND. ilcl .EQ. 0) THEN + ! This model level and previous model level straddle the lcl, + ! so first create a new level in the bottom-up array, at the lcl. + facden = 1.0/(ght_new(k,i,j) - ght_new(k+1,i,j)) + tmkenv = tmk_new(k+1,i,j)*((ght_new(k,i,j)-zlcl)*facden) + tmk_new(k,i,j)*& + ((zlcl-ght_new(k+1,i,j))*facden) + qvpenv = qvp_new(k+1,i,j)*((ght_new(k,i,j)-zlcl)*facden) + qvp_new(k,i,j)*& + ((zlcl-ght_new(k+1,i,j))*facden) + tvenv = tmkenv* (EPS + qvpenv) / (EPS * (1.D0 + qvpenv)) + tvlift = tlcl* (EPS + qvp_new(kpar,i,j)) / (EPS *(1.D0 + qvp_new(kpar,i,j))) + ghtlift = zlcl + ilcl = 1 + ELSE + tmklift = TONPSADIABAT(ethpari, prs_new(k,i,j), psadithte, psadiprs,& + psaditmk, GAMMA, errstat, errmsg) + eslift = EZERO*EXP(ESLCON1*(tmklift - CELKEL)/(tmklift - ESLCON2)) + qvplift = EPS*eslift/(prs_new(k,i,j) - eslift) + tvenv = tmk_new(k,i,j) * (EPS + qvp_new(k,i,j)) / (EPS * (1.D0 + qvp_new(k,i,j))) + tvlift = tmklift*(EPS + qvplift) / (EPS * (1.D0 + qvplift)) + ghtlift = ght_new(k,i,j) + END IF + ! Buoyancy + buoy(kk) = G*(tvlift - tvenv)/tvenv + zrel(kk) = ghtlift - ght_new(kpar,i,j) + IF ((kk .GT. 1) .AND. (buoy(kk)*buoy(kk-1) .LT. 0.0D0)) THEN + ! Parcel ascent curve crosses sounding curve, so create a new level + ! in the bottom-up array at the crossing. + kk = kk + 1 + buoy(kk) = buoy(kk-1) + zrel(kk) = zrel(kk-1) + buoy(kk-1) = 0.D0 + zrel(kk-1) = zrel(kk-2) + buoy(kk-2)/& + (buoy(kk-2) - buoy(kk))*(zrel(kk) - zrel(kk-2)) + END IF + IF (ilcl .EQ. 1) THEN + klcl = kk + ilcl = 2 + CYCLE + END IF + + END DO + + kmax = kk + ! IF (kmax .GT. 150) THEN + ! print *,'kmax got too big' + ! errstat = ALGERR + ! WRITE(errmsg, *) 'capecalc3d: kmax got too big. kmax=',kmax + ! RETURN + ! END IF + + ! If no lcl was found, set klcl to kmax. it is probably not really + ! at kmax, but this will make the rest of the routine behave + ! properly. + IF (ilcl .EQ. 0) klcl=kmax + + ! Get the accumulated buoyant energy from the parcel's starting + ! point, at all levels up to the top level. + benaccum(1) = 0.0D0 + benamin = 9d9 + DO k = 2,kmax + dz = zrel(k) - zrel(k-1) + benaccum(k) = benaccum(k-1) + .5D0*dz*(buoy(k-1) + buoy(k)) + IF (benaccum(k) .LT. benamin) THEN + benamin = benaccum(k) + END IF + END DO + ! Determine equilibrium level (el), which we define as the highest + ! level of non-negative buoyancy above the lcl. note, this may be + ! the top level if the parcel is still buoyant there. + + elfound = .FALSE. + DO k = kmax,klcl,-1 + IF (buoy(k) .GE. 0.D0) THEN + ! k of equilibrium level + kel = k + elfound = .TRUE. + EXIT + END IF + END DO + + ! If we got through that loop, then there is no non-negative + ! buoyancy above the lcl in the sounding. in these situations, + ! both cape and cin will be set to -0.1 j/kg. (see below about + ! missing values in v6.1.0). also, where cape is + ! non-zero, cape and cin will be set to a minimum of +0.1 j/kg, so + ! that the zero contour in either the cin or cape fields will + ! circumscribe regions of non-zero cape. + + ! In v6.1.0 of ncl, we added a _fillvalue attribute to the return + ! value of this function. at that time we decided to change -0.1 + ! to a more appropriate missing value, which is passed into this + ! routine as cmsg. + + IF (.NOT. elfound) THEN + !print *,'el not found' + cape(i,j,kpar) = cmsg + cin(i,j,kpar) = cmsg + klfc = kmax + CYCLE + END IF + + ! If there is an equilibrium level, then cape is positive. we'll + ! define the level of free convection (lfc) as the point below the + ! el, but at or above the lcl, where accumulated buoyant energy is a + ! minimum. the net positive area (accumulated buoyant energy) from + ! the lfc up to the el will be defined as the cape, and the net + ! negative area (negative of accumulated buoyant energy) from the + ! parcel starting point to the lfc will be defined as the convective + ! inhibition (cin). + + ! First get the lfc according to the above definition. + benamin = 9D9 + klfc = kmax + DO k = klcl,kel + IF (benaccum(k) .LT. benamin) THEN + benamin = benaccum(k) + klfc = k + END IF + END DO + + ! Now we can assign values to cape and cin + + cape(i,j,kpar) = MAX(benaccum(kel)-benamin, 0.1D0) + cin(i,j,kpar) = MAX(-benamin, 0.1D0) + + ! cin is uninteresting when cape is small (< 100 j/kg), so set + ! cin to -0.1 (see note about missing values in v6.1.0) in + ! that case. + + ! In v6.1.0 of ncl, we added a _fillvalue attribute to the return + ! value of this function. at that time we decided to change -0.1 + ! to a more appropriate missing value, which is passed into this + ! routine as cmsg. + + IF (cape(i,j,kpar) .LT. 100.D0) cin(i,j,kpar) = cmsg + + END DO + END DO + END DO + !$OMP END PARALLEL DO + + RETURN +END SUBROUTINE DCAPECALC3D + +!====================================================================== +! +! !IROUTINE: capecalc2d -- Calculate CAPE and CIN +! +! !DESCRIPTION: +! +! Calculates CAPE and CIN only for the parcel with max theta-e in ! the column, (i.e. something akin to Colman's MCAPE). By "parcel", ! we mean a 500-m deep parcel, with actual temperature and moisture ! averaged over that depth. ! -! In the case of i3dflag=0, ! CAPE and CIN are 2D fields that are placed in the k=mkzh slabs of -! the cape and cin arrays. Also, if i3dflag=0, LCL and LFC heights +! the cape and cin arrays. Also, LCL and LFC heights ! are put in the k=mkzh-1 and k=mkzh-2 slabs of the cin array. ! - - ! Important! The z-indexes must be arranged so that mkzh (max z-index) is the ! surface pressure. So, pressure must be ordered in ascending order before ! calling this routine. Other variables must be ordered the same (p,tk,q,z). -! Also, be advised that missing data values are not checked during the computation. +! Also, be advised that missing data values are not checked during the +! computation. ! Also also, Pressure must be hPa ! NCLFORTSTART -SUBROUTINE DCAPECALC3D(prs,tmk,qvp,ght,ter,sfp,cape,cin,& - cmsg,miy,mjx,mkzh,i3dflag,ter_follow,& +SUBROUTINE DCAPECALC2D(prs,tmk,qvp,ght,ter,sfp,cape,cin,& + cmsg,mix,mjy,mkzh,ter_follow,& psafile, errstat, errmsg) - USE wrf_constants, ONLY : ALGERR, CELKEL, G, EZERO, ESLCON1, ESLCON2, & + USE wrf_constants, ONLY : CELKEL, G, EZERO, ESLCON1, ESLCON2, & EPS, RD, CP, GAMMA, CPMD, RGASMD, GAMMAMD, TLCLC1, & TLCLC2, TLCLC3, TLCLC4, THTECON1, THTECON2, THTECON3 + !USE omp_lib IMPLICIT NONE !f2py threadsafe !f2py intent(in,out) :: cape, cin - INTEGER, INTENT(IN) :: miy, mjx, mkzh, i3dflag, ter_follow - REAL(KIND=8), DIMENSION(miy,mjx,mkzh), INTENT(IN) :: prs - REAL(KIND=8), DIMENSION(miy,mjx,mkzh), INTENT(IN) :: tmk - REAL(KIND=8), DIMENSION(miy,mjx,mkzh), INTENT(IN) :: qvp - REAL(KIND=8), DIMENSION(miy,mjx,mkzh), INTENT(IN) :: ght - REAL(KIND=8), DIMENSION(miy,mjx), INTENT(IN) :: ter - REAL(KIND=8), DIMENSION(miy,mjx), INTENT(IN) ::sfp - REAL(KIND=8), DIMENSION(miy,mjx,mkzh), INTENT(OUT) :: cape - REAL(KIND=8), DIMENSION(miy,mjx,mkzh), INTENT(OUT) :: cin + INTEGER, INTENT(IN) :: mix, mjy, mkzh, ter_follow + REAL(KIND=8), DIMENSION(mix,mjy,mkzh), INTENT(IN) :: prs + REAL(KIND=8), DIMENSION(mix,mjy,mkzh), INTENT(IN) :: tmk + REAL(KIND=8), DIMENSION(mix,mjy,mkzh), INTENT(IN) :: qvp + REAL(KIND=8), DIMENSION(mix,mjy,mkzh), INTENT(IN) :: ght + REAL(KIND=8), DIMENSION(mix,mjy), INTENT(IN) :: ter + REAL(KIND=8), DIMENSION(mix,mjy), INTENT(IN) ::sfp + REAL(KIND=8), DIMENSION(mix,mjy,mkzh), INTENT(OUT) :: cape + REAL(KIND=8), DIMENSION(mix,mjy,mkzh), INTENT(OUT) :: cin REAL(KIND=8), INTENT(IN) :: cmsg CHARACTER(LEN=*), INTENT(IN) :: psafile INTEGER, INTENT(INOUT) :: errstat @@ -275,26 +625,34 @@ SUBROUTINE DCAPECALC3D(prs,tmk,qvp,ght,ter,sfp,cape,cin,& ! NCLFORTEND + ! local variables INTEGER :: i, j, k, ilcl, kel, kk, klcl, klev, klfc, kmax, kpar, kpar1, kpar2 - REAL(KIND=8) :: davg, ethmax, q, t, p, e, eth, tlcl, zlcl + REAL(KIND=8) :: ethmax, p, e, tlcl, zlcl REAL(KIND=8) :: pavg, tvirtual, p1, p2, pp1, pp2, th, totthe, totqvp, totprs - REAL(KIND=8) :: cpm, deltap, ethpari, gammam, ghtpari, qvppari, prspari, tmkpari - REAL(KIND=8) :: facden, fac1, fac2, qvplift, tmklift, tvenv, tvlift, ghtlift + REAL(KIND=8) :: cpm, deltap, ethpari, gammam, qvppari, tmkpari + REAL(KIND=8) :: facden, qvplift, tmklift, tvenv, tvlift, ghtlift REAL(KIND=8) :: eslift, tmkenv, qvpenv, tonpsadiabat REAL(KIND=8) :: benamin, dz, pup, pdn REAL(KIND=8), DIMENSION(150) :: buoy, zrel, benaccum - REAL(KIND=8), DIMENSION(miy,mjx,mkzh) :: prsf + REAL(KIND=8), DIMENSION(mkzh,mix,mjy) :: prsf REAL(KIND=8), DIMENSION(150) :: psadithte, psadiprs REAL(KIND=8), DIMENSION(150,150) :: psaditmk LOGICAL :: elfound + REAL(KIND=8), DIMENSION(mkzh) :: eth_temp + REAL(KIND=8), DIMENSION(mkzh,mix,mjy) :: prs_new + REAL(KIND=8), DIMENSION(mkzh,mix,mjy) :: tmk_new + REAL(KIND=8), DIMENSION(mkzh,mix,mjy) :: qvp_new + REAL(KIND=8), DIMENSION(mkzh,mix,mjy) :: ght_new ! To remove compiler warnings + errstat = 0 tmkpari = 0 qvppari = 0 klev = 0 klcl = 0 kel = 0 + deltap = 0 ! the comments were taken from a mark stoelinga email, 23 apr 2007, @@ -320,10 +678,23 @@ SUBROUTINE DCAPECALC3D(prs,tmk,qvp,ght,ter,sfp,cape,cin,& ! kg/kg (should range from 0.000 to 0.025) ! + !$OMP PARALLEL DO COLLAPSE(3) SCHEDULE(runtime) + DO j = 1,mjy + DO i = 1,mix + DO k = 1,mkzh + prs_new(k,i,j) = prs(i,j,k) + tmk_new(k,i,j) = tmk(i,j,k) + qvp_new(k,i,j) = qvp(i,j,k) + ght_new(k,i,j) = ght(i,j,k) + END DO + END DO + END DO + !$OMP END PARALLEL DO + + ! calculated the pressure at full sigma levels (a set of pressure ! levels that bound the layers represented by the vertical grid points) - - CALL DPFCALC(prs, sfp, prsf, miy, mjx, mkzh, ter_follow) + CALL DPFCALC(prs_new, sfp, prsf, mix, mjy, mkzh, ter_follow) ! before looping, set lookup table for getting temperature on ! a pseudoadiabat. @@ -334,284 +705,272 @@ SUBROUTINE DCAPECALC3D(prs,tmk,qvp,ght,ter,sfp,cape,cin,& RETURN END IF - ! do j=1,mjx-1 - DO j = 1,mjx - ! do i=1,miy-1 - DO i = 1,miy - cape(i,j,1) = 0.d0 - cin(i,j,1) = 0.d0 - - IF (i3dflag .EQ. 1) THEN - kpar1 = 2 - kpar2 = mkzh - ELSE - ! find parcel with max theta-e in lowest 3 km agl. - ethmax = -1.d0 - DO k = mkzh,1,-1 - IF (ght(i,j,k)-ter(i,j) .LT. 3000.d0) THEN - q = MAX(qvp(i,j,k), 1.d-15) - t = tmk(i,j,k) - p = prs(i,j,k) - e = q*p/(EPS + q) - tlcl = TLCLC1 / (LOG(t**TLCLC2/e)-TLCLC3) + TLCLC4 - eth = t * (1000.d0/p)**(GAMMA*(1.d0 + GAMMAMD*q))*& - EXP((THTECON1/tlcl - THTECON2)*q*(1.d0 + THTECON3*q)) - IF (eth .GT. ethmax) THEN - klev = k - ethmax = eth - END IF - END IF - END DO - kpar1 = klev - kpar2 = klev - - ! Establish average properties of that parcel - ! (over depth of approximately davg meters) - - ! davg=.1 - davg = 500.d0 - pavg = davg*prs(i,j,kpar1)*& - G/(RD*tvirtual(tmk(i,j,kpar1), qvp(i,j,kpar1))) - p2 = MIN(prs(i,j,kpar1)+.5d0*pavg, prsf(i,j,mkzh)) - p1 = p2 - pavg - totthe = 0.D0 - totqvp = 0.D0 - totprs = 0.D0 - DO k = mkzh,2,-1 - IF (prsf(i,j,k) .LE. p1) EXIT !GOTO 35 - IF (prsf(i,j,k-1) .GE. p2) CYCLE !GOTO 34 - p = prs(i,j,k) - pup = prsf(i,j,k) - pdn = prsf(i,j,k-1) - q = MAX(qvp(i,j,k),1.D-15) - th = tmk(i,j,k)*(1000.D0/p)**(GAMMA*(1.D0 + GAMMAMD*q)) - pp1 = MAX(p1,pdn) - pp2 = MIN(p2,pup) - IF (pp2 .GT. pp1) THEN - deltap = pp2 - pp1 - totqvp = totqvp + q*deltap - totthe = totthe + th*deltap - totprs = totprs + deltap - END IF -! 34 CONTINUE - END DO -! 35 CONTINUE - qvppari = totqvp/totprs - tmkpari = (totthe/totprs)*& - (prs(i,j,kpar1)/1000.D0)**(GAMMA*(1.D0+GAMMAMD*qvp(i,j,kpar1))) - END IF - - DO kpar = kpar1, kpar2 - - ! Calculate temperature and moisture properties of parcel - ! (note, qvppari and tmkpari already calculated above for 2d case.) - - IF (i3dflag .EQ. 1) THEN - qvppari = qvp(i,j,kpar) - tmkpari = tmk(i,j,kpar) - END IF - prspari = prs(i,j,kpar) - ghtpari = ght(i,j,kpar) - gammam = GAMMA * (1.D0 + GAMMAMD*qvppari) - cpm = CP * (1.D0 + CPMD*qvppari) - - e = MAX(1.D-20,qvppari*prspari/(EPS + qvppari)) - tlcl = TLCLC1/(LOG(tmkpari**TLCLC2/e) - TLCLC3) + TLCLC4 - ethpari = tmkpari*(1000.D0/prspari)**(GAMMA*(1.D0 + GAMMAMD*qvppari))*& - EXP((THTECON1/tlcl - THTECON2)*qvppari*(1.D0 + THTECON3*qvppari)) - zlcl = ghtpari + (tmkpari - tlcl)/(G/cpm) - - ! Calculate buoyancy and relative height of lifted parcel at - ! all levels, and store in bottom up arrays. add a level at the lcl, - ! and at all points where buoyancy is zero. - ! - ! For arrays that go bottom to top - kk = 0 - ilcl = 0 - - IF (ghtpari .GE. zlcl) THEN - ! Initial parcel already saturated or supersaturated. - ilcl = 2 - klcl = 1 - END IF - - k = kpar - DO WHILE (k .GE. 1)!k = kpar, 1, -1 - !DO k = kpar, 1, -1 - ! For arrays that go bottom to top -! 33 kk = kk + 1 - kk = kk + 1 - - ! Model level is below lcl - IF (ght(i,j,k) .LT. zlcl) THEN - qvplift = qvppari - tmklift = tmkpari - G/cpm*(ght(i,j,k) - ghtpari) - tvenv = tvirtual(tmk(i,j,k), qvp(i,j,k)) - tvlift = tvirtual(tmklift, qvplift) - ghtlift = ght(i,j,k) - ELSE IF (ght(i,j,k) .GE. zlcl .AND. ilcl .EQ. 0) THEN - ! This model level and previous model level straddle the lcl, - ! so first create a new level in the bottom-up array, at the lcl. - tmklift = tlcl - qvplift = qvppari - facden = ght(i,j,k) - ght(i,j,k+1) - fac1 = (zlcl-ght(i,j,k+1))/facden - fac2 = (ght(i,j,k)-zlcl)/facden - tmkenv = tmk(i,j,k+1)*fac2 + tmk(i,j,k)*fac1 - qvpenv = qvp(i,j,k+1)*fac2 + qvp(i,j,k)*fac1 - tvenv = tvirtual(tmkenv, qvpenv) - tvlift = tvirtual(tmklift, qvplift) - ghtlift = zlcl - ilcl = 1 - ELSE - tmklift = TONPSADIABAT(ethpari, prs(i,j,k), psadithte, psadiprs,& - psaditmk, GAMMA, errstat, errmsg) - eslift = EZERO*EXP(ESLCON1*(tmklift - CELKEL)/(tmklift - ESLCON2)) - qvplift = EPS*eslift/(prs(i,j,k) - eslift) - tvenv = tvirtual(tmk(i,j,k), qvp(i,j,k)) - tvlift = tvirtual(tmklift, qvplift) - ghtlift = ght(i,j,k) - END IF - ! Buoyancy - buoy(kk) = G*(tvlift - tvenv)/tvenv - zrel(kk) = ghtlift - ghtpari - - IF ((kk .GT. 1) .AND. (buoy(kk)*buoy(kk-1) .LT. 0.0D0)) THEN - ! Parcel ascent curve crosses sounding curve, so create a new level - ! in the bottom-up array at the crossing. - kk = kk + 1 - buoy(kk) = buoy(kk-1) - zrel(kk) = zrel(kk-1) - buoy(kk-1) = 0.D0 - zrel(kk-1) = zrel(kk-2) + buoy(kk-2)/& - (buoy(kk-2) - buoy(kk))*(zrel(kk) - zrel(kk-2)) - END IF - - IF (ilcl .EQ. 1) THEN - klcl = kk - ilcl = 2 - !GOTO 33 - CYCLE - END IF - - k = k - 1 - END DO - - kmax = kk - IF (kmax .GT. 150) THEN - errstat = ALGERR - WRITE(errmsg, *) 'capecalc3d: kmax got too big. kmax=',kmax - RETURN - END IF - - ! If no lcl was found, set klcl to kmax. it is probably not really - ! at kmax, but this will make the rest of the routine behave - ! properly. - IF (ilcl .EQ. 0) klcl=kmax - - ! Get the accumulated buoyant energy from the parcel's starting - ! point, at all levels up to the top level. - benaccum(1) = 0.0D0 - benamin = 9d9 - DO k = 2,kmax - dz = zrel(k) - zrel(k-1) - benaccum(k) = benaccum(k-1) + .5D0*dz*(buoy(k-1) + buoy(k)) - IF (benaccum(k) .LT. benamin) THEN - benamin = benaccum(k) - END IF - END DO - - ! Determine equilibrium level (el), which we define as the highest - ! level of non-negative buoyancy above the lcl. note, this may be - ! the top level if the parcel is still buoyant there. - - elfound = .FALSE. - DO k = kmax,klcl,-1 - IF (buoy(k) .GE. 0.D0) THEN - ! k of equilibrium level - kel = k - elfound = .TRUE. - EXIT - !GOTO 50 - END IF - END DO - - ! If we got through that loop, then there is no non-negative - ! buoyancy above the lcl in the sounding. in these situations, - ! both cape and cin will be set to -0.1 j/kg. (see below about - ! missing values in v6.1.0). also, where cape is - ! non-zero, cape and cin will be set to a minimum of +0.1 j/kg, so - ! that the zero contour in either the cin or cape fields will - ! circumscribe regions of non-zero cape. - - ! In v6.1.0 of ncl, we added a _fillvalue attribute to the return - ! value of this function. at that time we decided to change -0.1 - ! to a more appropriate missing value, which is passed into this - ! routine as cmsg. - - ! cape(i,j,kpar) = -0.1D0 - ! cin(i,j,kpar) = -0.1D0 - IF (.NOT. elfound) THEN - cape(i,j,kpar) = cmsg - cin(i,j,kpar) = cmsg - klfc = kmax - CYCLE - END IF - -! GOTO 102 - -! 50 CONTINUE - - ! If there is an equilibrium level, then cape is positive. we'll - ! define the level of free convection (lfc) as the point below the - ! el, but at or above the lcl, where accumulated buoyant energy is a - ! minimum. the net positive area (accumulated buoyant energy) from - ! the lfc up to the el will be defined as the cape, and the net - ! negative area (negative of accumulated buoyant energy) from the - ! parcel starting point to the lfc will be defined as the convective - ! inhibition (cin). - - ! First get the lfc according to the above definition. - benamin = 9D9 - klfc = kmax - DO k = klcl,kel - IF (benaccum(k) .LT. benamin) THEN - benamin = benaccum(k) - klfc = k - END IF - END DO - - ! Now we can assign values to cape and cin - - cape(i,j,kpar) = MAX(benaccum(kel)-benamin, 0.1D0) - cin(i,j,kpar) = MAX(-benamin, 0.1D0) - - ! cin is uninteresting when cape is small (< 100 j/kg), so set - ! cin to -0.1 (see note about missing values in v6.1.0) in - ! that case. - - ! In v6.1.0 of ncl, we added a _fillvalue attribute to the return - ! value of this function. at that time we decided to change -0.1 - ! to a more appropriate missing value, which is passed into this - ! routine as cmsg. - - ! IF (cape(i,j,kpar).lt.100.D0) cin(i,j,kpar) = -0.1D0 - IF (cape(i,j,kpar) .LT. 100.D0) cin(i,j,kpar) = cmsg -! 102 CONTINUE - - END DO - - IF (i3dflag .EQ. 0) THEN - cape(i,j,mkzh) = cape(i,j,kpar1) - cin(i,j,mkzh) = cin(i,j,kpar1) - ! meters agl - cin(i,j,mkzh-1) = zrel(klcl) + ghtpari - ter(i,j) - ! meters agl - cin(i,j,mkzh-2) = zrel(klfc) + ghtpari - ter(i,j) - - ENDIF - END DO + !$OMP PARALLEL DO COLLAPSE(2) PRIVATE(tlcl, ethpari, & + !$OMP zlcl, kk, ilcl, klcl, tmklift, tvenv, tvlift, ghtlift, & + !$OMP facden, tmkenv, qvpenv, eslift, qvplift, buoy, benamin, & + !$OMP benaccum, zrel, kmax, dz, elfound, & + !$OMP kel, klfc, pavg, p2, p1, totthe, totqvp, totprs, & + !$OMP i, j, k, kpar, kpar1, kpar2, qvppari, tmkpari, p, pup, pdn, th, & + !$OMP pp1, pp2, ethmax, eth_temp, klev) SCHEDULE(runtime) + DO j = 1,mjy + DO i = 1,mix + cape(i,j,1) = 0.D0 + cin(i,j,1) = 0.D0 + ! find parcel with max theta-e in lowest 3 km agl. + ethmax = -1.D0 + eth_temp = -1.D0 + DO k = 1, mkzh + IF (ght_new(k,i,j)-ter(i,j) .LT. 3000.D0) THEN + tlcl = TLCLC1 / (LOG(tmk_new(k,i,j)**TLCLC2/& + (MAX(qvp_new(k,i,j), 1.d-15)*prs_new(k,i,j)/(EPS+MAX(qvp_new(k,i,j), 1.d-15))))-TLCLC3)+& + TLCLC4 + eth_temp(k) = tmk_new(k,i,j) * (1000.D0/prs_new(k,i,j))**& + (GAMMA*(1.D0 + GAMMAMD*(MAX(qvp_new(k,i,j), 1.d-15))))*& + EXP((THTECON1/tlcl - THTECON2)*(MAX(qvp_new(k,i,j), 1.d-15))*& + (1.D0 + THTECON3*(MAX(qvp_new(k,i,j), 1.d-15)))) + END IF + END DO + klev = mkzh + DO k = 1,mkzh + IF (eth_temp(k) .GT. ethmax) THEN + klev = k + ethmax = eth_temp(k) + END IF + END DO + + kpar1 = klev + kpar2 = klev + + + ! Establish average properties of that parcel + ! (over depth of approximately davg meters) + + !davg = 500.D0 + pavg = 500.D0 * prs_new(kpar1,i,j)*& + G/(RD*tvirtual(tmk_new(kpar1,i,j), qvp_new(kpar1,i,j))) + p2 = MIN(prs_new(kpar1,i,j)+.5d0*pavg, prsf(mkzh,i,j)) + p1 = p2 - pavg + totthe = 0.D0 + totqvp = 0.D0 + totprs = 0.D0 + DO k = mkzh,2,-1 + IF (prsf(k,i,j) .LE. p1) EXIT !GOTO 35 + IF (prsf(k-1,i,j) .GE. p2) CYCLE !GOTO 34 + p = prs_new(k,i,j) + pup = prsf(k,i,j) + pdn = prsf(k-1,i,j) + !q = MAX(qvp_new(k,i,j),1.D-15) + th = tmk_new(k,i,j)*(1000.D0/prs_new(k,i,j))**(GAMMA*(1.D0 + GAMMAMD*MAX(qvp_new(k,i,j),1.D-15))) + pp1 = MAX(p1,pdn) + pp2 = MIN(p2,pup) + IF (pp2 .GT. pp1) THEN + ! deltap = pp2 - pp1 + totqvp = totqvp + MAX(qvp_new(k,i,j),1.D-15)*(pp2 - pp1) + totthe = totthe + th*(pp2 - pp1) + totprs = totprs + (pp2 - pp1) + END IF + END DO + qvppari = totqvp/totprs + tmkpari = (totthe/totprs)*& + (prs_new(kpar1,i,j)/1000.D0)**(GAMMA*(1.D0+GAMMAMD*qvp_new(kpar1,i,j))) + + DO kpar = kpar1, kpar2 + + ! Calculate temperature and moisture properties of parcel + ! (note, qvppari and tmkpari already calculated above for 2d + ! case.) + + !prspari = prs_new(kpar,i,j) + !ghtpari = ght_new(kpar,i,j) + gammam = GAMMA * (1.D0 + GAMMAMD*qvppari) + cpm = CP * (1.D0 + CPMD*qvppari) + + e = MAX(1.D-20,qvppari*prs_new(kpar,i,j)/(EPS + qvppari)) + tlcl = TLCLC1/(LOG(tmkpari**TLCLC2/e) - TLCLC3) + TLCLC4 + ethpari = tmkpari*(1000.D0/prs_new(kpar,i,j))**(GAMMA*(1.D0 + GAMMAMD*qvppari))*& + EXP((THTECON1/tlcl - THTECON2)*qvppari*(1.D0 + THTECON3*qvppari)) + zlcl = ght_new(kpar,i,j) + (tmkpari - tlcl)/(G/cpm) + + ! Calculate buoyancy and relative height of lifted parcel at + ! all levels, and store in bottom up arrays. add a level at the + ! lcl, + ! and at all points where buoyancy is zero. + ! + ! + ! For arrays that go bottom to top + kk = 0 + ilcl = 0 + + IF (ght_new(kpar,i,j) .GE. zlcl) THEN + ! Initial parcel already saturated or supersaturated. + ilcl = 2 + klcl = 1 + END IF + + k = kpar + DO k = kpar,1,-1 + ! For arrays that go bottom to top + kk = kk + 1 + + ! Model level is below lcl + IF (ght_new(k,i,j) .LT. zlcl) THEN + tmklift = tmk_new(kpar,i,j) - G/(CP * (1.D0 + CPMD*qvp_new(kpar,i,j)))*& + (ght_new(k,i,j) - ght_new(kpar,i,j)) + tvenv = tmk_new(k,i,j)*(EPS + qvp_new(k,i,j))/(EPS*(1.D0 + qvp_new(k,i,j))) + tvlift = tmklift*(EPS + qvp_new(kpar,i,j))/(EPS*(1.D0 + qvp_new(kpar,i,j))) + ghtlift = ght_new(k,i,j) + ELSE IF (ght(i,j,k) .GE. zlcl .AND. ilcl .EQ. 0) THEN + ! This model level and previous model level straddle the lcl, + ! so first create a new level in the bottom-up array, at the lcl. + facden = 1/(ght_new(k,i,j) - ght_new(k+1,i,j)) + tmkenv = tmk_new(k+1,i,j)*((ght_new(k,i,j)-zlcl)*facden) + tmk_new(k,i,j)*& + ((zlcl-ght_new(k+1,i,j))*facden) + qvpenv = qvp_new(k+1,i,j)*((ght_new(k,i,j)-zlcl)*facden) + qvp_new(k,i,j)*& + ((zlcl-ght_new(k+1,i,j))*facden) + tvenv = tmkenv* (EPS + qvpenv) / (EPS * (1.D0 + qvpenv)) + tvlift = tlcl* (EPS + qvp_new(kpar,i,j)) / (EPS *(1.D0 + qvp_new(kpar,i,j))) + ghtlift = zlcl + ilcl = 1 + ELSE + tmklift = TONPSADIABAT(ethpari, prs_new(k,i,j), psadithte, psadiprs,& + psaditmk, GAMMA, errstat, errmsg) + eslift = EZERO*EXP(ESLCON1*(tmklift - CELKEL)/(tmklift - ESLCON2)) + qvplift = EPS*eslift/(prs_new(k,i,j) - eslift) + tvenv = tmk_new(k,i,j) * (EPS + qvp_new(k,i,j)) / (EPS * (1.D0 + qvp_new(k,i,j))) + tvlift = tmklift*(EPS + qvplift) / (EPS * (1.D0 + qvplift)) + ghtlift = ght_new(k,i,j) + END IF + ! Buoyancy + buoy(kk) = G*(tvlift - tvenv)/tvenv + zrel(kk) = ghtlift - ght_new(kpar,i,j) + IF ((kk .GT. 1) .AND. (buoy(kk)*buoy(kk-1) .LT. 0.0D0)) THEN + ! Parcel ascent curve crosses sounding curve, so create a new level + ! in the bottom-up array at the crossing. + kk = kk + 1 + buoy(kk) = buoy(kk-1) + zrel(kk) = zrel(kk-1) + buoy(kk-1) = 0.D0 + zrel(kk-1) = zrel(kk-2) + buoy(kk-2)/& + (buoy(kk-2) - buoy(kk))*(zrel(kk) - zrel(kk-2)) + END IF + IF (ilcl .EQ. 1) THEN + klcl = kk + ilcl = 2 + CYCLE + END IF + END DO + + kmax = kk + ! IF (kmax .GT. 150) THEN + ! errstat = ALGERR + ! WRITE(errmsg, *) 'capecalc3d: kmax got too big. kmax=',kmax + ! RETURN + ! END IF + + ! If no lcl was found, set klcl to kmax. it is probably not + ! really + ! at kmax, but this will make the rest of the routine behave + ! properly. + IF (ilcl .EQ. 0) klcl=kmax + + ! Get the accumulated buoyant energy from the parcel's starting + ! point, at all levels up to the top level. + benaccum(1) = 0.0D0 + benamin = 9d9 + DO k = 2,kmax + dz = zrel(k) - zrel(k-1) + benaccum(k) = benaccum(k-1) + .5D0*dz*(buoy(k-1) + buoy(k)) + IF (benaccum(k) .LT. benamin) THEN + benamin = benaccum(k) + END IF + END DO + ! Determine equilibrium level (el), which we define as the highest + ! level of non-negative buoyancy above the lcl. note, this may be + ! the top level if the parcel is still buoyant there. + + elfound = .FALSE. + DO k = kmax,klcl,-1 + IF (buoy(k) .GE. 0.D0) THEN + ! k of equilibrium level + kel = k + elfound = .TRUE. + EXIT + END IF + END DO + + ! If we got through that loop, then there is no non-negative + ! buoyancy above the lcl in the sounding. in these situations, + ! both cape and cin will be set to -0.1 j/kg. (see below about + ! missing values in v6.1.0). also, where cape is + ! non-zero, cape and cin will be set to a minimum of +0.1 j/kg, so + ! that the zero contour in either the cin or cape fields will + ! circumscribe regions of non-zero cape. + ! In v6.1.0 of ncl, we added a _fillvalue attribute to the return + ! value of this function. at that time we decided to change -0.1 + ! to a more appropriate missing value, which is passed into this + ! routine as cmsg. + + IF (.NOT. elfound) THEN + cape(i,j,kpar) = cmsg + cin(i,j,kpar) = cmsg + klfc = kmax + CYCLE + END IF + + + ! If there is an equilibrium level, then cape is positive. + ! we'll + ! define the level of free convection (lfc) as the point below + ! the + ! el, but at or above the lcl, where accumulated buoyant energy + ! is a + ! minimum. the net positive area (accumulated buoyant energy) + ! from + ! the lfc up to the el will be defined as the cape, and the net + ! negative area (negative of accumulated buoyant energy) from + ! the + ! parcel starting point to the lfc will be defined as the + ! convective + ! inhibition (cin). + + ! First get the lfc according to the above definition. + benamin = 9D9 + klfc = kmax + DO k = klcl,kel + IF (benaccum(k) .LT. benamin) THEN + benamin = benaccum(k) + klfc = k + END IF + END DO + + ! Now we can assign values to cape and cin + + cape(i,j,kpar) = MAX(benaccum(kel)-benamin, 0.1D0) + cin(i,j,kpar) = MAX(-benamin, 0.1D0) + + ! cin is uninteresting when cape is small (< 100 j/kg), so set + ! cin to -0.1 (see note about missing values in v6.1.0) in + ! that case. + + ! In v6.1.0 of ncl, we added a _fillvalue attribute to the return + ! value of this function. at that time we decided to change -0.1 + ! to a more appropriate missing value, which is passed into this + ! routine as cmsg. + + IF (cape(i,j,kpar) .LT. 100.D0) cin(i,j,kpar) = cmsg + + END DO + + cape(i,j,mkzh) = cape(i,j,kpar1) + cin(i,j,mkzh) = cin(i,j,kpar1) + ! meters agl + cin(i,j,mkzh-1) = zrel(klcl) + ght_new(kpar1,i,j) - ter(i,j) + ! meters agl + cin(i,j,mkzh-2) = zrel(klfc) + ght_new(kpar1,i,j) - ter(i,j) + + END DO END DO + !$OMP END PARALLEL DO RETURN -END SUBROUTINE DCAPECALC3D +END SUBROUTINE DCAPECALC2D diff --git a/fortran/wrf_cloud_fracf.f90 b/fortran/wrf_cloud_fracf.f90 index 61cbe4c..92ae745 100644 --- a/fortran/wrf_cloud_fracf.f90 +++ b/fortran/wrf_cloud_fracf.f90 @@ -18,7 +18,12 @@ SUBROUTINE DCLOUDFRAC(pres, rh, lowc, midc, highc, nz, ns, ew) kchi = 0 kcmi = 0 kclo = 0 + lowc = 0 + midc = 0 + highc = 0 + !$OMP PARALLEL DO COLLAPSE(2) PRIVATE(i, j, k, kchi, kcmi, kclo) & + !$OMP SCHEDULE(runtime) DO j = 1,ns DO i = 1,ew DO k = 1,nz-1 @@ -27,30 +32,125 @@ SUBROUTINE DCLOUDFRAC(pres, rh, lowc, midc, highc, nz, ns, ew) IF ( pres(i,j,k) .GT. 45000. ) kchi=k END DO - DO k = 1,nz-1 - IF (k .GE. kclo .AND. k .LT. kcmi) THEN - lowc(i,j) = MAX(rh(i,j,k), lowc(i,j)) - ELSE IF (k .GE. kcmi .AND. k .LT. kchi) THEN ! mid cloud - midc(i,j) = MAX(rh(i,j,k), midc(i,j)) - ELSE if (k .GE. kchi) THEN ! high cloud - highc(i,j) = MAX(rh(i,j,k), highc(i,j)) - END IF - END DO + DO k = 1,nz-1 + IF (k .GE. kclo .AND. k .LT. kcmi) THEN + lowc(i,j) = MAX(rh(i,j,k), lowc(i,j)) + ELSE IF (k .GE. kcmi .AND. k .LT. kchi) THEN ! mid cloud + midc(i,j) = MAX(rh(i,j,k), midc(i,j)) + ELSE if (k .GE. kchi) THEN ! high cloud + highc(i,j) = MAX(rh(i,j,k), highc(i,j)) + END IF + END DO - lowc(i,j) = 4.0*lowc(i,j)/100. - 3.0 - midc(i,j) = 4.0*midc(i,j)/100. - 3.0 - highc(i,j) = 2.5*highc(i,j)/100. - 1.5 + lowc(i,j) = 4.0*lowc(i,j)/100. - 3.0 + midc(i,j) = 4.0*midc(i,j)/100. - 3.0 + highc(i,j) = 2.5*highc(i,j)/100. - 1.5 - lowc(i,j) = MIN(lowc(i,j), 1.0) - lowc(i,j) = MAX(lowc(i,j), 0.0) - midc(i,j) = MIN(midc(i,j), 1.0) - midc(i,j) = MAX(midc(i,j), 0.0) - highc(i,j) = MIN(highc(i,j), 1.0) - highc(i,j) = MAX(highc(i,j), 0.0) + lowc(i,j) = MIN(lowc(i,j), 1.0) + lowc(i,j) = MAX(lowc(i,j), 0.0) + midc(i,j) = MIN(midc(i,j), 1.0) + midc(i,j) = MAX(midc(i,j), 0.0) + highc(i,j) = MIN(highc(i,j), 1.0) + highc(i,j) = MAX(highc(i,j), 0.0) END DO END DO + !$OMP END PARALLEL DO RETURN END SUBROUTINE DCLOUDFRAC + + +! NCLFORTSTART +SUBROUTINE DCLOUDFRAC2(vert, rh, vert_inc_w_height, low_thresh, mid_thresh, & + high_thresh, msg, lowc, midc, highc, nz, ns, ew) + IMPLICIT NONE + + !f2py threadsafe + !f2py intent(in,out) :: lowc, midc, highc + + INTEGER nz, ns, ew + REAL(KIND=8), DIMENSION(ew, ns, nz), INTENT(IN) :: rh, vert + REAL(KIND=8), INTENT(IN) :: low_thresh, mid_thresh, high_thresh, msg + INTEGER, INTENT(IN) :: vert_inc_w_height + REAL(KIND=8), DIMENSION(ew, ns), INTENT(OUT) :: lowc, midc, highc + +! NCLEND + + INTEGER i, j, k + INTEGER kchi, kcmi, kclo + + ! Initialize the output + lowc = 0 + midc = 0 + highc = 0 + + !$OMP PARALLEL DO COLLAPSE(2) PRIVATE(i, j, k, kchi, kcmi, kclo) & + !$OMP SCHEDULE(runtime) + DO j = 1,ns + DO i = 1,ew + ! A value of -1 means 'not found'. This is needed to handle + ! the mountains, where the level thresholds are below the lowest + ! model level. + kchi = -1 + kcmi = -1 + kclo = -1 + + IF (vert_inc_w_height .NE. 0) THEN ! Vert coord increase with height + DO k = 1,nz + IF (vert(i,j,k) .LT. low_thresh) kclo=k + IF (vert(i,j,k) .LT. mid_thresh) kcmi=k + IF (vert(i,j,k) .LT. high_thresh) kchi=k + END DO + ELSE ! Vert coord decrease with height + DO k = 1,nz + IF (vert(i,j,k) .GT. low_thresh) kclo=k + IF (vert(i,j,k) .GT. mid_thresh) kcmi=k + IF (vert(i,j,k) .GT. high_thresh) kchi=k + END DO + ENDIF + + DO k = 1,nz + IF (k .GE. kclo .AND. k .LT. kcmi) THEN + lowc(i,j) = MAX(rh(i,j,k), lowc(i,j)) + ELSE IF (k .GE. kcmi .AND. k .LT. kchi) THEN ! mid cloud + midc(i,j) = MAX(rh(i,j,k), midc(i,j)) + ELSE if (k .GE. kchi) THEN ! high cloud + highc(i,j) = MAX(rh(i,j,k), highc(i,j)) + END IF + END DO + + ! Only do this when a cloud threshold is in the model vertical + ! domain, otherwise it will be set to missing + IF (kclo .GE. 1) THEN + lowc(i,j) = 4.0*lowc(i,j)/100. - 3.0 + lowc(i,j) = MIN(lowc(i,j), 1.0) + lowc(i,j) = MAX(lowc(i,j), 0.0) + ELSE + lowc(i,j) = msg + END IF + + IF (kcmi .GE. 1) THEN + midc(i,j) = 4.0*midc(i,j)/100. - 3.0 + midc(i,j) = MIN(midc(i,j), 1.0) + midc(i,j) = MAX(midc(i,j), 0.0) + ELSE + midc(i,j) = msg + END IF + + IF (kchi .GE. 1) THEN + highc(i,j) = 2.5*highc(i,j)/100. - 1.5 + highc(i,j) = MIN(highc(i,j), 1.0) + highc(i,j) = MAX(highc(i,j), 0.0) + ELSE + highc(i,j) = msg + END IF + + END DO + END DO + !$OMP END PARALLEL DO + + RETURN + +END SUBROUTINE DCLOUDFRAC2 diff --git a/fortran/wrf_constants.f90 b/fortran/wrf_constants.f90 index 617e63f..336c0e3 100644 --- a/fortran/wrf_constants.f90 +++ b/fortran/wrf_constants.f90 @@ -9,7 +9,15 @@ MODULE wrf_constants REAL(KIND=8), PARAMETER :: PI = 3.1415926535897932384626433D0 REAL(KIND=8), PARAMETER :: RAD_PER_DEG = PI/180.D0 REAL(KIND=8), PARAMETER :: DEG_PER_RAD = 180.D0/PI - REAL(KIND=8), PARAMETER :: DEFAULT_FILL = 9.9692099683868690D36 + REAL(KIND=8), PARAMETER :: DEFAULT_FILL = 9.9692099683868690E36 + INTEGER(KIND=1), PARAMETER :: DEFAULT_FILL_INT8 = -127 + INTEGER(KIND=2), PARAMETER :: DEFAULT_FILL_INT16 = -32767 + INTEGER(KIND=4), PARAMETER :: DEFAULT_FILL_INT32 = -2147483647 + INTEGER(KIND=8), PARAMETER :: DEFAULT_FILL_INT64 = INT(-9223372036854775806D0, KIND=8) + REAL(KIND=4), PARAMETER :: DEFAULT_FILL_FLOAT = 9.9692099683868690E36 + REAL(KIND=8), PARAMETER :: DEFAULT_FILL_DOUBLE = 9.9692099683868690E36 + CHARACTER(LEN=1), PARAMETER :: DEFAULT_FILL_CHAR = ACHAR(0) + REAL(KIND=8), PARAMETER :: P1000MB = 100000.D0 ! j/k/kg diff --git a/fortran/wrf_fctt.f90 b/fortran/wrf_fctt.f90 index 97da1b9..efc59b6 100644 --- a/fortran/wrf_fctt.f90 +++ b/fortran/wrf_fctt.f90 @@ -18,23 +18,15 @@ SUBROUTINE wrfcttcalc(prs, tk, qci, qcw, qvp, ght, ter, ctt, haveqci, nz, ns, ew ! LOCAL VARIABLES INTEGER i,j,k,ripk - !INTEGER :: mjx,miy,mkzh - REAL(KIND=8) :: vt, opdepthu, opdepthd, dp - REAL(KIND=8) :: ratmix, arg1, arg2, agl_hgt - REAL(KIND=8) :: fac, prsctt - !REAL(KIND=8) :: eps,ussalr,rgas,grav,abscoefi,abscoef,celkel,wrfout - !REAL(KIND=8) :: ght(ew,ns,nz),stuff(ew,ns) - !REAL(KIND=8), DIMENSION(ew,ns,nz) :: pf(ns,ew,nz),p1,p2 + REAL(KIND=8) :: opdepthu, opdepthd, dp, arg1, fac, prsctt, ratmix + REAL(KIND=8) :: arg2, agl_hgt, vt REAL(KIND=8), DIMENSION(ew,ns,nz) :: pf REAL(KIND=8) :: p1, p2 - !mjx = ew - !miy = ns - !mkzh = nz + !$OMP PARALLEL - prsctt = 0 ! removes the warning - -! Calculate the surface pressure + ! Calculate the surface pressure + !$OMP DO COLLAPSE(2) SCHEDULE(runtime) DO j=1,ns DO i=1,ew ratmix = .001D0*qvp(i,j,1) @@ -46,7 +38,9 @@ SUBROUTINE wrfcttcalc(prs, tk, qci, qcw, qvp, ght, ter, ctt, haveqci, nz, ns, ew pf(i,j,nz) = prs(i,j,1)*(vt/(vt + USSALR*(agl_hgt)))**(arg1) END DO END DO + !$OMP END DO + !$OMP DO COLLAPSE(3) SCHEDULE(runtime) DO k=1,nz-1 DO j=1,ns DO i=1,ew @@ -55,26 +49,27 @@ SUBROUTINE wrfcttcalc(prs, tk, qci, qcw, qvp, ght, ter, ctt, haveqci, nz, ns, ew END DO END DO END DO + !$OMP END DO + !$OMP DO COLLAPSE(2) PRIVATE(i, j, k, ripk, opdepthd, opdepthu, & + !$OMP prsctt, dp, p1, p2, fac, arg1) SCHEDULE(runtime) DO j=1,ns DO i=1,ew opdepthd = 0.D0 k = 0 + prsctt = 0 -! Integrate downward from model top, calculating path at full -! model vertical levels. - -!20 opdepthu=opdepthd + ! Integrate downward from model top, calculating path at full + ! model vertical levels. DO k=1, nz opdepthu = opdepthd - !k=k+1 ripk = nz - k + 1 - IF (k .EQ. 1) THEN - dp = 200.D0*(pf(i,j,1) - prs(i,j,nz)) ! should be in Pa - ELSE + IF (k .NE. 1) THEN dp = 100.D0*(pf(i,j,k) - pf(i,j,k-1)) ! should be in Pa + ELSE + dp = 200.D0*(pf(i,j,1) - prs(i,j,nz)) ! should be in Pa END IF IF (haveqci .EQ. 0) then @@ -89,7 +84,6 @@ SUBROUTINE wrfcttcalc(prs, tk, qci, qcw, qvp, ght, ter, ctt, haveqci, nz, ns, ew END IF IF (opdepthd .LT. 1. .AND. k .LT. nz) THEN - !GOTO 20 CYCLE ELSE IF (opdepthd .LT. 1. .AND. k .EQ. nz) THEN @@ -111,15 +105,14 @@ SUBROUTINE wrfcttcalc(prs, tk, qci, qcw, qvp, ght, ter, ctt, haveqci, nz, ns, ew fac = (prsctt - p1)/(p2 - p1) arg1 = fac*(tk(i,j,ripk) - tk(i,j,ripk+1)) - CELKEL ctt(i,j) = tk(i,j,ripk+1) + arg1 - !GOTO 40 EXIT END IF END DO END DO END DO -! 30 CONTINUE -! 40 CONTINUE -! 190 CONTINUE + !$OMP END DO + + !$OMP END PARALLEL RETURN END SUBROUTINE wrfcttcalc diff --git a/fortran/wrf_pvo.f90 b/fortran/wrf_pvo.f90 index 209a257..0a4e788 100644 --- a/fortran/wrf_pvo.f90 +++ b/fortran/wrf_pvo.f90 @@ -23,29 +23,29 @@ SUBROUTINE DCOMPUTEABSVORT(av, u, v, msfu, msfv, msft, cor, dx, dy, nx, ny, nz,& REAL(KIND=8) :: dsy, dsx, dudy, dvdx, avort REAL(KIND=8) :: mm - ! PRINT*,'nx,ny,nz,nxp1,nyp1' - ! PRINT*,nx,ny,nz,nxp1,nyp1 + !$OMP PARALLEL DO COLLAPSE(3) PRIVATE(i, j, k, jp1, jm1, ip1, im1, & + !$OMP dsx, dsy, mm, dudy, dvdx, avort) SCHEDULE(runtime) DO k = 1,nz DO j = 1,ny - jp1 = MIN(j+1, ny) - jm1 = MAX(j-1, 1) DO i = 1,nx + jp1 = MIN(j+1, ny) + jm1 = MAX(j-1, 1) ip1 = MIN(i+1, nx) im1 = MAX(i-1, 1) - ! PRINT *,jp1,jm1,ip1,im1 dsx = (ip1 - im1) * dx dsy = (jp1 - jm1) * dy mm = msft(i,j)*msft(i,j) - ! PRINT *,j,i,u(i,jp1,k),msfu(i,jp1),u(i,jp1,k)/msfu(i,jp1) dudy = 0.5D0*(u(i,jp1,k)/msfu(i,jp1) + u(i+1,jp1,k)/msfu(i+1,jp1) - & u(i,jm1,k)/msfu(i,jm1) - u(i+1,jm1,k)/msfu(i+1,jm1))/dsy*mm dvdx = 0.5D0*(v(ip1,j,k)/msfv(ip1,j) + v(ip1,j+1,k)/msfv(ip1,j+1) - & v(im1,j,k)/msfv(im1,j) - v(im1,j+1,k)/msfv(im1,j+1))/dsx*mm avort = dvdx - dudy + cor(i,j) av(i,j,k) = avort*1.D5 + END DO END DO END DO + !$OMP END PARALLEL DO RETURN @@ -80,22 +80,23 @@ SUBROUTINE DCOMPUTEPV(pv, u, v, theta, prs, msfu, msfv, msft, cor, dx, dy, nx, & REAL(KIND=8) :: dsy, dsx, dp, dudy, dvdx, dudp, dvdp, dthdp, avort REAL(KIND=8) :: dthdx, dthdy, mm - ! PRINT*,'nx,ny,nz,nxp1,nyp1' - ! PRINT*,nx,ny,nz,nxp1,nyp1 + !$OMP PARALLEL DO COLLAPSE(3) PRIVATE(i, j, k, kp1, km1, jp1, jm1, ip1, & + !$OMP im1, dsx, dsy, mm, dudy, dvdx, avort, & + !$OMP dp, dudp, dvdp, dthdp, dthdx, dthdy) SCHEDULE(runtime) DO k = 1,nz - kp1 = MIN(k+1, nz) - km1 = MAX(k-1, 1) DO J = 1,ny - jp1 = MIN(j+1, ny) - jm1 = MAX(j-1, 1) DO i = 1,nx + kp1 = MIN(k+1, nz) + km1 = MAX(k-1, 1) + jp1 = MIN(j+1, ny) + jm1 = MAX(j-1, 1) ip1 = MIN(i+1, nx) im1 = MAX(i-1, 1) - ! PRINT *,jp1,jm1,ip1,im1 + dsx = (ip1 - im1)*dx dsy = (jp1 - jm1)*dy mm = msft(i,j)*msft(i,j) - ! PRINT *,j,i,u(i,jp1,k),msfu(i,jp1),u(i,jp1,k)/msfu(i,jp1) + dudy = 0.5D0*(u(i,jp1,k)/msfu(i,jp1) + u(i+1,jp1,k)/msfu(i+1,jp1) - & u(i,jm1,k)/msfu(i,jm1) - u(i+1,jm1,k)/msfu(i+1,jm1))/dsy*mm dvdx = 0.5D0*(v(ip1,j,k)/msfv(ip1,j) + v(ip1,j+1,k)/msfv(ip1,j+1) - & @@ -108,14 +109,11 @@ SUBROUTINE DCOMPUTEPV(pv, u, v, theta, prs, msfu, msfv, msft, cor, dx, dy, nx, & dthdx = (theta(ip1,j,k) - theta(im1,j,k))/dsx*msft(i,j) dthdy = (theta(i,jp1,k) - theta(i,jm1,k))/dsy*msft(i,j) pv(i,j,k) = -G*(dthdp*avort - dvdp*dthdx + dudp*dthdy)*10000.D0 - ! if(i.eq.300 .and. j.eq.300) then - ! PRINT*,'avort,dudp,dvdp,dthdp,dthdx,dthdy,pv' - ! PRINT*,avort,dudp,dvdp,dthdp,dthdx,dthdy,pv(i,j,k) - ! endif pv(i,j,k) = pv(i,j,k)*1.D2 END DO END DO END DO + !$OMP END PARALLEL DO RETURN diff --git a/fortran/wrf_pw.f90 b/fortran/wrf_pw.f90 index ba19ffb..bd80876 100644 --- a/fortran/wrf_pw.f90 +++ b/fortran/wrf_pw.f90 @@ -18,14 +18,21 @@ SUBROUTINE DCOMPUTEPW(p, tv, qv, ht, pw, nx, ny, nz, nzh) !REAL(KIND=8),PARAMETER :: R=287.06 pw = 0 + + !$OMP PARALLEL + DO k=1,nz + !$OMP DO COLLAPSE(2) SCHEDULE(runtime) DO j=1,ny DO i=1,nx pw(i,j) = pw(i,j) + ((p(i,j,k)/(RD*tv(i,j,k)))*qv(i,j,k)*(ht(i,j,k+1) - ht(i,j,k))) END DO END DO + !$OMP END DO END DO + !$OMP END PARALLEL + RETURN END SUBROUTINE DCOMPUTEPW diff --git a/fortran/wrf_relhl.f90 b/fortran/wrf_relhl.f90 index 9efeec8..b3e562f 100644 --- a/fortran/wrf_relhl.f90 +++ b/fortran/wrf_relhl.f90 @@ -56,10 +56,10 @@ SUBROUTINE DCALRELHL(u, v, ght, ter, top, sreh, miy, mjx, mkzh) INTEGER :: i, j, k, k10, k3, ktop !REAL(KIND=8), PARAMETER :: DTR=PI/180.d0, DPR=180.d0/PI - !DO j = 1, mjx-1 + !$OMP PARALLEL DO COLLAPSE(2) PRIVATE(i,j,k,k10,k3,ktop, cu, cv, x, & + !$OMP sum, dh, sdh, su, sv, ua, va, asp, adr, bsp, bdr) SCHEDULE(runtime) DO j=1, mjx DO i=1, miy - !DO i=1, miy-1 sdh = 0.D0 su = 0.D0 sv = 0.D0 @@ -82,12 +82,14 @@ SUBROUTINE DCALRELHL(u, v, ght, ter, top, sreh, miy, mjx, mkzh) IF (k10 .EQ. 0) THEN k10 = 2 ENDIF + DO k = k3, k10, -1 dh = ght(i,j,k-1) - ght(i,j,k) sdh = sdh + dh su = su + 0.5D0*dh*(u(i,j,k-1) + u(i,j,k)) sv = sv + 0.5D0*dh*(v(i,j,k-1) + v(i,j,k)) END DO + ua = su/sdh va = sv/sdh asp = SQRT(ua*ua + va*va) @@ -96,11 +98,13 @@ SUBROUTINE DCALRELHL(u, v, ght, ter, top, sreh, miy, mjx, mkzh) ELSE adr = DEG_PER_RAD * (PI + ATAN2(ua,va)) ENDIF + bsp = 0.75D0*asp bdr = adr + 30.D0 IF (bdr .GT. 360.D0) THEN bdr = bdr - 360.D0 ENDIF + cu = -bsp*SIN(bdr * RAD_PER_DEG) cv = -bsp*COS(bdr * RAD_PER_DEG) sum = 0.D0 @@ -112,6 +116,7 @@ SUBROUTINE DCALRELHL(u, v, ght, ter, top, sreh, miy, mjx, mkzh) sreh(i,j) = -sum END DO END DO + !$OMP END PARALLEL DO RETURN diff --git a/fortran/wrf_rip_phys_routines.f90 b/fortran/wrf_rip_phys_routines.f90 index 350fb75..51af679 100644 --- a/fortran/wrf_rip_phys_routines.f90 +++ b/fortran/wrf_rip_phys_routines.f90 @@ -50,13 +50,23 @@ SUBROUTINE WETBULBCALC(prs, tmk, qvp, twb, nx, ny, nz, psafile, errstat, errmsg) !NCLEND INTEGER :: i, j, k - INTEGER :: jtch, jt, ipch, ip + INTEGER :: jt, ip REAL(KIND=8) :: q, t, p, e, tlcl, eth REAL(KIND=8) :: fracip, fracip2, fracjt, fracjt2 REAL(KIND=8), DIMENSION(150) :: PSADITHTE, PSADIPRS REAL(KIND=8), DIMENSION(150,150) :: PSADITMK REAL(KIND=8) :: tonpsadiabat + INTEGER :: l1, h1, mid1, rang1, l2, h2, mid2, rang2 + INTEGER :: errcnt1, errcnt2, bad_i, bad_j, bad_k + REAL(KIND=8) :: bad_p, bad_eth + !INTEGER :: ip, ipch, jt, jtch + + errcnt1 = 0 + errcnt2 = 0 + bad_i = -1 + bad_j = -1 + bad_k = -1 ! Before looping, set lookup table for getting temperature on ! a pseudoadiabat. @@ -67,6 +77,10 @@ SUBROUTINE WETBULBCALC(prs, tmk, qvp, twb, nx, ny, nz, psafile, errstat, errmsg) RETURN END IF + !$OMP PARALLEL DO COLLAPSE(3) PRIVATE (i, j, k, jt, ip, q, t, p, e, tlcl, & + !$OMP eth, fracip, fracip2, fracjt, fracjt2, l1, h1, mid1, rang1, l2, h2, & + !$OMP mid2, rang2, tonpsadiabat) REDUCTION(+:errcnt1, errcnt2) & + !$OMP SCHEDULE(runtime) DO k=1,nz DO j=1,ny DO i=1,nx @@ -92,26 +106,51 @@ SUBROUTINE WETBULBCALC(prs, tmk, qvp, twb, nx, ny, nz, psafile, errstat, errmsg) tonpsadiabat = eth*(p/1000.)**GAMMA ELSE ! Otherwise, look for the given thte/prs point in the lookup table. - jt=-1 - DO jtch=1,150-1 - IF (eth .GE. PSADITHTE(jtch) .AND. eth .LT. PSADITHTE(jtch+1)) THEN - jt = jtch - EXIT - ENDIF + jt = -1 + l1 = 1 + h1 = 149 + rang1 = h1 - l1 + mid1 = (h1 + l1) / 2 + DO WHILE(rang1 .GT. 1) + IF (eth .GE. psadithte(mid1)) THEN + l1 = mid1 + ELSE + h1 = mid1 + END IF + rang1 = h1 - l1 + mid1 = (h1 + l1) / 2 END DO - - ip=-1 - DO ipch=1,150-1 - IF (p .LE. PSADIPRS(ipch) .AND. p .GT. PSADIPRS(ipch+1)) THEN - ip = ipch - EXIT - ENDIF + jt = l1 + + ip = -1 + l2 = 1 + h2 = 149 + rang2 = h2 - l2 + mid2 = (h2 + l2) / 2 + DO WHILE(rang2 .GT. 1) + IF (p .LE. psadiprs(mid2)) THEN + l2 = mid2 + ELSE + h2 = mid2 + END IF + rang2 = h2 - l2 + mid2 = (h2 + l2) / 2 END DO + ip = l2 IF (jt .EQ. -1 .OR. ip .EQ. -1) THEN - errstat = ALGERR - WRITE(errmsg, *) "Outside of lookup table bounds. prs,thte=", p, eth - RETURN + errcnt1 = errcnt1 + 1 + !$OMP CRITICAL + ! Only do this the first time + IF (bad_i .EQ. -1) THEN + bad_i = i + bad_j = j + bad_k = k + bad_p = p + bad_eth = eth + END IF + !$OMP END CRITICAL + CYCLE ENDIF fracjt = (eth - PSADITHTE(jt))/(PSADITHTE(jt+1) - PSADITHTE(jt)) @@ -119,12 +158,21 @@ SUBROUTINE WETBULBCALC(prs, tmk, qvp, twb, nx, ny, nz, psafile, errstat, errmsg) fracip = (PSADIPRS(ip) - p)/(PSADIPRS(ip) - PSADIPRS(ip+1)) fracip2 = 1. - fracip + IF (PSADITMK(ip,jt) .GT. 1e9 .OR. PSADITMK(ip+1,jt) .GT. 1e9 .OR. & PSADITMK(ip,jt+1) .GT. 1e9 .OR. PSADITMK(ip+1,jt+1) .GT. 1e9) THEN - !PRINT*,'Tried to access missing tmperature in lookup table.' - errstat = ALGERR - WRITE(errmsg, *) "Prs and Thte probably unreasonable. prs, thte=", p, eth - RETURN + errcnt2 = errcnt2 + 1 + !$OMP CRITICAL + ! Only do this the first time + IF (bad_i .EQ. -1) THEN + bad_i = i + bad_j = j + bad_k = k + bad_p = p + bad_eth = eth + END IF + !$OMP END CRITICAL + CYCLE ENDIF tonpsadiabat = fracip2*fracjt2*PSADITMK(ip,jt) + & @@ -139,6 +187,20 @@ SUBROUTINE WETBULBCALC(prs, tmk, qvp, twb, nx, ny, nz, psafile, errstat, errmsg) END DO END DO + !$OMP END PARALLEL DO + + IF (errcnt1 > 0) THEN + errstat = ALGERR + WRITE(errmsg, *) "Outside of lookup table bounds. i=", bad_i, ",j=", bad_j, ",k=", bad_k, ",p=", bad_p, ",eth=", bad_eth + RETURN + END IF + + IF (errcnt2 > 0) THEN + errstat = ALGERR + WRITE(errmsg, *) "PRS and THTE unreasonable. i=", bad_i, ",j=", bad_j, ",k=", bad_k, ",p=", bad_p, ",eth=", bad_eth + RETURN + END IF + RETURN END SUBROUTINE WETBULBCALC @@ -195,6 +257,7 @@ SUBROUTINE OMGCALC(qvp, tmk, www, prs, omg, mx, my, mz) INTEGER :: i, j, k !REAL(KIND=8), PARAMETER :: GRAV=9.81, RGAS=287.04, EPS=0.622 + !$OMP PARALLEL DO COLLAPSE(3) SCHEDULE(runtime) DO k=1,mz DO j=1,my DO i=1,mx @@ -204,6 +267,7 @@ SUBROUTINE OMGCALC(qvp, tmk, www, prs, omg, mx, my, mz) END DO END DO END DO + !$OMP END PARALLEL DO RETURN @@ -255,6 +319,7 @@ SUBROUTINE VIRTUAL_TEMP(temp, ratmix, tv, nx, ny, nz) INTEGER :: i,j,k !REAL(KIND=8),PARAMETER :: EPS = 0.622D0 + !$OMP PARALLEL DO COLLAPSE(3) SCHEDULE(runtime) DO k=1,nz DO j=1,ny DO i=1,nx @@ -262,6 +327,7 @@ SUBROUTINE VIRTUAL_TEMP(temp, ratmix, tv, nx, ny, nz) END DO END DO END DO + !$OMP END PARALLEL DO RETURN diff --git a/fortran/wrf_user.f90 b/fortran/wrf_user.f90 index 4bb7297..e0bfac8 100644 --- a/fortran/wrf_user.f90 +++ b/fortran/wrf_user.f90 @@ -16,13 +16,15 @@ SUBROUTINE DCOMPUTEPI(pi, pressure, nx, ny, nz) !REAL(KIND=8), PARAMETER :: P1000MB=100000.D0, R_D=287.D0, CP=7.D0*R_D/2.D0 + !$OMP PARALLEL DO COLLAPSE(3) SCHEDULE(runtime) DO k = 1,nz - DO j = 1,ny - DO i = 1,nx - pi(i,j,k) = (pressure(i,j,k)/P1000MB)**(RD/CP) - END DO - END DO + DO j = 1,ny + DO i = 1,nx + pi(i,j,k) = (pressure(i,j,k)/P1000MB)**(RD/CP) + END DO + END DO END DO + !$OMP END PARALLEL DO END SUBROUTINE DCOMPUTEPI @@ -37,7 +39,7 @@ SUBROUTINE DCOMPUTETK(tk, pressure, theta, nx) !f2py intent(in,out) :: tk INTEGER, INTENT(IN) :: nx - REAL(KIND=8) :: pi + !REAL(KIND=8) :: pi REAL(KIND=8), DIMENSION(nx), INTENT(IN) :: pressure REAL(KIND=8), DIMENSION(nx), INTENT(IN) :: theta REAL(KIND=8), DIMENSION(nx), INTENT(OUT) :: tk @@ -48,10 +50,13 @@ SUBROUTINE DCOMPUTETK(tk, pressure, theta, nx) !REAL(KIND=8), PARAMETER :: P1000MB=100000.D0, RD=287.D0, CP=7.D0*RD/2.D0 + !$OMP PARALLEL DO SCHEDULE(runtime) DO i = 1,nx - pi = (pressure(i)/P1000MB)**(RD/CP) - tk(i) = pi*theta(i) + !pi = (pressure(i)/P1000MB)**(RD/CP) + !tk(i) = pi * theta(i) + tk(i) = (pressure(i)/P1000MB)**(RD/CP) * theta(i) END DO + !$OMP END PARALLEL DO RETURN @@ -76,9 +81,7 @@ SUBROUTINE DINTERP3DZ(data3d, out2d, zdata, desiredloc, nx, ny, nz, missingval) INTEGER :: i,j,kp,ip,im LOGICAL :: dointerp - REAL(KIND=8) :: height,w1,w2 - - height = desiredloc + REAL(KIND=8) :: w1,w2 ! does vertical coordinate increase or decrease with increasing k? ! set offset appropriately @@ -90,6 +93,8 @@ SUBROUTINE DINTERP3DZ(data3d, out2d, zdata, desiredloc, nx, ny, nz, missingval) im = 0 END IF + !$OMP PARALLEL DO COLLAPSE(2) PRIVATE(i,j,kp,dointerp,w1,w2) & + !$OMP FIRSTPRIVATE(ip,im) SCHEDULE(runtime) DO i = 1,nx DO j = 1,ny ! Initialize to missing. Was initially hard-coded to -999999. @@ -98,17 +103,17 @@ SUBROUTINE DINTERP3DZ(data3d, out2d, zdata, desiredloc, nx, ny, nz, missingval) kp = nz DO WHILE ((.NOT. dointerp) .AND. (kp >= 2)) - IF (((zdata(i,j,kp-im) < height) .AND. (zdata(i,j,kp-ip) > height))) THEN - w2 = (height - zdata(i,j,kp-im))/(zdata(i,j,kp-ip) - zdata(i,j,kp-im)) + IF (((zdata(i,j,kp-im) < desiredloc) .AND. (zdata(i,j,kp-ip) > desiredloc))) THEN + w2 = (desiredloc - zdata(i,j,kp-im))/(zdata(i,j,kp-ip) - zdata(i,j,kp-im)) w1 = 1.D0 - w2 out2d(i,j) = w1*data3d(i,j,kp-im) + w2*data3d(i,j,kp-ip) dointerp = .TRUE. END IF kp = kp - 1 END DO - END DO END DO + !$OMP END PARALLEL DO RETURN @@ -195,20 +200,23 @@ SUBROUTINE DINTERP2DXY(v3d, v2d, xy, nx, ny, nz, nxy) INTEGER :: i, j, k, ij REAL(KIND=8) :: w11, w12, w21, w22, wx, wy + !$OMP PARALLEL DO PRIVATE(i,j,k,ij,w11,w12,w21,w22,wx,wy) & + !$OMP SCHEDULE(runtime) DO ij = 1,nxy - i = MAX(1,MIN(nx-1,INT(xy(1,ij)+1))) - j = MAX(1,MIN(ny-1,INT(xy(2,ij)+1))) - wx = DBLE(i+1) - (xy(1,ij)+1) - wy = DBLE(j+1) - (xy(2,ij)+1) - w11 = wx*wy - w21 = (1.D0-wx)*wy - w12 = wx*(1.D0-wy) - w22 = (1.D0-wx)* (1.D0-wy) - DO k = 1,nz - v2d(ij,k) = w11*v3d(i,j,k) + w21*v3d(i+1,j,k) + & - w12*v3d(i,j+1,k) + w22*v3d(i+1,j+1,k) - END DO + i = MAX(1,MIN(nx-1,INT(xy(1,ij)+1))) + j = MAX(1,MIN(ny-1,INT(xy(2,ij)+1))) + wx = DBLE(i+1) - (xy(1,ij)+1) + wy = DBLE(j+1) - (xy(2,ij)+1) + w11 = wx*wy + w21 = (1.D0-wx)*wy + w12 = wx*(1.D0-wy) + w22 = (1.D0-wx)* (1.D0-wy) + DO k = 1,nz + v2d(ij,k) = w11*v3d(i,j,k) + w21*v3d(i+1,j,k) + & + w12*v3d(i,j+1,k) + w22*v3d(i+1,j+1,k) + END DO END DO + !$OMP END PARALLEL DO RETURN @@ -241,27 +249,30 @@ SUBROUTINE DINTERP1D(v_in, v_out, z_in, z_out, vmsg, nz_in, nz_out) ip = 0 im = 1 IF (z_in(1) .GT. z_in(nz_in)) THEN - ip = 1 - im = 0 + ip = 1 + im = 0 END IF + !$OMP PARALLEL DO PRIVATE(kp, k, interp, height, w1, w2) & + !$OMP FIRSTPRIVATE(ip, im) SCHEDULE(runtime) DO k = 1,nz_out - v_out(k) = vmsg - - interp = .FALSE. - kp = nz_in - height = z_out(k) - - DO WHILE ((.NOT. interp) .AND. (kp .GE. 2)) - IF (((z_in(kp-im) .LE. height) .AND. (z_in(kp-ip) .GT. height))) THEN - w2 = (height - z_in(kp-im))/(z_in(kp-ip) - z_in(kp-im)) - w1 = 1.D0 - w2 - v_out(k) = w1*v_in(kp-im) + w2*v_in(kp-ip) - interp = .TRUE. - END IF - kp = kp - 1 - END DO + v_out(k) = vmsg + + interp = .FALSE. + kp = nz_in + height = z_out(k) + + DO WHILE ((.NOT. interp) .AND. (kp .GE. 2)) + IF (((z_in(kp-im) .LE. height) .AND. (z_in(kp-ip) .GT. height))) THEN + w2 = (height - z_in(kp-im))/(z_in(kp-ip) - z_in(kp-im)) + w1 = 1.D0 - w2 + v_out(k) = w1*v_in(kp-im) + w2*v_in(kp-ip) + interp = .TRUE. + END IF + kp = kp - 1 + END DO END DO + !$OMP END PARALLEL DO RETURN @@ -316,10 +327,12 @@ SUBROUTINE DCOMPUTESEAPRS(nx, ny, nz, z, t, p, q, sea_level_pressure, & INTEGER :: i, j, k INTEGER :: klo, khi + INTEGER :: errcnt, bad_i, bad_j + REAL(KIND=8) :: bad_sfp REAL(KIND=8) :: plo, phi, tlo, thi, zlo, zhi REAL(KIND=8) :: p_at_pconst, t_at_pconst, z_at_pconst - REAL(KIND=8) :: z_half_lowest + !REAL(KIND=8) :: z_half_lowest LOGICAL :: l1, l2, l3, found @@ -329,7 +342,13 @@ SUBROUTINE DCOMPUTESEAPRS(nx, ny, nz, z, t, p, q, sea_level_pressure, & ! heating cycle in the pressure field. errstat = 0 + errcnt = 0 + bad_i = -1 + bad_j = -1 + bad_sfp = -1 + !$OMP PARALLEL DO COLLAPSE(2) PRIVATE(i,j,k,found) REDUCTION(+:errcnt) & + !$OMP SCHEDULE(runtime) DO j = 1,ny DO i = 1,nx level(i,j) = -1 @@ -345,22 +364,31 @@ SUBROUTINE DCOMPUTESEAPRS(nx, ny, nz, z, t, p, q, sea_level_pressure, & END DO IF (level(i,j) == -1) THEN - errstat = ALGERR - errmsg = "Error in finding 100 hPa up" - RETURN - - !PRINT '(A,I4,A)','Troubles finding level ', NINT(PCONST)/100,' above ground.' - !PRINT '(A,I4,A,I4,A)','Problems first occur at (',I,',',J,')' - !PRINT '(A,F6.1,A)','Surface pressure = ',p(i,j,1)/100,' hPa.' - !STOP 'Error in finding 100 hPa up' - + errcnt = errcnt + 1 + !$OMP CRITICAL + ! Only do this the first time + IF (bad_i .EQ. -1) THEN + bad_i = i + bad_j = j + bad_sfp = p(i,j,1) / 100. + END IF + !$OMP END CRITICAL END IF END DO END DO + !$OMP END PARALLEL DO + + IF (errcnt > 0) THEN + errstat = ALGERR + WRITE(errmsg,*) "Error in finding 100 hPa up. i=", bad_i, "j=", bad_j, "sfc_p=", bad_sfp + RETURN + END IF ! Get temperature PCONST Pa above surface. Use this to extrapolate ! the temperature at the surface and down to sea level. - + !$OMP PARALLEL DO COLLAPSE(2) PRIVATE(i,j,klo,khi,plo, & + !$OMP phi,tlo,thi,zlo,zhi,p_at_pconst,t_at_pconst,z_at_pconst) & + !$OMP REDUCTION(+:errcnt) SCHEDULE(runtime) DO j = 1,ny DO i = 1,nx @@ -368,39 +396,43 @@ SUBROUTINE DCOMPUTESEAPRS(nx, ny, nz, z, t, p, q, sea_level_pressure, & khi = MIN(klo+1, nz-1) IF (klo == khi) THEN - errstat = ALGERR - errmsg = "Error trapping levels" - RETURN - - !PRINT '(A)','Trapping levels are weird.' - !PRINT '(A,I3,A,I3,A)','klo = ',klo,', khi = ',khi,': and they should not be equal.' - !STOP 'Error trapping levels' + errcnt = errcnt + 1 + !$OMP CRITICAL + IF (bad_i .EQ. -1) THEN + bad_i = i + bad_j = j + END IF + !$OMP END CRITICAL END IF plo = p(i,j,klo) phi = p(i,j,khi) tlo = t(i,j,klo)*(1.D0 + 0.608D0*q(i,j,klo)) thi = t(i,j,khi)*(1.D0 + 0.608D0*q(i,j,khi)) - ! zlo = zetahalf(klo)/ztop*(ztop-terrain(i,j))+terrain(i,j) - ! zhi = zetahalf(khi)/ztop*(ztop-terrain(i,j))+terrain(i,j) zlo = z(i,j,klo) zhi = z(i,j,khi) - p_at_pconst = p(i,j,1) - PCONST t_at_pconst = thi - (thi-tlo)*LOG(p_at_pconst/phi)*LOG(plo/phi) z_at_pconst = zhi - (zhi-zlo)*LOG(p_at_pconst/phi)*LOG(plo/phi) - + ! t_surf(i,j) = t_at_pconst * (p(i,j,1)/p_at_pconst)**(USSALR*RD/G) t_sea_level(i,j) = t_at_pconst + USSALR*z_at_pconst - END DO END DO + !$OMP END PARALLEL DO + + IF (errcnt > 0) THEN + errstat = ALGERR + WRITE(errmsg,*) "Error trapping levels at i=", bad_i, "j=", bad_j + RETURN + END IF ! If we follow a traditional computation, there is a correction to the ! sea level temperature if both the surface and sea level ! temperatures are *too* hot. IF (ridiculous_mm5_test) THEN + !$OMP PARALLEL DO COLLAPSE(2) PRIVATE(l1,l2,l3) SCHEDULE(runtime) DO j = 1,ny DO i = 1,nx l1 = t_sea_level(i,j) < TC @@ -413,21 +445,23 @@ SUBROUTINE DCOMPUTESEAPRS(nx, ny, nz, z, t, p, q, sea_level_pressure, & END IF END DO END DO + !$OMP END PARALLEL DO END IF ! The grand finale: ta da! + !$OMP PARALLEL DO COLLAPSE(2) SCHEDULE(runtime) DO j = 1,ny DO i = 1,nx - ! z_half_lowest=zetahalf(1)/ztop*(ztop-terrain(i,j))+terrain(i,j) - z_half_lowest = z(i,j,1) + !z_half_lowest = z(i,j,1) ! Convert to hPa in this step, by multiplying by 0.01. The original ! Fortran routine didn't do this, but the NCL script that called it ! did, so we moved it here. - sea_level_pressure(i,j) = 0.01*(p(i,j,1)*EXP((2.D0*G*z_half_lowest)/& + sea_level_pressure(i,j) = 0.01*(p(i,j,1)*EXP((2.D0*G*z(i,j,1))/& (RD*(t_sea_level(i,j) + t_surf(i,j))))) END DO END DO + !$OMP END PARALLEL DO ! PRINT *,'sea pres input at weird location i=20,j=1,k=1' ! PRINT *,'t=',t(20,1,1),t(20,2,1),t(20,3,1) @@ -464,12 +498,15 @@ SUBROUTINE DFILTER2D(a, b, nx, ny, it, missing) INTEGER :: i, j, iter DO iter=1,it + !$OMP PARALLEL DO COLLAPSE(2) SCHEDULE(runtime) DO j=1,ny DO i = 1,nx b(i,j) = a(i,j) END DO END DO + !$OMP END PARALLEL DO + !$OMP PARALLEL DO COLLAPSE(2) SCHEDULE(runtime) DO j=2,ny-1 DO i=1,nx IF (b(i,j-1) .EQ. missing .OR. b(i,j) .EQ. missing .OR. & @@ -480,7 +517,9 @@ SUBROUTINE DFILTER2D(a, b, nx, ny, it, missing) END IF END DO END DO + !$OMP END PARALLEL DO + !$OMP PARALLEL DO COLLAPSE(2) SCHEDULE(runtime) DO j=1,ny DO i=2,nx-1 IF (b(i-1,j) .EQ. missing .OR. b(i,j) .EQ. missing .OR. & @@ -491,6 +530,7 @@ SUBROUTINE DFILTER2D(a, b, nx, ny, it, missing) END IF END DO END DO + !$OMP END PARALLEL DO ! do j=1,ny ! do i=1,nx ! b(i,j) = a(i,j) @@ -533,13 +573,18 @@ SUBROUTINE FILTER2D(a, b, nx, ny, it, missing) INTEGER :: i, j, iter + !$OMP PARALLEL + DO iter=1,it + !$OMP DO COLLAPSE(2) SCHEDULE(runtime) DO j=1,ny DO i = 1,nx b(i,j) = a(i,j) END DO END DO + !$OMP END DO + !$OMP DO COLLAPSE(2) SCHEDULE(runtime) DO j=2,ny-1 DO i=1,nx IF (b(i,j-1) .EQ. missing .OR. b(i,j) .EQ. missing .OR. & @@ -550,7 +595,9 @@ SUBROUTINE FILTER2D(a, b, nx, ny, it, missing) END IF END DO END DO + !$OMP END DO + !$OMP DO COLLAPSE(2) SCHEDULE(runtime) DO j=1,ny DO i=2,nx-1 IF (b(i-1,j) .EQ. missing .OR. b(i,j) .EQ. missing .OR. & @@ -561,6 +608,7 @@ SUBROUTINE FILTER2D(a, b, nx, ny, it, missing) END IF END DO END DO + !$OMP END DO ! do j=1,ny ! do i=1,nx ! b(i,j) = a(i,j) @@ -578,6 +626,8 @@ SUBROUTINE FILTER2D(a, b, nx, ny, it, missing) ! enddo END DO + !$OMP END PARALLEL + RETURN END SUBROUTINE FILTER2D @@ -601,6 +651,7 @@ SUBROUTINE DCOMPUTERH(qv, p, t, rh, nx) INTEGER :: i REAL(KIND=8) :: qvs,es,pressure,temperature + !$OMP PARALLEL DO PRIVATE(qvs, es, pressure, temperature) SCHEDULE(runtime) DO i = 1,nx pressure = p(i) temperature = t(i) @@ -612,6 +663,7 @@ SUBROUTINE DCOMPUTERH(qv, p, t, rh, nx) ! rh(i) = 100.*qv(i)/qvs rh(i) = 100.D0*MAX(MIN(qv(i)/qvs, 1.0D0), 0.0D0) END DO + !$OMP END PARALLEL DO RETURN @@ -707,11 +759,14 @@ SUBROUTINE DCOMPUTEUVMET(u, v, uvmet, longca,longcb,flong,flat, & ! NCLEND INTEGER :: i,j - REAL(KIND=8) :: uk, vk + !REAL(KIND=8) :: uk, vk ! msg stands for missing value in this code ! WRITE (6,FMT=*) ' in compute_uvmet ',NX,NY,NXP1,NYP1,ISTAG + !$OMP PARALLEL + + !$OMP DO COLLAPSE(2) SCHEDULE(runtime) DO j = 1,ny DO i = 1,nx @@ -733,34 +788,81 @@ SUBROUTINE DCOMPUTEUVMET(u, v, uvmet, longca,longcb,flong,flat, & END DO END DO + !$OMP END DO - ! WRITE (6,FMT=*) " computing velocities " - DO j = 1,ny - DO i = 1,nx - IF (istag.EQ.1) THEN - IF (is_msg_val .AND. (u(i,j) .EQ. umsg .OR. v(i,j) .EQ. vmsg & - .OR. u(i+1,j) .EQ. umsg .OR. v(i,j+1) .EQ. vmsg)) THEN - uvmet(i,j,1) = uvmetmsg - uvmet(i,j,2) = uvmetmsg - ELSE - uk = 0.5D0*(u(i,j) + u(i+1,j)) - vk = 0.5D0*(v(i,j) + v(i,j+1)) - uvmet(i,j,1) = vk*longcb(i,j) + uk*longca(i,j) - uvmet(i,j,2) = vk*longca(i,j) - uk*longcb(i,j) - END IF - ELSE - IF (is_msg_val .AND. (u(i,j) .EQ. umsg .OR. v(i,j) .EQ. vmsg)) THEN - uvmet(i,j,1) = uvmetmsg - uvmet(i,j,2) = uvmetmsg - ELSE - uk = u(i,j) - vk = v(i,j) - uvmet(i,j,1) = vk*longcb(i,j) + uk*longca(i,j) - uvmet(i,j,2) = vk*longca(i,j) - uk*longcb(i,j) - END IF - END IF - END DO - END DO + ! Note: Intentionally removed as many IF statements as possible from loops + ! to improve vectorization. + + IF (istag .EQ. 0) THEN ! Not staggered + IF (.NOT. is_msg_val) THEN ! No missing values used + !$OMP DO COLLAPSE(2) SCHEDULE(runtime) + DO j = 1,ny + DO i = 1,nx + uvmet(i,j,1) = v(i,j)*longcb(i,j) + u(i,j)*longca(i,j) + uvmet(i,j,2) = v(i,j)*longca(i,j) - u(i,j)*longcb(i,j) + END DO + END DO + !$OMP END DO + ELSE ! Missing values used + !$OMP DO COLLAPSE(2) SCHEDULE(runtime) + DO j = 1,ny + DO i = 1,nx + IF ((u(i,j) .NE. umsg .AND. v(i,j) .NE. vmsg)) THEN + uvmet(i,j,1) = v(i,j)*longcb(i,j) + u(i,j)*longca(i,j) + uvmet(i,j,2) = v(i,j)*longca(i,j) - u(i,j)*longcb(i,j) + ELSE + uvmet(i,j,1) = uvmetmsg + uvmet(i,j,2) = uvmetmsg + END IF + END DO + END DO + !$OMP END DO + END IF + ELSE ! Staggered + IF (.NOT. is_msg_val) THEN ! No missing values used + !$OMP DO COLLAPSE(2) SCHEDULE(runtime) + DO j = 1,ny + DO i = 1,nx + ! This is the more readable version. + !uk = 0.5D0*(u(i,j) + u(i+1,j)) + !vk = 0.5D0*(v(i,j) + v(i,j+1)) + !uvmet(i,j,1) = vk*longcb(i,j) + uk*longca(i,j) + !uvmet(i,j,2) = vk*longca(i,j) - uk*longcb(i,j) + + uvmet(i,j,1) = (0.5D0*(v(i,j) + v(i,j+1)))*longcb(i,j) + & + (0.5D0*(u(i,j) + u(i+1,j)))*longca(i,j) + uvmet(i,j,2) = (0.5D0*(v(i,j) + v(i,j+1)))*longca(i,j) - & + (0.5D0*(u(i,j) + u(i+1,j)))*longcb(i,j) + + END DO + END DO + !$OMP END DO + ELSE ! Missing values used + !$OMP DO COLLAPSE(2) SCHEDULE(runtime) + DO j = 1,ny + DO i = 1,nx + IF (u(i,j) .NE. umsg .AND. v(i,j) .NE. vmsg .AND. u(i+1,j) .NE. umsg .AND. v(i,j+1) .NE. vmsg) THEN + ! This is the more readable version. + !uk = 0.5D0*(u(i,j) + u(i+1,j)) + !vk = 0.5D0*(v(i,j) + v(i,j+1)) + !uvmet(i,j,1) = vk*longcb(i,j) + uk*longca(i,j) + !uvmet(i,j,2) = vk*longca(i,j) - uk*longcb(i,j) + + uvmet(i,j,1) = (0.5D0*(v(i,j) + v(i,j+1)))*longcb(i,j) + & + (0.5D0*(u(i,j) + u(i+1,j)))*longca(i,j) + uvmet(i,j,2) = (0.5D0*(v(i,j) + v(i,j+1)))*longca(i,j) - & + (0.5D0*(u(i,j) + u(i+1,j)))*longcb(i,j) + ELSE + uvmet(i,j,1) = uvmetmsg + uvmet(i,j,2) = uvmetmsg + END IF + END DO + END DO + !$OMP END DO + END IF + END IF + + !$OMP END PARALLEL RETURN @@ -791,15 +893,17 @@ SUBROUTINE DCOMPUTETD(td, pressure, qv_in, nx) INTEGER :: i + !$OMP PARALLEL DO PRIVATE(i,qv,tdc) SCHEDULE(runtime) DO i = 1,nx - qv = MAX(qv_in(i), 0.D0) - ! vapor pressure - tdc = qv*pressure(i)/(.622D0 + qv) + qv = MAX(qv_in(i), 0.D0) + ! vapor pressure + tdc = qv*pressure(i)/(.622D0 + qv) - ! avoid problems near zero - tdc = MAX(tdc, 0.001D0) - td(i) = (243.5D0*LOG(tdc) - 440.8D0)/(19.48D0 - LOG(tdc)) + ! avoid problems near zero + tdc = MAX(tdc, 0.001D0) + td(i) = (243.5D0*LOG(tdc) - 440.8D0)/(19.48D0 - LOG(tdc)) END DO + !$OMP END PARALLEL DO RETURN @@ -825,11 +929,7 @@ SUBROUTINE DCOMPUTEICLW(iclw, pressure, qc_in, nx, ny, nz) REAL(KIND=8), PARAMETER :: GG = 1000.D0/G INTEGER i,j,k - DO j = 1,ny - DO i = 1,nx - iclw(i,j) = 0.D0 - END DO - END DO + iclw = 0 DO j = 3,ny - 2 DO i = 3,nx - 2 diff --git a/fortran/wrf_user_dbz.f90 b/fortran/wrf_user_dbz.f90 index d1151f0..2e5e17b 100644 --- a/fortran/wrf_user_dbz.f90 +++ b/fortran/wrf_user_dbz.f90 @@ -77,7 +77,10 @@ SUBROUTINE CALCDBZ(prs, tmk, qvp, qra, qsn, qgr, sn0, ivarint, iliqskin, dbz, nx REAL(KIND=8), PARAMETER :: RN0_S = 2.D7 REAL(KIND=8), PARAMETER :: RN0_G = 4.D6 + !$OMP PARALLEL + ! Force all Q arrays to be 0.0 or greater. + !$OMP DO COLLAPSE(3) SCHEDULE(runtime) DO k = 1,nz DO j = 1,ny DO i = 1,nx @@ -96,10 +99,12 @@ SUBROUTINE CALCDBZ(prs, tmk, qvp, qra, qsn, qgr, sn0, ivarint, iliqskin, dbz, nx END DO END DO END DO + !$OMP END DO ! Input pressure is Pa, but we need hPa in calculations IF (sn0 .EQ. 0) THEN + !$OMP DO COLLAPSE(3) SCHEDULE(runtime) DO k = 1,nz DO j = 1,ny DO i = 1,nx @@ -110,12 +115,17 @@ SUBROUTINE CALCDBZ(prs, tmk, qvp, qra, qsn, qgr, sn0, ivarint, iliqskin, dbz, nx END DO END DO END DO + !$OMP END DO END IF factor_r = GAMMA_SEVEN*1.D18*(1.D0/(PI*RHO_R))**1.75D0 factor_s = GAMMA_SEVEN*1.D18*(1.D0/(PI*RHO_S))**1.75D0*(RHO_S/RHOWAT)**2*ALPHA factor_g = GAMMA_SEVEN*1.D18*(1.D0/(PI*RHO_G))**1.75D0*(RHO_G/RHOWAT)**2*ALPHA + + !$OMP DO COLLAPSE(3) PRIVATE(i, j, k, temp_c, virtual_t, gonv, ronv, sonv, & + !$OMP factorb_g, factorb_s, rhoair, z_e) & + !$OMP FIRSTPRIVATE(factor_r, factor_s, factor_g) SCHEDULE(runtime) DO k = 1,nz DO j = 1,ny DO i = 1,nx @@ -171,6 +181,9 @@ SUBROUTINE CALCDBZ(prs, tmk, qvp, qra, qsn, qgr, sn0, ivarint, iliqskin, dbz, nx END DO END DO END DO + !$OMP END DO + + !$OMP END PARALLEL RETURN diff --git a/fortran/wrf_vinterp.f90 b/fortran/wrf_vinterp.f90 index c9bebb0..c34efd7 100644 --- a/fortran/wrf_vinterp.f90 +++ b/fortran/wrf_vinterp.f90 @@ -14,17 +14,19 @@ SUBROUTINE wrf_monotonic(out, in, lvprs, cor, idir, delta, ew, ns, nz, icorsw) REAL(KIND=8), INTENT(IN) :: delta REAL(KIND=8), DIMENSION(ew,ns,nz), INTENT(INOUT) :: in REAL(KIND=8), DIMENSION(ew,ns,nz), INTENT(OUT) :: out - REAL(KIND=8), DIMENSION(ew,ns,nz) :: lvprs - REAL(KIND=8), DIMENSION(ew,ns) :: cor + REAL(KIND=8), DIMENSION(ew,ns,nz), INTENT(IN) :: lvprs + REAL(KIND=8), DIMENSION(ew,ns), INTENT(IN) :: cor -!NCLEND - INTEGER :: i, j, k, ripk, k300 +!NCLEND - k300 = 1 ! removes the warning + INTEGER :: i, j, k, k300 + !$OMP PARALLEL DO COLLAPSE(2) PRIVATE(i, j, k, k300) SCHEDULE(runtime) DO j=1,ns DO i=1,ew + k300 = -1 + IF (icorsw .EQ. 1 .AND. cor(i,j) .LT. 0.) THEN DO k=1,nz in(i,j,k) = -in(i,j,k) @@ -33,14 +35,19 @@ SUBROUTINE wrf_monotonic(out, in, lvprs, cor, idir, delta, ew, ns, nz, icorsw) ! First find k index that is at or below (height-wise) ! the 300 hPa level. - DO k = 1,nz - ripk = nz-k+1 + DO k = 1,nz-1 IF (lvprs(i,j,k) .LE. 300.D0) THEN k300 = k EXIT END IF END DO + ! If the search fails for some reason, use the second to last + ! k index + IF (k300 .EQ. -1) THEN + k300 = nz-1 + END IF + DO k = k300,1,-1 IF (idir .EQ. 1) THEN out(i,j,k) = MIN(in(i,j,k), in(i,j,k+1) + delta) @@ -58,6 +65,7 @@ SUBROUTINE wrf_monotonic(out, in, lvprs, cor, idir, delta, ew, ns, nz, icorsw) END DO END DO END DO + !$OMP END PARALLEL DO RETURN @@ -65,7 +73,7 @@ END SUBROUTINE wrf_monotonic !NCLFORTSTART -FUNCTION wrf_intrp_value(wvalp0, wvalp1, vlev, vcp0, vcp1, icase, errstat, errmsg) +FUNCTION wrf_intrp_value(wvalp0, wvalp1, vlev, vcp0, vcp1, icase, errstat) USE wrf_constants, ONLY : ALGERR, SCLHT IMPLICIT NONE @@ -75,7 +83,6 @@ FUNCTION wrf_intrp_value(wvalp0, wvalp1, vlev, vcp0, vcp1, icase, errstat, errms INTEGER, INTENT(IN) :: icase REAL(KIND=8), INTENT(IN) :: wvalp0, wvalp1, vlev, vcp0, vcp1 INTEGER, INTENT(INOUT) :: errstat - CHARACTER(LEN=*), INTENT(INOUT) :: errmsg REAL(KIND=8) :: wrf_intrp_value !NCLEND @@ -83,10 +90,6 @@ FUNCTION wrf_intrp_value(wvalp0, wvalp1, vlev, vcp0, vcp1, icase, errstat, errms REAL(KIND=8) :: valp0, valp1, rvalue REAL(KIND=8) :: chkdiff - !REAL(KIND=8), PARAMETER :: RGAS=287.04d0 - !REAL(KIND=8), PARAMETER :: USSALR=0.0065d0 - !REAL(KIND=8), PARAMETER :: SCLHT=RGAS*256.d0/9.81d0 - errstat = 0 valp0 = wvalp0 @@ -99,7 +102,7 @@ FUNCTION wrf_intrp_value(wvalp0, wvalp1, vlev, vcp0, vcp1, icase, errstat, errms chkdiff = vcp1 - vcp0 IF(chkdiff .EQ. 0) THEN errstat = ALGERR - errmsg = "bad difference in vcp's" + !errmsg = "bad difference in vcp's" wrf_intrp_value = 0 RETURN !PRINT *,"bad difference in vcp's" @@ -152,6 +155,7 @@ SUBROUTINE wrf_vintrp(datain, dataout, pres, tk, qvp, ght, terrain,& INTEGER :: nreqlvs, ripk !njx,niy INTEGER :: i, j, k, kupper !itriv INTEGER :: ifound, isign !miy,mjx + INTEGER :: log_errcnt, interp_errcnt, interp_errstat REAL(KIND=8), DIMENSION(ew,ns) :: tempout REAL(KIND=8) :: rlevel, vlev, diff REAL(KIND=8) :: tmpvlev @@ -166,33 +170,15 @@ SUBROUTINE wrf_vintrp(datain, dataout, pres, tk, qvp, ght, terrain,& REAL(KIND=8) :: tlcl, gammam CHARACTER(LEN=1) :: cvcord - !REAL(KIND=8), PARAMETER :: RGAS = 287.04d0 !J/K/kg - !REAL(KIND=8), PARAMETER :: RGASMD = .608d0 - !REAL(KIND=8), PARAMETER :: USSALR = .0065d0 ! deg C per m - !REAL(KIND=8), PARAMETER :: SCLHT = RGAS*256.d0/9.81d0 - !REAL(KIND=8), PARAMETER :: EPS = 0.622d0 - !REAL(KIND=8), PARAMETER :: RCONST = -9.81d0/(RGAS * USSALR) - !REAL(KIND=8), PARAMETER :: EXPON = RGAS*USSALR/9.81d0 - !REAL(KIND=8), PARAMETER :: EXPONI = 1./EXPON - !REAL(KIND=8), PARAMETER :: TLCLC1 = 2840.d0 - !REAL(KIND=8), PARAMETER :: TLCLC2 = 3.5d0 - !REAL(KIND=8), PARAMETER :: TLCLC3 = 4.805d0 - !REAL(KIND=8), PARAMETER :: TLCLC4 = 55.d0 - !REAL(KIND=8), PARAMETER :: THTECON1 = 3376.d0 ! K - !REAL(KIND=8), PARAMETER :: THTECON2 = 2.54d0 - !REAL(KIND=8), PARAMETER :: THTECON3 = 0.81d0 - !REAL(KIND=8), PARAMETER :: CP = 1004.d0 - !REAL(KIND=8), PARAMETER :: CPMD = 0.887d0 - !REAL(KIND=8), PARAMETER :: GAMMA = RGAS/CP - !REAL(KIND=8), PARAMETER :: GAMMAMD = RGASMD-CPMD - !REAL(KIND=8), PARAMETER :: CELKEL = 273.16d0 - ! Removes the warnings for uninitialized variables cvcord = '' plev = 0 zlev = 0 vlev = 0 errstat = 0 + interp_errcnt = 0 + interp_errstat = 0 + log_errcnt = 0 IF (vcor .EQ. 1) THEN cvcord = 'p' @@ -202,17 +188,6 @@ SUBROUTINE wrf_vintrp(datain, dataout, pres, tk, qvp, ght, terrain,& cvcord = 't' END IF - !miy = ns - !mjx = ew - !njx = ew - !niy = ns - - DO j = 1,ns - DO i = 1,ew - tempout(i,j) = rmsg - END DO - END DO - DO nreqlvs = 1,numlevels IF (cvcord .EQ. 'z') THEN ! Convert rlevel to meters from km @@ -224,8 +199,16 @@ SUBROUTINE wrf_vintrp(datain, dataout, pres, tk, qvp, ght, terrain,& vlev = interp_levels(nreqlvs) END IF + !$OMP PARALLEL DO COLLAPSE(2) PRIVATE(i, j, k, ifound, & + !$OMP ripk, vcp1, vcp0, valp0, valp1, tmpvlev, interp_errstat, & + !$OMP vclhsl, vctophsl, diff, isign, plhsl, zlhsl, ezlhsl, tlhsl, & + !$OMP zsurf, qvapor, psurf, psurfsm, ezsurf, plev, ezlev, zlev, & + !$OMP ptarget, dpmin, kupper, pbot, zbot, pratio, tbotextrap, & + !$OMP vt, tlev, gammam, e, tlcl) REDUCTION (+:log_errcnt, interp_errcnt) & + !$OMP SCHEDULE(runtime) DO j=1,ns DO i=1,ew + tempout(i,j) = rmsg ! Get the interpolated value that is within the model domain ifound = 0 DO k = 1,nz-1 @@ -245,35 +228,34 @@ SUBROUTINE wrf_vintrp(datain, dataout, pres, tk, qvp, ght, terrain,& IF (logp .EQ. 1) THEN vcp1 = LOG(vcp1) vcp0 = LOG(vcp0) - IF (vlev .EQ. 0.0D0) THEN - errstat = ALGERR - WRITE(errmsg, *) "Pres=0. Unable to take log of 0." - RETURN - !PRINT *,"Pressure value = 0" - !PRINT *,"Unable to take log of 0" - !STOP + IF (vlev .NE. 0.0D0) THEN + tmpvlev = LOG(vlev) + ELSE + log_errcnt = log_errcnt + 1 + tmpvlev = rmsg END IF - tmpvlev = LOG(vlev) ELSE tmpvlev = vlev END IF - tempout(i,j) = wrf_intrp_value(valp0, valp1, tmpvlev, vcp0, & - vcp1, icase, errstat, errmsg) - IF (errstat .NE. 0) THEN - RETURN - END IF - ! print *,"one ",i,j,tempout(i,j) - ifound = 1 + IF (tmpvlev .NE. rmsg) THEN + tempout(i,j) = wrf_intrp_value(valp0, valp1, tmpvlev, vcp0, & + vcp1, icase, interp_errstat) + + IF (interp_errstat .NE. 0) THEN + tempout(i,j) = rmsg + interp_errcnt = interp_errcnt + 1 + END IF + + ifound = 1 + END IF END IF - !GOTO 115 ! EXIT + EXIT END IF END DO !end for the k loop - !115 CONTINUE IF (ifound .EQ. 1) THEN !Grid point is in the model domain - !GOTO 333 ! CYCLE CYCLE END IF @@ -281,7 +263,6 @@ SUBROUTINE wrf_vintrp(datain, dataout, pres, tk, qvp, ght, terrain,& !all values above or below the model level to rmsg. IF (extrap .EQ. 0) THEN tempout(i,j) = rmsg - !GOTO 333 ! CYCLE CYCLE END IF @@ -296,8 +277,6 @@ SUBROUTINE wrf_vintrp(datain, dataout, pres, tk, qvp, ght, terrain,& IF (isign*vlev .GE. isign*vctophsl) THEN ! Assign the highest model level to the out array tempout(i,j) = datain(i,j,nz) - ! print *,"at warn",i,j,tempout(i,j) - !GOTO 333 ! CYCLE CYCLE END IF @@ -307,7 +286,6 @@ SUBROUTINE wrf_vintrp(datain, dataout, pres, tk, qvp, ght, terrain,& IF (datain(i,j,1) .EQ. rmsg) THEN tempout(i,j) = rmsg - !GOTO 333 ! CYCLE CYCLE END IF @@ -351,7 +329,6 @@ SUBROUTINE wrf_vintrp(datain, dataout, pres, tk, qvp, ght, terrain,& zlev = -SCLHT*LOG(ezlev) IF (icase .EQ. 2) THEN tempout(i,j) = zlev - !GOTO 333 ! CYCLE CYCLE END IF @@ -362,7 +339,6 @@ SUBROUTINE wrf_vintrp(datain, dataout, pres, tk, qvp, ght, terrain,& psurf + (ezsurf - ezlev)*plhsl)/(ezsurf - ezlhsl) IF (icase .EQ. 1) THEN tempout(i,j) = plev - !GOTO 333 ! CYCLE CYCLE END IF END IF @@ -374,12 +350,11 @@ SUBROUTINE wrf_vintrp(datain, dataout, pres, tk, qvp, ght, terrain,& ripk = nz-k+1 dp = ABS((pres(i,j,ripk) * 0.01D0) - ptarget) IF (dp .GT. dpmin) THEN - !GOTO 334 ! EXIT EXIT END IF dpmin = MIN(dpmin, dp) END DO - !334 + kupper = k-1 ripk = nz - kupper + 1 @@ -394,7 +369,6 @@ SUBROUTINE wrf_vintrp(datain, dataout, pres, tk, qvp, ght, terrain,& zlev = zbot + vt/USSALR*(1. - (vlev/pbot)**EXPON) IF (icase .EQ. 2) THEN tempout(i,j) = zlev - !GOTO 333 ! CYCLE CYCLE END IF ELSE IF (cvcord .EQ. 'z') THEN @@ -402,7 +376,6 @@ SUBROUTINE wrf_vintrp(datain, dataout, pres, tk, qvp, ght, terrain,& plev = pbot*(1. + USSALR/vt*(zbot - zlev))**EXPONI IF (icase .EQ. 1) THEN tempout(i,j) = plev - !GOTO 333 ! CYCLE CYCLE END IF END IF @@ -434,13 +407,27 @@ SUBROUTINE wrf_vintrp(datain, dataout, pres, tk, qvp, ght, terrain,& END DO END DO + !$OMP END PARALLEL DO + + IF (log_errcnt > 0) THEN + errstat = ALGERR + WRITE(errmsg, *) "Pres=0. Unable to take log of 0." + RETURN + END IF + + IF (interp_errcnt > 0) THEN + errstat = ALGERR + WRITE(errmsg, *) "bad difference in vcp's" + RETURN + END IF - ! print *,"----done----",interp_levels(nreqlvs) + !$OMP PARALLEL DO COLLAPSE(2) SCHEDULE(runtime) DO j = 1,ns DO i = 1,ew dataout(i,j,nreqlvs) = tempout(i,j) END DO END DO + !$OMP END PARALLEL DO END DO !end for the nreqlvs diff --git a/fortran/wrf_wind.f90 b/fortran/wrf_wind.f90 index b08c019..1d93d20 100644 --- a/fortran/wrf_wind.f90 +++ b/fortran/wrf_wind.f90 @@ -13,11 +13,13 @@ SUBROUTINE DCOMPUTEWSPD(wspd, u, v, nx, ny) INTEGER i, j + !$OMP PARALLEL DO COLLAPSE(2) SCHEDULE(runtime) DO j = 1,ny DO i = 1,nx wspd(i,j) = SQRT(u(i,j)*u(i,j) + v(i,j)*v(i,j)) END DO END DO + !$OMP END PARALLEL DO END SUBROUTINE DCOMPUTEWSPD @@ -38,11 +40,13 @@ SUBROUTINE DCOMPUTEWDIR(wdir, u, v, nx, ny) INTEGER i, j + !$OMP PARALLEL DO COLLAPSE(2) SCHEDULE(runtime) DO j = 1,ny DO i = 1,nx wdir(i,j) = MOD(270.0 - ATAN2(v(i,j), u(i,j)) * DEG_PER_RAD, 360.) END DO END DO + !$OMP END PARALLEL DO END SUBROUTINE DCOMPUTEWDIR diff --git a/fortran/wrffortran.pyf b/fortran/wrffortran.pyf index bf99a1e..7211ff6 100644 --- a/fortran/wrffortran.pyf +++ b/fortran/wrffortran.pyf @@ -33,6 +33,165 @@ python module _wrffortran ! in integer, optional,intent(in),check(shape(qvp,1)==mjx),depend(qvp) :: mjx=shape(qvp,1) integer, optional,intent(in),check(shape(qvp,2)==mkzh),depend(qvp) :: mkzh=shape(qvp,2) end subroutine deqthecalc + module omp_constants ! in :_wrffortran:omp.f90 + integer, parameter,optional :: fomp_nest_lock_kind=8 + integer(kind=4), parameter,optional :: fomp_sched_dynamic=2 + integer(kind=4), parameter,optional :: fomp_sched_guided=3 + integer, parameter,optional :: fomp_sched_kind=4 + integer(kind=4), parameter,optional :: fomp_sched_static=1 + integer, parameter,optional :: fomp_lock_kind=4 + integer(kind=4), parameter,optional :: fomp_sched_auto=4 + end module omp_constants + function fomp_enabled() ! in :_wrffortran:omp.f90 + threadsafe + logical :: fomp_enabled + end function fomp_enabled + subroutine fomp_set_num_threads(num_threads) ! in :_wrffortran:omp.f90 + threadsafe + integer intent(in) :: num_threads + end subroutine fomp_set_num_threads + function fomp_get_num_threads() ! in :_wrffortran:omp.f90 + threadsafe + integer :: fomp_get_num_threads + end function fomp_get_num_threads + function fomp_get_max_threads() ! in :_wrffortran:omp.f90 + threadsafe + integer :: fomp_get_max_threads + end function fomp_get_max_threads + function fomp_get_thread_num() ! in :_wrffortran:omp.f90 + threadsafe + integer :: fomp_get_thread_num + end function fomp_get_thread_num + function fomp_get_num_procs() ! in :_wrffortran:omp.f90 + threadsafe + integer :: fomp_get_num_procs + end function fomp_get_num_procs + function fomp_in_parallel() ! in :_wrffortran:omp.f90 + threadsafe + logical :: fomp_in_parallel + end function fomp_in_parallel + subroutine fomp_set_dynamic(dynamic_threads) ! in :_wrffortran:omp.f90 + threadsafe + logical intent(in) :: dynamic_threads + end subroutine fomp_set_dynamic + function fomp_get_dynamic() ! in :_wrffortran:omp.f90 + threadsafe + logical :: fomp_get_dynamic + end function fomp_get_dynamic + subroutine fomp_set_nested(nested) ! in :_wrffortran:omp.f90 + threadsafe + logical intent(in) :: nested + end subroutine fomp_set_nested + function fomp_get_nested() ! in :_wrffortran:omp.f90 + threadsafe + logical :: fomp_get_nested + end function fomp_get_nested + subroutine fomp_set_schedule(kind,modifier) ! in :_wrffortran:omp.f90 + threadsafe + use omp_constants, only: fomp_sched_kind + integer(kind=4) intent(in) :: kind + integer intent(in) :: modifier + end subroutine fomp_set_schedule + subroutine fomp_get_schedule(kind,modifier) ! in :_wrffortran:omp.f90 + threadsafe + use omp_constants, only: fomp_sched_kind + integer(kind=4) intent(out) :: kind + integer intent(out) :: modifier + end subroutine fomp_get_schedule + function fomp_get_thread_limit() ! in :_wrffortran:omp.f90 + threadsafe + integer :: fomp_get_thread_limit + end function fomp_get_thread_limit + subroutine fomp_set_max_active_levels(max_levels) ! in :_wrffortran:omp.f90 + threadsafe + integer intent(in) :: max_levels + end subroutine fomp_set_max_active_levels + function fomp_get_max_active_levels() ! in :_wrffortran:omp.f90 + threadsafe + integer :: fomp_get_max_active_levels + end function fomp_get_max_active_levels + function fomp_get_level() ! in :_wrffortran:omp.f90 + threadsafe + integer :: fomp_get_level + end function fomp_get_level + function fomp_get_ancestor_thread_num(level) ! in :_wrffortran:omp.f90 + threadsafe + integer intent(in) :: level + integer :: fomp_get_ancestor_thread_num + end function fomp_get_ancestor_thread_num + function fomp_get_team_size(level) ! in :_wrffortran:omp.f90 + threadsafe + integer intent(in) :: level + integer :: fomp_get_team_size + end function fomp_get_team_size + function fomp_get_active_level() ! in :_wrffortran:omp.f90 + threadsafe + integer :: fomp_get_active_level + end function fomp_get_active_level + function fomp_in_final() ! in :_wrffortran:omp.f90 + threadsafe + logical :: fomp_in_final + end function fomp_in_final + subroutine fomp_init_lock(svar) ! in :_wrffortran:omp.f90 + threadsafe + use omp_constants, only: fomp_lock_kind + integer(kind=4) intent(out) :: svar + end subroutine fomp_init_lock + subroutine fomp_init_nest_lock(nvar) ! in :_wrffortran:omp.f90 + threadsafe + use omp_constants, only: fomp_nest_lock_kind + integer(kind=8) intent(out) :: nvar + end subroutine fomp_init_nest_lock + subroutine fomp_destroy_lock(svar) ! in :_wrffortran:omp.f90 + threadsafe + use omp_constants, only: fomp_lock_kind + integer(kind=4) intent(inout) :: svar + end subroutine fomp_destroy_lock + subroutine fomp_destroy_nest_lock(nvar) ! in :_wrffortran:omp.f90 + threadsafe + use omp_constants, only: fomp_nest_lock_kind + integer(kind=8) intent(inout) :: nvar + end subroutine fomp_destroy_nest_lock + subroutine fomp_set_lock(svar) ! in :_wrffortran:omp.f90 + threadsafe + use omp_constants, only: fomp_lock_kind + integer(kind=4) intent(inout) :: svar + end subroutine fomp_set_lock + subroutine fomp_set_nest_lock(nvar) ! in :_wrffortran:omp.f90 + threadsafe + use omp_constants, only: fomp_nest_lock_kind + integer(kind=8) intent(inout) :: nvar + end subroutine fomp_set_nest_lock + subroutine fomp_unset_lock(svar) ! in :_wrffortran:omp.f90 + threadsafe + use omp_constants, only: fomp_lock_kind + integer(kind=4) intent(inout) :: svar + end subroutine fomp_unset_lock + subroutine fomp_unset_nest_lock(nvar) ! in :_wrffortran:omp.f90 + threadsafe + use omp_constants, only: fomp_nest_lock_kind + integer(kind=8) intent(inout) :: nvar + end subroutine fomp_unset_nest_lock + function fomp_test_lock(svar) ! in :_wrffortran:omp.f90 + threadsafe + use omp_constants, only: fomp_lock_kind + integer(kind=4) intent(inout) :: svar + logical :: fomp_test_lock + end function fomp_test_lock + function fomp_test_nest_lock(nvar) ! in :_wrffortran:omp.f90 + threadsafe + use omp_constants, only: fomp_nest_lock_kind + integer(kind=8) intent(inout) :: nvar + integer :: fomp_test_nest_lock + end function fomp_test_nest_lock + function fomp_get_wtime() ! in :_wrffortran:omp.f90 + threadsafe + real(kind=8) :: fomp_get_wtime + end function fomp_get_wtime + function fomp_get_wtick() ! in :_wrffortran:omp.f90 + threadsafe + real(kind=8) :: fomp_get_wtick + end function fomp_get_wtick function tvirtual(temp,ratmix) ! in :_wrffortran:rip_cape.f90 threadsafe use wrf_constants, only: eps @@ -63,36 +222,55 @@ python module _wrffortran ! in integer intent(inout) :: errstat character*(*) intent(inout) :: errmsg end subroutine dlookup_table - subroutine dpfcalc(prs,sfp,pf,miy,mjx,mkzh,ter_follow) ! in :_wrffortran:rip_cape.f90 - real(kind=8) dimension(miy,mjx,mkzh),intent(in) :: prs - real(kind=8) dimension(miy,mjx),intent(in),depend(miy,mjx) :: sfp - real(kind=8) dimension(miy,mjx,mkzh),intent(out),depend(miy,mjx,mkzh) :: pf - integer, optional,intent(in),check(shape(prs,0)==miy),depend(prs) :: miy=shape(prs,0) - integer, optional,intent(in),check(shape(prs,1)==mjx),depend(prs) :: mjx=shape(prs,1) - integer, optional,intent(in),check(shape(prs,2)==mkzh),depend(prs) :: mkzh=shape(prs,2) + subroutine dpfcalc(prs,sfp,pf,mix,mjy,mkzh,ter_follow) ! in :_wrffortran:rip_cape.f90 + real(kind=8) dimension(mkzh,mix,mjy),intent(in) :: prs + real(kind=8) dimension(mix,mjy),intent(in),depend(mix,mjy) :: sfp + real(kind=8) dimension(mkzh,mix,mjy),intent(out),depend(mkzh,mix,mjy) :: pf + integer, optional,intent(in),check(shape(prs,1)==mix),depend(prs) :: mix=shape(prs,1) + integer, optional,intent(in),check(shape(prs,2)==mjy),depend(prs) :: mjy=shape(prs,2) + integer, optional,intent(in),check(shape(prs,0)==mkzh),depend(prs) :: mkzh=shape(prs,0) integer intent(in) :: ter_follow end subroutine dpfcalc - subroutine dcapecalc3d(prs,tmk,qvp,ght,ter,sfp,cape,cin,cmsg,miy,mjx,mkzh,i3dflag,ter_follow,psafile,errstat,errmsg) ! in :_wrffortran:rip_cape.f90 - threadsafe - use wrf_constants, only: tlclc2,gamma,tlclc1,rgasmd,tlclc4,g,tlclc3,thtecon3,eps,rd,cpmd,celkel,gammamd,eslcon2,eslcon1,cp,thtecon1,algerr,ezero,thtecon2 - real(kind=8) dimension(miy,mjx,mkzh),intent(in) :: prs - real(kind=8) dimension(miy,mjx,mkzh),intent(in),depend(miy,mjx,mkzh) :: tmk - real(kind=8) dimension(miy,mjx,mkzh),intent(in),depend(miy,mjx,mkzh) :: qvp - real(kind=8) dimension(miy,mjx,mkzh),intent(in),depend(miy,mjx,mkzh) :: ght - real(kind=8) dimension(miy,mjx),intent(in),depend(miy,mjx) :: ter - real(kind=8) dimension(miy,mjx),intent(in),depend(miy,mjx) :: sfp - real(kind=8) dimension(miy,mjx,mkzh),intent(out,in),depend(miy,mjx,mkzh) :: cape - real(kind=8) dimension(miy,mjx,mkzh),intent(out,in),depend(miy,mjx,mkzh) :: cin + subroutine dcapecalc3d(prs,tmk,qvp,ght,ter,sfp,cape,cin,cmsg,mix,mjy,mkzh,ter_follow,psafile,errstat,errmsg) ! in :_wrffortran:rip_cape.f90 + threadsafe + use wrf_constants, only: tlclc2,gamma,tlclc1,rgasmd,tlclc4,g,tlclc3,thtecon3,eps,rd,cpmd,celkel,gammamd,eslcon2,eslcon1,cp,thtecon1,ezero,thtecon2 + real(kind=8) dimension(mix,mjy,mkzh),intent(in) :: prs + real(kind=8) dimension(mix,mjy,mkzh),intent(in),depend(mix,mjy,mkzh) :: tmk + real(kind=8) dimension(mix,mjy,mkzh),intent(in),depend(mix,mjy,mkzh) :: qvp + real(kind=8) dimension(mix,mjy,mkzh),intent(in),depend(mix,mjy,mkzh) :: ght + real(kind=8) dimension(mix,mjy),intent(in),depend(mix,mjy) :: ter + real(kind=8) dimension(mix,mjy),intent(in),depend(mix,mjy) :: sfp + real(kind=8) dimension(mix,mjy,mkzh),intent(out,in),depend(mix,mjy,mkzh) :: cape + real(kind=8) dimension(mix,mjy,mkzh),intent(out,in),depend(mix,mjy,mkzh) :: cin real(kind=8) intent(in) :: cmsg - integer, optional,intent(in),check(shape(prs,0)==miy),depend(prs) :: miy=shape(prs,0) - integer, optional,intent(in),check(shape(prs,1)==mjx),depend(prs) :: mjx=shape(prs,1) + integer, optional,intent(in),check(shape(prs,0)==mix),depend(prs) :: mix=shape(prs,0) + integer, optional,intent(in),check(shape(prs,1)==mjy),depend(prs) :: mjy=shape(prs,1) integer, optional,intent(in),check(shape(prs,2)==mkzh),depend(prs) :: mkzh=shape(prs,2) - integer intent(in) :: i3dflag integer intent(in) :: ter_follow character*(*) intent(in) :: psafile integer intent(inout) :: errstat character*(*) intent(inout) :: errmsg end subroutine dcapecalc3d + subroutine dcapecalc2d(prs,tmk,qvp,ght,ter,sfp,cape,cin,cmsg,mix,mjy,mkzh,ter_follow,psafile,errstat,errmsg) ! in :_wrffortran:rip_cape.f90 + threadsafe + use wrf_constants, only: tlclc2,gamma,tlclc1,rgasmd,tlclc4,g,tlclc3,thtecon3,eps,rd,cpmd,celkel,gammamd,eslcon2,eslcon1,cp,thtecon1,ezero,thtecon2 + real(kind=8) dimension(mix,mjy,mkzh),intent(in) :: prs + real(kind=8) dimension(mix,mjy,mkzh),intent(in),depend(mix,mjy,mkzh) :: tmk + real(kind=8) dimension(mix,mjy,mkzh),intent(in),depend(mix,mjy,mkzh) :: qvp + real(kind=8) dimension(mix,mjy,mkzh),intent(in),depend(mix,mjy,mkzh) :: ght + real(kind=8) dimension(mix,mjy),intent(in),depend(mix,mjy) :: ter + real(kind=8) dimension(mix,mjy),intent(in),depend(mix,mjy) :: sfp + real(kind=8) dimension(mix,mjy,mkzh),intent(out,in),depend(mix,mjy,mkzh) :: cape + real(kind=8) dimension(mix,mjy,mkzh),intent(out,in),depend(mix,mjy,mkzh) :: cin + real(kind=8) intent(in) :: cmsg + integer, optional,intent(in),check(shape(prs,0)==mix),depend(prs) :: mix=shape(prs,0) + integer, optional,intent(in),check(shape(prs,1)==mjy),depend(prs) :: mjy=shape(prs,1) + integer, optional,intent(in),check(shape(prs,2)==mkzh),depend(prs) :: mkzh=shape(prs,2) + integer intent(in) :: ter_follow + character*(*) intent(in) :: psafile + integer intent(inout) :: errstat + character*(*) intent(inout) :: errmsg + end subroutine dcapecalc2d subroutine dcloudfrac(pres,rh,lowc,midc,highc,nz,ns,ew) ! in :_wrffortran:wrf_cloud_fracf.f90 threadsafe real(kind=8) dimension(ew,ns,nz),intent(in) :: pres @@ -104,8 +282,27 @@ python module _wrffortran ! in integer, optional,check(shape(pres,1)==ns),depend(pres) :: ns=shape(pres,1) integer, optional,check(shape(pres,0)==ew),depend(pres) :: ew=shape(pres,0) end subroutine dcloudfrac + subroutine dcloudfrac2(vert,rh,vert_inc_w_height,low_thresh,mid_thresh,high_thresh,msg,lowc,midc,highc,nz,ns,ew) ! in :_wrffortran:wrf_cloud_fracf.f90 + threadsafe + real(kind=8) dimension(ew,ns,nz),intent(in) :: vert + real(kind=8) dimension(ew,ns,nz),intent(in),depend(ew,ns,nz) :: rh + integer intent(in) :: vert_inc_w_height + real(kind=8) intent(in) :: low_thresh + real(kind=8) intent(in) :: mid_thresh + real(kind=8) intent(in) :: high_thresh + real(kind=8) intent(in) :: msg + real(kind=8) dimension(ew,ns),intent(out,in),depend(ew,ns) :: lowc + real(kind=8) dimension(ew,ns),intent(out,in),depend(ew,ns) :: midc + real(kind=8) dimension(ew,ns),intent(out,in),depend(ew,ns) :: highc + integer, optional,check(shape(vert,2)==nz),depend(vert) :: nz=shape(vert,2) + integer, optional,check(shape(vert,1)==ns),depend(vert) :: ns=shape(vert,1) + integer, optional,check(shape(vert,0)==ew),depend(vert) :: ew=shape(vert,0) + end subroutine dcloudfrac2 module wrf_constants ! in :_wrffortran:wrf_constants.f90 + real(kind=4), parameter,optional :: default_fill_float=9.96920996839e+36 + integer(kind=8), parameter,optional :: default_fill_int64=int(-9223372036854775806d0, kind=8) real(kind=8), parameter,optional :: wrf_earth_radius=6370000.d0 + character(len=1), parameter,optional :: default_fill_char=achar(0) real(kind=8), parameter,optional :: rhowat=1000.d0 real(kind=8), parameter,optional :: t_base=300.0d0 real(kind=8), parameter,optional :: cp=1004.5d0 @@ -115,6 +312,7 @@ python module _wrffortran ! in real(kind=8), parameter,optional :: p1000mb=100000.d0 real(kind=8), parameter,optional :: rv=461.6d0 real(kind=8), parameter,optional,depend(pi) :: rad_per_deg=pi/180.d0 + real(kind=8), parameter,optional :: default_fill_double=9.96920996839e+36 real(kind=8), parameter,optional :: rd=287.d0 real(kind=8), parameter,optional :: abscoef=.145d0 real(kind=8), parameter,optional :: celkel=273.15d0 @@ -122,16 +320,18 @@ python module _wrffortran ! in real(kind=8), parameter,optional :: eslcon2=29.65d0 real(kind=8), parameter,optional :: eslcon1=17.67d0 real(kind=8), parameter,optional :: pi=3.1415926535897932384626433d0 + integer(kind=1), parameter,optional :: default_fill_int8=-127 real(kind=8), parameter,optional :: tlclc2=3.5d0 real(kind=8), parameter,optional :: tlclc3=4.805d0 real(kind=8), parameter,optional :: rho_g=400.d0 real(kind=8), parameter,optional :: tlclc1=2840.d0 real(kind=8), parameter,optional :: tlclc4=55.d0 + integer(kind=2), parameter,optional :: default_fill_int16=-32767 real(kind=8), parameter,optional,depend(pi) :: deg_per_rad=180.d0/pi real(kind=8), parameter,optional :: cpmd=.887d0 real(kind=8), parameter,optional,depend(rd,g) :: sclht=rd*256.d0/g real(kind=8), parameter,optional :: ussalr=0.0065d0 - real(kind=8), parameter,optional :: default_fill=9.9692099683868690d36 + real(kind=8), parameter,optional :: default_fill=9.96920996839e+36 real(kind=8), parameter,optional :: rho_s=100.d0 real(kind=8), parameter,optional,depend(rhowat) :: rho_r=1000.0 real(kind=8), parameter,optional :: alpha=0.224d0 @@ -141,9 +341,10 @@ python module _wrffortran ! in real(kind=8), parameter,optional :: rgasmd=.608d0 real(kind=8), parameter,optional :: g=9.81d0 integer, optional :: errlen=512 - real(kind=8), parameter,optional :: eps=0.622d0 + integer(kind=4), parameter,optional :: default_fill_int32=-2147483647 real(kind=8), parameter,optional :: gamma_seven=720.d0 real(kind=8), parameter,optional,depend(cpmd,rgasmd) :: gammamd=-0.279 + real(kind=8), parameter,optional :: eps=0.622d0 integer, optional :: algerr=64 real(kind=8), parameter,optional,depend(cp,rd) :: gamma=0.285714285714 real(kind=8), parameter,optional,depend(expon,rd,ussalr,g) :: exponi=5.25864379523 @@ -508,8 +709,8 @@ python module _wrffortran ! in threadsafe real(kind=8) dimension(ew,ns,nz),intent(out,in) :: out real(kind=8) dimension(ew,ns,nz),intent(inout),depend(ew,ns,nz) :: in - real(kind=8) dimension(ew,ns,nz),depend(ew,ns,nz) :: lvprs - real(kind=8) dimension(ew,ns),depend(ew,ns) :: cor + real(kind=8) dimension(ew,ns,nz),intent(in),depend(ew,ns,nz) :: lvprs + real(kind=8) dimension(ew,ns),intent(in),depend(ew,ns) :: cor integer intent(in) :: idir real(kind=8) intent(in) :: delta integer, optional,intent(in),check(shape(out,0)==ew),depend(out) :: ew=shape(out,0) @@ -517,7 +718,7 @@ python module _wrffortran ! in integer, optional,intent(in),check(shape(out,2)==nz),depend(out) :: nz=shape(out,2) integer intent(in) :: icorsw end subroutine wrf_monotonic - function wrf_intrp_value(wvalp0,wvalp1,vlev,vcp0,vcp1,icase,errstat,errmsg) ! in :_wrffortran:wrf_vinterp.f90 + function wrf_intrp_value(wvalp0,wvalp1,vlev,vcp0,vcp1,icase,errstat) ! in :_wrffortran:wrf_vinterp.f90 threadsafe use wrf_constants, only: sclht,algerr real(kind=8) intent(in) :: wvalp0 @@ -527,7 +728,6 @@ python module _wrffortran ! in real(kind=8) intent(in) :: vcp1 integer intent(in) :: icase integer intent(inout) :: errstat - character*(*) intent(inout) :: errmsg real(kind=8) :: wrf_intrp_value end function wrf_intrp_value subroutine wrf_vintrp(datain,dataout,pres,tk,qvp,ght,terrain,sfp,smsfp,vcarray,interp_levels,numlevels,icase,ew,ns,nz,extrap,vcor,logp,rmsg,errstat,errmsg) ! in :_wrffortran:wrf_vinterp.f90 diff --git a/requirements.txt b/requirements.txt index 83c125c..b044c9f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ -numpy>=1.10 +numpy>=1.11 wrapt>=1.10 +setuptools>=38.0 diff --git a/setup.py b/setup.py index 8f42dad..41ec9e6 100755 --- a/setup.py +++ b/setup.py @@ -22,6 +22,7 @@ ext1 = numpy.distutils.core.Extension( "fortran/wrf_pw.f90", "fortran/wrf_vinterp.f90", "fortran/wrf_wind.f90", + "fortran/omp.f90", "fortran/wrffortran.pyf"] ) diff --git a/src/wrf/__init__.py b/src/wrf/__init__.py index ae2d6f1..25f0762 100755 --- a/src/wrf/__init__.py +++ b/src/wrf/__init__.py @@ -1,5 +1,18 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) +import os +import pkg_resources + +# For gfortran+msvc combination, extra shared libraries may exist (stored by numpy.distutils) +if os.name == "nt": + try: + req = pkg_resources.Requirement.parse("wrf-python") + extra_dll_dir = pkg_resources.resource_filename(req, + "wrf-python/.libs") + if os.path.isdir(extra_dll_dir): + os.environ["PATH"] += os.pathsep + extra_dll_dir + except ImportError: + pass from . import api from .api import * diff --git a/src/wrf/api.py b/src/wrf/api.py index c5dec8c..46f7d46 100644 --- a/src/wrf/api.py +++ b/src/wrf/api.py @@ -2,17 +2,36 @@ from .config import (xarray_enabled, disable_xarray, enable_xarray, cartopy_enabled, disable_cartopy, enable_cartopy, basemap_enabled, disable_basemap, enable_basemap, pyngl_enabled, enable_pyngl, disable_pyngl, - set_cache_size, get_cache_size) -from .constants import ALL_TIMES, Constants, ConversionFactors, ProjectionTypes + set_cache_size, get_cache_size, omp_enabled) +from .constants import (ALL_TIMES, Constants, ConversionFactors, + ProjectionTypes, default_fill, + OMP_SCHED_STATIC, OMP_SCHED_DYNAMIC, + OMP_SCHED_GUIDED, OMP_SCHED_AUTO) from .destag import destagger from .routines import getvar from .computation import (xy, interp1d, interp2dxy, interpz3d, slp, tk, td, rh, uvmet, smooth2d, cape_2d, cape_3d, cloudfrac, ctt, dbz, srhel, udhel, avo, pvo, eth, wetbulb, tvirtual, omega, pw) -from .extension import DiagnosticError +from .extension import (DiagnosticError, omp_set_num_threads, + omp_get_num_threads, + omp_get_max_threads, omp_get_thread_num, + omp_get_num_procs, omp_in_parallel, + omp_set_dynamic, omp_get_dynamic, omp_set_nested, + omp_get_nested, omp_set_schedule, + omp_get_schedule, omp_get_thread_limit, + omp_set_max_active_levels, + omp_get_max_active_levels, omp_get_level, + omp_get_ancestor_thread_num, omp_get_team_size, + omp_get_active_level, omp_in_final, + omp_init_lock, omp_init_nest_lock, + omp_destroy_lock, omp_destroy_nest_lock, + omp_set_lock, omp_set_nest_lock, + omp_unset_lock, omp_unset_nest_lock, + omp_test_lock, omp_test_nest_lock, + omp_get_wtime, omp_get_wtick) from .interp import (interplevel, vertcross, interpline, vinterp) -from .latlon import (xy_to_ll, ll_to_xy, xy_to_ll_proj, ll_to_xy_proj) +from .g_latlon import (xy_to_ll, ll_to_xy, xy_to_ll_proj, ll_to_xy_proj) from .py3compat import (viewitems, viewkeys, viewvalues, isstr, py2round, py3range, ucode) from .util import (to_np, extract_global_attrs, is_standard_wrf_var, @@ -26,7 +45,7 @@ from .util import (to_np, extract_global_attrs, is_standard_wrf_var, has_time_coord, is_multi_file, is_multi_time_req, get_coord_pairs, is_time_coord_var, geo_bounds, get_cartopy, get_basemap, get_pyngl, cartopy_xlim, - cartopy_ylim, latlon_coords) + cartopy_ylim, latlon_coords, ll_points, pairs_to_latlon) from .geobnds import GeoBounds, NullGeoBounds from .projection import (WrfProj, NullProjection, LambertConformal, Mercator, PolarStereographic, LatLon, RotatedLatLon, @@ -41,15 +60,33 @@ __all__ += ["xarray_enabled", "disable_xarray", "enable_xarray", "cartopy_enabled", "disable_cartopy", "enable_cartopy", "basemap_enabled", "disable_basemap", "enable_basemap", "pyngl_enabled", "enable_pyngl", "disable_pyngl", - "set_cache_size", "get_cache_size"] -__all__ += ["ALL_TIMES", "Constants", "ConversionFactors", "ProjectionTypes"] + "set_cache_size", "get_cache_size", "omp_enabled"] +__all__ += ["ALL_TIMES", "Constants", "ConversionFactors", "ProjectionTypes", + "default_fill", "OMP_SCHED_STATIC", "OMP_SCHED_DYNAMIC", + "OMP_SCHED_GUIDED", "OMP_SCHED_AUTO"] __all__ += ["destagger"] __all__ += ["getvar"] __all__ += ["xy", "interp1d", "interp2dxy", "interpz3d", "slp", "tk", "td", "rh", "uvmet", "smooth2d", "cape_2d", "cape_3d", "cloudfrac", "ctt", "dbz", "srhel", "udhel", "avo", "pvo", "eth", "wetbulb", "tvirtual", "omega", "pw"] -__all__ += ["DiagnosticError"] +__all__ += ["DiagnosticError", "omp_set_num_threads", + "omp_get_num_threads", + "omp_get_max_threads", "omp_get_thread_num", + "omp_get_num_procs", "omp_in_parallel", + "omp_set_dynamic", "omp_get_dynamic", "omp_set_nested", + "omp_get_nested", "omp_set_schedule", + "omp_get_schedule", "omp_get_thread_limit", + "omp_set_max_active_levels", + "omp_get_max_active_levels", "omp_get_level", + "omp_get_ancestor_thread_num", "omp_get_team_size", + "omp_get_active_level", "omp_in_final", + "omp_init_lock", "omp_init_nest_lock", + "omp_destroy_lock", "omp_destroy_nest_lock", + "omp_set_lock", "omp_set_nest_lock", + "omp_unset_lock", "omp_unset_nest_lock", + "omp_test_lock", "omp_test_nest_lock", + "omp_get_wtime", "omp_get_wtick"] __all__ += ["interplevel", "vertcross", "interpline", "vinterp"] __all__ += ["xy_to_ll", "ll_to_xy", "xy_to_ll_proj", "ll_to_xy_proj"] __all__ += ["viewitems", "viewkeys", "viewvalues", "isstr", "py2round", @@ -65,7 +102,7 @@ __all__ += ["to_np", "extract_global_attrs", "is_standard_wrf_var", "has_time_coord", "is_multi_file", "is_multi_time_req", "get_coord_pairs", "is_time_coord_var", "geo_bounds", "get_cartopy", "get_basemap", "get_pyngl", "cartopy_xlim", - "cartopy_ylim", "latlon_coords"] + "cartopy_ylim", "latlon_coords", "ll_points", "pairs_to_latlon"] __all__ += ["GeoBounds", "NullGeoBounds"] __all__ += ["WrfProj", "NullProjection", "LambertConformal", "Mercator", "PolarStereographic", "LatLon", "RotatedLatLon", "getproj"] diff --git a/src/wrf/cloudfrac.py b/src/wrf/cloudfrac.py deleted file mode 100644 index 75b500f..0000000 --- a/src/wrf/cloudfrac.py +++ /dev/null @@ -1,88 +0,0 @@ -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -from .constants import Constants -from .extension import _tk, _rh, _cloudfrac -from .metadecorators import set_cloudfrac_metadata -from .util import extract_vars - -@set_cloudfrac_metadata() -def get_cloudfrac(wrfin, timeidx=0, method="cat", squeeze=True, - cache=None, meta=True, _key=None): - """Return the cloud fraction. - - The leftmost dimension of the returned array represents three different - quantities: - - - return_val[0,...] will contain LOW level cloud fraction - - return_val[1,...] will contain MID level cloud fraction - - return_val[2,...] will contain HIGH level cloud fraction - - This functions extracts the necessary variables from the NetCDF file - object in order to perform the calculation. - - Args: - - wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`, or an \ - iterable): WRF-ARW NetCDF - data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` - or an iterable sequence of the aforementioned types. - - timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The - desired time index. This value can be a positive integer, - negative integer, or - :data:`wrf.ALL_TIMES` (an alias for None) to return - all times in the file or sequence. The default is 0. - - method (:obj:`str`, optional): The aggregation method to use for - sequences. Must be either 'cat' or 'join'. - 'cat' combines the data along the Time dimension. - 'join' creates a new dimension for the file index. - The default is 'cat'. - - squeeze (:obj:`bool`, optional): Set to False to prevent dimensions - with a size of 1 from being automatically removed from the shape - of the output. Default is True. - - cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) - that can be used to supply pre-extracted NetCDF variables to the - computational routines. It is primarily used for internal - purposes, but can also be used to improve performance by - eliminating the need to repeatedly extract the same variables - used in multiple diagnostics calculations, particularly when using - large sequences of files. - Default is None. - - meta (:obj:`bool`, optional): Set to False to disable metadata and - return :class:`numpy.ndarray` instead of - :class:`xarray.DataArray`. Default is True. - - _key (:obj:`int`, optional): A caching key. This is used for internal - purposes only. Default is None. - - Returns: - :class:`xarray.DataArray` or :class:`numpy.ndarray`: The - cloud fraction array whose leftmost dimension is 3 (LOW=0, MID=1, - HIGH=2). - If xarray is enabled and the *meta* parameter is True, then the result - will be a :class:`xarray.DataArray` object. Otherwise, the result will - be a :class:`numpy.ndarray` object with no metadata. - - """ - - ncvars = extract_vars(wrfin, timeidx, ("P", "PB", "QVAPOR", "T"), - method, squeeze, cache, meta=False, - _key=_key) - - p = ncvars["P"] - pb = ncvars["PB"] - qv = ncvars["QVAPOR"] - t = ncvars["T"] - - full_p = p + pb - full_t = t + Constants.T_BASE - - tk = _tk(full_p, full_t) - rh = _rh(qv, full_p, tk) - - return _cloudfrac(full_p, rh) diff --git a/src/wrf/computation.py b/src/wrf/computation.py index 81e37dd..3973709 100644 --- a/src/wrf/computation.py +++ b/src/wrf/computation.py @@ -4,7 +4,7 @@ from __future__ import (absolute_import, division, print_function, import numpy as np import numpy.ma as ma -from .constants import Constants +from .constants import default_fill from .extension import (_interpz3d, _interp2dxy, _interp1d, _slp, _tk, _td, _rh, _uvmet, _smooth2d, _cape, _cloudfrac, _ctt, _dbz, _srhel, _udhel, _avo, _pvo, _eth, _wetbulb, _tv, @@ -104,7 +104,7 @@ def xy(field, pivot_point=None, angle=None, start_point=None, end_point=None, @set_interp_metadata("1d") -def interp1d(field, z_in, z_out, missing=Constants.DEFAULT_FILL, +def interp1d(field, z_in, z_out, missing=default_fill(np.float64), meta=True): """Return the linear interpolation of a one-dimensional variable. @@ -128,7 +128,7 @@ def interp1d(field, z_in, z_out, missing=Constants.DEFAULT_FILL, to. Must be the same type as *z_in*. missing (:obj:`float`, optional): The fill value to use for the - output. Default is :data:`wrf.Constants.DEFAULT_FILL`. + output. Default is :data:`wrf.default_fill(np.float64)`. meta (:obj:`bool`, optional): Set to False to disable metadata and return :class:`numpy.ndarray` instead of @@ -251,7 +251,7 @@ def interp2dxy(field3d, xy, meta=True): @set_interp_metadata("horiz") -def interpz3d(field3d, vert, desiredlev, missing=Constants.DEFAULT_FILL, +def interpz3d(field3d, vert, desiredlev, missing=default_fill(np.float64), meta=True): """Return the field interpolated to a specified pressure or height level. @@ -280,7 +280,7 @@ def interpz3d(field3d, vert, desiredlev, missing=Constants.DEFAULT_FILL, Must be in the same units as the *vert* parameter. missing (:obj:`float`): The fill value to use for the output. - Default is :data:`wrf.Constants.DEFAULT_FILL`. + Default is :data:`wrf.default_fill(numpy.float64)`. meta (:obj:`bool`): Set to False to disable metadata and return :class:`numpy.ndarray` instead of @@ -713,7 +713,7 @@ def smooth2d(field, passes, meta=True): @set_cape_alg_metadata(is2d=True, copyarg="pres_hpa") def cape_2d(pres_hpa, tkel, qv, height, terrain, psfc_hpa, ter_follow, - missing=Constants.DEFAULT_FILL, meta=True): + missing=default_fill(np.float64), meta=True): """Return the two-dimensional CAPE, CIN, LCL, and LFC. This function calculates the maximum convective available potential @@ -790,7 +790,7 @@ def cape_2d(pres_hpa, tkel, qv, height, terrain, psfc_hpa, ter_follow, False for pressure level data. missing (:obj:`float`, optional): The fill value to use for the - output. Default is :data:`wrf.Constants.DEFAULT_FILL`. + output. Default is :data:`wrf.default_fill(numpy.float64)`. meta (:obj:`bool`): Set to False to disable metadata and return :class:`numpy.ndarray` instead of @@ -843,7 +843,7 @@ def cape_2d(pres_hpa, tkel, qv, height, terrain, psfc_hpa, ter_follow, @set_cape_alg_metadata(is2d=False, copyarg="pres_hpa") def cape_3d(pres_hpa, tkel, qv, height, terrain, psfc_hpa, ter_follow, - missing=Constants.DEFAULT_FILL, meta=True): + missing=default_fill(np.float64), meta=True): """Return the three-dimensional CAPE and CIN. This function calculates the maximum convective available potential @@ -926,7 +926,7 @@ def cape_3d(pres_hpa, tkel, qv, height, terrain, psfc_hpa, ter_follow, False for pressure level data. missing (:obj:`float`, optional): The fill value to use for the - output. Default is :data:`wrf.Constants.DEFAULT_FILL`. + output. Default is :data:`wrf.default_fill(numpy.float64)`. meta (:obj:`bool`): Set to False to disable metadata and return :class:`numpy.ndarray` instead of @@ -962,8 +962,9 @@ def cape_3d(pres_hpa, tkel, qv, height, terrain, psfc_hpa, ter_follow, return ma.masked_values(cape_cin, missing) -@set_cloudfrac_alg_metadata(copyarg="pres") -def cloudfrac(pres, relh, meta=True): +@set_cloudfrac_alg_metadata(copyarg="vert") +def cloudfrac(vert, relh, vert_inc_w_height, low_thresh, mid_thresh, + high_thresh, missing=default_fill(np.float64), meta=True): """Return the cloud fraction. The leftmost dimension of the returned array represents three different @@ -973,14 +974,22 @@ def cloudfrac(pres, relh, meta=True): - return_val[1,...] will contain MID level cloud fraction - return_val[2,...] will contain HIGH level cloud fraction + The *low_thresh*, *mid_thresh*, and *high_threshold* paramters specify the + low, mid, and high cloud levels in the same units as *vert*. + + In mountainous regions, there is a possibility + that the lowest WRF level will be higher than the low_cloud or mid_cloud + threshold. When this happens, a fill value will be used in the output at + that location. + This is the raw computational algorithm and does not extract any variables from WRF output files. Use :meth:`wrf.getvar` to both extract and compute diagnostic variables. Args: - pres (:class:`xarray.DataArray` or :class:`numpy.ndarray`): Full - pressure (perturbation + base state pressure) in [Pa], with the + vert (:class:`xarray.DataArray` or :class:`numpy.ndarray`): The + vertical coordinate variable (usually pressure or height) with the rightmost dimensions as bottom_top x south_north x west_east Note: @@ -990,8 +999,21 @@ def cloudfrac(pres, relh, meta=True): dimension names to the output. Otherwise, default names will be used. - relh (:class:`xarray.DataArray` or :class:`numpy.ndarray`) Relative - humidity with the same dimensionality as *pres* + relh (:class:`xarray.DataArray` or :class:`numpy.ndarray`): Relative + humidity with the same dimensionality as *vert* + + vert_inc_w_height (:obj:`int`): Set to 1 if the vertical coordinate + values increase with height (height values). Set to 0 if the + vertical coordinate values decrease with height (pressure values). + + low_thresh (:obj:`float`): The bottom vertical threshold for what is + considered a low cloud. + + mid_thresh (:obj:`float`): The bottom vertical threshold for what is + considered a mid level cloud. + + high_thresh (:obj:`float`): The bottom vertical threshold for what is + considered a high cloud. meta (:obj:`bool`): Set to False to disable metadata and return :class:`numpy.ndarray` instead of @@ -1016,7 +1038,10 @@ def cloudfrac(pres, relh, meta=True): :meth:`wrf.getvar`, :meth:`wrf.rh` """ - return _cloudfrac(pres, relh) + cfrac = _cloudfrac(vert, relh, vert_inc_w_height, low_thresh, mid_thresh, + high_thresh, missing) + + return ma.masked_values(cfrac, missing) @set_alg_metadata(2, "pres_hpa", refvarndims=3, diff --git a/src/wrf/config.py b/src/wrf/config.py index c7f28b2..d22d5de 100644 --- a/src/wrf/config.py +++ b/src/wrf/config.py @@ -2,35 +2,68 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) from threading import local +import wrapt + +from ._wrffortran import (fomp_enabled, fomp_set_num_threads, + fomp_set_schedule, fomp_set_dynamic, + omp_constants) _local_config = local() -_local_config.xarray_enabled = True -_local_config.cartopy_enabled = True -_local_config.basemap_enabled = True -_local_config.pyngl_enabled = True -_local_config.cache_size = 20 - -try: - from xarray import DataArray -except ImportError: - _local_config.xarray_enabled = False - -try: - from cartopy import crs -except ImportError: - _local_config.cartopy_enabled = False + +def _init_local(): + global _local_config -try: - from mpl_toolkits.basemap import Basemap -except ImportError: - _local_config.basemap_enabled = False + _local_config.xarray_enabled = True + _local_config.cartopy_enabled = True + _local_config.basemap_enabled = True + _local_config.pyngl_enabled = True + _local_config.cache_size = 20 + _local_config.initialized = True -try: - from Ngl import Resources -except ImportError: - _local_config.pyngl_enabled = False + try: + from xarray import DataArray + except ImportError: + _local_config.xarray_enabled = False + + try: + from cartopy import crs + except ImportError: + _local_config.cartopy_enabled = False + + try: + from mpl_toolkits.basemap import Basemap + except ImportError: + _local_config.basemap_enabled = False + + try: + from Ngl import Resources + except ImportError: + _local_config.pyngl_enabled = False + + +# Initialize the main thread's configuration +_init_local() + + +def init_local(): + """A decorator that initializes thread local data if necessary.""" + @wrapt.decorator + def func_wrapper(wrapped, instance, args, kwargs): + global _local_config + try: + init = _local_config.init + except AttributeError: + _init_local() + else: + if not init: + _init_local() + + return wrapped(*args, **kwargs) + + return func_wrapper +@init_local() def xarray_enabled(): """Return True if xarray is installed and enabled. @@ -43,18 +76,21 @@ def xarray_enabled(): return _local_config.xarray_enabled +@init_local() def disable_xarray(): """Disable xarray.""" global _local_config _local_config.xarray_enabled = False - + +@init_local() def enable_xarray(): """Enable xarray.""" global _local_config _local_config.xarray_enabled = True - + +@init_local() def cartopy_enabled(): """Return True if cartopy is installed and enabled. @@ -67,18 +103,21 @@ def cartopy_enabled(): return _local_config.cartopy_enabled +@init_local() def enable_cartopy(): """Enable cartopy.""" global _local_config _local_config.cartopy_enabled = True - + +@init_local() def disable_cartopy(): """Disable cartopy.""" global _local_config _local_config.cartopy_enabled = True - + +@init_local() def basemap_enabled(): """Return True if basemap is installed and enabled. @@ -91,17 +130,21 @@ def basemap_enabled(): return _local_config.basemap_enabled +@init_local() def enable_basemap(): """Enable basemap.""" global _local_config _local_config.basemap_enabled = True - + +@init_local() def disable_basemap(): """Disable basemap.""" global _local_config _local_config.basemap_enabled = True - + + +@init_local() def pyngl_enabled(): """Return True if pyngl is installed and enabled. @@ -114,18 +157,21 @@ def pyngl_enabled(): return _local_config.pyngl_enabled +@init_local() def enable_pyngl(): """Enable pyngl.""" global _local_config _local_config.pyngl_enabled = True - + +@init_local() def disable_pyngl(): """Disable pyngl.""" global _local_config _local_config.pyngl_enabled = True - + +@init_local() def set_cache_size(size): """Set the maximum number of items that the threadlocal cache can retain. @@ -143,7 +189,8 @@ def set_cache_size(size): global _local_config _local_config.cache_size = size - + +@init_local() def get_cache_size(): """Return the maximum number of items that the threadlocal cache can retain. @@ -155,5 +202,24 @@ def get_cache_size(): global _local_config return int(_local_config.cache_size) + +def omp_enabled(): + """Return True if OpenMP is enabled. + + OpenMP is only enabled if compiled with OpenMP features. + + Returns: + + :obj:`bool`: True if OpenMP is enabled, otherwise False. + + """ + + return True if fomp_enabled() else False +# Set OpenMP to use 1 thread, static scheduler, and no dynamic +# Note: Using the raw extension functions here to prevent possible +# circular import problems in the future. +fomp_set_num_threads(1) +fomp_set_schedule(omp_constants.fomp_sched_static, 0) +fomp_set_dynamic(False) diff --git a/src/wrf/constants.py b/src/wrf/constants.py index c8270db..6a6dc90 100755 --- a/src/wrf/constants.py +++ b/src/wrf/constants.py @@ -1,10 +1,12 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) +from sys import version_info +import struct import numpy as np from .py3compat import viewitems -from wrf._wrffortran import wrf_constants +from ._wrffortran import wrf_constants, omp_constants #: Indicates that all times should be used in a diagnostic routine. ALL_TIMES = None @@ -15,6 +17,12 @@ class Constants(object): for key,val in viewitems(wrf_constants.__dict__): setattr(Constants, key.upper(), np.asscalar(val)) +OMP_SCHED_STATIC = omp_constants.fomp_sched_static +OMP_SCHED_DYNAMIC = omp_constants.fomp_sched_dynamic +OMP_SCHED_GUIDED = omp_constants.fomp_sched_guided +OMP_SCHED_AUTO = omp_constants.fomp_sched_auto + + class ConversionFactors(object): PA_TO_HPA = .01 PA_TO_TORR = 760.0/101325.0 @@ -29,11 +37,49 @@ class ConversionFactors(object): M_TO_FT = 3.28084 M_TO_MILES = .000621371 + class ProjectionTypes(object): ZERO = 0 LAMBERT_CONFORMAL = 1 POLAR_STEREOGRAPHIC = 2 MERCATOR = 3 LAT_LON = 6 + +# Create the default fill mapping based on type. +_DEFAULT_FILL_MAP = {None: Constants.DEFAULT_FILL, + np.dtype(np.bool_) : False, + np.dtype(np.intc) : Constants.DEFAULT_FILL_INT32, # Usually true + np.dtype(np.int8) : Constants.DEFAULT_FILL_INT8, + np.dtype(np.uint8) : 255, + np.dtype(np.int16) : Constants.DEFAULT_FILL_INT16, + np.dtype(np.uint16) : 65535, + np.dtype(np.int32) : Constants.DEFAULT_FILL_INT32, + np.dtype(np.uint32) : 4294967295, + np.dtype(np.int64) : Constants.DEFAULT_FILL_INT64, + np.dtype(np.uint64) : 18446744073709551614, + np.dtype(np.float_) : Constants.DEFAULT_FILL_DOUBLE, + np.dtype(np.float32) : Constants.DEFAULT_FILL_FLOAT, + np.dtype(np.float64) : Constants.DEFAULT_FILL_DOUBLE + } + +if version_info >= (3, ): + _DEFAULT_FILL_MAP[np.int_] = Constants.DEFAULT_FILL_INT64 +else: + _DEFAULT_FILL_MAP[np.int_] = Constants.DEFAULT_FILL_INT32 + +if (struct.calcsize("P") == 8): + _DEFAULT_FILL_MAP[np.intp] = Constants.DEFAULT_FILL_INT64 +else: + _DEFAULT_FILL_MAP[np.intp] = Constants.DEFAULT_FILL_INT32 + + +# Add the integers based on python 2.x or 3.x +def default_fill(dtype=None): + dt = np.dtype(dtype) if dtype is not None else None + return _DEFAULT_FILL_MAP.get(dt, 0) + + + + \ No newline at end of file diff --git a/src/wrf/coordpair.py b/src/wrf/coordpair.py index ad42332..8b3121a 100644 --- a/src/wrf/coordpair.py +++ b/src/wrf/coordpair.py @@ -3,7 +3,7 @@ from __future__ import (absolute_import, division, print_function, from .py3compat import py2round - + def _binary_operator(operator): """Function wrapper for binary operators. @@ -262,5 +262,5 @@ for operator in ("__neg__", "__pos__", "__abs__", "__invert__"): for operator in ("__lt__", "__le__", "__eq__", "__ne__", "__gt__", "__ge__"): setattr(CoordPair, operator, _cmp_operator(operator)) - + \ No newline at end of file diff --git a/src/wrf/decorators.py b/src/wrf/decorators.py index 406dcec..7b1797f 100644 --- a/src/wrf/decorators.py +++ b/src/wrf/decorators.py @@ -11,7 +11,7 @@ from .units import do_conversion, check_units, dealias_and_clean_unit from .util import iter_left_indexes, from_args, to_np, combine_dims from .py3compat import viewitems, viewvalues, isstr from .config import xarray_enabled -from .constants import Constants +from .constants import default_fill if xarray_enabled(): from xarray import DataArray @@ -201,7 +201,7 @@ def left_iteration(ref_var_expected_dims, if all_masked: for output in viewvalues(outd): output[left_and_slice_idxs] = ( - Constants.DEFAULT_FILL) + default_fill(np.float64)) skip_missing = True mask_output = True break @@ -240,9 +240,9 @@ def left_iteration(ref_var_expected_dims, # Mostly when used with join if mask_output: if isinstance(output, np.ndarray): - output = ma.masked_values(output, Constants.DEFAULT_FILL) + output = ma.masked_values(output, default_fill(np.float64)) else: - output = tuple(ma.masked_values(arr, Constants.DEFAULT_FILL) + output = tuple(ma.masked_values(arr, default_fill(np.float64)) for arr in output) return output diff --git a/src/wrf/extension.py b/src/wrf/extension.py index 5f3fcea..a3357a3 100755 --- a/src/wrf/extension.py +++ b/src/wrf/extension.py @@ -3,16 +3,32 @@ from __future__ import (absolute_import, division, print_function, import numpy as np -from .constants import Constants +from .constants import Constants, default_fill -from ._wrffortran import (dcomputetk, dinterp3dz, dinterp2dxy, dinterp1d, +from wrf._wrffortran import (dcomputetk, dinterp3dz, dinterp2dxy, dinterp1d, dcomputeseaprs, dfilter2d, dcomputerh, dcomputeuvmet, - dcomputetd, dcapecalc3d, dcloudfrac, wrfcttcalc, - calcdbz, dcalrelhl, dcalcuh, dcomputepv, + dcomputetd, dcapecalc2d, dcapecalc3d, dcloudfrac2, + wrfcttcalc, calcdbz, dcalrelhl, dcalcuh, dcomputepv, dcomputeabsvort, dlltoij, dijtoll, deqthecalc, omgcalc, virtual_temp, wetbulbcalc, dcomputepw, wrf_monotonic, wrf_vintrp, dcomputewspd, - dcomputewdir) + dcomputewdir, + fomp_set_num_threads, fomp_get_num_threads, + fomp_get_max_threads, fomp_get_thread_num, + fomp_get_num_procs, fomp_in_parallel, + fomp_set_dynamic, fomp_get_dynamic, fomp_set_nested, + fomp_get_nested, fomp_set_schedule, + fomp_get_schedule, fomp_get_thread_limit, + fomp_set_max_active_levels, + fomp_get_max_active_levels, fomp_get_level, + fomp_get_ancestor_thread_num, fomp_get_team_size, + fomp_get_active_level, fomp_in_final, + fomp_init_lock, fomp_init_nest_lock, + fomp_destroy_lock, fomp_destroy_nest_lock, + fomp_set_lock, fomp_set_nest_lock, + fomp_unset_lock, fomp_unset_nest_lock, + fomp_test_lock, fomp_test_nest_lock, + fomp_get_wtime, fomp_get_wtick, fomp_enabled) from .decorators import (left_iteration, cast_type, extract_and_transpose, check_args) @@ -24,7 +40,7 @@ from .specialdec import (uvmet_left_iter, cape_left_iter, class DiagnosticError(Exception): """Raised when an error occurs in a diagnostic routine.""" def __init__(self, message=None): - """Initialize a :class:`wrf.DiagnosticError` objection. + """Initialize a :class:`wrf.DiagnosticError` object. Args: @@ -365,8 +381,8 @@ def _eth(qv, tk, p, outview=None): @cast_type(arg_idxs=(0,1,2,3)) @extract_and_transpose() def _uvmet(u, v, lat, lon, cen_long, cone, isstag=0, has_missing=False, - umissing=Constants.DEFAULT_FILL, vmissing=Constants.DEFAULT_FILL, - uvmetmissing=Constants.DEFAULT_FILL, outview=None): + umissing=default_fill(np.float64), vmissing=default_fill(np.float64), + uvmetmissing=default_fill(np.float64), outview=None): """Wrapper for dcomputeuvmet. Located in wrf_user.f90. @@ -597,8 +613,13 @@ def _cape(p_hpa, tk, qv, ht, ter, sfp, missing, i3dflag, ter_follow, errstat = np.array(0) errmsg = np.zeros(Constants.ERRLEN, "c") + if i3dflag: + cape_routine = dcapecalc3d + else: + cape_routine = dcapecalc2d + # note that p_hpa, tk, qv, and ht have the vertical flipped - result = dcapecalc3d(p_hpa, + result = cape_routine(p_hpa, tk, qv, ht, @@ -607,7 +628,6 @@ def _cape(p_hpa, tk, qv, ht, ter, sfp, missing, i3dflag, ter_follow, capeview, cinview, missing, - i3dflag, ter_follow, psafile, errstat, @@ -620,28 +640,34 @@ def _cape(p_hpa, tk, qv, ht, ter, sfp, missing, i3dflag, ter_follow, @check_args(0, 3, (3,3)) @cloudfrac_left_iter() -@cast_type(arg_idxs=(0, 1), outviews=("lowview", "medview", "highview")) -@extract_and_transpose(outviews=("lowview", "medview", "hightview")) -def _cloudfrac(p, rh, lowview=None, medview=None, highview=None): - """Wrapper for dcloudfrace. +@cast_type(arg_idxs=(0, 1), outviews=("lowview", "midview", "highview")) +@extract_and_transpose(outviews=("lowview", "midview", "highview")) +def _cloudfrac(vert, rh, vert_inc_w_height, low_thresh, mid_thresh, + high_thresh, missing, lowview=None, midview=None, highview=None): + """Wrapper for dcloudfrac2. Located in wrf_cloud_fracf.f90. """ if lowview is None: - lowview = np.zeros(p.shape[0:2], p.dtype, order="F") + lowview = np.zeros(vert.shape[0:2], vert.dtype, order="F") - if medview is None: - medview = np.zeros(p.shape[0:2], p.dtype, order="F") + if midview is None: + midview = np.zeros(vert.shape[0:2], vert.dtype, order="F") if highview is None: - highview = np.zeros(p.shape[0:2], p.dtype, order="F") - - result = dcloudfrac(p, - rh, - lowview, - medview, - highview) + highview = np.zeros(vert.shape[0:2], vert.dtype, order="F") + + result = dcloudfrac2(vert, + rh, + vert_inc_w_height, + low_thresh, + mid_thresh, + high_thresh, + missing, + lowview, + midview, + highview) return result @@ -769,7 +795,7 @@ def _smooth2d(field, passes, outview=None): if isinstance(field, np.ma.MaskedArray): missing = field.fill_value else: - missing = Constants.DEFAULT_FILL + missing = default_fill(np.float64) if outview is None: outview = field.copy(order="A") @@ -898,5 +924,686 @@ def _wdir(u, v, outview=None): v) return result + + +# OpenMP runtime wrappers + +def omp_set_num_threads(num_threads): + """Specify the number of threads to use. + + The omp_set_num_threads routine affects the number of threads to be used + for subsequent parallel regions that do not specify a num_threads + clause, by setting the value of the first element of the nthreads-var + ICV of the current task. + + Args: + + num_threads (a positive :obj:`int`): The number of threads. Must be + positive. + + Returns: + + None. + + """ + if num_threads < 0 and fomp_enabled(): + raise ValueError("'num_threads' must be a positive integer.") + + fomp_set_num_threads(num_threads) + + +def omp_get_num_threads(): + """Return the number of threads in the current team. + + The omp_get_num_threads routine returns the number of threads in the + team executing the parallel region to which the routine region binds. + If called from the sequential part of a program, this routine returns 1. + + Note: + + This function always returns 1 when called from within Python. + + Returns: + + :obj:`int`: The number of threads in the current team. + + See Also: + + :meth:`wrf.omp_get_max_threads`, :meth:`wrf.omp_set_num_threads` + + """ + return fomp_get_num_threads() + + +def omp_get_max_threads(): + """Return the maximum number of threads that can be used in a parallel \ + region. + + The omp_get_max_threads routine returns an upper bound on the number of + threads that could be used to form a new team if a parallel construct + without a num_threads clause were encountered after execution returns from + this routine. + + Returns: + + :obj:`int`: The number of threads in the current team. + + See Also: + + :meth:`wrf.omp_set_num_threads` + + """ + return fomp_get_max_threads() + + +def omp_get_thread_num(): + """Return the thread number, within the current team, of the \ + calling thread. + + The omp_get_thread_num routine returns the thread number of the calling + thread, within the team executing the parallel region to which the routine + region binds. The thread number is an integer between 0 and one less than + the value returned by omp_get_num_threads, inclusive. The thread number of + the master thread of the team is 0. The routine returns 0 if it is called + from the sequential part of a program. + + Note: + + This function always returns 0 when called from within Python. + + Returns: + + :obj:`int`: The thread number. + + See Also: + + :meth:`wrf.omp_get_num_procs` + + """ + return fomp_get_thread_num() + + +def omp_get_num_procs(): + """Return the number of processors on the device. + + The omp_get_num_procs routine returns the number of processors that are + available to the device at the time the routine is called. This value may + change between the time that it is determined by the omp_get_num_procs + routine and the time that it is read in the calling context due to system + actions outside the control of the OpenMP implementation. + + Returns: + + :obj:`int`: The number of processors. + + """ + return fomp_get_num_procs() + + +def omp_in_parallel(): + """Return 1 if the active-levels-var ICV is greater than zero; \ + otherwise, return 0. + + The effect of the omp_in_parallel routine is to return 1 if the current + task is enclosed by an active parallel region, and the parallel region is + enclosed by the outermost initial task region on the device; otherwise it + returns 0. + + Note: + + This function always returns 0 when called from within Python. + + Returns: + + :obj:`int`: Returns 1 if the active-levels-var ICV is greater than + zero. Otherwise, it returns 0. + + """ + return fomp_in_parallel() + + +def omp_set_dynamic(dynamic_threads): + """Enable or disable dynamic adjustment of the number of threads \ + available for the execution of subsequent parallel regions by setting the \ + value of the dyn-var ICV. + + For implementations that support dynamic adjustment of the number of + threads, if the argument to omp_set_dynamic evaluates to True, dynamic + adjustment is enabled for the current task; otherwise, dynamic adjustment + is disabled for the current task. For implementations that do not support + dynamic adjustment of the number of threads this routine has no effect: + the value of dyn-var remains false. + + Args: + + dynamic_threads (:obj:`bool`): Set to True to support the dynamic + adjustment of the number of threads. Otherwise, set to False. + + Returns: + + None. + + See Also: + + :meth:`wrf.omp_get_dynamic` + + """ + fomp_set_dynamic(dynamic_threads) + + +def omp_get_dynamic(): + """Return the value of the dyn-var ICV, which determines whether \ + dynamic adjustment of the number of threads is enabled or disabled. + + This routine returns 1 if dynamic adjustment of the number of threads + is enabled for the current task; it returns 0, otherwise. If an + implementation does not support dynamic adjustment of the + number of threads, then this routine always returns 0. + + Returns: + + :obj:`int`: Returns 1 if dynamic thread adjustment is enabled, 0 + if disabled. + + See Also: + + :meth:`wrf.omp_set_dynamic` + + """ + return fomp_get_dynamic() + + +def omp_set_nested(nested): + """Enable or disable nested parallelism, by setting the nest-var ICV + + For implementations that support nested parallelism, if the argument to + omp_set_nested evaluates to True, nested parallelism is enabled for the + current task; otherwise, nested parallelism is disabled for the current + task. For implementations that do not support nested parallelism, this + routine has no effect: the value of nest-var remains False. + + Args: + + dynamic_threads (:obj:`bool`): Set to True to support nested + parallelism, otherwise False. + + Returns: + + None + + See Also: + + :meth:`wrf.omp_get_nested` + + """ + fomp_set_nested(nested) + + +def omp_get_nested(): + """Return the value of the nest-var ICV, which determines if nested \ + parallelism is enabled or disabled + + This routine returns 1 if nested parallelism is enabled for the current + task; it returns 0, otherwise. If an implementation does not support + nested parallelism, this routine always returns 0. + + Returns: + + :obj:`int`: Returns 1 if nested parallelism is enabled, otherwise 0. + + See Also: + + :meth:`wrf.omp_set_nested` + + """ + return fomp_get_nested() + + +def omp_set_schedule(kind, modifier=0): + """Set the schedule that is applied when *runtime* is used as \ + schedule kind, by setting the value of the run-sched-var ICV. + + The effect of this routine is to set the value of the run-sched-var ICV + of the current task to the values specified in the two arguments. The + schedule is set to the schedule type specified by the first argument kind. + It can be any of the standard schedule types or any other implementation + specific one. For the schedule types static, dynamic, and guided the + chunk_size is set to the value of the second argument, or to the default + chunk_size if the value of the second argument is less than 1; for the + schedule type auto the second argument has no meaning; for implementation + specific schedule types, the values and associated meanings of the second + argument are implementation defined. + + Args: + + kind (:obj:`int`): Must be :data:`wrf.OMP_SCHED_STATIC`, + :data:`wrf.OMP_SCHED_DYNAMIC`, :data:`wrf.OMP_SCHED_GUIDED`, + or :data:`wrf.OMP_SCHED_AUTO`. + + modifier(:obj:`int`): An implementation specific value, depending on + the choice for *kind*. This parameter is alternatively named + chunk_size in some OpenMP documentation. Default is 0, which + means the OpenMP implementation will use its default value. + + Returns: + + None + + See Also: + + :meth:`wrf.omp_get_schedule` + + """ + fomp_set_schedule(kind, modifier) + + +def omp_get_schedule(): + """Return the schedule that is applied when the runtime schedule is used. + + This routine returns the run-sched-var ICV in the task to which the routine + binds. The first item is the schedule kind, which will be one of + :data:`wrf.OMP_SCHED_STATIC`, :data:`wrf.OMP_SCHED_DYNAMIC`, + :data:`wrf.OMP_SCHED_GUIDED`, or :data:`wrf.OMP_SCHED_AUTO`. The second + item returned is the modifier, which is often named chunk_size in + OpenMP documentation. + + Returns: + + :obj:`tuple`: The first item is an :obj:`int` for the schedule *kind*. + The second items is :obj:`int` for the *modifier* (chunk_size). + + See Also: + + :meth:`wrf.omp_set_schedule` + + """ + return fomp_get_schedule() + + +def omp_get_thread_limit(): + """Return the maximum number of OpenMP threads available to participate \ + in the current contention group. + + The omp_get_thread_limit routine returns the value of the thread-limit-var + ICV. + + Returns: + + :obj:`int`: The number of OpenMP threads available to participate in + the current contention group. + + See Also: + + :meth:`wrf.omp_get_max_threads` + + """ + return fomp_get_thread_limit() + + +def omp_set_max_active_levels(max_levels): + """Limit the number of nested active parallel regions on the device, \ + by setting the max-active-levels-var ICV. + + The effect of this routine is to set the value of the max-active-levels-var + ICV to the value specified in the argument. If the number of parallel + levels requested exceeds the number of levels of parallelism supported by + the implementation, the value of the max-active-levels-var ICV will be set + to the number of parallel levels supported by the implementation. This + routine has the described effect only when called from a sequential part + of the program. When called from within an explicit parallel region, the + effect of this routine is implementation defined. + + Args: + + max_levels (:obj:`int`): The maximum number of nested active parallel + regions. + + Returns: + + None. + + See Also: + + :meth:`wrf.omp_get_max_active_levels` + + """ + fomp_set_max_active_levels(max_levels) + + +def omp_get_max_active_levels(): + """Return the value of the max-active-levels-var ICV, which determines \ + the maximum number of nested active parallel regions on the device + + The omp_get_max_active_levels routine returns the value of the + max-active-levels-var ICV, which determines the maximum number of nested + active parallel regions on the device. + + Returns: + + :obj:`int`: The maximum number of nested active parallel regions. + + See Also: + + :meth:`wrf.omp_set_max_active_levels` + + """ + return fomp_get_max_active_levels() + + +def omp_get_level(): + """Return the value of the levels-var ICV. + + The effect of the omp_get_level routine is to return the number of nested + parallel regions (whether active or inactive) enclosing the current task + such that all of the parallel regions are enclosed by the outermost initial + task region on the current device. + + Returns: + + :obj:`int`: The number of nested parallel regions. + + See Also: + + :meth:`wrf.omp_get_active_level` + + """ + return fomp_get_level() + + +def omp_get_ancestor_thread_num(level): + """Return, for a given nested level of the current thread, the thread \ + number of the ancestor of the current thread. + + The omp_get_ancestor_thread_num routine returns the thread number of the + ancestor at a given nest level of the current thread or the thread number + of the current thread. If the requested nest level is outside the range of + 0 and the nest level of the current thread, as returned by the + omp_get_level routine, the routine returns -1. + + Args: + + level (:obj:`int`): The nested level of the current thread. + + Returns: + + :obj:`int`: The thread number of the ancestor at a given nest level + of the current thread. + + See Also: + + :meth:`wrf.omp_get_max_active_levels`, :meth:`wrf.omp_get_level` + + """ + return fomp_get_ancestor_thread_num(level) + + +def omp_get_team_size(level): + """Return, for a given nested level of the current thread, the size \ + of the thread team to which the ancestor or the current thread belongs + + The omp_get_team_size routine returns the size of the thread team to which + the ancestor or the current thread belongs. If the requested nested level + is outside the range of 0 and the nested level of the current thread, as + returned by the omp_get_level routine, the routine returns -1. Inactive + parallel regions are regarded like active parallel regions executed with + one thread. + + Args: + + level (:obj:`int`): The nested level of the current thread. + + Returns: + + :obj:`int`: The size of the thread team. + + See Also: + + :meth:`wrf.omp_get_ancestor_thread_num` + + """ + return fomp_get_team_size(level) + + +def omp_get_active_level(): + """Return the value of the active-level-vars ICV. + + The effect of the omp_get_active_level routine is to return the number of + nested, active parallel regions enclosing the current task such that all + of the parallel regions are enclosed by the outermost initial task region + on the current device. + + Returns: + + :obj:`int`: The number of nested activate parallel regions. + + See Also: + + :meth:`wrf.omp_get_team_size` + + """ + return fomp_get_active_level() + + +def omp_in_final(): + """Return 1 (True) if the routine is executed in a final task region; \ + otherwise, it returns 0 (False). + + Returns: + + :obj:`int`: Return 1 if the routine is executed in a final task + region, 0 otherwise. + + """ + return fomp_in_final() + + +def omp_init_lock(): + """Initialize a simple OpenMP lock. + + Returns: + + :obj:`int`: An integer representing the lock. + + See Also: + + :meth:`wrf.omp_init_nest_lock`, :meth:`wrf.omp_destroy_lock` + + """ + return fomp_init_lock() + + +def omp_init_nest_lock(): + """Initialize a nestable OpenMP lock. + + Returns: + + :obj:`int`: An integer representing the nestable lock. + + See Also: + + :meth:`wrf.omp_init_lock` + + """ + return fomp_init_nest_lock() + + +def omp_destroy_lock(svar): + """Destroy a simple OpenMP lock. + + This sets the lock to an uninitialized state. + + Args: + + svar (:obj:`int`): An integer representing the lock. + + See Also: + + :meth:`wrf.omp_destroy_nest_lock`, :meth:`wrf.omp_init_lock` + + """ + fomp_destroy_lock(svar) + + +def omp_destroy_nest_lock(nvar): + """Destroy a nestable OpenMP lock. + + This sets the lock to an uninitialized state. + + Args: + + nvar (:obj:`int`): An integer representing the nestable lock. + + See Also: + + :meth:`wrf.omp_destroy_lock`, :meth:`wrf.omp_init_nest_lock` + + """ + fomp_destroy_nest_lock(nvar) + + +def omp_set_lock(svar): + """Set a simple OpenMP lock. + + Args: + + svar (:obj:`int`): An integer representing the lock. + + See Also: + + :meth:`wrf.omp_unset_lock`, :meth:`wrf.omp_set_nest_lock` + + """ + fomp_set_lock(svar) + + +def omp_set_nest_lock(nvar): + """Set a nestable OpenMP lock. + + Args: + + nvar (:obj:`int`): An integer representing the nestable lock. + + See Also: + + :meth:`wrf.omp_unset_nest_lock`, :meth:`wrf.omp_set_lock` + + """ + fomp_set_nest_lock(nvar) + + +def omp_unset_lock(svar): + """Unset a simple OpenMP lock. + + Args: + + svar (:obj:`int`): An integer representing the simple lock. + + See Also: + + :meth:`wrf.omp_unset_nest_lock`, :meth:`wrf.omp_set_lock` + + """ + fomp_unset_lock(svar) + + +def omp_unset_nest_lock(nvar): + """Unset a nestable OpenMP lock. + + Args: + + nvar (:obj:`int`): An integer representing the nestable lock. + + See Also: + + :meth:`wrf.omp_set_nest_lock`, :meth:`wrf.omp_unset_lock` + + """ + fomp_unset_nest_lock(nvar) + + +def omp_test_lock(svar): + """Test a simple OpenMP lock. + + This method attempts to set the lock, but does not suspend execution. + + Args: + + svar (:obj:`int`): An integer representing the simple lock. + + Returns: + + :obj:`int`: Returns 1 (True) if the lock is successfully set, + otherwise 0 (False). + + See Also: + + :meth:`wrf.test_nest_lock`, :meth:`wrf.omp_set_lock` + + """ + return fomp_test_lock(svar) + + +def omp_test_nest_lock(nvar): + """Test a nestable OpenMP lock. + + This method attempts to set the lock, but does not suspend execution. + + Args: + + nvar (:obj:`int`): An integer representing the simple lock. + + Returns: + + :obj:`int`: Returns the nesting count if successful, + otherwise 0 (False). + + See Also: + + :meth:`wrf.test_lock`, :meth:`wrf.omp_set_nest_lock` + + """ + return fomp_test_nest_lock(nvar) + + +def omp_get_wtime(): + """Return elapsed wall clock time in seconds. + + The omp_get_wtime routine returns a value equal to the elapsed wall clock + time in seconds since some “time in the past”. The actual + “time in the past” is arbitrary, but it is guaranteed not to change during + the execution of the application program. The time returned is a + “per-thread time”, so it is not required to be globally consistent across + all threads participating in an application. + + Returns: + + :obj:`float`: Returns the wall clock time in seconds. + + See Also: + + :meth:`wrf.omp_get_wtick` + + """ + return fomp_get_wtime() + + +def omp_get_wtick(): + """Return the precision of the timer used by :meth:`wrf.omp_get_wtime`. + + The omp_get_wtick routine returns a value equal to the number of + seconds between successive clock ticks of the timer used by + :meth:`wrf.omp_get_wtime`. + + Returns: + + :obj:`float`: Returns the precision of the timer. + + See Also: + + :meth:`wrf.omp_get_wtime` + + """ + return fomp_get_wtick() + diff --git a/src/wrf/cape.py b/src/wrf/g_cape.py similarity index 96% rename from src/wrf/cape.py rename to src/wrf/g_cape.py index 7cb7efd..5f5ef10 100755 --- a/src/wrf/cape.py +++ b/src/wrf/g_cape.py @@ -6,13 +6,13 @@ import numpy.ma as ma from .extension import _tk, _cape from .destag import destagger -from .constants import Constants, ConversionFactors +from .constants import default_fill, Constants, ConversionFactors from .util import extract_vars from .metadecorators import set_cape_metadata @set_cape_metadata(is2d=True) def get_2dcape(wrfin, timeidx=0, method="cat", squeeze=True, cache=None, - meta=True, _key=None, missing=Constants.DEFAULT_FILL): + meta=True, _key=None, missing=default_fill(np.float64)): """Return the 2d fields of CAPE, CIN, LCL, and LFC. The leftmost dimension of the returned array represents four different @@ -66,7 +66,7 @@ def get_2dcape(wrfin, timeidx=0, method="cat", squeeze=True, cache=None, purposes only. Default is None. missing (:obj:`float`): The fill value to use for the output. - Default is :data:`wrf.Constants.DEFAULT_FILL`. + Default is :data:`wrf.default_fill(np.float64)`. Returns: :class:`xarray.DataArray` or :class:`numpy.ndarray`: The @@ -130,7 +130,7 @@ def get_2dcape(wrfin, timeidx=0, method="cat", squeeze=True, cache=None, @set_cape_metadata(is2d=False) def get_3dcape(wrfin, timeidx=0, method="cat", squeeze=True, cache=None, meta=True, - _key=None, missing=Constants.DEFAULT_FILL): + _key=None, missing=default_fill(np.float64)): """Return the three-dimensional CAPE and CIN. The leftmost dimension of the returned array represents two different @@ -182,7 +182,7 @@ def get_3dcape(wrfin, timeidx=0, method="cat", purposes only. Default is None. missing (:obj:`float`): The fill value to use for the output. - Default is :data:`wrf.Constants.DEFAULT_FILL`. + Default is :data:`wrf.default_fill(np.float64)`. Returns: :class:`xarray.DataArray` or :class:`numpy.ndarray`: The diff --git a/src/wrf/g_cloudfrac.py b/src/wrf/g_cloudfrac.py new file mode 100644 index 0000000..2417dfa --- /dev/null +++ b/src/wrf/g_cloudfrac.py @@ -0,0 +1,164 @@ +from __future__ import (absolute_import, division, print_function, + unicode_literals) + +import numpy as np +import numpy.ma as ma + +from .constants import Constants, default_fill +from .extension import _tk, _rh, _cloudfrac +from .metadecorators import set_cloudfrac_metadata +from .util import extract_vars +from .g_geoht import _get_geoht + + +@set_cloudfrac_metadata() +def get_cloudfrac(wrfin, timeidx=0, method="cat", squeeze=True, + cache=None, meta=True, _key=None, + vert_type="height_agl", low_thresh=None, mid_thresh=None, + high_thresh=None, missing=default_fill(np.float64)): + """Return the cloud fraction for low, mid, and high level clouds. + + The leftmost dimension of the returned array represents three different + quantities: + + - return_val[0,...] will contain LOW level cloud fraction + - return_val[1,...] will contain MID level cloud fraction + - return_val[2,...] will contain HIGH level cloud fraction + + If the vertical coordinate type is 'height_agl' or 'height_msl', the + default cloud levels are defined as: + + 300 m <= low_cloud < 2000 m + 2000 m <= mid_cloud < 6000 m + 6000 m <= high_cloud + + For 'pressure', the default cloud levels are defined as: + + 97000 Pa <= low_cloud < 80000 Pa + 80000 Pa <= mid_cloud < 45000 Pa + 45000 Pa <= high_cloud + + Note that the default low cloud levels are chosen to + exclude clouds near the surface (fog). If you want fog included, set + *low_thresh* to ~99500 Pa if *vert_type* is set to 'pressure', or 15 m if + using 'height_msl' or 'height_agl'. Keep in mind that the lowest mass grid + points are slightly above the ground, and in order to find clouds, the + *low_thresh* needs to be set to values that are slightly greater than + (less than) the lowest height (pressure) values. + + When using 'pressure' or 'height_agl' for *vert_type*, there is a + possibility that the lowest WRF level will be higher than the low_cloud or + mid_cloud threshold, particularly for mountainous regions. When this + happens, a fill value will be used in the output. + + This functions extracts the necessary variables from the NetCDF file + object in order to perform the calculation. + + Args: + + wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`, or an \ + iterable): WRF-ARW NetCDF + data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` + or an iterable sequence of the aforementioned types. + + timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The + desired time index. This value can be a positive integer, + negative integer, or + :data:`wrf.ALL_TIMES` (an alias for None) to return + all times in the file or sequence. The default is 0. + + method (:obj:`str`, optional): The aggregation method to use for + sequences. Must be either 'cat' or 'join'. + 'cat' combines the data along the Time dimension. + 'join' creates a new dimension for the file index. + The default is 'cat'. + + squeeze (:obj:`bool`, optional): Set to False to prevent dimensions + with a size of 1 from being automatically removed from the shape + of the output. Default is True. + + cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) + that can be used to supply pre-extracted NetCDF variables to the + computational routines. It is primarily used for internal + purposes, but can also be used to improve performance by + eliminating the need to repeatedly extract the same variables + used in multiple diagnostics calculations, particularly when using + large sequences of files. + Default is None. + + meta (:obj:`bool`, optional): Set to False to disable metadata and + return :class:`numpy.ndarray` instead of + :class:`xarray.DataArray`. Default is True. + + _key (:obj:`int`, optional): A caching key. This is used for internal + purposes only. Default is None. + + vert_type (:obj:`str`, optional): The type of vertical coordinate used + to determine cloud type thresholds. Must be 'height_agl', + 'height_msl', or 'pres'. The default is 'height_agl'. + + low_thresh (:obj:`float`, optional): The lower bound for what is + considered a low cloud. If *vert_type* is 'pres', the default is + 97000 Pa. If *vert_type* is 'height_agl' or 'height_msl', then the + default is 300 m. + + mid_thresh (:obj:`float`, optional): The lower bound for what is + considered a mid level cloud. If *vert_type* is 'pres', the + default is 80000 Pa. If *vert_type* is 'height_agl' or + 'height_msl', then the default is 2000 m. + + high_thresh (:obj:`float`, optional): The lower bound for what is + considered a high level cloud. If *vert_type* is 'pres', the + default is 45000 Pa. If *vert_type* is 'height_agl' or + 'height_msl', then the default is 6000 m. + + Returns: + :class:`xarray.DataArray` or :class:`numpy.ndarray`: The + cloud fraction array whose leftmost dimension is 3 (LOW=0, MID=1, + HIGH=2). + If xarray is enabled and the *meta* parameter is True, then the result + will be a :class:`xarray.DataArray` object. Otherwise, the result will + be a :class:`numpy.ndarray` object with no metadata. + + """ + + ncvars = extract_vars(wrfin, timeidx, ("P", "PB", "QVAPOR", "T"), + method, squeeze, cache, meta=False, + _key=_key) + + p = ncvars["P"] + pb = ncvars["PB"] + qv = ncvars["QVAPOR"] + t = ncvars["T"] + + full_p = p + pb + full_t = t + Constants.T_BASE + + tk = _tk(full_p, full_t) + rh = _rh(qv, full_p, tk) + + if vert_type.lower() == "pres" or vert_type.lower() == "pressure": + v_coord = full_p + _low_thresh = 97000. if low_thresh is None else low_thresh + _mid_thresh = 80000. if mid_thresh is None else mid_thresh + _high_thresh = 45000. if high_thresh is None else high_thresh + vert_inc_w_height = 0 + elif (vert_type.lower() == "height_msl" + or vert_type.lower() == "height_agl"): + is_msl = vert_type.lower() == "height_msl" + v_coord = _get_geoht(wrfin, timeidx, method, squeeze, + cache, meta=False, _key=_key, height=True, + msl=is_msl) + _low_thresh = 300. if low_thresh is None else low_thresh + _mid_thresh = 2000. if mid_thresh is None else mid_thresh + _high_thresh = 6000. if high_thresh is None else high_thresh + vert_inc_w_height = 1 + else: + raise ValueError("'vert_type' must be 'pres', 'height_msl', " + "or 'height_agl'") + + cfrac = _cloudfrac(v_coord, rh, vert_inc_w_height, + _low_thresh, _mid_thresh, _high_thresh, missing) + + return ma.masked_values(cfrac, missing) + diff --git a/src/wrf/ctt.py b/src/wrf/g_ctt.py similarity index 100% rename from src/wrf/ctt.py rename to src/wrf/g_ctt.py diff --git a/src/wrf/dbz.py b/src/wrf/g_dbz.py similarity index 100% rename from src/wrf/dbz.py rename to src/wrf/g_dbz.py diff --git a/src/wrf/dewpoint.py b/src/wrf/g_dewpoint.py similarity index 100% rename from src/wrf/dewpoint.py rename to src/wrf/g_dewpoint.py diff --git a/src/wrf/geoht.py b/src/wrf/g_geoht.py similarity index 61% rename from src/wrf/geoht.py rename to src/wrf/g_geoht.py index 98d9ceb..d9c7624 100755 --- a/src/wrf/geoht.py +++ b/src/wrf/g_geoht.py @@ -1,6 +1,8 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) +import warnings + from .constants import Constants from .destag import destagger from .decorators import convert_units @@ -9,7 +11,7 @@ from .util import extract_vars, either def _get_geoht(wrfin, timeidx, method="cat", squeeze=True, cache=None, meta=True, _key=None, - height=True, msl=True): + height=True, msl=True, stag=False): """Return the geopotential or geopotential height. If *height* is False, then geopotential is returned in units of @@ -67,6 +69,9 @@ def _get_geoht(wrfin, timeidx, method="cat", squeeze=True, as Mean Sea Level (MSL). Set to False to return the geopotential height as Above Ground Level (AGL) by subtracting the terrain height. Default is True. + + stag (:obj:`bool`, optional): Set to True to use the vertical + staggered grid, rather than the mass grid. Default is False. Returns: :class:`xarray.DataArray` or :class:`numpy.ndarray`: The @@ -86,14 +91,21 @@ def _get_geoht(wrfin, timeidx, method="cat", squeeze=True, phb = ph_vars["PHB"] hgt = ph_vars["HGT"] geopt = ph + phb - geopt_unstag = destagger(geopt, -3) + if not stag: + geopt_unstag = destagger(geopt, -3) + else: + geopt_unstag = geopt else: ght_vars = extract_vars(wrfin, timeidx, ("GHT", "HGT_M"), method, squeeze, cache, meta=False, _key=_key) geopt_unstag = ght_vars["GHT"] * Constants.G hgt = ght_vars["HGT_M"] - + + if stag: + warnings.warn("file contains no vertically staggered geopotential " + "height variable, returning unstaggered result " + "instead" ) if height: if msl: return geopt_unstag / Constants.G @@ -110,7 +122,7 @@ def _get_geoht(wrfin, timeidx, method="cat", squeeze=True, return geopt_unstag -@set_height_metadata(geopt=True) +@set_height_metadata(geopt=True, stag=False) def get_geopt(wrfin, timeidx=0, method="cat", squeeze=True, cache=None, meta=True, _key=None): """Return the geopotential. @@ -171,7 +183,7 @@ def get_geopt(wrfin, timeidx=0, method="cat", squeeze=True, cache=None, False, True) -@set_height_metadata(geopt=False) +@set_height_metadata(geopt=False, stag=False) @convert_units("height", "m") def get_height(wrfin, timeidx=0, method="cat", squeeze=True, cache=None, meta=True, _key=None, @@ -245,3 +257,139 @@ def get_height(wrfin, timeidx=0, method="cat", squeeze=True, return _get_geoht(wrfin, timeidx, method, squeeze, cache, meta, _key, True, msl) + +@set_height_metadata(geopt=True, stag=True) +def get_stag_geopt(wrfin, timeidx=0, method="cat", squeeze=True, cache=None, + meta=True, _key=None): + """Return the geopotential for the vertically staggered grid. + + The geopotential is returned in units of [m2 s-2]. + + This functions extracts the necessary variables from the NetCDF file + object in order to perform the calculation. + + Args: + + wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`, or an \ + iterable): WRF-ARW NetCDF + data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` + or an iterable sequence of the aforementioned types. + + timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The + desired time index. This value can be a positive integer, + negative integer, or + :data:`wrf.ALL_TIMES` (an alias for None) to return + all times in the file or sequence. The default is 0. + + method (:obj:`str`, optional): The aggregation method to use for + sequences. Must be either 'cat' or 'join'. + 'cat' combines the data along the Time dimension. + 'join' creates a new dimension for the file index. + The default is 'cat'. + + squeeze (:obj:`bool`, optional): Set to False to prevent dimensions + with a size of 1 from being automatically removed from the shape + of the output. Default is True. + + cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) + that can be used to supply pre-extracted NetCDF variables to the + computational routines. It is primarily used for internal + purposes, but can also be used to improve performance by + eliminating the need to repeatedly extract the same variables + used in multiple diagnostics calculations, particularly when using + large sequences of files. + Default is None. + + meta (:obj:`bool`, optional): Set to False to disable metadata and + return :class:`numpy.ndarray` instead of + :class:`xarray.DataArray`. Default is True. + + _key (:obj:`int`, optional): A caching key. This is used for internal + purposes only. Default is None. + + Returns: + :class:`xarray.DataArray` or :class:`numpy.ndarray`: The + geopotential. + If xarray is enabled and the *meta* parameter is True, then the result + will be a :class:`xarray.DataArray` object. Otherwise, the result will + be a :class:`numpy.ndarray` object with no metadata. + + """ + return _get_geoht(wrfin, timeidx, method, squeeze, cache, meta, _key, + False, True, stag=True) + + +@set_height_metadata(geopt=False, stag=True) +@convert_units("height", "m") +def get_stag_height(wrfin, timeidx=0, method="cat", squeeze=True, + cache=None, meta=True, _key=None, + msl=True, units="m"): + """Return the geopotential height for the vertically staggered grid. + + If *msl* is True, then geopotential height is returned as Mean Sea Level + (MSL). If *msl* is False, then geopotential height is returned as + Above Ground Level (AGL) by subtracting the terrain height. + + This functions extracts the necessary variables from the NetCDF file + object in order to perform the calculation. + + Args: + + wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`, or an \ + iterable): WRF-ARW NetCDF + data as a :class:`netCDF4.Dataset`, :class:`Nio.NioFile` + or an iterable sequence of the aforementioned types. + + timeidx (:obj:`int` or :data:`wrf.ALL_TIMES`, optional): The + desired time index. This value can be a positive integer, + negative integer, or + :data:`wrf.ALL_TIMES` (an alias for None) to return + all times in the file or sequence. The default is 0. + + method (:obj:`str`, optional): The aggregation method to use for + sequences. Must be either 'cat' or 'join'. + 'cat' combines the data along the Time dimension. + 'join' creates a new dimension for the file index. + The default is 'cat'. + + squeeze (:obj:`bool`, optional): Set to False to prevent dimensions + with a size of 1 from being automatically removed from the shape + of the output. Default is True. + + cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) + that can be used to supply pre-extracted NetCDF variables to the + computational routines. It is primarily used for internal + purposes, but can also be used to improve performance by + eliminating the need to repeatedly extract the same variables + used in multiple diagnostics calculations, particularly when using + large sequences of files. + Default is None. + + meta (:obj:`bool`, optional): Set to False to disable metadata and + return :class:`numpy.ndarray` instead of + :class:`xarray.DataArray`. Default is True. + + _key (:obj:`int`, optional): A caching key. This is used for internal + purposes only. Default is None. + + msl (:obj:`bool`, optional): Set to True to return geopotential height + as Mean Sea Level (MSL). Set to False to return the + geopotential height as Above Ground Level (AGL) by subtracting + the terrain height. Default is True. + + units (:obj:`str`): The desired units. Refer to the :meth:`getvar` + product table for a list of available units for 'z'. Default + is 'm'. + + Returns: + :class:`xarray.DataArray` or :class:`numpy.ndarray`: The + geopotential height. + If xarray is enabled and the *meta* parameter is True, then the result + will be a :class:`xarray.DataArray` object. Otherwise, the result will + be a :class:`numpy.ndarray` object with no metadata. + + """ + + return _get_geoht(wrfin, timeidx, method, squeeze, cache, meta, _key, + True, msl, stag=True) + \ No newline at end of file diff --git a/src/wrf/helicity.py b/src/wrf/g_helicity.py similarity index 100% rename from src/wrf/helicity.py rename to src/wrf/g_helicity.py diff --git a/src/wrf/latlon.py b/src/wrf/g_latlon.py similarity index 100% rename from src/wrf/latlon.py rename to src/wrf/g_latlon.py diff --git a/src/wrf/omega.py b/src/wrf/g_omega.py similarity index 100% rename from src/wrf/omega.py rename to src/wrf/g_omega.py diff --git a/src/wrf/precip.py b/src/wrf/g_precip.py similarity index 100% rename from src/wrf/precip.py rename to src/wrf/g_precip.py diff --git a/src/wrf/pressure.py b/src/wrf/g_pressure.py similarity index 100% rename from src/wrf/pressure.py rename to src/wrf/g_pressure.py diff --git a/src/wrf/pw.py b/src/wrf/g_pw.py similarity index 100% rename from src/wrf/pw.py rename to src/wrf/g_pw.py diff --git a/src/wrf/rh.py b/src/wrf/g_rh.py similarity index 100% rename from src/wrf/rh.py rename to src/wrf/g_rh.py diff --git a/src/wrf/slp.py b/src/wrf/g_slp.py similarity index 100% rename from src/wrf/slp.py rename to src/wrf/g_slp.py diff --git a/src/wrf/temp.py b/src/wrf/g_temp.py similarity index 100% rename from src/wrf/temp.py rename to src/wrf/g_temp.py diff --git a/src/wrf/terrain.py b/src/wrf/g_terrain.py similarity index 100% rename from src/wrf/terrain.py rename to src/wrf/g_terrain.py diff --git a/src/wrf/times.py b/src/wrf/g_times.py similarity index 100% rename from src/wrf/times.py rename to src/wrf/g_times.py diff --git a/src/wrf/uvmet.py b/src/wrf/g_uvmet.py similarity index 99% rename from src/wrf/uvmet.py rename to src/wrf/g_uvmet.py index 6dba5e6..5ef19a3 100755 --- a/src/wrf/uvmet.py +++ b/src/wrf/g_uvmet.py @@ -9,7 +9,7 @@ import numpy as np from .extension import _uvmet from .destag import destagger from .constants import Constants -from .wind import _calc_wspd_wdir +from .g_wind import _calc_wspd_wdir from .decorators import convert_units from .metadecorators import set_wind_metadata from .util import extract_vars, extract_global_attrs, either diff --git a/src/wrf/vorticity.py b/src/wrf/g_vorticity.py similarity index 100% rename from src/wrf/vorticity.py rename to src/wrf/g_vorticity.py diff --git a/src/wrf/wind.py b/src/wrf/g_wind.py similarity index 100% rename from src/wrf/wind.py rename to src/wrf/g_wind.py diff --git a/src/wrf/interp.py b/src/wrf/interp.py index a2e67aa..1892947 100755 --- a/src/wrf/interp.py +++ b/src/wrf/interp.py @@ -11,16 +11,16 @@ from .metadecorators import set_interp_metadata from .util import extract_vars, is_staggered, get_id, to_np from .py3compat import py3range from .interputils import get_xy, get_xy_z_params, to_xy_coords -from .constants import Constants, ConversionFactors -from .terrain import get_terrain -from .geoht import get_height -from .temp import get_theta, get_temp, get_eth -from .pressure import get_pressure +from .constants import Constants, default_fill, ConversionFactors +from wrf.g_terrain import get_terrain +from wrf.g_geoht import get_height +from wrf.g_temp import get_theta, get_temp, get_eth +from wrf.g_pressure import get_pressure # Note: Extension decorator is good enough to handle left dims @set_interp_metadata("horiz") -def interplevel(field3d, vert, desiredlev, missing=Constants.DEFAULT_FILL, +def interplevel(field3d, vert, desiredlev, missing=default_fill(np.float64), meta=True): """Return the three-dimensional field interpolated to a horizontal plane at the specified vertical level. @@ -40,7 +40,7 @@ def interplevel(field3d, vert, desiredlev, missing=Constants.DEFAULT_FILL, Must be in the same units as the *vert* parameter. missing (:obj:`float`): The fill value to use for the output. - Default is :data:`wrf.Constants.DEFAULT_FILL`. + Default is :data:`wrf.default_fill(numpy.float64)`. meta (:obj:`bool`): Set to False to disable metadata and return :class:`numpy.ndarray` instead of @@ -90,8 +90,9 @@ def interplevel(field3d, vert, desiredlev, missing=Constants.DEFAULT_FILL, @set_interp_metadata("cross") -def vertcross(field3d, vert, levels=None, missing=Constants.DEFAULT_FILL, - wrfin=None, timeidx=0, stagger=None, projection=None, +def vertcross(field3d, vert, levels=None, missing=default_fill(np.float64), + wrfin=None, timeidx=0, stagger=None, projection=None, + ll_point=None, pivot_point=None, angle=None, start_point=None, end_point=None, latlon=False, cache=None, meta=True): @@ -133,7 +134,7 @@ def vertcross(field3d, vert, levels=None, missing=Constants.DEFAULT_FILL, Default is None. missing (:obj:`float`): The fill value to use for the output. - Default is :data:`wrf.Constants.DEFAULT_FILL`. + Default is :data:`wrf.default_fill(numpy.float64)`. wrfin (:class:`netCDF4.Dataset`, :class:`Nio.NioFile`, or an \ iterable, optional): WRF-ARW NetCDF @@ -165,6 +166,12 @@ def vertcross(field3d, vert, levels=None, missing=Constants.DEFAULT_FILL, coordinates, and must be specified if *wrfin* is None. Default is None. + ll_point (:class:`wrf.CoordPair`, sequence of :class:`wrf.CoordPair`, \ + optional): The lower left latitude, longitude point for your domain, + and must be specified + if *wrfin* is None. If the domain is a moving nest, this should be + a sequence of :class:`wrf.CoordPair`. Default is None. + pivot_point (:class:`wrf.CoordPair`, optional): A coordinate pair for the pivot point, which indicates the location through which the plane will pass. Must also specify *angle*. The coordinate @@ -197,7 +204,16 @@ def vertcross(field3d, vert, levels=None, missing=Constants.DEFAULT_FILL, latlon (:obj:`bool`, optional): Set to True to also interpolate the two-dimensional latitude and longitude coordinates along the same horizontal line and include this information in the metadata - (if enabled). This can be helpful for plotting. Default is False. + (if enabled). This can be helpful for plotting. Default is False. + + Note: + + Currently, *field3d* must be of type :class:`xarray.DataArray` + and contain coordinate information in order to generate the + latitude and longitude coordinates along the line if + *latlon* is set to True. Otherwise, a warning will be issued, + and the latitude and longitude information will not be + present. cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) that can be used to supply pre-extracted NetCDF variables to the @@ -242,7 +258,7 @@ def vertcross(field3d, vert, levels=None, missing=Constants.DEFAULT_FILL, if pivot_point is not None: if pivot_point.lat is not None and pivot_point.lon is not None: xy_coords = to_xy_coords(pivot_point, wrfin, timeidx, - stagger, projection) + stagger, projection, ll_point) pivot_point_xy = (xy_coords.x, xy_coords.y) else: pivot_point_xy = (pivot_point.x, pivot_point.y) @@ -250,14 +266,14 @@ def vertcross(field3d, vert, levels=None, missing=Constants.DEFAULT_FILL, if start_point is not None and end_point is not None: if start_point.lat is not None and start_point.lon is not None: xy_coords = to_xy_coords(start_point, wrfin, timeidx, - stagger, projection) + stagger, projection, ll_point) start_point_xy = (xy_coords.x, xy_coords.y) else: start_point_xy = (start_point.x, start_point.y) if end_point.lat is not None and end_point.lon is not None: xy_coords = to_xy_coords(end_point, wrfin, timeidx, - stagger, projection) + stagger, projection, ll_point) end_point_xy = (xy_coords.x, xy_coords.y) else: end_point_xy = (end_point.x, end_point.y) @@ -282,6 +298,7 @@ def vertcross(field3d, vert, levels=None, missing=Constants.DEFAULT_FILL, @set_interp_metadata("line") def interpline(field2d, pivot_point=None, wrfin=None, timeidx=0, stagger=None, projection=None, + ll_point=None, angle=None, start_point=None, end_point=None, latlon=False, cache=None, meta=True): @@ -331,6 +348,12 @@ def interpline(field2d, pivot_point=None, not be used when working with x,y coordinates. Default is None. + ll_point (:class:`wrf.CoordPair`, sequence of :class:`wrf.CoordPair`, \ + optional): The lower left latitude, longitude point for your domain, + and must be specified + if *wrfin* is None. If the domain is a moving nest, this should be + a sequence of :class:`wrf.CoordPair`. Default is None. + pivot_point (:class:`wrf.CoordPair`, optional): A coordinate pair for the pivot point, which indicates the location through which the plane will pass. Must also specify *angle*. The coordinate @@ -365,6 +388,15 @@ def interpline(field2d, pivot_point=None, horizontal line and include this information in the metadata (if enabled). This can be helpful for plotting. Default is False. + Note: + + Currently, *field2d* must be of type :class:`xarray.DataArray` + and contain coordinate information in order to generate the + latitude and longitude coordinates along the line if + *latlon* is set to True. Otherwise, a warning will be issued, + and the latitude and longitude information will not be + present. + cache (:obj:`dict`, optional): A dictionary of (varname, ndarray) that can be used to supply pre-extracted NetCDF variables to the computational routines. It is primarily used for internal @@ -402,7 +434,7 @@ def interpline(field2d, pivot_point=None, if pivot_point is not None: if pivot_point.lat is not None and pivot_point.lon is not None: xy_coords = to_xy_coords(pivot_point, wrfin, timeidx, - stagger, projection) + stagger, projection, ll_point) pivot_point_xy = (xy_coords.x, xy_coords.y) else: pivot_point_xy = (pivot_point.x, pivot_point.y) @@ -410,14 +442,14 @@ def interpline(field2d, pivot_point=None, if start_point is not None and end_point is not None: if start_point.lat is not None and start_point.lon is not None: xy_coords = to_xy_coords(start_point, wrfin, timeidx, - stagger, projection) + stagger, projection, ll_point) start_point_xy = (xy_coords.x, xy_coords.y) else: start_point_xy = (start_point.x, start_point.y) if end_point.lat is not None and end_point.lon is not None: xy_coords = to_xy_coords(end_point, wrfin, timeidx, - stagger, projection) + stagger, projection, ll_point) end_point_xy = (xy_coords.x, xy_coords.y) else: end_point_xy = (end_point.x, end_point.y) @@ -584,9 +616,6 @@ def vinterp(wrfin, field, vert_coord, interp_levels, extrapolate=False, terht = get_terrain(wrfin, timeidx, units="m", method=method, squeeze=squeeze, cache=cache, meta=False, _key=_key) - t = get_theta(wrfin, timeidx, units="k", - method=method, squeeze=squeeze, cache=cache, - meta=False, _key=_key) tk = get_temp(wrfin, timeidx, units="k", method=method, squeeze=squeeze, cache=cache, meta=False, _key=_key) @@ -596,9 +625,6 @@ def vinterp(wrfin, field, vert_coord, interp_levels, extrapolate=False, ght = get_height(wrfin, timeidx, msl=True, units="m", method=method, squeeze=squeeze, cache=cache, meta=False, _key=_key) - ht_agl = get_height(wrfin, timeidx, msl=False, units="m", - method=method, squeeze=squeeze, cache=cache, - meta=False, _key=_key) smsfp = _smooth2d(sfp, 3) @@ -613,10 +639,18 @@ def vinterp(wrfin, field, vert_coord, interp_levels, extrapolate=False, vcord_array = np.exp(-ght/sclht) elif vert_coord == "ght_agl": + ht_agl = get_height(wrfin, timeidx, msl=False, units="m", + method=method, squeeze=squeeze, cache=cache, + meta=False, _key=_key) + vcor = 3 vcord_array = np.exp(-ht_agl/sclht) elif vert_coord in ("theta", "th"): + t = get_theta(wrfin, timeidx, units="k", + method=method, squeeze=squeeze, cache=cache, + meta=False, _key=_key) + vcor = 4 idir = 1 icorsw = 0 @@ -651,7 +685,7 @@ def vinterp(wrfin, field, vert_coord, interp_levels, extrapolate=False, if isinstance(field, ma.MaskedArray): missing = field.fill_value else: - missing = Constants.DEFAULT_FILL + missing = default_fill(np.float64) if (field.shape != p.shape): raise ValueError("'field' shape does not match other variable shapes. " diff --git a/src/wrf/interputils.py b/src/wrf/interputils.py index 114df41..cd4ddc1 100644 --- a/src/wrf/interputils.py +++ b/src/wrf/interputils.py @@ -2,7 +2,6 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) from math import floor, ceil -from collections import Iterable import numpy as np @@ -11,6 +10,7 @@ from .py3compat import py3range from .coordpair import CoordPair from .constants import Constants, ProjectionTypes from .latlonutils import _ll_to_xy +from .util import pairs_to_latlon def to_positive_idxs(shape, coord): @@ -158,23 +158,24 @@ def _calc_xy(xdim, ydim, pivot_point=None, angle=None, raise ValueError("end_point {} is outside of domain " "with shape {}".format(end_point, (xdim, ydim))) + # From the original NCL code, but the error above will be thrown + # instead. if ( x1 > xdim-1 ): - x1 = xdim + x1 = xdim - 1 if ( y1 > ydim-1): - y1 = ydim + y1 = ydim - 1 else: raise ValueError("invalid start/end or pivot/angle arguments") dx = x1 - x0 dy = y1 - y0 distance = (dx*dx + dy*dy)**0.5 - npts = int(distance) - dxy = distance/npts + npts = int(distance) + 1 xy = np.zeros((npts,2), "float") - dx = dx/npts - dy = dy/npts + dx = dx/(npts-1) + dy = dy/(npts-1) for i in py3range(npts): xy[i,0] = x0 + i*dx @@ -328,7 +329,8 @@ def get_xy(var, pivot_point=None, angle=None, return xy -def to_xy_coords(pairs, wrfin=None, timeidx=0, stagger=None, projection=None): +def to_xy_coords(pairs, wrfin=None, timeidx=0, stagger=None, projection=None, + ll_point=None): """Return the coordinate pairs in grid space. This function converts latitude,longitude coordinate pairs to @@ -370,6 +372,12 @@ def to_xy_coords(pairs, wrfin=None, timeidx=0, stagger=None, projection=None): projection object to use when working with latitude, longitude coordinates, and must be specified if *wrfin* is None. Default is None. + + ll_point (:class:`wrf.CoordPair`, sequence of :class:`wrf.CoordPair`, \ + optional): The lower left latitude, longitude point for your domain, + and must be specified + if *wrfin* is None. If the domain is a moving nest, this should be + a sequence of :class:`wrf.CoordPair`. Default is None. Returns: @@ -378,16 +386,13 @@ def to_xy_coords(pairs, wrfin=None, timeidx=0, stagger=None, projection=None): """ - if wrfin is None and projection is None: - raise ValueError ("'wrfin' or 'projection' parameter is required") + if (wrfin is None and (projection is None or ll_point is None)): + raise ValueError ("'wrfin' parameter or " + "'projection' and 'll_point' parameters " + "are required") + + lat, lon = pairs_to_latlon(pairs) - if isinstance(pairs, Iterable): - lat = [pair.lat for pair in pairs] - lon = [pair.lon for pair in pairs] - else: - lat = pairs.lat - lon = pairs.lon - if wrfin is not None: xy_vals = _ll_to_xy(lat, lon, wrfin=wrfin, timeidx=timeidx, squeeze=True, meta=False, stagger=stagger, as_int=True) @@ -407,15 +412,16 @@ def to_xy_coords(pairs, wrfin=None, timeidx=0, stagger=None, projection=None): pole_lon = 0.0 latinc = 0.0 loninc = 0.0 - + + ll_lat, ll_lon = pairs_to_latlon(ll_point) xy_vals = _ll_to_xy(lat, lon, meta=False, squeeze=True, as_int=True, map_proj=projection.map_proj, truelat1=projection.truelat1, truelat2=projection.truelat2, stand_lon=projection.stand_lon, - ref_lat=projection.ll_lat, - ref_lon=projection.ll_lon, + ref_lat=ll_lat, + ref_lon=ll_lon, pole_lat=pole_lat, pole_lon=pole_lon, known_x=0, diff --git a/src/wrf/latlonutils.py b/src/wrf/latlonutils.py index b050ae1..ccd5221 100644 --- a/src/wrf/latlonutils.py +++ b/src/wrf/latlonutils.py @@ -242,9 +242,9 @@ def _kwarg_proj_params(**projparams): if var is None: raise ValueError("'{}' argument required".format(name)) - # ref_lat and ref_lon are expectd to be lists - ref_lat = np.asarray([ref_lat]) - ref_lon = np.asarray([ref_lon]) + # ref_lat and ref_lon are expected to be lists + ref_lat = np.ravel(np.asarray([ref_lat])) + ref_lon = np.ravel(np.asarray([ref_lon])) # Fortran wants 1-based indexing known_x = known_x + 1 diff --git a/src/wrf/metadecorators.py b/src/wrf/metadecorators.py index e34b71d..27425c1 100644 --- a/src/wrf/metadecorators.py +++ b/src/wrf/metadecorators.py @@ -1,5 +1,6 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) +import warnings import wrapt from collections import OrderedDict @@ -322,6 +323,7 @@ def set_wind_metadata(copy_varname, name, description, return func_wrapper + def set_cape_metadata(is2d): """A decorator that sets the metadata for a wrapped CAPE function's output. @@ -468,14 +470,22 @@ def set_cloudfrac_metadata(): return wrapped(*args, **kwargs) argvars = from_args(wrapped, ("wrfin", "timeidx", "method", "squeeze", - "cache", "_key"), - *args, **kwargs) + "cache", "_key", "vert_type", + "low_thresh", "mid_thresh", + "high_thresh", "missing"), + *args, **kwargs) wrfin = argvars["wrfin"] timeidx = argvars["timeidx"] method = argvars["method"] squeeze = argvars["squeeze"] cache = argvars["cache"] _key = argvars["_key"] + vert_type = argvars["vert_type"] + low_thresh = argvars["low_thresh"] + mid_thresh = argvars["mid_thresh"] + high_thresh = argvars["high_thresh"] + missing = argvars["missing"] + if cache is None: cache = {} @@ -499,6 +509,20 @@ def set_cloudfrac_metadata(): outattrs.update(copy_var.attrs) outdimnames = [None] * result.ndim + # For printing units + unitstr = ("Pa" if vert_type.lower() == "pres" + or vert_type.lower() == "pressure" else "m") + + # For setting the threholds in metdata + if vert_type.lower() == "pres" or vert_type.lower() == "pressure": + _low_thresh = 97000. if low_thresh is None else low_thresh + _mid_thresh = 80000. if mid_thresh is None else mid_thresh + _high_thresh = 45000. if high_thresh is None else high_thresh + elif vert_type.lower() == "height_msl" or "height_agl": + _low_thresh = 300. if low_thresh is None else low_thresh + _mid_thresh = 2000. if mid_thresh is None else mid_thresh + _high_thresh = 6000. if high_thresh is None else high_thresh + # Right dims outdimnames[-2:] = copy_var.dims[-2:] # Left dims @@ -507,6 +531,11 @@ def set_cloudfrac_metadata(): outattrs["description"] = "low, mid, high clouds" outattrs["MemoryOrder"] = "XY" outattrs["units"] = "%" + outattrs["low_thresh"] = "{} {}".format(_low_thresh, unitstr) + outattrs["mid_thresh"] = "{} {}".format(_mid_thresh, unitstr) + outattrs["high_thresh"] = "{} {}".format(_high_thresh, unitstr) + outattrs["_FillValue"] = missing + outattrs["missing_value"] = missing outname = "cloudfrac" # xarray doesn't line up coordinate dimensions based on @@ -529,6 +558,7 @@ def set_cloudfrac_metadata(): return func_wrapper + def set_latlon_metadata(xy=False): """A decorator that sets the metadata for a wrapped latlon function's output. @@ -613,8 +643,9 @@ def set_latlon_metadata(xy=False): return da return func_wrapper - -def set_height_metadata(geopt=False): + + +def set_height_metadata(geopt=False, stag=False): """A decorator that sets the metadata for a wrapped height function's output. @@ -629,6 +660,9 @@ def set_height_metadata(geopt=False): returns geopotential. Set to True if the wrapped function returns geopotential height. Default is False. + stag (:obj:`bool`, optional): Set to True to use the vertical + staggered grid, rather than the mass grid. Default is False. + Returns: :class:`xarray.DataArray` or :class:`numpy.ndarray`: The wrapped @@ -664,9 +698,17 @@ def set_height_metadata(geopt=False): if cache is None: cache = {} + is_met_em = False # For height, either copy the met_em GHT variable or copy and modify # pressure (which has the same dims as destaggered height) - ht_metadata_varname = either("P", "GHT")(wrfin) + if not stag: + ht_metadata_varname = either("P", "GHT")(wrfin) + else: + ht_metadata_varname = either("PH", "GHT")(wrfin) + + if ht_metadata_varname == "GHT": + is_met_em = True + ht_var = extract_vars(wrfin, timeidx, ht_metadata_varname, method, squeeze, cache, meta=True, _key=_key) @@ -692,18 +734,28 @@ def set_height_metadata(geopt=False): if geopt: outname = "geopt" outattrs["units"] = "m2 s-2" - outattrs["description"] = "full model geopotential" + if not stag or is_met_em: + outattrs["description"] = "geopotential (mass grid)" + else: + outattrs["description"] = ("geopotential (vertically " + "staggered grid)") else: outname = "height" if msl else "height_agl" outattrs["units"] = units height_type = "MSL" if msl else "AGL" - outattrs["description"] = "model height ({})".format(height_type) + if not stag or is_met_em: + outattrs["description"] = ("model height - [{}] " + "(mass grid)".format(height_type)) + else: + outattrs["description"] = ("model height - [{}] (vertically " + "staggered grid)".format(height_type)) return DataArray(result, name=outname, dims=outdimnames, coords=outcoords, attrs=outattrs) return func_wrapper + def _set_horiz_meta(wrapped, instance, args, kwargs): """A decorator implementation that sets the metadata for a wrapped horizontal interpolation function. @@ -753,7 +805,7 @@ def _set_horiz_meta(wrapped, instance, args, kwargs): outname = None outdimnames = None outcoords = None - outattrs = None + outattrs = OrderedDict() # Get the vertical level units vert_units = None @@ -771,7 +823,6 @@ def _set_horiz_meta(wrapped, instance, args, kwargs): if isinstance(field3d, DataArray): outcoords = OrderedDict() - outattrs = OrderedDict() outdimnames = list(field3d.dims) outcoords.update(field3d.coords) outdimnames.remove(field3d.dims[-3]) @@ -784,7 +835,6 @@ def _set_horiz_meta(wrapped, instance, args, kwargs): else: outname = "field3d_{0}".format(name_levelstr) - outattrs = OrderedDict() outattrs["level"] = levelstr outattrs["missing_value"] = missingval @@ -798,7 +848,8 @@ def _set_horiz_meta(wrapped, instance, args, kwargs): return DataArray(result, name=outname, dims=outdimnames, coords=outcoords, attrs=outattrs) - + + def _set_cross_meta(wrapped, instance, args, kwargs): """A decorator implementation that sets the metadata for a wrapped cross \ section interpolation function. @@ -836,7 +887,7 @@ def _set_cross_meta(wrapped, instance, args, kwargs): argvars = from_args(wrapped, ("field3d", "vert", "levels", "latlon", "missing", "wrfin", "timeidx", "stagger", "projection", - "pivot_point", "angle", + "ll_point", "pivot_point", "angle", "start_point", "end_point", "cache"), *args, **kwargs) @@ -850,6 +901,7 @@ def _set_cross_meta(wrapped, instance, args, kwargs): timeidx = argvars["timeidx"] stagger = argvars["stagger"] projection = argvars["projection"] + ll_point = argvars["ll_point"] pivot_point = argvars["pivot_point"] angle = argvars["angle"] start_point = argvars["start_point"] @@ -863,7 +915,7 @@ def _set_cross_meta(wrapped, instance, args, kwargs): if pivot_point is not None: if pivot_point.lat is not None and pivot_point.lon is not None: xy_coords = to_xy_coords(pivot_point, wrfin, timeidx, - stagger, projection) + stagger, projection, ll_point) pivot_point_xy = (xy_coords.x, xy_coords.y) else: pivot_point_xy = (pivot_point.x, pivot_point.y) @@ -871,14 +923,14 @@ def _set_cross_meta(wrapped, instance, args, kwargs): if start_point is not None and end_point is not None: if start_point.lat is not None and start_point.lon is not None: xy_coords = to_xy_coords(start_point, wrfin, timeidx, - stagger, projection) + stagger, projection, ll_point) start_point_xy = (xy_coords.x, xy_coords.y) else: start_point_xy = (start_point.x, start_point.y) if end_point.lat is not None and end_point.lon is not None: xy_coords = to_xy_coords(end_point, wrfin, timeidx, - stagger, projection) + stagger, projection, ll_point) end_point_xy = (xy_coords.x, xy_coords.y) else: end_point_xy = (end_point.x, end_point.y) @@ -906,7 +958,7 @@ def _set_cross_meta(wrapped, instance, args, kwargs): outname = None outdimnames = None outcoords = None - outattrs = None + outattrs = OrderedDict() # Use XY to set the cross-section metadata st_x = xy[0,0] @@ -921,7 +973,6 @@ def _set_cross_meta(wrapped, instance, args, kwargs): if isinstance(field3d, DataArray): outcoords = OrderedDict() - outattrs = OrderedDict() outdimnames = list(field3d.dims) outcoords.update(field3d.coords) for i in py3range(-3,0,1): @@ -990,6 +1041,8 @@ def _set_cross_meta(wrapped, instance, args, kwargs): outcoords["xy_loc"] = (loc_dimnames, latlon_loc) else: + warnings.warn("'latlon' is set to True, but 'field3d' " + " contains no coordinate information") outcoords["xy_loc"] = ("cross_line_idx", np.asarray(tuple( CoordPair(xy[i,0], xy[i,1]) for i in py3range(xy.shape[-2])))) @@ -1002,8 +1055,11 @@ def _set_cross_meta(wrapped, instance, args, kwargs): outcoords["vertical"] = z_var2d[:] else: + if inc_latlon: + warnings.warn("'latlon' is set to True, but 'field3d' is " + "not of type xarray.DataArray and contains no " + "coordinate information") outname = "field3d_cross" - outattrs = OrderedDict() outattrs["orientation"] = cross_str outattrs["missing_value"] = missingval @@ -1012,7 +1068,6 @@ def _set_cross_meta(wrapped, instance, args, kwargs): return DataArray(result, name=outname, dims=outdimnames, coords=outcoords, attrs=outattrs) - def _set_line_meta(wrapped, instance, args, kwargs): """A decorator implementation that sets the metadata for a wrapped line @@ -1050,7 +1105,7 @@ def _set_line_meta(wrapped, instance, args, kwargs): """ argvars = from_args(wrapped, ("field2d", "wrfin", "timeidx", "stagger", "projection", - "pivot_point", "angle", + "ll_point", "pivot_point", "angle", "start_point", "end_point", "latlon", "cache"), *args, **kwargs) @@ -1060,6 +1115,7 @@ def _set_line_meta(wrapped, instance, args, kwargs): timeidx = argvars["timeidx"] stagger = argvars["stagger"] projection = argvars["projection"] + ll_point = argvars["ll_point"] pivot_point = argvars["pivot_point"] angle = argvars["angle"] start_point = argvars["start_point"] @@ -1077,7 +1133,7 @@ def _set_line_meta(wrapped, instance, args, kwargs): if pivot_point is not None: if pivot_point.lat is not None and pivot_point.lon is not None: xy_coords = to_xy_coords(pivot_point, wrfin, timeidx, - stagger, projection) + stagger, projection, ll_point) pivot_point_xy = (xy_coords.x, xy_coords.y) else: pivot_point_xy = (pivot_point.x, pivot_point.y) @@ -1086,14 +1142,14 @@ def _set_line_meta(wrapped, instance, args, kwargs): if start_point is not None and end_point is not None: if start_point.lat is not None and start_point.lon is not None: xy_coords = to_xy_coords(start_point, wrfin, timeidx, - stagger, projection) + stagger, projection, ll_point) start_point_xy = (xy_coords.x, xy_coords.y) else: start_point_xy = (start_point.x, start_point.y) if end_point.lat is not None and end_point.lon is not None: xy_coords = to_xy_coords(end_point, wrfin, timeidx, - stagger, projection) + stagger, projection, ll_point) end_point_xy = (xy_coords.x, xy_coords.y) else: end_point_xy = (end_point.x, end_point.y) @@ -1115,7 +1171,7 @@ def _set_line_meta(wrapped, instance, args, kwargs): outname = None outdimnames = None outcoords = None - outattrs = None + outattrs = OrderedDict() # Use XY to set the cross-section metadata st_x = xy[0,0] @@ -1130,7 +1186,6 @@ def _set_line_meta(wrapped, instance, args, kwargs): if isinstance(field2d, DataArray): outcoords = OrderedDict() - outattrs = OrderedDict() outdimnames = list(field2d.dims) outcoords.update(field2d.coords) for i in py3range(-2,0,1): @@ -1198,6 +1253,8 @@ def _set_line_meta(wrapped, instance, args, kwargs): outcoords["xy_loc"] = (loc_dimnames, latlon_loc) else: + warnings.warn("'latlon' is set to True, but 'field2d' " + "contains no coordinate information") outcoords["xy_loc"] = ("line_idx", np.asarray(tuple( CoordPair(xy[i,0], xy[i,1]) for i in py3range(xy.shape[-2])))) @@ -1208,8 +1265,11 @@ def _set_line_meta(wrapped, instance, args, kwargs): for i in py3range(xy.shape[-2])))) else: + if inc_latlon: + warnings.warn("'latlon' is set to True, but 'field2d' is " + "not of type xarray.DataArray and contains no " + "coordinate information") outname = "field2d_line" - outattrs = OrderedDict() outattrs["orientation"] = cross_str @@ -1270,12 +1330,11 @@ def _set_vinterp_meta(wrapped, instance, args, kwargs): outname = None outdimnames = None outcoords = None - outattrs = None + outattrs = OrderedDict() if isinstance(field, DataArray): outcoords = OrderedDict() - outattrs = OrderedDict() outdimnames = list(field.dims) outcoords.update(field.coords) @@ -1288,13 +1347,14 @@ def _set_vinterp_meta(wrapped, instance, args, kwargs): outdimnames.insert(-2, "interp_level") outcoords["interp_level"] = interp_levels outattrs.update(field.attrs) - outattrs["vert_interp_type"] = vert_coord + outname = field.name else: outname = field_type + outattrs["vert_interp_type"] = vert_coord return DataArray(result, name=outname, dims=outdimnames, coords=outcoords, attrs=outattrs) @@ -1338,8 +1398,7 @@ def _set_2dxy_meta(wrapped, instance, args, kwargs): argvars = from_args(wrapped, ("field3d", "xy"), *args, **kwargs) field3d = argvars["field3d"] - xy = argvars["xy"] - xy = to_np(xy) + xy = to_np(argvars["xy"]) result = wrapped(*args, **kwargs) @@ -1355,12 +1414,11 @@ def _set_2dxy_meta(wrapped, instance, args, kwargs): outname = None outdimnames = None outcoords = None - outattrs = None + outattrs = OrderedDict() # Dims are (...,xy,z) if isinstance(field3d, DataArray): outcoords = OrderedDict() - outattrs = OrderedDict() outdimnames = list(field3d.dims) outcoords.update(field3d.coords) @@ -1456,12 +1514,11 @@ def _set_1d_meta(wrapped, instance, args, kwargs): outname = None outdimnames = None outcoords = None - outattrs = None + outattrs = OrderedDict() # Dims are (...,xy,z) if isinstance(field, DataArray): outcoords = OrderedDict() - outattrs = OrderedDict() outdimnames = list(field.dims) outdimnames.pop(-1) @@ -1476,9 +1533,6 @@ def _set_1d_meta(wrapped, instance, args, kwargs): outname = "{0}_z".format(field.name) outcoords["z"] = z_out - outattrs["_FillValue"] = missingval - outattrs["missing_value"] = missingval - desc = field.attrs.get("description", None) if desc is not None: outattrs["description"] = desc @@ -1490,6 +1544,8 @@ def _set_1d_meta(wrapped, instance, args, kwargs): else: outname = "field_z" + outattrs["_FillValue"] = missingval + outattrs["missing_value"] = missingval return DataArray(result, name=outname, dims=outdimnames, coords=outcoords, attrs=outattrs) @@ -1770,6 +1826,7 @@ def set_alg_metadata(alg_ndims, refvarname, return func_wrapper + def set_smooth_metdata(): @wrapt.decorator def func_wrapper(wrapped, instance, args, kwargs): @@ -1994,14 +2051,14 @@ def set_cape_alg_metadata(is2d, copyarg="pres_hpa"): return func_wrapper -def set_cloudfrac_alg_metadata(copyarg="pres"): +def set_cloudfrac_alg_metadata(copyarg="vert"): """A decorator that sets the metadata for the wrapped raw cloud fraction diagnostic function. Args: copyarg (:obj:`str`): The wrapped function argument to use for - copying dimension names. Default is 'pres'. + copying dimension names. Default is 'vert'. Returns: @@ -2025,6 +2082,16 @@ def set_cloudfrac_alg_metadata(copyarg="pres"): result = wrapped(*args, **kwargs) + argvals = from_args(wrapped, (copyarg, "low_thresh", + "mid_thresh", "high_thresh", + "missing"), + *args, **kwargs) + cp = argvals[copyarg] + low_thresh = argvals["low_thresh"] + mid_thresh = argvals["mid_thresh"] + high_thresh = argvals["high_thresh"] + missing = argvals["missing"] + # Default dimension names outdims = ["dim_{}".format(i) for i in py3range(result.ndim)] @@ -2034,15 +2101,17 @@ def set_cloudfrac_alg_metadata(copyarg="pres"): outattrs["description"] = "low, mid, high clouds" outattrs["units"] = "%" outattrs["MemoryOrder"] = "XY" + outattrs["low_thresh"] = low_thresh + outattrs["mid_thresh"] = mid_thresh + outattrs["high_thresh"] = high_thresh + outattrs["_FillValue"] = missing + outattrs["missing_value"] = missing - - p = from_args(wrapped, copyarg, *args, **kwargs)[copyarg] - - if isinstance(p, DataArray): + if isinstance(cp, DataArray): # Right dims - outdims[-2:] = p.dims[-2:] + outdims[-2:] = cp.dims[-2:] # Left dims - outdims[1:-2] = p.dims[0:-3] + outdims[1:-2] = cp.dims[0:-3] outcoords = {} diff --git a/src/wrf/projection.py b/src/wrf/projection.py index f8b95d5..d5b6670 100644 --- a/src/wrf/projection.py +++ b/src/wrf/projection.py @@ -574,12 +574,16 @@ class LambertConformal(WrfProj): def _cartopy(self): if not cartopy_enabled(): return None - + + # Set cutoff to -30 for NH, +30.0 for SH. + cutoff = -30.0 if self.moad_cen_lat >= 0 else 30.0 + _cartopy = crs.LambertConformal( central_longitude = self.stand_lon, central_latitude = self.moad_cen_lat, standard_parallels = self._std_parallels, - globe = self._globe()) + globe = self._globe(), + cutoff = cutoff) return _cartopy @@ -787,7 +791,7 @@ class PolarStereographic(WrfProj): return _pyngl - def _basemap(self, **kwargs): + def _basemap(self, geobounds, **kwargs): if not basemap_enabled(): return None diff --git a/src/wrf/routines.py b/src/wrf/routines.py index b72423b..5442956 100644 --- a/src/wrf/routines.py +++ b/src/wrf/routines.py @@ -3,27 +3,27 @@ from __future__ import (absolute_import, division, print_function, from .util import (get_iterable, is_standard_wrf_var, extract_vars, viewkeys, get_id) -from .cape import get_2dcape, get_3dcape -from .ctt import get_ctt -from .dbz import get_dbz, get_max_dbz -from .dewpoint import get_dp, get_dp_2m -from .geoht import get_geopt, get_height -from .helicity import get_srh, get_uh -from .latlon import get_lat, get_lon -from .omega import get_omega -from .pressure import get_pressure, get_pressure_hpa -from .pw import get_pw -from .rh import get_rh, get_rh_2m -from .slp import get_slp -from .temp import get_tc, get_eth, get_temp, get_theta, get_tk, get_tv, get_tw -from .terrain import get_terrain -from .uvmet import (get_uvmet, get_uvmet10, get_uvmet10_wspd_wdir, +from .g_cape import get_2dcape, get_3dcape +from .g_ctt import get_ctt +from .g_dbz import get_dbz, get_max_dbz +from .g_dewpoint import get_dp, get_dp_2m +from .g_geoht import get_geopt, get_height, get_stag_geopt, get_stag_height +from .g_helicity import get_srh, get_uh +from .g_latlon import get_lat, get_lon +from .g_omega import get_omega +from .g_pressure import get_pressure, get_pressure_hpa +from .g_pw import get_pw +from .g_rh import get_rh, get_rh_2m +from .g_slp import get_slp +from .g_temp import get_tc, get_eth, get_temp, get_theta, get_tk, get_tv, get_tw +from .g_terrain import get_terrain +from .g_uvmet import (get_uvmet, get_uvmet10, get_uvmet10_wspd_wdir, get_uvmet_wspd_wdir) -from .vorticity import get_avo, get_pvo -from .wind import (get_destag_wspd_wdir, get_destag_wspd_wdir10, +from .g_vorticity import get_avo, get_pvo +from .g_wind import (get_destag_wspd_wdir, get_destag_wspd_wdir10, get_u_destag, get_v_destag, get_w_destag) -from .times import get_times -from .cloudfrac import get_cloudfrac +from .g_times import get_times +from .g_cloudfrac import get_cloudfrac # func is the function to call. kargs are required arguments that should @@ -68,7 +68,9 @@ _FUNC_MAP = {"cape2d" : get_2dcape, "uvmet_wspd_wdir" : get_uvmet_wspd_wdir, "uvmet10_wspd_wdir" : get_uvmet10_wspd_wdir, "ctt" : get_ctt, - "cloudfrac" : get_cloudfrac + "cloudfrac" : get_cloudfrac, + "geopt_stag" : get_stag_geopt, + "zstag" : get_stag_height } _VALID_KARGS = {"cape2d" : ["missing"], @@ -111,7 +113,10 @@ _VALID_KARGS = {"cape2d" : ["missing"], "uvmet_wspd_wdir" : ["units"], "uvmet10_wspd_wdir" : ["units"], "ctt" : [], - "cloudfrac" : [], + "cloudfrac" : ["vert_type", "low_thresh", + "mid_thresh", "high_thresh"], + "geopt_stag" : [], + "zstag" : ["msl", "units"], "default" : [] } diff --git a/src/wrf/specialdec.py b/src/wrf/specialdec.py index c093ca5..f72899b 100644 --- a/src/wrf/specialdec.py +++ b/src/wrf/specialdec.py @@ -7,7 +7,7 @@ import wrapt from .util import iter_left_indexes, to_np from .config import xarray_enabled -from .constants import Constants +from .constants import default_fill if xarray_enabled(): from xarray import DataArray @@ -74,12 +74,12 @@ def uvmet_left_iter(alg_dtype=np.float64): v_arr = to_np(v) - umissing = Constants.DEFAULT_FILL + umissing = default_fill(np.float64) if isinstance(u_arr, np.ma.MaskedArray): has_missing = True umissing = u_arr.fill_value - vmissing = Constants.DEFAULT_FILL + vmissing = default_fill(np.float64) if isinstance(v_arr, np.ma.MaskedArray): has_missing = True vmissing = v_arr.fill_value @@ -383,6 +383,7 @@ def cape_left_iter(alg_dtype=np.float64): return func_wrapper + def cloudfrac_left_iter(alg_dtype=np.float64): """A decorator to handle iterating over the leftmost dimensions for the cloud fraction diagnostic. @@ -409,72 +410,71 @@ def cloudfrac_left_iter(alg_dtype=np.float64): """ @wrapt.decorator def func_wrapper(wrapped, instance, args, kwargs): - # The cape calculations use an ascending vertical pressure coordinate new_args = list(args) new_kwargs = dict(kwargs) - p = args[0] + vert = args[0] rh = args[1] - num_left_dims = p.ndim - 3 - orig_dtype = p.dtype + num_left_dims = vert.ndim - 3 + orig_dtype = vert.dtype - # No special left side iteration, build the output from the cape,cin - # result + # No special left side iteration, build the output from the + # low, mid, high results. if (num_left_dims == 0): - low, med, high = wrapped(*new_args, **new_kwargs) + low, mid, high = wrapped(*new_args, **new_kwargs) output_dims = (3,) - output_dims += p.shape[-2:] + output_dims += vert.shape[-2:] output = np.empty(output_dims, orig_dtype) output[0,:] = low[:] - output[1,:] = med[:] + output[1,:] = mid[:] output[2,:] = high[:] return output - # Initial output is ...,cape_cin,nz,ny,nx to create contiguous views - outdims = p.shape[0:num_left_dims] + # Initial output is ...,low_mid_high,nz,ny,nx to create contiguous views + outdims = vert.shape[0:num_left_dims] extra_dims = tuple(outdims) # Copy the left-most dims for iteration outdims += (3,) # low_mid_high - outdims += p.shape[-2:] + outdims += vert.shape[-2:] outview_array = np.empty(outdims, alg_dtype) # Create the output array where the leftmost dim is the cloud type output_dims = (3,) output_dims += extra_dims - output_dims += p.shape[-2:] + output_dims += vert.shape[-2:] output = np.empty(output_dims, orig_dtype) has_missing = False - missing = Constants.DEFAULT_FILL + missing = default_fill(np.float64) for left_idxs in iter_left_indexes(extra_dims): left_and_slice_idxs = left_idxs + (slice(None),) low_idxs = left_idxs + (0, slice(None)) - med_idxs = left_idxs + (1, slice(None)) + mid_idxs = left_idxs + (1, slice(None)) high_idxs = left_idxs + (2, slice(None)) low_output_idxs = (0,) + left_idxs + (slice(None),) - med_output_idxs = (1,) + left_idxs + (slice(None),) + mid_output_idxs = (1,) + left_idxs + (slice(None),) high_output_idxs = (2,) + left_idxs + (slice(None),) - new_args[0] = p[left_and_slice_idxs] + new_args[0] = vert[left_and_slice_idxs] new_args[1] = rh[left_and_slice_idxs] # Skip the possible empty/missing arrays for the join method - # Note: Masking handled by cape.py or computation.py, so only + # Note: Masking handled by cloudfrac.py or computation.py, so only # supply the fill values here. skip_missing = False for arg in (new_args[0:2]): if isinstance(arg, np.ma.MaskedArray): if arg.mask.all(): output[low_output_idxs] = missing - output[med_output_idxs] = missing + output[mid_output_idxs] = missing output[high_output_idxs] = missing skip_missing = True @@ -484,14 +484,14 @@ def cloudfrac_left_iter(alg_dtype=np.float64): continue lowview = outview_array[low_idxs] - medview = outview_array[med_idxs] + midview = outview_array[mid_idxs] highview = outview_array[high_idxs] new_kwargs["lowview"] = lowview - new_kwargs["medview"] = medview + new_kwargs["midview"] = midview new_kwargs["highview"] = highview - low, med, high = wrapped(*new_args, **new_kwargs) + low, mid, high = wrapped(*new_args, **new_kwargs) # Make sure the result is the same data as what got passed in # Can delete this once everything works @@ -501,8 +501,8 @@ def cloudfrac_left_iter(alg_dtype=np.float64): output[low_output_idxs] = ( outview_array[low_idxs].astype(orig_dtype)) - output[med_output_idxs] = ( - outview_array[med_idxs].astype(orig_dtype)) + output[mid_output_idxs] = ( + outview_array[mid_idxs].astype(orig_dtype)) output[high_output_idxs] = ( outview_array[high_idxs].astype(orig_dtype)) diff --git a/src/wrf/util.py b/src/wrf/util.py index 6ac841f..3aa9583 100644 --- a/src/wrf/util.py +++ b/src/wrf/util.py @@ -30,16 +30,16 @@ import numpy as np import numpy.ma as ma from .config import xarray_enabled -from .constants import Constants, ALL_TIMES +from .constants import default_fill, ALL_TIMES from .py3compat import (viewitems, viewkeys, isstr, py3range, ucode) from .cache import cache_item, get_cached_item from .geobnds import GeoBounds, NullGeoBounds +from .coordpair import CoordPair from .projection import getproj if xarray_enabled(): from xarray import DataArray - from pandas import NaT _COORD_PAIR_MAP = {"XLAT" : ("XLAT", "XLONG"), @@ -209,11 +209,30 @@ def _generator_copy(gen): module = getmodule(gen.gi_frame) if module is not None: - res = module.get(funcname)(**argvals.locals) + try: + try: + argd = {key:argvals.locals[key] for key in argvals.args} + res = module.get(funcname)(**argd) + except AttributeError: + res = getattr(module, funcname)(**argd) + except: + # This is the old way it used to work, but it looks like this was + # fixed by Python. + try: + res = module.get(funcname)(**argvals.locals) + except AttributeError: + res = getattr(module, funcname)(**argvals.locals) else: # Created in jupyter or the python interpreter import __main__ - res = getattr(__main__, funcname)(**argvals.locals) + + try: + argd = {key:argvals.locals[key] for key in argvals.args} + res = getattr(__main__, funcname)(**argd) + except: + # This was the old way it used to work, but appears to have + # been fixed by Python. + res = getattr(__main__, funcname)(**argvals.locals) return res @@ -727,8 +746,12 @@ def is_moving_domain(wrfin, varname=None, latvar=either("XLAT", "XLAT_M"), # to be a shortcut in the netcdf files. if varname is not None: try: - coord_names = getattr(first_wrfnc.variables[varname], - "coordinates").split() + coord_str = getattr(first_wrfnc.variables[varname], "coordinates") + # scipy.io.netcdf stores attributes as bytes rather than str + if isinstance(coord_str, str): + coord_names = coord_str.split() + else: + coord_names = coord_str.decode().split() except AttributeError: # Variable doesn't have a coordinates attribute, use the # arguments @@ -878,7 +901,17 @@ def extract_dim(wrfin, dim): d = wrfin.dimensions[dim] if not isinstance(d, int): - return len(d) #netCDF4 + try: + return len(d) #netCDF4 + except TypeError: #scipy.io.netcdf + # Scipy can't handled unlimited dimensions, so now we have to + # figure it out + try: + s = wrfin.variables["P"].shape + return s[-4] + except: + raise ValueError("unsupported NetCDF reader") + return d # PyNIO @@ -1078,6 +1111,7 @@ def _find_max_time_size(wrfseq): return max_times + def _get_coord_names(wrfin, varname): # Need only the first real file @@ -1117,17 +1151,34 @@ def _get_coord_names(wrfin, varname): time_coord = None else: try: - # met_em files + # met_em files or old WRF files stag_attr = getattr(var, "stagger") except AttributeError: lon_coord = None lat_coord = None + + # Let's just check for xlat and xlong in this case + if "XLAT" in wrfnc.variables: + lat_coord = "XLAT" + lon_coord = "XLONG" else: # For met_em files, use the stagger name to get the lat/lon var lat_coord = "XLAT_{}".format(stag_attr) lon_coord = "XLONG_{}".format(stag_attr) + + # If this coord name is missing, it might be an old WRF file + if lat_coord not in wrfnc.variables: + lat_coord = None + lon_coord = None + + if "XLAT" in wrfnc.variables: + lat_coord = "XLAT" + lon_coord = "XLONG" else: - coord_names = coord_attr.split() + if isinstance(coord_attr, str): + coord_names = coord_attr.split() + else: + coord_names = coord_attr.decode().split() lon_coord = coord_names[0] lat_coord = coord_names[1] @@ -1186,16 +1237,46 @@ def _build_data_array(wrfnc, varname, timeidx, is_moving_domain, is_multifile, multitime = is_multi_time_req(timeidx) time_idx_or_slice = timeidx if not multitime else slice(None) var = wrfnc.variables[varname] - data = var[time_idx_or_slice, :] + if len(var.shape) > 1: + data = var[time_idx_or_slice, :] + else: + data = var[time_idx_or_slice] # Want to preserve the time dimension if not multitime: - data = data[np.newaxis, :] + if len(var.shape) > 1: + data = data[np.newaxis, :] + else: + data = data[np.newaxis] + + attrs = OrderedDict() + for dkey, val in viewitems(var.__dict__): + # scipy.io adds these but don't want them + if dkey in ("data", "_shape", "_size", "_typecode", "_attributes", + "maskandscale", "dimensions"): + continue + + _dkey = dkey if isinstance(dkey, str) else dkey.decode() + if isstr(val): + _val = val + else: + if isinstance(val, bytes): + _val = val.decode() # scipy.io.netcdf + else: + _val = val + + attrs[_dkey] = _val - attrs = OrderedDict(var.__dict__) dimnames = var.dimensions[-data.ndim:] - lat_coord, lon_coord, time_coord = _get_coord_names(wrfnc, varname) + lat_coord = lon_coord = time_coord = None + + try: + if dimnames[-2] == "south_north" and dimnames[-1] == "west_east": + lat_coord, lon_coord, time_coord = _get_coord_names(wrfnc, varname) + except IndexError: + pass + coords = OrderedDict() @@ -1274,10 +1355,9 @@ def _build_data_array(wrfnc, varname, timeidx, is_moving_domain, is_multifile, coords[time_coord] = (lon_coord_dims[0], [time_coord_vals[timeidx]]) - proj_params = get_proj_params(wrfnc) - proj = getproj(**proj_params) - attrs["projection"] = proj - + proj_params = get_proj_params(wrfnc) + proj = getproj(**proj_params) + attrs["projection"] = proj if dimnames[0] == "Time": t = extract_times(wrfnc, timeidx, meta=False, do_xtime=False) @@ -1341,8 +1421,13 @@ def _find_forward(wrfseq, varname, timeidx, is_moving, meta, _key): return _build_data_array(wrfnc, varname, filetimeidx, is_moving, True, _key) else: - result = wrfnc.variables[varname][filetimeidx, :] - return result[np.newaxis, :] # So that nosqueeze works + var = wrfnc.variables[varname] + if len(var.shape) > 1: + result = var[filetimeidx, :] + return result[np.newaxis, :] # So that nosqueeze works + else: + result = var[filetimeidx] + return result[np.newaxis] # So that nosqueeze works else: comboidx += numtimes @@ -1541,7 +1626,10 @@ def _cat_files(wrfseq, varname, timeidx, is_moving, squeeze, meta, _key): startidx = 0 endidx = numtimes - outdata[startidx:endidx, :] = first_var[:] + if first_var.ndim > 1: + outdata[startidx:endidx, :] = first_var[:] + else: + outdata[startidx:endidx] = first_var[:] if xarray_enabled() and meta: latname, lonname, timename = _find_coord_names(first_var.coords) @@ -1600,7 +1688,10 @@ def _cat_files(wrfseq, varname, timeidx, is_moving, squeeze, meta, _key): endidx = startidx + numtimes - outdata[startidx:endidx, :] = vardata[:] + if vardata.ndim > 1: + outdata[startidx:endidx, :] = vardata[:] + else: + outdata[startidx:endidx] = vardata[:] if xarray_enabled() and meta: if timename is not None and not timecached: @@ -1762,7 +1853,8 @@ def _join_files(wrfseq, varname, timeidx, is_moving, meta, _key): if xarray_enabled() and meta: first_var = _build_data_array(wrfnc, varname, ALL_TIMES, is_moving, True, _key) - time_coord = np.full((numfiles, maxtimes), int(NaT), "datetime64[ns]") + time_coord = np.full((numfiles, maxtimes), np.datetime64("NaT"), + "datetime64[ns]") time_coord[file_idx, 0:numtimes] = first_var.coords["Time"][:] else: first_var = wrfnc.variables[varname][:] @@ -1777,8 +1869,11 @@ def _join_files(wrfseq, varname, timeidx, is_moving, meta, _key): outdims += first_var.shape[1:] # For join, always need to start with full masked values - outdata = np.full(outdims, Constants.DEFAULT_FILL, first_var.dtype) - outdata[file_idx, 0:numtimes, :] = first_var[:] + outdata = np.full(outdims, default_fill(first_var.dtype), first_var.dtype) + if first_var.ndim > 1: + outdata[file_idx, 0:numtimes, :] = first_var[:] + else: + outdata[file_idx, 0:numtimes] = first_var[:] # Create the secondary coordinate arrays if xarray_enabled() and meta: @@ -1800,8 +1895,9 @@ def _join_files(wrfseq, varname, timeidx, is_moving, meta, _key): if timename is not None: outxtimes = get_cached_item(_key, timekey) if outxtimes is None: - outxtimes = np.full(outdims[0:2], Constants.DEFAULT_FILL, - first_var.dtype) + outxtimes = np.full(outdims[0:2], + default_fill(first_var.dtype), + first_var.dtype) outxtimes[file_idx, 0:numtimes] = first_var.coords[timename][:] else: timecached = True @@ -1810,8 +1906,9 @@ def _join_files(wrfseq, varname, timeidx, is_moving, meta, _key): if latname is not None: outlats = get_cached_item(_key, latkey) if outlats is None: - outlats = np.full(outcoorddims, Constants.DEFAULT_FILL, - first_var.dtype) + outlats = np.full(outcoorddims, + default_fill(first_var.dtype), + first_var.dtype) outlats[file_idx, 0:numtimes, :] = ( first_var.coords[latname][:]) else: @@ -1820,8 +1917,9 @@ def _join_files(wrfseq, varname, timeidx, is_moving, meta, _key): if lonname is not None: outlons = get_cached_item(_key, lonkey) if outlons is None: - outlons = np.full(outcoorddims, Constants.DEFAULT_FILL, - first_var.dtype) + outlons = np.full(outcoorddims, + default_fill(first_var.dtype), + first_var.dtype) outlons[file_idx, 0:numtimes, :] = ( first_var.coords[lonname][:]) else: @@ -1841,8 +1939,11 @@ def _join_files(wrfseq, varname, timeidx, is_moving, meta, _key): if not multitime: outvar = outvar[np.newaxis, :] - - outdata[file_idx, 0:numtimes, :] = outvar[:] + + if outvar.ndim > 1: + outdata[file_idx, 0:numtimes, :] = outvar[:] + else: + outdata[file_idx, 0:numtimes] = outvar[:] if xarray_enabled() and meta: # For join, the times are a function of fileidx @@ -1871,7 +1972,7 @@ def _join_files(wrfseq, varname, timeidx, is_moving, meta, _key): # then a mask array is needed to flag all the missing arrays with # missing values if file_times_less_than_max: - outdata = np.ma.masked_values(outdata, Constants.DEFAULT_FILL) + outdata = np.ma.masked_values(outdata, default_fill(outdata.dtype)) if xarray_enabled() and meta: # Cache the coords if applicable @@ -1898,8 +1999,8 @@ def _join_files(wrfseq, varname, timeidx, is_moving, meta, _key): outcoords["datetime"] = outdimnames[0:2], time_coord if isinstance(outdata, np.ma.MaskedArray): - outattrs["_FillValue"] = Constants.DEFAULT_FILL - outattrs["missing_value"] = Constants.DEFAULT_FILL + outattrs["_FillValue"] = default_fill(outdata.dtype) + outattrs["missing_value"] = default_fill(outdata.dtype) if timename is not None: outxtimes = outxtimes[:, time_idx_or_slice] @@ -2132,9 +2233,6 @@ def extract_vars(wrfin, timeidx, varnames, method="cat", squeeze=True, negative integer, or :data:`wrf.ALL_TIMES` (an alias for None) to return all times in the file or sequence. The default is 0. - - is_moving (:obj:`bool`): A boolean type that indicates if the - sequence is a moving nest. method (:obj:`str`, optional): The aggregation method to use for sequences. Must be either 'cat' or 'join'. @@ -2345,15 +2443,35 @@ def extract_times(wrfin, timeidx, method="cat", squeeze=True, cache=None, else: wrf_list = wrfin + dt = "datetime64[ns]" if not do_xtime else np.float64 + fill_value = (np.datetime64('NaT') if not do_xtime else + default_fill(np.float64)) + try: if method.lower() == "cat": time_list = [file_time for wrf_file in wrf_list for file_time in _file_times(wrf_file, do_xtime)] + time_arr = np.asarray(time_list, dtype=dt) + elif method.lower() == "join": time_list = [[file_time for file_time in _file_times(wrf_file, do_xtime)] - for wrf_file in wrf_list] + for wrf_file in wrf_list] + + num_rows = len(time_list) + num_cols = len(time_list[0]) + + time_arr = np.full((num_rows, num_cols), fill_value, dtype=dt) + for i,row in enumerate(time_list): + if len(row) == num_cols: + time_arr[i,:] = row[:] + else: + for j,val in enumerate(row): + time_arr[i,j] = val + + time_arr = ma.masked_values(time_arr, fill_value) + else: raise ValueError("invalid method argument '{}'".format(method)) except KeyError: @@ -2367,6 +2485,8 @@ def extract_times(wrfin, timeidx, method="cat", squeeze=True, cache=None, outdimnames = ["Time"] else: outdimnames = ["fileidx", "Time"] + outattrs["missing_value"] = fill_value + outattrs["_FillValue"] = fill_value if not do_xtime: outname = "times" @@ -2379,11 +2499,12 @@ def extract_times(wrfin, timeidx, method="cat", squeeze=True, cache=None, outname = "XTIME" - outarr = DataArray(time_list, name=outname, coords=outcoords, + + outarr = DataArray(time_arr, name=outname, coords=outcoords, dims=outdimnames, attrs=outattrs) else: - outarr = np.asarray(time_list, dtype="datetime64[ns]") + outarr = time_arr if not multitime: return outarr[timeidx] @@ -2569,26 +2690,6 @@ def get_proj_params(wrfin):#, timeidx=0, varname=None): "DX", "DY")) return proj_params -# multitime = is_multi_time_req(timeidx) -# if not multitime: -# time_idx_or_slice = timeidx -# else: -# time_idx_or_slice = slice(None) -# -# if varname is not None: -# if not is_coordvar(varname): -# coord_names = getattr(wrfin.variables[varname], -# "coordinates").split() -# lon_coord = coord_names[0] -# lat_coord = coord_names[1] -# else: -# lat_coord, lon_coord = get_coord_pairs(varname) -# else: -# lat_coord, lon_coord = latlon_coordvars(wrfin.variables) -# -# return (wrfin.variables[lat_coord][time_idx_or_slice,:], -# wrfin.variables[lon_coord][time_idx_or_slice,:], -# proj_params) def from_args(func, argnames, *args, **kwargs): @@ -2922,18 +3023,40 @@ def psafilepath(): return os.path.join(os.path.dirname(__file__), "data", "psadilookup.dat") -def get_id(obj): - """Return the object id. +def get_filepath(obj): + + try: + path = obj.filepath() + except AttributeError: + try: + path = obj.file.path + except: + # Let's make up a filename from the first file time + found = False + times = extract_times(obj, None, meta=False, do_xtime=False) + for t in times: + path = "wrfout_{}".format(str(t)) + found = True + break + + if not found: + raise ValueError("file contains no path information") + + return path + +def get_id(obj, prefix=''): + """Return the cache id. - The object id is used as a caching key for various routines. If the + The cache id is used as a caching key for various routines. If the object type is a mapping, then the result will also be a - mapping of each key to the object id for the value. Otherwise, only the - object id is returned. + mapping of each key to the object id for the value. Args: obj (:obj:`object`): Any object type. + prefix (:obj:`str`): A string to help with recursive calls. + Returns: :obj:`int` or :obj:`dict`: If the *obj* parameter is not a mapping, @@ -2941,12 +3064,18 @@ def get_id(obj): key to the object id for the value is returned. """ + if not is_multi_file(obj): + return hash(prefix + get_filepath(obj)) + + # For sequences, the hashing string will be the list ID and the + # path for the first file in the sequence if not is_mapping(obj): - return id(obj) + _next = next(iter(obj)) + return get_id(_next, prefix + str(id(obj))) # For each key in the mapping, recursively call get_id until # until a non-mapping is found - return {key : get_id(val) for key,val in viewitems(obj)} + return {key : get_id(val, prefix) for key,val in viewitems(obj)} def geo_bounds(var=None, wrfin=None, varname=None, timeidx=0, method="cat", @@ -3685,7 +3814,68 @@ def cartopy_ylim(var=None, geobounds=None, wrfin=None, varname=None, timeidx=0, return wrf_proj.cartopy_ylim(native_geobnds) +def ll_points(lat, lon): + """Return the lower left latitude and longitude point(s). + + This functions extracts the lower left corner points and returns the result + as either a single :class:`CoordPair` object, or a list of + :class:`CoordPair` objects. + + This is primarily used for testing or constructing the corner point objects + from the XLAT and XLONG variables. + + Args: + + lat (:class:`xarray.DataArray` or :class:`numpy.ndarray`): The latitude + array. Must be at least two dimensions. + + lon (:class:`xarray.DataArray` or :class:`numpy.ndarray`): The + longitude array. Must be at least two dimensions. + + Returns: + + :class:`wrf.CoordPair` or :obj:`list`: A single :class:`wrf.CoordPair` + object or a list of :class:`wrf.CoordPair` objects. + + """ + latvals = np.ravel(to_np(lat)[...,0,0]) + lonvals = np.ravel(to_np(lon)[...,0,0]) + + if latvals.shape[0] == 1: + return CoordPair(lat=float(latvals), lon=float(lonvals)) + else: + return [CoordPair(lat=latvals[i], lon=lonvals[i]) + for i in py3range(latvals.shape[0])] + +def pairs_to_latlon(pairs): + """Return latitude and longitude arrays from a sequence of \ + :class:`wrf.CoordPair` objects. + + This function converts a sequence of :class:`wrf.CoordPair` objects into + lists of latitude and longitude points. If the *pairs* argument is a + single :class:`wrf.CoordPair` object, then a single latitude and + longitude value is returned. + + Args: + + pairs (:class:`wrf.CoordPair` or sequence): A single + :class:`wrf.CoordPair` or sequence of :class:`wrf.CoordPair`. + + Returns: + + :obj:`tuple`: A tuple of (lat, lon), where lat and lon are single + values or lists of values. + + """ + + if isinstance(pairs, CoordPair): + return (pairs.lat, pairs.lon) + else: + lats = [pair.lat for pair in pairs] + lons = [pair.lon for pair in pairs] + + return lats, lons diff --git a/src/wrf/version.py b/src/wrf/version.py index c55bfe3..aebf1b8 100644 --- a/src/wrf/version.py +++ b/src/wrf/version.py @@ -1,2 +1,2 @@ -__version__ = "1.0.5" +__version__ = "1.1.0" diff --git a/test/cachetest.py b/test/cachetest.py index cc7efee..fad5e88 100644 --- a/test/cachetest.py +++ b/test/cachetest.py @@ -2,7 +2,10 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) from threading import Thread -from Queue import Queue +try: + from Queue import Queue +except ImportError: + from queue import Queue from collections import OrderedDict import unittest as ut @@ -62,4 +65,4 @@ class CacheTest(ut.TestCase): if __name__ == "__main__": - ut.main() \ No newline at end of file + ut.main() diff --git a/test/ci_tests/ci_result_file.nc b/test/ci_tests/ci_result_file.nc index 75c253a..2cf9ffb 100644 Binary files a/test/ci_tests/ci_result_file.nc and b/test/ci_tests/ci_result_file.nc differ diff --git a/test/ci_tests/ci_test_file.nc b/test/ci_tests/ci_test_file.nc index 13b9f54..b0f8945 100644 Binary files a/test/ci_tests/ci_test_file.nc and b/test/ci_tests/ci_test_file.nc differ diff --git a/test/ci_tests/make_test_file.py b/test/ci_tests/make_test_file.py index 0f65c0d..e6ec29b 100644 --- a/test/ci_tests/make_test_file.py +++ b/test/ci_tests/make_test_file.py @@ -9,9 +9,9 @@ from netCDF4 import Dataset from wrf import (getvar, interplevel, interpline, vertcross, vinterp, py2round, CoordPair, ll_to_xy, xy_to_ll, to_np) -VARS_TO_KEEP = ("XLAT", "XLONG", "XLAT_U", "XLAT_V", "XLONG_U", "XLONG_V", - "U", "V", "W", "PH", "PHB", "T", "P", "PB", "Q2", "T2", - "PSFC", "U10", "V10", "XTIME", "QVAPOR", "QCLOUD", +VARS_TO_KEEP = ("Times", "XLAT", "XLONG", "XLAT_U", "XLAT_V", "XLONG_U", + "XLONG_V", "U", "V", "W", "PH", "PHB", "T", "P", "PB", "Q2", + "T2", "PSFC", "U10", "V10", "XTIME", "QVAPOR", "QCLOUD", "QGRAUP", "QRAIN", "QSNOW", "MAPFAC_M", "MAPFAC_U", "MAPFAC_V", "F", "HGT", "RAINC", "RAINSH", "RAINNC") @@ -22,7 +22,7 @@ WRF_DIAGS = ["avo", "eth", "cape_2d", "cape_3d", "ctt", "dbz", "mdbz", "geopt", "helicity", "lat", "lon", "omg", "p", "pressure", "pvo", "pw", "rh2", "rh", "slp", "ter", "td2", "td", "tc", "theta", "tk", "tv", "twb", "updraft_helicity", "ua", "va", - "wa", "uvmet10", "uvmet", "z", "cfrac"] + "wa", "uvmet10", "uvmet", "z", "cfrac", "zstag"] INTERP_METHS = ["interplevel", "vertcross", "interpline", "vinterp"] @@ -33,6 +33,7 @@ def copy_and_reduce(opts): infilename = opts.filename outfilename = os.path.expanduser( os.path.join(opts.outdir, "ci_test_file.nc")) + with Dataset(infilename) as infile, Dataset(outfilename, "w") as outfile: # Copy the global attributes @@ -164,12 +165,14 @@ def main(opts): if __name__ == "__main__": - + DEFAULT_FILE = ("/Users/ladwig/Documents/wrf_files/" + "wrf_vortex_multi/wrfout_d02_2005-08-28_12:00:00") parser = argparse.ArgumentParser(description="Generate conda test files " "for unit testing.") - parser.add_argument("-f", "--filename", required=True, + parser.add_argument("-f", "--filename", required=False, + default=DEFAULT_FILE, help="the WRF test file") - parser.add_argument("-o", "--outdir", required=True, + parser.add_argument("-o", "--outdir", required=False, default="./", help="the location for the output files") opts = parser.parse_args() diff --git a/test/ci_tests/utests.py b/test/ci_tests/utests.py index bf233de..a61cf5c 100644 --- a/test/ci_tests/utests.py +++ b/test/ci_tests/utests.py @@ -8,7 +8,8 @@ import subprocess from wrf import (getvar, interplevel, interpline, vertcross, vinterp, disable_xarray, xarray_enabled, to_np, xy_to_ll, ll_to_xy, xy_to_ll_proj, ll_to_xy_proj, - extract_global_attrs, viewitems, CoordPair) + extract_global_attrs, viewitems, CoordPair, + omp_get_num_procs, omp_set_num_threads) from wrf.util import is_multi_file TEST_FILE = "ci_test_file.nc" @@ -99,6 +100,10 @@ def make_interp_test(varname, wrf_in, referent, multi=False, ref_ht_850 = _get_refvals(referent, "interplevel", repeat, multi) hts = getvar(in_wrfnc, "z", timeidx=timeidx) p = getvar(in_wrfnc, "pressure", timeidx=timeidx) + + # Check that it works with numpy arrays + hts_850 = interplevel(to_np(hts), p, 850) + #print (hts_850) hts_850 = interplevel(hts, p, 850) nt.assert_allclose(to_np(hts_850), ref_ht_850) @@ -110,6 +115,10 @@ def make_interp_test(varname, wrf_in, referent, multi=False, p = getvar(in_wrfnc, "pressure", timeidx=timeidx) pivot_point = CoordPair(hts.shape[-1] // 2, hts.shape[-2] // 2) + # Check that it works with numpy arrays + ht_cross = vertcross(to_np(hts), to_np(p), + pivot_point=pivot_point, angle=90.) + #print (ht_cross) ht_cross = vertcross(hts, p, pivot_point=pivot_point, angle=90.) nt.assert_allclose(to_np(ht_cross), ref_ht_cross, rtol=.01) @@ -122,6 +131,10 @@ def make_interp_test(varname, wrf_in, referent, multi=False, t2 = getvar(in_wrfnc, "T2", timeidx=timeidx) pivot_point = CoordPair(t2.shape[-1] // 2, t2.shape[-2] // 2) + # Check that it works with numpy arrays + t2_line1 = interpline(to_np(t2), pivot_point=pivot_point, + angle=90.0) + #print (t2_line1) t2_line1 = interpline(t2, pivot_point=pivot_point, angle=90.0) nt.assert_allclose(to_np(t2_line1), ref_t2_line) @@ -135,6 +148,17 @@ def make_interp_test(varname, wrf_in, referent, multi=False, interp_levels = [200,300,500,1000] + # Check that it works with numpy arrays + field = vinterp(in_wrfnc, + field=to_np(tk), + vert_coord="theta", + interp_levels=interp_levels, + extrapolate=True, + field_type="tk", + timeidx=timeidx, + log_p=True) + #print (field) + field = vinterp(in_wrfnc, field=tk, vert_coord="theta", @@ -210,7 +234,7 @@ if __name__ == "__main__": "geopt", "helicity", "lat", "lon", "omg", "p", "pressure", "pvo", "pw", "rh2", "rh", "slp", "ter", "td2", "td", "tc", "theta", "tk", "tv", "twb", "updraft_helicity", "ua", "va", - "wa", "uvmet10", "uvmet", "z", "cfrac"] + "wa", "uvmet10", "uvmet", "z", "cfrac", "zstag"] interp_methods = ["interplevel", "vertcross", "interpline", "vinterp"] latlon_tests = ["xy", "ll"] diff --git a/test/comp_utest.py b/test/comp_utest.py index 846466f..55448a8 100644 --- a/test/comp_utest.py +++ b/test/comp_utest.py @@ -498,21 +498,25 @@ def get_args(varname, wrfnc, timeidx, method, squeeze): return (u, v, lat, lon, cen_lon, cone) if varname == "cloudfrac": + from wrf.g_geoht import get_height vars = extract_vars(wrfnc, timeidx, ("P", "PB", "QVAPOR", "T"), method, squeeze, cache=None, meta=True) - + p = vars["P"] pb = vars["PB"] qv = vars["QVAPOR"] t = vars["T"] + geoht_agl = get_height(wrfnc, timeidx, method, squeeze, + cache=None, meta=True, msl=False) + full_p = p + pb full_t = t + Constants.T_BASE tkel = tk(full_p, full_t) relh = rh(qv, full_p, tkel) - return (full_p, relh) + return (geoht_agl, relh, 1, 300., 2000., 6000.) class WRFVarsTest(ut.TestCase): @@ -538,7 +542,7 @@ def make_func(varname, wrfnc, timeidx, method, squeeze, meta): if meta: self.assertEqual(result.dims, ref.dims) - + return func @@ -607,6 +611,10 @@ if __name__ == "__main__": "omg", "pw", "rh", "slp", "td", "tk", "tv", "twb", "uvmet", "cloudfrac"] + omp_set_num_threads(omp_get_num_procs()-1) + omp_set_schedule(OMP_SCHED_STATIC, 0) + omp_set_dynamic(False) + # Turn this one off when not needed, since it's slow #varnames += ["cape_2d", "cape_3d"] diff --git a/test/generator_test.py b/test/generator_test.py new file mode 100644 index 0000000..6a45d89 --- /dev/null +++ b/test/generator_test.py @@ -0,0 +1,17 @@ +from __future__ import (absolute_import, division, print_function, unicode_literals) + +from wrf import getvar +from netCDF4 import Dataset as nc +#ncfile = nc("/Users/ladwig/Documents/wrf_files/wrfout_d01_2016-02-25_18_00_00") +ncfile = nc("/Users/ladwig/Documents/wrf_files/wrfout_d01_2016-10-07_00_00_00") + +def gen_seq(): + wrfseq = [ncfile, ncfile, ncfile] + for wrf in wrfseq: + yield wrf + +p_gen = getvar(gen_seq(), "P", method="join") + +print(p_gen) +del p_gen + diff --git a/test/ipynb/Doc_Examples.ipynb b/test/ipynb/Doc_Examples.ipynb index 1006a79..4b8d794 100644 --- a/test/ipynb/Doc_Examples.ipynb +++ b/test/ipynb/Doc_Examples.ipynb @@ -2,10 +2,7 @@ "cells": [ { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "# Cartopy Examples" ] @@ -13,11 +10,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "%matplotlib inline" @@ -27,9 +20,6 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false, - "deletable": true, - "editable": true, "scrolled": false }, "outputs": [], @@ -115,11 +105,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "%matplotlib inline\n", @@ -203,9 +189,6 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false, - "deletable": true, - "editable": true, "scrolled": false }, "outputs": [], @@ -343,9 +326,6 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false, - "deletable": true, - "editable": true, "scrolled": false }, "outputs": [], @@ -411,11 +391,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "%matplotlib inline\n", @@ -484,11 +460,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "from __future__ import (absolute_import, division, print_function, unicode_literals)\n", @@ -568,11 +540,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "from __future__ import print_function\n", @@ -606,11 +574,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "from __future__ import print_function\n", @@ -631,10 +595,7 @@ }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "# Basemap Examples" ] @@ -642,11 +603,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "\n", @@ -704,11 +661,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "%matplotlib inline\n", @@ -781,11 +734,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "%matplotlib inline\n", @@ -921,11 +870,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "from __future__ import print_function\n", @@ -960,11 +905,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "from __future__ import print_function\n", @@ -986,11 +927,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "# SLP\n", @@ -1041,11 +978,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "from __future__ import print_function\n", @@ -1079,11 +1012,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "from __future__ import print_function\n", @@ -1101,6 +1030,233 @@ "\n", "print (bounds)" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# OpenMP Routines" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from __future__ import print_function\n", + "\n", + "from wrf import omp_enabled\n", + "\n", + "print(omp_enabled())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from __future__ import print_function\n", + "\n", + "from wrf import omp_get_num_procs\n", + "\n", + "print(omp_get_num_procs())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from __future__ import print_function\n", + "\n", + "from wrf import omp_set_num_threads, omp_get_max_threads\n", + "\n", + "omp_set_num_threads(4)\n", + "\n", + "print(omp_get_max_threads())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from __future__ import print_function\n", + "\n", + "from wrf import omp_set_schedule, omp_get_schedule, OMP_SCHED_GUIDED\n", + "\n", + "omp_set_schedule(OMP_SCHED_GUIDED, 0)\n", + "\n", + "sched, modifier = omp_get_schedule()\n", + "\n", + "print(sched, modifier)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Loop and Fill Technique" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from __future__ import print_function, division\n", + "\n", + "import numpy as np\n", + "from netCDF4 import Dataset\n", + "from wrf import getvar, ALL_TIMES\n", + "\n", + "filename_list = [\"/Users/ladwig/Documents/wrf_files/wrf_vortex_multi/wrfout_d02_2005-08-28_00:00:00\",\n", + " \"/Users/ladwig/Documents/wrf_files/wrf_vortex_multi/wrfout_d02_2005-08-28_12:00:00\", \n", + " \"/Users/ladwig/Documents/wrf_files/wrf_vortex_multi/wrfout_d02_2005-08-29_00:00:00\"]\n", + "\n", + "# Result shape (hardcoded for this example, modify as necessary)\n", + "result_shape = (9, 29, 96, 96)\n", + "\n", + "# Only need 4-byte floats\n", + "z_final = np.empty(result_shape, np.float32)\n", + "\n", + "# Modify this number if using more than 1 time per file\n", + "times_per_file = 4\n", + "\n", + "for timeidx in xrange(result_shape[0]):\n", + " # Compute the file index and the time index inside the file\n", + " fileidx = timeidx // times_per_file\n", + " file_timeidx = timeidx % times_per_file\n", + "\n", + " f = Dataset(filename_list[fileidx]) \n", + " z = getvar(f, \"z\", file_timeidx)\n", + "\n", + " z_final[timeidx,:] = z[:]\n", + " f.close()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Using the cache argument" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from __future__ import print_function\n", + "\n", + "import time\n", + "from netCDF4 import Dataset\n", + "from wrf import getvar, ALL_TIMES, extract_vars\n", + "\n", + "wrf_filenames = [\"/Users/ladwig/Documents/wrf_files/wrf_vortex_multi/wrfout_d02_2005-08-28_00:00:00\",\n", + " \"/Users/ladwig/Documents/wrf_files/wrf_vortex_multi/wrfout_d02_2005-08-28_12:00:00\", \n", + " \"/Users/ladwig/Documents/wrf_files/wrf_vortex_multi/wrfout_d02_2005-08-29_00:00:00\"]\n", + "\n", + "wrfin = [Dataset(x) for x in wrf_filenames]\n", + "\n", + "start = time.time()\n", + "my_cache = extract_vars(wrfin, ALL_TIMES, (\"P\", \"PSFC\", \"PB\", \"PH\", \"PHB\", \"T\", \"QVAPOR\", \n", + " \"HGT\", \"U\", \"V\", \"W\"))\n", + "end = time.time()\n", + "print (\"Time taken to build cache: \", (end-start), \"s\")\n", + "\n", + "vars = (\"avo\", \"eth\", \"cape_2d\", \"cape_3d\", \"ctt\", \"dbz\", \"mdbz\", \n", + " \"geopt\", \"helicity\", \"lat\", \"lon\", \"omg\", \"p\", \"pressure\", \n", + " \"pvo\", \"pw\", \"rh2\", \"rh\", \"slp\", \"ter\", \"td2\", \"td\", \"tc\", \n", + " \"theta\", \"tk\", \"tv\", \"twb\", \"updraft_helicity\", \"ua\", \"va\", \n", + " \"wa\", \"uvmet10\", \"uvmet\", \"z\", \"cfrac\", \"zstag\", \"geopt_stag\")\n", + "\n", + "start = time.time()\n", + "for var in vars:\n", + " v = getvar(wrfin, var, ALL_TIMES)\n", + "end = time.time()\n", + "no_cache_time = (end-start)\n", + "\n", + "print (\"Time taken without variable cache: \", no_cache_time, \"s\")\n", + "\n", + "start = time.time()\n", + "for var in vars:\n", + " v = getvar(wrfin, var, ALL_TIMES, cache=my_cache)\n", + "end = time.time()\n", + "cache_time = (end-start)\n", + "\n", + "print (\"Time taken with variable cache: \", cache_time, \"s\")\n", + "\n", + "improvement = ((no_cache_time-cache_time)/no_cache_time) * 100 \n", + "print (\"The cache decreased computation time by: \", improvement, \"%\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Using the cache argument with OpenMP" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from __future__ import print_function\n", + "\n", + "import time\n", + "from netCDF4 import Dataset\n", + "from wrf import getvar, ALL_TIMES, extract_vars, omp_set_num_threads, omp_get_num_procs\n", + "\n", + "wrf_filenames = [\"/Users/ladwig/Documents/wrf_files/wrf_vortex_multi/wrfout_d02_2005-08-28_00:00:00\",\n", + " \"/Users/ladwig/Documents/wrf_files/wrf_vortex_multi/wrfout_d02_2005-08-28_12:00:00\", \n", + " \"/Users/ladwig/Documents/wrf_files/wrf_vortex_multi/wrfout_d02_2005-08-29_00:00:00\"]\n", + "\n", + "wrfin = [Dataset(x) for x in wrf_filenames]\n", + "\n", + "start = time.time()\n", + "my_cache = extract_vars(wrfin, ALL_TIMES, (\"P\", \"PSFC\", \"PB\", \"PH\", \"PHB\", \"T\", \"QVAPOR\", \n", + " \"HGT\", \"U\", \"V\", \"W\"))\n", + "end = time.time()\n", + "print (\"Time taken to build cache: \", (end-start), \"s\")\n", + "\n", + "omp_set_num_threads(omp_get_num_procs())\n", + "\n", + "vars = (\"avo\", \"eth\", \"cape_2d\", \"cape_3d\", \"ctt\", \"dbz\", \"mdbz\", \n", + " \"geopt\", \"helicity\", \"lat\", \"lon\", \"omg\", \"p\", \"pressure\", \n", + " \"pvo\", \"pw\", \"rh2\", \"rh\", \"slp\", \"ter\", \"td2\", \"td\", \"tc\", \n", + " \"theta\", \"tk\", \"tv\", \"twb\", \"updraft_helicity\", \"ua\", \"va\", \n", + " \"wa\", \"uvmet10\", \"uvmet\", \"z\", \"cfrac\", \"zstag\", \"geopt_stag\")\n", + "\n", + "start = time.time()\n", + "for var in vars:\n", + " v = getvar(wrfin, var, ALL_TIMES)\n", + "end = time.time()\n", + "no_cache_time = (end-start)\n", + "\n", + "print (\"Time taken without variable cache: \", no_cache_time, \"s\")\n", + "\n", + "start = time.time()\n", + "for var in vars:\n", + " v = getvar(wrfin, var, ALL_TIMES, cache=my_cache)\n", + "end = time.time()\n", + "cache_time = (end-start)\n", + "\n", + "print (\"Time taken with variable cache: \", cache_time, \"s\")\n", + "\n", + "improvement = ((no_cache_time-cache_time)/no_cache_time) * 100 \n", + "print (\"The cache decreased computation time by: \", improvement, \"%\")\n", + "\n", + "omp_set_num_threads(1)" + ] } ], "metadata": { @@ -1119,7 +1275,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", - "version": "2.7.12" + "version": "2.7.13" } }, "nbformat": 4, diff --git a/test/ipynb/WRF_python_demo.ipynb b/test/ipynb/WRF_python_demo.ipynb index 8df9d49..977aaed 100644 --- a/test/ipynb/WRF_python_demo.ipynb +++ b/test/ipynb/WRF_python_demo.ipynb @@ -2,10 +2,7 @@ "cells": [ { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "# 1.0 Basic Variable Extraction" ] @@ -13,11 +10,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "from __future__ import (absolute_import, division, print_function, unicode_literals)\n", @@ -32,9 +25,6 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false, - "deletable": true, - "editable": true, "scrolled": false }, "outputs": [], @@ -45,10 +35,7 @@ }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "### 1.0.1 DataArray attributes: 'dims', 'coords', 'attrs'" ] @@ -56,11 +43,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "print(\"dims: \", p.dims)\n", @@ -71,10 +54,7 @@ }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "### 1.0.2 Removing implicit 'squeeze' behavior to preserve single sized dimensions" ] @@ -82,11 +62,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "p_nosqueeze = getvar(ncfile, \"P\", timeidx=0, squeeze=False)\n", @@ -95,10 +71,7 @@ }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "### 1.0.3 Single element metadata" ] @@ -106,11 +79,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "print (p_nosqueeze[0,0,100,200])\n", @@ -119,10 +88,7 @@ }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "### 1.0.4 Disabling/Enabling xarray" ] @@ -130,11 +96,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "from wrf import disable_xarray, enable_xarray\n", @@ -157,20 +119,14 @@ }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "# 2.0 Sequences of Input Files " ] }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "## 2.0.1 Combining via the 'cat' method" ] @@ -178,11 +134,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "from wrf import ALL_TIMES\n", @@ -196,10 +148,7 @@ }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "## 2.0.2 Combining via the 'join' method" ] @@ -207,11 +156,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "p_join = getvar(wrflist, \"P\", timeidx=ALL_TIMES, method=\"join\")\n", @@ -220,10 +165,7 @@ }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "Note how the Time dimension was replaced with the file dimension, due to the 'squeezing' of the Time dimension.\n", "\n", @@ -234,11 +176,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "from wrf import ALL_TIMES\n", @@ -249,10 +187,7 @@ }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "## 2.0.3 Dictionary Sequences" ] @@ -260,11 +195,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "wrf_dict = {\"label1\" : [ncfile, ncfile],\n", @@ -276,10 +207,7 @@ }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "## 2.0.4 Generator Sequences" ] @@ -287,11 +215,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "def gen_seq():\n", @@ -306,10 +230,7 @@ }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "## 2.0.5 Custom Iterable Classes" ] @@ -317,11 +238,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "class FileGen(object):\n", @@ -334,7 +251,7 @@ " return self\n", " \n", " def next(self):\n", - " if self._i >= self._total:\n", + " if self._i > self._total:\n", " raise StopIteration\n", " else:\n", " val = self.ncfile[self._i]\n", @@ -347,7 +264,7 @@ "\n", "obj_gen = FileGen(ncfile, 3)\n", "\n", - "p_obj_gen = getvar(gen_seq(), \"P\", method=\"join\", squeeze=False)\n", + "p_obj_gen = getvar(obj_gen, \"P\", method=\"join\", squeeze=False)\n", "print(p_obj_gen)\n", "\n", "del p_obj_gen\n", @@ -356,10 +273,7 @@ }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "# 3.0 WRF Variable Computational Routines" ] @@ -367,11 +281,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "wrf_vars = [\"avo\", \"eth\", \"cape_2d\", \"cape_3d\", \"ctt\", \"dbz\", \"mdbz\", \n", @@ -390,10 +300,7 @@ }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "(Note all of the NaNs in the above routines which produce missing values (e.g. cape_2d). xarray always converts all masked_array missing values to NaN in order to work with pandas. To get back the original missing values in a numpy masked_array, you need to use the 'to_np' method from wrf.)" ] @@ -401,11 +308,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "from wrf import to_np\n", @@ -417,11 +320,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "keys = [x for x in vard.keys()]\n", @@ -431,20 +330,14 @@ }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "## 3.1 Interpolation Routines" ] }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "### 3.1.1 Horizontal Level Interpolation" ] @@ -452,11 +345,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "# 500 MB Heights\n", @@ -472,10 +361,7 @@ }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "### 3.1.2 Vertical Cross Section Interpolation" ] @@ -483,11 +369,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "# Pressure using pivot and angle\n", @@ -515,11 +397,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "# Pressure using pivot and angle\n", @@ -570,11 +448,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "# Pressure using pivot and angle\n", @@ -626,10 +500,7 @@ }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "### 3.1.3 Interpolate 2D Variable to a Line" ] @@ -637,11 +508,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "# T2 using pivot and angle\n", @@ -685,10 +552,7 @@ }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "### 3.1.4 Vertical Coordinate Interpolation" ] @@ -696,11 +560,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "from wrf import vinterp, getvar\n", @@ -782,10 +642,7 @@ }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "## 3.2 Lat/Lon to X/Y Routines" ] @@ -793,11 +650,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "from wrf.latlon import xy_to_ll, ll_to_xy \n", @@ -835,9 +688,7 @@ { "cell_type": "markdown", "metadata": { - "collapsed": true, - "deletable": true, - "editable": true + "collapsed": true }, "source": [ "# 4.0 Plotting with Cartopy" @@ -846,11 +697,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "%matplotlib inline" @@ -859,11 +706,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "# SLP\n", @@ -905,11 +748,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "# SLP\n", @@ -986,11 +825,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "# 500 MB Heights and Winds\n", @@ -1043,9 +878,6 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false, - "deletable": true, - "editable": true, "scrolled": false }, "outputs": [], @@ -1075,11 +907,7 @@ }, { "cell_type": "markdown", - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "# Multi-time Moving Domain Files" ] @@ -1087,11 +915,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "import os\n", @@ -1111,11 +935,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "p = getvar(ncfiles, \"ctt\", timeidx=ALL_TIMES)" @@ -1124,11 +944,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "print (p)\n", @@ -1138,11 +954,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "print (p.attrs[\"projection\"])\n" @@ -1151,11 +963,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "ncfiles[2].variables[\"XTIME\"][:]\n" @@ -1164,11 +972,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "p = getvar(ncfiles, \"P\", timeidx=None, method=\"cat\", meta=True, squeeze=True)\n" @@ -1177,11 +981,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "print (p)" @@ -1190,11 +990,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "print (type(p.coords[\"Time\"]))" @@ -1203,11 +999,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "import datetime\n", @@ -1220,11 +1012,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "wrf_vars = [\"avo\", \"eth\", \"cape_2d\", \"cape_3d\", \"ctt\", \"dbz\", \"mdbz\", \n", @@ -1249,11 +1037,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "import os\n", @@ -1290,11 +1074,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "import os\n", @@ -1335,11 +1115,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "from wrf import getvar\n" @@ -1348,22 +1124,14 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "from wrf.latlon import xy_to_ll, ll_to_xy \n", @@ -1390,11 +1158,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "from glob import glob\n", @@ -1435,11 +1199,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [] } @@ -1460,9 +1220,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", - "version": "2.7.12" + "version": "2.7.13" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/test/listBug.ncl b/test/listBug.ncl index 49b81f0..8e9745a 100644 --- a/test/listBug.ncl +++ b/test/listBug.ncl @@ -1,6 +1,18 @@ +; Bug1: This segfaults l = NewList("fifo") name = "foo" ListAppend(l, (/name/)) print(l) print(l[0]) name = "bar" + +; Bug2 Variables disappear +a = addfile("/Users/ladwig/Documents/wrf_files/wrfout_d02_2010-06-13_21:00:00.nc", "r") +b := wrf_user_getvar(a, "slp", -1) +c = NewList("fifo") +ListAppend(c, (/b/)) +b := wrf_user_getvar(a, "rh", -1) +ListAppend(c, (/b/)) + +print(c[0]) +print(c[1]) ; Variables start disappearing diff --git a/test/mocktest.py b/test/mocktest.py new file mode 100644 index 0000000..211936f --- /dev/null +++ b/test/mocktest.py @@ -0,0 +1,44 @@ +import sys +import os + +try: + from unittest.mock import MagicMock +except ImportError: + from mock import Mock as MagicMock + +class Mock(MagicMock): + @classmethod + def __getattr__(cls, name): + return Mock() + +MOCK_MODULES = ["numpy", "numpy.ma", "xarray", "cartopy", + "pandas", "matplotlib", "netCDF4", "mpl_toolkits.basemap", + "wrf._wrffortran"] +sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES) + +consts = {"DEFAULT_FILL" : 9.9692099683868690E36, + "DEFAULT_FILL_INT8" : -127, + "DEFAULT_FILL_INT16" : -32767, + "DEFAULT_FILL_INT32" : -2147483647, + "DEFAULT_FILL_INT64" : -9223372036854775806, + "DEFAULT_FILL_FLOAT" : 9.9692099683868690E36, + "DEFAULT_FILL_DOUBLE" : 9.9692099683868690E36, + "fomp_sched_static" : 1, + "fomp_sched_dynamic" : 2, + "fomp_sched_guided" : 3, + "fomp_sched_auto" : 4} + +class MockWrfConstants(object): + def __init__(self): + self.__dict__ = consts + +def mock_asscalar(val): + return float(val) + +sys.modules["wrf._wrffortran"].wrf_constants = MockWrfConstants() +sys.modules["wrf._wrffortran"].omp_constants = MockWrfConstants() + +sys.modules["numpy"].asscalar = mock_asscalar + +import wrf +print (wrf.get_coord_pairs.__doc__) diff --git a/test/test_filevars.py b/test/test_filevars.py new file mode 100644 index 0000000..dc816c3 --- /dev/null +++ b/test/test_filevars.py @@ -0,0 +1,77 @@ +import unittest as ut +import numpy.testing as nt +import numpy as np +import numpy.ma as ma +import os, sys +import subprocess + +from wrf import getvar, ALL_TIMES + +TEST_DIR = "/Users/ladwig/Documents/wrf_files/wrf_vortex_multi" +TEST_FILENAMES = ["wrfout_d02_2005-08-28_00:00:00", + "wrfout_d02_2005-08-28_12:00:00", + "wrfout_d02_2005-08-29_00:00:00"] +TEST_FILES = [os.path.join(TEST_DIR, x) for x in TEST_FILENAMES] + +# Python 3 +if sys.version_info > (3,): + xrange = range + + +class WRFFileVarsTest(ut.TestCase): + longMessage = True + +def make_test(ncfiles, varname): + def test(self): + #import time + #very_start = time.time() + #start = time.time() + t1 = getvar(ncfiles, varname, 0) + + #end = time.time() + #print ("t1: ", start-end) + #start = time.time() + t2 = getvar(ncfiles, varname, 0, meta=False) + #end = time.time() + #print ("t2: ", start-end) + #start = time.time() + t3 = getvar(ncfiles, varname, ALL_TIMES) + #end = time.time() + #print ("t3: ", start-end) + #start = time.time() + t4 = getvar(ncfiles, varname, ALL_TIMES, meta=False) + #end = time.time() + #print ("t4: ", start-end) + #start = time.time() + t5 = getvar(ncfiles, varname, ALL_TIMES, method="join") + #end = time.time() + #print ("t5: ", start-end) + #start = time.time() + t6 = getvar(ncfiles, varname, ALL_TIMES, method="join", meta=False) + #end = time.time() + #print ("t6: ", start-end) + #start = time.time() + + #print ("Total Time: ", (end-start)) + return test + + +if __name__ == "__main__": + from netCDF4 import Dataset + ncfiles = [Dataset(x) for x in TEST_FILES] + + #import scipy.io + #ncfiles = [scipy.io.netcdf.netcdf_file(x) for x in TEST_FILES] + + file_vars = ncfiles[0].variables.keys() + + ignore_vars = [] + + for var in file_vars: + if var in ignore_vars: + continue + + test_func1 = make_test(ncfiles, var) + setattr(WRFFileVarsTest, 'test_{0}'.format(var), test_func1) + + ut.main() \ No newline at end of file diff --git a/test/test_omp.py b/test/test_omp.py new file mode 100644 index 0000000..5c621a9 --- /dev/null +++ b/test/test_omp.py @@ -0,0 +1,122 @@ +from __future__ import (absolute_import, division, print_function, + unicode_literals) + +import unittest as ut +import numpy.testing as nt + +from wrf import (omp_set_num_threads, omp_get_num_threads, + omp_get_max_threads, omp_get_thread_num, + omp_get_num_procs, omp_in_parallel, + omp_set_dynamic, omp_get_dynamic, omp_set_nested, + omp_get_nested, omp_set_schedule, + omp_get_schedule, omp_get_thread_limit, + omp_set_max_active_levels, + omp_get_max_active_levels, omp_get_level, + omp_get_ancestor_thread_num, omp_get_team_size, + omp_get_active_level, omp_in_final, + omp_init_lock, omp_init_nest_lock, + omp_destroy_lock, omp_destroy_nest_lock, + omp_set_lock, omp_set_nest_lock, + omp_unset_lock, omp_unset_nest_lock, + omp_test_lock, omp_test_nest_lock, + omp_get_wtime, omp_get_wtick, + OMP_SCHED_STATIC, OMP_SCHED_DYNAMIC, + OMP_SCHED_GUIDED, OMP_SCHED_AUTO) + + +class OmpTest(ut.TestCase): + longMessage = True + + def test_locks(self): + l = omp_init_lock() + omp_set_lock(l) + omp_unset_lock(l) + omp_test_lock(l) + omp_destroy_lock(l) + + nl = omp_init_nest_lock() + omp_set_nest_lock(nl) + omp_unset_nest_lock(nl) + omp_test_nest_lock(nl) + omp_destroy_nest_lock(nl) + + + def test_thread_set(self): + omp_set_num_threads(4) + max_threads = omp_get_max_threads() + self.assertEqual(max_threads, 4) + + num_threads = omp_get_num_threads() + self.assertEqual(num_threads, 1) # Always 1 outside of parallel region + + thread_num = omp_get_thread_num() + self.assertEqual(thread_num, 0) # Always 0 outside of parallel region + num_procs = omp_get_num_procs() + in_parallel = omp_in_parallel() + self.assertFalse(in_parallel) # Always False outside of parallel region + + limit = omp_get_thread_limit() + + + def test_dynamic(self): + omp_set_dynamic(True) + dynamic = omp_get_dynamic() + self.assertTrue(dynamic) + + omp_set_dynamic(False) + dynamic = omp_get_dynamic() + self.assertFalse(dynamic) + + def test_nested(self): + omp_set_nested(True) + nested = omp_get_nested() + self.assertTrue(nested) + + omp_set_nested(False) + nested = omp_get_nested() + self.assertFalse(nested) + + + def test_schedule(self): + omp_set_schedule(OMP_SCHED_STATIC, 100000) + kind, modifier = omp_get_schedule() + self.assertEqual(kind, OMP_SCHED_STATIC) + self.assertEqual(modifier, 100000) + + omp_set_schedule(OMP_SCHED_DYNAMIC, 10000) + kind, modifier = omp_get_schedule() + self.assertEqual(kind, OMP_SCHED_DYNAMIC) + self.assertEqual(modifier, 10000) + + omp_set_schedule(OMP_SCHED_GUIDED, 100) + kind, modifier = omp_get_schedule() + self.assertEqual(kind, OMP_SCHED_GUIDED) + self.assertEqual(modifier, 100) + + omp_set_schedule(OMP_SCHED_AUTO, 10) + kind, modifier = omp_get_schedule() + self.assertEqual(kind, OMP_SCHED_AUTO) + self.assertNotEqual(modifier, 10) # The modifier argument is ignored, + # so it will be set to the previous + # value of 100. + + + def test_team_level(self): + omp_set_max_active_levels(10) + active_levels = omp_get_max_active_levels() + self.assertEqual(active_levels, 10) + + level = omp_get_level() + ancestor_thread = omp_get_ancestor_thread_num(level) + team_size = omp_get_team_size(level) + active_level = omp_get_active_level() + in_final = omp_in_final() + + + def test_time(self): + wtime = omp_get_wtime() + wtick = omp_get_wtick() + +if __name__ == "__main__": + ut.main() + \ No newline at end of file diff --git a/test/utests.py b/test/utests.py index 234239a..4962ef5 100644 --- a/test/utests.py +++ b/test/utests.py @@ -8,7 +8,7 @@ import subprocess from wrf import (getvar, interplevel, interpline, vertcross, vinterp, disable_xarray, xarray_enabled, to_np, xy_to_ll, ll_to_xy, xy_to_ll_proj, ll_to_xy_proj, - extract_global_attrs, viewitems, CoordPair) + extract_global_attrs, viewitems, CoordPair, ll_points) from wrf.util import is_multi_file NCL_EXE = "/Users/ladwig/nclbuild/6.3.0/bin/ncl" @@ -93,57 +93,71 @@ def make_test(varname, wrf_in, referent, multi=False, repeat=3, pynio=False): multiproduct = varname in ("uvmet", "uvmet10", "cape_2d", "cape_3d", "cfrac") + # These varnames don't have NCL functions to test against + ignore_referent = ("zstag", "geopt_stag") - if not multi: - ref_vals = refnc.variables[varname][:] - else: - data = refnc.variables[varname][:] - if (varname != "uvmet" and varname != "uvmet10" - and varname != "cape_2d" and varname != "cape_3d"): - new_dims = [repeat] + [x for x in data.shape] - elif (varname == "uvmet" or varname == "uvmet10" - or varname == "cape_3d"): - new_dims = [2] + [repeat] + [x for x in data.shape[1:]] - elif (varname == "cape_2d"): - new_dims = [4] + [repeat] + [x for x in data.shape[1:]] - elif (varname == "cfrac"): - new_dims = [3] + [repeat] + [x for x in data.shape[1:]] - - - masked=False - if (isinstance(data, ma.core.MaskedArray)): - masked=True - - if not masked: - ref_vals = np.zeros(new_dims, data.dtype) + if varname not in ignore_referent: + if not multi: + ref_vals = refnc.variables[varname][:] else: - ref_vals = ma.asarray(np.zeros(new_dims, data.dtype)) - - for i in xrange(repeat): - if not multiproduct: - ref_vals[i,:] = data[:] + data = refnc.variables[varname][:] + if (varname != "uvmet" and varname != "uvmet10" + and varname != "cape_2d" and varname != "cape_3d"): + new_dims = [repeat] + [x for x in data.shape] + elif (varname == "uvmet" or varname == "uvmet10" + or varname == "cape_3d"): + new_dims = [2] + [repeat] + [x for x in data.shape[1:]] + elif (varname == "cape_2d"): + new_dims = [4] + [repeat] + [x for x in data.shape[1:]] + elif (varname == "cfrac"): + new_dims = [3] + [repeat] + [x for x in data.shape[1:]] + + + masked=False + if (isinstance(data, ma.core.MaskedArray)): + masked=True - if masked: - ref_vals.mask[i,:] = data.mask[:] - + if not masked: + ref_vals = np.zeros(new_dims, data.dtype) else: - for prod in xrange(ref_vals.shape[0]): - ref_vals[prod,i,:] = data[prod,:] - + ref_vals = ma.asarray(np.zeros(new_dims, data.dtype)) + + for i in xrange(repeat): + if not multiproduct: + ref_vals[i,:] = data[:] + if masked: - ref_vals.mask[prod,i,:] = data.mask[prod,:] + ref_vals.mask[i,:] = data.mask[:] + + else: + for prod in xrange(ref_vals.shape[0]): + ref_vals[prod,i,:] = data[prod,:] + + if masked: + ref_vals.mask[prod,i,:] = data.mask[prod,:] if (varname == "tc"): my_vals = getvar(in_wrfnc, "temp", timeidx=timeidx, units="c") tol = 1/100. atol = .1 # Note: NCL uses 273.16 as conversion for some reason nt.assert_allclose(to_np(my_vals), ref_vals, tol, atol) + elif (varname == "cfrac"): + # Change the vert_type to height_agl when NCL gets updated. + my_vals = getvar(in_wrfnc, "cfrac", timeidx=timeidx, + vert_type="pres") + tol = 1/100. + atol = .1 # Note: NCL uses 273.16 as conversion for some reason + nt.assert_allclose(to_np(my_vals), ref_vals, tol, atol) elif (varname == "pw"): my_vals = getvar(in_wrfnc, "pw", timeidx=timeidx) tol = .5/100.0 atol = 0 # NCL uses different constants and doesn't use same # handrolled virtual temp in method - nt.assert_allclose(to_np(my_vals), ref_vals, tol, atol) + try: + nt.assert_allclose(to_np(my_vals), ref_vals, tol, atol) + except AssertionError: + print (np.amax(np.abs(to_np(my_vals) - ref_vals))) + raise elif (varname == "cape_2d"): cape_2d = getvar(in_wrfnc, varname, timeidx=timeidx) tol = 0/100. @@ -165,12 +179,20 @@ def make_test(varname, wrf_in, referent, multi=False, repeat=3, pynio=False): #print np.amax(np.abs(to_np(cape_3d[0,:]) - ref_vals[0,:])) nt.assert_allclose(to_np(cape_3d), ref_vals, tol, atol) + elif (varname == "zstag" or varname == "geopt_stag"): + v = getvar(in_wrfnc, varname, timeidx=timeidx) + # For now, only make sure it runs without crashing since no NCL + # to compare with yet. else: my_vals = getvar(in_wrfnc, varname, timeidx=timeidx) tol = 2/100. atol = 0.1 #print (np.amax(np.abs(to_np(my_vals) - ref_vals))) - nt.assert_allclose(to_np(my_vals), ref_vals, tol, atol) + try: + nt.assert_allclose(to_np(my_vals), ref_vals, tol, atol) + except: + print (np.amax(np.abs(to_np(my_vals) - ref_vals))) + raise return test @@ -247,6 +269,9 @@ def make_interp_test(varname, wrf_in, referent, multi=False, ref_ht_500 = _get_refvals(referent, "z_500", repeat, multi) hts = getvar(in_wrfnc, "z", timeidx=timeidx) p = getvar(in_wrfnc, "pressure", timeidx=timeidx) + + # Make sure the numpy versions work first + hts_500 = interplevel(to_np(hts), to_np(p), 500) hts_500 = interplevel(hts, p, 500) nt.assert_allclose(to_np(hts_500), ref_ht_500) @@ -259,23 +284,48 @@ def make_interp_test(varname, wrf_in, referent, multi=False, p = getvar(in_wrfnc, "pressure", timeidx=timeidx) pivot_point = CoordPair(hts.shape[-1] / 2, hts.shape[-2] / 2) + #ht_cross = vertcross(to_np(hts), p, pivot_point=pivot_point, + # angle=90., latlon=True) + # Make sure the numpy versions work first + ht_cross = vertcross(to_np(hts), to_np(p), + pivot_point=pivot_point, angle=90.) ht_cross = vertcross(hts, p, pivot_point=pivot_point, angle=90.) - - nt.assert_allclose(to_np(ht_cross), ref_ht_cross, rtol=.01) + + # Note: Until the bug is fixed in NCL, the wrf-python cross + # sections will contain one extra point + nt.assert_allclose(to_np(ht_cross)[...,0:-1], ref_ht_cross, + rtol=.01) + + # Test the manual projection method with lat/lon + lats = hts.coords["XLAT"] + lons = hts.coords["XLONG"] + ll_point = ll_points(lats, lons) + pivot = CoordPair(lat=lats[int(lats.shape[-2]/2), + int(lats.shape[-1]/2)], + lon=lons[int(lons.shape[-2]/2), + int(lons.shape[-1]/2)]) + v1 = vertcross(hts,p,wrfin=in_wrfnc,pivot_point=pivot_point, + angle=90.0) + v2 = vertcross(hts,p,projection=hts.attrs["projection"], + ll_point=ll_point, + pivot_point=pivot_point, angle=90.) + + nt.assert_allclose(to_np(v1), to_np(v2), rtol=.01) # Test opposite p_cross1 = vertcross(p,hts,pivot_point=pivot_point, angle=90.0) - - nt.assert_allclose(to_np(p_cross1), + + nt.assert_allclose(to_np(p_cross1)[...,0:-1], ref_p_cross, rtol=.01) # Test point to point start_point = CoordPair(0, hts.shape[-2]/2) end_point = CoordPair(-1,hts.shape[-2]/2) - + + p_cross2 = vertcross(p,hts,start_point=start_point, end_point=end_point) - + nt.assert_allclose(to_np(p_cross1), to_np(p_cross2)) @@ -286,9 +336,30 @@ def make_interp_test(varname, wrf_in, referent, multi=False, t2 = getvar(in_wrfnc, "T2", timeidx=timeidx) pivot_point = CoordPair(t2.shape[-1] / 2, t2.shape[-2] / 2) + #t2_line1 = interpline(to_np(t2), pivot_point=pivot_point, + # angle=90.0, latlon=True) + # Make sure the numpy version works + t2_line1 = interpline(to_np(t2), pivot_point=pivot_point, + angle=90.0) t2_line1 = interpline(t2, pivot_point=pivot_point, angle=90.0) - nt.assert_allclose(to_np(t2_line1), ref_t2_line) + # Note: After NCL is fixed, remove the slice. + nt.assert_allclose(to_np(t2_line1)[...,0:-1], ref_t2_line) + + # Test the manual projection method with lat/lon + lats = t2.coords["XLAT"] + lons = t2.coords["XLONG"] + ll_point = ll_points(lats, lons) + pivot = CoordPair(lat=lats[int(lats.shape[-2]/2), + int(lats.shape[-1]/2)], + lon=lons[int(lons.shape[-2]/2), + int(lons.shape[-1]/2)]) + l1 = interpline(t2,wrfin=in_wrfnc,pivot_point=pivot_point, + angle=90.0) + l2 = interpline(t2,projection=t2.attrs["projection"], + ll_point=ll_point, + pivot_point=pivot_point, angle=90.) + nt.assert_allclose(to_np(l1), to_np(l2), rtol=.01) # Test point to point start_point = CoordPair(0, t2.shape[-2]/2) @@ -307,6 +378,16 @@ def make_interp_test(varname, wrf_in, referent, multi=False, interp_levels = [200,300,500,1000] + # Make sure the numpy version works + field = vinterp(in_wrfnc, + field=to_np(tk), + vert_coord="theta", + interp_levels=interp_levels, + extrapolate=True, + field_type="tk", + timeidx=timeidx, + log_p=True) + field = vinterp(in_wrfnc, field=tk, vert_coord="theta", @@ -599,12 +680,18 @@ class WRFLatLonTest(ut.TestCase): if __name__ == "__main__": + from wrf import (omp_set_num_threads, omp_set_schedule, omp_get_schedule, + omp_set_dynamic, omp_get_num_procs, OMP_SCHED_STATIC) + omp_set_num_threads(omp_get_num_procs()-1) + omp_set_schedule(OMP_SCHED_STATIC, 0) + omp_set_dynamic(False) + ignore_vars = [] # Not testable yet wrf_vars = ["avo", "eth", "cape_2d", "cape_3d", "ctt", "dbz", "mdbz", "geopt", "helicity", "lat", "lon", "omg", "p", "pressure", "pvo", "pw", "rh2", "rh", "slp", "ter", "td2", "td", "tc", "theta", "tk", "tv", "twb", "updraft_helicity", "ua", "va", - "wa", "uvmet10", "uvmet", "z", "cfrac"] + "wa", "uvmet10", "uvmet", "z", "cfrac", "zstag", "geopt_stag"] interp_methods = ["interplevel", "vertcross", "interpline", "vinterp"] latlon_tests = ["xy", "ll"] diff --git a/test/varcache.py b/test/varcache.py new file mode 100644 index 0000000..1e0fe3d --- /dev/null +++ b/test/varcache.py @@ -0,0 +1,36 @@ +from __future__ import print_function + +import time +from netCDF4 import Dataset +from wrf import getvar, ALL_TIMES, extract_vars + +wrf_filenames = ["/Users/ladwig/Documents/wrf_files/wrf_vortex_multi/wrfout_d02_2005-08-28_00:00:00", + "/Users/ladwig/Documents/wrf_files/wrf_vortex_multi/wrfout_d02_2005-08-28_12:00:00", + "/Users/ladwig/Documents/wrf_files/wrf_vortex_multi/wrfout_d02_2005-08-29_00:00:00"] + +wrfin = [Dataset(x) for x in wrf_filenames] + +my_cache = extract_vars(wrfin, ALL_TIMES, ("P", "PB", "PH", "PHB", "T", "QVAPOR", "HGT", "U", "V", "W", "PSFC")) + +start = time.time() +for var in ("avo", "eth", "cape_2d", "cape_3d", "ctt", "dbz", "mdbz", + "geopt", "helicity", "lat", "lon", "omg", "p", "pressure", + "pvo", "pw", "rh2", "rh", "slp", "ter", "td2", "td", "tc", + "theta", "tk", "tv", "twb", "updraft_helicity", "ua", "va", + "wa", "uvmet10", "uvmet", "z", "cfrac", "zstag", "geopt_stag"): + v = getvar(wrfin, var, ALL_TIMES) +end = time.time() + +print ("Time taken without variable cache: ", (end-start)) + +start = time.time() +for var in ("avo", "eth", "cape_2d", "cape_3d", "ctt", "dbz", "mdbz", + "geopt", "helicity", "lat", "lon", "omg", "p", "pressure", + "pvo", "pw", "rh2", "rh", "slp", "ter", "td2", "td", "tc", + "theta", "tk", "tv", "twb", "updraft_helicity", "ua", "va", + "wa", "uvmet10", "uvmet", "z", "cfrac", "zstag", "geopt_stag"): + v = getvar(wrfin, var, ALL_TIMES, cache=my_cache) +end = time.time() + +print ("Time taken with variable cache: ", (end-start)) +