diff --git a/.gitignore b/.gitignore index 90ab4a98b..334b86fb5 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,8 @@ cy_*.c empty_BW_image.png .DS_Store .cache/ +*.swn +*.swo *.ipynb_checkpoints/ @@ -53,6 +55,98 @@ py_gnome/junk_tiny.txt py_gnome/tests/unit_tests/test_utilities/test_file_tools/test.bna py_gnome/tests/unit_tests/test_utilities/test_file_tools/test_bad.bna +# ignore gnerated and sample data for tests + +py_gnome/tests/junk.txt +py_gnome/tests/junk_large.txt +py_gnome/tests/unit_tests/ElementType_6.json +py_gnome/tests/unit_tests/Emulsification_1.json +py_gnome/tests/unit_tests/Evaporation_0.json +py_gnome/tests/unit_tests/LongIslandSoundMap.BNA +py_gnome/tests/unit_tests/Model.json +py_gnome/tests/unit_tests/Model.zip +py_gnome/tests/unit_tests/Skim_2.json +py_gnome/tests/unit_tests/Spill_0.json +py_gnome/tests/unit_tests/Water_0.json +py_gnome/tests/unit_tests/Waves_3.json +py_gnome/tests/unit_tests/Wind_1.json +py_gnome/tests/unit_tests/Wind_1_data.WND +py_gnome/tests/unit_tests/junk.txt +py_gnome/tests/unit_tests/junk_conda_list.txt +py_gnome/tests/unit_tests/junk_large.txt +py_gnome/tests/unit_tests/test_environment/sample_data/3D_circular.nc +py_gnome/tests/unit_tests/test_environment/sample_data/currents/ +py_gnome/tests/unit_tests/test_environment/sample_data/staggered_sine_channel.nc +py_gnome/tests/unit_tests/test_environment/sample_data/tri_ring.nc +py_gnome/tests/unit_tests/test_weatherers/ElementType_6.json +py_gnome/tests/unit_tests/test_weatherers/Emulsification_1.json +py_gnome/tests/unit_tests/test_weatherers/Evaporation_0.json +py_gnome/tests/unit_tests/test_weatherers/LongIslandSoundMap.BNA +py_gnome/tests/unit_tests/test_weatherers/Model.json +py_gnome/tests/unit_tests/test_weatherers/Model.zip +py_gnome/tests/unit_tests/test_weatherers/Skim_2.json +py_gnome/tests/unit_tests/test_weatherers/Spill_0.json +py_gnome/tests/unit_tests/test_weatherers/Water_0.json +py_gnome/tests/unit_tests/test_weatherers/Waves_3.json +py_gnome/tests/unit_tests/test_weatherers/Wind_1.json +py_gnome/tests/unit_tests/test_weatherers/Wind_1_data.WND + +#ignore generated and downloaded files from scripts +py_gnome/scripts/arctic_avg2_0001_gnome.nc +py_gnome/scripts/arctic_avg2_0002_gnome.nc +py_gnome/scripts/script_animation_demo/images/ +py_gnome/scripts/script_animation_demo/raster.bmp +py_gnome/scripts/script_boston/EbbTides.cur +py_gnome/scripts/script_boston/EbbTidesShio.txt +py_gnome/scripts/script_boston/MassBayMap.bna +py_gnome/scripts/script_boston/MassBaySewage.cur +py_gnome/scripts/script_boston/MerrimackMassCoast.cur +py_gnome/scripts/script_boston/MerrimackMassCoastOSSM.txt +py_gnome/scripts/script_boston/WAC10msNW.cur +py_gnome/scripts/script_boston/WAC10msSW.cur +py_gnome/scripts/script_boston/gnome_profile +py_gnome/scripts/script_boston/script_boston.kmz +py_gnome/scripts/script_boston/script_boston.nc +py_gnome/scripts/script_boston/script_boston_uncertain.nc +py_gnome/scripts/script_chesapeake_bay/ChesapeakeBay.bna +py_gnome/scripts/script_chesapeake_bay/ChesapeakeBay.dat +py_gnome/scripts/script_chesapeake_bay/ChesapeakeBay.nc +py_gnome/scripts/script_chesapeake_bay/script_chesapeake_bay.nc +py_gnome/scripts/script_chesapeake_bay/script_chesapeake_bay_uncertain.nc +py_gnome/scripts/script_columbia_river/COOPSu_CREOFS24.nc +py_gnome/scripts/script_columbia_river/COOPSu_CREOFS24.nc.dat +py_gnome/scripts/script_columbia_river/columbia_river.bna +py_gnome/scripts/script_delaware_bay/DelawareRiverMap.bna +py_gnome/scripts/script_delaware_bay/FloodTides.cur +py_gnome/scripts/script_delaware_bay/FloodTidesShio.txt +py_gnome/scripts/script_delaware_bay/NW30ktwinds.cur +py_gnome/scripts/script_delaware_bay/Offshore.cur +py_gnome/scripts/script_delaware_bay/SW30ktwinds.cur +py_gnome/scripts/script_delaware_bay/script_delaware_bay.nc +py_gnome/scripts/script_guam/GuamMap.bna +py_gnome/scripts/script_guam/OutsideWAC.cur +py_gnome/scripts/script_guam/WACFTideShioHts.txt +py_gnome/scripts/script_guam/WACFloodTide.cur +py_gnome/scripts/script_guam/script_guam.nc +py_gnome/scripts/script_guam_get_data_test/GuamMap.bna +py_gnome/scripts/script_guam_get_data_test/OutsideWAC.cur +py_gnome/scripts/script_guam_get_data_test/WACFTideShioHts.txt +py_gnome/scripts/script_guam_get_data_test/WACFloodTide.cur +py_gnome/scripts/script_guam_get_data_test/script_guam.nc +py_gnome/scripts/script_ice/ak_arctic.bna +py_gnome/scripts/script_ice/script_ice.nc +py_gnome/scripts/script_plume/images/ +py_gnome/scripts/script_plume/script_plume.nc +py_gnome/scripts/script_tamoc/HYCOM_3d.nc +py_gnome/scripts/script_tamoc/HYCOM_3d.nc.dat +py_gnome/scripts/script_tamoc/Input/case_01 +py_gnome/scripts/script_tamoc/KIKT.osm +py_gnome/scripts/script_tamoc/cat +py_gnome/scripts/script_tamoc/diff.txt +py_gnome/scripts/script_tamoc/gulf_tamoc.nc +py_gnome/scripts/script_tamoc/images/ + + # ignore generated oildb oil_library/oil_library/OilLib.db oil_library/temp.txt diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 724ed4f60..2acc84fc7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,22 +1,27 @@ -before_script: +before_script: - yum update -y - yum install gcc gcc-c++ -y - yum install netcdf-devel -y # needed by the C++ code -- it can't find the conda versions + - yum install libXext libSM libXrender -y # needed by matplotlib on CentOS + # make sure the conda channels are set up right # the last channel you add will be first on the list - - conda config --add channels NOAA-ORR-ERD - conda config --add channels defaults + - conda config --add channels NOAA-ORR-ERD - conda config --add channels conda-forge + + # install the requirements - conda install --file conda_requirements.txt + # get the oil libary package from gitlab - pip install -U git+https://srccontrol.orr.noaa.gov/gnome/OilLibrary.git@v0.0.9#egg=oil_library - cd ./py_gnome - python ./setup.py develop -master: +master: script: - py.test -runslow - only: + only: - master develop: diff --git a/InstallingWithAnaconda.rst b/InstallingWithAnaconda.rst index 06de802fc..b9418e900 100644 --- a/InstallingWithAnaconda.rst +++ b/InstallingWithAnaconda.rst @@ -58,7 +58,7 @@ manager that Anaconda is built on. So when working with Anaconda, you use the conda package manager for installing conda packages. ``pip`` can also be used with conda, but it's best to use use conda if you can. -We have made sure that every pacakge you need is available for conda. +We have made sure that every package you need is available for conda. Setting up .......... @@ -122,13 +122,21 @@ install a package, conda will first look in conda-forge, then NOAA-ORR-ERD, and then in the default channel. This order should work well for PyGNOME. Be sure to add the channels in the order we specify. You can see what channels you have with:: > conda config --get channels +<<<<<<< HEAD +======= It should return something like this:: --add channels 'defaults' # lowest priority --add channels 'NOAA-ORR-ERD' --add channels 'conda-forge' # highest priority +>>>>>>> develop +It should return something like this:: + + --add channels 'defaults' # lowest priority + --add channels 'conda-forge' + --add channels 'NOAA-ORR-ERD' # highest priority conda environments ------------------ @@ -170,7 +178,11 @@ and kept separate from your main Anaconda install. You will need to activate the environment any time you want to work with ``py_gnome`` in the future +<<<<<<< HEAD **NOTE:** Again, if you are only using Python / Anaconda for GNOME, there is not reason to deal with the complications of environments. +======= +**NOTE:** Again, if you are only using Python / Anaconda for GNOME, it is not necessary to deal with the complications of environments. +>>>>>>> develop Download GNOME -------------- @@ -185,9 +197,19 @@ you can "clone" the git repository. If you clone the repository, you will be able to update the code with the latest version with a simple command, rather than having to re-download the whole package. +Downloading a single release +---------------------------- + +zip and tar archives of the PyGnome source code can be found here: + +https://github.com/NOAA-ORR-ERD/PyGnome/releases + +This will get you the entire source archive of a given release, which is a fine way to work with PyGnome. However, if you want to be able to quickly include changes as we update the code, you may want to work with a git "clone" of the source code instead. + Cloning the PyGNOME git repository ---------------------------------- + git ... @@ -266,7 +288,7 @@ If you want to use py_gnome with "real oil", rather than inert particles, you wi https://github.com/NOAA-ORR-ERD/OilLibrary -This is under active development along with ``py_gnome``, so you are best off downloading the sources from gitHub and installing it from source -- similar to ``py_gnome``. +This is under active development along with ``py_gnome``, so you are best off downloading the sources from gitHub and installing it from source -- similar to ``py_gnome``. Though the lated releases of each should be compatible. cloning the repository :: @@ -308,10 +330,13 @@ Store. [you may be able to install only the command line tools -- Apple keeps changing its mind] After installing XCode, you still need to install the "Command Line -Tools". XCode includes a new "Downloads" preference pane to install +Tools". XCode includes a new "Downloads" preference pane to install optional components such as command line tools, and previous iOS Simulators. +**NOTE:** This may be slightly different on different versions of OS-X +and XCode -- google is your friend. + To install the XCode command line tools: - Start XCode from the launchpad - Click the "XCode" dropdown menu button in the top left of the screen near the Apple logo - Click "Preferences", then click @@ -398,7 +423,7 @@ cleans files generated by the build as well as files auto-generated by cython. It is a good idea to run ``cleanall`` after updating from the gitHub repo -- particularly if strange errors are occurring. -You will need to re-run "develop" or "install" after running "cleanall" +You will need to re-run ``develop`` or ``install`` after running ``cleanall`` Testing ``py_gnome`` -------------------- diff --git a/NormalInstall.rst b/NormalInstall.rst index 78899300b..881d1b475 100644 --- a/NormalInstall.rst +++ b/NormalInstall.rst @@ -1,5 +1,5 @@ -Installation in Development Mode -================================ +Installation without conda / Anaconda +===================================== Since this is development work, it might be good to create and run this in a virtual environment. `Virtualenv `__ and `Virtual envwrapper `__ eases @@ -12,9 +12,9 @@ may require a virtualenv in order to freely install python packages in python's site-packages area. (site-packages is the standard place where python installers will put packages after building them) -You may also want to consider using conda environments. +You may also want to consider using conda environments -- see above. -There is C++/Cython code that must be built - you will need the corect C compiler and recent setuptools, etc. +There is C++/Cython code that must be built - you will need the correct C compiler and recent setuptools, etc. See "Installing With Anaconda" for more detail. python.org ========== @@ -22,32 +22,28 @@ python.org The following has been tested against `Python 2.7 `__ -Linux (Tested in 32-bit, Ubuntu raring 13.04) ---------------------------------------------- +Linux (Tested in 64-bit, CentOS) +-------------------------------- -For Linux use appropriate package manager (apt-get on ubuntu) to +For Linux use appropriate package manager (yum on CentOS, apt-get on Ubuntu) to download/install binary dependencies. + Binary Dependencies ................... 1. setuptools is required. ``> sudo apt-get install python-setuptools`` \` \` -2. `Pillow `__ - has binary dependencies. Visit the docs to get list of dependencies - for your system. Pillow requires Python's development libraries:: + +2. To compile Python extensions, you need the development libs for Python: > sudo apt-get install python-dev -This did not build symlinks to libraries for me in /usr/lib, so had to manually create them:: +3. netCDF4 python module requires NetCDF libraries: - > sudo ln -s /usr/lib/``\ uname - -i``-linux-gnu/libfreetype.so /usr/lib/`` ``> sudo ln -s /usr/lib/``\ uname - -i``-linux-gnu/libjpeg.so /usr/lib/`` ``> sudo ln -s /usr/lib/``\ uname - -i``-linux-gnu/libz.so /usr/lib/```` \` + libhdf5-serial-dev -3. netCDF4 python module requires NetCDF libraries: libhdf5-serial-dev, libnetcdf-dev 4. The following python packages, documented in PyGnome's @@ -56,6 +52,7 @@ This did not build symlinks to libraries for me in /usr/lib, so had to manually Binaries for `Numpy `__ and + `Cython `__ can be installed using apt-get. @@ -71,7 +68,6 @@ latest packages in your virtualenv once the above dependencies are met:: > pip install numpy > pip install cython > pip install netCDF4 - > pip install Pillow The remaining dependencies are python packages and can be installed using:: @@ -129,7 +125,7 @@ installed packages:: > pip - Usage: + Usage: pip [options] Commands: @@ -185,7 +181,7 @@ Build PyGnome 4. build the ``py_gnome`` module in develop mode first as install mode may still need some testing/work. - + The other option you may need is ``cleanall``, which should clean the development environment -- good to do after puling new code from git. 5. If this successfully completes, then run the unit tests:: diff --git a/conda_requirements.txt b/conda_requirements.txt index 176544f9e..3517a0bb0 100644 --- a/conda_requirements.txt +++ b/conda_requirements.txt @@ -23,8 +23,14 @@ colander>=1.2 sqlalchemy>=0.7.6 zope.interface>=4.1 zope.sqlalchemy>=0.7.6 +<<<<<<< HEAD gdal>=2.1.1 netCDF4=1.2.7 +======= +hdf5=1.8.* +netCDF4=1.2.7 +gdal>=2.1.1 +>>>>>>> develop awesome-slugify>=1.6 regex>=2014.12 unidecode>=0.04.19 diff --git a/experiments/Spreading/ChrisSpreading.ipynb b/experiments/Spreading/ChrisSpreading.ipynb index 4e5a610a2..1235c2596 100644 --- a/experiments/Spreading/ChrisSpreading.ipynb +++ b/experiments/Spreading/ChrisSpreading.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 229, + "execution_count": 1, "metadata": { "collapsed": false }, @@ -13,44 +13,91 @@ "%matplotlib inline" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Initial Area\n", + "\n", + "We want the initial area to be the end of Fay's Gravity-inertial phase.\n", + "\n", + "From Dodge(1983):\n", + "\n", + "$A_0 = \\pi \\frac{{K_2}^4}{{K_1}^2}\\left(\\frac{g \\cdot \\Delta \\rho \\cdot V_0^5 }{\\nu_w^2}\\right)^{1/6}$\n", + "\n", + "with:\n", + "\n", + "$K_1 = 1.53$ (inertial phase constant)\n", + "\n", + "$K_2 = 1.21$ (viscous phase constant)\n", + "\n", + "We can combine the various constants, and get:\n", + "\n", + "$A_0 = C_{ia} \\cdot {\\Delta \\rho}^6 \\cdot V_0^{5/6}$\n", + "\n", + "$C_{ia} = \\pi \\frac{{K_2}^4}{{K_1}^2} \\left(\\frac{g}{\\nu_w^2}\\right)^{1/6} \\approx 42.09$\n" + ] + }, { "cell_type": "code", - "execution_count": 230, + "execution_count": 3, "metadata": { - "collapsed": true + "collapsed": false }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "initial area constant: 420.878887158\n" + ] + } + ], "source": [ "# a few utilities and constants\n", "\n", "# Constants\n", - "## fixme: check where these came from -- slightly different than Fay (1971)\n", - "## Fay(1971) has 1.45 and 1.14 (I think -- nomenclature not clear)\n", - "## I'm not even sure what K1 and K2 are -- in Fay (1971) K1 is one-dimensional, and k2 is axisymetric\n", + "## These came from Dodge(1983) -- slightly different than Fay (1971)\n", + "## Fay(1971) has 1.45 for viscous, and 1.14 for gravity\n", + "## but the Dodge numbers are derived from experiments, so what the heck\n", "\n", - "K1=1.53\n", - "K2=1.21\n", + "K1=1.53 # Intertial phase\n", + "K2=1.21 # Viscous phase\n", "\n", - "\n", - "visc_w = 1e-6 # viscoscity of water: m^2/s\n", + "visc_w = 1e-6 # viscosity of water: m^2/s\n", "\n", "g = 9.806 # gravity, m/s^2\n", + "\n", "rho_water = 1025.0 # kg/m^3 (typical seawater)\n", "\n", + "rho_oil = 900 # kg/m^3 -- arbitrary number\n", + "\n", + "# Combining the contants for the initial area:\n", + "C_ia = np.pi * (K2**4 / K1**2) * (g / visc_w**2)**(1.0/6.0)\n", + "\n", + "print \"initial area constant:\", C_ia\n", + "\n", "def Delta(rho_oil):\n", + " \"\"\"difference between oil and seawater density\"\"\"\n", " return (float(rho_water) - rho_oil) / rho_water\n", "\n", "def init_area(V0, rho_oil):\n", " \"\"\"\n", " initial area -- end of Fay gravity-inertial\n", " \"\"\"\n", - " return np.pi * (K1**4/K2**2) * ((g * V0**5 * Delta(rho_oil))/visc_w**2 )**(1./6.) \n", + " return np.pi * (K2**4 / K1**2) * (g * Delta(rho_oil) * V0**5 / visc_w**2 )**(1./6.) \n", + "\n", + "def init_area2(V0, rho_oil):\n", + " \"\"\"\n", + " initial area -- end of Fay gravity-inertial with constants combined\n", + " \"\"\"\n", + " return C_ia * Delta(rho_oil)**(1.0/6.0) * V0**(5.0/6.0) \n", "\n", "def init_radius(V0, rho_oil):\n", " \"\"\"\n", " initial radius -- end of Fay gravity-inertial\n", " \"\"\"\n", - " return (K1**2/K2) * ((g * V0**5 * delta())/visc_w**2 )**(1./12.) \n", + " return (K2**2/K1) * ((g * V0**5 * delta())/visc_w**2 )**(1./12.) \n", "\n", "def min_thickness(visc_oil):\n", " if visc_oil <= 1e-6:\n", @@ -60,7 +107,6 @@ " else:\n", " return 1e-5 + (90.0 / 99.0) * (visc_oil - 1e-6)\n", "\n", - "\n", "def max_area(volume, visc_oil):\n", " return volume / min_thickness(visc_oil)\n", "\n", @@ -68,57 +114,277 @@ " \"\"\"\n", " Fay Gravity-viscous phase analytical solution\n", " \"\"\"\n", - " # fixme: why is K2 squared???\n", - " return np.pi * K2**2 * (Delta(rho_oil) * g * V0**2/np.sqrt(visc_w))**(1./3) * np.sqrt(t)\n", - "\n" + " return np.pi * K1**2 * (Delta(rho_oil) * g / np.sqrt(visc_w))**(1./3.) * V0**(2./3.) * np.sqrt(t)\n", + "\n", + "## dont need these anymore:\n", + "# def bill_area2(V0, rho_oil):\n", + "# \"\"\" from Jan 2017 spreading doc\n", + "# this constant is wrong, but I can see where it came from. the actual value is:\n", + "# 420.87 -- so off by a factor of 100 (units?)\n", + "# \"\"\"\n", + "# return 4.2 * (Delta(rho_oil) * V0**5)**(1.0 / 6.0)\n", + " \n", + "# def bill_radius2(V0, rho_oil):\n", + "# np.sqrt(bill_area2(V0, rho_oil) / np.pi)\n", + "\n", + "# def bill_area1(V0, rho_oil):\n", + "# \"\"\" from May 2016 spreading doc\n", + "# wrong constant -- no idea where it came from\n", + "# \"\"\"\n", + "# return 16 * ((Delta(rho_oil) * g * V0**5) / visc_w**2)**(1.0 / 6.0)\n", + "\n", + " " ] }, { "cell_type": "code", - "execution_count": 239, + "execution_count": 7, "metadata": { "collapsed": false }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\nbW1tjBkzBoAzzjiDL3zhC71S10qodH0nT57MD3/4w4ptz6zZtLVVdnsOTA1g0qRJ3Hvvve2ZY6tX\nr2bDhg3Mnz9/s7Lly5czadKkWlbVzBpMWxscdVRlt+nA1AAOPfRQXnrpJRYsWADAnDlzmDx5Mnvv\nvfdmZWPHjmXEiBEA9OvXj0cffZQrr7ySa6+9losvvpjBgwdzwgkntG/3T3/6EwcddBBDhw5lypQp\nvPTSSyXrcOWVV7LffvsxePBgDjjggPb9LlmyhMmTJzN06FAOPPBAfvGLX7Svc8YZZzB16lSOP/54\nBg8ezOGHH85f//rX9tfPPfdchg8fzg477MDBBx/MokWLStb3oosuYs8992zf/80339y+nauvvpqj\njjqKT33qUwwbNoyxY8dy++23A/C5z32Ou+++m6lTpzJ48GA+8YlP9Oi9MGs2Dz8MCxdWeKO1npG2\n3n6yU7KlUuU5zz8fce+92e/u6On6kydPjksvvTQiIqZOnRqzZs2Kz33uc5uVnXnmme3L9+vXL5Yv\nXx4REaeffnp8/vOf32x7Y8aMiQkTJsRTTz0Va9eujX333Td+8IMfFN337NmzY9SoUfHHP/4xIiKW\nL18eK1asiPXr18eee+4ZX//612P9+vVx5513xqBBg2Lp0qXt+91xxx1j3rx58fLLL8cpp5wSU6ZM\niYiI22+/PQ455JB4Pp2QJUuWxFNPPVWyvj/96U/bX589e3Zsv/327c+vuuqq2HrrrWPmzJmxcePG\nmDFjRuyyyy7t67a0tMTMmTNLntvO3nuzZvb88xEHHdT+d1KR72G3mCog15SdNCn73dX+1p6uD3D0\n0UczZ84cAO6++26OOuoojjzyyM3Kjj766Pblo4wLRs8555z2Fsvb3va29lZQoZkzZ/LpT3+a8ePH\nA7DHHnuw6667cv/99/PCCy/wmc98hv79+zN58mSOP/54rrvuuvZ13/nOd/La176Wfv36ccopp7Tv\nY8CAAbS1tbFo0SIigr333pvhw4eXrOu73vWu9tdPOukkxo0bx9y5c9tfHz16NB/84AeRxGmnncbf\n/vY3nn666C25zKyItja4774tv58GDYK7767svhyYKiDXlN2wARYt6nqztqfrQzbOdM8997Bu3Tqe\nffZZxo4dy+te9zruvfde1q1bx8MPP9zl8aX8QLDddtvxj3/8o+hyK1euZOzYsVuUP/nkk+y6666b\n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\nqyv/HwfHEP1g0TmhAAAAAElFTkSuQmCC\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ - "# Simple Euler method integration for area as a function of area\n", - "def Euler(f, T, A_0):\n", - " dt = T[1] - T[0] # assume it's the same...\n", - " A = [A_0]\n", - " for t in enumerate(T[1:]):\n", - " A.append(A[-1] + f(A[-1]) * dt)\n", - " return np.array(A)\n", - "\n" + "# plot initial area\n", + "V0 = np.linspace(1,1000) # volume in m^3\n", + "fig, ax = plt.subplots()\n", + "ax.plot(V0, init_area2(V0, rho_oil), \".\", label=\"With constant\")\n", + "#ax.set_xlim(1e-7, 1e-3)\n", + "#ax.set_ylim(0, 110)\n", + "ax.set_title(\"Initial area as a function of Volume\")\n", + "ax.set_xlabel(\"Oil Volume ($m^3$)\")\n", + "ax.set_ylabel(\"Initial Area ($m^2$)\")\n", + "ax.legend(loc='upper left')" ] }, { "cell_type": "code", - "execution_count": 240, + "execution_count": 12, "metadata": { "collapsed": false }, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/chris.barker/miniconda2/envs/gnome/lib/python2.7/site-packages/ipykernel/__main__.py:38: RuntimeWarning: invalid value encountered in power\n" + ] + }, { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 240, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" }, { "data": { - "image/png": "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\ngg==\n", + "image/png": "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\nTkSuQmCC\n", "text/plain": [ - "" + "" ] }, "metadata": {}, "output_type": "display_data" } ], + "source": [ + "# plot initial area as a function of oil density\n", + "# V0 = np.linspace(1000) # volume in m^3\n", + "V0 = 1000 # volume in m^3\n", + "rho_oil = np.linspace(800, 1200) # density in kg/m^3\n", + "fig, ax = plt.subplots()\n", + "ax.plot(rho_oil, init_area2(V0, rho_oil))\n", + "#ax.set_xlim(1e-7, 1e-3)\n", + "#ax.set_ylim(0, 110)\n", + "ax.set_title(\"Initial area as a function of Density\")\n", + "ax.set_xlabel(\"Oil Density ($kg/m^3$)\")\n", + "ax.set_ylabel(\"Initial Area ($m^2$)\")\n", + "# ax.legend(loc='upper right')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Intial area -- matching Inertial and Viscous phases\n", + "\n", + "from Fay (1971):\n", + "\n", + "$r_i = K_i (\\Delta g V t^2)^{1/4}$\n", + "\n", + "$r_v = K_v ( \\frac{ \\Delta g V t^{3/2}}{\\nu_w^{1/2}})^{1/6}$\n", + "\n", + "where:\n", + "\n", + "$ K_i=1.14 $\n", + "\n", + "$ K_v=1.45 $\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'a_v3' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 29\u001b[0m \u001b[0;31m#ax.plot(t, r_i(Vol, rho_oil, t), label=\"Inertial\")\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 30\u001b[0m \u001b[0max\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mplot\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mt\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0ma_v\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mVol\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mrho_oil\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mt\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlabel\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m\"Area2\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 31\u001b[0;31m \u001b[0max\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mplot\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mt\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0ma_v3\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mVol\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mrho_oil\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mt\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'.'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mlabel\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m\"Area3\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 32\u001b[0m \u001b[0max\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mplot\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mt\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mFay_gv\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mVol\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mrho_oil\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mt\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlabel\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m\"Area1\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 33\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mNameError\u001b[0m: name 'a_v3' is not defined" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "K_i = 1.14\n", + "K_v = 1.45\n", + "\n", + "def r_i(Vol, rho_oil, t):\n", + " \"\"\"radius of Fay inertial phase\"\"\"\n", + " return K_i * (Delta(rho_oil) * g * Vol * t**2)**(0.25)\n", + "\n", + "def r_v(Vol, rho_oil, t):\n", + " \"\"\"radius of Fay viscous phase\"\"\"\n", + " #return K_v * (Delta(rho_oil) * g * Vol**2 * t**1.5 / np.sqrt(visc_w))**(1.0 / 6.0)\n", + " return K_v * (Delta(rho_oil) * g / np.sqrt(visc_w))**(1./6.) * Vol**(1./3.) * t**0.25\n", + "\n", + "def a_v(Vol, rho_oil, t):\n", + " \"\"\"area of Fay viscous phase: pi r^2\"\"\"\n", + " return np.pi * r_v(Vol, rho_oil, t)**2\n", + "\n", + "def Fay_gv(V0, rho_oil, t):\n", + " \"\"\"\n", + " Fay Gravity-viscous phase analytical solution\n", + " \"\"\"\n", + " return np.pi * K_v**2 * (Delta(rho_oil) * g / np.sqrt(visc_w))**(1./3.) * V0**(2./3.) * np.sqrt(t)\n", + "\n", + "\n", + "rho_oil = 900\n", + "Vol = 1000\n", + "t = np.linspace(0, 3600*1)\n", + "\n", + "fig, ax = plt.subplots()\n", + "#ax.plot(t, r_i(Vol, rho_oil, t), label=\"Inertial\")\n", + "ax.plot(t, a_v(Vol, rho_oil, t), label=\"Area2\")\n", + "ax.plot(t, a_v3(Vol, rho_oil, t), '.',label=\"Area3\")\n", + "ax.plot(t, Fay_gv(Vol, rho_oil, t), label=\"Area1\")\n", + "\n", + "#ax.set_xlim(1e-7, 1e-3)\n", + "#ax.set_ylim(0, 110)\n", + "ax.set_xlabel(\"time (s)\")\n", + "ax.set_ylabel(\"radius ($m$)\")\n", + "ax.legend(loc='upper left')\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Fay -- matching rate of change of radius\n", + "\n", + "Where does viscous take over?\n", + "\n", + "Hmm -- this looks like the viscous phase starts stronger, then inertial pahse takes over -- backwards??" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "K_i = 1.14\n", + "K_v = 1.45\n", + "\n", + "C_i = 0.5 * K_i * (Delta(rho_oil) * g * Vol)**0.25\n", + "C_v = 0.25 * K_v * (Delta(rho_oil) * g * Vol**2 / np.sqrt(visc_w))**(1.0/6.0)\n", + "\n", + "def drdt_i(t):\n", + " return C_i * t**(-0.5)\n", + "\n", + "def drdt_v(t):\n", + " return C_v * t**(-.75)\n", + "\n", + "fig, ax = plt.subplots()\n", + "ax.plot(t, drdt_i(t), label=\"inertial\")\n", + "ax.plot(t, drdt_v(t), label=\"viscous\")\n", + "\n", + "ax.set_title(\"derivative of radius change\")\n", + "#ax.set_xlim(1e-7, 1e-3)\n", + "#ax.set_ylim(0, 110)\n", + "ax.set_xlabel(\"time (s)\")\n", + "ax.set_ylabel(\"dr/dt\")\n", + "ax.legend()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Simple Euler method integration for area as a function of area\n", + "def Euler(f, T, A_0):\n", + " dt = T[1] - T[0] # assume it's the same...\n", + " A = [A_0]\n", + " for t in enumerate(T[1:]):\n", + " A.append(A[-1] + f(A[-1]) * dt)\n", + " return np.array(A)\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], "source": [ "# plot thickness computation\n", "nu_oil = np.logspace(-7,-3, 1000)\n", @@ -134,26 +400,11 @@ }, { "cell_type": "code", - "execution_count": 241, + "execution_count": null, "metadata": { "collapsed": false }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "nu: 9.99900000e-07, delta: 1.00000000e-05\n", - "nu: 1.00000000e-06, delta: 1.00000000e-05\n", - "nu: 1.00000010e-06, delta: 1.00000001e-05\n", - "nu: 1.00000000e-05, delta: 1.81818182e-05\n", - "nu: 5.05000000e-05, delta: 5.50000000e-05\n", - "nu: 9.99999990e-05, delta: 9.99999991e-05\n", - "nu: 1.00000000e-04, delta: 1.00000000e-04\n", - "nu: 1.10000000e-04, delta: 1.00000000e-04\n" - ] - } - ], + "outputs": [], "source": [ "half = (1e-4 - 1e-6) / 2.0 + 1e-6\n", "for nu in [9.999e-7, 1e-6, 1.0000001e-6, 1e-5, half, 9.9999999e-5, 1e-4, 1.1e-4]:\n", @@ -195,32 +446,11 @@ }, { "cell_type": "code", - "execution_count": 242, + "execution_count": null, "metadata": { "collapsed": false }, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 242, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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\nRmig62PIAAAAAElFTkSuQmCC\n", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "# as a function of time:\n", "C = 2 # shouldn't be 1! more likely to find errors this way!\n", @@ -236,32 +466,11 @@ }, { "cell_type": "code", - "execution_count": 243, + "execution_count": null, "metadata": { "collapsed": false }, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 243, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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\n4sSJ9OvXj5EjR/Lss8827D9jxgxmzZrFpZdeSnZ2NuPHjyccjpzbhRdeiHOOU089lezsbJ599tkm\n9QW46667OOGEE8jOzmb06NG8+OKLbbounco5l3I/kWqLSGeI9f9b4U8LHTfhKIn6uQlX+NPCVh+3\nI46xd+9eN3ToUHfvvfe6ffv2uUWLFrlu3bq54uJiV1ZW5nJychr2zcvLc48++mjD65KSEjd16tSG\n1y+88II78cQT3Zo1a9z+/fvdbbfd5s4555yG983MTZw40dXU1Lg9e/a43bt3u5ycHDdv3jx34MAB\nt3z5cte/f3+3atUq55xz06dPd/369XPvvfee279/vyssLHTXXHNNk+OtX7++4XXz+i5atMht2bLF\nOefcM88843r06NHw+vHHH3fnn39+i9elpd+RXnmH/05Wi0RE2qyqtgq6NyvsDptrN3fqMcrLy9m3\nbx/XX389Xbp0YdKkSZx55pmt/ny0Rx55hDlz5jBixAiysrKYPXs2K1asYNOmTQ373HTTTfTu3Zsj\njjiCV155hWAwyLRp0zAzTjvtNCZNmsSiRYsa9r/yyis544wzyMrKorCwkBUrVjT5TneIpWcmTZrE\ngAEDAJg8eTInnngiS5cubde5JZoCiYi0WSA7AHubFe6FwdmDO/UYmzdvJhAINCkbOnRoqz8fbcOG\nDfzsZz+jb9++9O3bl379+mFmVFVVNewzZMiQJvuXl5c37N+nTx8WLlxIdXV1wz4DBw5s2D766KPZ\ntWtXq+vzxBNPcPrpp9OnTx/69OnDxx9/zNatW9t1bommQCIibRa6MUTuB7mNgWAv5H6QS+jGUKce\nY9CgQU1+0QNNxjXa4vjjj+fhhx9m+/btbN++nZqaGnbt2sW4ceMa9okeIM/JySEvL6/J/rW1tcyd\nO7dd39/8HH70ox/xwAMPUFNTQ01NDaNGjUraxTMVSESkzYLDgpTOLaWwrpD8cD6FdYWUzi0lOCzY\nqccYP348Xbt25f7772f//v08//zzTbp/2vKL97rrruP2229n1apVAOzcubNJN1Vzl156KWvXrmX+\n/Pns27ePr7/+mvfee481a9a06vsGDhzI+vXrY763e/dusrKy6N+/PwcOHOCxxx7jo48+avW5dDbN\nIxGRdgkOCzL/vvm+HqNbt248//zz/PCHP+Tmm2/mkksuYdKkSQ3vR7cgDpdu+73vfY/du3czZcoU\nNm7cSK9evSgoKOCqq66K+fmePXvy2muvccMNN3DjjTfinGPMmDHcfffdrap7SUkJ06ZNY8+ePTzy\nyCMce+yxDe+NHDmSn//854wbN44uXbowbdq0mI8MThZ6HomIHJKeR5J69DwSEfFduDLMlJlFnPSd\nfL+rIilAXVsi0sQbS8JMuqmAnRMq4BzgNb9rJMlOgUQkQ4UrwxTfXUxVbRWDjglw4SkhnlsU5O2P\ni/lqesXBczxEWqBAIpKB6pcnqRhTAf2AvfD8A+Xcfm0pXw2s4m0FEWkDjZGIZBjn4Mf/uzgSROoD\nRnf46rsVLAsXk9M7xkRBkUNQi0QkjUV3Xw3sEWBsMMQzTwf5qK4KRjTb2Vue5NGSRymfVd400Igc\nggKJSJqK1X21eGE5988q5Y3lAZ7cS9NA4S1PUj9RsPjuYjbXbmZ53+VpteR5JmjvMjHtpXkkImnI\nOZh4TRGv5y44KFgU1hUSujHUGGS607A8SVtnlktqSdQ8ErVIRFJcdPfVgB4BTh8a4smFQdZ+VQXN\nn9zqdV81b3UMzh5MaG5IQUTaRYFEJIXF6r566elyHrihlNL3AixsofsKOmaJExFIcNaWmT1qZtVm\n9mFU2S1m9pmZLfN+Lop6b46ZfWpmq81sYiLrJpIOYmVf/c9FFbz+fjG/+nn8q+uKtEaiWySPAfcD\nTzQrv9s512RlMzMbCXyfSGN8CPC6mZ2owRCRiPourM92VrFvR4C/fxZi1a6Ws6/UfSWdJaGBxDn3\njpnFSh+INdhzBfCUc24fUGlmnwJnAe8mso4iqSBcGWbCjwsIj23swhpQW87EMaN4Wd1X4jO/JiTO\nNLMVZvZ7M+vllQWATVH7VHllIhnt00/hO9cUR4JIVBdW9TkVZB0wdV+J7/wYbH8AuNU558zsV8B/\nAD8kdiulxW6tkpKShu28vDzy8vI6tpYiPojOwOq6JwDbQ6xYHuTokbGfb15LrbqvpEVlZWWUlZUl\n/HsSPo/E69p62Tl36qHeM7PZgHPO3eW99ypwi3PuoK4tzSORdBSuDPPtmQWsP61xbkf/t3Mpe7iU\nOx4qZsExseeEqOtKWiuVn0diRLU2zGxg1HtXAvXPj3wJmGJm3c0sCJwALEUkA9TWwlX/VNwYRAC6\nw9YLKrjjoeIOeb65SKIktGvLzBYCeUA/M9sI3ALkm9lpwAGgErgOwDm3ysyeAVYBXwM/UbND0lF0\n91XvrgGO7RJZvr3r8NjdV8rAkmSnJVJEOlGTCYRe91WvN3J5+d9LefgZdV9JYiWqa0uBRKSTOAcF\nU4p44wStfyX+0FpbIimk+dMHzzkpxLzHg3y8qwpObrazuq8kxSmQiHSwmE8f/F059/6klLdXav0r\nST96QqJIB/vFXbGfPviXj7T+laQnBRKRDlJdDXPmwAulh8++KqwrJD+cT2FdocZAJOWpa0uknerH\nQSq+qGJbZYDq1SGmTg1yyQUBXlL3lWQQZW2JtEO4MsyFPypg05mNGVbDluWy5MFSAGVfSVJS+m8U\nBRLx07Jl8L1/KGLTd1ue81HfWmnIvrpR2VfiP6X/ivggOo2321cB9m4Jse7TIEd9o+VxEFD3lWQW\nBRKRFsRK4z22qpy/vFFK6LcB1h1iHEQkk6hrSyQG52DC94t4c4RmoUv6UNeWSAJFd2FRF6BmfYhP\nv6qC0c121Cx0kYMokEjGi9WFNWBbOd8ePkppvCKtoAmJktGcg+k3HjwTvfrcCkyPsRVpFQUSyUjO\nwcsvwze/Ce+vOfRjbDULXeTQ1LUlGSNcGebmu4tZGa6iak2A/lkh7rg9yHNvtryQorqvRA5PWVuS\nEdaHw5z3jwV8Pr4xy2r4B7m8Plcz0SVzaGZ7FAUSaYu334arriviyys1E10ym9J/RVohOo23+94A\nuzaG+HxzkH4jqvhSM9FFEkKBRNJGzJnon5Xzzp9LufX+AJ9oJrpIQqhrS9LG5TOKeHmwZqKLtCRR\nXVtK/5WUV1UF110H//UXPVBKxA/q2pKUUz8OUrmtiu0bA1StDHHdj4JcMSHAc5qJLtLp1LUlKSVc\nGWbCzALCpzV2UQ19P5c3H1Iar8jhKP03igJJZtq3D869vIilpyuNV6Q9lP4rGSU6jXdwdoALRoe4\n954gm7tVwdnNdlYar4ivFEgk6cRK4332t+U89K+lvLG85eVMRMQfytqSpFN898Gr8X59aQVLlhfz\nqxtDWpFXJMkokEhS2b4d3npfabwiqURdW+Kb6HGQgT0DDO8d4nePBOk1PBBpcSiNVyQlKGtLfNFk\nHMRL1T3q1VxevLOUE09UGq9IIij9N4oCSeorur6IBccojVekMyn9V9JGdTW8sbQKLm72htJ4RVKS\nAokkXH3rYtOOKmo3Bwi/H2LQqABblMYrkhYUSCShDpoTkgPHdynn4eI/8A93lh80DhKaqzRekVSj\nMRJJqMumF/FKoOWl3TUOItJ5NEYiKaWuDkIhePWdKpja7M2oOSEaBxFJfZqQKB0iXBmm6Poi8qfn\nc+7lRZxwYpgvvoDLvxVonIVeT2MhImlFXVsSt1hzQgLv5vKX32tpd5FkonkkURRIksvVPy7imb6a\nEyKS7DRGIknHOXjuOXjxjSoobPam5oSIZAwFEmm16LWxsrMC7FgfYtvWIBeMDfC65oSIZKxWdW2Z\n2WjgZODI+jLn3BMJrNfh6qOurU4Waxyk71u5/O2xUrp10ziISCrwbYzEzG4B8ogEkj8RWdjiHefc\nVR1dmdZSIOl8WhtLJPX5OUZyFTAGWO6cm2FmAwB1eGeQmhpYorWxRKQFrQkk/+OcO2Bm+8wsG/gC\nyElwvcQnzZ+VPv4bIW6/LUjPoYd+RoiIZK7WBJL3zKw38DvgfWAX8LeE1kp8EfNZ6Q+Xs/D+Us44\nI0TBLK2NJSIHa9M8EjMbBmQ75z5s5f6PApcC1c65U72yPsDTwFCgEvi+c26n9959RDpQdgPTnXMr\nWjiuxkgSQOMgIunNtzESMzMiswSGO+duNbPjzews59zSVhz/MeB+IDrDazbwunPu12b2C2AOMNvM\nLgZynXMnmtnZwEPAuLaekLTfuuqqSEskmsZBROQwWrPW1gPAeOAa73Ud8NvWHNw59w5Q06z4CmCe\ntz3Pe11f/oT3uXeBXt7AviTY/v3wm9/A8r9oXSwRabvWjJGc7Zwba2bLAZxzNWbW/XAfOoTjnHPV\n3rG2mNlxXnkA2BS1X5VXVh3Hd0kM0QPqPQlQtTLEMT2DvLowxD/9u8ZBRKRtWhNIvjazLoADMLNj\ngQMJqEusfrsWB0JKSkoatvPy8sjLy+v4GqWhWAPq/SvKKX+slNzhQUqHlTYdB5mrcRCRVFVWVkZZ\nWVnCv6c1ExILgauBsUS6oq4CbnbOPduqLzAbCrwcNdi+GshzzlWb2UDgTefcSDN7yNt+2tvvE+DC\n+tZLs2NqsL2dDjegLiLpK1GD7YcdI3HOLQD+DbgD+Bz4XmuDiMdo2tp4CZjubU8HFkeVTwMws3HA\njlhBRNrPOVi6uqppEIEmA+oiIm11yK4tM8sCVjnnTgI+aevBzWwhkeVV+pnZRuAW4E7gWTP7B2Aj\nMBnAOfcnM7vEzNYRSf+d0dbvk0bR4yCB7AAzfxCi5JYg2zYE4Cw0sVBEOkxrurYWAz91zm3snCod\nnrq2Di3WAotZL+by04mlzJwJF/+zFlgUyUR+Ltr4NnA6sJRISwHAOeeuaPlTiaVAcmiaWCgisfi5\naGNxdD2A82icUyJJqKpWEwtFpPO0ZrD9LWAn8F3gcWACkVnnkoS+/hq+DGtioYh0nha7tsxsBDCF\nSOtjG5H1sf7FOTe086oXm7q2GjV/auHGFSGOOQY2HVVA5RkaBxGRRp0+RmJmB4C/AP/onFvnla13\nzg3v6Eq0lQJJRKxB9X5v5fLuE6VkZaFxEBFpwo9A8r+ItEjOAV4FngJ+75zz/beRAkmEJheKSFt0\n+oRE59wLzrmrgZOAMuAGYICZPWhmEzu6ItJ2az/X5EIR8V9rBtt3O+cWOOcuBYYAK4gsBS8+mjcP\nPvyrBtVFxH9terBVssi0rq3oAfUBPQIc2Briww+C3HNvmFn3a3KhiLSObxMSk1EmBZJYA+o9X8vl\nb4+XMnpUUJMLRaTVFEiiZFIg0YC6iHQU31b/FX99tlMD6iKS3BRIkti2bbDmPQ2oi0hyUyBJUh98\nAGeeCZedF2L4itzGYFL/+Nsb9fhbEUkOGiNJEtGZWV9vD/DxOyEefCDIlCloQF1EOoQG26OkWyCJ\nlZk1ZGkub/9Oabwi0nE02J7Giu8ubgwiAN3hs7MqKL67+JCfExFJBgokSaDiC2VmiUjqUiDxWXk5\nrHhHmVkikro0RtKJogfUA9kBzh4R4tb/E+SOO8Pc+YKWOhGRxNJge5RUDCSxBtS7LM5l8a9L+e4l\nWupERBJPgSRKKgYSLXUiIn5T1laKq6rVgLqIpCcFkk7S7wgNqItIelIg6QQbNsD7pSH6LNFSJyKS\nfhRIEuzjj+H88+GGfw7y/pOlFNYVkh/Op7CuUFlZIpIWNNjewaJTfLvvDfDeayHuuzdIYaHfNROR\nTJeowfauHX3ATNYkxbcfsBcGnVrOOeeWAmp5iEh6UtdWB4q1Ztbn47VmloikNwWSDqQUXxHJRAok\nHejr7UrxFZHMo0DSQZ58EtaUhxjy30rxFZHMoqytDrBwIfz851BaCj16as0sEUlOWmsrit+BJDrF\nd++2AJ++G2LJkiCjR/tWJRGRw1L6b5I4KMU3ADlfldOjp1J8RSQzaYykjWKl+G46Uym+IpK5FEja\nSCm+IiJNKZC00VH7leIrIhJNgaQNKirgvddCDPirUnxFROopa6uVqqvh3HPhX/4FvnORUnxFJPUo\n/TdKZwWS+jTfjTVVfPS3AFMvDXHvPQoYIpKaFEiidEYgaZLm252GLiw9Q0REUpWe2d7JYqX5VoxR\nmq+ISHMKJC1Qmq+ISOsokLTA1SrNV0SkNRRIYlizBj58K0TgXaX5iogcjgbbm6mthbPPjqzmO+Hb\nSvMVkfSRdllbZlYJ7AQOAF87584ysz7A08BQoBL4vnNuZ4zPJiSQHDgAV14JAwfCQw91+OFFRHyV\njllbB4A859zpzrmzvLLZwOvOuW8AS4A5nVmh226DL7+E++7rzG8VEUltfi4jbxwcyK4ALvS25wFl\nRIJLwtRPOvwwXMW6FQHefC5E9+7qvhIRaS0/u7bWA9sBBzzsnPu9mdU45/pE7bPNOdcvxmc7pGtL\nkw5FJJOk44OtznHObTGzY4HXzGwNkaDSKiUlJQ3beXl55OXltbkCh5p0OP+++W0+nohIMikrK6Os\nrCzh35MUWVtmdguwC/ghkXGTajMbCLzpnBsZY/8OaZHkT8+nLFh2cHk4nyWPL4n7+CIiySStBtvN\n7Ggz6+lt9wAmAiuBl4Dp3m7XAosTWY/uezXpUEQkXr60SMwsCLxApCurK7DAOXenmfUFngFygI3A\nZOfcjhifj7tF8ve/w8mjwuwZWkD1uRojEZH0l3bzSOLREYHkhhtgyxa4/Q5NOhSRzKBAEiXeQPL2\n2zBlCqxcCf0OygkTEUlPaTVG4qddu2DGjMjMdQUREZH4ZVyLZObMSDCZN6+DKyUikuTScR5Jp6mf\nvf7RhirWLgvw/14JARoHERHpCGnfItHsdRGRCI2RtJMemSsiklhpH0j0yFwRkcRK+0ASyNbsdRGR\nREr7QHLt5SGyXtQjc0VEEiXtB9u//30YFgyz+X80e11EMptmtkdpbSBZsQIuvhjWrYMePTqhYiIi\nSUxZW+3wy1/CnDkKIiIiiZS2ExLLyyMtkmee8bsmIiLpLW1bJMXFkZ8jj/S7JiIi6S0tA0lZGaxf\nD9On+10TEZH0l3aBxDm4+WYoKYFu3fyujYhI+kubQBKuDFN0fRGnXZ7Pys1FjD8n7HeVREQyQlqk\n/2phRhGRw1P67yFoYUYREf+kRSDRwowiIv5Ji0CihRlFRPyTNmMkE35SQPh0jZGIiLREa21FibXW\n1pNPhvlpSTGnjtfCjCIisSiQRIkVSO66Cz7/HO65x6dKiYgkOWVtHca778LZZ/tdCxGRzKNAIiIi\ncUmLQPLZZ7B3LwQ1JCIi0unSIpDUt0asw3v+RETkcNIikCxdqm4tERG/pEUgefddOOssv2shIpKZ\nUj79d/9+6N0bNm6EPn18rpiISBJT+m8LPv4YBg9WEBER8UvKBxKl/YqI+EuBRERE4qJAIiIicUnp\nwfa6Ohg4EGpqoHvz55GIiEgTGmyP4f33YcwYBRERET+ldCDR/BEREf+lfCDR+IiIiL8USEREJC4p\nG0i04q+ISHJI2UBSv1CjVvwVEfFXygYSdWuJiCQHBRIREYlLyk5I7NnTacVfEZE20ITEZrTir4hI\nckjKQGJmF5nZJ2a21sx+EWsfdWuJiCSHpAskZpYFzAW+A4wCrjGzk5rvp0ASUVZW5ncVkoauRSNd\ni0a6FomXdIEEOAv41Dm3wTn3NfAUcEXznV75WxHhynCnVy7Z6H+SRroWjXQtGulaJF4yBpIAsCnq\n9WdeWROvDl1AwawCBRMREZ8lYyCJlVFwcGpZd6gYU0Hx3cWJr5GIiLQo6dJ/zWwcUOKcu8h7PRtw\nzrm7ovZJrkqLiKSIRKT/JmMg6QKsASYAnwNLgWucc6t9rZiIiMTU1e8KNOec229ms4DXiHS9Paog\nIiKSvJKuRSIiIqklGQfbD6k1kxVTmZkNMbMlZrbKzFaa2fVeeR8ze83M1pjZn82sV9Rn7jOzT81s\nhZmdFlV+rXed1pjZND/OpyOYWZaZLTOzl7zXw8ys3DuvJ82sq1fe3cye8q7F38zs+KhjzPHKV5vZ\nRL/OJR5m1svMnvXO4WMzOztT7wszu8HMPjKzD81sgfdvnxH3hZk9ambVZvZhVFmH3QdmNta7rmvN\n7J5WVco5lzI/RALfOmAo0A1YAZzkd706+BwHAqd52z2JjBedBNwF/JtX/gvgTm/7YuCP3vbZQLm3\n3QeoAHoBveu3/T6/dl6TG4D5wEve66eByd72g8B13vaPgQe87auBp7ztk4HlRLpyh3n3kPl9Xu24\nDo8DM7ztrt6/bcbdF8BgYD3QPep+uDZT7gvgPOA04MOosg67D4B3gbO87T8B3zlsnfy+KG28gOOA\n/4p6PRv4hd/1SvA5vwh8G/gEGOCVDQRWe9sPAVdH7b8aGABMAR6MKn8wer9U+QGGAKVAHo2B5Esg\nq/k9AbwKnO1tdwG+iHWfAP9Vv1+q/ADHABUxyjPuvvACyQbvl2FX4CWgAPgiU+4LIn9MRweSDrkP\nvM+uiipvsl9LP6nWtdWqyYrpwsyGEfnLo5zITVIN4JzbAhzn7dbSNWleXkVqXqvfAP+KN5fIzPoB\nNc65A96ghd4NAAADz0lEQVT70fdAwzk75/YDO82sL+lxLYYDW83sMa+b7xEzO5oMvC+cc5uB/wA2\nEqn/TmAZsCMD74t6x3XQfRDw9mm+/yGlWiBp3WTFNGBmPYFFwM+cc7to+TybXxPz9k35a2Vm3wWq\nnXMraDwf4+Bzc1HvNZcW14LIX95jgd8658YCu4n8RZ2J90VvIssmDSXSOulBpAunuUy4Lw6nrfdB\nu65JqgWSz4Djo14PATb7VJeE8QYJFwH/6Zxb7BVXm9kA7/2BRJrxELkmOVEfr78m6XCtzgUuN7P1\nwJPAt4B7gF7e4p7Q9LwaroU3H6mXc66Glq9RKvkM2OSce897/RyRwJKJ98W3gfXOue1eC+MF4Byg\ndwbeF/U66j5o1zVJtUDy38AJZjbUzLoT6b97yec6JcIfiPRT3htV9hIw3dueDiyOKp8GDasC7PCa\nuH8GCrxMnz5E+pD/nPiqdxzn3E3OueOdc8OJ/Fsvcc4VAW8Ck73drqXptbjW254MLIkqn+Jl7wSB\nE4hMdE0Z3r/pJjMb4RVNAD4mA+8LIl1a48zsSDMzGq9FJt0XzVvmHXIfeN1itWZ2lndtp0Udq2V+\nDxq1Y5DpIiKZTJ8Cs/2uTwLO71xgP5GMtOVE+n4vAvoCr3vnXgr0jvrMXCIZJx8AY6PKp3vXaS0w\nze9zi/O6XEjjYHuQSGbJWiKZOt288iOAZ7xzLgeGRX1+jneNVgMT/T6fdl6DMUT+mFoBPE8k4yYj\n7wvgFu/f8kNgHpEszoy4L4CFRFoJXxEJqjOIJB50yH0AnAGs9N67tzV10oREERGJS6p1bYmISJJR\nIBERkbgokIiISFwUSEREJC4KJCIiEhcFEhERiYsCiUgreBO3fuxtDzKzZ/yuk0iy0DwSkVbwFtB8\n2Tl3is9VEUk6SfeoXZEkdQcw3MyWEZklPNI5d4qZXQt8j8jCgScQWZW2OzAV2ANc4pzbYWbDgd8C\n/YG/A//knFvrw3mIdDh1bYm0zmwizwMZS9Sy9p5RRILJWcBtwC5vv3K8dY6AR4BZzrkzvc8/2FkV\nF0k0tUhE4vemc+7vwN/NbAfwile+EjjFzHoQWZ32WW8hPIisDSWSFhRIROL3VdS2i3p9gMj/Y1lE\nHsY1trMrJtIZ1LUl0jp1RB53C7Ef/tMi51wdEDazq+rLzOzUDqybiK8USERawTm3HfirmX0I/JqW\nnxrXUnkR8I9mtsLMPgIuT0A1RXyh9F8REYmLWiQiIhIXBRIREYmLAomIiMRFgUREROKiQCIiInFR\nIBERkbgokIiISFwUSEREJC7/H1RRI/QC4XkmAAAAAElFTkSuQmCC\n", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "# as a differential equation:\n", "def dadt(t):\n", @@ -295,38 +504,11 @@ }, { "cell_type": "code", - "execution_count": 245, + "execution_count": null, "metadata": { "collapsed": false }, - "outputs": [ - { - "ename": "ValueError", - "evalue": "x and y must have same first dimension", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 20\u001b[0m \u001b[0mfig\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0max\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mplt\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msubplots\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 21\u001b[0m \u001b[0max\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mplot\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mT\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mA1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlabel\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m\"Fay\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 22\u001b[0;31m \u001b[0max\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mplot\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mT\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mA3\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'o'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlabel\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m\"differential area\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 23\u001b[0m \u001b[0max\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlegend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 24\u001b[0m \u001b[0max\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mset_xlabel\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"time\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/Users/chris.barker/miniconda2/envs/gnome/lib/python2.7/site-packages/matplotlib/__init__.pyc\u001b[0m in \u001b[0;36minner\u001b[0;34m(ax, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1817\u001b[0m warnings.warn(msg % (label_namer, func.__name__),\n\u001b[1;32m 1818\u001b[0m RuntimeWarning, stacklevel=2)\n\u001b[0;32m-> 1819\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mfunc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0max\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1820\u001b[0m \u001b[0mpre_doc\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0minner\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__doc__\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1821\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mpre_doc\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/Users/chris.barker/miniconda2/envs/gnome/lib/python2.7/site-packages/matplotlib/axes/_axes.pyc\u001b[0m in \u001b[0;36mplot\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1380\u001b[0m \u001b[0mkwargs\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mcbook\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnormalize_kwargs\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0m_alias_map\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1381\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1382\u001b[0;31m \u001b[0;32mfor\u001b[0m \u001b[0mline\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_get_lines\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1383\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0madd_line\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mline\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1384\u001b[0m \u001b[0mlines\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mline\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/Users/chris.barker/miniconda2/envs/gnome/lib/python2.7/site-packages/matplotlib/axes/_base.pyc\u001b[0m in \u001b[0;36m_grab_next_args\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 379\u001b[0m \u001b[0;32mreturn\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 380\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mremaining\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m<=\u001b[0m \u001b[0;36m3\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 381\u001b[0;31m \u001b[0;32mfor\u001b[0m \u001b[0mseg\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_plot_args\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mremaining\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 382\u001b[0m \u001b[0;32myield\u001b[0m \u001b[0mseg\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 383\u001b[0m \u001b[0;32mreturn\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/Users/chris.barker/miniconda2/envs/gnome/lib/python2.7/site-packages/matplotlib/axes/_base.pyc\u001b[0m in \u001b[0;36m_plot_args\u001b[0;34m(self, tup, kwargs)\u001b[0m\n\u001b[1;32m 357\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mindex_of\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtup\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 358\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 359\u001b[0;31m \u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_xy_from_xy\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 360\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 361\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcommand\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m'plot'\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/Users/chris.barker/miniconda2/envs/gnome/lib/python2.7/site-packages/matplotlib/axes/_base.pyc\u001b[0m in \u001b[0;36m_xy_from_xy\u001b[0;34m(self, x, y)\u001b[0m\n\u001b[1;32m 217\u001b[0m \u001b[0my\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0m_check_1d\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0my\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 218\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mshape\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mshape\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 219\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"x and y must have same first dimension\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 220\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mndim\u001b[0m \u001b[0;34m>\u001b[0m \u001b[0;36m2\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mndim\u001b[0m \u001b[0;34m>\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 221\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"x and y can be no greater than 2-D\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mValueError\u001b[0m: x and y must have same first dimension" - ] - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "# differential equation in terms of Area\n", "\n", @@ -523,6 +705,19 @@ "ax.legend(loc=\"upper left\")\n" ] }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "## Okubo: constant for Richardson spreading\n", + "\n", + "Okubu (1971) is the key paper for asertign and getting constants for Richardson's 4/3 diffusion.\n", + "\n", + "However, while it has a line-fit, they don't seem to have published the constant. So this is a little code to try to match the figure." + ] + }, { "cell_type": "code", "execution_count": null, diff --git a/experiments/Spreading/Richardson.ipynb b/experiments/Spreading/Richardson.ipynb index db49583c7..21f117aa5 100644 --- a/experiments/Spreading/Richardson.ipynb +++ b/experiments/Spreading/Richardson.ipynb @@ -20,7 +20,13 @@ "\n", "where: $\\alpha$ is a constant that appears to be pretty consistent with environmental flows:\n", "\n", - "(0.002 < $\\alpha$ < 0.01 $cm^{2/3}s^{-1}$ )\n", + "(0.002 < $\\alpha$ < 0.01 \\; \\; $cm^{2/3}s^{-1}$ )\n", + "\n", + "From Okubu(1971) -- for length scales less than a km or so:\n", + "\n", + "$\\alpha \\approx 0.0019 \\; cm^{2/3}s^{-1}$ (taken by eye off Figure 4.)\n", + "\n", + "($ 8.816 \\times 10^{-5} \\; m^{2/3}s^{-1}$)\n", "\n", "Once all put together, you get the variance of the \"plume\" growing as:\n", "\n", @@ -38,16 +44,34 @@ "\n", "$ D = \\frac{4}{9} \\alpha^3 t^2 $\n", "\n", + "### note on units:\n", + "\n", + "$\\alpha$ is in units of Length to the two-thirds over time. So we need to convert between CGS and SI units:\n", + "\n", + "$1.0 \\; cm^{2/3}s^{-1} = 0.0464 \\; m^{2/3}s^{-1}$\n", + "\n", + "or \n", + "\n", + "$1.0 \\; m^{2/3}s^{-1} = 21.54 \\; cm^{2/3}s^{-1}$\n", "\n" ] }, { "cell_type": "code", - "execution_count": 58, + "execution_count": 2, "metadata": { - "collapsed": true + "collapsed": false }, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/chris.barker/miniconda2/envs/gnome/lib/python2.7/site-packages/matplotlib/font_manager.py:273: UserWarning: Matplotlib is building the font cache using fc-list. This may take a moment.\n", + " warnings.warn('Matplotlib is building the font cache using fc-list. This may take a moment.')\n" + ] + } + ], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", @@ -56,7 +80,7 @@ }, { "cell_type": "code", - "execution_count": 59, + "execution_count": 3, "metadata": { "collapsed": true }, @@ -98,7 +122,7 @@ }, { "cell_type": "code", - "execution_count": 60, + "execution_count": 4, "metadata": { "collapsed": false }, @@ -156,7 +180,7 @@ }, { "cell_type": "code", - "execution_count": 61, + "execution_count": 5, "metadata": { "collapsed": false }, @@ -164,10 +188,10 @@ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 61, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" }, @@ -175,7 +199,7 @@ "data": { "image/png": "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\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -206,7 +230,7 @@ }, { "cell_type": "code", - "execution_count": 62, + "execution_count": 6, "metadata": { "collapsed": false }, @@ -217,7 +241,7 @@ }, { "cell_type": "code", - "execution_count": 63, + "execution_count": 7, "metadata": { "collapsed": false }, @@ -225,10 +249,10 @@ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 63, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" }, @@ -236,7 +260,7 @@ "data": { "image/png": "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\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -282,7 +306,7 @@ }, { "cell_type": "code", - "execution_count": 64, + "execution_count": 8, "metadata": { "collapsed": false }, @@ -304,7 +328,7 @@ }, { "cell_type": "code", - "execution_count": 65, + "execution_count": 9, "metadata": { "collapsed": false }, @@ -312,10 +336,10 @@ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 65, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" }, @@ -323,7 +347,7 @@ "data": { "image/png": "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\ntwAAAABJRU5ErkJggg==\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -354,7 +378,7 @@ }, { "cell_type": "code", - "execution_count": 66, + "execution_count": 10, "metadata": { "collapsed": true }, @@ -382,7 +406,7 @@ }, { "cell_type": "code", - "execution_count": 67, + "execution_count": 11, "metadata": { "collapsed": false }, @@ -390,10 +414,10 @@ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 67, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" }, @@ -401,7 +425,7 @@ "data": { "image/png": "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\njRocOHAg53VgA7yI0Lhx41zrK4rNmzfTvXt3GjVqRJ06dXjggQfYvXt3oZbdsWPHrzoDNG3alPT0\n9JzXDRs2zHles2bNXO/jiSeeoFWrVtStW5e6deuSmZlZ6G0bE8nKQ1IBSywRrUOHDlSvXp133nkn\n5PS4uLhc9zn75ptviI0t/u2wv/3225znqsr27duJi4sD3Jf3wYMHc6bv3Lkz53moarI///nPtGzZ\nktTUVPbt28cjjzxS6LOe2NjYXLEAbNu2LSeW/CxatIixY8fy5ptvsnfvXvbu3UtMTIzdpsWUe+Ul\nqYAllogWExPD6NGjue2223j33Xf5+eefOXLkCLNnz+bee++lb9++jBkzht27d7N7926Sk5NLdI3G\nypUreeeddzh69ChPPvkk1atX54ILLgDg3HPPZerUqRw7dozZs2fnqtZq0KABP/zwA5mZmTll+/fv\nJyYmhpo1a7Jx40aeffbZXNtq2LBhru7GgS644AJOOOEExo4dy5EjR0hJSeH999+nbyE66B84cIBq\n1apRr149srKyePjhh9m/f39xdocxESO7S3F5SCpgiSXi3XXXXYwbN44xY8ZQv359mjRpwjPPPMPV\nV1/Ngw8+SNu2bWndujVnn3025513Xq5G7GDBZxbBr3v06MHrr79O3bp1mTJlCjNmzKBKlSoAjB8/\nnpkzZ1K3bl1ee+01rg64X8QZZ5xB3759Oe200zjppJPYuXMn//znP5kyZQoxMTEMGTKEPn365NrW\nqFGjGDhwICeddBJvvvlmrmnVqlVj5syZzJo1i5NPPplhw4bx6quv0rx585BxB7r88svp0qULLVq0\nID4+npo1axb5GhtjIkl2Uhk9unwkFbC7G9vdbD2jR48mNTWVSZMm+R2Kr+x4MJEkEpKK3d3YGGMq\niEhIKsVlicUYYyJMeU4qYFVhVvVhcrHjwfgt0pKKVYUZY0w5FmlJpbgssRhjTATYsAE6diz/SQVK\nObGIyEQR2SUiawPKxorIBhFZLSJviUhMwLT7RGSzN71zQHkXEdkoIl+JyL0B5c1EZKmIbBKR10Sk\namm+H2OMKQ3r17szlcceK/9JBUr/jOUl4PKgsrnAmap6DrAZuA9ARFoBvYGWQFdggjhRwNPees4E\n+orIb711PQ48oapnAPuAm0v5/RhjTFitXg2dOsETT0TOeColVaqJRVUXA3uDyj5S1eybXy0FGnvP\n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\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -420,13 +444,27 @@ ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": { "collapsed": true }, + "source": [ + "# Okubo plots\n", + "Okubo (1971) and referenced in Thorpe (2007) shows plot of the 4/3 law on top of various data, indicating a contant ($\\alpha$) that varies with length scale, but constant within a range -- i.e. $L \\lt 10^5 cm$.\n", + "\n", + "But while there is a fitted line on the plot, I don't see the constant in the paper anywhere. So this tries to approcimate it from teh figure." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false + }, "outputs": [], - "source": [] + "source": [ + "L = np.logspace(3, 6)" + ] }, { "cell_type": "code", @@ -454,7 +492,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", - "version": "2.7.12" + "version": "2.7.11" } }, "nbformat": 4, diff --git a/experiments/Spreading/SpreadingUtilities.ipynb b/experiments/Spreading/SpreadingUtilities.ipynb index b966e4ba0..d52703fd0 100644 --- a/experiments/Spreading/SpreadingUtilities.ipynb +++ b/experiments/Spreading/SpreadingUtilities.ipynb @@ -11,7 +11,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 36, "metadata": { "collapsed": true }, @@ -51,7 +51,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 37, "metadata": { "collapsed": false }, @@ -68,18 +68,21 @@ "def delta():\n", " rho_oil = 0.8 # density of oil\n", " rho_water = 1 # density of water\n", - " return (pw-po)/pw\n", + " return (rho_water-rho_oil)/rho_water\n", "\n", "def init_area(V0):\n", " return np.pi * (K1**4/K2**2) * ((g * V0**5 * delta())/visc_w**2 )**(1./6.) \n", "\n", "def init_radius(V0):\n", - " return (K1**2/K2) * ((g * V0**5 * delta())/visc_w**2 )**(1./12.) \n" + " return (K1**2/K2) * ((g * V0**5 * delta())/visc_w**2 )**(1./12.)\n", + "\n", + "def old_init_area(V0):\n", + " return np.pi*(K2**4/K1**2)*((V0**5*g*delta())/(visc_w**2))**(1./6.)\n" ] }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 38, "metadata": { "collapsed": false }, @@ -87,31 +90,37 @@ "source": [ "vol = np.linspace(1,100) # 1 to 100 cubic meters\n", "area = init_area(vol)\n", + "old_area = old_init_area(vol)\n", "radius = init_radius(vol)" ] }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 39, "metadata": { "collapsed": false }, "outputs": [ { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 33, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "[ 0.54390769 0.45164263 0.40277741 0.36826818 0.3412104 0.31878132\n", + " 0.29953158 0.28261209 0.26748015 0.25376655 0.24120815 0.22961034\n", + " 0.21882497 0.20873648 0.19925288 0.19029971 0.18181578 0.17375017\n", + " 0.16606003 0.15870892 0.15166561 0.14490309 0.13839786 0.13212931\n", + " 0.1260793 0.12023173 0.11457228 0.10908811 0.10376772 0.09860071\n", + " 0.09357769 0.0886901 0.08393018 0.07929079 0.07476543 0.0703481\n", + " 0.06603329 0.06181591 0.05769125 0.05365492 0.04970289 0.04583136\n", + " 0.04203681 0.03831597 0.03466574 0.03108325 0.02756579 0.02411082\n", + " 0.02071594 0.01737891]\n" + ] }, { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYoAAAEACAYAAACtVTGuAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3Xmcj/X+//HHayzRQlSopFQ40rekjk6rOUeRFqpzEpUl\nZE0pkdFiVBJtJER2KQflUEdIzHEqWUKUiWmxliFC2piZ1++PudTnzI/ZzTUzn+f9dnPr+rx6v695\nXZcxz7m2z8fcHRERkSOJCbsBEREp3BQUIiKSKQWFiIhkSkEhIiKZUlCIiEimFBQiIpKpLIPCzGqa\n2SozWxn8d6+Z3WdmFcxsvpmtN7N5ZlY+Yk6cmSWZWaKZNYqo1zOzNWa2wcyGRNRLm9nUYM4SM6uW\n/5sqIiK5kWVQuPsGd7/I3esBFwM/ATOBPsACd68FLATiAMzsPKA5UBtoAowwMwtWNxJo7+41gZpm\n1jiotwd2u3sNYAgwOL82UERE8ianp56uAb5y9y1AM2BiUJ8I3BwsNwWmunuKu28EkoD6ZlYFOMHd\nlwfjJkXMiVzXDKBhTjdERESOjpwGxe3A68FyZXdPBnD37UCloH46sCVizragdjqwNaK+Naj9zxx3\nTwX2mFnFHPYmIiJHQbaDwsxKkX60MD0oZXzvj/x8LxDLeoiIiBSEkjkY2wT4xN2/D14nm1lld08O\nTivtCOrbgDMi5lUNakeqR8751sxKAOXcfXfGBsxMb0wlIpIL7p7rX8BzcuqpJfBGxOvZQNtguQ0w\nK6LeIriTqTpwLrAsOD2118zqBxe3W2eY0yZYvo30i+OH5e76406/fv1C76Gw/NG+0L7Qvsj8T15l\n64jCzI4l/UJ2x4jyIGCambUDNpF+pxPuvs7MpgHrgINAV/+j027ABKAMMMfd5wb1scBkM0sCdgEt\n8rJRIiKSf7IVFO7+M3BKhtpu0sPjcOMHAgMPU/8E+L/D1H8jCBoRESlc9GR2ERUbGxt2C4WG9sUf\ntC/+oH2Rfyw/zl8VFDPzotSviEhhYGZ4AV3MFhGRKKSgEBGRTCkoREQkUwoKERHJlIJCREQypaAQ\nEZFMKShERCRTCgoREcmUgkJERDKloBARkUwpKEREJFMKChERyZSCQkREMqWgEBGRTCkoREQkUwoK\nERHJlIJCREQypaAQEZFMKShERCRTCgoREcmUgkJERDKVraAws/JmNt3MEs3sczO71MwqmNl8M1tv\nZvPMrHzE+DgzSwrGN4qo1zOzNWa2wcyGRNRLm9nUYM4SM6uWv5spIhJ90tKcgdPm53k92T2iGArM\ncffawIXAF0AfYIG71wIWAnEAZnYe0ByoDTQBRpiZBesZCbR395pATTNrHNTbA7vdvQYwBBic5y0T\nEYlSaWnOI5NnU67npcR/3CPP68syKMysHHCVu48HcPcUd98LNAMmBsMmAjcHy02BqcG4jUASUN/M\nqgAnuPvyYNykiDmR65oBNMzTVomIRKEDB1N5cOx0jutZlxdX9qPL/z3MT89+luf1lszGmOrA92Y2\nnvSjiRVAD6CyuycDuPt2M6sUjD8dWBIxf1tQSwG2RtS3BvVDc7YE60o1sz1mVtHdd+dus0REosev\nB1J4YOxUxiUNoJSX5+FLnubxltcTE2NZT86G7ARFSaAe0M3dV5jZi6SfdvIM4zK+zosjbl18fPzv\ny7GxscTGxubjlxURKTr2/3KAbqMn8fqWgRyXUpUnrxzGQ7c2ZPHi//DEE/3z7euYe+Y/382sMrDE\n3c8OXl9JelCcA8S6e3JwWmmRu9c2sz6Au/ugYPxcoB+w6dCYoN4CaODuXQ6NcfelZlYC+M7dKx2m\nF8+qXxGR4m7P/l/pPGocM7YPovzBWvRv+Bj33nTVEcebGe6e68OLLK9RBKeXtphZzaDUEPgcmA20\nDWptgFnB8mygRXAnU3XgXGCZu28H9ppZ/eDidusMc9oEy7eRfnFcREQifL/3Z25+5kVOeuIcFm19\nl1HXTGPXkPmZhkR+yM6pJ4D7gClmVgr4GrgbKAFMM7N2pB8tNAdw93VmNg1YBxwEukYcBnQDJgBl\nSL+Lam5QHwtMNrMkYBfQIq8bJiJSXHy760favzKC+fte5NSDV/Ja03doGXtRgX39LE89FSY69SQi\n0WRT8h7ajRrGop+GUS3lGob+/RGaXV4nx+vJ66mn7B5RiIhIAUnauot2rw7hw99GcnbKjbzb4gMa\nX1Iz64lHiYJCRKSQ+HzjDtqPeYFlKa9SM/VWFt61jNgLzw67LQWFiEjYVn/1HR3GPsdKH895aS34\nb+uVXFHnzLDb+p2CQkQkJMvXb6XD+MGstde4kNYsu3stl9Q8PeuJBUxBISJSwD78fBMdJz1DYsw/\nubhEe1a3X8cFZ1cJu60jUlCIiBSQhE+/ptNrT5NUYiaXlu7I5/esp3a1U8JuK0sKChGRo+y9T5Lo\n+sbTfFXyba48tivv3LOBGlVPCrutbFNQiIgcJXOWfcG9/xzAxlLv0uCE7szvmET1UyuE3VaOKShE\nRPLZ7I/X0X36k2wp+T4NT7yfhE4vU61S+awnFlIKChGRfPLmB2vp8daTbCv1HxpXfJAlnUdz2kkn\nhN1WnikoRETyaNriT3ngX0+wvdSHNDnlIZZ3GkeViseH3Va+UVCIiOTSlIUreejtJ9hRahk3VerF\nmC6TObn8sWG3le8UFCIiOTRpwQp6//sJdpb6hGan9mZM5zeoWK5s2G0dNQoKEZFsGj9/GQ+/259d\nJT/l1tP68GqXaZx4fJmw2zrqFBQiIlkYO28pcXP7s7vkWv5RNY5XOr0ZFQFxiIJCROQIRr+7hL7z\n+7OnZCLNz4jjlU4zKXfcMWG3VeAUFCIiGYx+dwl958Wzp9QXtDjzEV7p1Jbjy5YOu63QKChERAKj\n5nzEI/P7s6fkelqc1TfqA+IQBYWIRL3IgLjjrEcY0amNAiKCgkJEopYCInsUFCISdRQQOaOgEJGo\noYDIHQWFiBR7v9/FpIDIlZjsDDKzjWb2qZmtMrNlQa2Cmc03s/VmNs/MykeMjzOzJDNLNLNGEfV6\nZrbGzDaY2ZCIemkzmxrMWWJm1fJzI0UkOo2Z+zEnP3AdXd9vyfVn/YM9T25gUo97FBI5lK2gANKA\nWHe/yN3rB7U+wAJ3rwUsBOIAzOw8oDlQG2gCjDAzC+aMBNq7e02gppk1Durtgd3uXgMYAgzO43aJ\nSBQbM/djTnmgCZ3fv53G1W5RQORRdoPCDjO2GTAxWJ4I3BwsNwWmunuKu28EkoD6ZlYFOMHdlwfj\nJkXMiVzXDKBhTjZCRATS34up0gPX0/n95lx7RjN2x29gygOdFBB5lN1rFA68Z2apwCh3HwNUdvdk\nAHffbmaVgrGnA0si5m4LainA1oj61qB+aM6WYF2pZrbHzCq6++7cbJSIRJfX3v+Enu/0Y1fJT7mt\nal9GdY7Ot9o4WrIbFFe4+3dmdgow38zWkx4ekTK+zgs70v+Ij4//fTk2NpbY2Nh8/LIiUpRMWbiS\nnm/Hs7PkSv5+ehxjurypgAASEhJISEjIt/WZe85+vptZP2A/0IH06xbJwWmlRe5e28z6AO7ug4Lx\nc4F+wKZDY4J6C6CBu3c5NMbdl5pZCeA7d690mK/tOe1XRIqfaYs/pcfMeJJLLeOWU/owpss9UfVu\nrjllZrj7EX8Bz0qW1yjM7FgzOz5YPg5oBKwFZgNtg2FtgFnB8mygRXAnU3XgXGCZu28H9ppZ/eDi\ndusMc9oEy7eRfnFcROR/vPnBWqo++A9avnMdl1ZpwM5Hv2RGr+4KiaMsO6eeKgMzzcyD8VPcfb6Z\nrQCmmVk70o8WmgO4+zozmwasAw4CXSMOA7oBE4AywBx3nxvUxwKTzSwJ2AW0yJetE5FiYfbH67h3\nWn+2lfoP15/Si9VdJxXLjxwtrHJ86ilMOvUkEl3eXb6eLlOfYHPJBVxXricTunajUoXjwm6ryMnr\nqSc9mS0ihc77q76k45Qn+Kbku1xb4UE+6vIKp510QthtRS0FhYgUGovXfEOHyU/xZYlZ/LXc/Szq\nPIxqlcpnPVGOKgWFiIRuybrNtJswgPUxM7jy2G7M65xE9VMrhN2WBBQUIhKaFRu20W7cQD7jDS49\npiPrO22gRtWTwm5LMlBQiEiB++ybZFqPfobVPomLS7Rj7T2J1Dnr/3t0SgoJBYWIFJj1W76n9ahn\nWZ4yhgvsLlbe8xl1zzk17LYkCwoKETnqvvnuB1qNfJ6PDoyktjdnabtP+XOtqmG3JdmkoBCRo2br\nzn20GTmURT8P5dzUZixu8wlXnn9W2G1JDikoRCTf7fjhJ9qOGM7cfc9xZkoj5t2xhGsvrhF2W5JL\nCgoRyTd79v/K3cNfYfauQZyaciUz/7GIZpfXCbstySMFhYjk2f5fDtBx5DimfTeAk1Mu4vWb3+X2\nBnXDbkvyiYJCRHLt1wMp3Dv6NSZu7E/5lJq8ev2b3N2oftYTpUhRUIhIjqWkptFz3HRe+aIfZVIr\n8cLfJtK96dVhtyVHiYJCRLItLc159LXZvLj6MUp4WfpfPozef7+GmJhcvzGpFAEKChHJUlqaM2jG\newxY8iip/MZD9QbQ/84bFRBRQkEhIpka8c4H9F3wCL+USKZrnSd49u5/ULJElh+OKcWIgkJEDmvS\nghX0/Pdj7CnxBW3O7cfLHe+iTGn9yIhG+lsXkf8x66PP6TL9MZJLLeW2qo8wpussji9bOuy2JEQK\nChEBYOHqr+gwOZ6NJedzfaVeTOj6mj6XWgAFhUjUW75+K23GPsUXMTOILXcfi7sOp+op5cJuSwoR\nBYVIlErcvJO7XhnIqrSJ/Ll0B9Z3Xq8PDZLDUlCIRJnNO/Zy1/Dn+eC34ZxPS30mhGRJQSESJb7f\n+zNthg/n3b3Pck7qDSxuq7f8luzJ9s3QZhZjZivNbHbwuoKZzTez9WY2z8zKR4yNM7MkM0s0s0YR\n9XpmtsbMNpjZkIh6aTObGsxZYmbV8msDRaLd/l8O0PL5kVQeUINPv1/G7Fv/Q9Jz4xUSkm05eWrm\nfmBdxOs+wAJ3rwUsBOIAzOw8oDlQG2gCjDCzQ49vjgTau3tNoKaZNQ7q7YHd7l4DGAIMzuX2iEjg\nwMFUuox8jQqP1ea9rbOY2Hg2W1+Yzo2X1g67NSlishUUZlYVuB4YE1FuBkwMlicCNwfLTYGp7p7i\n7huBJKC+mVUBTnD35cG4SRFzItc1A2iY800REUh/u42+k2ZR7uG6TFk/kueuGsf3L87lroYXh92a\nFFHZvUbxItALKB9Rq+zuyQDuvt3MKgX104ElEeO2BbUUYGtEfWtQPzRnS7CuVDPbY2YV3X13TjZG\nJNq9MHMR/Rb3JYWf6X3xQOLvuEHvxyR5lmVQmNkNQLK7rzaz2EyGer51BUf8zo6Pj/99OTY2ltjY\n2Hz8siJF06QFK3jgnb7sK/E1nf/0JC92uF3vxxTFEhISSEhIyLf1mXvmP9/N7GngLtKPCMoCJwAz\ngUuAWHdPDk4rLXL32mbWB3B3HxTMnwv0AzYdGhPUWwAN3L3LoTHuvtTMSgDfuXulDK1gZp5VvyLR\nZM6yL+g49VG+K/ExLU57jFe7tOPYMqXCbksKGTPD3XN9aJnlrxzu3tfdq7n72UALYKG7twLeBtoG\nw9oAs4Ll2UCL4E6m6sC5wDJ33w7sNbP6wcXt1hnmtAmWbyP94riIHMHSxC3U6tWBG9+8mgtPvpSd\njyUx5YFOCgk5KvLyHMUzwDQza0f60UJzAHdfZ2bTSL9D6iDQNeIwoBswASgDzHH3uUF9LDDZzJKA\nXaQHkohksH7L99w5ciAr0yZwWdnOzO+2gTMrnxh2W1LMZXnqqTDRqSeJVtt37+fOl19k0c9DqeO3\nM7njo3qaWrItr6ee9GS2SCG2/5cDtH15FDO/H8AZKQ1Z0Gopf6t7TthtSZRRUIgUQimpaXQf/Tpj\nvnqcCqm1ef3mudzeoG7YbUmUUlCIFCJpac4Tb8xh0Iq+lPTjeOFvE+je9Oqw25Iop6AQKSRGzfmI\n3vP78FvMLh686GmeuqupHpaTQkFBIRKy2R+vo9M/+7Kz5CrantufEZ1aUbpUibDbEvmdgkIkJEsT\nt9BqXD++jHmHGyo9zOTuUznx+DJhtyXy/1FQiBSwr77dTYvhA/kkdRyXle3Me3oWQgo5BYVIAdm9\n7xfuHPYS8358jlppt7LinrXUq3Fa2G2JZElBIXKU/Xoghc6vTGTyln6cmvoX/t3yA5r8uVbYbYlk\nm4JC5ChJS3MefW02z6+Oo2zaKYy6bgYdrvtL2G2J5JiCQuQoGDXnI3rN782BmL30veQ5HmvRRLe6\nSpGloBDJR3OWfUGHN+LYUfIT2p77hG51lWJBQSGSD1Z/9R0tR8WzPuYtmpzSm8n3vk7FcmXDbksk\nXygoRPJg6859tHz5WT78bQQXl2pHUrf1nHNaxbDbEslXCgqRXNj/ywHufnk0b33/FGelNuK/d6/k\nijpnht2WyFGhoBDJgbQ0p9f4Nxm2Lo5yaefwxi3zaH71hWG3JXJUKShEsunlt/9Ln4W9SOM3nrh8\nBH1uuzbslkQKhIJCJAtzln1B+zf6sLPkKu6pOYBhHe+gZIksP25epNhQUIgcwWffJHP7K/Ek2gya\nnNKbKffpTfskOikoRDLY8cNPtBz2Aot+HspFJVqzvusX1Kh6UthtiYRGQSESOHAwlXtGjOe1bf04\nPeUqFrZZRuyFZ4fdlkjoFBQS9dLSnKenzeOpZb04Jq0CY66fyd2N6ofdlkihoaCQqDZt8ad0easX\nP5bYRM8LBzGgVTO9J5NIBlneumFmx5jZUjNbZWZrzaxfUK9gZvPNbL2ZzTOz8hFz4swsycwSzaxR\nRL2ema0xsw1mNiSiXtrMpgZzlphZtfzeUJFIKzZso2avdrT4dyMaVm3GngGfMbDNzQoJkcPIMijc\n/Tfgr+5+EVAXaGJm9YE+wAJ3rwUsBOIAzOw8oDlQG2gCjDCzQ//6RgLt3b0mUNPMGgf19sBud68B\nDAEG59cGikT6dtePXN3vceqPu4CTylRmY88NTHuoG8eWKRV2ayKFVrZuBnf3n4PFY0g/XeVAM2Bi\nUJ8I3BwsNwWmunuKu28EkoD6ZlYFOMHdlwfjJkXMiVzXDKBhrrZG5Ah+PZDCXUNGc8agWmzd/w3/\nbbWSJU8OpFql8llPFoly2bpGYWYxwCfAOcBwd19uZpXdPRnA3bebWaVg+OnAkojp24JaCrA1or41\nqB+asyVYV6qZ7TGziu6+O5fbJfK7Af+cx5NLH6JMWkXG3zib1tdcEnZLIkVKtoLC3dOAi8ysHDDT\nzOqQflTxP8Pysa8jniiOj4//fTk2NpbY2Nh8/LJSnMz88DPumfEQ+2K+pueFg3WhWqJGQkICCQkJ\n+bY+c8/Zz3czewz4GegAxLp7cnBaaZG71zazPoC7+6Bg/FygH7Dp0Jig3gJo4O5dDo1x96VmVgL4\nzt0rHeZre077leiz5uvt3P7K46y3Wdxy0iNM7N6Z48uWDrstkdCYGe6e69+SsnPX08mH7mgys7LA\ntUAiMBtoGwxrA8wKlmcDLYI7maoD5wLL3H07sNfM6gcXt1tnmNMmWL6N9IvjIjmye98vNHryaeqO\nPp/jSpXjqx5f8Gbv+xQSInmUnVNPpwITg+sUMcA/3X2OmX0MTDOzdqQfLTQHcPd1ZjYNWAccBLpG\nHAZ0AyYAZYA57j43qI8FJptZErALaJEvWydRIS3N6T76DUZ9FUeV1PosaL2Uv9U9J+y2RIqNHJ96\nCpNOPUlGo+Z8RM/3HsRJ5dmGL9L1xivDbkmk0MnrqSc9mS1F0gefbeSOcQ/zbYmPuKfmQL31t8hR\npKCQImXrzn3c9tJAlh58ldjy97Gyx3hOLn9s2G2JFGsKCikS/nhn18epntaIFZ3WUK/GaWG3JRIV\nFBRS6L0wcxGPLH6A0n6CHpgTCYGCQgqt91d9SatJvdhZYjX3nf8sz979dz0wJxICBYUUOpt37OXv\nQ5/ik9TxNDrpIab2eEMfQSoSIgWFFBoHDqbSYfg4pnz7OOf49azu8hkXnF0l7LZEop6CQgqFobP+\nQ1xCD0r58Uy86R3uanhx2C2JSEBBIaFavOYb7pjQi+0xK+heZzDPt7tN1yFEChkFhYRixw8/ceuL\nA/nowEj+emIP1vSYTMVyZcNuS0QOQ0EhBSotzbl39OuM/qoPZ6Q1YGnHT/lzraphtyUimVBQSIGZ\ntGAF3d65jzQ7yPBr/0mn6y8PuyURyQYFhRx1a77ezj9G9uUrm0ubcwYwumsbvS+TSBGioJCjZv8v\nB2g5ZBj/3vsMl5S+m009vqDqKeXCbktEckhBIUfFU1Pn8uTyHpRPO5t37/yQxpfUDLslEcklBYXk\nq/dXfcmdkx5kd0wifesNIf7OG8JuSUTySEEh+WL77v3c8uIAlh58letO7s3UHtMpd9wxYbclIvlA\nQSF5kpbm9BjzT0Yk9eKMtAas6Ky3/xYpbhQUkmtvfrCW9jO685vt4aWGb+hjSEWKKQWF5Nim5D00\nG9KPNWlv0PyMeCbd14nSpUqE3ZaIHCUKCsm2lNQ07hk+gUnb+lLTm5HYfR21zjg57LZE5ChTUEi2\nTFm4ks5vd8NxJt74b727q0gUUVBIpr76djfNhj5KIm/RuvrTvNqtrZ6qFokyWf6LN7OqZrbQzD43\ns7Vmdl9Qr2Bm881svZnNM7PyEXPizCzJzBLNrFFEvZ6ZrTGzDWY2JKJe2symBnOWmFm1/N5QyZmU\n1DTaDB1DzaHnEWMxfNkjkfH3tVNIiESh7PyrTwEedPc6wGVANzP7E9AHWODutYCFQByAmZ0HNAdq\nA02AEWZ26AMGRgLt3b0mUNPMGgf19sBud68BDAEG58vWSa689v4nnPjQZbz1zThea/Iua555meqn\nVgi7LREJSZZB4e7b3X11sLwfSASqAs2AicGwicDNwXJTYKq7p7j7RiAJqG9mVYAT3H15MG5SxJzI\ndc0AGuZloyR3vvnuB85/uCtt5t9A87M788PzH9Ay9qKw2xKRkOXoGoWZnQXUBT4GKrt7MqSHiZlV\nCoadDiyJmLYtqKUAWyPqW4P6oTlbgnWlmtkeM6vo7rtztDWSKympaXQeOYnxm+OobbfwZY9EHUGI\nyO+yHRRmdjzpv+3f7+77zcwzDMn4Oi+O+FmY8fHxvy/HxsYSGxubj182+sz47xravdmVNDvA+Bve\npvU1l4TdkojkUUJCAgkJCfm2PnPP+ue7mZUE3gHedfehQS0RiHX35OC00iJ3r21mfQB390HBuLlA\nP2DToTFBvQXQwN27HBrj7kvNrATwnbtXOkwfnp1+JWvf7vqRm57vx6rU12hZ5UnG39tBD82JFFNm\nhrvn+sPos3sLyzhg3aGQCMwG2gbLbYBZEfUWwZ1M1YFzgWXuvh3Ya2b1g4vbrTPMaRMs30b6xXE5\nCtLSnAfHTueMZ2qz78APfN7tc6Y8oCerReTIsjyiMLMrgMXAWtJPLznQF1gGTAPOIP1oobm77wnm\nxJF+J9NB0k9VzQ/qFwMTgDLAHHe/P6gfA0wGLgJ2AS2CC+EZe9ERRR4sXP0VzSd2Y79t5bm/juTe\nm64KuyURKQB5PaLI1qmnwkJBkTv7fvqNm58bRMIvL3HdCb2Z0fMBji1TKuy2RKSA5DUo9GR2Mffs\nm+/zyEddOCntPP7b4ROuqHNm2C2JSBGjoCimPt+4gxuHPcgW+4C4usN4stVNYbckIkWUgqKYSUlN\no92wsbz23SNcXKYNSx/6nEoVjgu7LREpwhQUxcjMDz+jzfTOpFkKU295j+ZXXxh2SyJSDCgoioHv\n9/5M0+ee4uODr3L7GU8w8b6Out1VRPKNgqKIe2b6ezy2tDOnpl3Cym5rqHvOqWG3JCLFjIKiiErc\nvJMbX+rJJhbzWL0R9Lvj+rBbEpFiSkFRxKSlOZ1GTmLs5t7UK30XHz70GVUqHh92WyJSjCkoipD3\nV33JbRM78av9wKQb5+jjSEWkQCgoioCffz3I359/gXk/PssNleOY3vN+ypTWX52IFAz9tCnkpixc\nyT1vd6Csn0xCu+VcfUH1sFsSkSijoCikdu/7hSaD41l+cALtzx3MqC6tiYnJ9Vu1iIjkmoKiEBry\nrwR6//ceqng91nRfw/nVK4fdkohEMQVFIbJ5x14aP9ebJJ/Dw3WHM6BV07BbEhFRUBQW8VP+zVOr\nO1PDrufrXp9RrVL5sFsSEQEUFKH76tvdNHqhB5v5gEGXT6TnrX8LuyURkf+hoAhR7/Fv8Xzivfxf\nqdvY0muNHpwTkUJJQRGCzzfu4LqX7iXZPmXYX6fR9cYrw25JROSIYsJuINo8MGYaF4y8gFPLnsX2\nfqsVEiJS6OmIooAkbt5JoyHd2MFaRjeeRfvGl4bdkohItuiIogD0Hv8W5798AVXKnsl38SsVEiJS\npOiI4ihK2rqLa1/szrd8woiGb9Lp+svDbklEJMcUFEfJY5Pf5um1nal7zO2seHgVJ5c/NuyWRERy\nJctTT2Y21sySzWxNRK2Cmc03s/VmNs/Mykf8vzgzSzKzRDNrFFGvZ2ZrzGyDmQ2JqJc2s6nBnCVm\nVi0/N7Cgbd25j5q92vPMpz0YetVUPnn6BYWEiBRp2blGMR5onKHWB1jg7rWAhUAcgJmdBzQHagNN\ngBFmduid7EYC7d29JlDTzA6tsz2w291rAEOAwXnYnlANm72Y6oMupISVYFPcau696aqwWxIRybMs\ng8LdPwB+yFBuBkwMlicCNwfLTYGp7p7i7huBJKC+mVUBTnD35cG4SRFzItc1A2iYi+0I1Z79v3LJ\nIw/R44MWxNUdRuLg0Zx20glhtyUiki9ye42ikrsnA7j7djOrFNRPB5ZEjNsW1FKArRH1rUH90Jwt\nwbpSzWyPmVV099257K1AvZGwirtnt+Jk/sS6B9ZQ64yTw25JRCRf5dfFbM+n9QBk+qEL8fHxvy/H\nxsYSGxubj186+w4cTKXpoGeZ/+MLdKrxAsM73anPixCRQiEhIYGEhIR8W19ugyLZzCq7e3JwWmlH\nUN8GnBGbcQPSAAAHdklEQVQxrmpQO1I9cs63ZlYCKJfZ0URkUIRlaeIWGo9qBTgfdlzBZecV6evv\nIlLMZPwlun///nlaX3YfuDP+9zf92UDbYLkNMCui3iK4k6k6cC6wzN23A3vNrH5wcbt1hjltguXb\nSL84Xmg9MGYal024mL+cfB07nl2okBCRYs/cMz9rZGavA7HASUAy0A/4FzCd9COBTUBzd98TjI8j\n/U6mg8D97j4/qF8MTADKAHPc/f6gfgwwGbgI2AW0CC6EH64Xz6rfo+XbXT9y9cDubPaPGNPkdVpf\nc0kofYiI5JSZ4e65PjeeZVAUJmEFxZi5H9NlwZ2cG9OQRX1e0NuBi0iRkteg0JPZmUhJTeOGgYN4\n78ch9Dr/FQa1vSXslkRECpyC4gjWfL2dv77UihR+Y2mXT/hzraphtyQiEgq9e+xhPDP9PS56pR51\nyl9G8uCFCgkRiWo6oojw868HaTjgcZb9NonBf3lNn18tIoKC4ncffr6J615tyTFWjjX3r6LOWZWy\nniQiEgV06gno//ocrppUn6sr3cL25+YoJEREIkT1EcWBg6lc81R/Pvx5HC83eFOfXy0ichhRGxTr\nt3zPFc/fSQq/seq+FVxwdpWwWxIRKZSi8tTT+PnLqPPSxZxzfF22D16gkBARyURUHVGkpTl3DRnF\n1OTH6VVnlB6gExHJhqgJin0//cbF/TqzJW0F8+78kGsvrhF2SyIiRUJUBMVn3yRz+dBbKRdzKpsf\n+5hKFY4LuyURkSKj2F+jmLb4U+oOv5SLTryGjc9OU0iIiORQsT6ieHjCTJ79oiPda73M0HtuD7sd\nEZEiqVgGRVqa03jA0yza9woTGr+rz44QEcmDYhcUu/f9Qr3+7fk+7UuWdV1KvRqnhd2SiEiRVqyC\nYuvOfZw/oCnlYiqzuf9/qFiubNgtiYgUecXmYvb6Ld9T6+mGnH5Mbb5+9g2FhIhIPikWQbFiwzYu\nfLEBFxx/DWsHjqBkiWKxWSIihUKR/4m6cPVXXDb6KmIrtmLJkwOJicn1x8KKiMhhFOmgmPnhZ1w7\npQG3ndabuY/2CbsdEZFiqcgGxfj5y/j77IZ0Pmcwrz/YOex2RESKrUITFGZ2nZl9YWYbzOzhzMYm\nbt5J+/dv4pHzxzC88x0F1aKISFQqFEFhZjHAy0BjoA7Q0sz+dKTxtaudwsqOq3my1U0F1WKhk5CQ\nEHYLhYb2xR+0L/6gfZF/CkVQAPWBJHff5O4HgalAs8wm1D3n1AJprLDSP4I/aF/8QfviD9oX+aew\nBMXpwJaI11uDmoiIhKywBIWIiBRS5u5h94CZ/QWId/frgtd9AHf3QRnGhd+siEgR5O65fsissARF\nCWA90BD4DlgGtHT3xFAbExGRwvGmgO6eamb3AvNJPx02ViEhIlI4FIojChERKbyKzMXsnDyQV5yY\nWVUzW2hmn5vZWjO7L6hXMLP5ZrbezOaZWfmwey0oZhZjZivNbHbwOir3hZmVN7PpZpYYfH9cGsX7\n4gEz+8zM1pjZFDMrHS37wszGmlmyma2JqB1x280szsySgu+bRtn5GkUiKHL6QF4xkwI86O51gMuA\nbsG29wEWuHstYCEQF2KPBe1+YF3E62jdF0OBOe5eG7gQ+IIo3BdmdhrQHajn7heQfkq9JdGzL8aT\n/rMx0mG33czOA5oDtYEmwAgzy/Iid5EICnLxQF5x4e7b3X11sLwfSASqkr79E4NhE4Gbw+mwYJlZ\nVeB6YExEOer2hZmVA65y9/EA7p7i7nuJwn0RKAEcZ2YlgbLANqJkX7j7B8APGcpH2vamwNTg+2Uj\nkET6z9dMFZWg0AN5gJmdBdQFPgYqu3sypIcJUCm8zgrUi0AvIPLiWjTui+rA92Y2PjgNN9rMjiUK\n94W7fws8D2wmPSD2uvsConBfRKh0hG3P+LN0G9n4WVpUgiLqmdnxwAzg/uDIIuNdCMX+rgQzuwFI\nDo6wMjtcLvb7gvTTK/WA4e5eD/iJ9NMN0fh9cSLpv0GfCZxG+pHFnUThvshEnra9qATFNqBaxOuq\nQS0qBIfTM4DJ7j4rKCebWeXg/1cBdoTVXwG6AmhqZl8DbwB/M7PJwPYo3BdbgS3uviJ4/SbpwRGN\n3xfXAF+7+253TwVmApcTnfvikCNt+zbgjIhx2fpZWlSCYjlwrpmdaWalgRbA7JB7KkjjgHXuPjSi\nNhtoGyy3AWZlnFTcuHtfd6/m7meT/j2w0N1bAW8TffsiGdhiZjWDUkPgc6Lw+4L0U05/MbMywYXZ\nhqTf7BBN+8L436PsI237bKBFcFdYdeBc0h9wznzlReU5CjO7jvS7PA49kPdMyC0VCDO7AlgMrCX9\n8NGBvqT/5U4j/beDTUBzd98TVp8FzcwaAD3dvamZVSQK94WZXUj6Rf1SwNfA3aRf1I3GfdGP9F8e\nDgKrgA7ACUTBvjCz14FY4CQgGegH/AuYzmG23czigPak76v73X1+ll+jqASFiIiEo6icehIRkZAo\nKEREJFMKChERyZSCQkREMqWgEBGRTCkoREQkUwoKERHJlIJCREQy9f8AW9FsGP16Qe0AAAAASUVO\nRK5CYII=\n", + "image/png": "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\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -120,13 +129,18 @@ ], "source": [ "fig, ax = plt.subplots(1,1)\n", - "ax.plot(vol, area)\n", - "ax.plot(vol, np.pi * radius**2)" + "ax.plot(vol, area, c='r')\n", + "# ax.plot(vol, np.pi * radius**2)\n", + "ax.plot(vol, old_area, c='b')\n", + "# ax.plot(vol, 600* vol)\n", + "# ax.plot(vol, area - 600*vol)\n", + "ax.grid(True)\n", + "print((area - 600*vol)/area)\n" ] }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 40, "metadata": { "collapsed": false }, @@ -160,7 +174,7 @@ }, { "cell_type": "code", - "execution_count": 74, + "execution_count": 41, "metadata": { "collapsed": true }, @@ -172,11 +186,24 @@ }, { "cell_type": "code", - "execution_count": 75, + "execution_count": 42, "metadata": { "collapsed": false }, - "outputs": [], + "outputs": [ + { + "ename": "NameError", + "evalue": "global name 'k2' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0mV0\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;36m100\u001b[0m \u001b[1;31m# cubic meters ~ 16kgal\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0mtime\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mlinspace\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m24\u001b[0m\u001b[1;33m*\u001b[0m\u001b[1;36m3600\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0mfay_area\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mFay\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mV0\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mtime\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m\u001b[0m in \u001b[0;36mFay\u001b[0;34m(V0, t)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mFay\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mV0\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mt\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[1;32mreturn\u001b[0m \u001b[0mnp\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mpi\u001b[0m \u001b[1;33m*\u001b[0m \u001b[0mk2\u001b[0m\u001b[1;33m**\u001b[0m\u001b[1;36m2\u001b[0m \u001b[1;33m*\u001b[0m \u001b[1;33m(\u001b[0m\u001b[0mdelta\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;33m*\u001b[0m \u001b[0mg\u001b[0m \u001b[1;33m*\u001b[0m \u001b[0mV0\u001b[0m\u001b[1;33m**\u001b[0m\u001b[1;36m2\u001b[0m\u001b[1;33m/\u001b[0m\u001b[0mnp\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msqrt\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mvisc_w\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m**\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m1.\u001b[0m\u001b[1;33m/\u001b[0m\u001b[1;36m3\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;33m*\u001b[0m \u001b[0mnp\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msqrt\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mt\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mNameError\u001b[0m: global name 'k2' is not defined" + ] + } + ], "source": [ "V0 = 100 # cubic meters ~ 16kgal\n", "time = np.linspace(1, 24*3600)\n", @@ -185,32 +212,11 @@ }, { "cell_type": "code", - "execution_count": 85, + "execution_count": null, "metadata": { "collapsed": false }, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 85, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAcIAAAETCAYAAAClegbPAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3XmcVmX9//HXW1RwAYQMMVDcsDT9qiioaTZoKmYKmSim\niEtlLmXf+vZLbQGy1KzMJWlRU8QFcQtNv4CKY+EGKAiKIuoXFQxUUARXls/vj+sM3IwzwAwzc+7l\n/Xw85sGZ6z7XuT/3edzw4brOtSgiMDMzq1Qb5B2AmZlZnpwIzcysojkRmplZRXMiNDOziuZEaGZm\nFc2J0MzMKlouiVDSBpKelnRP9nsHSeMkzZQ0VlL7gnPPlzRL0vOSDiso7yFpmqQXJV1eUL6xpJFZ\nncclbVvw2qDs/JmSTm6pz2tmZsUrrxbhucCMgt/PAx6MiM8D44HzASTtChwH7AIcAQyTpKzOn4HT\nI2JnYGdJh2flpwMLI6I7cDlwaXatDsAvgZ7AvsDgwoRrZmaVqcUToaSuwNeAawuK+wLDs+PhQL/s\n+GhgZEQsi4jZwCygl6TOQNuImJSdd2NBncJr3QEcnB0fDoyLiEUR8S4wDujTlJ/NzMxKTx4twj8C\nPwEKl7TZKiLmA0TEPKBTVt4FeL3gvLlZWRdgTkH5nKxstToRsRxYJKnjGq5lZmYVrEUToaQjgfkR\nMRXQGk5tynXf1vQ+ZmZW4TZs4fc7ADha0teATYC2kkYA8yRtFRHzs27PN7Pz5wLbFNTvmpXVV15Y\n5w1JrYB2EbFQ0lygqladh+sKUpIXYDUza4SIKLnGR4u2CCPigojYNiJ2AAYA4yNiIHAvcEp22iBg\ndHZ8DzAgGwm6PbATMDHrPl0kqVc2eObkWnUGZcf9SYNvAMYCh0pqnw2cOTQrqy9W/0QwePDg3GMo\nlh/fC98L34s1/5Sqlm4R1ucSYJSk04BXSSNFiYgZkkaRRpguBc6KVXf7bOAGoA1wf0SMycqvA0ZI\nmgUsICVcIuIdSRcCk0ldr0MjDZoxM7MKllsijIhHgEey44XAV+s572Lg4jrKnwJ2r6P8Y7JEWsdr\nN5CSp5mZGeCVZWwtqqqq8g6haPherOJ7sYrvRelTKffrNhdJ4ftiZtYwkogSHCxTLM8IzcxKwnbb\nbcerr76adxi56tatG7Nnz847jCbjFmEd3CI0s/pkrZ68w8hVffegVFuEfkZoZmYVzYnQzMwqmhOh\nmZlVNCdCMzOraE6EZmZlZLvttmPTTTelXbt2tG3blnbt2jFv3ry8wypqToRmZmVEEvfddx/vvfce\nixcv5r333qNz5855h1XUnAjNzMpM7akNEUH//v3Zeuut6dixIwcffDAvvPACAE888QRduqy+Neuo\nUaPYZ599WizevDkRmplVgKOOOoqXX36ZefPmsdtuuzFw4EAA9ttvP9q1a8dDDz208tybbrqJU045\nJadIW54n1NfBE+rNrD7rMqFeTTClvLH/BG2//fYsWLCADTdMC4dVVVVx1113rXbO22+/TadOnXj/\n/ffZZJNNuOiii3jxxRe54YYbePvtt9luu+2YPXs2W265ZZ3vUW4T6r3EmplZE8v7/9GjR4+md+/e\nK39fsWIF5513HnfeeScLFixAEpJ4++232WabbRg4cCB77LEHH3/8MSNHjqR37971JsFy5K5RM7My\nU7u1duONNzJmzBiqq6t59913eemll1Y7b5tttmHvvffm7rvv5qabblrZbVop3CI0MytzixcvpnXr\n1nTo0IH333+fCy644FPnDBw4kIsvvpjXXnuNvn375hBlftwiNDMrI6rjAeWpp57K1ltvzec+9zl2\n3313DjzwwE+d881vfpNXXnmF/v3707p165YItWh4sEwdPFjGzOpTzrtPbL/99gwfPpyDDjpojeeV\n22AZtwjNzIzbbruNNm3arDUJlqMWTYSSWkt6UtIUSdMlDc7KB0uaI+np7KdPQZ3zJc2S9LykwwrK\ne0iaJulFSZcXlG8saWRW53FJ2xa8Nig7f6akk1vqc5uZFbMvf/nL/PCHP2TYsGF5h5KLFu8albRp\nRHwgqRXwKPAD4AhgcURcVuvcXYBbgJ5AV+BBoHtEhKQngXMiYpKk+4ErImKspDOB3SPiLEnHA9+I\niAGSOgCTgR6AgKeAHhGxqI4Y3TVqZnUq567RdeWu0fUUER9kh61Jo1Zr7mZdN68vMDIilkXEbGAW\n0EtSZ6BtREzKzrsR6FdQZ3h2fAdwcHZ8ODAuIhZFxLvAOGBly9PMzCpTiydCSRtImgLMAx4oSGbn\nSJoq6VpJ7bOyLsDrBdXnZmVdgDkF5XOystXqRMRyYJGkjmu4lpmZVbA8WoQrImIvUldnL0m7AsOA\nHSJiT1KC/EMTvmXJNdPNzKzl5DahPiLek1QN9Kn1bPAa4N7seC6wTcFrXbOy+soL67yRPYdsFxEL\n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\n", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "fig, ax = plt.subplots(1,1)\n", "ax.plot(time / 3600, fay_area)\n", @@ -232,7 +238,7 @@ }, { "cell_type": "code", - "execution_count": 77, + "execution_count": null, "metadata": { "collapsed": true }, @@ -273,32 +279,11 @@ }, { "cell_type": "code", - "execution_count": 80, + "execution_count": null, "metadata": { "collapsed": false }, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 80, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "fig, ax = plt.subplots(1,1)\n", "ax.plot(time / 3600, fay_area,label='Fay')\n", @@ -336,7 +321,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", - "version": "2.7.12" + "version": "2.7.11" } }, "nbformat": 4, diff --git a/experiments/gnome_weathering/Evaporation.ipynb b/experiments/gnome_weathering/Evaporation.ipynb new file mode 100644 index 000000000..9e485ab4f --- /dev/null +++ b/experiments/gnome_weathering/Evaporation.ipynb @@ -0,0 +1,75 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# some experiments with evaporation" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "from matplotlib import pyplot as plt\n", + "%matplotlib inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### transfer coeff:\n", + "\n", + "We are considering a transfer coeff that scales iwth \"sea spray\" -- i.e. it goes up with high wind speed.\n", + "\n", + "I (nor Robert) am not sure there is a basius for this -- water evaporated faster with sea spray, which I think is where Bill got the idea an coeff, but oil is no linger a surface film under these conditions, so who the heck knows? and if anything, I might expect morre atomization of the oil, rather than evaporation -- some of Joe Katz' work, and other GOMRI folks seem to indicate that.\n", + "\n", + "atomozatino woudl be a mass loss mechnism, but it would be the whole oil, not jsut teh light ends, like evaporation.\n", + "\n", + "but what difference does it actually make?\n", + "\n", + "Here's what we got:\n", + "\n", + "\n", + "\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 2", + "language": "python", + "name": "python2" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.12" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/experiments/property_experiments/gridcurrent/script_columbia_pymover.py b/experiments/property_experiments/gridcurrent/script_columbia_pymover.py index 83d0829d4..4e08ca34c 100644 --- a/experiments/property_experiments/gridcurrent/script_columbia_pymover.py +++ b/experiments/property_experiments/gridcurrent/script_columbia_pymover.py @@ -22,8 +22,8 @@ from gnome.movers import RandomMover, constant_wind_mover, GridCurrentMover from gnome.movers.py_wind_movers import PyWindMover -from gnome.environment.property_classes import WindTS, GridCurrent -from gnome.movers.py_current_movers import PyGridCurrentMover +from gnome.environment import WindTS, GridCurrent +from gnome.movers.py_current_movers import PyCurrentMover from gnome.outputters import Renderer import gnome.utilities.profiledeco as pd @@ -54,7 +54,7 @@ def make_model(images_dir=os.path.join(base_dir, 'images')): # default is 'forecast' LEs draw on top renderer = Renderer( mapfile, images_dir, image_size=(600, 1200)) - renderer.delay=15 + renderer.delay = 15 # renderer.viewport = ((-123.35, 45.6), (-122.68, 46.13)) # renderer.viewport = ((-122.9, 45.6), (-122.6, 46.0)) @@ -86,7 +86,7 @@ def make_model(images_dir=os.path.join(base_dir, 'images')): series.append((start_time + timedelta(hours=i[0]), i[1])) wind1 = WindTS.constant_wind('wind1', 0.5, 0, 'm/s') - wind2 = WindTS(timeseries = series, units='knots', extrapolate=True) + wind2 = WindTS(timeseries=series, units='knots', extrapolate=True) # wind = Wind(timeseries=series, units='knots') @@ -100,7 +100,7 @@ def make_model(images_dir=os.path.join(base_dir, 'images')): curr_file = get_datafile('COOPSu_CREOFS24.nc') curr = GridCurrent.from_netCDF(name='gc2', filename=curr_file,) - c_mover = PyGridCurrentMover(curr, extrapolate = True, default_num_method='Trapezoid') + c_mover = PyCurrentMover(curr, extrapolate=True, default_num_method='Trapezoid') # renderer.add_grid(curr.grid) # renderer.add_vec_prop(curr) diff --git a/experiments/property_experiments/gridcurrent/script_roms_pymover.py b/experiments/property_experiments/gridcurrent/script_roms_pymover.py index 0b96f39b7..1d8b95e7e 100644 --- a/experiments/property_experiments/gridcurrent/script_roms_pymover.py +++ b/experiments/property_experiments/gridcurrent/script_roms_pymover.py @@ -18,8 +18,8 @@ from gnome.movers import RandomMover, constant_wind_mover, GridCurrentMover from gnome.outputters import Renderer -from gnome.environment.property_classes import GridCurrent -from gnome.movers.py_current_movers import PyGridCurrentMover +from gnome.environment import GridCurrent +from gnome.movers.py_current_movers import PyCurrentMover import gnome.utilities.profiledeco as pd # define base directory @@ -34,7 +34,7 @@ def make_model(images_dir=os.path.join(base_dir, 'images')): model = Model(start_time=start_time, duration=timedelta(hours=12), - time_step=.25*3600) + time_step=.25 * 3600) mapfile = (os.path.join(base_dir, 'coast.bna')) @@ -63,39 +63,39 @@ def make_model(images_dir=os.path.join(base_dir, 'images')): 27.5475, 0.0), release_time=start_time, - end_release_time=start_time+timedelta(hours=24)) + end_release_time=start_time + timedelta(hours=24)) spill2 = point_line_release_spill(num_elements=500, start_position=(-82.73888, 27.545, 0.0), release_time=start_time, - end_release_time=start_time+timedelta(hours=24)) + end_release_time=start_time + timedelta(hours=24)) spill3 = point_line_release_spill(num_elements=500, start_position=(-82.73888, 27.5425, 0.0), release_time=start_time, - end_release_time=start_time+timedelta(hours=24)) + end_release_time=start_time + timedelta(hours=24)) spill4 = point_line_release_spill(num_elements=500, start_position=(-82.73988, 27.5475, 0.0), release_time=start_time, - end_release_time=start_time+timedelta(hours=24)) + end_release_time=start_time + timedelta(hours=24)) spill5 = point_line_release_spill(num_elements=500, start_position=(-82.73988, 27.5450, 0.0), release_time=start_time, - end_release_time=start_time+timedelta(hours=24)) + end_release_time=start_time + timedelta(hours=24)) spill6 = point_line_release_spill(num_elements=500, start_position=(-82.73988, 27.5425, 0.0), release_time=start_time, - end_release_time=start_time+timedelta(hours=24)) + end_release_time=start_time + timedelta(hours=24)) model.spills += spill1 @@ -110,11 +110,11 @@ def make_model(images_dir=os.path.join(base_dir, 'images')): print 'adding a current mover:' fn = 'nos.tbofs.fields.n000.20160406.t00z_sgrid.nc' - #fn = 'dbofs_newFormat.nc' + # fn = 'dbofs_newFormat.nc' cf = GridCurrent.from_netCDF(filename=fn) - u_mover = PyGridCurrentMover(cf, extrapolate=True) - #u_mover = GridCurrentMover(fn) + u_mover = PyCurrentMover(cf, extrapolate=True) + # u_mover = GridCurrentMover(fn) renderer.add_grid(cf.grid) # renderer.add_vec_prop(cf) model.movers += u_mover diff --git a/experiments/property_experiments/num_method_scripts/script_multi_model.py b/experiments/property_experiments/num_method_scripts/script_multi_model.py index 531fb274e..e956cd8ae 100644 --- a/experiments/property_experiments/num_method_scripts/script_multi_model.py +++ b/experiments/property_experiments/num_method_scripts/script_multi_model.py @@ -23,7 +23,7 @@ from gnome.movers.py_wind_movers import PyWindMover from gnome.environment.property_classes import GridCurrent -from gnome.movers.py_current_movers import PyGridCurrentMover +from gnome.movers.py_current_movers import PyCurrentMover from gnome.outputters import Renderer, NetCDFOutput from gnome.environment.vector_field import ice_field @@ -72,7 +72,7 @@ def make_models(): release_time=start_time) mod.spills += spill mod.movers += RandomMover(diffusion_coef=100) - mod.movers += PyGridCurrentMover(current=curr, default_num_method=method) + mod.movers += PyCurrentMover(current=curr, default_num_method=method) images_dir = method + '-' + str(time_step / 60) + 'min-' + str(num_steps) + 'steps' renderer = Renderer(mapfile, images_dir, image_size=(1024, 768)) diff --git a/experiments/property_experiments/test_grid_gen_scripts/circular.py b/experiments/property_experiments/test_grid_gen_scripts/circular.py index 1f853373f..b495ecc35 100644 --- a/experiments/property_experiments/test_grid_gen_scripts/circular.py +++ b/experiments/property_experiments/test_grid_gen_scripts/circular.py @@ -1,28 +1,23 @@ import numpy as np -from datetime import datetime from pysgrid import SGrid from gnome.environment.grid_property import GriddedProp import os from datetime import datetime, timedelta -import numpy as np from gnome import scripting from gnome import utilities -from gnome.utilities.remote_data import get_datafile from gnome.model import Model -from gnome.map import MapFromBNA, GnomeMap -from gnome.environment import Wind from gnome.spill import point_line_release_spill from gnome.movers import RandomMover, constant_wind_mover, GridCurrentMover -from gnome.environment.property_classes import GridCurrent -from gnome.movers.py_current_movers import PyGridCurrentMover +from gnome.environment import GridCurrent +from gnome.movers.py_current_movers import PyCurrentMover from gnome.outputters import Renderer, NetCDFOutput x, y = np.mgrid[-30:30:61j, -30:30:61j] @@ -33,13 +28,13 @@ g = SGrid(node_lon=x, node_lat=y) g.build_celltree() -t = datetime(2000,1,1,0,0) -angs = -np.arctan2(y,x) -mag = np.sqrt(x**2 + y**2) +t = datetime(2000, 1, 1, 0, 0) +angs = -np.arctan2(y, x) +mag = np.sqrt(x ** 2 + y ** 2) vx = np.cos(angs) * mag vy = np.sin(angs) * mag -vx = vx[np.newaxis,:] * 20 -vy = vy[np.newaxis,:] * 20 +vx = vx[np.newaxis, :] * 20 +vy = vy[np.newaxis, :] * 20 # vx = 1/x[np.newaxis,:] # vy = 1/y[np.newaxis,:] # vx[vx == np.inf] = 0 @@ -54,18 +49,18 @@ # value[:,0] = x # value[:,1] = y -vels_x = GriddedProp(name='v_x',units='m/s',time=[t], grid=g, data=vx) -vels_y = GriddedProp(name='v_y',units='m/s',time=[t], grid=g, data=vy) -vg = GridCurrent(variables = [vels_y, vels_x], time=[t], grid=g, units='m/s') -point = np.zeros((1,2)) -print vg.at(point,t) +vels_x = GriddedProp(name='v_x', units='m/s', time=[t], grid=g, data=vx) +vels_y = GriddedProp(name='v_y', units='m/s', time=[t], grid=g, data=vy) +vg = GridCurrent(variables=[vels_y, vels_x], time=[t], grid=g, units='m/s') +point = np.zeros((1, 2)) +print vg.at(point, t) # define base directory base_dir = os.path.dirname(__file__) def make_model(): - duration_hrs=48 - time_step=900 + duration_hrs = 48 + time_step = 900 num_steps = duration_hrs * 3600 / time_step mod = Model(start_time=t, duration=timedelta(hours=duration_hrs), @@ -78,13 +73,13 @@ def make_model(): 0.5, 0.0), release_time=t, - end_release_time=t+timedelta(hours=4) + end_release_time=t + timedelta(hours=4) ) mod.spills += spill - method='Trapezoid' + method = 'Trapezoid' images_dir = method + '-' + str(time_step / 60) + 'min-' + str(num_steps) + 'steps' - renderer = Renderer(output_dir=images_dir, image_size=(800,800)) + renderer = Renderer(output_dir=images_dir, image_size=(800, 800)) renderer.delay = 5 renderer.add_grid(g) renderer.add_vec_prop(vg) @@ -92,7 +87,7 @@ def make_model(): renderer.graticule.set_max_lines(max_lines=0) mod.outputters += renderer - mod.movers += PyGridCurrentMover(current=vg, default_num_method=method, extrapolate=True) + mod.movers += PyCurrentMover(current=vg, default_num_method=method, extrapolate=True) mod.movers += RandomMover(diffusion_coef=10) netCDF_fn = os.path.join(base_dir, images_dir + '.nc') @@ -109,7 +104,7 @@ def make_model(): startTime = datetime.now() for step in model: if step['step_num'] == 0: - rend.set_viewport(((-10,-10), (10,10))) + rend.set_viewport(((-10, -10), (10, 10))) # if step['step_num'] == 0: # rend.set_viewport(((-175, 65), (-160, 70))) print "step: %.4i -- memuse: %fMB" % (step['step_num'], diff --git a/experiments/wave_checks/check_waves.ipynb b/experiments/wave_checks/check_waves.ipynb index ca7d66c56..ede378e78 100644 --- a/experiments/wave_checks/check_waves.ipynb +++ b/experiments/wave_checks/check_waves.ipynb @@ -1,270 +1,420 @@ { - "metadata": { - "name": "", - "signature": "sha256:7e6322ff77095b3775b1c7fad8eafef1265e8ec326f69cf9bce56f277117b631" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ + "cells": [ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Notebook to check waves code\n", - "\n", - "Is doing reasonable things?\n", - "\n", - "Does it match SPM?\n", - "\n", - "etc...." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "# plotting imports\n", - "import numpy as np\n", - "%pylab inline" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "Populating the interactive namespace from numpy and matplotlib\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING: pylab import has clobbered these variables: ['f']\n", - "`%matplotlib` prevents importing * from pylab and numpy\n" - ] - } - ], - "prompt_number": 146 - }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Notebook to check waves code\n", + "\n", + "Is doing reasonable things?\n", + "\n", + "Does it match SPM?\n", + "\n", + "etc...." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# plotting imports\n", + "import numpy as np\n", + "%matplotlib inline\n", + "from matplotlib import pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import datetime\n", + "# first imort stuff from GNOME:\n", + "import gnome\n", + "import gnome.environment.waves\n", + "reload(gnome.environment.waves)\n", + "Waves = gnome.environment.waves.Waves\n", + "\n", + "from gnome.environment import wind\n", + "from gnome.environment import Water\n", + "from gnome.basic_types import datetime_value_2d" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## fetch unlimited wave height" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# create some sample wind and water objects\n", + "#start_time = datetime.datetime(2014, 12, 1, 0)\n", + "# 5 m/s\n", + "#series = np.array((start_time, (5, 45)),\n", + "# dtype=datetime_value_2d).reshape((1, ))\n", + "test_wind_5 = wind.constant_wind(5, 45)\n", + "default_water = Water()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "w = Waves(test_wind_5, default_water)\n", + "U = range(40)\n", + "H = [w.compute_H(u) for u in U]\n", + "#print H" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "import datetime\n", - "# first imort stuff from GNOME:\n", - "import gnome\n", - "import gnome.environment.waves\n", - "reload(gnome.environment.waves)\n", - "Waves = gnome.environment.waves.Waves\n", - "\n", - "from gnome.environment import wind\n", - "from gnome.environment import Water\n", - "from gnome.basic_types import datetime_value_2d" - ], - "language": "python", + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 5, "metadata": {}, - "outputs": [], - "prompt_number": 147 + "output_type": "execute_result" }, { - "cell_type": "markdown", + "data": { + "image/png": "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\ncDIwMzPg/wO6PWqCWDEA4AAAAABJRU5ErkJggg==\n", + "text/plain": [ + "" + ] + }, "metadata": {}, - "source": [ - "## fetch unlimited wave height" - ] - }, + "output_type": "display_data" + } + ], + "source": [ + "fig, ax = plt.subplots()\n", + "ax.plot(U,H)\n", + "fig.suptitle('wave height with unlimited fetch')\n", + "ax.set_xlabel('wind speed ($m/s$)')\n", + "ax.set_ylabel('H (m)')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**NOTE:** This seem to be pretty high for the large wind speeds -- which we dont really expect to do well anyway, but still. I think there are issues with wave hieght being depth limitied, etc, when it gets big. Also tiem scale -- a 3 or 6 or whatever time average isn't going to cut it with waves potentially this big." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "water = Water()\n", + "w = Waves(test_wind_5, water)\n", + "H = []\n", + "# fetch range from 1km to 100km\n", + "fetch = range(1000, 100000, 1000)\n", + "for f in fetch:\n", + " water.fetch = f\n", + " H.append(w.compute_H(10))" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "# create some sample wind and water objects\n", - "#start_time = datetime.datetime(2014, 12, 1, 0)\n", - "# 5 m/s\n", - "#series = np.array((start_time, (5, 45)),\n", - "# dtype=datetime_value_2d).reshape((1, ))\n", - "test_wind_5 = wind.constant_wind(5, 45)\n", - "default_water = Water()" - ], - "language": "python", + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 7, "metadata": {}, - "outputs": [], - "prompt_number": 148 + "output_type": "execute_result" }, { - "cell_type": "code", - "collapsed": false, - "input": [ - "w = Waves(test_wind_5, default_water)\n", - "U = range(40)\n", - "H = [w.compute_H(u) for u in U]\n", - "#print H" - ], - "language": "python", + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAZMAAAEhCAYAAAC6Hk0fAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3XmYFOW1x/HvQcFdiCZxQQUMKGKiqBE3NCNwWeKCelXA\nCKLGqHFJzDWK2cTEuEASNCoYjKKCiBgX0IjiwgiK7CCgILiDENwVFVlmzv3jrWHaziwNPdXVy+/z\nPPNQXV1dfbqm6DPvbu6OiIhINholHYCIiBQ+JRMREcmakomIiGRNyURERLKmZCIiIllTMhERkawp\nmUjeMLNJZnbwJhy/m5mNzeC41bXs72lmbWt57ttmNs3MZpvZUZnGlHaOU81soZlVpH8uM7vKzJaa\n2SIz67o550873ywz27IBznOCmV2xia8ZYWanZPveUtiUTKRguftKdz89k0Nr2X8SsH8tz3UB5rv7\nIe7+YibxmFn6/6cFwMnA82nH7QecDuwH9ACGmpll8h61vG8LYLm7b9jcc1Rx98fcfVC255HSo2Qi\n/8XMfm1mF0fbQ8zs2Wi7k5ndG20PNbMZZrbAzK6O9nU3swdSzvMjMxsfbXc1s6nRX9APmNm2tbz9\n6WY23cwWV5UIzKyRmQ2K9s8zs/Oi/S3MbEG0vU103oVm9nBUqqgqDZiZXRu9dqqZfcfMjgBOBAaZ\n2Rwza5US94HAjcBJ0XNbmVkfM5sf/dyQcuxqM/uLmc0FDk/9IO7+mrsvBdITRU9gjLtvcPe3gaVA\nhxp+D6ujz73QzCaa2aFR6e11Mzs+5dAewJPRdRoRxfiymf0i7XyNzOyNaLtZVGLqGD2ebGZ7m9lZ\nZnZLtG+Emd1sZi9G73lKyrlujUpVE4Hv1vK7lBKiZCI1mQwcHW0fAmxnZlsAHYEp0f7fuHsH4ECg\nzMy+DzwNHGZm20TH9ALuN7Odgd8Cnd39h8Bs4P9qee8t3P0w4DJgYLTvXODTaH8H4GfRX+NQXer4\nOfCxu38f+D2QWq20HTDV3dtH8Z/n7i8B44Ffu/vB7v5W1cHu/jLwB8IX/sHATsANQBnQHjjUzE5M\nOfdL7n6Qu0+t5TOlaw4sS3n8XrQv3XbAM9Fn+gL4E9AZOCXartIdeDKKrbm7H+DuBwIjUk/m7pXA\na1HJ6ChgFnC0mTWJXvdm1aEpL9vV3Y8CTiAkWKKk0sbd9wPOAo7M8HNLEVMykZrMBg4xs+2BtcBL\nwKGEBFOVTHqb2WxgLtAOaOfuFYQvtROi5HMc4Qv78OiYF6O/4PsBe9Xy3g+nxFCVMLoC/aLXTid8\nubdJe11HYAyAu79CqGKqstbdn0g5b8vMLsNGhwKT3P3j6Av5PuCY6LmKlJgzVVOVVk1VcWvdfWK0\nvQB4Pnr/BUSfwcwaExLB28CbQKuoNNENqKmtaArwoyj+6wm/00OBmbXE+iiAuy+iugRyNHB/tH8l\n8FxtH1RKR9YNdlJ83H2Dmb0DnA28CMwHjgX2dvfFZtaSULI4xN0/N7MRwNbRy8cCFwGfADPc/cuo\nPWCiu/8kg7dfG/1bQfX9acAl7v506oEppZOqY2qzPmU79byZsjrOv8Y3fYK75cCeKY/3AFbUcFxq\n3JVE18bdPUrWEL7YX4j2fxpV0XUDzie0y5ybds4XgAuA3QgluCsIJa7JtcS6NmU79RpoUj/5BpVM\npDaTgcujf6u+gOZFz+1IqHZZbWa7EOrsq5QTqpjOA6raT6YBR5nZ92Bj+0Z6yaImVV9eTwE/r+qt\nZGZtUqrSqrxAqFbDzNoBP6jhPOlWR5+lPtOBY8xsp+hLvA/hc9Z17nSpx40nlOyaRG01rYEZ9bym\ntue6AxMAourELdz9EUKiOKiWz3IkUOnu6wi/0/OpLnFm8hkmR/E3MrPdCH9oSIlTMpHaTAF2JbQH\nvA+sIfrr1d3nE76EFgGjiP4yjp6rBB4nfMk9Hu37EOhPaD95mVBttm8N75n+127V438CrwJzogb3\n2/nv0sVQ4NtmthD4I7AQ+KyW81YZA/zaQvffVrUcg7v/B7iKkEDmArPd/fF6zo2ZnWRmywjVfI+b\n2YTofK8SSnCvAk8AP6+ldFPXX/9Vz5VR3VusOVAeVQeOBAbU8FnWAe8SfgcQfs/bu/uC9GNreH+P\nzvEI8DrwCnA3kGlbkRQx0xT0UgwsdMtt7O5rzWxv4Blgn4boLpuvzKw5MNzdj0s6FhElEykKUWeB\nSUDjaNcVKY3XIhIzJRMREcma2kxERCRrSiYiIpI1JRMREcmakomIiGRNyURERLKmZCIiIllTMhER\nkazFmkzM7E4zW2Vm82t5/oxo3YV5ZvaCmf2gpuNERCS/xV0yGUGYwbQ2bwLHROtMXAvcEXM8IiIS\ng1inoHf3F9KmCU9/flrKw2nUvECQiIjkuXxqM/kp0VTaIiJSWPJicSwzO5awEFPHpGMREZFNl3gy\nMbMDgOFAd3f/pI7jNCOliMhmcPdMF3HbbLmo5qp1yVMz2wt4COjr7m/UdyJ31487V199deIx5MuP\nroWuha5F3T+5EmvJxMxGE1aC29nM3gWuBpoQlrEeTlhadCdgaLRO+Hp37xBnTCIi0vDi7s11Rj3P\nn0dYK1xERApYPvXmkgyVlZUlHULe0LWopmtRTdci9wpmpUUz80KJVUQkX5gZXiQN8CIiUuSUTERE\nJGtKJiIikjUlExERyZqSiYhIEVm1CpLoq6RkIiJS4N5+G4YMgaOPhn33hbfeyn0M6hosIlKAFi2C\nhx8OP+++Cz17wimnQOfOsNVW1cflqmuwkomISAFwh7lzQ/J46CFYvTokj1NOgY4dYcta5jNRMkmj\nZCIipaayEmbMCMnjoYfADP73f8PPoYdCowwaKnKVTBKfgl5ERKpVVMDUqfCvf4VSyA47hOTx8MNw\n4IEhoeQjJRMRkYRt2ACTJ4cE8sgjsMsucOqpMHEi7Ldf0tFlRslERCQB69fDpEkhgTz6KLRoERLI\nlCnQunXS0W06JRMRkRxZtw6efTYkkHHjQtI49dTQLtKyZdLRZUcN8CIiMVq3Dp55Bh58EMaPD+NA\nTjsttIPstVf876/eXGmUTESkUFSVQMaODQmkbdvqBLLnnrmNRckkjZKJiOSz9evhuedCAnn00VAC\nOf30UI21xx7JxaVkkkbJRETyzYYNoRF97NjQC6tNG+jVK5kSSG00zkREJA9VVIRuvA88EMZ+tGoV\nSiBz5uSmDSRfKZmIiNSjsjIMJBwzJvTE2n33UAKZPj0kE1EyERGpkTvMmhUSyNix0KxZSCBTpoTq\nLPkmJRMRkYg7LFwYEsiYMbDFFtC7Nzz5JOy/f9LR5TclExEpea+/HpLH/feH2Xh79w7VWe3b5+9c\nWPlGvblEpCS9915oRL///rAeyGmnQZ8+cMQRmc3GWyjUNTiNkomIZOvjj0OJ4/77Yd48OOkkOOMM\nOPbY2tcDKXRKJmmUTERkc3z5JTz2GIweDc8/D926hRJIjx6w9dZJRxc/JZM0SiYikqn168N8WKNH\nh0RyxBGhBNKzJ+y4Y9LR5VZRJBMzuxM4Hljl7gfUcszfgR7Al0B/d59Xy3FKJiJSK3eYNg3uuy90\n5W3dOiSQ004L64OUqmIZAT8CuAW4t6YnzawH8D13b2NmhwG3A4fHHJOIFJHFi0MCGT0amjSBn/wE\nXnoJvve9pCMrLbEmE3d/wcxa1HFIT6JE4+7Tzaypme3i7qvijEtECtuqVaEr76hRoVdW795hiveD\nDlJX3qQk3X+hObAs5fF70T4lExH5hi+/DLPxjhoVSh4nngjXXQedOoXBhZKspJNJTX9DqGFERIAw\nqeJzz8HIkWFdkCOOgL59Q/fe7bZLOjpJlXQyWQ6kTtS8B7CitoMHDhy4cbusrIyysrK44hKRBC1Y\nAPfeG9pBdt01JJDBg0u7IT1T5eXllJeX5/x9Y+8abGYtgcfc/Qc1PPdj4CJ3P87MDgducvcaG+DV\nm0ukuP3nPyF5jBwJH30UGtL79oV27ZKOrLAVRW8uMxsNlAE7m9m7wNVAE8Ddfbi7P2FmPzaz1wld\ng8+OMx4RyS9r1oTqq3vuCe0gJ50Ef/0rlJUV15QmpUCDFkUkp9zD2iD33BPaPn74Q+jXD04+We0g\ncSiKkomISJV33glVWPfcE3pf9e8P8+cnuz66NBwlExGJzZdfhqVt774bXn45LG87ahR06KDxIMVG\nyUREGpQ7vPhiSCAPPQRHHgkXXAAnnFAaEyuWKiUTEWkQy5eH7rx33x2qsc4+G159FXbbLenIJBeU\nTERks61dC+PGwV13wYwZoRpr5EhVY5UiJRMR2WTz5oUEMno0HHggnHNOaBvZdtukI5OkKJmISEY+\n+SQkjzvvDIMKzz4bZs2Cli2TjkzygcaZiEitKiuhvDwkkH//G7p3D6WQzp01uWKhKIrFsRqSkolI\n7qxYERrS77wzDCQ891w480zYeeekI5NNpUGLIpJTGzbAE0/AP/8JU6aEFQrHjAkj1NWYLvVRMhEp\ncW+9FUogI0ZAixZw3nmhbWT77ZOOTAqJkolICVq3LkyweMcdMGdOmKF34kTYf/+kI5NCpWQiUkLe\neCMkkLvvhv32C6WQceM0Ml2yp2QiUuTWrQsJY/jwMD9W377w/POw775JRybFRMlEpEi99VYohdx1\nF7RtCz/7GZxyikohEg8lE5EismFDGA9y++0wc2YohUyaFKq0ROKkZCJSBFasCF1677gD9twTzj8/\nTG+yzTZJRyalQslEpEC5h1LHsGHwzDPQqxc8/niYK0sk15RMRArMp5+Gqd6HDYMtt4QLLwzjRHbc\nMenIpJQpmYgUiHnzYOhQePDBMEfW8OHQsaNGp0t+UDIRyWPr1oXVCm+9Fd59N7SFLFoEu+6adGQi\n36RkIpKH3nsP/vGP0KDerh383//BiSeGai2RfNQo6QBEJHAPEyz26gU/+AF8/DE8+2z4OeUUJRLJ\nb7o9RRK2Zk2YWPGWW+Crr+Dii0OJRA3qUki0nolIQpYtCw3q//xnWDP9kkuga1dopPoCaUC5Ws9E\nt61IDrnDCy/A6aeH8SBr1sDUqdWrGCqRSKFSNZdIDqxbBw88ADffDJ99BpdeGkokqsqSYqFqLpEY\nvf9+6JU1bFhYK+SXv4QePVQCkdxRNZdIAVu4MKybvu++YXzIxInw9NNw3HFKJFKcYr+tzay7mS02\nsyVmdmUNz+9pZs+Z2Rwzm2dmPeKOSSQOlZUwYUJoRO/aFVq1giVLQs+s738/6ehE4hVrNZeZNQKW\nAJ2BFcBMoLe7L0455h/AHHf/h5ntBzzh7q1qOJequSQvff01jBoFf/sbNG4Mv/oV9O4NW22VdGQi\nuavmirsBvgOw1N3fATCzMUBPYHHKMZVAVTNkM+C9mGMSaRAffBC69g4bBoccEsaJdOqkubKkNMVd\nzdUcWJbyeHm0L9U1QF8zWwY8DlwSc0wiWVmyJMzUu88+sHw5PPdc6NrbubMSiZSuuEsmNf3XSq+r\n6gOMcPchZnY4MArYv6aTDRw4cON2WVkZZWVlDROlSAamToXBg8M4kQsugMWLYZddko5K5JvKy8sp\nLy/P+fvG3WZyODDQ3btHjwcA7u43phyzEOjm7u9Fj98ADnP3D9POpTYTybnKSnjsMRg0CFauDO0h\nZ58N222XdGQimSmWNpOZQGszawGsBHoTSiKp3gG6APdEDfBbpScSkVxbuzY0qg8eDNtvD1dcockW\nReoS638Nd68ws4uBiYT2mTvdfZGZXQPMdPfHgcuBO8zsMkJj/FlxxiRSl88/D4MMb7opzNw7bBiU\nlaktRKQ+GgEvAqxaFaY6GT48jBG54gpo3z7pqESypxHwIjnw1lvw859D27ZhzqwZM8J08EokIptG\nyURK0iuvQN++8MMfQtOmoWfWbbfB3nsnHZlIYVIykZIycyacfHIYE9KuHbz5Jlx/vbr4imRLfVOk\nJEyeDNdeG0ogv/413HcfbLtt0lGJFA8lEyla7mGm3muvhRUr4KqrQtVWkyZJRyZSfJRMpOi4h+lN\n/vQn+OIL+O1vw8qGGiMiEh/995KiUVkJ48aFJFJRAb//fRhoqPVDROKnZCIFr7ISHnkE/vjHUPq4\n+mo44QQlEZFcUjKRglVZCQ8/DNdcE9YOufZaOP54jVYXSYKSiRScqpJIVRK54Qb48Y+VRESSpGQi\nBcMdxo8P1VhbbAHXXRfWVFcSEUmekonkPfewtvof/gAbNoS2kRNOUBIRySdKJpLXnn029Mr6/PNQ\nrXXyyWpYF8lHSiaSl6ZOhd/9LiyLO3Ag9OoVqrZEJD/pbzzJK/PmhXaQPn3gzDPh1VfhjDOUSETy\nnZKJ5IWlS0MC6dEDuneHJUvgnHM0al2kUCiZSKJWrIALLoAjjggrGy5dCpdcErr8ikjhUDKRRHzy\nCQwYEBJI06ahJPKb34T11kWk8CiZSE59/TX85S+wzz7w4Yfw8stw442w005JRyYi2VCNtORERQWM\nGhXGihx8cFhfZL/9ko5KRBpKxsnEzL4F7A6sAd5298rYopKiMnEiXHFFWIxq9Gg46qikIxKRhlZn\nMjGzpsBFQB+gCfABsDWwi5lNA4a6+6TYo5SC9PLLYVXDt98O82edfLJGrYsUq/pKJv8C7gWOdvdP\nU58ws0OAvma2t7vfGVeAUnhWrAij1h9/PPx7/vnQuHHSUYlInOpMJu7+P3U8NxuY3eARScH66qvQ\nuH7zzfDTn4YeWk2bJh2ViOTCprSZHAC0TH2Nuz8cQ0xSYCor4b77Qtfeo46C2bOhZcukoxKRXMoo\nmZjZXcABwCtAVcO7A0omJW7qVPjlL0NbyAMPwJFHJh2RiCQh05LJ4e7eLtZIpKAsWxYGHU6eDNdf\nH+bP0my+IqUr0//+L5mZkomwZg386U9w0EHwve/B4sVhQkYlEpHSlulXwD2EhPKamc03swVmNj+T\nF5pZdzNbbGZLzOzKWo453cxeic47KtPgJXfcw3rr7dqFLr+zZoVFqrbbLunIRCQfmLvXf5DZ68Cv\ngAVUt5ng7u/U87pGwBKgM7ACmAn0dvfFKce0Bh4AjnX3z83s2+7+YQ3n8kxilYa3aBFceimsXAl/\n/zt06pR0RCKSKTPD3WMf4ZVpyeQDdx/v7m+5+ztVPxm8rgOwNDp+PTAG6Jl2zHnAbe7+OUBNiUSS\nsXp1GLl+zDFw/PEwd64SiYjULNMG+LlmNhp4DFhbtTODrsHNgWUpj5cTEkyqfQDM7AVCcrvG3Z/K\nMC6JgXvomXX55dClCyxcCLvsknRUIpLPMk0m2xCSSNeUfZl0Da6paJVeV7Ul0Bo4BtgLmGJm+1eV\nVFINHDhw43ZZWRllZWX1xS2baPFiuPhi+OCDkFA0j5ZIYSkvL6e8vDzn75tRm8lmn9zscGCgu3eP\nHg8A3N1vTDlmGPCSu98bPX4GuDIaYZ96LrWZxGjNGvjzn+Ef/whrr190kVY5FCkGedFmYma/M7Na\nV5ows05mdnwdp5gJtDazFmbWBOgNjE875lGgU3S+bwNtgDczCV4axpNPwve/H1Y5fPll+MUvlEhE\nZNPU95WxAHjMzL4G5lA9a3AboD3wDHBdbS929wozuxiYSEhcd7r7IjO7Bpjp7o+7+1Nm1tXMXgE2\nAJe7+ydZfzKp18qVYfT67NkwdCh065Z0RCJSqDLtGtwGOArYjbCeySJgsruviTe8b8Sgaq4GUlkJ\nw4eHGX1/9rNQrbXNNklHJSJxyFU1V0aVGe6+FFgacyySA4sWwXnnhYQyaVKo3hIRyZYmwSgR69aF\nEevHHBPm0XrhBSUSEWk4amYtATNmwDnnQKtWMGcO7Lln0hGJSLFRMiliX30FV18NI0fCTTdBr15a\nNldE4lHfGvC38N+DDDdy90sbPCJpEFOmwLnnwiGHwIIF8J3vJB2RiBSz+koms1K2rwGujjEWaQBf\nfRVWPBw7NnT3PemkpCMSkVKQ8Qh4M5vr7gfFHE9d76+uwfV48UXo3x86dAiz++68c9IRiUjS8qpr\ncETf5Hnq66/hD38IbSNDh8LJJycdkYiUGjXAF7i5c6FvX9h3X5g/X20jIpKM+hrgV1NdItnWzKpm\n8jXChI07xhmc1K6iAgYNgr/9DYYMgZ/8RD21RCQ5dSYTd98hV4FI5t56C/r1C5Mxzp4Ne+2VdEQi\nUuo0Ar7AjBoVGth79oRnn1UiEZH8oDaTAvHZZ2GNkTlz4OmnoX37pCMSEammkkkBmDYNDjoIdtgB\nZs1SIhGR/KOSSR6rrAyN7EOGhBUQNQBRRPKVkkmeWrUqdPldsyaURjQ5o4jkM1Vz5aFJk+Dgg+Gw\nw8K2EomI5DuVTPJIZSVcdx3cdlsYzd6lS9IRiYhkRskkT3z4IZx5Jnz5ZajWat486YhERDKnaq48\nMGNGmCr+wANDtZYSiYgUGpVMEuQOd9wBv/td6K2lCRpFpFApmSTk66/DIMTp08N67Pvsk3REIiKb\nT9VcCXjvPSgrg88/DwMSlUhEpNApmeTY1Klhbq0TTwyrIW6/fdIRiYhkT9VcOTRiBFx5Zfj3uOOS\njkZEpOEomeRARQUMGACPPgqTJ0PbtklHJCLSsJRMYrZ6NZxxBnzxRWgf0brsIlKM1GYSo+XLoWNH\n2H13mDhRiUREilfsycTMupvZYjNbYmZX1nHcqWZWaWYHxx1TLsybB0ccEUa13347NG6cdEQiIvGJ\ntZrLzBoBtwKdgRXATDMb5+6L047bHrgEmBZnPLnyxBPQvz8MHQqnnpp0NCIi8Yu7ZNIBWOru77j7\nemAM0LOG4/4E3AisjTme2N11F5x7Lowbp0QiIqUj7mTSHFiW8nh5tG8jM2sP7OHuT8QcS6zc4dpr\nw8/zz4cqLhGRUhF3by6rYZ9vfNLMgCHAWfW8BoCBAwdu3C4rK6OsrCzrABtCRQVcemkYkPjii7Db\nbklHJCKlqry8nPLy8py/r7l7/Udt7snNDgcGunv36PEAwN39xujxjsDrwBeEJLIr8BFworvPSTuX\nxxnr5lq3Dvr1g/ffD+NIdtwx6YhERKqZGe5e6x/pDSXukslMoLWZtQBWAr2BPlVPuvvnwHerHpvZ\nJOBX7j435rgaxFdfhXaRJk1Co/vWWycdkYhIMmJtM3H3CuBiYCLwCjDG3ReZ2TVmdnxNL6GOaq58\n8tln0L17GDvy4INKJCJS2mKt5mpI+VTN9fHH0K1bmLDxllugkYZ+ikieylU1l74GN9GHH0LnznDM\nMXDrrUokIiKgZLJJ3n8fOnUK1Vt/+QtYQVTIiYjET8kkQx98AMceCyedBNddp0QiIpJKbSYZ+Pjj\nkEhOOCEMShQRKRS5ajNRMqnHp59Cly4hmQwapBKJiBQWJZM0SSST1auha1c49FC4+WYlEhEpPEom\naXKdTNauDUvrtmoFw4crkYhIYVIySZPLZFJRAb17Q2UljB0LW2yRk7cVEWlwxTKdSsFxh4sugo8+\nClOkKJGIiNRPySTNNdfAzJkwaZKmSBERyZSSSYp77gk/06Zp9l8RkU2hNpPIpEnQqxeUl0O7drG9\njYhITmlurhxatCgkkjFjlEhERDZHySeTjz6C448PAxI7dUo6GhGRwlTS1VwbNkCPHtC+PQwe3KCn\nFhHJC6rmyoEBA8JgxOuvTzoSEZHCVrK9uUaPhkceCd2AtyzZqyAi0jBKsppr/vywwNWzz8IBBzTI\nKUVE8pKquWLyxRdw+ukwZIgSiYhIQym5kkn//qGdZMSI7GMSEcl3mpsrBiNHwvTpMGtW0pGIiBSX\nkimZvPYadOyodhIRKS1qM2lAGzZA375hEkclEhGRhlcSyWTwYGjaFC68MOlIRESKU9FXcy1cGNZv\nnzULWrSIITARkTymaq4GsGEDnH02/PnPSiQiInEq6mQyaBA0awbnnZd0JCIixa1oq7neeAMOOwxm\nz1apRERKV9FUc5lZdzNbbGZLzOzKGp6/zMxeMbN5Zva0me3ZEO972WVw+eVKJCIiuRBrMjGzRsCt\nQDdgf6CPmbVNO2wOcIi7twceArKeDH7ChLDg1WWXZXsmERHJRNwlkw7AUnd/x93XA2OAnqkHuPvz\n7v519HAa0DybN1y3Dn75S7jpJthqq2zOJCIimYo7mTQHlqU8Xk7dyeJcYEI2b3jzzdC6NRx3XDZn\nERGRTRH33Fw1NfrU2IpuZmcChwA/2tw3W7kSbrwRXnppc88gIiKbI+5kshzYK+XxHsCK9IPMrAtw\nFXBMVB1Wo4EDB27cLisro6ys7BvP33BDmDalTZusYhYRKVjl5eWUl5fn/H1j7RpsZlsArwGdgZXA\nDKCPuy9KOeYg4EGgm7u/Uce56uwavHIl7L8/vPoq7LprQ30CEZHCVhRdg929ArgYmAi8Aoxx90Vm\ndo2ZHR8dNgjYDnjQzOaa2aOb816DB0O/fkokIiJJKIpBi6tWwX77hXm4dt89x4GJiOSxoiiZ5Mrg\nwXDmmUokIiJJKfiSyfvvQ9u2sGABNM9qhIqISPFRySRDQ4ZAnz5KJCIiSSroksn69bDnnjB5Muyz\nT0KBiYjkMZVMMjBhQhjtrkQiIpKsgk4md90VFr8SEZFkFWw116pVoeH93Xdhhx0SDExEJI+pmqse\no0ZBz55KJCIi+aAgk4k7jBgB55yTdCQiIgIFmkxmzYKvv4ajj046EhERgQJNJlUN7xZ7LaCIiGSi\n4BrgN2yAPfaAmTPDGBMREaldrhrgCy6ZAHz6KTRrlnBAIiIFQMkkTX3rmYiIyH9T12ARESkYSiYi\nIpI1JRMREcmakomIiGRNyURERLKmZCIiIllTMhERkawpmYiISNaUTEREJGtKJiIikjUlExERyZqS\niYiIZE3JREREsqZkIiIiWYs9mZhZdzNbbGZLzOzKGp5vYmZjzGypmb1kZnvFHZOIiDSsWJOJmTUC\nbgW6AfsDfcysbdph5wIfu3sb4CZgUJwxFYPy8vKkQ8gbuhbVdC2q6VrkXtwlkw7AUnd/x93XA2OA\nnmnH9ATuibb/BXSOOaaCp/8o1XQtqulaVNO1yL24k0lzYFnK4+XRvhqPcfcK4FMz2ynmuEREpAHF\nnUxqWip2DsAKAAAHUUlEQVQyfe3d9GOshmNERCSPxboGvJkdDgx09+7R4wGAu/uNKcdMiI6ZbmZb\nACvd/bs1nEsJRkRkM+RiDfgtYz7/TKC1mbUAVgK9gT5pxzwGnAVMB04DnqvpRLm4GCIisnliTSbu\nXmFmFwMTCVVqd7r7IjO7Bpjp7o8DdwIjzWwp8BEh4YiISAGJtZpLRERKQ0GMgK9v4GMhMrM9zOw5\nM3vVzBaY2aXR/m+Z2UQze83MnjKzpimv+Xs0uHOembVP2X9WdG1eM7N+KfsPNrP50XM35fYTbjoz\na2Rmc8xsfPS4pZlNiz7X/Wa2ZbS/1oGuZnZVtH+RmXVN2V8w95CZNTWzB6PP8IqZHVaq94WZXWZm\nC6N474t+9yVxX5jZnWa2yszmp+yL/T6o6z3q5O55/UNIeK8DLYDGwDygbdJxNcDn2hVoH21vD7wG\ntAVuBK6I9l8J3BBt9wD+HW0fBkyLtr8FvAE0BZpVbUfPTQc6RNtPAN2S/tz1XJPLgFHA+OjxA8Bp\n0fYw4Pxo+0JgaLTdCxgTbbcD5hKqb1tG940V2j0E3A2cHW1vGf1uS+6+AHYH3gSapNwPZ5XKfQF0\nBNoD81P2xX4f1PYe9cab9AXL4IIeDkxIeTwAuDLpuGL4nI8CXYDFwC7Rvl2BRdH27UCvlOMXAbsQ\n2piGpewfFv1H2hV4NWX/N47Ltx9gD+BpoIzqZPIB0Cj9PgCeBA6LtrcA3q/p3gAmRP+xCuYeAnYA\n3qhhf8ndF4Rk8k70hbglMB74H+D9UrkvCIkuNZnEfh/U8B6LM4m1EKq5Mhn4WNDMrCXhL5BphF/i\nKgB3/w9Q1U26tuuQvv+9lP3Lazg+Xw0Bfk00xsjMdgY+cffK6PnU+NMHun5mYaBrXdeiUO6hvYEP\nzWxEVOU33My2pQTvC3dfAfwVeJcQ/2fAHODTErwvqnw3B/dB+r32nUwCK4RkksnAx4JlZtsTppH5\nhbt/Qe2frbbBnbVdn4K5bmZ2HLDK3edRHbfx35/BU55LVxTXgvAX+MHAbe5+MPAl4S/mUrwvmhGm\nW2pBKKVsR6jOSVcK90V9Er8PCiGZLAdSZxLeA1iRUCwNKmo4/Bcw0t3HRbtXmdku0fO7Eor0EK7D\nnikvr7oOtV2f2o7PR0cBJ5rZm8D9QCfCpJ9NLUwWCt+Mf+NnszDQtam7f8KmX6N8tBxY5u6zoscP\nEZJLKd4XXYA33f3jqKTxCHAk0KwE74squbgP/lPLe9SpEJLJxoGPZtaEULc3PuGYGspdhHrLm1P2\njQf6R9v9gXEp+/vBxpkFPo2Kok8B/xP1APoWoU75qah4+rmZdTAzi147jjzk7r9x973cfW/C7/c5\ndz8TmEQYyAqh4TX1WpwVbacOdB0P9I569bQCWgMzKKB7KPqdLjOzfaJdnYFXKMH7glC9dbiZbR3F\nWnUtSum+SC+h5+I+SH2P1Otbt6QbmDJshOpO6O20FBiQdDwN9JmOAioIPUjmEuqCuwM7Ac9En/dp\noFnKa24l9D55GTg4ZX//6NosAfql7D8EWBA9d3PSnznD6/IjqhvgWxF6nCwh9OBpHO3fChgbfa5p\nQMuU118VXaNFQNdCvIeAAwlfdPOAhwk9cUryvgCujn6X8wmzizculfsCGE0oLawlJNazCZ0RYr0P\n6rrX6vrRoEUREclaIVRziYhInlMyERGRrCmZiIhI1pRMREQka0omIiKSNSUTERHJmpKJiIhkTclE\nSoqZXWphDZmRdRxzoJnVNAdU+nGrM3zPnmb2u02M8+mM15EQyQNKJlJqLgS6uHvfOo5pD/w4g3Nl\nOuL3CmBohsdWuRe4aBNfI5IYJRMpGWY2jDDF+wQz+4WZbRutZjfdzGab2Qlm1hj4I3B6NAX8aWa2\nnZndFa1KN8/MTq4+pV0b7ZtqZv81VbeZtQG+dvePo8cjzGxotBLg62Z2TBTDq2Z2V8pLHwP6xHtF\nRBqOkomUDHe/kLCeQ5mHyTV/Czzr7ocRZir+C2EK+D8AD7j7we7+IPB7wsR5B7h7e6onENwOmBrt\nmwKcV8PbHkWYdy1VM3c/AvgVIWn81d3bAQeY2QFRrJ8CTaLJ+UTynpKJlJrUWVi7AgPMbC5QDjTh\nm9N1V+kC3Fb1wN0/izbXuvsT0fZswpKw6XYjrBiZ6rHo3wXAf9z91ejxK2nn+ICwjodI3tsy6QBE\nEva/7r40dUc0hXe6mtpH1qdsV1Dz/6c1wI5p+9ZG/1ambFc9Tj3H1tHrRfKeSiZSyp4CLq16YGbt\no83VfDMBTAQuSTmuWdVmBu+xCGhTx/N1nWMX4O0M3kMkcUomUmpSSxjXAo2jhvX5hIZ3CIsvtatq\ngI+O+5aZLYiqxMpqOFdtJhN6h9X0/umPN26b2SHANK9e61wkr2k9E5GYmdkQ4DF3f67eg6tfcxMw\nzt0nxReZSMNRyUQkftcB227iaxYokUghUclERESyppKJiIhkTclERESypmQiIiJZUzIREZGsKZmI\niEjW/h8BxcXuolqjRwAAAABJRU5ErkJggg==\n", + "text/plain": [ + "" + ] + }, "metadata": {}, - "outputs": [], - "prompt_number": 149 - }, + "output_type": "display_data" + } + ], + "source": [ + "fig, ax = plt.subplots()\n", + "ax.plot(fetch, H)\n", + "fig.suptitle('wave height for 10 m/s wind')\n", + "ax.set_xlabel('fetch (m)')\n", + "ax.set_ylabel('H (m)')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## NOTE:\n", + "\n", + "This seems kind of small -- only just over 1 m waves for 20 knot wind?\n", + "\n", + "We need to check against the SPM.\n", + "\n", + "This calculator:\n", + "https://woodshole.er.usgs.gov/project-pages/coastal_model/Tools/fetch_limited.html\n", + "\n", + "gives 1.8m for 10m/s and very high fetch and duration (and deep water).\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Whitecap Fraction\n", + "\n", + "Note that the slolution for $U < 4$ is pretty fudged -- used to be zero for $U < 3$, now it is a linear interpolation form 0 to the $U=4$ value. maybe an exponential or quadratic would be better?" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "water = Water()\n", + "w = Waves(test_wind_5, water)\n", + "wf = [] # whitecap fraction\n", + "U = np.arange(0,20,0.2)\n", + "for u in U:\n", + " wf.append(w.whitecap_fraction(u))" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "fig, ax = plt.subplots()\n", - "ax.plot(U,H)\n", - "fig.suptitle('wave height with unlimited fetch')\n", - "ax.set_xlabel('wind speed ($m/s$)')\n", - "ax.set_ylabel('H (m)')" - ], - "language": "python", + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 15, "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 150, - "text": [ - "" - ] - }, - { - "metadata": {}, - "output_type": "display_data", - "png": "iVBORw0KGgoAAAANSUhEUgAAAYEAAAEnCAYAAABPHP/XAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3XmcXuP9//HXW6xJVJtaog2iVUqTNiGWWscStdfeUoTW\nUhRdqKVfRPFTiu70a22KSlFLLCWRZmSpJYlEFkRt/aIkIUWCrPP5/XGdkdtkZjIzmXvOued+Px+P\n+3Gf+9xn+dwnk/M513Wdc12KCMzMrDqtlHcAZmaWHycBM7Mq5iRgZlbFnATMzKqYk4CZWRVzEjAz\nq2JOAtZuJNVK2qoVy39O0p0tWG5eE/O/KWnz1sRYsu7+ks7Opg8s3U5rf0cr9nmspN9l0ydJOrqV\n64/L3jeSdEQb9v8nSYc0Mv/LkiZLmijpC82sf15b92HF5SRg7alVD51ExH8i4rAV2O5BwBat2WfJ\nvu+PiMuzjwc22E65Hp75eLsR8b8RcUurVo7YIZvcGDiyjftv7LcdCNwZEVtFxMvNrH9uC/dhFcRJ\noBOSdJak07LpX0kamU3vJunWbPpaSeMlTZM0OJu3l6Q7SrZTI+n+bHpPSf/MrhbvkNStid0fJulJ\nSTMk7Zit20XSLyU9JekZSSdm83tLmppNd822O13S3ZKekLRlSSyXZFerj0taV9L2wP7ALyVNKr2C\nzfb3cjb9aUlLSmIZLWmT+qtySV8v2c7TJdtZ5nc0OMYfH5vs8+8lDcqmX5U0ODtWUyRt1sj6gyX9\nJJuulXR19u/xnKStJd0j6QVJF5esU18i+gWwU/a7z5C0UhPHV1lcz0saAawLqEEc+wBnACeX/J0c\nlf32SZL+mG3/F8Aa2bxbsuWOyfY3WdKQks3uLGmcpJdcKig+J4HOaTSwUzY9AOgmaeVs3mPZ/PMi\nYmvga8AukvoAI4BtJa2RLfMt4HZJawM/A3aPiK2AicCPm9h3l4jYFvghcGE273vAuxGxDbANcIKk\n3g3WOwV4JyK+ApwPlFbHdAMej4h+2W87ISL+CQwDzoyI/qVXsBGxBJghaQtgxyzenSWtBvSKiBdL\nln28ZDtblmynsd/RnNKr7ABmZ8fqWuDMbL6aWX5B9u9xLXAf8H2gD3CspM+ULAdwNjAm+92/AY6n\n8eN7ELApsDlwDLA9Da7UI+Ih4I/A1RGxu1K12OHA9hHRH6gDvhMR5wAfZfs8WtJXSH8Tu2b/LmeU\n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\n", - "text": [ - "" - ] - } - ], - "prompt_number": 150 + "output_type": "execute_result" }, { - "cell_type": "markdown", + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, "metadata": {}, - "source": [ - "**NOTE:** This seem to be pretty high for the large wind speeds -- which we dont really expect to do well anyway, but still. I think there are issues with wave hieght being depth limitied, etc, when it gets big. Also tiem scale -- a 3 or 6 or whatever time average isn't going to cut it with waves potentially this big." - ] - }, + "output_type": "display_data" + } + ], + "source": [ + "fig, ax = plt.subplots()\n", + "ax.plot(U, wf)\n", + "fig.suptitle('Whitecap Fraction')\n", + "ax.set_xlabel('Wind speed (m/s)')\n", + "ax.set_ylabel('Fraction')\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false + }, + "source": [ + "## Energy Disspation rate\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "water = Water()\n", + "w = Waves(test_wind_5, water)\n", + "H = np.linspace(0, 3, 10)\n", + "eps = w.dissipative_wave_energy(H)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "water = Water()\n", - "w = Waves(test_wind_5, water)\n", - "H = []\n", - "# fetch range from 1km to 100km\n", - "fetch = range(1000, 100000, 1000)\n", - "for f in fetch:\n", - " water.fetch = f\n", - " H.append(w.compute_H(10))" - ], - "language": "python", + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 18, "metadata": {}, - "outputs": [], - "prompt_number": 151 + "output_type": "execute_result" }, { - "cell_type": "code", - "collapsed": false, - "input": [ - "fig, ax = plt.subplots()\n", - "ax.plot(fetch, H)\n", - "fig.suptitle('wave height for 10 m/s wind')\n", - "ax.set_xlabel('fetch (m)')\n", - "ax.set_ylabel('H (m)')" - ], - "language": "python", + "data": { + "image/png": "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\nSUVORK5CYII=\n", + "text/plain": [ + "" + ] + }, "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 152, - "text": [ - "" - ] - }, - { - "metadata": {}, - "output_type": "display_data", - "png": "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\n", - "text": [ - "" - ] - } - ], - "prompt_number": 152 - }, + "output_type": "display_data" + } + ], + "source": [ + "fig, ax = plt.subplots()\n", + "ax.plot(H, eps)\n", + "fig.suptitle('Energy Dissipation rate')\n", + "ax.set_xlabel('Wave height (m)')\n", + "ax.set_ylabel('Energy Dissipatioin rate (kg/s^3)')" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Whitecap Fraction\n", - "\n", - "Note that the slolution for $U < 4$ is pretty fudged -- used to be zero or $U < 3$, now it is a linear interpolation form 0 to the $U=4$ value. maybe an exponential or quadratic would be better?" + "name": "stdout", + "output_type": "stream", + "text": [ + "rate for 1m wave: 34.099243 kg/m^3\n" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "water = Water()\n", - "w = Waves(test_wind_5, water)\n", - "wf = [] # whitecap fraction\n", - "U = np.arange(0,20,0.2)\n", - "for u in U:\n", - " wf.append(w.comp_whitecap_fraction(u))" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 153 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "fig, ax = plt.subplots()\n", - "ax.plot(U, wf)\n", - "fig.suptitle('Whitecap Fraction')\n", - "ax.set_xlabel('Wind speed (m/s)')\n", - "ax.set_ylabel('Fraction')\n" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 154, - "text": [ - "" - ] - }, - { - "metadata": {}, - "output_type": "display_data", - "png": "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\nAAAASUVORK5CYII=\n", - "text": [ - "" - ] - } - ], - "prompt_number": 154 - }, + } + ], + "source": [ + "eps = w.dissipative_wave_energy(1)\n", + "print \"rate for 1m wave: %f kg/m^3\"%eps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Whitecap decay constant\n", + "\n", + "We use a formula from Monahan (JPO, 1971)\n", + "\n", + "The saltwater value for this constant is 3.85 sec while the freshwater value is 2.54 sec.\n", + "\n", + "this code interpolated for other salinities.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# make sure we get what we think we should get.\n", + "\n", + "from gnome.utilities.weathering.monahan import Monahan" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 154 + "name": "stdout", + "output_type": "stream", + "text": [ + "fresh water value: 2.54\n", + "salt water value: 3.84999995\n" + ] } ], - "metadata": {} + "source": [ + "print \"fresh water value:\", Monahan.whitecap_decay_constant(0)\n", + "print \"salt water value:\", Monahan.whitecap_decay_constant(35)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] } - ] -} \ No newline at end of file + ], + "metadata": { + "kernelspec": { + "display_name": "Python 2", + "language": "python", + "name": "python2" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.12" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/gnome1/gui_gnome/TVectMap.cpp b/gnome1/gui_gnome/TVectMap.cpp index f0cef8713..1ff4ccc17 100644 --- a/gnome1/gui_gnome/TVectMap.cpp +++ b/gnome1/gui_gnome/TVectMap.cpp @@ -504,7 +504,7 @@ OSErr TVectorMap::ImportMap (char *path) while (err == 0 && !InterruptFlag) { MySpinCursor(); // JLM 8/4/99 - if (++line >= numLinesText - 1) + if (++line > numLinesText - 1) ReadErrCode = 1; else { @@ -536,7 +536,7 @@ OSErr TVectorMap::ImportMap (char *path) { for (PointIndex = 1; PointIndex <= labs(PointCount); ++PointIndex) { - if (++line >= numLinesText - 1) + if (++line > numLinesText - 1) ReadErrCode = 1; else { @@ -588,7 +588,7 @@ OSErr TVectorMap::ImportMap (char *path) for (PointIndex = 1; PointIndex <= PointCount && err == 0; ++PointIndex) { // InterruptFlag = Progress ((long) (((double) TotalBytesRead / (double) FileSize) * 100.0 + 1), "", &ProgressWPtr); - if (++line >= numLinesText - 1) + if (++line > numLinesText - 1) ReadErrCode = 1; else { @@ -981,7 +981,7 @@ OSErr TVectorMap::ImportESIData (char *path) while (err == 0 && !InterruptFlag) { MySpinCursor(); // JLM 8/4/99 - if (++line >= numLinesText - 1) + if (++line > numLinesText - 1) ReadErrCode = 1; else { @@ -1034,7 +1034,7 @@ OSErr TVectorMap::ImportESIData (char *path) for (PointIndex = 1; PointIndex <= PointCount && err == 0; ++PointIndex) { // InterruptFlag = Progress ((long) (((double) TotalBytesRead / (double) FileSize) * 100.0 + 1), "", &ProgressWPtr); - if (++line >= numLinesText - 1) + if (++line > numLinesText - 1) ReadErrCode = 1; else { diff --git a/lib_gnome/GridCurrentMover_c.cpp b/lib_gnome/GridCurrentMover_c.cpp index 7751f60ab..970b6244a 100644 --- a/lib_gnome/GridCurrentMover_c.cpp +++ b/lib_gnome/GridCurrentMover_c.cpp @@ -287,7 +287,8 @@ WorldPoint3D GridCurrentMover_c::GetMove(const Seconds& model_time, Seconds time { OSErr err = 0; char errmsg[256]; - if (num_method == EULER) { //EULER + if (num_method == EULER) + { WorldPoint3D deltaPoint = {{0,0},0.}; WorldPoint3D refPoint; double dLong, dLat; @@ -320,44 +321,77 @@ WorldPoint3D GridCurrentMover_c::GetMove(const Seconds& model_time, Seconds time deltaPoint.p.pLat = dLat * 1000000; return deltaPoint; - } else { //RK4 + } + else if (num_method == TRAPEZOID) + { + WorldPoint3D startPoint, p1, deltaPoint; + startPoint.p = (*theLE).p; + startPoint.z = (*theLE).z; + p1.p = (*theLE).p; + p1.z = (*theLE).z; + VelocityRec v0, v1; + double dLong, dLat; + if(!fIsOptimizedForStep) + { + err = timeGrid->SetInterval(errmsg, model_time + timeStep); + if (err) return deltaPoint; + } + v0 = timeGrid->GetScaledPatValue(model_time, startPoint); + v0.u *= fCurScale; + v0.v *= fCurScale; + dLong = ((v0.u / METERSPERDEGREELAT) * timeStep) / LongToLatRatio3 (startPoint.p.pLat); + dLat = (v0.v / METERSPERDEGREELAT) * timeStep; + p1.p.pLong = p1.p.pLong + dLong * 1000000; + p1.p.pLat = p1.p.pLat + dLat * 1000000; + v1 = timeGrid->GetScaledPatValue(model_time + timeStep, p1); + v1.u *= fCurScale; + v1.v *= fCurScale; + v1.u += v0.u; + v1.v += v0.v; + dLong = ((v1.u / METERSPERDEGREELAT) * timeStep/2) / LongToLatRatio3 (startPoint.p.pLat); + dLat = (v0.v / METERSPERDEGREELAT) * timeStep/2; + deltaPoint.p.pLong = dLong * 1000000; + deltaPoint.p.pLat = dLat * 1000000; + return deltaPoint; + } + else + { WorldPoint3D deltaD[5] = {{0,0},0}; // [ dummy, dy1, dy2, dy3, dy4 ] - double RK_dy_Factors[4] = {0, .5, .5, 1}; - double RK_Factors[4] = {1./6., 1./3., 1./3., 1./6.}; - WorldPoint3D finalDelta = {{0,0},0}; - WorldPoint3D startPoint; // yn - startPoint.p = (*theLE).p; - startPoint.z = (*theLE).z; - VelocityRec scaledVel[4]; - double dLong, dLat; - Boolean useEddyUncertainty = false; - // check the interval and set if necessary each time - /*if(!fIsOptimizedForStep) - { - err = timeGrid->SetInterval(errmsg, model_time); - - if (err) return finalDelta; - }*/ - - for (int i = 0; i < 4; i++) { - WorldPoint3D RKDelta = scale_WP(deltaD[i], RK_dy_Factors[i]); - // could check the end time and only set interval if RK time is past the end time - err = timeGrid->SetInterval(errmsg, model_time + (Seconds)(timeStep*RK_dy_Factors[i])); - if (err) return finalDelta; - scaledVel[i] = timeGrid->GetScaledPatValue(model_time + (Seconds)(timeStep*RK_dy_Factors[i]), add_two_WP3D(startPoint, RKDelta)); - scaledVel[i].u *= fCurScale; - scaledVel[i].v *= fCurScale; - dLong = ((scaledVel[i].u / METERSPERDEGREELAT) * timeStep) / LongToLatRatio3 (startPoint.p.pLat); - dLat = (scaledVel[i].v / METERSPERDEGREELAT) * timeStep; - deltaD[i+1].p.pLong = dLong * 1000000; - deltaD[i+1].p.pLat = dLat * 1000000; - } + double RK_dy_Factors[4] = {0, .5, .5, 1}; + double RK_Factors[4] = {1./6., 1./3., 1./3., 1./6.}; + WorldPoint3D finalDelta = {{0,0},0}; + WorldPoint3D startPoint; // yn + startPoint.p = (*theLE).p; + startPoint.z = (*theLE).z; + VelocityRec scaledVel[4]; + double dLong, dLat; + Boolean useEddyUncertainty = false; + // check the interval and set if necessary each time + /*if(!fIsOptimizedForStep) + { + err = timeGrid->SetInterval(errmsg, model_time); - for (int i = 0; i <4; i++) { - finalDelta = add_two_WP3D(finalDelta, scale_WP(deltaD[i+1], RK_Factors[i])); - } + if (err) return finalDelta; + }*/ + + for (int i = 0; i < 4; i++) { + WorldPoint3D RKDelta = scale_WP(deltaD[i], RK_dy_Factors[i]); + // could check the end time and only set interval if RK time is past the end time + err = timeGrid->SetInterval(errmsg, model_time + (Seconds)(timeStep*RK_dy_Factors[i])); + if (err) return finalDelta; + scaledVel[i] = timeGrid->GetScaledPatValue(model_time + (Seconds)(timeStep*RK_dy_Factors[i]), add_two_WP3D(startPoint, RKDelta)); + scaledVel[i].u *= fCurScale; + scaledVel[i].v *= fCurScale; + dLong = ((scaledVel[i].u / METERSPERDEGREELAT) * timeStep) / LongToLatRatio3 (startPoint.p.pLat); + dLat = (scaledVel[i].v / METERSPERDEGREELAT) * timeStep; + deltaD[i+1].p.pLong = dLong * 1000000; + deltaD[i+1].p.pLat = dLat * 1000000; + } - return finalDelta; + for (int i = 0; i <4; i++) { + finalDelta = add_two_WP3D(finalDelta, scale_WP(deltaD[i+1], RK_Factors[i])); + } + return finalDelta; } } diff --git a/lib_gnome/GridWindMover_c.cpp b/lib_gnome/GridWindMover_c.cpp index 4c6725952..5001192a4 100644 --- a/lib_gnome/GridWindMover_c.cpp +++ b/lib_gnome/GridWindMover_c.cpp @@ -285,6 +285,36 @@ OSErr GridWindMover_c::TextRead(char *path, char *topFilePath) } +OSErr GridWindMover_c::GetScaledVelocities(Seconds model_time, VelocityFRec *velocities) +{ + return timeGrid->GetScaledVelocities(model_time, velocities); +} + +LongPointHdl GridWindMover_c::GetPointsHdl(void) +{ + if ((timeGrid->fVar.gridType)==CURVILINEAR) + { + return timeGrid->fGrid->GetPointsHdl(); + } + else + { + return timeGrid->GetPointsHdl(); + } +} + +GridCellInfoHdl GridWindMover_c::GetCellDataHdl(void) +{ + return timeGrid->GetCellData(); +} + +WORLDPOINTH GridWindMover_c::GetCellCenters(void) +{ // should rename this function... + //if (IsTriangleGrid()) + //return timeGrid->fGrid->GetCenterPointsHdl(); + //else + return timeGrid->GetCellCenters(); +} + long GridWindMover_c::GetNumTriangles(void) { long numTriangles = 0; @@ -294,3 +324,12 @@ long GridWindMover_c::GetNumTriangles(void) return numTriangles; } +long GridWindMover_c::GetNumPoints(void) +{ + long numPoints = 0; + + numPoints = (timeGrid->fNumRows) * (timeGrid->fNumCols); + + return numPoints; +} + diff --git a/lib_gnome/GridWindMover_c.h b/lib_gnome/GridWindMover_c.h index b3be7c328..3913ffc9c 100644 --- a/lib_gnome/GridWindMover_c.h +++ b/lib_gnome/GridWindMover_c.h @@ -73,9 +73,16 @@ class DLL_API GridWindMover_c : virtual public WindMover_c { OSErr TextRead(char *path,char *topFilePath); OSErr ExportTopology(char* path){return timeGrid->ExportTopology(path);} + OSErr GetScaledVelocities(Seconds model_time, VelocityFRec *velocity); + LongPointHdl GetPointsHdl(void); + WORLDPOINTH GetCellCenters(); + GridCellInfoHdl GetCellDataHdl(void); + bool IsRegularGrid(){return timeGrid->IsRegularGrid();} + OSErr get_move(int n, Seconds model_time, Seconds step_len, WorldPoint3D* ref, WorldPoint3D* delta, double* windages, short* LE_status, LEType spillType, long spill_ID); long GetNumTriangles(void); + long GetNumPoints(void); }; #endif diff --git a/lib_gnome/TimeGridVel_c.cpp b/lib_gnome/TimeGridVel_c.cpp index e1e713c1e..8085eceda 100644 --- a/lib_gnome/TimeGridVel_c.cpp +++ b/lib_gnome/TimeGridVel_c.cpp @@ -2732,7 +2732,11 @@ float TimeGridVelCurv_c::GetTotalDepth(WorldPoint refPoint,long ptIndex) { //if (triNum < 0) useTriNum = false; if (bVelocitiesOnNodes) - err = (dynamic_cast(fGrid))->GetRectCornersFromTriIndexOrPoint(&index1, &index2, &index3, &index4, refPoint, triNum, useTriNum, fVerdatToNetCDFH, fNumCols); + { + totalDepth = GetInterpolatedTotalDepth(refPoint); + return totalDepth; + } + //err = (dynamic_cast(fGrid))->GetRectCornersFromTriIndexOrPoint(&index1, &index2, &index3, &index4, refPoint, triNum, useTriNum, fVerdatToNetCDFH, fNumCols); else err = (dynamic_cast(fGrid))->GetRectCornersFromTriIndexOrPoint(&index1, &index2, &index3, &index4, refPoint, triNum, useTriNum, fVerdatToNetCDFH, fNumCols+1); diff --git a/lib_gnome/TimeGridVel_c.h b/lib_gnome/TimeGridVel_c.h index e83a10ad2..ab231c5e4 100644 --- a/lib_gnome/TimeGridVel_c.h +++ b/lib_gnome/TimeGridVel_c.h @@ -119,6 +119,7 @@ class DLL_API TimeGridVel_c virtual OSErr GetScaledVelocities(Seconds time, VelocityFRec *velocity){return -1;} virtual GridCellInfoHdl GetCellData() {return 0;} virtual WORLDPOINTH GetCellCenters() {return 0;} + virtual LongPointHdl GetPointsHdl() {return 0;} long GetNumTimesInFile(); long GetNumFiles(); @@ -140,6 +141,7 @@ class DLL_API TimeGridVel_c void DisposeAllLoadedData(); virtual bool IsTriangleGrid(){return false;} + virtual bool IsRegularGrid(){return false;} virtual bool IsDataOnCells(){return true;} virtual OSErr get_values(int n, Seconds model_time, WorldPoint3D* ref, VelocityRec* vels) {return 0;} }; @@ -199,6 +201,8 @@ class TimeGridVelRect_c : virtual public TimeGridVel_c virtual long GetNumDepthLevelsInFile(); // eventually get rid of this virtual OSErr TextRead(const char *path, const char *topFilePath); + + virtual bool IsRegularGrid(){return true;} }; @@ -219,7 +223,7 @@ class TimeGridVelCurv_c : virtual public TimeGridVelRect_c //virtual ClassID GetClassID () { return TYPE_TIMEGRIDVELCURV; } //virtual Boolean IAm(ClassID id) { if(id==TYPE_TIMEGRIDVELCURV) return TRUE; return TimeGridVelRect_c::IAm(id); } - LongPointHdl GetPointsHdl(); + virtual LongPointHdl GetPointsHdl(); TopologyHdl GetTopologyHdl(); OSErr ReadTimeData(long index,VelocityFH *velocityH, char* errmsg); VelocityRec GetScaledPatValue(const Seconds& model_time, WorldPoint3D refPoint); @@ -268,7 +272,7 @@ class TimeGridVelTri_c : virtual public TimeGridVelCurv_c virtual void Dispose (); //virtual ClassID GetClassID () { return TYPE_TIMEGRIDVELTRI; } //virtual Boolean IAm(ClassID id) { if(id==TYPE_TIMEGRIDVELTRI) return TRUE; return TimeGridVelCurv_c::IAm(id); } - LongPointHdl GetPointsHdl(); + virtual LongPointHdl GetPointsHdl(); void GetDepthIndices(long ptIndex, float depthAtPoint, long *depthIndex1, long *depthIndex2); OSErr ReadTimeData(long index,VelocityFH *velocityH, char* errmsg); VelocityRec GetScaledPatValue(const Seconds& model_time, WorldPoint3D refPoint); @@ -425,6 +429,10 @@ class DLL_API TimeGridWindRect_c : virtual public TimeGridVel_c { public: + LongPointHdl fPtsH; + WORLDPOINTH fCenterPtsH; + GridCellInfoHdl fGridCellInfoH; + TimeGridWindRect_c(); virtual ~TimeGridWindRect_c() { Dispose (); } virtual void Dispose(); @@ -437,6 +445,14 @@ class DLL_API TimeGridWindRect_c : virtual public TimeGridVel_c virtual OSErr ReadTimeData(long index,VelocityFH *velocityH, char* errmsg); virtual OSErr TextRead(const char *path, const char *topFilePath); + + virtual LongPointHdl GetPointsHdl(); + virtual WORLDPOINTH GetCellCenters(); + virtual GridCellInfoHdl GetCellData(); + + virtual OSErr GetScaledVelocities(Seconds time, VelocityFRec *velocity); + + virtual bool IsRegularGrid(){return true;} }; @@ -446,6 +462,8 @@ class DLL_API TimeGridWindCurv_c : virtual public TimeGridWindRect_c LONGH fVerdatToNetCDFH; // for curvilinear WORLDPOINTFH fVertexPtsH; // for curvilinear, all vertex points from file + //WORLDPOINTH fCenterPtsH; // for curvilinear, all vertex points from file + //GridCellInfoHdl fGridCellInfoH; Boolean bVelocitiesOnNodes; // default is velocities on cells @@ -475,6 +493,12 @@ class DLL_API TimeGridWindCurv_c : virtual public TimeGridWindRect_c virtual OSErr TextRead(const char *path, const char *topFilePath); virtual OSErr get_values(int n, Seconds model_time, WorldPoint3D* ref, VelocityRec* vels); + + virtual LongPointHdl GetPointsHdl(); + TopologyHdl GetTopologyHdl(); + virtual WORLDPOINTH GetCellCenters(); + virtual GridCellInfoHdl GetCellData(); + virtual OSErr GetScaledVelocities(Seconds time, VelocityFRec *velocity); }; diff --git a/lib_gnome/TimeGridWind_c.cpp b/lib_gnome/TimeGridWind_c.cpp index 45accc027..447e2c4be 100644 --- a/lib_gnome/TimeGridWind_c.cpp +++ b/lib_gnome/TimeGridWind_c.cpp @@ -59,16 +59,62 @@ }*/ TimeGridWindRect_c::TimeGridWindRect_c() : TimeGridVel_c() -{ - +{ + fPtsH = 0; + fGridCellInfoH = 0; + fCenterPtsH = 0; } void TimeGridWindRect_c::Dispose() { + if(fPtsH) {DisposeHandle((Handle)fPtsH); fPtsH=0;} + if(fGridCellInfoH) {DisposeHandle((Handle)fGridCellInfoH); fGridCellInfoH=0;} + if(fCenterPtsH) {DisposeHandle((Handle)fCenterPtsH); fCenterPtsH=0;} + TimeGridVel_c::Dispose (); } +LongPointHdl TimeGridWindRect_c::GetPointsHdl() +{ + long i, j, numPoints; + float fLat, fLong, dLong, dLat; + WorldRect gridBounds = fGridBounds; // loLong, loLat, hiLong, hiLat + LongPoint vertex; + OSErr err = 0; + + if (fPtsH) return fPtsH; + + numPoints = fNumRows*fNumCols; + dLong = (gridBounds.hiLong - gridBounds.loLong) / (fNumCols-1); + dLat = (gridBounds.hiLat - gridBounds.loLat) / (fNumRows-1); + fPtsH = (LongPointHdl)_NewHandle(numPoints * sizeof(LongPoint)); + if (!fPtsH) { + err = -1; + TechError("TriGridWindRect_c::GetPointsHdl()", "_NewHandle()", 0); + goto done; + } + + for (i=0; i(fGrid)); + VelocityFRec velocity; + + err = this -> SetInterval(errmsg, time); + if(err) return err; + + loaded = this -> CheckInterval(timeDataInterval, time); + + if(!loaded) return -1; + + ptsHdl = this->GetPointsHdl(); + if(ptsHdl) + numPoints = _GetHandleSize((Handle)ptsHdl)/sizeof(**ptsHdl); + else + numPoints = 0; + + // Check for time varying current + if((GetNumTimesInFile()>1 || GetNumFiles()>1) && loaded && !err) + { + // Calculate the time weight factor + if (GetNumFiles()>1 && fOverLap) + startTime = fOverLapStartTime + fTimeShift; + else + startTime = (*fTimeHdl)[fStartData.timeIndex] + fTimeShift; + + if (fEndData.timeIndex == UNASSIGNEDINDEX && (time > startTime || time < startTime) && fAllowExtrapolationInTime) + { + timeAlpha = 1; + } + else + { + endTime = (*fTimeHdl)[fEndData.timeIndex] + fTimeShift; + timeAlpha = (endTime - time)/(double)(endTime - startTime); + } + } + // for now just get every other one since each pair of triangles corresponds to a cell + for (i = 0 ; i< numPoints; i+=1) + //for (i = 0 ; i< numTri; i++) + { + + longPt = (*ptsHdl)[i]; + wp.pLat = longPt.v; + wp.pLong = longPt.h; + index = GetVelocityIndex(wp); // regular grid + + //if (index < 0) {scaled_velocity[i].u = 0; scaled_velocity[i].v = 0;}// should this be an error? + //index = i; + // Should check vs fFillValue + // Check for constant current + if(((GetNumTimesInFile()==1 && !(GetNumFiles()>1)) || timeAlpha == 1) && index!=-1) + { + velocity.u = GetStartUVelocity(index); + velocity.v = GetStartVVelocity(index); + } + else if (index!=-1)// time varying current + { + velocity.u = timeAlpha*GetStartUVelocity(index) + (1-timeAlpha)*GetEndUVelocity(index); + velocity.v = timeAlpha*GetStartVVelocity(index) + (1-timeAlpha)*GetEndVVelocity(index); + } + if (velocity.u == fFillValue) velocity.u = 0.; + if (velocity.v == fFillValue) velocity.v = 0.; + /*if ((velocity.u != 0 || velocity.v != 0) && (velocity.u != fFillValue && velocity.v != fFillValue)) // should already have handled fill value issue + { + // code goes here, fill up arrays with data + float inchesX = (velocity.u * refScale * fVar.fileScaleFactor) / arrowScale; + float inchesY = (velocity.v * refScale * fVar.fileScaleFactor) / arrowScale; + }*/ + //u[i] = velocity.u * fVar.fileScaleFactor; + //v[i] = velocity.v * fVar.fileScaleFactor; + scaled_velocity[i].u = velocity.u * fVar.fileScaleFactor / 100.; + scaled_velocity[i].v = velocity.v * fVar.fileScaleFactor / 100.; + //vel_index++; + } + return err; +} + +WORLDPOINTH TimeGridWindRect_c::GetCellCenters() +{ + OSErr err = 0; + LongPointHdl ptsH = 0; + WORLDPOINTH wpH = 0; + //TopologyHdl topH ; + LongPoint wp1,wp2,wp3,wp4; + WorldPoint wp; + int32_t numPts = 0, numTri = 0, numCells; + int32_t i, index1, index2; + //Topology tri1, tri2; + + if (fCenterPtsH) return fCenterPtsH; + + //topH = GetTopologyHdl(); + ptsH = GetPointsHdl(); + //numTri = _GetHandleSize((Handle)topH)/sizeof(Topology); + numPts = _GetHandleSize((Handle)ptsH)/sizeof(LongPoint); + numCells = (fNumCols-1)*(fNumRows-1); + // for now just return the points since velocities are on the points + //fCenterPtsH = (WORLDPOINTH)_NewHandle(numCells * sizeof(WorldPoint)); + fCenterPtsH = (WORLDPOINTH)_NewHandle(numPts * sizeof(WorldPoint)); + if (!fCenterPtsH) { + err = -1; + TechError("TriGridWindRect_c::GetCellCenters()", "_NewHandle()", 0); + goto done; + } + + //for (i=0; i(fGrid)) -> GetPointsHdl(); + //return ((TTriGridVel*)fGrid) -> GetPointsHdl(); +} + +TopologyHdl TimeGridWindCurv_c::GetTopologyHdl() +{ + return (dynamic_cast(fGrid)) -> GetTopologyHdl(); + //return ((TTriGridVel*)fGrid) -> GetPointsHdl(); +} + long TimeGridWindCurv_c::GetVelocityIndex(WorldPoint wp) { long index = -1; @@ -2526,6 +2775,215 @@ OSErr TimeGridWindCurv_c::GetLatLonFromIndex(long iIndex, long jIndex, WorldPoin } +OSErr TimeGridWindCurv_c::GetScaledVelocities(Seconds time, VelocityFRec *scaled_velocity) +{ // use for curvilinear + double timeAlpha; + Seconds startTime,endTime; + OSErr err = 0; + char errmsg[256]; + + long numVertices,i,numTri,index=-1,vel_index=0; + InterpolationVal interpolationVal; + LongPointHdl ptsHdl = 0; + TopologyHdl topH ; + long timeDataInterval; + Boolean loaded; + //TTriGridVel* triGrid = (TTriGridVel*)fGrid; + TTriGridVel* triGrid = (dynamic_cast(fGrid)); + VelocityFRec velocity; + + err = this -> SetInterval(errmsg, time); + if(err) return err; + + loaded = this -> CheckInterval(timeDataInterval, time); + + if(!loaded) return -1; + + //topH = triGrid -> GetTopologyHdl(); + topH = fGrid -> GetTopologyHdl(); + if(topH) + numTri = _GetHandleSize((Handle)topH)/sizeof(**topH); + else + numTri = 0; + + /*ptsHdl = triGrid -> GetPointsHdl(); + if(ptsHdl) + numVertices = _GetHandleSize((Handle)ptsHdl)/sizeof(**ptsHdl); + else + numVertices = 0;*/ + + // Check for time varying current + if((GetNumTimesInFile()>1 || GetNumFiles()>1) && loaded && !err) + { + // Calculate the time weight factor + if (GetNumFiles()>1 && fOverLap) + startTime = fOverLapStartTime + fTimeShift; + else + startTime = (*fTimeHdl)[fStartData.timeIndex] + fTimeShift; + + if (fEndData.timeIndex == UNASSIGNEDINDEX && (time > startTime || time < startTime) && fAllowExtrapolationInTime) + { + timeAlpha = 1; + } + else + { + endTime = (*fTimeHdl)[fEndData.timeIndex] + fTimeShift; + timeAlpha = (endTime - time)/(double)(endTime - startTime); + } + } + // for now just get every other one since each pair of triangles corresponds to a cell + for (i = 0 ; i< numTri; i+=2) + //for (i = 0 ; i< numTri; i++) + { + if (bVelocitiesOnNodes) + { + //index = ((TTriGridVel*)fGrid)->GetRectIndexFromTriIndex(refPoint,fVerdatToNetCDFH,fNumCols);// curvilinear grid + //interpolationVal = triGrid -> GetInterpolationValues(refPoint.p); + interpolationVal = triGrid -> GetInterpolationValuesFromIndex(i); + if (interpolationVal.ptIndex1<0) {scaled_velocity[i].u = 0; scaled_velocity[i].v = 0;}// should this be an error? + //ptIndex1 = (*fVerdatToNetCDFH)[interpolationVal.ptIndex1]; + //ptIndex2 = (*fVerdatToNetCDFH)[interpolationVal.ptIndex2]; + //ptIndex3 = (*fVerdatToNetCDFH)[interpolationVal.ptIndex3]; + index = (*fVerdatToNetCDFH)[interpolationVal.ptIndex1]; + } + else // for now just use the u,v at left and bottom midpoints of grid box as velocity over entire gridbox + //index = (dynamic_cast(fGrid))->GetRectIndexFromTriIndex(refPoint.p,fVerdatToNetCDFH,fNumCols+1);// curvilinear grid + index = triGrid->GetRectIndexFromTriIndex2(i,fVerdatToNetCDFH,fNumCols+1);// curvilinear grid + + if (index < 0) {scaled_velocity[i].u = 0; scaled_velocity[i].v = 0;}// should this be an error? + + // Should check vs fFillValue + // Check for constant current + if(((GetNumTimesInFile()==1 && !(GetNumFiles()>1)) || timeAlpha == 1) && index!=-1) + { + velocity.u = GetStartUVelocity(index); + velocity.v = GetStartVVelocity(index); + } + else if (index!=-1)// time varying current + { + velocity.u = timeAlpha*GetStartUVelocity(index) + (1-timeAlpha)*GetEndUVelocity(index); + velocity.v = timeAlpha*GetStartVVelocity(index) + (1-timeAlpha)*GetEndVVelocity(index); + } + if (velocity.u == fFillValue) velocity.u = 0.; + if (velocity.v == fFillValue) velocity.v = 0.; + /*if ((velocity.u != 0 || velocity.v != 0) && (velocity.u != fFillValue && velocity.v != fFillValue)) // should already have handled fill value issue + { + // code goes here, fill up arrays with data + float inchesX = (velocity.u * refScale * fVar.fileScaleFactor) / arrowScale; + float inchesY = (velocity.v * refScale * fVar.fileScaleFactor) / arrowScale; + }*/ + //u[i] = velocity.u * fVar.fileScaleFactor; + //v[i] = velocity.v * fVar.fileScaleFactor; + // will want to do the extra scaling in the client eventually + scaled_velocity[vel_index].u = velocity.u * fVar.fileScaleFactor / 100.; + scaled_velocity[vel_index].v = velocity.v * fVar.fileScaleFactor / 100.; + vel_index++; + } + return err; +} + +WORLDPOINTH TimeGridWindCurv_c::GetCellCenters() +{ + OSErr err = 0; + LongPointHdl ptsH = 0; + WORLDPOINTH wpH = 0; + TopologyHdl topH ; + LongPoint wp1,wp2,wp3,wp4; + WorldPoint wp; + int32_t numPts = 0, numTri = 0, numCells; + int32_t i, index1, index2; + Topology tri1, tri2; + + if (fCenterPtsH) return fCenterPtsH; + + topH = GetTopologyHdl(); + ptsH = GetPointsHdl(); + numTri = _GetHandleSize((Handle)topH)/sizeof(Topology); + numPts = _GetHandleSize((Handle)ptsH)/sizeof(LongPoint); + numCells = numTri / 2; + fCenterPtsH = (WORLDPOINTH)_NewHandle(numCells * sizeof(WorldPoint)); + if (!fCenterPtsH) { + err = -1; + TechError("TriGridWind_c::GetCenterPointsHdl()", "_NewHandle()", 0); + goto done; + } + + for (i=0; i GetTopologyHdl(); + if(topH) + numTri = _GetHandleSize((Handle)topH)/sizeof(**topH); + else + numTri = 0; + + numCells = numTri / 2; + GridCellInfoHdl fGridCellInfoH = (GridCellInfoHdl)_NewHandleClear(numCells * sizeof(**fGridCellInfoH)); + if (!fGridCellInfoH) + { + err = memFullErr; + goto done; + } + + for (i=0; i`_. -Briefly, it has 3 header lines, followed by comma seperated data. An example is given here with +Briefly, it has 3 header lines, followed by comma seperated data. Alternatively, the An example is given here with annotations in brackets at the end of the lines: | 23NM W Cape Mohican AK *(Location name, can be blank)* @@ -83,6 +49,7 @@ annotations in brackets at the end of the lines: | 14, 10, 2015, 11, 0, 16.00, 20 | 14, 10, 2015, 12, 0, 16.00, 20 | 14, 10, 2015, 13, 0, 13.00, 20 +| Gridded wind movers @@ -184,3 +151,38 @@ with the same value for high and low parameters. Here's a complete example where model.full_run() +PyMovers +---------- + +This new type of mover includes the gnome.environment.PyGridCurrentMover and gnome.environment.PyWindMover. They are +being developed to work more seamlessly with native model grids (e.g. staggered grids) and will ultimately replace GridCurrentMover and GridWindMover. However, they are still under active development and this documentation may not +accurately reflect the current state of development. + +PyMovers are built to work with the Property objects, and also provide multiple types of numerical methods for moving the particles. :: + + from gnome.environment.property_classes import GridCurrent + from gnome.movers import PyGridCurrentMover + fn = 'my_data.nc' + current = GridCurrent.from_netCDF(filename=fn) + curr_mover = PyGridCurrentMover(current) + +There are three types of numerical methods currently supported. + +1. Euler method ('Euler') +2. Trapezoidal/Heun's 2nd order method ('Trapezoid') +3. Runge-Kutta 4th order method ('RK4') + +To use them, set the 'default_num_method' argument when constructing a mover. Alternatively, you may alter the mover as follows: :: + + fn = 'my_data.nc' + current = GridCurrent.from_netCDF(filename=fn) + curr_mover = PyGridCurrentMover(current, default_num_method = 'RK4') + + #RK4 is too slow, so lets go to the 2nd order method. + curr_mover.default_num_method = 'Trapezoid' + +The get_move function has the same interface as previous movers. You may also pass in a numerical method here and it will use it instead +of the default. :: + + curr_mover.get_move(sc, time_step, model_time_datetime, num_method = 'Euler') + \ No newline at end of file diff --git a/py_gnome/documentation/scripting/scripting_intro.rst b/py_gnome/documentation/scripting/scripting_intro.rst index 9422571d8..42ab1c090 100644 --- a/py_gnome/documentation/scripting/scripting_intro.rst +++ b/py_gnome/documentation/scripting/scripting_intro.rst @@ -82,7 +82,7 @@ can do a full run:: View the results ---------------- -The renderer that we added was png images every 6 hours. Since we did not specify an output directory for these images, +The renderer that we added generated png images every 6 hours. Since we did not specify an output directory for these images, they will have been saved in the same directory that the script was executed from. The sequence of images should show a cloud of particles moving east and spreading. diff --git a/py_gnome/documentation/scripting/utilities.rst b/py_gnome/documentation/scripting/utilities.rst index 39c55a90a..5074945b7 100644 --- a/py_gnome/documentation/scripting/utilities.rst +++ b/py_gnome/documentation/scripting/utilities.rst @@ -5,6 +5,9 @@ GNOME has a scripting module where many utilties and helper functions can be acc common tasks. These include helper functions for easier creation of certain types of spills and movers. To use these functions import gnome.scripting +NOTE: the scripting utilities are under active development as we determine which helper functions will +be useful to make setting up and running pyGNOME easier. + :mod:`gnome.scripting` ---------------------- diff --git a/py_gnome/documentation/structure.rst b/py_gnome/documentation/structure.rst index 23e656f78..ed0e27686 100644 --- a/py_gnome/documentation/structure.rst +++ b/py_gnome/documentation/structure.rst @@ -83,14 +83,23 @@ equal. Movers ------------- -Movers are any physical process that moves or effects the particles. These can -be ocean currents, winds, turbulent diffusion, and weathering processes -(evaporation, etc). Each move is initialized with the the data it needs to -compute the movement. (or links to files with data in them) +Movers are any physical process that moves the particles. These can +be ocean currents, winds, and/or turbulent diffusion. Each mover is +initialized with the the data it needs to compute the movement or +with links to appropriate files in the case of gridded model output. The Mover API is defined so that you can write your own movers -- for instance to model fish swimming behavior, etc. See the reference docs for the the API. + +Weatherers +-------------- + +Weatherers are processes that change the mass of the floating oil or of oil droplets +within the water column. These include processes that are traditionally described as +"weathering" (e.g. evaporation, dispersion) and response options (e.g. skimming, +burning oil). + Outputters ------------ diff --git a/py_gnome/documentation/units.rst b/py_gnome/documentation/units.rst index 5c92e1a8b..c6c932a20 100644 --- a/py_gnome/documentation/units.rst +++ b/py_gnome/documentation/units.rst @@ -11,13 +11,13 @@ Conversion is done on I/O or occasionally within a class (i.e the class may stor Time Time is expressed in integer seconds -- stored in a C ``unsigned long`` - + Date-times are in seconds since 1904 (1904-01-01T00:00) -- stored in a C ``unsigned long`` - + (NOTE: much of the Python code uses ``datetime.datetime`` objects and/or numpy ``datetime64`` objects, but the internal C++ code uses integer seconds) Length - Lengths are in meters + Lengths are in meters Mass Mass is in grams @@ -29,8 +29,8 @@ Density Density is in grams per cubic centimeter (g/cm^3) Velocity - Velocities are in meters per second - + Velocities are in meters per second + Latitude-Longitude Lat-long is in floating point degrees -- range generally -360 to 360, so we can do stuff across the date line. @@ -43,6 +43,8 @@ Droplet Diameter Viscosity Viscosity is usually kinematic viscosity, and is in units of m^2/s +Salinity + Salinity is used in various calculations for sedimentiaon, wave formation, etc. Standard units is Practical Salinity Units (PSU) -- more or less parts per thousand -- i.e. fresh is 0, typical seawater is 35. diff --git a/py_gnome/gnome/array_types.py b/py_gnome/gnome/array_types.py index db019afa9..4b93add3e 100644 --- a/py_gnome/gnome/array_types.py +++ b/py_gnome/gnome/array_types.py @@ -251,6 +251,7 @@ def split_element(self, num, value, l_frac=None): 'bulk_init_volume': ((), np.float64, 'bulk_init_volume', 0, ArrayTypeDivideOnSplit), 'density': ((), np.float64, 'density', 0), + 'oil_density': ((), np.float64, 'oil_density', 0), 'evap_decay_constant': (None, np.float64, 'evap_decay_constant', None), @@ -268,6 +269,7 @@ def split_element(self, num, value, l_frac=None): 'at_max_area': ((), np.uint8, 'at_max_area', False), 'viscosity': ((), np.float64, 'viscosity', 0), + 'oil_viscosity': ((), np.float64, 'oil_viscosity', 0), # fractional water content in emulsion 'frac_water': ((), np.float64, 'frac_water', 0), 'interfacial_area': ((), np.float64, 'interfacial_area', 0), diff --git a/py_gnome/gnome/basic_types.py b/py_gnome/gnome/basic_types.py index 9eacfba19..447bddee3 100644 --- a/py_gnome/gnome/basic_types.py +++ b/py_gnome/gnome/basic_types.py @@ -46,7 +46,7 @@ # value has two components: (u, v) or (r, theta) etc datetime_value_2d = np.dtype([('time', 'datetime64[s]'), - ('value', mover_type, (2, ))], align=True) + ('value', mover_type, (2,))], align=True) # value has one component: (u,) # convert from datetime_value_1d to time_value_pair by setting 2nd component @@ -71,8 +71,9 @@ disperse=32, # marked for chemical_dispersion ) -numerical_methods = enum(euler=0, - rk4=1) +numerical_methods = {'Euler': 0, + 'Trapezoid': 1, + 'RK4': 2} # ---------------------------------------------------------------- # Mirror C++ structures, following are used by cython code diff --git a/py_gnome/gnome/cy_gnome/cy_currentcycle_mover.pxd b/py_gnome/gnome/cy_gnome/cy_currentcycle_mover.pxd new file mode 100644 index 000000000..4632d81d8 --- /dev/null +++ b/py_gnome/gnome/cy_gnome/cy_currentcycle_mover.pxd @@ -0,0 +1,10 @@ +cimport numpy as cnp +import numpy as np + +from current_movers cimport CurrentCycleMover_c +from gnome.cy_gnome.cy_mover cimport CyMover + + +cdef class CyCurrentCycleMover(CyMover): + cdef CurrentCycleMover_c *current_cycle + cdef char *_num_method diff --git a/py_gnome/gnome/cy_gnome/cy_currentcycle_mover.pyx b/py_gnome/gnome/cy_gnome/cy_currentcycle_mover.pyx index e138d9abd..cafa4badf 100644 --- a/py_gnome/gnome/cy_gnome/cy_currentcycle_mover.pyx +++ b/py_gnome/gnome/cy_gnome/cy_currentcycle_mover.pyx @@ -1,26 +1,26 @@ +import os + cimport numpy as cnp import numpy as np -import os from type_defs cimport * -from movers cimport Mover_c -from current_movers cimport CurrentCycleMover_c from gnome import basic_types + +from gnome.cy_gnome.cy_helpers cimport to_bytes from gnome.cy_gnome.cy_ossm_time cimport CyOSSMTime from gnome.cy_gnome.cy_shio_time cimport CyShioTime -from gnome.cy_gnome cimport cy_mover -from gnome.cy_gnome.cy_helpers cimport to_bytes + +from movers cimport Mover_c +from current_movers cimport CurrentCycleMover_c +from gnome.cy_gnome.cy_mover cimport CyMover cdef extern from *: CurrentCycleMover_c* dynamic_cast_ptr "dynamic_cast" (Mover_c *) except NULL -cdef class CyCurrentCycleMover(cy_mover.CyMover): - - cdef CurrentCycleMover_c *current_cycle - - # note - current_cycle is derived from grid_current +cdef class CyCurrentCycleMover(CyMover): + # note - current_cycle is derived from grid_current def __cinit__(self): self.mover = new CurrentCycleMover_c() self.current_cycle = dynamic_cast_ptr(self.mover) @@ -58,7 +58,7 @@ cdef class CyCurrentCycleMover(cy_mover.CyMover): # def export_topology(self, topology_file): # """ # .. function::export_topology -# +# # """ # cdef OSErr err # topology_file = os.path.normpath(topology_file) @@ -66,114 +66,139 @@ cdef class CyCurrentCycleMover(cy_mover.CyMover): # err = self.current_cycle.ExportTopology(topology_file) # if err != 0: # """ -# For now just raise an OSError - until the types of possible errors -# are defined and enumerated +# For now just raise an OSError - until the types of +# possible errors are defined and enumerated # """ -# raise OSError("CurrentCycleMover_c.ExportTopology returned an error.") -# - def __init__(self, current_scale=1, uncertain_duration=24*3600, uncertain_time_delay=0, - uncertain_along = .5, uncertain_cross = .25, num_method = 0): +# raise OSError('CurrentCycleMover_c.ExportTopology ' +# 'returned an error.') +# + def __init__(self, current_scale=1, + uncertain_duration=24*3600, + uncertain_time_delay=0, + uncertain_along=.5, + uncertain_cross=.25, + num_method='Euler'): """ - .. function:: __init__(self, current_scale=1, uncertain_duration=24*3600, uncertain_time_delay=0, - uncertain_along = .5, uncertain_cross = .25) - + .. function:: __init__(self, current_scale=1, + uncertain_duration=24*3600, + uncertain_time_delay=0, + uncertain_along=.5, + uncertain_cross = .25) + initialize a current cycle mover - - :param uncertain_duation: time in seconds after which the uncertainty values are updated - :param uncertain_time_delay: wait this long after model_start_time to turn on uncertainty - :param uncertain_cross: used in uncertainty computation, perpendicular to current flow - :param uncertain_along: used in uncertainty computation, parallel to current flow + + :param uncertain_duation: time in seconds after which the + uncertainty values are updated + :param uncertain_time_delay: wait this long after model_start_time + to turn on uncertainty + :param uncertain_cross: used in uncertainty computation, perpendicular + to current flow + :param uncertain_along: used in uncertainty computation, parallel + to current flow :param current_scale: scale factor applied to current values - """ + self.num_method = num_method + self.current_cycle.fCurScale = current_scale - #self.current_cycle.fUncertainParams.durationInHrs = uncertain_duration + # self.current_cycle.fUncertainParams.durationInHrs = uncertain_duration self.current_cycle.fDuration = uncertain_duration - #self.current_cycle.fUncertainParams.startTimeInHrs = uncertain_time_delay + # self.current_cycle.fUncertainParams.startTimeInHrs = uncertain_time_delay self.current_cycle.fUncertainStartTime = uncertain_time_delay - #self.current_cycle.fUncertainParams.crossCurUncertainty = uncertain_cross - #self.current_cycle.fUncertainParams.alongCurUncertainty = uncertain_along + # self.current_cycle.fUncertainParams.crossCurUncertainty = uncertain_cross + # self.current_cycle.fUncertainParams.alongCurUncertainty = uncertain_along self.current_cycle.fDownCurUncertainty = -1*uncertain_along self.current_cycle.fUpCurUncertainty = uncertain_along self.current_cycle.fLeftCurUncertainty = -1*uncertain_cross self.current_cycle.fRightCurUncertainty = uncertain_cross - self.current_cycle.num_method = num_method - + self.current_cycle.fIsOptimizedForStep = 0 def __repr__(self): """ unambiguous repr of object, reuse for str() method """ - info = "CyCurrentCycleMover(uncertain_duration=%s,uncertain_time_delay=%s,uncertain_along=%s,uncertain_cross=%s)" \ - % (self.current_cycle.fDuration, self.current_cycle.fUncertainStartTime, self.current_cycle.fUpCurUncertainty, self.current_cycle.fRightCurUncertainty) + info = ('CyCurrentCycleMover(uncertain_duration={}, ' + 'uncertain_time_delay={}, ' + 'uncertain_along={}, uncertain_cross={})' + .format(self.current_cycle.fDuration, + self.current_cycle.fUncertainStartTime, + self.current_cycle.fUpCurUncertainty, + self.current_cycle.fRightCurUncertainty)) return info - + def __str__(self): """Return string representation of this object""" - - info = "CyCurrentCycleMover object - \n uncertain_duration: %s \n uncertain_time_delay: %s \n uncertain_along: %s\n uncertain_cross: %s" \ - % (self.current_cycle.fDuration, self.current_cycle.fUncertainStartTime, self.current_cycle.fUpCurUncertainty, self.current_cycle.fRightCurUncertainty) - + + info = ('CyCurrentCycleMover object - \n' + '\tuncertain_duration: %s \n' + '\tuncertain_time_delay: %s \n' + '\tuncertain_along: %s\n' + '\tuncertain_cross: %s' + .format(self.current_cycle.fDuration, + self.current_cycle.fUncertainStartTime, + self.current_cycle.fUpCurUncertainty, + self.current_cycle.fRightCurUncertainty)) + return info - + property current_scale: def __get__(self): return self.current_cycle.fCurScale - + def __set__(self, value): self.current_cycle.fCurScale = value - + property uncertain_duration: def __get__(self): return self.current_cycle.fDuration - - def __set__(self,value): + + def __set__(self, value): self.current_cycle.fDuration = value - + property uncertain_time_delay: def __get__(self): return self.current_cycle.fUncertainStartTime - + def __set__(self, value): self.current_cycle.fUncertainStartTime = value - + property uncertain_cross: def __get__(self): return self.current_cycle.fRightCurUncertainty - + def __set__(self, value): self.current_cycle.fRightCurUncertainty = value self.current_cycle.fLeftCurUncertainty = -1.*value - + property uncertain_along: def __get__(self): return self.current_cycle.fUpCurUncertainty - + def __set__(self, value): self.current_cycle.fUpCurUncertainty = value self.current_cycle.fDownCurUncertainty = -1.*value - + property extrapolate: def __get__(self): return self.current_cycle.GetExtrapolationInTime() - + def __set__(self, value): self.current_cycle.SetExtrapolationInTime(value) - + property time_offset: def __get__(self): return self.current_cycle.GetTimeShift() - + def __set__(self, value): self.current_cycle.SetTimeShift(value) property num_method: def __get__(self): - return self.current_cycle.num_method - + return self._num_method + def __set__(self, value): - self.current_cycle.num_method = value + self._num_method = value + self.current_cycle.num_method = basic_types.numerical_methods[value] def extrapolate_in_time(self, extrapolate): self.current_cycle.SetExtrapolationInTime(extrapolate) @@ -186,22 +211,24 @@ cdef class CyCurrentCycleMover(cy_mover.CyMover): def set_shio(self, CyShioTime cy_shio): """ - Takes a CyShioTime object as input and sets C++ CurrentCycle mover properties from the Shio object. + Takes a CyShioTime object as input and sets C++ CurrentCycle + mover properties from the Shio object. """ self.current_cycle.SetTimeDep(cy_shio.shio) self.current_cycle.SetRefPosition(cy_shio.shio.GetStationLocation()) self.current_cycle.bTimeFileActive = True - #self.current_cycle.scaleType = 1 + # self.current_cycle.scaleType = 1 return True - + def set_ossm(self, CyOSSMTime ossm): """ - Takes a CyOSSMTime object as input and sets C++ CurrentCycle mover properties from the OSSM object. + Takes a CyOSSMTime object as input and sets C++ CurrentCycle + mover properties from the OSSM object. """ self.current_cycle.SetTimeDep(ossm.time_dep) self.current_cycle.bTimeFileActive = True # What is this? return True - + def get_move(self, model_time, step_len, @@ -236,17 +263,22 @@ cdef class CyCurrentCycleMover(cy_mover.CyMover): N = len(ref_points) err = self.current_cycle.get_move(N, model_time, step_len, - &ref_points[0], - &delta[0], - &LE_status[0], - spill_type, - 0) + &ref_points[0], + &delta[0], + &LE_status[0], + spill_type, + 0) + if err == 1: - raise ValueError("Make sure numpy arrays for ref_points and delta are defined") + raise ValueError('Make sure numpy arrays for ref_points ' + 'and delta are defined') """ Can probably raise this error before calling the C++ code, but the C++ also throwing this error """ if err == 2: - raise ValueError("The value for spill type can only be 'forecast' or 'uncertainty' - you've chosen: " + str(spill_type)) + raise ValueError('The value for spill type can only be ' + '"forecast" or "uncertainty" - ' + 'you\'ve chosen: {}' + .format(spill_type)) diff --git a/py_gnome/gnome/cy_gnome/cy_gridcurrent_mover.pxd b/py_gnome/gnome/cy_gnome/cy_gridcurrent_mover.pxd index 57454ff5f..e3acdbb82 100644 --- a/py_gnome/gnome/cy_gnome/cy_gridcurrent_mover.pxd +++ b/py_gnome/gnome/cy_gnome/cy_gridcurrent_mover.pxd @@ -1,8 +1,10 @@ cimport numpy as cnp import numpy as np + from current_movers cimport GridCurrentMover_c from gnome.cy_gnome.cy_mover cimport CyCurrentMoverBase cdef class CyGridCurrentMover(CyCurrentMoverBase): - cdef GridCurrentMover_c *grid_current \ No newline at end of file + cdef GridCurrentMover_c *grid_current + cdef char *_num_method diff --git a/py_gnome/gnome/cy_gnome/cy_gridcurrent_mover.pyx b/py_gnome/gnome/cy_gnome/cy_gridcurrent_mover.pyx index 55b26320c..bbf225d2e 100644 --- a/py_gnome/gnome/cy_gnome/cy_gridcurrent_mover.pyx +++ b/py_gnome/gnome/cy_gnome/cy_gridcurrent_mover.pyx @@ -2,16 +2,18 @@ import os cimport numpy as cnp import numpy as np + from libc.string cimport memcpy from type_defs cimport * +from gnome import basic_types + from utils cimport _GetHandleSize from movers cimport Mover_c from current_movers cimport GridCurrentMover_c, CurrentMover_c -from gnome import basic_types -from gnome.cy_gnome.cy_mover cimport CyCurrentMoverBase from gnome.cy_gnome.cy_helpers cimport to_bytes +from gnome.cy_gnome.cy_mover cimport CyCurrentMoverBase cdef extern from *: @@ -22,9 +24,6 @@ cdef extern from *: cdef class CyGridCurrentMover(CyCurrentMoverBase): - - #cdef GridCurrentMover_c *grid_current - def __cinit__(self): self.mover = new GridCurrentMover_c() self.grid_current = dc_mover_to_gc(self.mover) @@ -83,7 +82,7 @@ cdef class CyGridCurrentMover(CyCurrentMoverBase): uncertain_time_delay=0, uncertain_along=.5, uncertain_cross=.25, - num_method = 0): + num_method='Euler'): """ .. function:: __init__(self, current_scale=1, uncertain_duration=24*3600, uncertain_time_delay=0, @@ -110,7 +109,8 @@ cdef class CyGridCurrentMover(CyCurrentMoverBase): self.grid_current.fCurScale = current_scale self.grid_current.fIsOptimizedForStep = 0 - self.grid_current.num_method = num_method + + self.num_method = num_method def __repr__(self): return ('CyGridCurrentMover(uncertain_duration={0}, ' @@ -172,14 +172,14 @@ cdef class CyGridCurrentMover(CyCurrentMoverBase): def __set__(self, value): self.grid_current.SetTimeShift(value) - - #Does this need to be here? - Jay + property num_method: def __get__(self): - return self.grid_current.num_method - + return self._num_method + def __set__(self, value): - self.grid_current.num_method = value + self.grid_current.num_method = basic_types.numerical_methods[value] + self._num_method = value def extrapolate_in_time(self, extrapolate): self.grid_current.SetExtrapolationInTime(extrapolate) diff --git a/py_gnome/gnome/cy_gnome/cy_gridwind_mover.pyx b/py_gnome/gnome/cy_gnome/cy_gridwind_mover.pyx index ef7c15229..7ea6dd789 100644 --- a/py_gnome/gnome/cy_gnome/cy_gridwind_mover.pyx +++ b/py_gnome/gnome/cy_gnome/cy_gridwind_mover.pyx @@ -1,12 +1,15 @@ +import os + cimport numpy as cnp import numpy as np -import os +from libc.string cimport memcpy from type_defs cimport * +from utils cimport _GetHandleSize from movers cimport GridWindMover_c, WindMover_c, Mover_c +from gnome import basic_types from cy_mover cimport CyWindMoverBase - from gnome.cy_gnome.cy_helpers cimport to_bytes @@ -184,6 +187,21 @@ cdef class CyGridWindMover(CyWindMoverBase): " 'forecast' or 'uncertainty' - you've chosen: " + str(spill_type)) + def _is_regular_grid(self): + """ + Invokes the IsRegularGrid TimeGridVel_c object + """ + return self.grid_wind.IsRegularGrid() + + def get_num_points(self): + """ + Invokes the GetNumPoints method of TimeGridVel_c object + to get the number of triangles + """ + num_points = self.grid_wind.GetNumPoints() + + return num_points + def get_num_triangles(self): """ Invokes the GetNumTriangles method of TriGridVel_c object @@ -193,3 +211,88 @@ cdef class CyGridWindMover(CyWindMoverBase): return num_tri + def _get_points(self): + """ + Invokes the GetPointsHdl method of TriGridWind_c object + to get the points for the grid + """ + cdef short tmp_size = sizeof(LongPoint) + cdef LongPointHdl pts_hdl + cdef cnp.ndarray[LongPoint, ndim = 1] pts + + # allocate memory and copy it over + pts_hdl = self.grid_wind.GetPointsHdl() + sz = _GetHandleSize(pts_hdl) + + # will this always work? + pts = np.empty((sz / tmp_size,), dtype=basic_types.long_point) + + memcpy(&pts[0], pts_hdl[0], sz) + + return pts + + def _get_center_points(self): + """ + Invokes the GetCellCenters method of TriGridWind_c object + to get the velocities for the grid + """ + cdef short tmp_size = sizeof(WorldPoint) + cdef WORLDPOINTH pts_hdl + cdef cnp.ndarray[WorldPoint, ndim = 1] pts + + # allocate memory and copy it over + pts_hdl = self.grid_wind.GetCellCenters() + sz = _GetHandleSize(pts_hdl) + + # will this always work? + pts = np.empty((sz / tmp_size,), dtype=basic_types.w_point_2d) + + memcpy(&pts[0], pts_hdl[0], sz) + + return pts + + def _get_cell_data(self): + """ + Invokes the GetCellDataHdl method of TimeGridWind_c object + to get the velocities for the grid + """ + cdef short tmp_size = sizeof(GridCellInfo) + cdef GridCellInfoHdl cell_data_hdl + cdef cnp.ndarray[GridCellInfo, ndim = 1] cell_data + + # allocate memory and copy it over + # should check that cell data exists + cell_data_hdl = self.grid_wind.GetCellDataHdl() + if not cell_data_hdl: + """ + For now just raise an OSError - until the types of possible errors + are defined and enumerated + """ + raise OSError('GridWindMover_c.GetCellDataHdl ' + 'returned an error.') + + sz = _GetHandleSize(cell_data_hdl) + + # will this always work? + cell_data = np.empty((sz / tmp_size,), dtype=basic_types.cell_data) + + memcpy(&cell_data[0], cell_data_hdl[0], sz) + + return cell_data + + def get_scaled_velocities(self, Seconds model_time, + cnp.ndarray[VelocityFRec] vels): + """ + Invokes the GetScaledVelocities method of TimeGridVel_c object + to get the velocities on the triangles + """ + cdef OSErr err + + err = self.grid_wind.GetScaledVelocities(model_time, &vels[0]) + if err != 0: + """ + For now just raise an OSError - until the types of possible errors + are defined and enumerated + """ + raise OSError('GridWindMover_c.GetScaledVelocities ' + 'returned an error.') diff --git a/py_gnome/gnome/cy_gnome/movers.pxd b/py_gnome/gnome/cy_gnome/movers.pxd index 89fe9cac7..12f22bd55 100644 --- a/py_gnome/gnome/cy_gnome/movers.pxd +++ b/py_gnome/gnome/cy_gnome/movers.pxd @@ -93,7 +93,13 @@ cdef extern from "GridWindMover_c.h": long GetTimeShift() OSErr GetDataStartTime(Seconds *startTime) OSErr GetDataEndTime(Seconds *endTime) + OSErr GetScaledVelocities(Seconds time, VelocityFRec *velocity) + LongPointHdl GetPointsHdl() + WORLDPOINTH GetCellCenters() + GridCellInfoHdl GetCellDataHdl() long GetNumTriangles() + long GetNumPoints() + bool IsRegularGrid() cdef extern from "IceWindMover_c.h": diff --git a/py_gnome/gnome/environment/__init__.py b/py_gnome/gnome/environment/__init__.py index cb946427a..c5c3d4855 100644 --- a/py_gnome/gnome/environment/__init__.py +++ b/py_gnome/gnome/environment/__init__.py @@ -1,10 +1,10 @@ ''' environment module ''' -from environment import Environment, Water, WaterSchema +from environment import Environment, Water, WaterSchema, env_from_netCDF, ice_env_from_netCDF from property import EnvProp, VectorProp, Time from ts_property import TimeSeriesProp, TSVectorProp -from grid_property import GriddedProp, GridVectorProp +from grid_property import GriddedProp, GridVectorProp, GridPropSchema, GridVectorPropSchema from environment_objects import (WindTS, GridCurrent, GridWind, @@ -14,15 +14,12 @@ IceAwareWind, TemperatureTS) -from environment_objects import (Wind, - Current, - Temperature) - from waves import Waves, WavesSchema from tide import Tide, TideSchema from wind import Wind, WindSchema, constant_wind, wind_from_values from running_average import RunningAverage, RunningAverageSchema -from grid import Grid, GridSchema +from grid import Grid, GridSchema, PyGrid, PyGrid_S, PyGrid_U +# from gnome.environment.environment_objects import IceAwareCurrentSchema __all__ = [Environment, @@ -38,16 +35,21 @@ RunningAverageSchema, Grid, GridSchema, + PyGrid, + PyGrid_S, + PyGrid_U, constant_wind, WindTS, GridCurrent, + GridVectorPropSchema, + GridPropSchema, GridWind, IceConcentration, GridTemperature, IceAwareCurrent, +# IceAwareCurrentSchema, IceAwareWind, TemperatureTS, - Wind, - Current, - Temperature + env_from_netCDF, + ice_env_from_netCDF ] diff --git a/py_gnome/gnome/environment/environment.py b/py_gnome/gnome/environment/environment.py index 028994bd0..030e942aa 100644 --- a/py_gnome/gnome/environment/environment.py +++ b/py_gnome/gnome/environment/environment.py @@ -16,6 +16,16 @@ from .. import _valid_units +class EnvironmentMeta(type): + def __init__(cls, name, bases, dct): +# if hasattr(cls, '_state'): +# cls._state = copy.deepcopy(bases[0]._state) + cls._subclasses = [] + for c in cls.__mro__: + if hasattr(c, '_subclasses') and c is not cls: + c._subclasses.append(cls) + + class Environment(object): """ A base class for all classes in environment module @@ -23,6 +33,8 @@ class Environment(object): This is primarily to define a dtype such that the OrderedCollection defined in the Model object requires it. """ + + _subclasses = [] _state = copy.deepcopy(serializable.Serializable._state) # env objects referenced by others using this attribute name @@ -32,6 +44,8 @@ class Environment(object): # reference environment objects _ref_as = 'environment' + __metaclass__ = EnvironmentMeta + def __init__(self, name=None, make_default_refs=True): ''' base class for environment objects @@ -117,6 +131,14 @@ class Water(Environment, serializable.Serializable): ''' _ref_as = 'water' _state = copy.deepcopy(Environment._state) + _field_descr = {'units': ('update', 'save'), + 'temperature:': ('update', 'save'), + 'salinity': ('update', 'save'), + 'sediment': ('update', 'save'), + 'fetch': ('update', 'save'), + 'wave_height': ('update', 'save'), + 'density': ('update', 'save'), + 'kinematic_viscosity': ('update', 'save')} _state += [serializable.Field('units', update=True, save=True), serializable.Field('temperature', update=True, save=True), serializable.Field('salinity', update=True, save=True), @@ -154,7 +176,7 @@ class Water(Environment, serializable.Serializable): def __init__(self, temperature=300.0, salinity=35.0, - sediment=.005, # kg/m^3 oceanic default + sediment=.005, # kg/m^3 oceanic default wave_height=None, fetch=None, units={'temperature': 'K', @@ -292,7 +314,105 @@ def _convert_sediment_units(self, from_, to): if from_ == 'mg/l': # convert to kg/m^3 - return self.sediment/1000.0 + return self.sediment / 1000.0 else: return self.sediment * 1000.0 + + +def env_from_netCDF(filename=None, dataset=None, grid_file=None, data_file=None, _cls_list=None, **kwargs): + ''' + Returns a list of instances of environment objects that can be produced from a file or dataset. + These instances will be created with a common underlying grid, and will interconnect when possible + For example, if an IceAwareWind can find an existing IceConcentration, it will use it instead of + instantiating another. This function tries ALL gridded types by default. This means if a particular + subclass of object is possible to be built, it is likely that all it's parents will be built and included + as well. + + If you wish to limit the types of environment objects that will be used, pass a list of the types + using "_cls_list" kwarg''' + def attempt_from_netCDF(cls, **klskwargs): + obj = None + try: + obj = c.from_netCDF(**klskwargs) + except Exception as e: + import logging + logging.warn('''Class {0} could not be constituted from netCDF file + Exception: {1}'''.format(c.__name__, e)) + return obj + + from gnome.utilities.file_tools.data_helpers import _get_dataset + from gnome.environment.environment_objects import GriddedProp, GridVectorProp + from gnome.environment import PyGrid, Environment + import copy + + new_env = [] + + if filename is not None: + data_file = filename + grid_file = filename + + ds = None + dg = None + if dataset is None: + if grid_file == data_file: + ds = dg = _get_dataset(grid_file) + else: + ds = _get_dataset(data_file) + dg = _get_dataset(grid_file) + else: + if grid_file is not None: + dg = _get_dataset(grid_file) + else: + dg = dataset + ds = dataset + dataset = ds + + grid = kwargs.pop('grid', None) + if grid is None: + grid = PyGrid.from_netCDF(filename=filename, dataset=dg, **kwargs) + kwargs['grid'] = grid + scs = copy.copy(Environment._subclasses) if _cls_list is None else _cls_list + for c in scs: + if issubclass(c, (GriddedProp, GridVectorProp)) and not any([isinstance(o, c) for o in new_env]): + clskwargs = copy.copy(kwargs) + obj = None + try: + req_refs = c._req_refs + except AttributeError: + req_refs = None + + if req_refs is not None: + for ref, klass in req_refs.items(): + for o in new_env: + if isinstance(o, klass): + clskwargs[ref] = o + if ref in clskwargs.keys(): + continue + else: + obj = attempt_from_netCDF(c, filename=filename, dataset=dataset, grid_file=grid_file, data_file=data_file, **clskwargs) + clskwargs[ref] = obj + if obj is not None: + new_env.append(obj) + + obj = attempt_from_netCDF(c, filename=filename, dataset=dataset, grid_file=grid_file, data_file=data_file, **clskwargs) + if obj is not None: + new_env.append(obj) + return new_env + + +def ice_env_from_netCDF(filename=None, **kwargs): + ''' + A short function to generate a list of all the 'ice_aware' classes for use in env_from_netCDF + (this excludes GridCurrent, GridWind, GridTemperature etc) + ''' + from gnome.environment import Environment + cls_list = Environment._subclasses + ice_cls_list = [c for c in cls_list if (hasattr(c, '_ref_as') and 'ice_aware' in c._ref_as)] +# for c in cls_list: +# if hasattr(c, '_ref_as'): +# if ((not isinstance(c._ref_as, basestring) and +# any(['ice_aware' in r for r in c._ref_as])) or +# 'ice_aware' in c._ref_as): +# ice_cls_list.append(c) + return env_from_netCDF(filename=filename, _cls_list=ice_cls_list, **kwargs) \ No newline at end of file diff --git a/py_gnome/gnome/environment/environment_objects.py b/py_gnome/gnome/environment/environment_objects.py index e7e1f9451..96ce9c695 100644 --- a/py_gnome/gnome/environment/environment_objects.py +++ b/py_gnome/gnome/environment/environment_objects.py @@ -17,10 +17,11 @@ import unit_conversion from .. import _valid_units from gnome.environment import Environment +from gnome.environment.grid import PyGrid from gnome.environment.property import Time, PropertySchema, VectorProp, EnvProp -from gnome.environment.ts_property import TSVectorProp, TimeSeriesProp -from gnome.environment.grid_property import GridVectorProp, GriddedProp, GridPropSchema -from gnome.utilities.file_tools.data_helpers import _init_grid, _get_dataset +from gnome.environment.ts_property import TSVectorProp, TimeSeriesProp, TimeSeriesPropSchema +from gnome.environment.grid_property import GridVectorProp, GriddedProp, GridPropSchema, GridVectorPropSchema +from gnome.utilities.file_tools.data_helpers import _get_dataset class Depth(object): @@ -39,7 +40,7 @@ def interpolation_alphas(self, points, data_shape, _hash=None): return None, None -class S_Depth(object): +class S_Depth_T1(object): default_terms = [['Cs_w', 's_w', 'hc', 'Cs_r', 's_rho']] @@ -59,7 +60,7 @@ def __init__(self, self.bathymetry = bathymetry self.terms = terms if len(terms) == 0: - for s in S_Depth.default_terms: + for s in S_Depth_T1.default_terms: for term in s: self.terms[term] = ds[term][:] @@ -80,7 +81,7 @@ def from_netCDF(cls, @property def surface_index(self): return -1 - + @property def bottom_index(self): return 0 @@ -104,8 +105,13 @@ def _r_level_depth_given_bathymetry(self, depths, lvl): Cs_r = self.terms['Cs_r'][lvl] hc = self.terms['hc'] return -(hc * (s_rho - Cs_r) + Cs_r * depths) - + def interpolation_alphas(self, points, data_shape, _hash=None): + ''' + Returns a pair of values. The 1st value is an array of the depth indices of all the particles. + The 2nd value is an array of the interpolation alphas for the particles between their depth + index and depth_index+1. If both values are None, then all particles are on the surface layer. + ''' underwater = points[:, 2] > 0.0 if len(np.where(underwater)[0]) == 0: return None, None @@ -134,7 +140,7 @@ def interpolation_alphas(self, points, data_shape, _hash=None): if ulev == 0: und_alph[within_layer] = -2 else: - a = ((pts[:, 2].take(within_layer) - blev_depths.take(within_layer)) / + a = ((pts[:, 2].take(within_layer) - blev_depths.take(within_layer)) / (ulev_depths.take(within_layer) - blev_depths.take(within_layer))) und_alph[within_layer] = a blev_depths = ulev_depths @@ -144,40 +150,7 @@ def interpolation_alphas(self, points, data_shape, _hash=None): return indices, alphas -class TemperatureTSSchema(PropertySchema): - timeseries = SequenceSchema( - TupleSchema( - children=[SchemaNode(DateTime(default_tzinfo=None), missing=drop), - SchemaNode(Float(), missing=0) - ], - missing=drop) - ) - varnames = SequenceSchema(SchemaNode(String(), missing=drop)) - - -class VelocityTSSchema(PropertySchema): - timeseries = SequenceSchema( - TupleSchema( - children=[SchemaNode(DateTime(default_tzinfo=None), missing=drop), - TupleSchema(children=[ - SchemaNode(Float(), missing=0), - SchemaNode(Float(), missing=0) - ] - ) - ], - missing=drop) - ) - varnames = SequenceSchema(SchemaNode(String(), missing=drop)) - - -class VelocityTS(TSVectorProp, serializable.Serializable): - - _state = copy.deepcopy(serializable.Serializable._state) - _schema = VelocityTSSchema - - _state.add_field([serializable.Field('units', save=True, update=True), - serializable.Field('timeseries', save=True, update=True), - serializable.Field('varnames', save=True, update=True)]) +class VelocityTS(TSVectorProp): def __init__(self, name=None, @@ -219,7 +192,7 @@ def constant(cls, :param speed: speed of wind :param direction: direction -- degrees True, direction wind is from (degrees True) - :param unit='m/s': units for speed, as a string, i.e. "knots", "m/s", + :param units='m/s': units for speed, as a string, i.e. "knots", "m/s", "cm/s", etc. .. note:: @@ -229,7 +202,9 @@ def constant(cls, direction = direction * -1 - 90 u = speed * np.cos(direction * np.pi / 180) v = speed * np.sin(direction * np.pi / 180) - return super(VelocityTS, self).constant(name, units, variables=[[u], [v]]) + u = TimeSeriesProp.constant('u', units, u) + v = TimeSeriesProp.constant('v', units, v) + return super(VelocityTS, cls).constant(name, units, variables=[u, v]) @property def timeseries(self): @@ -237,100 +212,80 @@ def timeseries(self): y = self.variables[1].data return map(lambda t, x, y: (t, (x, y)), self._time, x, y) - def serialize(self, json_='webapi'): - dict_ = serializable.Serializable.serialize(self, json_=json_) - # The following code is to cover the needs of webapi - if json_ == 'webapi': - dict_.pop('timeseries') - dict_.pop('units') - x = np.asanyarray(self.variables[0].data) - y = np.asanyarray(self.variables[1].data) - direction = -(np.arctan2(y, x) * 180 / np.pi + 90) - magnitude = np.sqrt(x ** 2 + y ** 2) - ts = (unicode(tx.isoformat()) for tx in self._time) - dict_['timeseries'] = map(lambda t, x, y: (t, (x, y)), ts, magnitude, direction) - dict_['units'] = (unicode(self.variables[0].units), u'degrees') - dict_['varnames'] = [u'magnitude', u'direction', dict_['varnames'][0], dict_['varnames'][1]] - return dict_ - - @classmethod - def deserialize(cls, json_): - dict_ = super(VelocityTS, cls).deserialize(json_) - - ts, data = zip(*dict_.pop('timeseries')) - ts = np.array(ts) - data = np.array(data).T - units = dict_['units'] - if len(units) > 1 and units[1] == 'degrees': - u_data, v_data = data - v_data = ((-v_data - 90) * np.pi / 180) - u_t = u_data * np.cos(v_data) - v_data = u_data * np.sin(v_data) - u_data = u_t - data = np.array((u_data, v_data)) - dict_['varnames'] = dict_['varnames'][2:] - - units = units[0] - dict_['units'] = units - dict_['time'] = ts - dict_['data'] = data - return dict_ - - @classmethod - def new_from_dict(cls, dict_): - varnames = dict_['varnames'] - vs = [] - for i, varname in enumerate(varnames): - vs.append(TimeSeriesProp(name=varname, - units=dict_['units'], - time=dict_['time'], - data=dict_['data'][i])) - dict_.pop('data') - dict_['variables'] = vs - return super(VelocityTS, cls).new_from_dict(dict_) - - -class VelocityGridSchema(PropertySchema): - data_file = SchemaNode(String(), missing=drop) - grid_file = SchemaNode(String(), missing=drop) - - -class VelocityGrid(GridVectorProp, serializable.Serializable): - _state = copy.deepcopy(serializable.Serializable._state) - - _schema = VelocityGridSchema - - _state.add_field([serializable.Field('units', save=True, update=True), - serializable.Field('varnames', save=True, update=True), - serializable.Field('time', save=True, update=True), - serializable.Field('data_file', save=True, update=True), - serializable.Field('grid_file', save=True, update=True)]) - - def __init__(self, - name=None, - units=None, - time=None, - grid=None, - depth=None, - variables=None, - data_file=None, - grid_file=None, - dataset=None, - **kwargs): - - GridVectorProp.__init__(self, - name=name, - units=units, - time=time, - grid=grid, - depth=depth, - variables=variables, - data_file=data_file, - grid_file=grid_file, - dataset=dataset, - **kwargs) - if len(variables) == 2: - self.variables.append(TimeSeriesProp(name='constant w', data=[0.0], time=[datetime.now()], units='m/s')) +# def serialize(self, json_='webapi'): +# dict_ = serializable.Serializable.serialize(self, json_=json_) +# # The following code is to cover the needs of webapi +# if json_ == 'webapi': +# dict_.pop('timeseries') +# dict_.pop('units') +# x = np.asanyarray(self.variables[0].data) +# y = np.asanyarray(self.variables[1].data) +# direction = -(np.arctan2(y, x) * 180 / np.pi + 90) +# magnitude = np.sqrt(x ** 2 + y ** 2) +# ts = (unicode(tx.isoformat()) for tx in self._time) +# dict_['timeseries'] = map(lambda t, x, y: (t, (x, y)), ts, magnitude, direction) +# dict_['units'] = (unicode(self.variables[0].units), u'degrees') +# dict_['varnames'] = [u'magnitude', u'direction', dict_['varnames'][0], dict_['varnames'][1]] +# return dict_ + +# @classmethod +# def deserialize(cls, json_): +# if json_ == 'webapi': +# dict_ = super(VelocityTS, cls).deserialize(json_) +# +# ts, data = zip(*dict_.pop('timeseries')) +# ts = np.array(ts) +# data = np.array(data).T +# units = dict_['units'] +# if len(units) > 1 and units[1] == 'degrees': +# u_data, v_data = data +# v_data = ((-v_data - 90) * np.pi / 180) +# u_t = u_data * np.cos(v_data) +# v_data = u_data * np.sin(v_data) +# u_data = u_t +# data = np.array((u_data, v_data)) +# dict_['varnames'] = dict_['varnames'][2:] +# +# units = units[0] +# dict_['units'] = units +# dict_['time'] = ts +# dict_['data'] = data +# return dict_ +# else: +# return super(VelocityTS, cls).deserialize(json_) +# +# @classmethod +# def new_from_dict(cls, dict_): +# varnames = dict_['varnames'] +# vs = [] +# for i, varname in enumerate(varnames): +# vs.append(TimeSeriesProp(name=varname, +# units=dict_['units'], +# time=dict_['time'], +# data=dict_['data'][i])) +# dict_.pop('data') +# dict_['variables'] = vs +# return super(VelocityTS, cls).new_from_dict(dict_) + + +class VelocityGrid(GridVectorProp): + + def __init__(self, **kwargs): + if 'variables' in kwargs: + variables = kwargs['variables'] + if len(variables) == 2: + variables.append(TimeSeriesProp(name='constant w', data=[0.0], time=Time.constant_time(), units='m/s')) + kwargs['variables'] = variables + self.angle = None + df = None + if kwargs.get('dataset', None) is not None: + df = kwargs['dataset'] + elif kwargs.get('grid_file', None) is not None: + df = _get_dataset(kwargs['grid_file']) + if df is not None and 'angle' in df.variables.keys(): + # Unrotated ROMS Grid! + self.angle = GriddedProp(name='angle', units='radians', time=None, grid=kwargs['grid'], data=df['angle']) + super(VelocityGrid, self).__init__(**kwargs) def __eq__(self, o): if o is None: @@ -486,26 +441,20 @@ class GridSediment(GriddedProp, Environment): default_names = ['sand_06'] -class IceConcentration(GriddedProp, Environment, serializable.Serializable): - _state = copy.deepcopy(serializable.Serializable._state) - - _schema = GridPropSchema - - _state.add_field([serializable.Field('units', save=True, update=True), - serializable.Field('varname', save=True, update=False), - serializable.Field('time', save=True, update=True), - serializable.Field('data_file', save=True, update=True), - serializable.Field('grid_file', save=True, update=True)]) - +class IceConcentration(GriddedProp, Environment): + _ref_as = ['ice_concentration', 'ice_aware'] default_names = ['ice_fraction', ] - def __eq__(self, o): - t1 = (self.name == o.name and - self.units == o.units and - self.time == o.time and - self.varname == o.varname) - t2 = self.data == o.data - return t1 and t2 + def __init__(self, *args, **kwargs): + super(IceConcentration, self).__init__(*args, **kwargs) + +# def __eq__(self, o): +# t1 = (self.name == o.name and +# self.units == o.units and +# self.time == o.time and +# self.varname == o.varname) +# t2 = self.data == o.data +# return t1 and t2 def __str__(self): return self.serialize(json_='save').__repr__() @@ -525,39 +474,7 @@ class GridCurrent(VelocityGrid, Environment): ['water_u', 'water_v'], ['curr_ucmp', 'curr_vcmp']] - def __init__(self, - name=None, - units=None, - time=None, - variables=None, - grid=None, - depth=None, - grid_file=None, - data_file=None, - dataset=None, - **kwargs): - VelocityGrid.__init__(self, - name=name, - units=units, - time=time, - variables=variables, - grid=grid, - depth=depth, - grid_file=grid_file, - data_file=data_file, - dataset=dataset) - self.angle = None - df = None - if dataset is not None: - df = dataset - elif grid_file is not None: - df = _get_dataset(grid_file) - if df is not None and 'angle' in df.variables.keys(): - # Unrotated ROMS Grid! - self.angle = GriddedProp(name='angle', units='radians', time=None, grid=self.grid, data=df['angle']) - self.depth = depth - - def at(self, points, time, units=None, depth=-1, extrapolate=False, **kwargs): + def at(self, points, time, units=None, extrapolate=False, **kwargs): ''' Find the value of the property at positions P at time T @@ -593,8 +510,7 @@ def at(self, points, time, units=None, depth=-1, extrapolate=False, **kwargs): y = value[:, 0] * np.sin(angs) + value[:, 1] * np.cos(angs) value[:, 0] = x value[:, 1] = y - z = value[:, 2] - z[points[:, 2] == 0.0] = 0 + value[:, 2][points[:, 2] == 0.0] = 0 if mem: self._memoize_result(points, time, value, self._result_memo, _hash=_hash) return value @@ -603,39 +519,17 @@ def at(self, points, time, units=None, depth=-1, extrapolate=False, **kwargs): class GridWind(VelocityGrid, Environment): _ref_as = 'wind' - default_names = [['air_u', 'air_v'], ['Air_U', 'Air_V'], ['air_ucmp', 'air_vcmp'], ['wind_u', 'wind_v']] - def __init__(self, - name=None, - units=None, - time=None, - variables=None, - grid=None, - grid_file=None, - data_file=None, - dataset=None, - **kwargs): - VelocityGrid.__init__(self, - name=name, - units=units, - time=time, - variables=variables, - grid=grid, - grid_file=grid_file, - data_file=data_file, - dataset=dataset) - self.angle = None - df = None - if dataset is not None: - df = dataset - elif grid_file is not None: - df = _get_dataset(grid_file) - if df is not None and 'angle' in df.variables.keys(): - # Unrotated ROMS Grid! - self.angle = GriddedProp(name='angle', units='radians', time=None, grid=self.grid, data=df['angle']) + def __init__(self, wet_dry_mask=None, *args, **kwargs): + super(GridWind, self).__init__(*args, **kwargs) + if wet_dry_mask != None: + if self.grid.infer_location(wet_dry_mask) != 'center': + raise ValueError('Wet/Dry mask does not correspond to grid cell centers') + self.wet_dry_mask = wet_dry_mask + - def at(self, points, time, units=None, depth=-1, extrapolate=False, **kwargs): + def at(self, points, time, units=None, extrapolate=False, **kwargs): ''' Find the value of the property at positions P at time T @@ -665,615 +559,180 @@ def at(self, points, time, units=None, depth=-1, extrapolate=False, **kwargs): return res value = super(GridWind, self).at(points, time, units, extrapolate=extrapolate, **kwargs) + value[points[:, 2] > 0.0] = 0 # no wind underwater! if self.angle is not None: angs = self.angle.at(points, time, extrapolate=extrapolate, **kwargs) x = value[:, 0] * np.cos(angs) - value[:, 1] * np.sin(angs) y = value[:, 0] * np.sin(angs) + value[:, 1] * np.cos(angs) value[:, 0] = x value[:, 1] = y + + if self.wet_dry_mask is not None: + idxs = self.grid.locate_faces(points) + if mem: self._memoize_result(points, time, value, self._result_memo, _hash=_hash) return value -class IceVelocity(VelocityGrid, Environment): +class LandMask(GriddedProp): + def __init__(self, *args, **kwargs): + data = kwargs.pop('data', None) + if data is None or not isinstance(data, (np.ma.MaskedArray, nc4.Variable, np.ndarray)): + raise ValueError('Must provide a netCDF4 Variable, masked numpy array, or an explicit mask on nodes or faces') + if isinstance(data, np.ma.MaskedArray): + data = data.mask + kwargs['data'] = data + + def at(self, points, time, units=None, extrapolate=False, _hash=None, _mem=True, **kwargs): + + if _hash is None: + _hash = self._get_hash(points, time) + if _mem: + res = self._get_memoed(points, time, self._result_memo, _hash=_hash) + if res is not None: + return res + idxs = self.grid.locate_faces(points) + time_idx = self.time.index_of(time) + order = self.dimension_ordering + if order[0] == 'time': + value = self._time_interp(points, time, extrapolate, _mem=_mem, _hash=_hash, **kwargs) + elif order[0] == 'depth': + value = self._depth_interp(points, time, extrapolate, _mem=_mem, _hash=_hash, **kwargs) + else: + value = self._xy_interp(points, time, extrapolate, _mem=_mem, _hash=_hash, **kwargs) + + if _mem: + self._memoize_result(points, time, value, self._result_memo, _hash=_hash) + return value + + + +class IceVelocity(VelocityGrid, Environment): + _ref_as = ['ice_velocity', 'ice_aware'] default_names = [['ice_u', 'ice_v', ], ] - def __init__(self, - name=None, - units=None, - time=None, - variables=None, - grid=None, - grid_file=None, - data_file=None, - dataset=None, - **kwargs): - VelocityGrid.__init__(self, - name=name, - units=units, - time=time, - variables=variables, - grid=grid, - grid_file=grid_file, - data_file=data_file, - dataset=dataset, - **kwargs) - - -class IceAwareProp(serializable.Serializable, Environment): - _state = copy.deepcopy(serializable.Serializable._state) - _schema = VelocityGridSchema - _state.add_field([serializable.Field('units', save=True, update=True), - serializable.Field('time', save=True, update=True), - serializable.Field('data_file', save=True, update=True), - serializable.Field('grid_file', save=True, update=True)]) - def __init__(self, - name=None, - units=None, - time=None, - ice_var=None, - ice_conc_var=None, - grid=None, - grid_file=None, - data_file=None, - **kwargs): - self.name = name - self.units = units - self.time = time - self.ice_var = ice_var - self.ice_conc_var = ice_conc_var - self.grid = grid - self.grid_file = grid_file - self.data_file = data_file +class IceAwarePropSchema(GridVectorPropSchema): + ice_concentration = GridPropSchema(missing=drop) - @classmethod - def from_netCDF(cls, - filename=None, - grid_topology=None, - name=None, - units=None, - time=None, - ice_var=None, - ice_conc_var=None, - grid=None, - dataset=None, - grid_file=None, - data_file=None, - **kwargs): - if filename is not None: - data_file = filename - grid_file = filename - - ds = None - dg = None - if dataset is None: - if grid_file == data_file: - ds = dg = _get_dataset(grid_file) - else: - ds = _get_dataset(data_file) - dg = _get_dataset(grid_file) - else: - ds = dg = dataset - - if grid is None: - grid = _init_grid(grid_file, - grid_topology=grid_topology, - dataset=dg) - if ice_var is None: - ice_var = IceVelocity.from_netCDF(filename, - grid=grid, - dataset=ds, - **kwargs) - if time is None: - time = ice_var.time - - if ice_conc_var is None: - ice_conc_var = IceConcentration.from_netCDF(filename, - time=time, - grid=grid, - dataset=ds, - **kwargs) - if name is None: - name = 'IceAwareProp' - if units is None: - units = ice_var.units - return cls(name='foo', - units=units, - time=time, - ice_var=ice_var, - ice_conc_var=ice_conc_var, - grid=grid, - grid_file=grid_file, - data_file=data_file, - **kwargs) +class IceAwareCurrentSchema(IceAwarePropSchema): + ice_velocity = GridVectorPropSchema(missing=drop) + + +class IceAwareCurrent(GridCurrent): + + _ref_as = ['current', 'ice_aware'] + _req_refs = {'ice_concentration': IceConcentration, 'ice_velocity': IceVelocity} -class IceAwareCurrent(IceAwareProp): + _schema = IceAwareCurrentSchema + _state = copy.deepcopy(GridCurrent._state) + + _state.add_field([serializable.Field('ice_velocity', save=True, update=True, save_reference=True), + serializable.Field('ice_concentration', save=True, update=True, save_reference=True)]) def __init__(self, - name=None, - units=None, - time=None, - ice_var=None, - water_var=None, - ice_conc_var=None, - grid=None, - grid_file=None, - data_file=None, + ice_velocity=None, + ice_concentration=None, + *args, **kwargs): - IceAwareProp.__init__(self, - name=name, - units=units, - time=time, - ice_var=ice_var, - ice_conc_var=ice_conc_var, - grid=grid, - grid_file=grid_file, - data_file=data_file) - self.water_var = water_var - if self.name == 'IceAwareProp': - self.name = 'IceAwareCurrent' + self.ice_velocity = ice_velocity + self.ice_concentration = ice_concentration + super(IceAwareCurrent, self).__init__(*args, **kwargs) @classmethod + @GridCurrent._get_shared_vars() def from_netCDF(cls, - filename=None, - grid_topology=None, - name=None, - units=None, - time=None, - ice_var=None, - water_var=None, - ice_conc_var=None, - grid=None, - dataset=None, - grid_file=None, - data_file=None, + ice_concentration=None, + ice_velocity=None, **kwargs): - - if filename is not None: - data_file = filename - grid_file = filename - - ds = None - dg = None - if dataset is None: - if grid_file == data_file: - ds = dg = _get_dataset(grid_file) - else: - ds = _get_dataset(data_file) - dg = _get_dataset(grid_file) - else: - ds = dg = dataset - - if grid is None: - grid = _init_grid(grid_file, - grid_topology=grid_topology, - dataset=dg) - if water_var is None: - water_var = GridCurrent.from_netCDF(filename, - time=time, - grid=grid, - dataset=ds, - **kwargs) - - return super(IceAwareCurrent, cls).from_netCDF(grid_topology=grid_topology, - name=name, - units=units, - time=time, - ice_var=ice_var, - water_var=water_var, - ice_conc_var=ice_conc_var, - grid=grid, - dataset=ds, - grid_file=grid_file, - data_file=data_file, + if ice_concentration is None: + ice_concentration = IceConcentration.from_netCDF(**kwargs) + if ice_velocity is None: + ice_velocity = IceVelocity.from_netCDF(**kwargs) + return super(IceAwareCurrent, cls).from_netCDF(ice_concentration=ice_concentration, + ice_velocity=ice_velocity, **kwargs) - def at(self, points, time, units=None, extrapolate=False): - interp = self.ice_conc_var.at(points, time, extrapolate=extrapolate).copy() + def at(self, points, time, units=None, extrapolate=False, **kwargs): + interp = self.ice_concentration.at(points, time, extrapolate=extrapolate, **kwargs).copy() interp_mask = np.logical_and(interp >= 0.2, interp < 0.8) + interp_mask = interp_mask.reshape(-1) if len(interp > 0.2): ice_mask = interp >= 0.8 - water_v = self.water_var.at(points, time, units, extrapolate) - ice_v = self.ice_var.at(points, time, units, extrapolate).copy() - interp = (interp * 10) / 6 - 0.2 + water_v = super(IceAwareCurrent, self).at(points, time, units, extrapolate, **kwargs) + ice_v = self.ice_velocity.at(points, time, units, extrapolate, **kwargs).copy() + interp = (interp - 0.2) * 10 / 6. vels = water_v.copy() vels[ice_mask] = ice_v[ice_mask] diff_v = ice_v diff_v -= water_v - vels[interp_mask] += diff_v[interp_mask] * interp[interp_mask][:, np.newaxis] + vels[interp_mask] += (diff_v[interp_mask] * interp[interp_mask][:, np.newaxis]) return vels else: - return self.water_var.at(points, time, units, extrapolate) + return super(IceAwareCurrent, self).at(points, time, units, extrapolate, **kwargs) -class IceAwareWind(IceAwareProp): +class IceAwareWind(GridWind): + + _ref_as = ['wind', 'ice_aware'] + _req_refs = {'ice_concentration': IceConcentration} + + _schema = IceAwarePropSchema + _state = copy.deepcopy(GridWind._state) + + _state.add_field([serializable.Field('ice_concentration', save=True, update=True, save_reference=True)]) def __init__(self, - name=None, - units=None, - time=None, - ice_var=None, - wind_var=None, - ice_conc_var=None, - grid=None, - grid_file=None, - data_file=None, + ice_concentration=None, + *args, **kwargs): - IceAwareProp.__init__(self, - name=name, - units=units, - time=time, - ice_var=ice_var, - ice_conc_var=ice_conc_var, - grid=grid, - grid_file=grid_file, - data_file=data_file) - self.wind_var = wind_var - if self.name == 'IceAwareProp': - self.name = 'IceAwareWind' + self.ice_concentration = ice_concentration + super(IceAwareWind, self).__init__(*args, **kwargs) @classmethod + @GridCurrent._get_shared_vars() def from_netCDF(cls, - filename=None, - grid_topology=None, - name=None, - units=None, - time=None, - ice_var=None, - wind_var=None, - ice_conc_var=None, - grid=None, - dataset=None, - grid_file=None, - data_file=None, + ice_concentration=None, + ice_velocity=None, **kwargs): - - if filename is not None: - data_file = filename - grid_file = filename - - ds = None - dg = None - if dataset is None: - if grid_file == data_file: - ds = dg = _get_dataset(grid_file) - else: - ds = _get_dataset(data_file) - dg = _get_dataset(grid_file) - else: - ds = dg = dataset - - if grid is None: - grid = _init_grid(grid_file, - grid_topology=grid_topology, - dataset=dg) - if wind_var is None: - wind_var = GridWind.from_netCDF(filename, - time=time, - grid=grid, - dataset=ds, - **kwargs) - - return super(IceAwareWind, cls).from_netCDF(grid_topology=grid_topology, - name=name, - units=units, - time=time, - ice_var=ice_var, - wind_var=wind_var, - ice_conc_var=ice_conc_var, - grid=grid, - dataset=ds, - grid_file=grid_file, - data_file=data_file, + if ice_concentration is None: + ice_concentration = IceConcentration.from_netCDF(**kwargs) + if ice_velocity is None: + ice_velocity = IceVelocity.from_netCDF(**kwargs) + return super(IceAwareWind, cls).from_netCDF(ice_concentration=ice_concentration, + ice_velocity=ice_velocity, **kwargs) - def at(self, points, time, units=None, extrapolate=False): - interp = self.ice_conc_var.at(points, time, extrapolate=extrapolate) + def at(self, points, time, units=None, extrapolate=False, **kwargs): + interp = self.ice_concentration.at(points, time, extrapolate=extrapolate, **kwargs) interp_mask = np.logical_and(interp >= 0.2, interp < 0.8) + interp_mask = interp_mask if len(interp >= 0.2) != 0: ice_mask = interp >= 0.8 - wind_v = self.wind_var.at(points, time, units, extrapolate) - interp = (interp * 10) / 6 - 0.2 + wind_v = super(IceAwareWind, self).at(points, time, units, extrapolate, **kwargs) + interp = (interp - 0.2) * 10 / 6. vels = wind_v.copy() vels[ice_mask] = 0 - vels[interp_mask] = vels[interp_mask] * (1 - interp[interp_mask][:, np.newaxis]) # scale winds from 100-0% depending on ice coverage + vels[interp_mask] = vels[interp_mask] * (1 - interp[interp_mask])[:, np.newaxis] # scale winds from 100-0% depending on ice coverage return vels else: - return self.wind_var.at(points, time, units, extrapolate) - - -_valid_temp_units = _valid_units('Temperature') -_valid_dist_units = _valid_units('Length') -_valid_kvis_units = _valid_units('Kinematic Viscosity') -_valid_density_units = _valid_units('Density') -_valid_salinity_units = ('psu',) -_valid_sediment_units = _valid_units('Concentration In Water') - - -class WaterConditions(Environment, serializable.Serializable): - - _ref_as = 'water' - _state = copy.deepcopy(Environment._state) - _units_type = {'temperature': ('temperature', _valid_temp_units), - 'salinity': ('salinity', _valid_salinity_units), - 'sediment': ('concentration in water', - _valid_sediment_units), - 'wave_height': ('length', _valid_dist_units), - 'fetch': ('length', _valid_dist_units), - 'kinematic_viscosity': ('kinematic viscosity', - _valid_kvis_units), - 'density': ('density', _valid_density_units), - } - - # keep track of valid SI units for properties - these are used for - # conversion since internal code uses SI units. Don't expect to change - # these so make it a class level attribute - _si_units = {'temperature': 'K', - 'salinity': 'psu', - 'sediment': 'kg/m^3'} - - def __init__(self, - temperature=300., - salinity=35.0, - sediment=.005, - fetch=0, - name='WaterConditions', - **kwargs): - ''' - Assume units are SI for all properties. 'units' attribute assumes SI - by default. This can be changed, but initialization takes SI. - ''' - if isinstance(temperature, (Number)): - self.temperature = TemperatureTS.constant(data=temperature) - elif isinstance(temperature, (EnvProp)): - self.temperature = temperature - else: - raise TypeError('Temperature is not an environment object or number') - if isinstance(salinity, (Number)): - self.salinity = TimeSeriesProp.constant(name='Salinity', units='psu', data=salinity) - elif isinstance(salinity, (EnvProp)): - self.salinity = salinity - else: - raise TypeError('Salinity is not an environment object or number') - if isinstance(sediment, (Number)): - self.sediment = TimeSeriesProp.constant(name='Sediment', units='kg/m^3', data=sediment) - elif isinstance(sediment, (EnvProp)): - self.sediment = sediment - else: - raise TypeError('Sediment is not an environment object or number') -# self.wave_height = wave_height - self.fetch = fetch - self.kinematic_viscosity = 0.000001 - self.name = 'WaterConditions' -# self._units = dict(self._si_units) -# self.units = units - - @classmethod - def from_netCDF(cls, - filename=None, - grid_topology=None, - name=None, - temperature=None, - salinity=None, - sediment=None, - grid=None, - dataset=None, - grid_file=None, - data_file=None): - if filename is not None: - data_file = filename - grid_file = filename - - ds = None - dg = None - if dataset is None: - if grid_file == data_file: - ds = dg = _get_dataset(grid_file) - else: - ds = _get_dataset(data_file) - dg = _get_dataset(grid_file) - else: - ds = dg = dataset - - if grid is None: - grid = _init_grid(grid_file, - grid_topology=grid_topology, - dataset=dg) - - if time is None: - time = ice_var.time - - if temperature is None: - try: - temperature = GridTemperature.from_netCDF(filename, - time=time, - grid=grid, - dataset=ds) - except: - temperature = 300. - if salinity is None: - try: - salinity = GridSalinity.from_netCDF(filename, - time=time, - grid=grid, - dataset=ds) - except: - salinity = 35. - if sediment is None: - try: - sediment = GridSediment.from_netCDF(filename, - time=time, - grid=grid, - dataset=ds) - except: - sediment = .005 - if name is None: - name = 'WaterConditions' - if units is None: - units = water_var.units - return cls(name=name, - units=units, - time=time, - ice_var=ice_var, - water_var=water_var, - ice_conc_var=ice_conc_var, - grid=grid, - grid_file=grid_file, - data_file=data_file) - - def get(self, attr, unit=None, points=None, time=None, extrapolate=True): # Arguments should be reorganized eventually - var = getattr(self, attr) - if isinstance(var, TimeSeriesProp, TSVectorProp): - if var.is_constant() or (time is not None): - return var.at(points, time, units=unit) - else: - raise ValueError("Time must be specified to get value from non-constant time series property") - elif isinstance(var, GriddedProp, GridVectorProp): - if points is None: - raise ValueError("Points must be defined to get value from gridded property") - if time is None: - raise ValueError("Time must be defined to get value from gridded property") - return var.at(points, time, units=unit) - else: - raise ValueError("var is not a property object") - - -def Wind(*args, **kwargs): - ''' - Wind environment object factory function - ''' - units = kwargs['units'] if 'units' in kwargs else 'm/s' - name = kwargs['name'] if 'name' in kwargs else 'Wind' - - # Constant wind - Wind(speed=s, direction=d) - if ('speed' in kwargs and 'direction' in kwargs) or len(args) == 2: - speed = direction = 0 - if len(args) == 2: - speed = args[0] - direction = args[1] - else: - speed = kwargs['speed'] - direction = kwargs['direction'] - name = 'Constant Wind' if name is 'Wind' else name - if isinstance(speed, Number): - return WindTS.constant(name, units, speed, direction) - else: - raise TypeError('speed must be a single value. For a timeseries, use timeseries=[(t0, (mag,dir)),(t1, (mag,dir))]') - - # Time-varying wind - Wind(timeseries=[(t0, (mag,dir)),(t1, (mag,dir)),...]) - if ('timeseries' in kwargs): - name = 'Wind Time Series' if name is 'Wind' else name - return WindTS(name=name, units=units, timeseries=kwargs['timeseries']) - - # Gridded Wind - Wind(filename=fn, dataset=ds) - if ('filename' in kwargs or 'dataset' in kwargs): - if 'ice_aware' in kwargs: - # Ice Aware Gridded Wind - Wind(filename=fn, dataset=ds, ice_aware=True) - name = 'IceAwareWind' if name is 'Wind' else name - return IceAwareWind.from_netCDF(**kwargs) - name = 'GridWind' if name is 'Wind' else name - return GridWind.from_netCDF(**kwargs) - - # Arguments do not trigger a factory route. Attempt construction using default class __init__s - w = None - warnings.warn('Attempting default Wind constructions') - return _attempt_construction([WindTS, GridWind], **kwargs) - - -def Current(*args, **kwargs): - ''' - Wind environment object factory function - ''' - units = kwargs['units'] if 'units' in kwargs else 'm/s' - name = kwargs['name'] if 'name' in kwargs else 'Current' - - # Constant wind - Wind(speed=s, direction=d) - if ('speed' in kwargs and 'direction' in kwargs) or len(args) == 2: - speed = direction = 0 - if len(args) == 2: - speed = args[0] - direction = args[1] - else: - speed = kwargs['speed'] - direction = kwargs['direction'] - name = 'Constant Current' if name is 'Wind' else name - if isinstance(speed, Number): - return CurrentTS.constant_current(name, units, speed, direction) - else: - raise TypeError('speed must be a single value. For a timeseries, use timeseries=[(t0, (mag,dir)),(t1, (mag,dir))]') - - # Time-varying wind - Wind(timeseries=[(t0, (mag,dir)),(t1, (mag,dir)),...]) - if ('timeseries' in kwargs): - name = 'Current Time Series' if name is 'Current' else name - return CurrentTS(name=name, units=units, timeseries=kwargs['timeseries']) - - # Gridded Wind - Wind(filename=fn, dataset=ds) - if ('filename' in kwargs or 'dataset' in kwargs): - if 'ice_aware' in kwargs: - # Ice Aware Gridded Current - Current(filename=fn, dataset=ds, ice_aware=True) - name = 'IceAwareCurrent' if name is 'Current' else name - return IceAwareCurrent.from_netCDF(**kwargs) - name = 'GridCurrent' if name is 'Current' else name - return GridCurrent.from_netCDF(**kwargs) - - # Arguments do not trigger a factory route. Attempt construction using default class __init__s - w = None - warnings.warn('Attempting default Wind constructions') - return _attempt_construction([CurrentTS, GridCurrent], **kwargs) - - -def Temperature(*args, **kwargs): - units = kwargs['units'] if 'units' in kwargs else 'K' - name = kwargs['name'] if 'name' in kwargs else 'WaterTemp' - kwargs['units'] = units - kwargs['name'] = name - # Constant Temperature - WaterTemp(temp=t) - if ('temp' in kwargs or 'temperature' in kwargs) or len(args) == 1: - temp = 0 - if len(args) == 1: - temp = args[0] - else: - temp = kwargs['temp'] if 'temp' in kwargs else kwargs['temperature'] - name = 'Constant Temperature' if name is 'Temperature' else name - if isinstance(speed, Number): - return TemperatureTS.constant_temp(name, units, temp) - else: - raise TypeError('temperature must be a single value. For a timeseries, use timeseries=[(t1, temp1), (t2, temp2),...]') - - # Time-varying temp - Temperature(timeseries=[(t1, temp1), (t2, temp2),...]) - if ('timeseries' in kwargs): - name = 'Temperature Time Series' if name is 'Temperature' else name - return TemperatureTS(name=name, units=units, timeseries=kwargs['timeseries']) - - # Gridded Temp - Temperature(filename=fn, dataset=ds) - if ('filename' in kwargs or 'dataset' in kwargs): - if 'ice_aware' in kwargs: - # Ice Aware Gridded Wind - Wind(filename=fn, dataset=ds, ice_aware=True) - name = 'IceAwareWaterTemp' if name is 'Temperature' else name - return IceAwareWaterTemperature.from_netCDF(**kwargs) - name = 'GridTemperature' if name is 'Temperature' else name - return GridTemperature.from_netCDF(**kwargs) - - # Arguments do not trigger a factory route. Attempt construction using default class __init__s - w = None - warnings.warn('Attempting default Temperature constructions') - return _attempt_construction([TemperatureTS, GridTemperature], **kwargs) - - -def _attempt_construction(types, **kwargs): - for t in types: - try: - prop = t(**kwargs) - return prop - except Error as e: - print('t.__name__ construction failed: {0}'.format(e)) - raise RuntimeError('Unable to build any type of environment object using the arguments provided. Please see the documentation for the usage of') - + return super(IceAwareWind, self).at(points, time, units, extrapolate, **kwargs) + +def load_all_from_netCDF(filename=None, + grid_topology=None, + name=None, + time=None, + grid=None, + depth=None, + dataset=None, + data_file=None, + grid_file=None, + **kwargs): + pass diff --git a/py_gnome/gnome/environment/grid.py b/py_gnome/gnome/environment/grid.py index a3edaf99d..752130bd1 100644 --- a/py_gnome/gnome/environment/grid.py +++ b/py_gnome/gnome/environment/grid.py @@ -2,21 +2,317 @@ grid for wind or current data """ -import datetime import copy import numpy as np -from colander import (SchemaNode, drop, Float) +from colander import (SchemaNode, drop, Float, String, SequenceSchema, Sequence) from gnome.cy_gnome.cy_grid_curv import CyTimeGridWindCurv from gnome.cy_gnome.cy_grid_rect import CyTimeGridWindRect from gnome.utilities.time_utils import date_to_sec from gnome.utilities.serializable import Serializable, Field -from gnome.persist import validators, base_schema +from gnome.persist import base_schema from .environment import Environment +import pyugrid +import pysgrid +import zipfile +from gnome.utilities.file_tools.data_helpers import _get_dataset, _gen_topology + + +class PyGridSchema(base_schema.ObjType): +# filename = SequenceSchema(SchemaNode(String()), accept_scalar=True) + filename = SchemaNode(typ=Sequence(accept_scalar=True), children=[SchemaNode(String())]) + + +class PyGrid(Serializable): + + _def_count = 0 + + _state = copy.deepcopy(Serializable._state) + _schema = PyGridSchema + _state.add_field([Field('filename', save=True, update=True, isdatafile=True)]) + + def __new__(cls, *args, **kwargs): + ''' + If you construct a PyGrid object directly, you will always + get one of the child types based on your input + ''' + if cls is not PyGrid_U and cls is not PyGrid_S: + if 'faces' in kwargs: + cls = PyGrid_U + else: + cls = PyGrid_S +# cls.obj_type = c.obj_type + return super(type(cls), cls).__new__(cls, *args, **kwargs) + + def __init__(self, + filename=None, + *args, + **kwargs): + ''' + Init common to all PyGrid types. This constructor will take all the kwargs of both + pyugrid.UGrid and pysgrid.SGrid. See their documentation for details + + :param filename: Name of the file this grid was constructed from, if available. + ''' + super(PyGrid, self).__init__(**kwargs) + if 'name' in kwargs: + self.name = kwargs['name'] + else: + self.name = self.name + '_' + str(type(self)._def_count) + self.obj_type = str(type(self).__bases__[0]) + self.filename = filename + type(self)._def_count += 1 + + @classmethod + def load_grid(cls, filename, topology_var): + ''' + Redirect to grid-specific loading routine. + ''' + if hasattr(topology_var, 'face_node_connectivity') or isinstance(topology_var, dict) and 'faces' in topology_var.keys(): + cls = PyGrid_U + return cls.from_ncfile(filename) + else: + cls = PyGrid_S + return cls.load_grid(filename) + pass + + @classmethod + def from_netCDF(cls, filename=None, dataset=None, grid_type=None, grid_topology=None, *args, **kwargs): + ''' + :param filename: File containing a grid + :param dataset: Takes precedence over filename, if provided. + :param grid_type: Must be provided if Dataset does not have a 'grid_type' attribute, or valid topology variable + :param grid_topology: A dictionary mapping of grid attribute to variable name. Takes precendence over discovered attributes + :param **kwargs: All kwargs to SGrid or UGrid are valid, and take precedence over all. + :returns: Instance of PyGrid_U, PyGrid_S, or PyGrid_R + ''' + gf = dataset if filename is None else _get_dataset(filename, dataset) + if gf is None: + raise ValueError('No filename or dataset provided') + + cls = PyGrid._get_grid_type(gf, grid_topology, grid_type) + init_args, gf_vars = cls._find_required_grid_attrs(filename, + dataset=dataset, + grid_topology=grid_topology) + return cls(**init_args) + + @classmethod + def _find_required_grid_attrs(cls, filename, dataset=None, grid_topology=None,): + ''' + This function is the top level 'search for attributes' function. If there are any + common attributes to all potential grid types, they will be sought here. + + This function returns a dict, which maps an attribute name to a netCDF4 + Variable or numpy array object extracted from the dataset. When called from + PyGrid_U or PyGrid_S, this function should provide all the kwargs needed to + create a valid instance. + ''' + gf_vars = dataset.variables if dataset is not None else _get_dataset(filename).variables + init_args = {} + init_args['filename'] = filename + node_attrs = ['node_lon', 'node_lat'] + node_coord_names = [['node_lon', 'node_lat'], ['lon', 'lat'], ['lon_psi', 'lat_psi']] + composite_node_names = ['nodes', 'node'] + if grid_topology is None: + for n1, n2 in node_coord_names: + if n1 in gf_vars and n2 in gf_vars: + init_args[node_attrs[0]] = gf_vars[n1][:] + init_args[node_attrs[1]] = gf_vars[n2][:] + break + if node_attrs[0] not in init_args: + for n in composite_node_names: + if n in gf_vars: + v = gf_vars[n][:].reshape(-1, 2) + init_args[node_attrs[0]] = v[:, 0] + init_args[node_attrs[1]] = v[:, 1] + break + if node_attrs[0] not in init_args: + raise ValueError('Unable to find node coordinates.') + else: + for n, v in grid_topology.items(): + if n in node_attrs: + init_args[n] = gf_vars[v][:] + if n in composite_node_names: + v = gf_vars[n][:].reshape(-1, 2) + init_args[node_attrs[0]] = v[:, 0] + init_args[node_attrs[1]] = v[:, 1] + return init_args, gf_vars + + @classmethod + def new_from_dict(cls, dict_): + dict_.pop('json_') + filename = dict_['filename'] + rv = cls.from_netCDF(filename) + rv.__class__._restore_attr_from_save(rv, dict_) + rv._id = dict_.pop('id') if 'id' in dict_ else rv.id + rv.__class__._def_count -= 1 + return rv + + @staticmethod + def _get_grid_type(dataset, grid_topology=None, grid_type=None): + sgrid_names = ['sgrid', 'pygrid_s', 'staggered', 'curvilinear', 'roms'] + ugrid_names = ['ugrid', 'pygrid_u', 'triangular', 'unstructured'] + if grid_type is not None: + if grid_type.lower() in sgrid_names: + return PyGrid_S + elif grid_type.lower() in ugrid_names: + return PyGrid_U + else: + raise ValueError('Specified grid_type not recognized/supported') + if grid_topology is not None: + if 'faces' in grid_topology.keys() or grid_topology.get('grid_type', 'notype').lower() in ugrid_names: + return PyGrid_U + else: + return PyGrid_S + else: + # no topology, so search dataset for grid_type variable + if hasattr(dataset, 'grid_type') and dataset.grid_type in sgrid_names + ugrid_names: + if dataset.grid_type.lower() in ugrid_names: + return PyGrid_U + else: + return PyGrid_S + else: + # no grid type explicitly specified. is a topology variable present? + topology = PyGrid._find_topology_var(None, dataset=dataset) + if topology is not None: + if hasattr(topology, 'node_coordinates') and not hasattr(topology, 'node_dimensions'): + return PyGrid_U + else: + return PyGrid_S + else: + # no topology variable either, so generate and try again. + # if no defaults are found, _gen_topology will raise an error + try: + u_init_args, u_gf_vars = PyGrid_U._find_required_grid_attrs(None, dataset) + return PyGrid_U + except ValueError: + s_init_args, s_gf_vars = PyGrid_S._find_required_grid_attrs(None, dataset) + return PyGrid_S + + @staticmethod + def _find_topology_var(filename, + dataset=None): + gf = _get_dataset(filename, dataset) + gts = [] + for v in gf.variables: + if hasattr(v, 'cf_role') and 'topology' in v.cf_role: + gts.append(v) +# gts = gf.get_variables_by_attributes(cf_role=lambda t: t is not None and 'topology' in t) + if len(gts) != 0: + return gts[0] + else: + return None + + @property + def shape(self): + return self.node_lon.shape + + def __eq__(self, o): + if self is o: + return True + for n in ('nodes', 'faces'): + if hasattr(self, n) and hasattr(o, n) and getattr(self, n) is not None and getattr(o, n) is not None: + s = getattr(self, n) + s2 = getattr(o, n) + if s.shape != s2.shape or np.any(s != s2): + return False + return True + + def serialize(self, json_='webapi'): + pass + return Serializable.serialize(self, json_=json_) + + def _write_grid_to_file(self, pth): + self.save_as_netcdf(pth) + + def save(self, saveloc, references=None, name=None): + ''' + INCOMPLETE + Write Wind timeseries to file or to zip, + then call save method using super + ''' +# name = self.name +# saveloc = os.path.splitext(name)[0] + '_grid.GRD' + + if zipfile.is_zipfile(saveloc): + if self.filename is None: + self._write_grid_to_file(saveloc) + self._write_grid_to_zip(saveloc, saveloc) + self.filename = saveloc +# else: +# self._write_grid_to_zip(saveloc, self.filename) + else: + if self.filename is None: + self._write_grid_to_file(saveloc) + self.filename = saveloc + return super(PyGrid, self).save(saveloc, references, name) + + +class PyGrid_U(PyGrid, pyugrid.UGrid): + + @classmethod + def _find_required_grid_attrs(cls, filename, dataset=None, grid_topology=None): + + # Get superset attributes + init_args, gf_vars = super(PyGrid_U, cls)._find_required_grid_attrs(filename=filename, + dataset=dataset, + grid_topology=grid_topology) + + face_attrs = ['faces'] + if grid_topology is not None: + face_var_names = [grid_topology.get(n) for n in face_attrs] + else: + face_var_names = ['faces', 'tris', 'nv', 'ele'] + + for n in face_var_names: + if n in gf_vars: + init_args[face_attrs[0]] = gf_vars[n][:] + break + if face_attrs[0] in init_args: + if init_args[face_attrs[0]].shape[0] == 3: + init_args[face_attrs[0]] = np.ascontiguousarray(np.array(init_args[face_attrs[0]]).T - 1) + return init_args, gf_vars + else: + raise ValueError('Unable to find faces variable') + + +class PyGrid_S(PyGrid, pysgrid.SGrid): + + @classmethod + def _find_required_grid_attrs(cls, filename, dataset=None, grid_topology=None): + + # THESE ARE ACTUALLY ALL OPTIONAL. This should be migrated when optional attributes are dealt with + # Get superset attributes + init_args, gf_vars = super(PyGrid_S, cls)._find_required_grid_attrs(filename, + dataset=dataset, + grid_topology=grid_topology) + + center_attrs = ['center_lon', 'center_lat'] + edge1_attrs = ['edge1_lon', 'edge1_lat'] + edge2_attrs = ['edge2_lon', 'edge2_lat'] + + center_coord_names = [['center_lon', 'center_lat'], ['lon_rho', 'lat_rho']] + edge1_coord_names = [['edge1_lon', 'edge1_lat'], ['lon_u', 'lat_u']] + edge2_coord_names = [['edge2_lon', 'edge2_lat'], ['lon_v', 'lat_v']] + + if grid_topology is None: + for attr, names in (zip((center_attrs, edge1_attrs, edge2_attrs), + (center_coord_names, edge1_coord_names, edge2_coord_names))): + for n1, n2 in names: + if n1 in gf_vars and n2 in gf_vars: + init_args[attr[0]] = gf_vars[n1][:] + init_args[attr[1]] = gf_vars[n2][:] + break + else: + for n, v in grid_topology.items(): + if n in center_attrs + edge1_attrs + edge2_attrs and v in gf_vars: + init_args[n] = gf_vars[v][:] + return init_args, gf_vars + class GridSchema(base_schema.ObjType): name = 'grid' @@ -38,7 +334,6 @@ class Grid(Environment, Serializable): _state.add(save=_create, update=_update) _schema = GridSchema - def __init__(self, filename, topology_file=None, grid_type=1, extrapolate=False, time_offset=0, **kwargs): @@ -69,6 +364,7 @@ def __init__(self, filename, topology_file=None, grid_type=1, super(Grid, self).__init__(**kwargs) def __repr__(self): + self_ts = None return ('{0.__class__.__module__}.{0.__class__.__name__}(' 'timeseries={1}' ')').format(self, self_ts) @@ -89,7 +385,6 @@ def grid_type(self, value): # may want a check on value self._grid_type = value - extrapolate = property(lambda self: self.grid.extrapolate, lambda self, val: setattr(self.grid, 'extrapolate', @@ -107,7 +402,6 @@ def prepare_for_model_run(self, model_time): pass - def prepare_for_model_step(self, model_time): """ Make sure we have the right data loaded @@ -117,10 +411,9 @@ def prepare_for_model_step(self, model_time): def get_value(self, time, location): ''' - Return the value at specified time and location. - + Return the value at specified time and location. ''' - data = self.grid.get_value(time,location) + data = self.grid.get_value(time, location) return data @@ -150,3 +443,5 @@ def deserialize(cls, json_): _to_dict = schema.deserialize(json_) return _to_dict + + diff --git a/py_gnome/gnome/environment/grid_property.py b/py_gnome/gnome/environment/grid_property.py index 6bd1c7cda..ead4e8718 100644 --- a/py_gnome/gnome/environment/grid_property.py +++ b/py_gnome/gnome/environment/grid_property.py @@ -1,27 +1,36 @@ import netCDF4 as nc4 import numpy as np -from datetime import datetime, timedelta from collections import OrderedDict -from colander import SchemaNode, Float, Boolean, Sequence, MappingSchema, drop, String, OneOf, SequenceSchema, TupleSchema, DateTime -from gnome.utilities.file_tools.data_helpers import _init_grid, _gen_topology, _get_dataset +from colander import SchemaNode, SchemaType, Float, Boolean, Sequence, MappingSchema, drop, String, OneOf, SequenceSchema, TupleSchema, DateTime, List +from gnome.utilities.file_tools.data_helpers import _get_dataset from gnome.environment.property import * +from gnome.environment.grid import PyGrid, PyGrid_U, PyGrid_S, PyGridSchema -import pyugrid -import pysgrid -import unit_conversion -import collections import hashlib - +from gnome.utilities.orderedcollection import OrderedCollection +from gnome.environment.ts_property import TimeSeriesProp +from functools import wraps +import pytest class GridPropSchema(PropertySchema): - varname = SchemaNode(String(), missing=drop) - data_file = SchemaNode(String(), missing=drop) - grid_file = SchemaNode(String(), missing=drop) + varname = SchemaNode(String()) + grid = PyGridSchema(missing=drop) + data_file = SchemaNode(typ=Sequence(accept_scalar=True), children=[SchemaNode(String())]) + grid_file = SchemaNode(typ=Sequence(accept_scalar=True), children=[SchemaNode(String())]) class GriddedProp(EnvProp): + _state = copy.deepcopy(EnvProp._state) + + _schema = GridPropSchema + + _state.add_field([serializable.Field('grid', save=True, update=True, save_reference=True), + serializable.Field('varname', save=True, update=True), + serializable.Field('data_file', save=True, update=True, isdatafile=True), + serializable.Field('grid_file', save=True, update=True, isdatafile=True)]) + default_names = [] _def_count = 0 @@ -59,14 +68,12 @@ def __init__(self, :type varname: string ''' - self._grid = self._data_file = self._grid_file = None - if any([grid is None, data is None]): raise ValueError("Grid and Data must be defined") if not hasattr(data, 'shape'): if grid.infer_location is None: raise ValueError('Data must be able to fit to the grid') - self._grid = grid + self.grid = grid self.depth = depth super(GriddedProp, self).__init__(name=name, units=units, time=time, data=data) self.data_file = data_file @@ -75,6 +82,9 @@ def __init__(self, self._result_memo = OrderedDict() self.fill_value = fill_value +# def __repr__(self): +# return str(self.serialize()) + @classmethod def from_netCDF(cls, filename=None, @@ -115,7 +125,7 @@ def from_netCDF(cls, :type time: [] of datetime.datetime, netCDF4 Variable, or Time object :type data: netCDF4.Variable or numpy.array :type grid: pysgrid or pyugrid - :type S_Depth or L_Depth + :type depth: Depth, S_Depth or L_Depth :type dataset: netCDF4.Dataset :type data_file: string :type grid_file: string @@ -140,9 +150,9 @@ def from_netCDF(cls, ds = dataset if grid is None: - grid = _init_grid(grid_file, - grid_topology=grid_topology, - dataset=dg) + grid = PyGrid.from_netCDF(grid_file, + dataset=dg, + grid_topology=grid_topology) if varname is None: varname = cls._gen_varname(data_file, dataset=ds) @@ -157,19 +167,15 @@ def from_netCDF(cls, units = data.units except AttributeError: units = None - timevar = None if time is None: - try: - timevar = data.time if data.time == data.dimensions[0] else data.dimensions[0] - except AttributeError: - if len(data.dimensions) > 2: - timevar = data.dimensions[0] - time = Time(ds[timevar]) - else: - time = None + time = Time.from_netCDF(filename=data_file, + dataset=ds, + datavar=data) if depth is None: - from gnome.environment.environment_objects import Depth - depth = Depth(surface_index=-1) + if (isinstance(grid, PyGrid_S) and len(data.shape) == 4 or + isinstance(grid, PyGrid_U) and len(data.shape) == 3): + from gnome.environment.environment_objects import Depth + depth = Depth(surface_index=-1) # if len(data.shape) == 4 or (len(data.shape) == 3 and time is None): # from gnome.environment.environment_objects import S_Depth # depth = S_Depth.from_netCDF(grid=grid, @@ -188,6 +194,7 @@ def from_netCDF(cls, grid_file=grid_file, data_file=data_file, fill_value=fill_value, + varname=varname, **kwargs) @property @@ -220,18 +227,6 @@ def data(self, d): raise ValueError("Data/grid shape mismatch. Data shape is {0}, Grid shape is {1}".format(d.shape, self.grid.node_lon.shape)) self._data = d - @property - def grid(self): - return self._grid - - @grid.setter - def grid(self, g): - if not (isinstance(g, (pyugrid.UGrid, pysgrid.SGrid))): - raise ValueError('Grid must be set with a pyugrid.UGrid or pysgrid.SGrid object') - if self.data is not None and g.infer_location(self.data) is None: - raise ValueError("Data/grid shape mismatch. Data shape is {0}, Grid shape is {1}".format(self.data.shape, self.grid.node_lon.shape)) - self._grid = g - @property def grid_shape(self): if hasattr(self.grid, 'shape'): @@ -272,31 +267,6 @@ def _get_memoed(self, points, time, D, _copy=False, _hash=None): else: return None - def set_attr(self, - name=None, - time=None, - data=None, - data_file=None, - grid=None, - grid_file=None): - self.name = name if name is not None else self.name -# self.units = units if units is not None else self.units - if time is not None: - if data and len(time) != data.shape[0]: - raise ValueError("Time provided is incompatible with data source time dimension") - self.time = time - if data is not None: - if (grid and grid.infer_location(data) is None) or self.grid.infer_location(data) is None: - raise ValueError("Data shape is incompatible with grid shape.") - self._data = data - if grid is not None: - if data is None: - if grid.infer_location(self.data) is None: - raise ValueError("Grid shape is incompatible with current data shape.") - self._grid = grid - self.grid_file = grid_file if grid_file is not None else self.grid_file - self.data_file = data_file if data_file is not None else self.data_file - def center_values(self, time, units=None, extrapolate=False): # NOT COMPLETE if not extrapolate: @@ -315,6 +285,50 @@ def center_values(self, time, units=None, extrapolate=False): value = self.at(centers, time, units) return value + @property + def dimension_ordering(self): + ''' + Returns a list that describes the dimensions of the property's data. If a dimension_ordering is assigned, + it will continue to use that. If no dimension_ordering is set, then a default ordering will be generated + based on the object properties and data shape. + + For example, if the data has 4 dimensions and is represented by a PyGrid_S (structured grid), and the + GriddedProp has a depth and time assigned, then the assumed ordering is ['time','depth','lon','lat'] + + If the data has 3 dimensions, self.grid is a PyGrid_S, and self.time is None, then the ordering is + ['depth','lon','lat'] + If the data has 3 dimensions, self.grid is a PyGrid_U, the ordering is ['time','depth','ele'] + ''' + if not hasattr(self, '_order'): + self._order = None + if self._order is not None: + return self._order + else: + if isinstance(self.grid, PyGrid_S): + order = ['time', 'depth', 'lon', 'lat'] + else: + order = ['time', 'depth', 'ele'] + ndim = len(self.data.shape) + diff = len(order) - ndim + if diff == 0: + return order + elif diff == 1: + if self.time is not None: + del order[1] + elif self.depth is not None: + del order[0] + else: + raise ValueError('Generated ordering too short to fit data. Time or depth must not be None') + elif diff == 2: + order = order[2:] + else: + raise ValueError('Too many/too few dimensions ndim={0}'.format(ndim)) + return order + + @dimension_ordering.setter + def dimension_ordering(self, order): + self._order = order + # @profile def at(self, points, time, units=None, extrapolate=False, _hash=None, _mem=True, **kwargs): ''' @@ -322,7 +336,6 @@ def at(self, points, time, units=None, extrapolate=False, _hash=None, _mem=True, :param points: Coordinates to be queried (P) :param time: The time at which to query these points (T) - :param depth: Specifies the depth level of the variable :param units: units the values will be returned in (or converted to) :param extrapolate: if True, extrapolation will be supported :type points: Nx2 array of double @@ -339,169 +352,141 @@ def at(self, points, time, units=None, extrapolate=False, _hash=None, _mem=True, if _mem: res = self._get_memoed(points, time, self._result_memo, _hash=_hash) if res is not None: - return np.ma.filled(res) + return res - value = self._at_4D(points, time, self.data, units=units, extrapolate=extrapolate, _hash=_hash) + order = self.dimension_ordering + if order[0] == 'time': + value = self._time_interp(points, time, extrapolate, _mem=_mem, _hash=_hash, **kwargs) + elif order[0] == 'depth': + value = self._depth_interp(points, time, extrapolate, _mem=_mem, _hash=_hash, **kwargs) + else: + value = self._xy_interp(points, time, extrapolate, _mem=_mem, _hash=_hash, **kwargs) if _mem: self._memoize_result(points, time, value, self._result_memo, _hash=_hash) - return np.ma.filled(value) + return value -# @profile - def _at_2D(self, pts, data, slices=None, **kwargs): - cur_dim = len(data.shape) - len(slices) if slices is not None else len(data.shape) - if slices is not None and cur_dim != 2: - raise ValueError("Data dimensions are incorrect! dimension is {0}".format(len(data.shape) - len(slices))) + def _xy_interp(self, points, time, extrapolate, slices=(), **kwargs): + ''' + Uses the py(s/u)grid interpolation to determine the values at the points, and returns it + :param points: Coordinates to be queried (3D) + :param time: Time of the query + :param extrapolate: Turns extrapolation on or off + :param slices: describes how the data needs to be sliced to reach the appropriate dimension + :type points: Nx3 array of double + :type time: datetime.datetime object + :type extrapolate: boolean + :type slices: tuple of integers or slice objects + ''' _hash = kwargs['_hash'] if '_hash' in kwargs else None units = kwargs['units'] if 'units' in kwargs else None - value = self.grid.interpolate_var_to_points(pts, data, _hash=_hash[0], slices=slices, _memo=True) + value = self.grid.interpolate_var_to_points(points[:, 0:2], self.data, _hash=_hash[0], slices=slices, _memo=True) if units is not None and units != self.units: value = unit_conversion.convert(self.units, units, value) return value -# @profile - def _at_3D(self, points, data, slices=None, **kwargs): - cur_dim = len(data.shape) - len(slices) if slices is not None else len(data.shape) - if slices is not None and cur_dim != 3: - raise ValueError("Data dimensions are incorrect! dimension is {0}".format(len(data.shape) - len(slices))) - indices, alphas = self.depth.interpolation_alphas(points, data.shape[1:], kwargs['_hash']) + def _time_interp(self, points, time, extrapolate, slices=(), **kwargs): + ''' + Uses the Time object to interpolate the result of the next level of interpolation, as specified + by the dimension_ordering attribute. + :param points: Coordinates to be queried (3D) + :param time: Time of the query + :param extrapolate: Turns extrapolation on or off + :param slices: describes how the data needs to be sliced to reach the appropriate dimension + :type points: Nx3 array of double + :type time: datetime.datetime object + :type extrapolate: boolean + :type slices: tuple of integers or slice objects + ''' + order = self.dimension_ordering + idx = order.index('time') + if order[idx + 1] != 'depth': + val_func = self._xy_interp + else: + val_func = self._depth_interp + + if time == self.time.min_time or (extrapolate and time < self.time.min_time): + # min or before + return val_func(points, time, extrapolate, slices=(0,), ** kwargs) + elif time == self.time.max_time or (extrapolate and time > self.time.max_time): + return val_func(points, time, extrapolate, slices=(-1,), **kwargs) + else: + ind = self.time.index_of(time) + s1 = slices + (ind,) + s0 = slices + (ind - 1,) + v0 = val_func(points, time, extrapolate, slices=s0, **kwargs) + v1 = val_func(points, time, extrapolate, slices=s1, **kwargs) + alphas = self.time.interp_alpha(time) + value = v0 + (v1 - v0) * alphas + return value + + def _depth_interp(self, points, time, extrapolate, slices=(), **kwargs): + ''' + Uses the Depth object to interpolate the result of the next level of interpolation, as specified + by the dimension_ordering attribute. + :param points: Coordinates to be queried (3D) + :param time: Time of the query + :param extrapolate: Turns extrapolation on or off + :param slices: describes how the data needs to be sliced to reach the appropriate dimension + :type points: Nx3 array of double + :type time: datetime.datetime object + :type extrapolate: boolean + :type slices: tuple of integers or slice objects + ''' + order = self.dimension_ordering + idx = order.index('depth') + if order[idx + 1] != 'time': + val_func = self._xy_interp + else: + val_func = self._time_interp + indices, alphas = self.depth.interpolation_alphas(points, self.data.shape[1:], kwargs.get('_hash', None)) if indices is None and alphas is None: # all particles are on surface - return self._at_2D(points[:, 0:2], data, slices=slices + (self.depth.surface_index,), **kwargs) + return val_func(points, time, extrapolate, slices=slices + (self.depth.surface_index,), **kwargs) else: min_idx = indices[indices != -1].min() - 1 max_idx = indices.max() - pts = points[:, 0:2] values = np.zeros(len(points), dtype=np.float64) - v0 = self._at_2D(pts, data, slices=slices + (min_idx - 1,), **kwargs) + v0 = val_func(points, time, extrapolate, slices=slices + (min_idx - 1,), **kwargs) for idx in range(min_idx + 1, max_idx + 1): - v1 = self._at_2D(pts, data, slices=slices + (idx,), **kwargs) + v1 = val_func(points, time, extrapolate, slices=slices + (idx,), **kwargs) pos_idxs = np.where(indices == idx)[0] sub_vals = v0 + (v1 - v0) * alphas if len(pos_idxs) > 0: values.put(pos_idxs, sub_vals.take(pos_idxs)) v0 = v1 - if 'extrapolate' in kwargs and kwargs['extrapolate']: + if extrapolate: underground = (indices == self.depth.bottom_index) - values[underground] = self._at_2D(pts, data, slices=slices + (self.depth.bottom_index,) ** kwargs) + values[underground] = val_func(points, time, extrapolate, slices=slices + (self.depth.bottom_index,), **kwargs) else: underground = (indices == self.depth.bottom_index) values[underground] = self.fill_value return values -# @profile - def _at_4D(self, points, time, data, **kwargs): - value = None - if self.time is None or len(self.time) == 1: - if len(data.shape) == 2: - return self._at_2D(points[:, 0:2], data, **kwargs) - if len(data.shape) == 3: - return self._at_3D(points, data, **kwargs) - if len(data.shape) == 4 and len(data) == 1: - return self._at_3D(points, data, slices=(0,), **kwargs) - else: - raise ValueError("Cannot determine correct time index without time axis") - else: - extrapolate = kwargs['extrapolate'] if 'extrapolate' in kwargs else False - if not extrapolate: - self.time.valid_time(time) - if time == self.time.min_time or (extrapolate and time < self.time.min_time): - return self._at_3D(points, data, slices=(0,), **kwargs) - elif time == self.time.max_time or (extrapolate and time > self.time.max_time): - return self._at_3D(points, data, slices=(-1,), **kwargs) - else: - surface_only = False - if all(np.isclose(points[:, 2], 0.0, atol=0.0001)): - surface_only = True - ind = self.time.index_of(time) - alphas = self.time.interp_alpha(time) - s1 = (ind,) - s0 = (ind - 1,) - v0 = v1 = None - if surface_only and self.depth is not None: - pts = points[:, 0:2] - s1 = s1 + (self.depth.surface_index,) - s0 = s0 + (self.depth.surface_index,) - v0 = self._at_2D(pts, data, slices=s0, **kwargs) - v1 = self._at_2D(pts, data, slices=s1, **kwargs) - elif surface_only and self.depth is None and len(data.shape) == 3: - pts = points[:, 0:2] - v0 = self._at_2D(pts, data, slices=s0, **kwargs) - v1 = self._at_2D(pts, data, slices=s1, **kwargs) - else: - v0 = self._at_3D(points, data, slices=s0, **kwargs) - v1 = self._at_3D(points, data, slices=s1, **kwargs) - value = v0 + (v1 - v0) * alphas - return value +# def serialize(self, json_='webapi'): +# _dict = serializable.Serializable.serialize(self, json_=json_) +# if self.data_file is not None: +# # put file in save zip +# pass +# else: +# # write data to file and put in zip +# pass +# if self.grid_file is not None: +# # put grid in save zip. make sure it's not in there twice. +# pass +# else: +# # write grid to file and put in zip +# pass - def _at_surface_only(self, - pts, - time, - units=None, - depth=-1, - extrapolate=False, - memoize=True, - _hash=None, - mask=False, - **kwargs): - sg = False - mem = memoize - if self.time is None: - # special case! prop has no time variance - v0 = self.grid.interpolate_var_to_points(pts, - self.data, - slices=None, - slice_grid=sg, - _memo=mem, - _hash=_hash,) - return np.ma.filled(v0) - - t_alphas = s0 = s1 = value = None - if not extrapolate: - self.time.valid_time(time) - t_index = self.time.index_of(time, extrapolate) - if len(self.time) == 1: - value = self.grid.interpolate_var_to_points(pts, - self.data, - slices=[0], - _memo=mem, - _hash=_hash,) - else: - if time > self.time.max_time: - value = self.data[-1] - if time <= self.time.min_time: - value = self.data[0] - if extrapolate and t_index == len(self.time.time): - s0 = [t_index - 1] - value = self.grid.interpolate_var_to_points(pts, - self.data, - slices=s0, - _memo=mem, - _hash=_hash,) - else: - t_alphas = self.time.interp_alpha(time, extrapolate) - s1 = [t_index] - s0 = [t_index - 1] - if len(self.data.shape) == 4: - s0.append(depth) - s1.append(depth) - v0 = self.grid.interpolate_var_to_points(pts, - self.data, - slices=s0, - slice_grid=sg, - _memo=mem, - _hash=_hash[0],) - v1 = self.grid.interpolate_var_to_points(pts, - self.data, - slices=s1, - slice_grid=sg, - _memo=mem, - _hash=_hash[0],) - value = v0 + (v1 - v0) * t_alphas + @classmethod + def new_from_dict(cls, dict_): + if 'data' not in dict_: + return cls.from_netCDF(**dict_) + return super(GriddedProp, cls).new_from_dict(dict_) - if units is not None and units != self.units: - value = unit_conversion.convert(self.units, units, value) - return value + @classmethod + def deserialize(cls, json_): + return super(GriddedProp, cls).deserialize(json_) @classmethod def _gen_varname(cls, @@ -527,15 +512,33 @@ def _gen_varname(cls, raise ValueError("Default names not found.") +class GridVectorPropSchema(VectorPropSchema): + varnames = SequenceSchema(SchemaNode(String())) + grid = PyGridSchema(missing=drop) + data_file = SchemaNode(typ=Sequence(accept_scalar=True), children=[SchemaNode(String())]) + grid_file = SchemaNode(typ=Sequence(accept_scalar=True), children=[SchemaNode(String())]) + + def __init__(self, json_='webapi', *args, **kwargs): + if json_ == 'save': + self.add(SchemaNode(typ=Sequence(), children=[SchemaNode(EnvProp())], name='variables')) + super(GridVectorPropSchema, self).__init__(*args, **kwargs) + class GridVectorProp(VectorProp): + _state = copy.deepcopy(VectorProp._state) + + _schema = GridVectorPropSchema + + _state.add_field([serializable.Field('grid', save=True, update=True, save_reference=True), + serializable.Field('variables', save=True, update=True, iscollection=True), + serializable.Field('varnames', save=True, update=True), + serializable.Field('data_file', save=True, update=True, isdatafile=True), + serializable.Field('grid_file', save=True, update=True, isdatafile=True)]) + + default_names = [] _def_count = 0 def __init__(self, - name=None, - units=None, - time=None, - variables=None, grid=None, depth=None, grid_file=None, @@ -544,34 +547,21 @@ def __init__(self, varnames=None, **kwargs): - self._grid = self._grid_file = self._data_file = None - - if any([units is None, time is None, grid is None]) and not all([isinstance(v, GriddedProp) for v in variables]): - raise ValueError("All attributes except name, varnames and MUST be defined if variables is not a list of TimeSeriesProp objects") - - if variables is None or len(variables) < 2: - raise TypeError("Variables needs to be a list of at least 2 GriddedProp objects or ndarray-like arrays") - - if all([isinstance(v, GriddedProp) for v in variables]): - grid = variables[0].grid if grid is None else grid - depth = variables[0].depth if depth is None else depth - grid_file = variables[0].grid_file if grid_file is None else grid_file - data_file = variables[0].data_file if data_file is None else data_file - VectorProp.__init__(self, - name, - units, - time, - variables, - grid=grid, - depth=depth, - dataset=dataset, - data_file=data_file, - grid_file=grid_file, - **kwargs) + super(GridVectorProp, self).__init__(**kwargs) + if isinstance(self.variables, list): + self.variables = OrderedCollection(elems=self.variables, dtype=EnvProp) + if isinstance(self.variables[0], GriddedProp): + self.grid = self.variables[0].grid if grid is None else grid + self.depth = self.variables[0].depth if depth is None else depth + self.grid_file = self.variables[0].grid_file if grid_file is None else grid_file + self.data_file = self.variables[0].data_file if data_file is None else data_file # self._check_consistency() self._result_memo = OrderedDict() + def __repr__(self): + return str(self.serialize()) + @classmethod def from_netCDF(cls, filename=None, @@ -634,26 +624,27 @@ def from_netCDF(cls, ds = dataset if grid is None: - grid = _init_grid(grid_file, - grid_topology=grid_topology, - dataset=dg) + grid = PyGrid.from_netCDF(grid_file, + dataset=dg, + grid_topology=grid_topology) if varnames is None: varnames = cls._gen_varnames(data_file, dataset=ds) if name is None: name = cls.__name__ + str(cls._def_count) cls._def_count += 1 - timevar = None data = ds[varnames[0]] if time is None: - try: - timevar = data.time if data.time == data.dimensions[0] else data.dimensions[0] - except AttributeError: - timevar = data.dimensions[0] - time = Time(ds[timevar]) + time = Time.from_netCDF(filename=data_file, + dataset=ds, + datavar=data) if depth is None: - from gnome.environment.environment_objects import Depth - depth = Depth(surface_index=-1) + if (isinstance(grid, PyGrid_S) and len(data.shape) == 4 or + (len(data.shape) == 3 and time is None) or + (isinstance(grid, PyGrid_U) and len(data.shape) == 3 or + (len(data.shape) == 2 and time is None))): + from gnome.environment.environment_objects import Depth + depth = Depth(surface_index=-1) # if len(data.shape) == 4 or (len(data.shape) == 3 and time is None): # from gnome.environment.environment_objects import S_Depth # depth = S_Depth.from_netCDF(grid=grid, @@ -661,10 +652,9 @@ def from_netCDF(cls, # data_file=data_file, # grid_file=grid_file, # **kwargs) - variables = [] + variables = OrderedCollection(dtype=EnvProp) for vn in varnames: variables.append(GriddedProp.from_netCDF(filename=filename, - name=name + '_' + vn, varname=vn, grid_topology=grid_topology, units=units, @@ -676,79 +666,47 @@ def from_netCDF(cls, dataset=ds, load_all=load_all, **kwargs)) - return cls(name, - units, - time, - variables, + if units is None: + units = [v.units for v in variables] + if all(u == units[0] for u in units): + units = units[0] + return cls(name=name, + filename=filename, + varnames=varnames, + grid_topology=grid_topology, + units=units, + time=time, grid=grid, depth=depth, - grid_file=grid_file, + variables=variables, data_file=data_file, + grid_file=grid_file, dataset=ds, + load_all=load_all, **kwargs) - def _check_consistency(self): - ''' - Checks that the attributes of each GriddedProp in varlist are the same as the GridVectorProp - ''' - if self.units is None or self.time is None or self.grid is None: - return - for v in self.variables: - if v.units != self.units: - raise ValueError("Variable {0} did not have units consistent with what was specified. Got: {1} Expected {2}".format(v.name, v.units, self.units)) - if v.time != self.time: - raise ValueError("Variable {0} did not have time consistent with what was specified Got: {1} Expected {2}".format(v.name, v.time, self.time)) - if v.grid != self.grid: - raise ValueError("Variable {0} did not have grid consistent with what was specified Got: {1} Expected {2}".format(v.name, v.grid, self.grid)) - if v.grid_file != self.grid_file: - raise ValueError("Variable {0} did not have grid_file consistent with what was specified Got: {1} Expected {2}".format(v.name, v.grid_file, self.grid_file)) - if v.data_file != self.data_file: - raise ValueError("Variable {0} did not have data_file consistent with what was specified Got: {1} Expected {2}".format(v.name, v.data_file, self.data_file)) + @classmethod + def _gen_varnames(cls, + filename=None, + dataset=None): + """ + Function to find the default variable names if they are not provided. - @property - def grid(self): - return self._grid - - @grid.setter - def grid(self, g): - if not (isinstance(g, (pyugrid.UGrid, pysgrid.SGrid))): - raise ValueError('Grid must be set with a pyugrid.UGrid or pysgrid.SGrid object') - if self._variables is not None: - if g.infer_location(self.variables[0]) is None: - raise ValueError("Grid with shape {0} not compatible with data of shape {1}".format(g.shape, self.data_shape)) - for v in self.variables: - v.grid = g + :param filename: Name of file that will be searched for variables + :param dataset: Existing instance of a netCDF4.Dataset + :type filename: string + :type dataset: netCDF.Dataset + :return: List of default variable names, or None if none are found + """ + df = None + if dataset is not None: + df = dataset else: - self._grid = g - - @property - def variables(self): - return self._variables - - @variables.setter - def variables(self, vs): - if vs is None: - self._variables = None - return - new_vars = [] - for i, var in enumerate(vs): - if not isinstance(var, GriddedProp): - if (isinstance(var, (collections.Iterable, nc4.Variable)) and - len(var) == len(self.time) and - self.grid.infer_location(var) is not None): - new_vars.append(GriddedProp(name='var{0}'.format(i), - units=self.units, - time=self.time, - grid=self.grid, - data=vs[i], - grid_file=self.grid_file, - data_file=self.data_file)) - else: - raise ValueError('Variables must contain an iterable, netCDF4.Variable or GriddedProp objects') - else: - new_vars.append(var) - self._variables = new_vars - self._check_consistency() + df = _get_dataset(filename) + for n in cls.default_names: + if all([sn in df.variables.keys() for sn in n]): + return n + raise ValueError("Default names not found.") @property def is_data_on_nodes(self): @@ -777,8 +735,8 @@ def time(self, t): @property def data_shape(self): - if self._variables is not None: - return self.variables.data.shape + if self.variables is not None: + return self.variables[0].data.shape else: return None @@ -806,55 +764,7 @@ def _get_memoed(self, points, time, D, _copy=True, _hash=None): else: return None - def set_attr(self, - name=None, - units=None, - time=None, - variables=None, - grid=None, - grid_file=None, - data_file=None,): - - self.name = name if name is not None else self.name - if variables is not None: - if self.variables is not None and len(variables) != len(self.variables): - raise ValueError('Cannot change the number of variables using set_attr. {0} provided, {1} required'.format(len(variables), len(self.variables))) - - for i, v in enumerate(variables): - if isinstance(v, GriddedProp): - variables[i] = variables.data - - units = self.units if units is None else units - time = self.time if time is None else time - grid = self.grid if grid is None else grid - grid_file = self.grid_file if grid_file is None else grid_file - data_file = self.data_file if data_file is None else data_file - - for i, var in enumerate(self.variables): - if variables is None: - nv = None - else: - nv = variables[i] - var.set_attr(units=units, - time=time, - data=nv, - grid=grid, - grid_file=grid_file, - data_file=data_file,) - else: - for i, var in enumerate(self.variables): - var.set_attr(units=units, - time=time, - grid=grid, - grid_file=grid_file, - data_file=data_file,) - self._units = units - self._time = time - self._grid = grid - self.grid_file = grid_file - self.grid_file = grid_file - - def at(self, points, time, units=None, depth=-1, extrapolate=False, memoize=True, _hash=None, **kwargs): + def at(self, points, time, units=None, extrapolate=False, memoize=True, _hash=None, **kwargs): mem = memoize if hash is None: _hash = self._get_hash(points, time) @@ -867,7 +777,6 @@ def at(self, points, time, units=None, depth=-1, extrapolate=False, memoize=True value = super(GridVectorProp, self).at(points=points, time=time, units=units, - depth=depth, extrapolate=extrapolate, memoize=memoize, _hash=_hash, @@ -877,26 +786,49 @@ def at(self, points, time, units=None, depth=-1, extrapolate=False, memoize=True self._memoize_result(points, time, value, self._result_memo, _hash=_hash) return value - @classmethod - def _gen_varnames(cls, - filename=None, - dataset=None): - """ - Function to find the default variable names if they are not provided. - :param filename: Name of file that will be searched for variables - :param dataset: Existing instance of a netCDF4.Dataset - :type filename: string - :type dataset: netCDF.Dataset - :return: List of default variable names, or None if none are found - """ - df = None - if dataset is not None: - df = dataset + @classmethod + def _get_shared_vars(cls, *sh_args): + default_shared = ['dataset', 'data_file', 'grid_file', 'grid'] + if len(sh_args) != 0: + shared = sh_args else: - df = _get_dataset(filename) - for n in cls.default_names: - if all([sn in df.variables.keys() for sn in n]): - return n - raise ValueError("Default names not found.") - + shared = default_shared + + def getvars(func): + @wraps(func) + def wrapper(*args, **kws): + def _mod(n): + k = kws + s = shared + return (n in s) and ((n not in k) or (n in k and k[n] is None)) + if 'filename' in kws and kws['filename'] is not None: + kws['data_file'] = kws['grid_file'] = kws['filename'] + if _mod('dataset'): + if 'grid_file' in kws and 'data_file' in kws: + if kws['grid_file'] == kws['data_file']: + ds = dg = _get_dataset(kws['grid_file']) + else: + ds = _get_dataset(kws['data_file']) + dg = _get_dataset(kws['grid_file']) + kws['dataset'] = ds + else: + if 'grid_file' in kws and kws['grid_file'] is not None: + dg = _get_dataset(kws['grid_file']) + else: + dg = kws['dataset'] + ds = kws['dataset'] + if _mod('grid'): + gt = kws.get('grid_topology', None) + kws['grid'] = PyGrid.from_netCDF(kws['grid_file'], dataset=dg, grid_topology=gt) + if kws.get('varnames', None) is None: + varnames = cls._gen_varnames(kws['data_file'], + dataset=ds) +# if _mod('time'): +# time = Time.from_netCDF(filename=kws['data_file'], +# dataset=ds, +# varname=data) +# kws['time'] = time + return func(*args, **kws) + return wrapper + return getvars diff --git a/py_gnome/gnome/environment/property.py b/py_gnome/gnome/environment/property.py index e8dde25f8..5ab15469e 100644 --- a/py_gnome/gnome/environment/property.py +++ b/py_gnome/gnome/environment/property.py @@ -1,5 +1,8 @@ import warnings +import os import copy +import StringIO +import zipfile import netCDF4 as nc4 import numpy as np @@ -9,21 +12,37 @@ from gnome.persist.base_schema import ObjType from gnome.utilities import serializable from gnome.persist import base_schema +from gnome.utilities.file_tools.data_helpers import _get_dataset import pyugrid import pysgrid import unit_conversion import collections from collections import OrderedDict +from gnome.gnomeobject import GnomeId + + +class TimeSchema(base_schema.ObjType): +# time = SequenceSchema(SchemaNode(DateTime(default_tzinfo=None), missing=drop), missing=drop) + filename = SchemaNode(typ=Sequence(accept_scalar=True), children=[SchemaNode(String())], missing=drop) + varname = SchemaNode(String(), missing=drop) + data = SchemaNode(typ=Sequence(), children=[SchemaNode(DateTime(None))], missing=drop) class PropertySchema(base_schema.ObjType): - name = SchemaNode(String(), missing='default') - units = SchemaNode(typ=Sequence(accept_scalar=True), children=[SchemaNode(String(), missing=drop), SchemaNode(String(), missing=drop)]) - time = SequenceSchema(SchemaNode(DateTime(default_tzinfo=None), missing=drop), missing=drop) + name = SchemaNode(String(), missing=drop) + units = SchemaNode(String(), missing=drop) +# units = SchemaNode(typ=Sequence(accept_scalar=True), children=[SchemaNode(String(), missing=drop), SchemaNode(String(), missing=drop)]) + time = TimeSchema(missing=drop) # SequenceSchema(SchemaNode(DateTime(default_tzinfo=None), missing=drop), missing=drop) -class EnvProp(object): +class EnvProp(serializable.Serializable): + + _state = copy.deepcopy(serializable.Serializable._state) + _schema = PropertySchema + + _state.add_field([serializable.Field('units', save=True, update=True), + serializable.Field('time', save=True, update=True, save_reference=True)]) def __init__(self, name=None, @@ -59,28 +78,25 @@ def __str__(self): return self.__repr__() def __repr__(self): - r = OrderedDict() - r['type'] = type(self).__name__ - r['name'] = self.name - if self.time is not None: - r['time'] = 'start:{0.min_time}, end:{0.max_time}, length={1}'.format(self.time, len(self.time.time)) - else: - r['time'] = self.time - r['data'] = type(self.data) - return str(r) + return ('{0.__class__.__module__}.{0.__class__.__name__}(' + 'name="{0.name}", ' + 'time="{0.time}", ' + 'units="{0.units}", ' + 'data="{0.data}", ' + ')').format(self) ''' Subclasses should override\add any attribute property function getter/setters as needed ''' - @property - def data(self): - ''' - Underlying data - - :rtype: netCDF4.Variable or numpy.array - ''' - return self._data +# @property +# def data(self): +# ''' +# Underlying data +# +# :rtype: netCDF4.Variable or numpy.array +# ''' +# return self._data @property def units(self): @@ -109,7 +125,9 @@ def time(self): @time.setter def time(self, t): - if isinstance(t, Time): + if t is None: + self._time = None + elif isinstance(t, Time): self._time = t elif isinstance(t, collections.Iterable): self._time = Time(t) @@ -155,7 +173,18 @@ def in_units(self, unit): return cpy -class VectorProp(object): +class VectorPropSchema(base_schema.ObjType): + units = SchemaNode(String(), missing=drop) + time = TimeSchema(missing=drop) + + +class VectorProp(serializable.Serializable): + + _state = copy.deepcopy(serializable.Serializable._state) + _schema = VectorPropSchema + + _state.add_field([serializable.Field('units', save=True, update=True), + serializable.Field('time', save=True, update=True, save_reference=True)]) def __init__(self, name=None, @@ -186,34 +215,29 @@ def __init__(self, time = Time(time) units = variables[0].units if units is None else units time = variables[0].time if time is None else time - for v in variables: - if (v.units != units or - v.time != time): - raise ValueError("Variable {0} did not have parameters consistent with what was specified".format(v.name)) - if units is None: units = variables[0].units self._units = units if variables is None or len(variables) < 2: raise ValueError('Variables must be an array-like of 2 or more Property objects') - self.time = time - for k in kwargs: - setattr(self, k, kwargs[k]) self.variables = variables + self._time = time + unused_args = kwargs.keys() if kwargs is not None else None + if len(unused_args) > 0: +# print(unused_args) + kwargs = {} + super(VectorProp, self).__init__(**kwargs) def __str__(self): return self.__repr__() - + def __repr__(self): - r = OrderedDict() - r['type'] = type(self).__name__ - r['name'] = self.name - if self.time is not None: - r['time'] = 'start:{0.min_time}, end:{0.max_time}, length={1}'.format(self.time, len(self.time.time)) - else: - r['time'] = self.time - r['variables'] = str(self.variables) - return str(r) + return ('{0.__class__.__module__}.{0.__class__.__name__}(' + 'name="{0.name}", ' + 'time="{0.time}", ' + 'units="{0.units}", ' + 'variables="{0.variables}", ' + ')').format(self) @property def time(self): @@ -256,7 +280,7 @@ def varnames(self): :rtype: [] of strings ''' - return [v.name for v in self.variables] + return [v.varname if hasattr(v, 'varname') else v.name for v in self.variables ] def _check_consistency(self): ''' @@ -281,25 +305,46 @@ def at(self, *args, **kwargs): :return: returns a Nx2 array of interpolated values :rtype: double ''' - return np.column_stack([var.at(*args, **kwargs) for var in self._variables]) + return np.column_stack([var.at(*args, **kwargs) for var in self.variables]) + +class Time(serializable.Serializable): -class Time(object): + _state = copy.deepcopy(serializable.Serializable._state) + _schema = TimeSchema + + _state.add_field([serializable.Field('filename', save=True, update=True, isdatafile=True), + serializable.Field('varname', save=True, update=True), + serializable.Field('data', save=True, update=True)]) - def __init__(self, time_seq, tz_offset=None, offset=None): + _const_time = None + + def __init__(self, + time=None, + filename=None, + varname=None, + tz_offset=None, + offset=None, + **kwargs): ''' Representation of a time axis. Provides interpolation alphas and indexing. - :param time_seq: Ascending list of times to use + :param time: Ascending list of times to use :param tz_offset: offset to compensate for time zone shifts - :type time_seq: netCDF4.Variable or [] of datetime.datetime + :type time: netCDF4.Variable or [] of datetime.datetime :type tz_offset: datetime.timedelta ''' - if isinstance(time_seq, (nc4.Variable, nc4._netCDF4._Variable)): - self.time = nc4.num2date(time_seq[:], units=time_seq.units) + if isinstance(time, (nc4.Variable, nc4._netCDF4._Variable)): + self.time = nc4.num2date(time[:], units=time.units) else: - self.time = time_seq + self.time = time + + self.filename = filename + self.varname = varname + +# if self.filename is None: +# self.filename = self.id + '_time.txt' if tz_offset is not None: self.time += tz_offset @@ -309,9 +354,104 @@ def __init__(self, time_seq, tz_offset=None, offset=None): if self._has_duplicates(self.time): raise ValueError("Time sequence has duplicate entries") + self.name = time.name if hasattr(time, 'name') else None + @classmethod - def time_from_nc_var(cls, var): - return cls(nc4.num2date(var[:], units=var.units)) + def from_netCDF(cls, + filename=None, + dataset=None, + varname=None, + datavar=None, + tz_offset=None, + **kwargs): + if dataset is None: + dataset = _get_dataset(filename) + if datavar is not None: + if hasattr(datavar, 'time') and datavar.time in dataset.dimensions.keys(): + varname = datavar.time + else: + varname = datavar.dimensions[0] if 'time' in datavar.dimensions[0] else None + if varname is None: + return None + time = cls(time=dataset[varname], + filename=filename, + varname=varname, + tz_offset=tz_offset, + **kwargs + ) + return time + + @staticmethod + def constant_time(): + if Time._const_time is None: + Time._const_time = Time([datetime.now()]) + return Time._const_time + + @classmethod + def from_file(cls, filename=None, **kwargs): + if isinstance(filename, list): + filename = filename[0] + fn = open(filename, 'r') + t = [] + for l in fn: + l = l.rstrip() + if l is not None: + t.append(datetime.strptime(l, '%c')) + fn.close() + return Time(t) + + def save(self, saveloc, references=None, name=None): + ''' + Write Wind timeseries to file or to zip, + then call save method using super + ''' +# if self.filename is None: +# self.filename = self.id + '_time.txt' +# if zipfile.is_zipfile(saveloc): +# self._write_time_to_zip(saveloc, self.filename) +# else: +# datafile = os.path.join(saveloc, self.filename) +# self._write_time_to_file(datafile) +# rv = super(Time, self).save(saveloc, references, name) +# self.filename = None +# else: +# rv = super(Time, self).save(saveloc, references, name) +# return rv + super(Time, self).save(saveloc, references, name) + + def _write_time_to_zip(self, saveloc, ts_name): + ''' + use a StringIO type of file descriptor and write directly to zipfile + ''' + fd = StringIO.StringIO() + self._write_time_to_fd(fd) + self._write_to_zip(saveloc, ts_name, fd.getvalue()) + + def _write_time_to_file(self, datafile): + '''write timeseries data to file ''' + with open(datafile, 'w') as fd: + self._write_time_to_fd(fd) + + def _write_time_to_fd(self, fd): + for t in self.time: + fd.write(t.strftime('%c') + '\n') + + @classmethod + def new_from_dict(cls, dict_): + if 'varname' not in dict_: + dict_['time'] = dict_['data'] +# if 'filename' not in dict_: +# raise ValueError + return cls(**dict_) + else: + return cls.from_netCDF(**dict_) + + @property + def data(self): + if self.filename is None: + return self.time + else: + return None def __len__(self): return len(self.time) diff --git a/py_gnome/gnome/environment/ts_property.py b/py_gnome/gnome/environment/ts_property.py index f370e5eb3..547bf5327 100644 --- a/py_gnome/gnome/environment/ts_property.py +++ b/py_gnome/gnome/environment/ts_property.py @@ -4,17 +4,40 @@ import netCDF4 as nc4 import numpy as np -from gnome.environment.property import EnvProp, VectorProp, Time +from gnome.environment.property import EnvProp, VectorProp, Time, PropertySchema, TimeSchema, \ + VectorPropSchema from datetime import datetime, timedelta from dateutil import parser from colander import SchemaNode, Float, Boolean, Sequence, MappingSchema, drop, String, OneOf, SequenceSchema, TupleSchema, DateTime from numbers import Number +from gnome.utilities import serializable import unit_conversion import collections +from gnome.utilities.orderedcollection import OrderedCollection -class TimeSeriesProp(EnvProp): +class TimeSeriesPropSchema(PropertySchema): + time = TimeSchema(missing=drop) + data = SequenceSchema(SchemaNode(Float()), missing=drop) + timeseries = SequenceSchema( + TupleSchema( + children=[SchemaNode(DateTime(default_tzinfo=None), missing=drop), + SchemaNode(Float(), missing=0) + ], + missing=drop), + missing=drop) + + +class TimeSeriesProp(EnvProp, serializable.Serializable): + + _state = copy.deepcopy(EnvProp._state) + _schema = TimeSeriesPropSchema + + _state.add_field([serializable.Field('timeseries', save=False, update=True), + serializable.Field('data', save=True, update=False)]) + +# _state.update('time', update=False) def __init__(self, name=None, @@ -39,6 +62,8 @@ def __init__(self, len(time) == {0}, len(data) == {1}".format(len(time), len(data))) super(TimeSeriesProp, self).__init__(name, units, time, data) self.time = time + if isinstance(self.data, list): + self.data = np.asarray(self.data) @classmethod def constant(cls, @@ -50,8 +75,8 @@ def constant(cls, if not isinstance(data, Number): raise TypeError('{0} data must be a number'.format(name)) - t = datetime.now().replace(microsecond=0, second=0, minute=0) - return cls(name=name, units=units, time=[t], data=[data]) + t = Time.constant_time() + return cls(name=name, units=units, time=t, data=[data]) @property def timeseries(self): ''' @@ -151,8 +176,31 @@ def __ne__(self, o): return not self.__eq__(o) +class TSVectorPropSchema(VectorPropSchema): + timeseries = SequenceSchema( + TupleSchema( + children=[SchemaNode(DateTime(default_tzinfo=None), missing=drop), + TupleSchema(children=[ + SchemaNode(Float(), missing=0), + SchemaNode(Float(), missing=0) + ] + ) + ], + missing=drop), + missing=drop) +# variables = SequenceSchema(TupleSchema(SchemaNode(Float()))) + varnames = SequenceSchema(SchemaNode(String(), missing=drop), missing=drop) + + class TSVectorProp(VectorProp): + _schema = TSVectorPropSchema + _state = copy.deepcopy(VectorProp._state) + + _state.add_field([serializable.Field('timeseries', save=False, update=True), + serializable.Field('variables', save=True, update=True, iscollection=True), + serializable.Field('varnames', save=True, update=False)]) + def __init__(self, name=None, units=None, @@ -170,7 +218,6 @@ def __init__(self, if variables is None or len(variables) < 2: raise TypeError('Variables must be an array-like of 2 or more TimeSeriesProp or array-like') VectorProp.__init__(self, name, units, time, variables) - self._check_consistency() @classmethod def constant(cls, @@ -182,17 +229,8 @@ def constant(cls, if not isinstance(variables, collections.Iterable): raise TypeError('{0} variables must be an iterable'.format(name)) - t = datetime.now().replace(microsecond=0, second=0, minute=0) - return cls(name=name, units=units, time=[t], variables=[v for v in variables]) - - def _check_consistency(self): - ''' - Checks that the attributes of each GriddedProp in varlist are the same as the GridVectorProp - ''' - for v in self.variables: - if (v.units != self.units or - v.time != self.time): - raise ValueError("Variable {0} did not have parameters consistent with what was specified".format(v.name)) + t = Time.constant_time() + return cls(name=name, units=units, time=t, variables=[v for v in variables]) @property def timeseries(self): @@ -203,26 +241,6 @@ def timeseries(self): ''' return map(lambda x, y, z: (x, (y, z)), self.time.time, self.variables[0], self.variables[1]) - @property - def variables(self): - return self._variables - - @variables.setter - def variables(self, vs): - new_vars = [] - for i, var in enumerate(vs): - if not isinstance(var, TimeSeriesProp): - if isinstance(var, collections.Iterable) and len(var) == len(self.time): - new_vars.append(TimeSeriesProp(name='var{0}'.format(i), - units=self.units, time=self.time, - data=vs[i])) - else: - raise ValueError('Variables must contain iterables or TimeSeriesProp objects') - else: - new_vars.append(var) - self._variables = new_vars - self._check_consistency() - @property def time(self): return self._time @@ -239,29 +257,16 @@ def time(self, t): else: raise ValueError("Object being assigned must be an iterable or a Time object") - def set_attr(self, - name=None, - units=None, - time=None, - variables=None): - self.name = name if name is not None else self.name - self.units = units if units is not None else self.units - if variables is not None and time is not None: - new_vars = [] - for i, var in enumerate(variables): - if not isinstance(var, TimeSeriesProp): - if isinstance(var, collections.Iterable) and len(var) == len(time): - new_vars.append(TimeSeriesProp(name='var{0}'.format(i), - units=self.units, time=time, - data=variables[i])) - else: - raise ValueError('Variables must contain iterables or TimeSeriesProp objects') - self._variables = new_vars - self.time = time - else: - if variables is not None: - self.variables = variables - self.time = time if time is not None else self.time + @property + def variables(self): + return self._variables + + @variables.setter + def variables(self, v): + if v is None: + self._variables = v + if isinstance(v, collections.Iterable): + self._variables = OrderedCollection(v) def is_constant(self): return len(self.variables[0]) == 1 diff --git a/py_gnome/gnome/gnomeobject.py b/py_gnome/gnome/gnomeobject.py index daa465e83..8668228c2 100644 --- a/py_gnome/gnome/gnomeobject.py +++ b/py_gnome/gnome/gnomeobject.py @@ -139,6 +139,20 @@ def name(self): def name(self, val): self._name = val + def _attach_default_refs(self, ref_dict): + ''' + If provided a dictionary of references this function will validate it + against the _req_refs specified by the class, and if a match is found + and the instance's reference is None, it will set it to the instance + from ref_dict + ''' + if not hasattr(self, '_req_refs'): + return + else: + for var in ref_dict.keys(): + if getattr(self, var) is None: + setattr(self, var, ref_dict[var]) + def validate_refs(self, refs=['wind', 'water', 'waves']): ''' level is the logging level to use for messages. Default is 'warning' diff --git a/py_gnome/gnome/map.py b/py_gnome/gnome/map.py index 803a86e5f..0fa98d6b9 100644 --- a/py_gnome/gnome/map.py +++ b/py_gnome/gnome/map.py @@ -21,7 +21,6 @@ from osgeo import ogr import py_gd -from osgeo import ogr # import pyugrid import numpy as np @@ -84,7 +83,8 @@ class GnomeMap(Serializable): The very simplest map for GNOME -- all water with only a bounding box for the map bounds. - This also serves as a description of the interface + This also serves as a description of the interface and + base class for more complex maps """ _update = ['map_bounds', 'spillable_area'] _create = [] @@ -102,8 +102,9 @@ def __init__(self, map_bounds=None, spillable_area=None, land_polys=None, Optional parameters (kwargs) - :param map_bounds: The polygon bounding the map -- could be larger - or smaller than the land raster + :param map_bounds: The polygon bounding the map if any elements are + outside the map bounds, they are removed from the + simulation. :param spillable_area: The PolygonSet bounding the spillable_area. :type spillable_area: Either a PolygonSet object or a list of lists @@ -116,7 +117,7 @@ def __init__(self, map_bounds=None, spillable_area=None, land_polys=None, is a list of points defining a polygon. Note on 'map_bounds': - ( (x1,y1), (x2,y2),(x3,y3),..) + ( (x1,y1), (x2,y2), (x3,y3),..) An NX2 array of points that describe a polygon if no map bounds is provided -- the whole world is valid """ @@ -580,7 +581,7 @@ def refloat_elements(self, spill_container, time_step): :param spill_container: the current spill container :type spill_container: :class:`gnome.spill_container.SpillContainer` """ - r_idx = np.where(spill_container['status_codes'] == + r_idx = np.where(spill_container['status_codes'] == oil_status.on_land)[0] if r_idx.size == 0: # no particles on land @@ -589,7 +590,7 @@ def refloat_elements(self, spill_container, time_step): if self._refloat_halflife > 0.0: # if 0.0, then r_idx is all of them -- they will all refloat. # refloat particles based on probability - refloat_probability = 1.0 - 0.5 ** (float(time_step) / + refloat_probability = 1.0 - 0.5 ** (float(time_step) / self._refloat_halflife) rnd = np.random.uniform(0, 1, len(r_idx)) @@ -934,7 +935,7 @@ def refloat_elements(self, spill_container, time_step): """ # index into array of particles on_land - r_idx = np.where(spill_container['status_codes'] == + r_idx = np.where(spill_container['status_codes'] == oil_status.on_land)[0] if r_idx.size == 0: # no particles on land @@ -944,7 +945,7 @@ def refloat_elements(self, spill_container, time_step): # if 0.0, then r_idx is all of them -- they will all refloat. # refloat particles based on probability - refloat_probability = 1.0 - 0.5 ** (float(time_step) / + refloat_probability = 1.0 - 0.5 ** (float(time_step) / self._refloat_halflife) rnd = np.random.uniform(0, 1, len(r_idx)) @@ -1035,8 +1036,6 @@ def __init__(self, filename, raster_size=4096 * 4096, **kwargs): :param filename: full path to the data file - :param refloat_halflife: the half-life (in hours) for the re-floating. - :param raster_size: the total number of pixels (bytes) to make the raster -- the actual size will match the aspect ratio of the bounding box of the land @@ -1044,6 +1043,8 @@ def __init__(self, filename, raster_size=4096 * 4096, **kwargs): Optional arguments (kwargs): + :param refloat_halflife: the half-life (in hours) for the re-floating. + :param map_bounds: The polygon bounding the map -- could be larger or smaller than the land raster @@ -1070,10 +1071,10 @@ def __init__(self, filename, raster_size=4096 * 4096, **kwargs): spillable_area = PolygonSet() for p in polygons: - if p.metadata[1].lower() == 'spillablearea': + if p.metadata[1].lower().replace(' ', '') == 'spillablearea': spillable_area.append(p) - elif p.metadata[1].lower() == 'map bounds': + elif p.metadata[1].lower().replace(' ', '') == 'mapbounds': map_bounds = p else: land_polys.append(p) @@ -1089,11 +1090,17 @@ def __init__(self, filename, raster_size=4096 * 4096, **kwargs): map_bounds = kwargs.pop('map_bounds', map_bounds) if map_bounds is None: - map_bounds = BB.AsPoly() + if spillable_area: # add the spillable area to the bounds + saBB = spillable_area.bounding_box + saBB.Merge(BB) + map_bounds = saBB.AsPoly() + else: + map_bounds = BB.AsPoly() - if len(spillable_area) == 0: + elif spillable_area: spillable_area.append(map_bounds) + # user defined spillable_area, map_bounds overrides data obtained # from polygons @@ -1103,7 +1110,7 @@ def __init__(self, filename, raster_size=4096 * 4096, **kwargs): # stretch the bounding box, to get approximate aspect ratio in # projected coords. - aspect_ratio = (np.cos(BB.Center[1] * np.pi / 180) * + aspect_ratio = (np.cos(BB.Center[1] * np.pi / 180) * (BB.Width / BB.Height)) w = int(np.sqrt(raster_size * aspect_ratio)) @@ -1291,7 +1298,7 @@ def __init__(self, filename, raster_size=1024 * 1024, **kwargs): # stretch the bounding box, to get approximate aspect ratio in # projected coords. - aspect_ratio = (np.cos(BB.Center[1] * np.pi / 180) * + aspect_ratio = (np.cos(BB.Center[1] * np.pi / 180) * (BB.Width / BB.Height)) w = int(np.sqrt(raster_size * aspect_ratio)) diff --git a/py_gnome/gnome/model.py b/py_gnome/gnome/model.py index 490d9e54a..e3727aed7 100644 --- a/py_gnome/gnome/model.py +++ b/py_gnome/gnome/model.py @@ -6,6 +6,8 @@ import inspect import zipfile +import numpy as np + from colander import (SchemaNode, String, Float, Int, Bool, drop, OneOf) @@ -32,7 +34,7 @@ class_from_objtype) from gnome.persist.base_schema import (ObjType, CollectionItemsList) -from gnome.exceptions import ReferencedObjectNotSet +from gnome.exceptions import ReferencedObjectNotSet, GnomeRuntimeError class ModelSchema(ObjType): @@ -231,7 +233,6 @@ def __init__(self, weathering_substeps, uncertain, cache_enabled, map, name, mode) - self._register_callbacks() def _register_callbacks(self): @@ -277,6 +278,7 @@ def __restore__(self, time_step, start_time, duration, self._start_time = start_time self._duration = duration self.weathering_substeps = weathering_substeps + if not map: map = gnome.map.GnomeMap() @@ -540,11 +542,18 @@ def find_by_attr(self, attr, value, collection, allitems=False): items = [] for item in collection: try: - if getattr(item, attr) == value: - if allitems: - items.append(item) - else: - return item + if not isinstance(getattr(item, attr), basestring): + if any([value == v for v in getattr(item, attr)]): + if allitems: + items.append(item) + else: + return item + else: + if getattr(item, attr) == value: + if allitems: + items.append(item) + else: + return item except AttributeError: pass items = None if items == [] else items @@ -562,7 +571,7 @@ def _attach_references(self): ''' attach references ''' - attr = {'wind': None, 'water': None, 'waves': None} + attr = {} attr['wind'] = self.find_by_attr('_ref_as', 'wind', self.environment) attr['water'] = self.find_by_attr('_ref_as', 'water', self.environment) attr['waves'] = self.find_by_attr('_ref_as', 'waves', self.environment) @@ -572,31 +581,54 @@ def _attach_references(self): spread = None for coll in ('environment', 'weatherers', 'movers'): for item in getattr(self, coll): + if hasattr(item, '_req_refs'): + ref_dict = {} + for var in item._req_refs.keys(): + inst = self.find_by_attr('_ref_as', var, + self.environment) + if inst is not None: + ref_dict[var] = inst + if len(ref_dict) > 0: + item._attach_default_refs(ref_dict) + else: + if coll == 'weatherers': + # by default turn WeatheringData and spreading object + # off + if isinstance(item, WeatheringData): + item.on = False + wd = item - if coll == 'weatherers': - # by default turn WeatheringData and spreading object off - if isinstance(item, WeatheringData): - item.on = False - wd = item + try: + if item._ref_as == 'spreading': + item.on = False + spread = item - try: - if item._ref_as == 'spreading': - item.on = False - spread = item + except AttributeError: + pass - except AttributeError: - pass + if item.on: + weather_data.update(item.array_types) - if item.on: - weather_data.update(item.array_types) + if hasattr(item, 'on') and not item.on: + # no need to setup references if item is not on + continue - if hasattr(item, 'on') and not item.on: - # no need to setup references if item is not on - continue + for name, val in attr.iteritems(): + if (hasattr(item, name) and + getattr(item, name) is None and + item.make_default_refs): + setattr(item, name, val) - for name, val in attr.iteritems(): - if hasattr(item, name) and item.make_default_refs: - setattr(item, name, val) + all_spills = [sp + for sc in self.spills.items() + for sp in sc.spills.values()] + + for spill in all_spills: + for name, val in attr.iteritems(): + if (hasattr(spill, name) and + getattr(spill, name) is None and + spill.make_default_refs): + setattr(spill, name, val) # if WeatheringData object and FayGravityViscous (spreading object) # are not defined by user, add them automatically because most @@ -878,7 +910,7 @@ def step(self): (msgs, isvalid) = self.check_inputs() if not isvalid: raise RuntimeError("Setup model run complete but model " - "is invalid", msgs) + "is invalid", msgs) # (msgs, isvalid) = self.validate() # if not isvalid: # raise StopIteration("Setup model run complete but model " @@ -1024,7 +1056,7 @@ def __eq__(self, other): check = super(Model, self).__eq__(other) if check: # also check the data in ordered collections - if type(self.spills) != type(other.spills): + if not isinstance(self.spills, other.spills.__class__): return False if self.spills != other.spills: @@ -1397,9 +1429,6 @@ def check_inputs(self): raise an exception if user can't run the model todo: check if all spills start after model ends ''' - msgs = [] - isvalid = True - (msgs, isvalid) = self.validate() someSpillIntersectsModel = False @@ -1421,11 +1450,34 @@ def check_inputs(self): msgs.append('error: ' + self.__class__.__name__ + ': ' + msg) isvalid = False + if spill.substance is not None: + # min_k1 = spill.substance.get('pour_point_min_k') + pour_point = spill.substance.pour_point() + if spill.water is not None: + water_temp = spill.water.get('temperature') + if water_temp < pour_point[0]: + msg = ('The water temperature, {0} K, is less than ' + 'the minimum pour point of the selected oil, ' + '{1} K. The results may be unreliable.' + .format(water_temp, pour_point[0])) + + self.logger.warning(msg) + msgs.append(self._warn_pre + msg) + + rho_h2o = spill.water.get('density') + rho_oil = spill.substance.density_at_temp(water_temp) + if np.any(rho_h2o < rho_oil): + msg = ("Found particles with relative_buoyancy < 0. " + "Oil is a sinker") + raise GnomeRuntimeError(msg) + if num_spills > 0 and not someSpillIntersectsModel: if num_spills > 1: - msg = ('All of the spills are released after the time interval being modeled.') + msg = ('All of the spills are released after the ' + 'time interval being modeled.') else: - msg = ('The spill is released after the time interval being modeled.') + msg = ('The spill is released after the time interval ' + 'being modeled.') self.logger.warning(msg) # for now make this a warning # self.logger.error(msg) msgs.append('warning: ' + self.__class__.__name__ + ': ' + msg) @@ -1555,7 +1607,8 @@ def get_spill_data(self, target_properties, conditions, ucert=0): Example case:: get_spill_data('position && mass', - 'position > 50 && spill_num == 1 || status_codes == 1') + 'position > 50 && spill_num == 1 || status_codes == 1' + ) WARNING: EXPENSIVE! USE AT YOUR OWN RISK ON LARGE num_elements! @@ -1601,7 +1654,6 @@ def num(s): conditions = conditions.rsplit('&&') conditions = [str(cond).rsplit('||') for cond in conditions] - sc = self.spills.items()[ucert] result = {} @@ -1622,3 +1674,92 @@ def num(s): result[k].append(n) return result + + def add_env(self, env, quash=False): + for item in env: + if not quash: + self.environment.add(item) + else: + for o in self.environment: + if o.__class__ == item.__class__: + idx = self.environment.index(o) + self.environment[idx] = item + break + else: + self.environment.add(item) + +# def env_from_netCDF(self, filename=None, dataset=None, grid_file=None, data_file=None, _cls_list=None, **kwargs): +# def attempt_from_netCDF(cls, **kwargs): +# obj = None +# try: +# obj = c.from_netCDF(filename=filename, dataset=dataset, grid_file=grid_file, data_file=data_file, **clskwargs) +# except Exception as e: +# self.logger.warn('''Class {0} could not be constituted from netCDF file +# Exception: {1}'''.format(c.__name__, e)) +# return obj +# +# from gnome.utilities.file_tools.data_helpers import _get_dataset +# from gnome.environment.environment_objects import GriddedProp, GridVectorProp +# from gnome.environment import PyGrid +# +# if filename is not None: +# data_file = filename +# grid_file = filename +# +# ds = None +# dg = None +# if dataset is None: +# if grid_file == data_file: +# ds = dg = _get_dataset(grid_file) +# else: +# ds = _get_dataset(data_file) +# dg = _get_dataset(grid_file) +# else: +# if grid_file is not None: +# dg = _get_dataset(grid_file) +# else: +# dg = dataset +# ds = dataset +# dataset = ds +# +# grid = kwargs.pop('grid', None) +# if grid is None: +# grid = PyGrid.from_netCDF(filename=filename, dataset=dg, **kwargs) +# kwargs['grid'] = grid +# scs = copy.copy(Environment._subclasses) if _cls_list is None else _cls_list +# for c in scs: +# if issubclass(c, (GriddedProp, GridVectorProp)) and not any([isinstance(o, c) for o in self.environment]): +# clskwargs = copy.copy(kwargs) +# obj = None +# try: +# req_refs = c._req_refs +# except AttributeError: +# req_refs = None +# +# if req_refs is not None: +# for ref, klass in req_refs.items(): +# for o in self.environment: +# if isinstance(o, klass): +# clskwargs[ref] = o +# break +# if ref in clskwargs.keys(): +# continue +# else: +# obj = attempt_from_netCDF(c, filename=filename, dataset=dataset, grid_file=grid_file, data_file=data_file, **clskwargs) +# clskwargs[ref] = obj +# self.environment.append(obj) +# +# obj = attempt_from_netCDF(c, filename=filename, dataset=dataset, grid_file=grid_file, data_file=data_file, **clskwargs) +# if obj is not None: +# self.environment.append(obj) +# +# def ice_env_from_netCDF(self, filename=None, **kwargs): +# cls_list = Environment._subclasses +# ice_cls_list = self.find_by_attr('_ref_as', 'ice_aware', cls_list, allitems=True) +# # for c in cls_list: +# # if hasattr(c, '_ref_as'): +# # if ((not isinstance(c._ref_as, basestring) and +# # any(['ice_aware' in r for r in c._ref_as])) or +# # 'ice_aware' in c._ref_as): +# # ice_cls_list.append(c) +# self.env_from_netCDF(filename=filename, _cls_list=ice_cls_list, **kwargs) diff --git a/py_gnome/gnome/movers/__init__.py b/py_gnome/gnome/movers/__init__.py index c0ebcd216..cf5e65e87 100644 --- a/py_gnome/gnome/movers/__init__.py +++ b/py_gnome/gnome/movers/__init__.py @@ -1,82 +1,28 @@ -# import modules so WindMover, RandomMover etc can be imported as: -# import gnome.movers.WindMover -# import gnome.movers.RandomMover and so forth .. +''' + __init__.py for the gnome.movers package +''' -""" -__init__.py for the gnome package - -""" - -from movers import Mover, Process, ProcessSchema, CyMover -from simple_mover import SimpleMover, SimpleMoverSchema +from movers import Mover, Process, CyMover, ProcessSchema +from simple_mover import SimpleMover from wind_movers import (WindMover, - WindMoverSchema, constant_wind_mover, wind_mover_from_file, - GridWindMoverSchema, GridWindMover, - IceWindMoverSchema, - IceWindMover, - ) -from ship_drift_mover import (ShipDriftMoverSchema, - ShipDriftMover) -from random_movers import (RandomMoverSchema, - RandomMover, + IceWindMover) + +from ship_drift_mover import ShipDriftMover +from random_movers import (RandomMover, IceAwareRandomMover, - RandomVerticalMoverSchema, RandomVerticalMover) -from current_movers import (CatsMoverSchema, - CatsMover, - ComponentMoverSchema, + +from current_movers import (CatsMover, ComponentMover, - GridCurrentMoverSchema, GridCurrentMover, - IceMoverSchema, IceMover, - CurrentCycleMoverSchema, CurrentCycleMover) -from vertical_movers import RiseVelocityMoverSchema, RiseVelocityMover, TamocRiseVelocityMover -from ugrid_movers import UGridCurrentMover -# from py_ice_mover import PyIceMover +from vertical_movers import (RiseVelocityMover, + TamocRiseVelocityMover) from py_wind_movers import PyWindMover -from py_current_movers import PyGridCurrentMover - -# no reason for __all__ if you are going to put everything in it. -# in fact, no reason for __all__unless you want to support "import *", which we don't. -# __all__ = [Mover, -# CyMover, -# Process, -# ProcessSchema, -# SimpleMover, -# SimpleMoverSchema, -# WindMover, -# WindMoverSchema, -# constant_wind_mover, -# wind_mover_from_file, -# GridWindMoverSchema, -# GridWindMover, -# ShipDriftMoverSchema, -# ShipDriftMover, -# IceWindMoverSchema, -# IceWindMover, -# RandomMoverSchema, -# RandomMover, -# IceAwareRandomMover, -# RandomVerticalMoverSchema, -# RandomVerticalMover, -# CatsMoverSchema, -# CatsMover, -# ComponentMoverSchema, -# ComponentMover, -# GridCurrentMoverSchema, -# GridCurrentMover, -# IceMoverSchema, -# IceMover, -# CurrentCycleMoverSchema, -# CurrentCycleMover, -# RiseVelocityMoverSchema, -# RiseVelocityMover, -# PyWindMover, -# PyGridCurrentMover] +from py_current_movers import PyCurrentMover diff --git a/py_gnome/gnome/movers/current_movers.py b/py_gnome/gnome/movers/current_movers.py index fe8760151..58917c749 100644 --- a/py_gnome/gnome/movers/current_movers.py +++ b/py_gnome/gnome/movers/current_movers.py @@ -9,22 +9,24 @@ from colander import (SchemaNode, Bool, String, Float, drop) -from gnome.persist.base_schema import ObjType, WorldPoint - -from gnome.movers import CyMover, ProcessSchema -from gnome import environment -from gnome.utilities import serializable -from gnome.utilities import time_utils - from gnome import basic_types + +from gnome.cy_gnome.cy_shio_time import CyShioTime +from gnome.cy_gnome.cy_ossm_time import CyOSSMTime from gnome.cy_gnome.cy_cats_mover import CyCatsMover from gnome.cy_gnome.cy_gridcurrent_mover import CyGridCurrentMover from gnome.cy_gnome.cy_ice_mover import CyIceMover from gnome.cy_gnome.cy_currentcycle_mover import CyCurrentCycleMover -from gnome.cy_gnome.cy_shio_time import CyShioTime -from gnome.cy_gnome.cy_ossm_time import CyOSSMTime from gnome.cy_gnome.cy_component_mover import CyComponentMover +from gnome.utilities.serializable import Serializable, Field +from gnome.utilities.time_utils import sec_to_datetime + +from gnome.environment import Tide, TideSchema, Wind, WindSchema +from gnome.movers import CyMover, ProcessSchema + +from gnome.persist.base_schema import ObjType, WorldPoint + class CurrentMoversBaseSchema(ObjType, ProcessSchema): uncertain_duration = SchemaNode(Float(), missing=drop) @@ -48,6 +50,7 @@ def __init__(self, ''' self.uncertain_duration = uncertain_duration self.uncertain_time_delay = uncertain_time_delay + super(CurrentMoversBase, self).__init__(**kwargs) uncertain_duration = property(lambda self: @@ -72,6 +75,7 @@ def get_triangles(self): dtype = triangle_data[0].dtype.descr unstructured_type = dtype[0][1] + unstructured = (triangle_data.view(dtype=unstructured_type) .reshape(-1, len(dtype))[:, :3]) @@ -87,6 +91,7 @@ def get_cells(self): dtype = cell_data[0].dtype.descr unstructured_type = dtype[0][1] + unstructured = (cell_data.view(dtype=unstructured_type) .reshape(-1, len(dtype))[:, 1:]) @@ -109,14 +114,6 @@ def get_cell_center_points(self): ''' return self.mover._get_center_points().view(dtype=' box_to_merge[0][0]: left = box_to_merge[0][0] + if right < box_to_merge[1][0]: right = box_to_merge[1][0] if bottom > box_to_merge[0][1]: bottom = box_to_merge[0][1] + if top < box_to_merge[1][1]: top = box_to_merge[1][1] @@ -822,10 +830,12 @@ def get_scaled_velocities(self, model_time): :param model_time=0: """ num_tri = self.mover.get_num_triangles() + if self.mover._is_triangle_grid(): num_cells = num_tri else: num_cells = num_tri / 2 + vels = np.zeros(num_cells, dtype=basic_types.velocity_rec) self.mover.get_scaled_velocities(model_time, vels) @@ -836,6 +846,7 @@ def get_ice_velocities(self, model_time): :param model_time=0: """ num_tri = self.mover.get_num_triangles() + vels = np.zeros(num_tri, dtype=basic_types.velocity_rec) self.mover.get_ice_velocities(model_time, vels) @@ -846,6 +857,7 @@ def get_movement_velocities(self, model_time): :param model_time=0: """ num_tri = self.mover.get_num_triangles() + vels = np.zeros(num_tri, dtype=basic_types.velocity_rec) self.mover.get_movement_velocities(model_time, vels) @@ -857,8 +869,10 @@ def get_ice_fields(self, model_time): """ num_tri = self.mover.get_num_triangles() num_cells = num_tri / 2 + frac_coverage = np.zeros(num_cells, dtype=np.float64) thickness = np.zeros(num_cells, dtype=np.float64) + self.mover.get_ice_fields(model_time, frac_coverage, thickness) return frac_coverage, thickness @@ -907,11 +921,11 @@ class CurrentCycleMoverSchema(ObjType, ProcessSchema): uncertain_cross = SchemaNode(Float(), default=.25, missing=drop) -class CurrentCycleMover(GridCurrentMover, serializable.Serializable): +class CurrentCycleMover(GridCurrentMover, Serializable): _state = copy.deepcopy(GridCurrentMover._state) - _state.add_field([serializable.Field('tide', - save=True, update=True, - save_reference=True)]) + _state.add_field([Field('tide', save=True, update=True, + save_reference=True)]) + _schema = CurrentCycleMoverSchema def __init__(self, @@ -948,9 +962,9 @@ def __init__(self, # use super with kwargs to invoke base class __init__ self.mover = CyCurrentCycleMover() - tide = kwargs.pop('tide', None) self._tide = None + tide = kwargs.pop('tide', None) if tide is not None: self.tide = tide @@ -985,7 +999,7 @@ def tide(self): @tide.setter def tide(self, tide_obj): - if not isinstance(tide_obj, environment.Tide): + if not isinstance(tide_obj, Tide): raise TypeError('tide must be of type environment.Tide') if isinstance(tide_obj.cy_obj, CyShioTime): @@ -1013,7 +1027,7 @@ def serialize(self, json_='webapi'): schema = self.__class__._schema() if json_ == 'webapi' and 'tide' in toserial: - schema.add(environment.TideSchema(name='tide')) + schema.add(TideSchema(name='tide')) return schema.serialize(toserial) @@ -1025,7 +1039,7 @@ def deserialize(cls, json_): schema = cls._schema() if 'tide' in json_: - schema.add(environment.TideSchema()) + schema.add(TideSchema()) return schema.deserialize(json_) @@ -1040,10 +1054,10 @@ class ComponentMoverSchema(ObjType, ProcessSchema): # scale_value = SchemaNode(Float()) -#class ComponentMover(CyMover, serializable.Serializable): -class ComponentMover(CurrentMoversBase, serializable.Serializable): +# class ComponentMover(CyMover, serializable.Serializable): +class ComponentMover(CurrentMoversBase, Serializable): - #_state = copy.deepcopy(CyMover._state) + # _state = copy.deepcopy(CyMover._state) _state = copy.deepcopy(CurrentMoversBase._state) _update = ['scale_refpoint', @@ -1054,15 +1068,13 @@ class ComponentMover(CurrentMoversBase, serializable.Serializable): _create = [] _create.extend(_update) _state.add(update=_update, save=_create) - _state.add_field([serializable.Field('filename1', - save=True, read=True, isdatafile=True, - test_for_eq=False), - serializable.Field('filename2', - save=True, read=True, isdatafile=True, - test_for_eq=False), - serializable.Field('wind', - save=True, update=True, - save_reference=True)]) + _state.add_field([Field('filename1', save=True, read=True, isdatafile=True, + test_for_eq=False), + Field('filename2', save=True, read=True, isdatafile=True, + test_for_eq=False), + Field('wind', save=True, update=True, + save_reference=True)]) + _schema = ComponentMoverSchema def __init__(self, filename1, filename2=None, wind=None, @@ -1110,10 +1122,6 @@ def __init__(self, filename1, filename2=None, wind=None, if wind is not None: self.wind = wind - # self.scale = kwargs.pop('scale', self.mover.scale_type) - # self.scale_value = kwargs.get('scale_value', - # self.mover.scale_value) - # TODO: no need to check for None since properties that are None # are not persisted @@ -1121,11 +1129,6 @@ def __init__(self, filename1, filename2=None, wind=None, if 'scale_refpoint' in kwargs: self.scale_refpoint = kwargs.pop('scale_refpoint') -# if self.scale and self.scale_value != 0.0 \ -# and self.scale_refpoint is None: -# raise TypeError("Provide a reference point in 'scale_refpoint'." -# ) - super(ComponentMover, self).__init__(**kwargs) def __repr__(self): @@ -1135,15 +1138,6 @@ def __repr__(self): return 'ComponentMover(filename={0})'.format(self.filename1) # Properties - - # scale_type = property(lambda self: bool(self.mover.scale_type), - # lambda self, val: setattr(self.mover, 'scale_type', - # int(val))) - - # scale_by = property(lambda self: bool(self.mover.scale_by), - # lambda self, val: setattr(self.mover, 'scale_by', - # int(val))) - pat1_angle = property(lambda self: self.mover.pat1_angle, lambda self, val: setattr(self.mover, 'pat1_angle', val)) @@ -1189,19 +1183,20 @@ def __repr__(self): val)) use_averaged_winds = property(lambda self: self.mover.use_averaged_winds, - lambda self, val: setattr(self.mover, - 'use_averaged_winds', - val)) + lambda self, val: + setattr(self.mover, 'use_averaged_winds', + val)) wind_power_factor = property(lambda self: self.mover.wind_power_factor, lambda self, val: setattr(self.mover, 'wind_power_factor', val)) - past_hours_to_average = property(lambda self: self.mover.past_hours_to_average, - lambda self, val: setattr(self.mover, - 'past_hours_to_average', - val)) + past_hours_to_average = property(lambda self: (self.mover + .past_hours_to_average), + lambda self, val: + setattr(self.mover, + 'past_hours_to_average', val)) scale_factor_averaged_winds = property(lambda self: self.mover.scale_factor_averaged_winds, lambda self, val: setattr(self.mover, @@ -1234,7 +1229,7 @@ def wind(self): @wind.setter def wind(self, wind_obj): - if not isinstance(wind_obj, environment.Wind): + if not isinstance(wind_obj, Wind): raise TypeError('wind must be of type environment.Wind') self.mover.set_ossm(wind_obj.ossm) @@ -1244,8 +1239,6 @@ def get_grid_data(self): """ Invokes the GetToplogyHdl method of TriGridVel_c object """ - # we are assuming cats are always triangle grids, - # but may want to extend return self.get_triangles() def get_center_points(self): @@ -1255,19 +1248,7 @@ def get_scaled_velocities(self, model_time): """ Get file values scaled to ref pt value, with tide applied (if any) """ - velocities = self.mover._get_velocity_handle() - #ref_scale = self.ref_scale # this needs to be computed, needs a time - - #if self._tide is not None: - #time_value = self._tide.cy_obj.get_time_value(model_time) - #tide = time_value[0][0] - #else: - #tide = 1 - - #velocities['u'] *= ref_scale * tide - #velocities['v'] *= ref_scale * tide - - return velocities + return self.mover._get_velocity_handle() def serialize(self, json_='webapi'): """ @@ -1278,7 +1259,7 @@ def serialize(self, json_='webapi'): schema = self.__class__._schema() if json_ == 'webapi' and 'wind' in dict_: - schema.add(environment.WindSchema(name='wind')) + schema.add(WindSchema(name='wind')) return schema.serialize(dict_) @@ -1293,7 +1274,6 @@ def deserialize(cls, json_): # for 'webapi', there will be nested Wind structure # for 'save' option, there should be no nested 'wind'. It is # removed, loaded and added back after deserialization - schema.add(environment.WindSchema()) - _to_dict = schema.deserialize(json_) + schema.add(WindSchema()) - return _to_dict + return schema.deserialize(json_) diff --git a/py_gnome/gnome/movers/movers.py b/py_gnome/gnome/movers/movers.py index 102021a0d..7ba271685 100644 --- a/py_gnome/gnome/movers/movers.py +++ b/py_gnome/gnome/movers/movers.py @@ -217,6 +217,12 @@ def __init__(self, 'Trapezoid': self.get_delta_Trapezoid} self.default_num_method = default_num_method + if 'env' in kwargs: + if hasattr(self, '_req_refs'): + for k, v in self._req_refs.items(): + for o in kwargs['env']: + if k in o._ref_as: + setattr(self, k, o) Mover.__init__(self, **kwargs) def get_delta_Euler(self, sc, time_step, model_time, pos, vel_field): @@ -247,17 +253,17 @@ def get_delta_RK4(self, sc, time_step, model_time, pos, vel_field): v0 = vel_field.at(pos, t, extrapolate=self.extrapolate) d0 = FlatEarthProjection.meters_to_lonlat(v0 * dt_s / 2, pos) p1 = pos.copy() - p1[:, 0:2] += d0 + p1 += d0 v1 = vel_field.at(p1, t + dt / 2, extrapolate=self.extrapolate) d1 = FlatEarthProjection.meters_to_lonlat(v1 * dt_s / 2, pos) p2 = pos.copy() - p2[:, 0:2] += d1 + p2 += d1 v2 = vel_field.at(p2, t + dt / 2, extrapolate=self.extrapolate) d2 = FlatEarthProjection.meters_to_lonlat(v2 * dt_s, pos) p3 = pos.copy() - p3[:, 0:2] += d2 + p3 += d2 v3 = vel_field.at(p3, t + dt, extrapolate=self.extrapolate) diff --git a/py_gnome/gnome/movers/py_current_movers.py b/py_gnome/gnome/movers/py_current_movers.py index cb84d1cb7..9cd84678d 100644 --- a/py_gnome/gnome/movers/py_current_movers.py +++ b/py_gnome/gnome/movers/py_current_movers.py @@ -3,7 +3,7 @@ import datetime import copy from gnome import basic_types -from gnome.environment import GridCurrent +from gnome.environment import GridCurrent, GridVectorPropSchema from gnome.utilities import serializable from gnome.utilities.projections import FlatEarthProjection from gnome.basic_types import oil_status @@ -11,15 +11,33 @@ world_point_type, spill_type, status_code_type) +from gnome.persist import base_schema +from colander import SchemaNode, Float, Boolean, Sequence, MappingSchema, drop, String, OneOf, SequenceSchema, TupleSchema, DateTime, Bool -class PyGridCurrentMover(movers.PyMover, serializable.Serializable): +class PyCurrentMoverSchema(base_schema.ObjType): + filename = SchemaNode(typ=Sequence(accept_scalar=True), children=[SchemaNode(String())], missing=drop) + current_scale = SchemaNode(Float(), missing=drop) + extrapolate = SchemaNode(Bool(), missing=drop) + time_offset = SchemaNode(Float(), missing=drop) + current = GridVectorPropSchema(missing=drop) - _state = copy.deepcopy(movers.Mover._state) + +class PyCurrentMover(movers.PyMover, serializable.Serializable): + + _state = copy.deepcopy(movers.PyMover._state) + + _state.add_field([serializable.Field('filename', + save=True, read=True, isdatafile=True, + test_for_eq=False), + serializable.Field('current', save=True, read=True, save_reference=True)]) _state.add(update=['uncertain_duration', 'uncertain_time_delay'], save=['uncertain_duration', 'uncertain_time_delay']) + _schema = PyCurrentMoverSchema - _ref_as = 'ugrid_current_movers' + _ref_as = 'py_current_movers' + + _req_refs = {'current': GridCurrent} def __init__(self, current=None, @@ -32,7 +50,8 @@ def __init__(self, uncertain_along=.5, uncertain_across=.25, uncertain_cross=.25, - default_num_method='Trapezoid' + default_num_method='Trapezoid', + **kwargs ): self.current = current self.filename = filename @@ -50,8 +69,12 @@ def __init__(self, # either a 1, or 2 depending on whether spill is certain or not self.spill_type = 0 - movers.PyMover.__init__(self, - default_num_method=default_num_method) + super(PyCurrentMover, self).__init__(default_num_method=default_num_method, + **kwargs) + + def _attach_default_refs(self, ref_dict): + pass + return serializable.Serializable._attach_default_refs(self, ref_dict) @classmethod def from_netCDF(cls, @@ -73,7 +96,8 @@ def from_netCDF(cls, current_scale=current_scale, uncertain_along=uncertain_along, uncertain_across=uncertain_across, - uncertain_cross=uncertain_cross) + uncertain_cross=uncertain_cross, + **kwargs) def get_scaled_velocities(self, time): @@ -81,10 +105,6 @@ def get_scaled_velocities(self, time): :param model_time=0: """ points = None - if isinstance(self.grid, pysgrid): - points = np.column_stack(self.grid.node_lon[:], self.grid.node_lat[:]) - if isinstance(self.grid, pyugrid): - raise NotImplementedError("coming soon...") vels = self.grid.interpolated_velocities(time, points) return vels diff --git a/py_gnome/gnome/movers/py_wind_movers.py b/py_gnome/gnome/movers/py_wind_movers.py index e5b28f0c0..9cbf9fb4a 100644 --- a/py_gnome/gnome/movers/py_wind_movers.py +++ b/py_gnome/gnome/movers/py_wind_movers.py @@ -3,6 +3,7 @@ import datetime import copy from gnome import basic_types +from gnome.environment import GridCurrent, GridVectorPropSchema from gnome.utilities import serializable, rand from gnome.utilities.projections import FlatEarthProjection from gnome.environment import GridWind @@ -11,15 +12,33 @@ world_point_type, spill_type, status_code_type) +from gnome.persist import base_schema +from colander import SchemaNode, Float, Boolean, Sequence, MappingSchema, drop, String, OneOf, SequenceSchema, TupleSchema, DateTime, Bool + + +class PyWindMoverSchema(base_schema.ObjType): + filename = SchemaNode(typ=Sequence(accept_scalar=True), children=[SchemaNode(String())], missing=drop) + current_scale = SchemaNode(Float(), missing=drop) + extrapolate = SchemaNode(Bool(), missing=drop) + time_offset = SchemaNode(Float(), missing=drop) + wind = GridVectorPropSchema(missing=drop) class PyWindMover(movers.PyMover, serializable.Serializable): - _state = copy.deepcopy(movers.Mover._state) + _state = copy.deepcopy(movers.PyMover._state) + + _state.add_field([serializable.Field('filename', + save=True, read=True, isdatafile=True, + test_for_eq=False), + serializable.Field('wind', save=True, read=True, save_reference=True)]) _state.add(update=['uncertain_duration', 'uncertain_time_delay'], save=['uncertain_duration', 'uncertain_time_delay']) + _schema = PyWindMoverSchema _ref_as = 'py_wind_movers' + + _req_refs = {'wind': GridWind} def __init__(self, wind=None, @@ -45,8 +64,6 @@ def __init__(self, to False since user provided a valid Wind and does not wish to use the default from the Model. """ - movers.PyMover.__init__(self, - default_num_method=default_num_method) self._wind = wind self.make_default_refs = False @@ -58,11 +75,12 @@ def __init__(self, # also sets self._uncertain_angle_units self.uncertain_angle_scale = uncertain_angle_scale + super(PyWindMover, self).__init__(default_num_method=default_num_method, + **kwargs) self.array_types.update({'windages', 'windage_range', 'windage_persist'}) - # set optional attributes @classmethod def from_netCDF(cls, diff --git a/py_gnome/gnome/movers/random_movers.py b/py_gnome/gnome/movers/random_movers.py index 6f25cab9c..757e2a456 100644 --- a/py_gnome/gnome/movers/random_movers.py +++ b/py_gnome/gnome/movers/random_movers.py @@ -7,19 +7,19 @@ from colander import (SchemaNode, Float, drop) -from gnome.persist.base_schema import ObjType -from gnome.utilities.serializable import Serializable -from gnome.movers import CyMover, ProcessSchema +from gnome.basic_types import (oil_status) from gnome.cy_gnome.cy_random_mover import CyRandomMover from gnome.cy_gnome.cy_random_vertical_mover import CyRandomVerticalMover + +from gnome.utilities.serializable import Serializable, Field + from gnome.environment import IceConcentration -from gnome.environment.grid_property import _init_grid -from gnome.utilities.projections import FlatEarthProjection -from gnome.basic_types import oil_status -from gnome.basic_types import (world_point, - world_point_type, - spill_type, - status_code_type) +from gnome.environment.grid import PyGrid +from gnome.environment.grid_property import GridPropSchema + +from gnome.movers import CyMover, ProcessSchema +from gnome.persist.base_schema import ObjType + class RandomMoverSchema(ObjType, ProcessSchema): diffusion_coef = SchemaNode(Float(), missing=drop) @@ -35,7 +35,7 @@ class RandomMover(CyMover, Serializable): """ _state = copy.deepcopy(CyMover._state) _state.add(update=['diffusion_coef', 'uncertain_factor'], - save=['diffusion_coef', 'uncertain_factor']) + save=['diffusion_coef', 'uncertain_factor']) _schema = RandomMoverSchema def __init__(self, **kwargs): @@ -51,9 +51,12 @@ def __init__(self, **kwargs): Remaining kwargs are passed onto :class:`gnome.movers.Mover` __init__ using super. See Mover documentation for remaining valid kwargs. """ - self.mover = \ - CyRandomMover(diffusion_coef=kwargs.pop('diffusion_coef', 100000), - uncertain_factor=kwargs.pop('uncertain_factor', 2)) + diffusion_coeff = kwargs.pop('diffusion_coef', 100000) + uncertain_factor = kwargs.pop('uncertain_factor', 2) + + self.mover = CyRandomMover(diffusion_coef=diffusion_coeff, + uncertain_factor=uncertain_factor) + super(RandomMover, self).__init__(**kwargs) @property @@ -73,25 +76,36 @@ def uncertain_factor(self, value): self.mover.uncertain_factor = value def __repr__(self): - return ('RandomMover(diffusion_coef={0}, ' - 'uncertain_factor={1}, ' - 'active_start={2}, active_stop={3}, ' - 'on={4})'.format(self.diffusion_coef, self.uncertain_factor, - self.active_start, self.active_stop, self.on)) + return ('RandomMover(diffusion_coef={0}, uncertain_factor={1}, ' + 'active_start={2}, active_stop={3}, on={4})' + .format(self.diffusion_coef, self.uncertain_factor, + self.active_start, self.active_stop, self.on)) + + +class IceAwareRandomMoverSchema(RandomMoverSchema): + ice_concentration = GridPropSchema(missing=drop) class IceAwareRandomMover(RandomMover): - def __init__(self, *args, **kwargs): - if 'ice_conc_var' in kwargs.keys(): - self.ice_conc_var = kwargs.pop('ice_conc_var') + + _state = copy.deepcopy(RandomMover._state) + _state.add_field([Field('ice_concentration', + save=True, read=True, save_reference=True)]) + _schema = IceAwareRandomMoverSchema + + _req_refs = {'ice_concentration': IceConcentration} + + def __init__(self, ice_concentration=None, **kwargs): + self.ice_concentration = ice_concentration super(IceAwareRandomMover, self).__init__(**kwargs) @classmethod def from_netCDF(cls, filename=None, + dataset=None, grid_topology=None, units=None, time=None, - ice_conc_var=None, + ice_concentration=None, grid=None, grid_file=None, data_file=None, @@ -99,41 +113,58 @@ def from_netCDF(cls, filename=None, if filename is not None: data_file = filename grid_file = filename + if grid is None: - grid = _init_grid(grid_file, - grid_topology=grid_topology) - if ice_conc_var is None: - ice_conc_var = IceConcentration.from_netCDF(filename, - time=time, - grid=grid) - return cls(ice_conc_var=ice_conc_var, **kwargs) + grid = PyGrid.from_netCDF(grid_file, + grid_topology=grid_topology) + + if ice_concentration is None: + ice_concentration = (IceConcentration + .from_netCDF(filename=filename, + dataset=dataset, + data_file=data_file, + grid_file=grid_file, + time=time, grid=grid, + **kwargs)) + + return cls(ice_concentration=ice_concentration, **kwargs) def get_move(self, sc, time_step, model_time_datetime): status = sc['status_codes'] != oil_status.in_water positions = sc['positions'] deltas = np.zeros_like(positions) - interp = self.ice_conc_var.at(positions, model_time_datetime, extrapolate=True).copy() + + interp = self.ice_concentration.at(positions, model_time_datetime, + extrapolate=True).copy() interp_mask = np.logical_and(interp >= 0.2, interp < 0.8) + if len(np.where(interp_mask)[0]) != 0: ice_mask = interp >= 0.8 - deltas = super(IceAwareRandomMover, self).get_move(sc, time_step, model_time_datetime) + deltas = (super(IceAwareRandomMover, self) + .get_move(sc, time_step, model_time_datetime)) + interp -= 0.2 interp *= 1.25 interp *= 1.3333333333 deltas[:, 0:2][ice_mask] = 0 - deltas[:, 0:2][interp_mask] *= (1 - interp[interp_mask][:, np.newaxis]) # scale winds from 100-0% depending on ice coverage + # scale winds from 100-0% depending on ice coverage + deltas[:, 0:2][interp_mask] *= (1 - interp[interp_mask][:, np.newaxis]) deltas[status] = (0, 0, 0) + return deltas else: - return super(IceAwareRandomMover, self).get_move(sc, time_step, model_time_datetime) + return (super(IceAwareRandomMover, self) + .get_move(sc, time_step, model_time_datetime)) class RandomVerticalMoverSchema(ObjType, ProcessSchema): vertical_diffusion_coef_above_ml = SchemaNode(Float(), missing=drop) vertical_diffusion_coef_below_ml = SchemaNode(Float(), missing=drop) + mixed_layer_depth = SchemaNode(Float(), missing=drop) + horizontal_diffusion_coef_above_ml = SchemaNode(Float(), missing=drop) horizontal_diffusion_coef_below_ml = SchemaNode(Float(), missing=drop) @@ -147,15 +178,15 @@ class RandomVerticalMover(CyMover, Serializable): """ _state = copy.deepcopy(CyMover._state) _state.add(update=['vertical_diffusion_coef_above_ml', - 'vertical_diffusion_coef_below_ml', - 'horizontal_diffusion_coef_above_ml', - 'horizontal_diffusion_coef_below_ml', - 'mixed_layer_depth'], - save=['vertical_diffusion_coef_above_ml', - 'vertical_diffusion_coef_below_ml', - 'horizontal_diffusion_coef_above_ml', - 'horizontal_diffusion_coef_below_ml', - 'mixed_layer_depth']) + 'vertical_diffusion_coef_below_ml', + 'horizontal_diffusion_coef_above_ml', + 'horizontal_diffusion_coef_below_ml', + 'mixed_layer_depth'], + save=['vertical_diffusion_coef_above_ml', + 'vertical_diffusion_coef_below_ml', + 'horizontal_diffusion_coef_above_ml', + 'horizontal_diffusion_coef_below_ml', + 'mixed_layer_depth']) _schema = RandomVerticalMoverSchema def __init__(self, **kwargs): @@ -168,10 +199,15 @@ def __init__(self, **kwargs): :param vertical_diffusion_coef_below_ml: Vertical diffusion coefficient for random diffusion below the mixed layer. Default is .11 cm2/s :param mixed_layer_depth: Mixed layer depth. Default is 10 meters. - :param horizontal_diffusion_coef_above_ml: Horizontal diffusion coefficient - for random diffusion above the mixed layer. Default is 100000 cm2/s. - :param horizontal_diffusion_coef_below_ml: Horizontal diffusion coefficient - for random diffusion below the mixed layer. Default is 126 cm2/s. + :param horizontal_diffusion_coef_above_ml: Horizontal diffusion + coefficient for random + diffusion above the mixed + layer. Default is + 100000 cm2/s. + :param horizontal_diffusion_coef_below_ml: Horizontal diffusion + coefficient for random + diffusion below the mixed + layer. Default is 126 cm2/s. Remaining kwargs are passed onto Mover's __init__ using super. See Mover documentation for remaining valid kwargs. @@ -224,16 +260,28 @@ def mixed_layer_depth(self, value): self.mover.mixed_layer_depth = value def __repr__(self): - ''' - .. todo:: We probably want to include more information. - ''' return ('RandomVerticalMover(vertical_diffusion_coef_above_ml={0}, ' 'vertical_diffusion_coef_below_ml={1}, mixed_layer_depth={2}, ' 'horizontal_diffusion_coef_above_ml={3}, ' - 'horizontal_diffusion_coef_below_ml={4}, active_start={5}, active_stop={6}, ' - 'on={6})'.format(self.vertical_diffusion_coef_above_ml, - self.vertical_diffusion_coef_below_ml, - self.mixed_layer_depth, - self.horizontal_diffusion_coef_above_ml, - self.horizontal_diffusion_coef_below_ml, - self.active_start, self.active_stop, self.on)) + 'horizontal_diffusion_coef_below_ml={4}, ' + 'active_start={5}, active_stop={6}, on={6})' + .format(self.vertical_diffusion_coef_above_ml, + self.vertical_diffusion_coef_below_ml, + self.mixed_layer_depth, + self.horizontal_diffusion_coef_above_ml, + self.horizontal_diffusion_coef_below_ml, + self.active_start, self.active_stop, self.on)) + + + + + + + + + + + + + + diff --git a/py_gnome/gnome/movers/simple_mover.py b/py_gnome/gnome/movers/simple_mover.py index d2fa33bca..77281ee75 100644 --- a/py_gnome/gnome/movers/simple_mover.py +++ b/py_gnome/gnome/movers/simple_mover.py @@ -11,8 +11,8 @@ """ import copy -import numpy -np = numpy +import numpy as np + from numpy import random from colander import (SchemaNode, Float) @@ -55,15 +55,13 @@ class SimpleMover(Mover, serializable.Serializable): _state = copy.deepcopy(Mover._state) _state.add(update=['uncertainty_scale', 'velocity'], - save=['uncertainty_scale', 'velocity']) + save=['uncertainty_scale', 'velocity']) _schema = SimpleMoverSchema - def __init__( - self, - velocity, - uncertainty_scale=0.5, - **kwargs - ): + def __init__(self, + velocity, + uncertainty_scale=0.5, + **kwargs): """ simple_mover (velocity) @@ -76,21 +74,16 @@ def __init__( """ # use this, to be compatible with whatever we are using for location - self.velocity = np.asarray(velocity, - dtype=mover_type).reshape((3, - )) + self.velocity = np.asarray(velocity, dtype=mover_type).reshape((3,)) + self.uncertainty_scale = uncertainty_scale super(SimpleMover, self).__init__(**kwargs) def __repr__(self): return 'SimpleMover(<%s>)' % self.id - def get_move( - self, - spill, - time_step, - model_time, - ): + def get_move(self, spill, + time_step, model_time): """ moves the particles defined in the spill object @@ -132,12 +125,12 @@ def get_move( num = sum(in_water_mask) scale = self.uncertainty_scale * self.velocity \ * time_step - delta[in_water_mask, 0] += random.uniform(-scale[0], - scale[0], num) - delta[in_water_mask, 1] += random.uniform(-scale[1], - scale[1], num) - delta[in_water_mask, 2] += random.uniform(-scale[2], - scale[2], num) + delta[in_water_mask, 0] += random.uniform(-scale[0], scale[0], + num) + delta[in_water_mask, 1] += random.uniform(-scale[1], scale[1], + num) + delta[in_water_mask, 2] += random.uniform(-scale[2], scale[2], + num) # scale for projection diff --git a/py_gnome/gnome/movers/wind_movers.py b/py_gnome/gnome/movers/wind_movers.py index f1e506931..57ffceba8 100644 --- a/py_gnome/gnome/movers/wind_movers.py +++ b/py_gnome/gnome/movers/wind_movers.py @@ -5,27 +5,29 @@ import os import copy from datetime import datetime -import math -import numpy -np = numpy +import numpy as np + from colander import (SchemaNode, Bool, String, Float, drop) -from gnome.basic_types import (ts_format, - world_point, +from gnome.basic_types import (world_point, world_point_type, velocity_rec, datetime_value_2d) -from gnome.utilities import serializable, rand -from gnome.utilities import time_utils - -from gnome import environment -from gnome.movers import CyMover, ProcessSchema from gnome.cy_gnome.cy_wind_mover import CyWindMover from gnome.cy_gnome.cy_gridwind_mover import CyGridWindMover from gnome.cy_gnome.cy_ice_wind_mover import CyIceWindMover +from gnome.utilities.serializable import Serializable, Field +from gnome.utilities.time_utils import sec_to_datetime +from gnome.utilities.rand import random_with_persistance + + +from gnome import environment +from gnome import basic_types +from gnome.movers import CyMover, ProcessSchema + from gnome.persist.base_schema import ObjType from gnome.exceptions import ReferencedObjectNotSet @@ -35,7 +37,6 @@ class WindMoversBaseSchema(ObjType, ProcessSchema): uncertain_time_delay = SchemaNode(Float(), missing=drop) uncertain_speed_scale = SchemaNode(Float(), missing=drop) uncertain_angle_scale = SchemaNode(Float(), missing=drop) - #extrapolate = SchemaNode(Bool(), missing=drop) class WindMoverSchema(WindMoversBaseSchema): @@ -60,7 +61,6 @@ def __init__(self, uncertain_time_delay=0, uncertain_speed_scale=2., uncertain_angle_scale=0.4, - #extrapolate=False, **kwargs): """ This is simply a base class for WindMover and GridWindMover for the @@ -91,8 +91,6 @@ def __init__(self, # also sets self._uncertain_angle_units self.uncertain_angle_scale = uncertain_angle_scale - #self.extrapolate = extrapolate - self.array_types.update({'windages', 'windage_range', 'windage_persist'}) @@ -100,13 +98,12 @@ def __init__(self, # no conversion necessary - simply sets/gets the stored value uncertain_speed_scale = \ property(lambda self: self.mover.uncertain_speed_scale, - lambda self, val: setattr(self.mover, - 'uncertain_speed_scale', + lambda self, val: setattr(self.mover, 'uncertain_speed_scale', val)) + uncertain_angle_scale = \ property(lambda self: self.mover.uncertain_angle_scale, - lambda self, val: setattr(self.mover, - 'uncertain_angle_scale', + lambda self, val: setattr(self.mover, 'uncertain_angle_scale', val)) def _seconds_to_hours(self, seconds): @@ -131,11 +128,6 @@ def uncertain_time_delay(self): def uncertain_time_delay(self, val): self.mover.uncertain_time_delay = self._hours_to_seconds(val) -# extrapolate = property(lambda self: self.mover.extrapolate, -# lambda self, val: setattr(self.mover, -# 'extrapolate', -# val)) - def prepare_for_model_step(self, sc, time_step, model_time_datetime): """ Call base class method using super @@ -150,14 +142,14 @@ def prepare_for_model_step(self, sc, time_step, model_time_datetime): # if no particles released, then no need for windage # TODO: revisit this since sc.num_released shouldn't be None - if sc.num_released is None or sc.num_released == 0: + if sc.num_released is None or sc.num_released == 0: return - rand.random_with_persistance(sc['windage_range'][:, 0], - sc['windage_range'][:, 1], - sc['windages'], - sc['windage_persist'], - time_step) + random_with_persistance(sc['windage_range'][:, 0], + sc['windage_range'][:, 1], + sc['windages'], + sc['windage_persist'], + time_step) def get_move(self, sc, time_step, model_time_datetime): """ @@ -172,13 +164,10 @@ def get_move(self, sc, time_step, model_time_datetime): self.prepare_data_for_get_move(sc, model_time_datetime) if self.active and len(self.positions) > 0: - self.mover.get_move(self.model_time, - time_step, - self.positions, - self.delta, + self.mover.get_move(self.model_time, time_step, + self.positions, self.delta, sc['windages'], - self.status_codes, - self.spill_type) + self.status_codes, self.spill_type) return (self.delta.view(dtype=world_point_type) .reshape((-1, len(world_point)))) @@ -198,7 +187,7 @@ def _state_as_str(self): return info.format(self) -class WindMover(WindMoversBase, serializable.Serializable): +class WindMover(WindMoversBase, Serializable): """ Python wrapper around the Cython wind_mover module. This class inherits from CyMover and contains CyWindMover @@ -209,12 +198,12 @@ class WindMover(WindMoversBase, serializable.Serializable): _state = copy.deepcopy(WindMoversBase._state) _state.add(update=['extrapolate'], save=['extrapolate']) - _state.add_field(serializable.Field('wind', save=True, update=True, - save_reference=True)) + _state.add_field(Field('wind', + save=True, update=True, save_reference=True)) + _schema = WindMoverSchema def __init__(self, wind=None, extrapolate=False, **kwargs): - #def __init__(self, wind=None, **kwargs): """ Uses super to call CyMover base class __init__ @@ -233,39 +222,32 @@ def __init__(self, wind=None, extrapolate=False, **kwargs): self._wind = None if wind is not None: self.wind = wind - kwargs['make_default_refs'] = \ - kwargs.pop('make_default_refs', False) - kwargs['name'] = \ - kwargs.pop('name', wind.name) + kwargs['make_default_refs'] = kwargs.pop('make_default_refs', + False) + kwargs['name'] = kwargs.pop('name', wind.name) self.extrapolate = extrapolate # set optional attributes super(WindMover, self).__init__(**kwargs) - # this will have to be updated when wind is set or changed + # this will have to be updated when wind is set or changed if self.wind is not None: - self.real_data_start = time_utils.sec_to_datetime(self.wind.ossm.get_start_time()) - self.real_data_stop = time_utils.sec_to_datetime(self.wind.ossm.get_end_time()) + self.real_data_start = sec_to_datetime(self.wind.ossm + .get_start_time()) + self.real_data_stop = sec_to_datetime(self.wind.ossm + .get_end_time()) def __repr__(self): - """ - .. todo:: - We probably want to include more information. - """ - return ('{0.__class__.__module__}.{0.__class__.__name__}(\n' - '{1}' - ')'.format(self, self._state_as_str())) + return ('{0.__class__.__module__}.{0.__class__.__name__}(\n{1})' + .format(self, self._state_as_str())) def __str__(self): - info = ('WindMover - current _state. ' - 'See "wind" object for wind conditions:\n' - '{0}'.format(self._state_as_str())) - return info - + return ('WindMover - current _state. ' + 'See "wind" object for wind conditions:\n{0}' + .format(self._state_as_str())) extrapolate = property(lambda self: self.mover.extrapolate, - lambda self, val: setattr(self.mover, - 'extrapolate', + lambda self, val: setattr(self.mover, 'extrapolate', val)) @property @@ -298,13 +280,12 @@ def serialize(self, json_='webapi'): """ toserial = self.to_serialize(json_) schema = self.__class__._schema() + if json_ == 'webapi': # add wind schema schema.add(environment.WindSchema(name='wind')) - serial = schema.serialize(toserial) - - return serial + return schema.serialize(toserial) @classmethod def deserialize(cls, json_): @@ -312,11 +293,11 @@ def deserialize(cls, json_): append correct schema for wind object """ schema = cls._schema() + if 'wind' in json_: schema.add(environment.WindSchema()) - _to_dict = schema.deserialize(json_) - return _to_dict + return schema.deserialize(json_) def wind_mover_from_file(filename, **kwargs): @@ -330,9 +311,8 @@ def wind_mover_from_file(filename, **kwargs): :returns mover: returns a wind mover, built from the file """ w = environment.Wind(filename=filename, format='r-theta') - wm = WindMover(w, **kwargs) - return wm + return WindMover(w, **kwargs) def constant_wind_mover(speed, direction, units='m/s'): @@ -348,43 +328,47 @@ def constant_wind_mover(speed, direction, units='m/s'): :return: returns a gnome.movers.WindMover object all set up. .. note:: - The time for a constant wind timeseries is irrelevant. - This function simply sets it to datetime.now() accurate to hours. + The time for a constant wind timeseries is irrelevant. + This function simply sets it to datetime.now() accurate to hours. """ - series = np.zeros((1, ), dtype=datetime_value_2d) # note: if there is ony one entry, the time is arbitrary dt = datetime.now().replace(microsecond=0, second=0, minute=0) series[0] = (dt, (speed, direction)) wind = environment.Wind(timeseries=series, units=units) - w_mover = WindMover(wind) - return w_mover + + return WindMover(wind) class GridWindMoverSchema(WindMoversBaseSchema): - """ Similar to WindMover except it doesn't have wind_id""" - wind_file = SchemaNode(String(), missing=drop) + """ + Similar to WindMover except it doesn't have wind_id + """ + filename = SchemaNode(String(), missing=drop) topology_file = SchemaNode(String(), missing=drop) wind_scale = SchemaNode(Float(), missing=drop) extrapolate = SchemaNode(Bool(), missing=drop) -class GridWindMover(WindMoversBase, serializable.Serializable): +class GridWindMover(WindMoversBase, Serializable): _state = copy.deepcopy(WindMoversBase._state) - _state.add(update=['wind_scale', 'extrapolate'], save=['wind_scale', 'extrapolate']) - _state.add_field([serializable.Field('wind_file', save=True, - read=True, isdatafile=True, test_for_eq=False), - serializable.Field('topology_file', save=True, - read=True, isdatafile=True, test_for_eq=False)]) + _state.add(update=['wind_scale', 'extrapolate'], + save=['wind_scale', 'extrapolate']) + + _state.add_field([Field('filename', save=True, read=True, + isdatafile=True, test_for_eq=False), + Field('topology_file', save=True, read=True, + isdatafile=True, test_for_eq=False)]) _schema = GridWindMoverSchema - def __init__(self, wind_file, topology_file=None, + def __init__(self, filename, topology_file=None, extrapolate=False, time_offset=0, **kwargs): """ :param wind_file: file containing wind data on a grid + :param filename: file containing wind data on a grid :param topology_file: Default is None. When exporting topology, it is stored in this file :param wind_scale: Value to scale wind data @@ -396,9 +380,9 @@ def __init__(self, wind_file, topology_file=None, uses super: super(GridWindMover,self).__init__(\*\*kwargs) """ - if not os.path.exists(wind_file): + if not os.path.exists(filename): raise ValueError('Path for wind file does not exist: {0}' - .format(wind_file)) + .format(filename)) if topology_file is not None: if not os.path.exists(topology_file): @@ -406,15 +390,18 @@ def __init__(self, wind_file, topology_file=None, .format(topology_file)) # is wind_file and topology_file is stored with cy_gridwind_mover? - self.wind_file = wind_file + self.filename = filename self.topology_file = topology_file self.mover = CyGridWindMover(wind_scale=kwargs.pop('wind_scale', 1)) - self.name = os.path.split(wind_file)[1] + self.name = os.path.split(filename)[1] + super(GridWindMover, self).__init__(**kwargs) - self.mover.text_read(wind_file, topology_file) - self.real_data_start = time_utils.sec_to_datetime(self.mover.get_start_time()) - self.real_data_stop = time_utils.sec_to_datetime(self.mover.get_end_time()) + self.mover.text_read(filename, topology_file) + + self.real_data_start = sec_to_datetime(self.mover.get_start_time()) + self.real_data_stop = sec_to_datetime(self.mover.get_end_time()) + self.mover.extrapolate_in_time(extrapolate) self.mover.offset_time(time_offset * 3600.) @@ -423,29 +410,82 @@ def __repr__(self): .. todo:: We probably want to include more information. """ - info = 'GridWindMover(\n{0})'.format(self._state_as_str()) - return info + return 'GridWindMover(\n{0})'.format(self._state_as_str()) def __str__(self): - info = ('GridWindMover - current _state.\n' - '{0}'.format(self._state_as_str())) - return info + return ('GridWindMover - current _state.\n{0}' + .format(self._state_as_str())) wind_scale = property(lambda self: self.mover.wind_scale, - lambda self, val: setattr(self.mover, - 'wind_scale', + lambda self, val: setattr(self.mover, 'wind_scale', val)) extrapolate = property(lambda self: self.mover.extrapolate, - lambda self, val: setattr(self.mover, - 'extrapolate', + lambda self, val: setattr(self.mover, 'extrapolate', val)) time_offset = property(lambda self: self.mover.time_offset / 3600., - lambda self, val: setattr(self.mover, - 'time_offset', + lambda self, val: setattr(self.mover, 'time_offset', val * 3600.)) + def get_grid_data(self): + return self.get_cells() + + def get_cells(self): + """ + Invokes the GetCellDataHdl method of TimeGridWind_c object. + Cross-references point data to get cell coordinates. + """ + cell_data = self.mover._get_cell_data() + points = self.get_points() + + dtype = cell_data[0].dtype.descr + unstructured_type = dtype[0][1] + unstructured = (cell_data.view(dtype=unstructured_type) + .reshape(-1, len(dtype))[:, 1:]) + + return points[unstructured] + + def get_points(self): + points = (self.mover._get_points() + .astype([('long', ' 2: - #curvilinear grid; ugrids never have line segments greater than 2 points - for l in lines.transpose((1,0,2)).copy(): + # curvilinear grid; ugrids never have line segments greater than 2 points + for l in lines.transpose((1, 0, 2)).copy(): img.draw_polyline(l, line_color=self.color, line_width=self.width) @@ -761,33 +763,33 @@ def __init__(self, scale=1000 ): self.prop = prop - self.projection=projection - self.on=on - self.color=color - self.mask_color=mask_color - self.size=size - self.width=width - self.scale=scale + self.projection = projection + self.on = on + self.color = color + self.mask_color = mask_color + self.size = size + self.width = width + self.scale = scale def draw_to_image(self, img, time): if not self.on: return t0 = self.prop.time.index_of(time, extrapolate=True) - 1 data_u = self.prop.variables[0].data[t0] - data_u2 = self.prop.variables[0].data[t0+1] if len(self.prop.time)> 1 else data_u + data_u2 = self.prop.variables[0].data[t0 + 1] if len(self.prop.time) > 1 else data_u data_v = self.prop.variables[1].data[t0] - data_v2 = self.prop.variables[1].data[t0+1] if len(self.prop.time)> 1 else data_v + data_v2 = self.prop.variables[1].data[t0 + 1] if len(self.prop.time) > 1 else data_v t_alphas = self.prop.time.interp_alpha(time, extrapolate=True) data_u = data_u + t_alphas * (data_u2 - data_u) data_v = data_v + t_alphas * (data_v2 - data_v) data_u = data_u.reshape(-1) data_v = data_v.reshape(-1) - start=end=None + start = end = None # if self.prop.grid.infer_grid(data_u) == 'centers': # start = self.prop.grid.centers # else: try: - start = self.prop.grid.nodes.copy().reshape(-1,2) + start = self.prop.grid.nodes.copy().reshape(-1, 2) except AttributeError: start = np.column_stack((self.prop.grid.node_lon, @@ -795,19 +797,19 @@ def draw_to_image(self, img, time): # deltas = FlatEarthProjection.meters_to_lonlat(data*self.scale, lines[:0]) if hasattr(data_u, 'mask'): - start[data_u.mask] = [0.,0.] + start[data_u.mask] = [0., 0.] data_u *= self.scale * 8.9992801e-06 data_v *= self.scale * 8.9992801e-06 - data_u /= np.cos(np.deg2rad(start[:,1])) + data_u /= np.cos(np.deg2rad(start[:, 1])) end = start.copy() - end[:,0] += data_u - end[:,1] += data_v + end[:, 0] += data_u + end[:, 1] += data_v if hasattr(data_u, 'mask'): - end[data_u.mask] = [0.,0.] + end[data_u.mask] = [0., 0.] bounds = self.projection.image_box - pt1 = ((bounds[0][0] <= start[:, 0]) * (start[:, 0] <= bounds[1][0]) * + pt1 = ((bounds[0][0] <= start[:, 0]) * (start[:, 0] <= bounds[1][0]) * (bounds[0][1] <= start[:, 1]) * (start[:, 1] <= bounds[1][1])) - pt2 = ((bounds[0][0] <= end[:, 0]) * (end[:, 0] <= bounds[1][0]) * + pt2 = ((bounds[0][0] <= end[:, 0]) * (end[:, 0] <= bounds[1][0]) * (bounds[0][1] <= end[:, 1]) * (end[:, 1] <= bounds[1][1])) start = start[pt1 * pt2] end = end[pt1 * pt2] @@ -815,10 +817,10 @@ def draw_to_image(self, img, time): end = self.projection.to_pixel_multipoint(end, asint=True) img.draw_dots(start, diameter=self.size, color=self.color) - line = np.array([[0.,0.],[0.,0.]]) - for i in xrange(0,len(start)): + line = np.array([[0., 0.], [0., 0.]]) + for i in xrange(0, len(start)): line[0] = start[i] line[1] = end[i] img.draw_polyline(line, - line_color = self.color, - line_width = self.width) + line_color=self.color, + line_width=self.width) diff --git a/py_gnome/gnome/persist/save_load.py b/py_gnome/gnome/persist/save_load.py index 54a834902..2faafb752 100644 --- a/py_gnome/gnome/persist/save_load.py +++ b/py_gnome/gnome/persist/save_load.py @@ -8,6 +8,7 @@ import logging import gnome +import colander # as long as loggers are configured before module is loaded, module scope # logger will work. If loggers are configured after this module is loaded and @@ -333,24 +334,43 @@ def _move_data_file(self, saveloc, json_): for field in fields: if field.name not in json_: continue - - # data filename - d_fname = os.path.split(json_[field.name])[1] - - if zipfile.is_zipfile(saveloc): - # add datafile to zip archive - with zipfile.ZipFile(saveloc, 'a', - compression=zipfile.ZIP_DEFLATED, - allowZip64=self._allowzip64) as z: - if d_fname not in z.namelist(): - z.write(json_[field.name], d_fname) + + raw_paths = json_[field.name] + if isinstance(raw_paths, list): + for i, p in enumerate(raw_paths): + d_fname = os.path.split(p)[1] + if zipfile.is_zipfile(saveloc): + # add datafile to zip archive + with zipfile.ZipFile(saveloc, 'a', + compression=zipfile.ZIP_DEFLATED, + allowZip64=self._allowzip64) as z: + if d_fname not in z.namelist(): + z.write(p, d_fname) + else: + # move datafile to saveloc + if p != os.path.join(saveloc, d_fname): + shutil.copy(p, saveloc) + + # always want to update the reference so it is relative to saveloc + json_[field.name][i] = d_fname else: - # move datafile to saveloc - if json_[field.name] != os.path.join(saveloc, d_fname): - shutil.copy(json_[field.name], saveloc) - - # always want to update the reference so it is relative to saveloc - json_[field.name] = d_fname + # data filename + d_fname = os.path.split(json_[field.name])[1] + + if zipfile.is_zipfile(saveloc): + # add datafile to zip archive + with zipfile.ZipFile(saveloc, 'a', + compression=zipfile.ZIP_DEFLATED, + allowZip64=self._allowzip64) as z: + if d_fname not in z.namelist(): + z.write(json_[field.name], d_fname) + else: + # move datafile to saveloc + if json_[field.name] != os.path.join(saveloc, d_fname): + shutil.copy(json_[field.name], saveloc) + + # always want to update the reference so it is relative to saveloc + json_[field.name] = d_fname return json_ @@ -398,8 +418,13 @@ def _update_datafile_path(cls, json_data, saveloc): # For zip files coming from the web, is_savezip_valid() tests # filenames in archive do not contain paths with '..' # In here, we just extract datafile to saveloc/. - json_data[field.name] = os.path.join(saveloc, - json_data[field.name]) + raw_n = json_data[field.name] + if isinstance(raw_n, list): + for i, n in enumerate(raw_n): + json_data[field.name][i] = os.path.join(saveloc, n) + else: + json_data[field.name] = os.path.join(saveloc, + json_data[field.name]) @classmethod def loads(cls, json_data, saveloc=None, references=None): @@ -432,7 +457,11 @@ def loads(cls, json_data, saveloc=None, references=None): cls._update_datafile_path(json_data, saveloc) # deserialize after removing references - _to_dict = cls.deserialize(json_data) + try: + _to_dict = cls.deserialize(json_data) + except colander.Invalid as e: + print('Class {0} failed to deserialize.'.format(cls.__name__)) + raise e if ref_dict: _to_dict.update(ref_dict) diff --git a/py_gnome/gnome/spill/elements/element_type.py b/py_gnome/gnome/spill/elements/element_type.py index ba0ef92f9..d940b1fa0 100644 --- a/py_gnome/gnome/spill/elements/element_type.py +++ b/py_gnome/gnome/spill/elements/element_type.py @@ -76,28 +76,81 @@ def __repr__(self): 'substance={0.substance!r}' ')'.format(self)) - def __getattr__(self, att): - """ - delegates some attribute access to the element types. + # def __getattr__(self, att): + # """ + # delegates some attribute access to the element types. + + # .. todo:: + # There is an issue in that if two initializers have the same + # property - could be the case if they both define a 'distribution', + # then it does not know which one to return + # """ + # for initr in self.initializers: + # try: + # return getattr(initr, att) + # except AttributeError: + # pass + + # # nothing returned, then attribute was not found + # msg = ('{0} attribute does not exist in element_type or initializers' + # .format(att)) + # # NOTE: this would get trigggered by a a hasattr() call -- + # # which isn't something we need to log + # ## self.logger.warning(msg) + # raise AttributeError(msg) + + # properties for attributes the need to be pulled from initializers + @property + def windage_range(self): + for initr in self.initializers: + try: + return getattr(initr, 'windage_range') + except AttributeError: + pass + msg = 'windage_range attribute does not exist any initializers' - .. todo:: - There is an issue in that if two initializers have the same - property - could be the case if they both define a 'distribution', - then it does not know which one to return - """ + self.logger.warning(msg) + raise AttributeError(msg) + @windage_range.setter + def windage_range(self, wr): + print self.initializers + for initr in self.initializers: + print "initr:" + if hasattr(initr, "windage_range"): + print "setting windage_range" + initr.windage_range = wr + return None + msg = "can't set windage_range: no initializer has it" + + self.logger.warning(msg) + raise AttributeError(msg) + + @property + def windage_persist(self): for initr in self.initializers: try: - return getattr(initr, att) + return getattr(initr, 'windage_persist') except AttributeError: pass + msg = 'windage_persist attribute does not exist any initializers' - # nothing returned, then attribute was not found - msg = ('{0} attribute does not exist in element_type initializers' - .format(att)) + self.logger.warning(msg) + raise AttributeError(msg) + @windage_persist.setter + def windage_persist(self, wp): + print self.initializers + for initr in self.initializers: + print "initr:" + if hasattr(initr, "windage_persist"): + print "setting windage_persist" + initr.windage_persist = wp + return None + msg = "can't set windage_persist: no initializer has it" self.logger.warning(msg) raise AttributeError(msg) + def contains_object(self, obj_id): for o in self.initializers: if obj_id == o.id: @@ -362,7 +415,7 @@ def plume(distribution_type='droplet_size', ) if density is not None: - # Assume density is at 15 K - convert density to api + # Assume density is at 15 C - convert density to api api = uc.convert('density', density_units, 'API', density) if substance_name is not None: substance = get_oil_props({'name': substance_name, diff --git a/py_gnome/gnome/spill/elements/initializers.py b/py_gnome/gnome/spill/elements/initializers.py index e1759ecb4..3f0e6f7c7 100644 --- a/py_gnome/gnome/spill/elements/initializers.py +++ b/py_gnome/gnome/spill/elements/initializers.py @@ -334,8 +334,16 @@ def initialize(self, num_new_particles, spill, data_arrays, substance): self.distribution.set_values(drop_size) data_arrays['droplet_diameter'][-num_new_particles:] = drop_size - water_temp = spill.water.get('temperature') - le_density[:] = substance.density_at_temp(water_temp) + + #don't require a water object + #water_temp = spill.water.get('temperature') + #le_density[:] = substance.density_at_temp(water_temp) + + if spill.water is not None: + water_temp = spill.water.get('temperature') + le_density[:] = substance.density_at_temp(water_temp) + else: + le_density[:] = substance.density_at_temp() # now update rise_vel with droplet size - dummy for now rise_velocity_from_drop_size( diff --git a/py_gnome/gnome/spill/spill.py b/py_gnome/gnome/spill/spill.py index 3e89f7aba..78a603928 100644 --- a/py_gnome/gnome/spill/spill.py +++ b/py_gnome/gnome/spill/spill.py @@ -251,10 +251,12 @@ def __init__(self, self.element_type = element_type - self.on = on # spill is active or not + self.on = on # spill is active or not # raise Exception("stopping") + # fixme: shouldn't units default to 'kg'? self.units = None + # fixme -- and amount always be in kg? self.amount = amount if amount is not None: @@ -265,9 +267,8 @@ def __init__(self, self.amount_uncertainty_scale = amount_uncertainty_scale - ''' - fraction of area covered by oil - ''' + # fixme: why is fractional area part of spill??? + # fraction of area covered by oil self.frac_coverage = 1.0 self.name = name @@ -660,7 +661,6 @@ def substance(self, subs): self.element_type.substance = subs def get_mass(self, units=None): - """ Return the mass released during the spill. User can also specify desired output units in the function. @@ -668,18 +668,27 @@ def get_mass(self, units=None): If volume is given, then use density to find mass. Density is always at 15degC, consistent with API definition """ - + # fixme: This really should be re-factored to always store mass. if self.amount is None: return self.amount - # first convert amount to 'kg' if self.units in self.valid_mass_units: + # first convert amount to 'kg' mass = uc.convert('Mass', self.units, 'kg', self.amount) elif self.units in self.valid_vol_units: - water_temp = self.water.get('temperature') - rho = self.element_type.substance.density_at_temp(water_temp) - vol = uc.convert('Volume', self.units, 'm^3', self.amount) + # need to convert to mass + if self.element_type.substance is None: + # unspecified substance gets a density 1000 kg/m^3 + rho = 1000.0 + else: + # DO NOT change this back! + # for the UI to be consistent, the conversion needs to use standard + # density -- not the current water temp. + # water_temp = self.water.get('temperature') + # ideally substance would have a "standard_density" attribute for this. + rho = self.element_type.substance.density_at_temp(288.15) + vol = uc.convert('Volume', self.units, 'm^3', self.amount) mass = rho * vol else: raise ValueError("{} is not a valid mass or Volume unit" diff --git a/py_gnome/gnome/utilities/file_tools/data_helpers.py b/py_gnome/gnome/utilities/file_tools/data_helpers.py index 80194a968..ac95d8d72 100644 --- a/py_gnome/gnome/utilities/file_tools/data_helpers.py +++ b/py_gnome/gnome/utilities/file_tools/data_helpers.py @@ -133,11 +133,13 @@ def _gen_topology(filename, break return gt - -def _get_dataset(filename): +def _get_dataset(filename, dataset=None): + if dataset is not None: + return dataset df = None if isinstance(filename, basestring): df = nc4.Dataset(filename) else: df = nc4.MFDataset(filename) return df + diff --git a/py_gnome/gnome/utilities/geometry/polygons.py b/py_gnome/gnome/utilities/geometry/polygons.py index 9d513b88e..26ae545c7 100644 --- a/py_gnome/gnome/utilities/geometry/polygons.py +++ b/py_gnome/gnome/utilities/geometry/polygons.py @@ -221,10 +221,10 @@ def append(self, polygon, metadata=None): old_length = self._PointsArray.shape[0] added_length = polygon.shape[0] - self._PointsArray.resize((old_length+added_length, 2)) + self._PointsArray.resize((old_length + added_length, 2)) self._PointsArray[-added_length:, :] = polygon - self._IndexArray.resize((self._IndexArray.shape[0]+1)) + self._IndexArray.resize((self._IndexArray.shape[0] + 1)) self._IndexArray[-1] = self._PointsArray.shape[0] self._MetaDataList.append(metadata) @@ -319,6 +319,11 @@ def __len__(self): # there is an extra index at the end, so that IndexArray[i+1] works return len(self._IndexArray) - 1 + def __bool__(self): + return bool(len(self)) + + __nonzero__ = __bool__ # for python 2 + def __getitem__(self, index): """ returns a Polygon object diff --git a/py_gnome/gnome/utilities/timeseries.py b/py_gnome/gnome/utilities/timeseries.py index 9272000f7..08e0ac042 100644 --- a/py_gnome/gnome/utilities/timeseries.py +++ b/py_gnome/gnome/utilities/timeseries.py @@ -85,9 +85,12 @@ def __init__(self, timeseries=None, filename=None, format='uv'): self._filename = filename if filename is None: - self._check_timeseries(timeseries) # will raise an Exception if it fails + # will raise an Exception if it fails + self._check_timeseries(timeseries) + datetime_value_2d = self._xform_input_timeseries(timeseries) time_value_pair = to_time_value_pair(datetime_value_2d, format) + self.ossm = CyTimeseries(timeseries=time_value_pair) else: ts_format = tsformat(format) @@ -108,7 +111,9 @@ def _check_timeseries(self, timeseries): else: for i in timeseries: if not self._is_timeseries_value(i): - raise TimeseriesError('value: %s is not a timeseries value' % (i,)) + raise TimeseriesError('value: {} ' + 'is not a timeseries value' + .format(i)) return True if not self._timeseries_is_ascending(timeseries): @@ -273,7 +278,8 @@ def __eq__(self, other): if not np.all(self_ts['time'] == other_ts['time']): return False - if not np.allclose(self_ts['value'], other_ts['value'], atol=1e-10, rtol=1e-10): + if not np.allclose(self_ts['value'], other_ts['value'], + atol=1e-10, rtol=1e-10): return False return True diff --git a/py_gnome/gnome/utilities/weathering/adios2.py b/py_gnome/gnome/utilities/weathering/adios2.py index 9139817f2..3fdaebcef 100644 --- a/py_gnome/gnome/utilities/weathering/adios2.py +++ b/py_gnome/gnome/utilities/weathering/adios2.py @@ -14,8 +14,8 @@ class Adios2(object): Note: We should really try to look up the references for these and move them to an appropriately referenced class. ''' - @classmethod - def wave_height(cls, U, fetch): + @staticmethod + def wave_height(U, fetch): """ compute the wave height @@ -43,8 +43,8 @@ def wave_height(cls, U, fetch): # into account? return Hrms if Hrms < 30.0 else 30.0 - @classmethod - def wind_speed_from_height(cls, H): + @staticmethod + def wind_speed_from_height(H): """ Compute the wind speed to use for the whitecap fraction This is the reverse of wave_height() @@ -61,8 +61,8 @@ def wind_speed_from_height(cls, H): return U_h - @classmethod - def mean_wave_period(cls, U, wave_height, fetch): + @staticmethod + def mean_wave_period(U, wave_height, fetch): """ Compute the mean wave period @@ -84,9 +84,14 @@ def mean_wave_period(cls, U, wave_height, fetch): return T - @classmethod - def dissipative_wave_energy(self, water_density, H): + @staticmethod + def dissipative_wave_energy(water_density, H): """ Compute the dissipative wave energy + + units? -- should be: 1/s^3 + i.e. energy disspiation rate per m^2 + so water density is in there -- but something else is up + does the constant have units? """ return 0.0034 * water_density * g * H ** 2 diff --git a/py_gnome/gnome/utilities/weathering/delvigne_sweeney.py b/py_gnome/gnome/utilities/weathering/delvigne_sweeney.py index 8c2f70017..22967f01f 100644 --- a/py_gnome/gnome/utilities/weathering/delvigne_sweeney.py +++ b/py_gnome/gnome/utilities/weathering/delvigne_sweeney.py @@ -8,8 +8,8 @@ class DelvigneSweeney(object): less than 10 knots. ''' - @classmethod - def breaking_waves_frac(cls, wind_speed, peak_wave_period): + @staticmethod + def breaking_waves_frac(wind_speed, peak_wave_period): ''' Field observations of Holthuysen and Herbers (1986) and Toba et al. (1971) lead to a simple empirical relation diff --git a/py_gnome/gnome/utilities/weathering/lehr_simecek.py b/py_gnome/gnome/utilities/weathering/lehr_simecek.py index 95ef4726d..95aaf03f9 100644 --- a/py_gnome/gnome/utilities/weathering/lehr_simecek.py +++ b/py_gnome/gnome/utilities/weathering/lehr_simecek.py @@ -6,14 +6,14 @@ class LehrSimecek(object): This based on formulas by: Lehr and Simecek-Beatty ''' - @classmethod - def whitecap_fraction(cls, U, salinity): + @staticmethod + def whitecap_fraction(U, salinity): """ compute the white capping fraction This based on Lehr and Simecek-Beatty The Relation of Langmuir Circulation Processes to the Standard - Oil Spill Spreading, Dispersion and Transport Algorithms + Oil Spill Spreading, Dispersion, and Transport Algorithms Spill Sci. and Tech. Bull, 6:247-253 (2000) (maybe typo -- didn't match) @@ -24,16 +24,18 @@ def whitecap_fraction(cls, U, salinity): if U < 4.0: # m/s # linear fit from 0 to the 4m/s value from Ding and Farmer - # maybe should be a exponential / quadratic fit? - # or zero less than 3, then a sharp increase to 4m/s? + # The Lehr and Simecek-Beatty paper had a different formulation: + # fw = 0.025 * (U - 3.0) / Tm + # that one produces a kink at 4 m/s and negative for U < 1 fw = (0.0125 * U) / Tm else: # # Ding and Farmer (JPO 1994) # fw = (0.01*U + 0.01) / Tm - # old ADIOS had a .5 factor - not sure why but we'll keep it - # for now # Ding and Farmer (JPO 1994) - fw = 0.5 * (0.01 * U + 0.01) / Tm + fw = (0.01 * U + 0.01) / Tm - return fw if fw <= 1.0 else 1.0 # only with U > 200m/s! + fw *= 0.5 # old ADIOS had a .5 factor - not sure why but we'll keep it + # for now + + return min(fw, 1.0) # only with U > 200m/s! diff --git a/py_gnome/gnome/utilities/weathering/monahan.py b/py_gnome/gnome/utilities/weathering/monahan.py index 2dd4fe6d0..60db225ab 100644 --- a/py_gnome/gnome/utilities/weathering/monahan.py +++ b/py_gnome/gnome/utilities/weathering/monahan.py @@ -4,15 +4,19 @@ class Monahan(object): This based on formulas by: Monahan (JPO, 1971) ''' - @classmethod - def whitecap_decay_constant(cls, salinity): + @staticmethod + def whitecap_decay_constant(salinity): """ Monahan(JPO, 1971) time constant characterizing exponential whitecap decay. + :param salinity: the salinity of the water + :type salinity: integer or float in PSU + + The saltwater value for this constant is 3.85 sec while the freshwater value is 2.54 sec. - We will interpolate with salinity + This interpolates between those values by salinity """ return 0.03742857 * salinity + 2.54 diff --git a/py_gnome/gnome/utilities/weathering/stokes.py b/py_gnome/gnome/utilities/weathering/stokes.py index e9197eff7..120d3d503 100644 --- a/py_gnome/gnome/utilities/weathering/stokes.py +++ b/py_gnome/gnome/utilities/weathering/stokes.py @@ -1,7 +1,7 @@ class Stokes(object): - @classmethod - def water_phase_xfer_velocity(cls, oil_water_rho_delta, diameter): + @staticmethod + def water_phase_xfer_velocity(oil_water_rho_delta, diameter): ''' water phase transfer velocity k_w (m/s) diff --git a/py_gnome/gnome/weatherers/__init__.py b/py_gnome/gnome/weatherers/__init__.py index 36f294d2a..83bb86892 100644 --- a/py_gnome/gnome/weatherers/__init__.py +++ b/py_gnome/gnome/weatherers/__init__.py @@ -7,60 +7,62 @@ from emulsification import Emulsification from weathering_data import WeatheringData from spreading import Langmuir, FayGravityViscous, ConstantArea +from roc import Burn as ROC_Burn +from roc import Disperse as ROC_Disperse ''' Weatherers are to be ordered as follows: - 0. half-life - This weatherer is just used for testing. It is not used + half-life - This weatherer is just used for testing. It is not used with the following real weatherers but we need to include it so sorting always works - 1. cleanup options including Skimmer, Burn, Beaching - 2. chemical dispersion - 3. evaporation - assign to all classes in this module - 4. natural dispersion - 5. oil particle aggregation - what is this? - 6. dissolution - 7. biodegradation - 8. emulsification - 9. WeatheringData - defines initialize_data() method to initialize all + + Cleanup options including Skimmer, Burn, Beaching + + WeatheringData - defines initialize_data() method to initialize all weathering data arrays. In weather_elements, this updates data arrays corresponding with wd properties (density, viscosity) - 10. FayGravityViscous - initializes the 'fay_area' and 'area' array. Also + + FayGravityViscous - initializes the 'fay_area' and 'area' array. Also updates the 'area' and 'fay_area' array - 11. Langmuir - modifies the 'area' array with fractional coverage based on + + Langmuir - modifies the 'area' array with fractional coverage based on langmuir cells. - removal options have been re-prioritized - Burn, Skim, Disperse, Beach + Removal options have been re-prioritized - Burn, Skim, Disperse, Beach the first three are listed in reverse order because the marking done in prepare_for_model_step prioritizes whichever operation gets marked last. Once they are marked the weathering order doesn't matter. ''' -__all__ = [Weatherer, - HalfLifeWeatherer, - ChemicalDispersion, - Skimmer, - Burn, - Beaching, - Evaporation, - NaturalDispersion, - # OilParticleAggregation, - Dissolution, - # Biodegradation, - Emulsification, - WeatheringData, - FayGravityViscous, - ConstantArea, - Langmuir, - ] - -weatherers_idx = dict([(v, i) for i, v in enumerate(__all__)]) +# NOTE: this list specifies sort order! +sort_order = [ChemicalDispersion, + Skimmer, + Burn, + ROC_Burn, + ROC_Disperse, + Beaching, + HalfLifeWeatherer, + Evaporation, + NaturalDispersion, + # OilParticleAggregation, + Dissolution, + # Biodegradation, + Emulsification, + WeatheringData, + FayGravityViscous, + ConstantArea, + Langmuir, + ] +weatherers_idx = dict([(v, i) for i, v in enumerate(sort_order)]) def weatherer_sort(weatherer): ''' Returns an int describing the sorting order of the weatherer or None if an order is not defined for the weatherer + + :param weatherer: weatherer instance ''' return weatherers_idx.get(weatherer.__class__) diff --git a/py_gnome/gnome/weatherers/emulsification.py b/py_gnome/gnome/weatherers/emulsification.py index b22edb74f..6a9b5f1bd 100644 --- a/py_gnome/gnome/weatherers/emulsification.py +++ b/py_gnome/gnome/weatherers/emulsification.py @@ -7,11 +7,17 @@ import numpy as np +import gnome + from gnome.array_types import (frac_lost, # due to evaporation and dissolution age, mass, + oil_density, + density, bulltime, interfacial_area, + oil_viscosity, + viscosity, frac_water) from gnome.utilities.serializable import Serializable, Field @@ -38,12 +44,15 @@ def __init__(self, ''' self.waves = waves + self._bw = 0 if waves is not None: kwargs['make_default_refs'] = \ kwargs.pop('make_default_refs', False) super(Emulsification, self).__init__(**kwargs) self.array_types.update({'age', 'bulltime', 'frac_water', + 'density', 'viscosity', + 'oil_density', 'oil_viscosity', 'mass', 'interfacial_area', 'frac_lost'}) def prepare_for_model_run(self, sc): @@ -56,6 +65,7 @@ def prepare_for_model_run(self, sc): if self.on: super(Emulsification, self).prepare_for_model_run(sc) sc.mass_balance['water_content'] = 0.0 + self._bw = 0 def prepare_for_model_step(self, sc, time_step, model_time): ''' @@ -70,6 +80,178 @@ def prepare_for_model_step(self, sc, time_step, model_time): if not self.active: return + # eventually switch this in + def new_weather_elements(self, sc, time_step, model_time): + ''' + weather elements over time_step + - sets 'water_content' in sc.mass_balance + ''' + + if not self.active: + return + if sc.num_released == 0: + return + + for substance, data in sc.itersubstancedata(self.array_types): + if len(data['age']) == 0: + #if len(data['frac_water']) == 0: + # substance does not contain any surface_weathering LEs + continue + + product_type = substance.get('product_type') + if product_type == 'Refined': + data['frac_water'][:] = 0.0 # since there can only be one product type this could be return... + continue # since there can only be one product type this could be return... + + # compute energy dissipation rate (m^2/s^3) based on wave height + wave_height = self.waves.get_value(model_time)[0] + if wave_height > 0: + eps = (.0355 * wave_height ** .215) / ((np.log(6.31 / wave_height ** 1.45)) ** 3) + else: + #eps = 0. + continue + + water_temp = self.waves.water.get('temperature', 'K') + rho_oil = substance.density_at_temp(water_temp) + dens_emul = data['density'] + visc_emul = data['viscosity'] + dens_oil = data['oil_density'] + visc_oil = data['oil_viscosity'] + sigma_ow = substance.oil_water_surface_tension() # does this vary in time? + print "sigma_ow" + print sigma_ow[0] + v0 = substance.kvis_at_temp(water_temp) #viscosity is calculated in weathering_data + if wave_height > 0: + delta_T_emul = 1630 + 450 / wave_height ** (1.5) + else: + continue + + visc_min = .00001 # 10 cSt + visc_max = .01 # 10000 cSt + sigma_min = .01 # 10 dyne/com + # new suggestion .03 <= f_asph <= .2 + # latest update, min only .03 <= f_asph + f_min = .03 + f_max = .2 + r_min = .2 + r_max = 1.4 + rho_min = 600 #kg/m^3 + drop_min = .000008 # 8 microns + + #k_emul2 = 2.3 / delta_T_emul + k_emul2 = 1. / delta_T_emul + k_emul = self._water_uptake_coeff(model_time, substance) + + # bulltime is not in database, but could be set by user + #emul_time = substance.get_bulltime() + emul_time = substance.bulltime + + resin_mask = substance._sara['type'] == 'Resins' + asphaltene_mask = substance._sara['type'] == 'Asphaltenes' + saturates_mask = substance._sara['type'] == 'Saturates' + aromatics_mask = substance._sara['type'] == 'Aromatics' + + f_sat = (saturates_mask * data['mass_components']).sum(axis=1) / data['mass'].sum() + f_arom = (aromatics_mask * data['mass_components']).sum(axis=1) / data['mass'].sum() + + # will want to use mass_components to update over time + f_res1 = resin_mask * substance._sara['fraction'] + f_res = np.sum(resin_mask * substance._sara['fraction']) + f_asph = np.sum(asphaltene_mask * substance._sara['fraction']) + rho_asph = np.sum(asphaltene_mask * substance._sara['density']) # 1100 kg/m^3 + + f_res2 = resin_mask * data['mass_components'] + if data['mass'].sum() == 0: + continue + f_res3 = (resin_mask * data['mass_components']).sum(axis=1) / data['mass'].sum() + f_asph3 = (asphaltene_mask * data['mass_components']).sum(axis=1) / data['mass'].sum() + + if f_res > 0: + r_oil = f_asph / f_res + else: + #r_oil = 0 + continue + if f_asph <= 0: + continue + r_oil3 = np.where(f_res3 > 0, f_asph3 / f_res3, 0) # check if limits are just for S_b calculation + + Y_max = .61 + .5 * r_oil - .28 * r_oil **2 + # limit on r_oil3 values or just final Y_max or set Y_max = 0 if out of bounds? + if Y_max > .9: + Y_max = .9 + + m = .5 * (visc_max + visc_min) + x_visc = (visc_oil - m) / (visc_max - visc_min) + + x_sig_min = (sigma_ow[0] - sigma_min) / sigma_ow[0] + + #m = .5 * (f_max + f_min) + #x_fasph = (f_asph3 - m) / (f_max - f_min) + # changed to one-sided, add check for f_asph3 = 0 + x_fasph = (f_asph3 - f_min) / (f_asph3) + + m = .5 * (r_max + r_min) + x_r = (r_oil - m) / (r_max - r_min) + + x_s = 0 # placeholder since this isn't used + + # decide which factors use initial value and which use current value + # once Bw is set it stays on + Bw = self._Bw(x_visc,x_sig_min,x_fasph,x_r,x_s) + + T_week = 604800 + + # Bill's calculation uses sigma_ow[0] in dynes/cm, visc in cSt and a fudge factor of .478834 + # so we need to convert and scale + print "dens_oil" + print dens_oil + print "visc_oil" + print visc_oil + print "r_oil" + print r_oil + S_b = .478834 * ((dens_oil * (1000000*visc_oil)**.25 / (1000*sigma_ow[0])) * r_oil * np.exp(-2 * r_oil**2))**(1/6) + S_b[S_b > 1] = 1. + S_b[S_b < 0] = 0. + print "S_b" + print S_b + T_week = 604800 + + + k_lw = np.where(data['frac_water'] > 0, (1 - S_b) / T_week, 0.) + + #data['frac_water'] += (Bw * (k_emul2 * (Y_max - data['frac_water'])) - k_lw * data['frac_water']) * time_step + Y_prime = 1.582 * Y_max # Y_max / (1 - 1/e) + data['frac_water'] += (Bw * (k_emul2 * (Y_prime - data['frac_water'])) - k_lw * data['frac_water']) * time_step + data['frac_water'] = np.where(data['frac_water']>Y_max,Y_max,data['frac_water']) + # get from database bullwinkle (could be overridden by user) + #emul_constant = substance.get('bullwinkle_fraction') + # will want to check if user set a value, and change the interface since there is no longer a bullwinkle + emul_constant = substance.bullwinkle + + # max water content fraction - get from database + Y_max = substance.get('emulsion_water_fraction_max') + + # doesn't emulsify, avoid the nans + if Y_max <= 0: + continue + S_max = (6. / constants.drop_min) * (Y_max / (1.0 - Y_max)) + + #sc.mass_balance['water_content'] += \ + #np.sum(data['frac_water'][:]) / sc.num_released + # just average the water fraction each time - it is not per time + # step value but at a certain time value + # todo: probably should be weighted avg + if data['mass'].sum() > 0: + sc.mass_balance['water_content'] = \ + np.sum(data['mass']/data['mass'].sum() * data['frac_water']) + + self.logger.debug(self._pid + 'water_content for {0}: {1}'. + format(substance.name, + sc.mass_balance['water_content'])) + + sc.update_from_fatedataview() + + def weather_elements(self, sc, time_step, model_time): ''' weather elements over time_step @@ -160,13 +342,78 @@ def deserialize(cls, json_): dict_ = schema.deserialize(json_) if 'waves' in json_: - waves = class_from_objtype(json_['waves'].pop('obj_type')) - dict_['waves'] = waves.deserialize(json_['waves']) + obj = json_['waves']['obj_type'] + dict_['waves'] = (eval(obj).deserialize(json_['waves'])) +# if 'waves' in json_: +# waves = class_from_objtype(json_['waves'].pop('obj_type')) +# dict_['waves'] = waves.deserialize(json_['waves']) return dict_ else: return json_ + + def _H_log(self, k, x): + ''' + logistic function for turning on emulsification + ''' + H_log = 1 / (1 + np.exp(-1*k*x)) + + return H_log + + def _H_4(self, k, x): + ''' + symmetric function for turning on emulsification + ''' + H_4 = 1 / (1 + x**(2*k)) + + return H_4 + + def _Bw(self, x_visc, x_sig_min, x_fasph, x_r, x_s): + ''' + ''' + k_v = 4 + k_sig = 4 + k_fasph = 3 + k_r = 2 + k_s = 1.5 + + # for now, I think P_min will be determined elsewhere + U = 0 + P_min = .03 + #P_min = P_min - U*P_min + #P_min = P_min + U*(1 - P_min) + + k = 4 + P_1 = self._H_log(k,x_sig_min) + + k = 4 + P_2 = self._H_4(k,x_visc) + + k = 3 + P_3 = self._H_4(k,x_fasph) + + k = 2 + P_4 = self._H_4(k,x_r) + + k = 1.5 + #P_5 = self._H_log(k,x_s) + P_5 = 1 # placeholder until Bill comes up with a good option + # in his AMOP paper he is using slick thickness... + + P_all = P_1 * P_2 * P_3 * P_4 * P_5 + #P_all = self._H_log(k_v,x_v_min) * self._H_log(k_v,x_v_max) * self._H_log(k_sig,x_sig_min) * self._H_log(k_fasph,x_fasph) * self._H_log(k_r,x_r_min) * self._H_log(k_r,x_r_max) * self._H_log(k_s,x_s_min) + + #if (P_all.any() < P_min): + if (P_all.all() < P_min): + Bw = 0 + else: + Bw = 1 + + Bw = np.where(P_all < .03, 0, 1) + + return Bw + def _water_uptake_coeff(self, model_time, substance): ''' Use higher of wind or pseudo wind corresponding to wave height diff --git a/py_gnome/gnome/weatherers/platforms.json b/py_gnome/gnome/weatherers/platforms.json new file mode 100644 index 000000000..e0b16f39d --- /dev/null +++ b/py_gnome/gnome/weatherers/platforms.json @@ -0,0 +1,835 @@ +{ + "vessel": [ + { + "swath_width_max": 100, + "max_op_time": 36, + "application_speed_max": 20, + "cascade_transit_speed": 5, + "payload": 2000, + "fuel_load": 40, + "application_speed_min": 3, + "swath_width": 65, + "application_speed": 5, + "staging_area_brief": 45, + "transit_speed": 5, + "max_range_with_payload": 1000, + "pump_rate_max": 20, + "pump_rate": "", + "pump_rate_min": 3, + "refuel": false, + "name": "Typical Large Vessel", + "dispersant_load": 20, + "swath_width_min": 20, + "transit_speed_max": 20, + "u_turn_time": 1, + "transit_speed_min": 5 + } + ], + "aircraft": [ + { + "swath_width_max": 200, + "max_op_time": 4, + "application_speed_max": 200, + "FIELD11": "", + "payload": 400, + "fuel_load": 10, + "reposition_speed": 120, + "taxi_time_landing": 3, + "max_range_no_payload": 1000, + "application_speed_min": 50, + "swath_width": 100, + "cascade_transit_speed_max_without_payload": null, + "cascade_transit_speed_without_payload": null, + "application_speed": 120, + "approach": 0.5, + "cascade_transit_speed_min_without_payload": null, + "staging_area_brief": 45, + "transit_speed": 150, + "max_range_with_payload": 850, + "u_turn_time": 1, + "cascade_transit_speed_with_payload": 150, + "pump_rate_max": 1000, + "pump_rate_min": 5, + "cascade_transit_speed_max_with_payload": null, + "name": "Test Platform", + "dispersant_load": 10, + "cascade_transit_speed_min_with_payload": null, + "swath_width_min": 20, + "departure": 0.5, + "taxi_land_depart": 3, + "transit_speed_max": 50, + "taxi_time_takeoff": 3, + "transit_speed_min": 250 + }, + { + "swath_width_max": 75, + "max_op_time": 4, + "application_speed_max": 130, + "FIELD11": "X", + "payload": 400, + "fuel_load": 5, + "reposition_speed": 140, + "taxi_time_landing": 3, + "max_range_no_payload": 532, + "application_speed_min": 104, + "swath_width": 70, + "cascade_transit_speed_max_without_payload": 140, + "cascade_transit_speed_without_payload": 122, + "application_speed": 130, + "approach": 0.5, + "cascade_transit_speed_min_without_payload": 122, + "staging_area_brief": 45, + "transit_speed": 128, + "max_range_with_payload": 442, + "u_turn_time": 0.75, + "cascade_transit_speed_with_payload": 128, + "pump_rate_max": 260, + "pump_rate_min": 15, + "cascade_transit_speed_max_with_payload": 128, + "name": "Air Tractor AT-402 A & B 216 gal", + "dispersant_load": 8, + "cascade_transit_speed_min_with_payload": 128, + "swath_width_min": 50, + "departure": 0.5, + "taxi_land_depart": 3, + "transit_speed_max": 140, + "taxi_time_takeoff": 3, + "transit_speed_min": 122 + }, + { + "swath_width_max": 75, + "max_op_time": 3, + "application_speed_max": 135, + "FIELD11": "X", + "payload": 500, + "fuel_load": 5, + "reposition_speed": 140, + "taxi_time_landing": 3, + "max_range_no_payload": 450, + "application_speed_min": 104, + "swath_width": 70, + "cascade_transit_speed_max_without_payload": 122, + "cascade_transit_speed_without_payload": 122, + "application_speed": 135, + "approach": 0.5, + "cascade_transit_speed_min_without_payload": 122, + "staging_area_brief": 45, + "transit_speed": 120, + "max_range_with_payload": 360, + "u_turn_time": 0.75, + "cascade_transit_speed_with_payload": 120, + "pump_rate_max": 260, + "pump_rate_min": 15, + "cascade_transit_speed_max_with_payload": 120, + "name": "Air Tractor AT-502 B 170 gal", + "dispersant_load": 10, + "cascade_transit_speed_min_with_payload": 120, + "swath_width_min": 45, + "departure": 0.5, + "taxi_land_depart": 3, + "transit_speed_max": 152, + "taxi_time_takeoff": 3, + "transit_speed_min": 120 + }, + { + "swath_width_max": 85, + "max_op_time": 2.5, + "application_speed_max": 170, + "FIELD11": "X", + "payload": 630, + "fuel_load": 10, + "reposition_speed": 160, + "taxi_time_landing": 3, + "max_range_no_payload": 500, + "application_speed_min": 110, + "swath_width": 85, + "cascade_transit_speed_max_without_payload": 167, + "cascade_transit_speed_without_payload": 167, + "application_speed": 145, + "approach": 1, + "cascade_transit_speed_min_without_payload": 167, + "staging_area_brief": 45, + "transit_speed": 160, + "max_range_with_payload": 400, + "u_turn_time": 1, + "cascade_transit_speed_with_payload": 160, + "pump_rate_max": 260, + "pump_rate_min": 15, + "cascade_transit_speed_max_with_payload": 160, + "name": "Air Tractor AT-602 216 gal", + "dispersant_load": 10, + "cascade_transit_speed_min_with_payload": 160, + "swath_width_min": 60, + "departure": 1, + "taxi_land_depart": 3, + "transit_speed_max": 172, + "taxi_time_takeoff": 3, + "transit_speed_min": 150 + }, + { + "swath_width_max": 90, + "max_op_time": 4.2, + "application_speed_max": 180, + "FIELD11": "x", + "payload": 800, + "fuel_load": 5, + "reposition_speed": 170, + "taxi_time_landing": 3, + "max_range_no_payload": 946, + "application_speed_min": 110, + "swath_width": 90, + "cascade_transit_speed_max_without_payload": 166, + "cascade_transit_speed_without_payload": 166, + "application_speed": 150, + "approach": 1, + "cascade_transit_speed_min_without_payload": 144, + "staging_area_brief": 45, + "transit_speed": 150, + "max_range_with_payload": 630, + "u_turn_time": 1, + "cascade_transit_speed_with_payload": 150, + "pump_rate_max": 370, + "pump_rate_min": 15, + "cascade_transit_speed_max_with_payload": 150, + "name": "Air Tractor AT-802 A or F 380 gal", + "dispersant_load": 10, + "cascade_transit_speed_min_with_payload": 150, + "swath_width_min": 70, + "departure": 1, + "taxi_land_depart": 3, + "transit_speed_max": 166, + "taxi_time_takeoff": 3, + "transit_speed_min": 144 + }, + { + "swath_width_max": 90, + "max_op_time": 3.3, + "application_speed_max": 180, + "FIELD11": "X", + "payload": 800, + "fuel_load": 5, + "reposition_speed": 170, + "taxi_time_landing": 3, + "max_range_no_payload": 747, + "application_speed_min": 110, + "swath_width": 90, + "cascade_transit_speed_max_without_payload": 166, + "cascade_transit_speed_without_payload": 166, + "application_speed": 150, + "approach": 1, + "cascade_transit_speed_min_without_payload": 166, + "staging_area_brief": 45, + "transit_speed": 150, + "max_range_with_payload": 495, + "u_turn_time": 1, + "cascade_transit_speed_with_payload": 150, + "pump_rate_max": 370, + "pump_rate_min": 15, + "cascade_transit_speed_max_with_payload": 150, + "name": "Air Tractor AT-802 A or F 308 gal", + "dispersant_load": 10, + "cascade_transit_speed_min_with_payload": 150, + "swath_width_min": 70, + "departure": 1, + "taxi_land_depart": 3, + "transit_speed_max": 192, + "taxi_time_takeoff": 3, + "transit_speed_min": 150 + }, + { + "swath_width_max": 90, + "max_op_time": 2.6, + "application_speed_max": 180, + "FIELD11": "X", + "payload": 800, + "fuel_load": 5, + "reposition_speed": 150, + "taxi_time_landing": 3, + "max_range_no_payload": 598, + "application_speed_min": 110, + "swath_width": 90, + "cascade_transit_speed_max_without_payload": 188, + "cascade_transit_speed_without_payload": 166, + "application_speed": 145, + "approach": 1, + "cascade_transit_speed_min_without_payload": 166, + "staging_area_brief": 45, + "transit_speed": 150, + "max_range_with_payload": 390, + "u_turn_time": 1, + "cascade_transit_speed_with_payload": 150, + "pump_rate_max": 370, + "pump_rate_min": 15, + "cascade_transit_speed_max_with_payload": 188, + "name": "Air Tractor AT-802 A 254 gal", + "dispersant_load": 10, + "cascade_transit_speed_min_with_payload": 150, + "swath_width_min": 70, + "departure": 1, + "taxi_land_depart": 3, + "transit_speed_max": 192, + "taxi_time_takeoff": 3, + "transit_speed_min": 150 + }, + { + "swath_width_max": 150, + "max_op_time": 6.8, + "application_speed_max": 165, + "FIELD11": "x", + "payload": 3750, + "fuel_load": 32, + "reposition_speed": 165, + "taxi_time_landing": 15, + "max_range_no_payload": 2400, + "application_speed_min": 130, + "swath_width": 150, + "cascade_transit_speed_max_without_payload": 330, + "cascade_transit_speed_without_payload": 282, + "application_speed": 140, + "approach": 1, + "cascade_transit_speed_min_without_payload": 282, + "staging_area_brief": 45, + "transit_speed": 225, + "max_range_with_payload": 2000, + "u_turn_time": 1.75, + "cascade_transit_speed_with_payload": 295, + "pump_rate_max": 350, + "pump_rate_min": 150, + "cascade_transit_speed_max_with_payload": 330, + "name": "Lockheed Electra L-188", + "dispersant_load": 30, + "cascade_transit_speed_min_with_payload": 295, + "swath_width_min": 100, + "departure": 1, + "taxi_land_depart": 15, + "transit_speed_max": 250, + "taxi_time_takeoff": 15, + "transit_speed_min": 150 + }, + { + "swath_width_max": 150, + "max_op_time": 4, + "application_speed_max": 200, + "FIELD11": "X", + "payload": 4000, + "fuel_load": 21, + "reposition_speed": 150, + "taxi_time_landing": 15, + "max_range_no_payload": 2700, + "application_speed_min": 130, + "swath_width": 150, + "cascade_transit_speed_max_without_payload": 225, + "cascade_transit_speed_without_payload": 225, + "application_speed": 150, + "approach": 1, + "cascade_transit_speed_min_without_payload": 225, + "staging_area_brief": 45, + "transit_speed": 250, + "max_range_with_payload": 1000, + "u_turn_time": 1.75, + "cascade_transit_speed_with_payload": 250, + "pump_rate_max": 600, + "pump_rate_min": 50, + "cascade_transit_speed_max_with_payload": 250, + "name": "Lockheed Orion P-3A", + "dispersant_load": 27, + "cascade_transit_speed_min_with_payload": 250, + "swath_width_min": 100, + "departure": 1, + "taxi_land_depart": 15, + "transit_speed_max": 350, + "taxi_time_takeoff": 15, + "transit_speed_min": 250 + }, + { + "swath_width_max": 70, + "max_op_time": 5.7, + "application_speed_max": 200, + "FIELD11": "x", + "payload": 2000, + "fuel_load": 50, + "reposition_speed": 200, + "taxi_time_landing": 15, + "max_range_no_payload": 2380, + "application_speed_min": 170, + "swath_width": 65, + "cascade_transit_speed_max_without_payload": 330, + "cascade_transit_speed_without_payload": 290, + "application_speed": 200, + "approach": 1, + "cascade_transit_speed_min_without_payload": 280, + "staging_area_brief": 45, + "transit_speed": 290, + "max_range_with_payload": 1650, + "u_turn_time": 1.75, + "cascade_transit_speed_with_payload": 290, + "pump_rate_max": 450, + "pump_rate_min": 50, + "cascade_transit_speed_max_with_payload": 330, + "name": "C-130 H with MASS", + "dispersant_load": 30, + "cascade_transit_speed_min_with_payload": 250, + "swath_width_min": 50, + "departure": 1, + "taxi_land_depart": 15, + "transit_speed_max": 300, + "taxi_time_takeoff": 15, + "transit_speed_min": 250 + }, + { + "swath_width_max": 150, + "max_op_time": 4.7, + "application_speed_max": 200, + "FIELD11": "x", + "payload": 3250, + "fuel_load": 20, + "reposition_speed": 150, + "taxi_time_landing": 15, + "max_range_no_payload": 2086, + "application_speed_min": 150, + "swath_width": 150, + "cascade_transit_speed_max_without_payload": 370, + "cascade_transit_speed_without_payload": 298, + "application_speed": 150, + "approach": 1, + "cascade_transit_speed_min_without_payload": 250, + "staging_area_brief": 45, + "transit_speed": 298, + "max_range_with_payload": 1400, + "u_turn_time": 1.67, + "cascade_transit_speed_with_payload": 298, + "pump_rate_max": 523, + "pump_rate_min": 60, + "cascade_transit_speed_max_with_payload": 298, + "name": "C-130 A internal tank", + "dispersant_load": 20, + "cascade_transit_speed_min_with_payload": 230, + "swath_width_min": 100, + "departure": 1, + "taxi_land_depart": 15, + "transit_speed_max": 298, + "taxi_time_takeoff": 15, + "transit_speed_min": 230 + }, + { + "swath_width_max": 125, + "max_op_time": 4, + "application_speed_max": 150, + "FIELD11": "x", + "payload": 3170, + "fuel_load": 30, + "reposition_speed": 150, + "taxi_time_landing": 15, + "max_range_no_payload": 2700, + "application_speed_min": 140, + "swath_width": 100, + "cascade_transit_speed_max_without_payload": 300, + "cascade_transit_speed_without_payload": 300, + "application_speed": 145, + "approach": 1, + "cascade_transit_speed_min_without_payload": 275, + "staging_area_brief": 45, + "transit_speed": 250, + "max_range_with_payload": 1200, + "u_turn_time": 1.75, + "cascade_transit_speed_with_payload": 300, + "pump_rate_max": 612, + "pump_rate_min": 51, + "cascade_transit_speed_max_with_payload": 300, + "name": "C-130 Hercules L-382 B, E, & G NIMBUS", + "dispersant_load": 45, + "cascade_transit_speed_min_with_payload": 275, + "swath_width_min": 75, + "departure": 1, + "taxi_land_depart": 15, + "transit_speed_max": 300, + "taxi_time_takeoff": 15, + "transit_speed_min": 250 + }, + { + "swath_width_max": 200, + "max_op_time": 4, + "application_speed_max": 150, + "FIELD11": "x", + "payload": 5000, + "fuel_load": 30, + "reposition_speed": 150, + "taxi_time_landing": 15, + "max_range_no_payload": 2700, + "application_speed_min": 140, + "swath_width": 150, + "cascade_transit_speed_max_without_payload": 300, + "cascade_transit_speed_without_payload": 300, + "application_speed": 145, + "approach": 1, + "cascade_transit_speed_min_without_payload": 275, + "staging_area_brief": 45, + "transit_speed": 250, + "max_range_with_payload": 1200, + "u_turn_time": 1.75, + "cascade_transit_speed_with_payload": 300, + "pump_rate_max": 612, + "pump_rate_min": 51, + "cascade_transit_speed_max_with_payload": 300, + "name": "C-130 Hercules L-382 B, E, & G ADDS", + "dispersant_load": 45, + "cascade_transit_speed_min_with_payload": 275, + "swath_width_min": 100, + "departure": 1, + "taxi_land_depart": 15, + "transit_speed_max": 300, + "taxi_time_takeoff": 15, + "transit_speed_min": 250 + }, + { + "swath_width_max": 120, + "max_op_time": 6.2, + "application_speed_max": 160, + "FIELD11": "X", + "payload": 1200, + "fuel_load": 13, + "reposition_speed": 130, + "taxi_time_landing": 15, + "max_range_no_payload": 1066, + "application_speed_min": 120, + "swath_width": 120, + "cascade_transit_speed_max_without_payload": 130, + "cascade_transit_speed_without_payload": 130, + "application_speed": 130, + "approach": 1, + "cascade_transit_speed_min_without_payload": 130, + "staging_area_brief": 45, + "transit_speed": 130, + "max_range_with_payload": 806, + "u_turn_time": 1, + "cascade_transit_speed_with_payload": 130, + "pump_rate_max": 600, + "pump_rate_min": 40, + "cascade_transit_speed_max_with_payload": 130, + "name": "Douglas DC-3", + "dispersant_load": 15, + "cascade_transit_speed_min_with_payload": 130, + "swath_width_min": 70, + "departure": 1, + "taxi_land_depart": 15, + "transit_speed_max": 130, + "taxi_time_takeoff": 15, + "transit_speed_min": 130 + }, + { + "swath_width_max": 150, + "max_op_time": 9, + "application_speed_max": 140, + "FIELD11": "x", + "payload": 2000, + "fuel_load": 15, + "reposition_speed": 150, + "taxi_time_landing": 7.5, + "max_range_no_payload": 2400, + "application_speed_min": 140, + "swath_width": 150, + "cascade_transit_speed_max_without_payload": 220, + "cascade_transit_speed_without_payload": 220, + "application_speed": 140, + "approach": 1, + "cascade_transit_speed_min_without_payload": 210, + "staging_area_brief": 45, + "transit_speed": 220, + "max_range_with_payload": 1800, + "u_turn_time": 1, + "cascade_transit_speed_with_payload": 220, + "pump_rate_max": 600, + "pump_rate_min": 40, + "cascade_transit_speed_max_with_payload": 220, + "name": "Douglas DC-3 BT-67", + "dispersant_load": 15, + "cascade_transit_speed_min_with_payload": 210, + "swath_width_min": 150, + "departure": 1, + "taxi_land_depart": 15, + "transit_speed_max": 220, + "taxi_time_takeoff": 7.5, + "transit_speed_min": 200 + }, + { + "swath_width_max": 150, + "max_op_time": 8.2, + "application_speed_max": 160, + "FIELD11": "x", + "payload": 2000, + "fuel_load": 16, + "reposition_speed": 150, + "taxi_time_landing": 15, + "max_range_no_payload": 1725, + "application_speed_min": 120, + "swath_width": 150, + "cascade_transit_speed_max_without_payload": 150, + "cascade_transit_speed_without_payload": 150, + "application_speed": 150, + "approach": 1, + "cascade_transit_speed_min_without_payload": 150, + "staging_area_brief": 45, + "transit_speed": 150, + "max_range_with_payload": 1230, + "u_turn_time": 1, + "cascade_transit_speed_with_payload": 150, + "pump_rate_max": 600, + "pump_rate_min": 40, + "cascade_transit_speed_max_with_payload": 150, + "name": "Douglas DC-4", + "dispersant_load": 20, + "cascade_transit_speed_min_with_payload": 150, + "swath_width_min": 75, + "departure": 1, + "taxi_land_depart": 15, + "transit_speed_max": 150, + "taxi_time_takeoff": 15, + "transit_speed_min": 150 + }, + { + "swath_width_max": 150, + "max_op_time": 12, + "application_speed_max": 145, + "FIELD11": "x", + "payload": 3320, + "fuel_load": 30, + "reposition_speed": 170, + "taxi_time_landing": 15, + "max_range_no_payload": 1725, + "application_speed_min": 120, + "swath_width": 150, + "cascade_transit_speed_max_without_payload": 237, + "cascade_transit_speed_without_payload": 237, + "application_speed": 130, + "approach": 1, + "cascade_transit_speed_min_without_payload": 237, + "staging_area_brief": 45, + "transit_speed": 237, + "max_range_with_payload": 1230, + "u_turn_time": 1, + "cascade_transit_speed_with_payload": 237, + "pump_rate_max": 375, + "pump_rate_min": 40, + "cascade_transit_speed_max_with_payload": 237, + "name": "Douglas DC-6", + "dispersant_load": 30, + "cascade_transit_speed_min_with_payload": 237, + "swath_width_min": 150, + "departure": 1, + "taxi_land_depart": 15, + "transit_speed_max": 256, + "taxi_time_takeoff": 15, + "transit_speed_min": 237 + }, + { + "swath_width_max": 90, + "max_op_time": 2.4, + "application_speed_max": 160, + "FIELD11": "X", + "payload": 250, + "fuel_load": 5, + "reposition_speed": 185, + "taxi_time_landing": 3, + "max_range_no_payload": 1900, + "application_speed_min": 130, + "swath_width": 65, + "cascade_transit_speed_max_without_payload": 200, + "cascade_transit_speed_without_payload": 185, + "application_speed": 140, + "approach": 0.5, + "cascade_transit_speed_min_without_payload": 150, + "staging_area_brief": 45, + "transit_speed": 185, + "max_range_with_payload": 450, + "u_turn_time": 1, + "cascade_transit_speed_with_payload": 185, + "pump_rate_max": 260, + "pump_rate_min": 15, + "cascade_transit_speed_max_with_payload": 185, + "name": "Cessna C208 Grand Caravan", + "dispersant_load": 5, + "cascade_transit_speed_min_with_payload": 150, + "swath_width_min": 50, + "departure": 0.3, + "taxi_land_depart": 3, + "transit_speed_max": 220, + "taxi_time_takeoff": 3, + "transit_speed_min": 185 + }, + { + "swath_width_max": 85, + "max_op_time": 1.2, + "application_speed_max": 175, + "FIELD11": "X", + "payload": 425, + "fuel_load": 12, + "reposition_speed": 150, + "taxi_time_landing": 3, + "max_range_no_payload": 796, + "application_speed_min": 130, + "swath_width": 75, + "cascade_transit_speed_max_without_payload": 200, + "cascade_transit_speed_without_payload": 185, + "application_speed": 150, + "approach": 0.75, + "cascade_transit_speed_min_without_payload": 160, + "staging_area_brief": 45, + "transit_speed": 185, + "max_range_with_payload": 222, + "u_turn_time": 1, + "cascade_transit_speed_with_payload": 185, + "pump_rate_max": 180, + "pump_rate_min": 20, + "cascade_transit_speed_max_with_payload": 200, + "name": "Beechcraft King Air 90A (BE-90A)", + "dispersant_load": 10, + "cascade_transit_speed_min_with_payload": 160, + "swath_width_min": 60, + "departure": 0.75, + "taxi_land_depart": 3, + "transit_speed_max": 200, + "taxi_time_takeoff": 3, + "transit_speed_min": 150 + }, + { + "swath_width_max": 70, + "max_op_time": 1.1, + "application_speed_max": 160, + "FIELD11": "x", + "payload": 500, + "fuel_load": 5, + "reposition_speed": 140, + "taxi_time_landing": 3, + "max_range_no_payload": 680, + "application_speed_min": 140, + "swath_width": 60, + "cascade_transit_speed_max_without_payload": 170, + "cascade_transit_speed_without_payload": 170, + "application_speed": 140, + "approach": 0.5, + "cascade_transit_speed_min_without_payload": 170, + "staging_area_brief": 45, + "transit_speed": 165, + "max_range_with_payload": 181, + "u_turn_time": 1, + "cascade_transit_speed_with_payload": 165, + "pump_rate_max": 260, + "pump_rate_min": 15, + "cascade_transit_speed_max_with_payload": 165, + "name": "CASA C-212-200 AVIOCAR", + "dispersant_load": 5, + "cascade_transit_speed_min_with_payload": 165, + "swath_width_min": 40, + "departure": 0.5, + "taxi_land_depart": 3, + "transit_speed_max": 200, + "taxi_time_takeoff": 3, + "transit_speed_min": 140 + }, + { + "swath_width_max": 80, + "max_op_time": 3.9, + "application_speed_max": 150, + "FIELD11": "x", + "payload": 440, + "fuel_load": 10, + "reposition_speed": 160, + "taxi_time_landing": 10, + "max_range_no_payload": 1060, + "application_speed_min": 120, + "swath_width": 60, + "cascade_transit_speed_max_without_payload": 225, + "cascade_transit_speed_without_payload": 200, + "application_speed": 120, + "approach": 1, + "cascade_transit_speed_min_without_payload": 200, + "staging_area_brief": 45, + "transit_speed": 200, + "max_range_with_payload": 780, + "u_turn_time": 1, + "cascade_transit_speed_with_payload": 200, + "pump_rate_max": 300, + "pump_rate_min": 40, + "cascade_transit_speed_max_with_payload": 225, + "name": "EMBRAER EMB 110 Bandierante", + "dispersant_load": 15, + "cascade_transit_speed_min_with_payload": 200, + "swath_width_min": 50, + "departure": 1, + "taxi_land_depart": 10, + "transit_speed_max": 220, + "taxi_time_takeoff": 10, + "transit_speed_min": 200 + }, + { + "swath_width_max": 70, + "max_op_time": 1.7, + "application_speed_max": 90, + "FIELD11": "x", + "payload": 240, + "fuel_load": 5, + "reposition_speed": 50, + "taxi_time_landing": 1, + "max_range_no_payload": 238, + "application_speed_min": 30, + "swath_width": 65, + "cascade_transit_speed_max_without_payload": 140, + "cascade_transit_speed_without_payload": 140, + "application_speed": 50, + "approach": 0.5, + "cascade_transit_speed_min_without_payload": 140, + "staging_area_brief": 45, + "transit_speed": 90, + "max_range_with_payload": 153, + "u_turn_time": 0.75, + "cascade_transit_speed_with_payload": 90, + "pump_rate_max": 50, + "pump_rate_min": 30, + "cascade_transit_speed_max_with_payload": 110, + "name": "Bell 407 with Simplex 6810", + "dispersant_load": 10, + "cascade_transit_speed_min_with_payload": 90, + "swath_width_min": 60, + "departure": 0.5, + "taxi_land_depart": 1, + "transit_speed_max": 90, + "taxi_time_takeoff": 1, + "transit_speed_min": 90 + }, + { + "swath_width_max": 70, + "max_op_time": 2, + "application_speed_max": 90, + "FIELD11": "x", + "payload": 240, + "fuel_load": 5, + "reposition_speed": 50, + "taxi_time_landing": 1, + "max_range_no_payload": 225, + "application_speed_min": 30, + "swath_width": 65, + "cascade_transit_speed_max_without_payload": 90, + "cascade_transit_speed_without_payload": 90, + "application_speed": 50, + "approach": 0.5, + "cascade_transit_speed_min_without_payload": 90, + "staging_area_brief": 45, + "transit_speed": 90, + "max_range_with_payload": 180, + "u_turn_time": 0.75, + "cascade_transit_speed_with_payload": 90, + "pump_rate_max": 50, + "pump_rate_min": 30, + "cascade_transit_speed_max_with_payload": 110, + "name": "Bell UH-1H with Simplex 6810", + "dispersant_load": 10, + "cascade_transit_speed_min_with_payload": 90, + "swath_width_min": 60, + "departure": 0.5, + "taxi_land_depart": 1, + "transit_speed_max": 90, + "taxi_time_takeoff": 1, + "transit_speed_min": 90 + } + ] +} \ No newline at end of file diff --git a/py_gnome/gnome/weatherers/roc.py b/py_gnome/gnome/weatherers/roc.py new file mode 100644 index 000000000..96dab5f4c --- /dev/null +++ b/py_gnome/gnome/weatherers/roc.py @@ -0,0 +1,1805 @@ +''' +oil removal from various cleanup options +add these as weatherers +''' +from __future__ import division + +import pytest +import datetime +import copy +import unit_conversion as uc +import json +import os +import logging +import numpy as np +import math +from collections import OrderedDict + +from colander import (drop, SchemaNode, MappingSchema, Integer, Float, String, OneOf, Mapping, SequenceSchema, TupleSchema, DateTime) + +from gnome.weatherers import Weatherer +from gnome.utilities.serializable import Serializable, Field +from gnome.persist.extend_colander import LocalDateTime, DefaultTupleSchema, NumpyArray, TimeDelta +from gnome.persist import validators, base_schema + +from gnome.weatherers.core import WeathererSchema +from gnome import _valid_units +from gnome.basic_types import oil_status, fate as bt_fate + +from gnome.array_types import (mass, + density, + fay_area, + frac_water) + +# define valid units at module scope because the Schema and Object both use it +_valid_dist_units = _valid_units('Length') +_valid_vel_units = _valid_units('Velocity') +_valid_vol_units = _valid_units('Volume') +_valid_dis_units = _valid_units('Discharge') +_valid_time_units = _valid_units('Time') +_valid_oil_concentration_units = _valid_units('Oil Concentration') +_valid_concentration_units = _valid_units('Concentration In Water') + + +class OnSceneTupleSchema(TupleSchema): + start = SchemaNode(DateTime(default_tzinfo=None)) + end = SchemaNode(DateTime(default_tzinfo=None)) + +class OnSceneTimeSeriesSchema(SequenceSchema): + value = OnSceneTupleSchema() + +# def validator(self, node, cstruct): +# ''' +# validate on-scene timeseries list +# ''' +# validators.no_duplicate_datetime(node, cstruct) +# validators.ascending_datetime(node, cstruct) + +class ResponseSchema(WeathererSchema): + timeseries = OnSceneTimeSeriesSchema() + +class Response(Weatherer, Serializable): + + _schema = ResponseSchema + _state = copy.deepcopy(Weatherer._state) +# _state += [Field('timeseries', update=True, save=True)] + _oc_list = ['timeseries'] + + _schema = ResponseSchema + + _state = copy.deepcopy(Weatherer._state) + + _state += [Field('timeseries', save=True, update=True)] + + def __init__(self, + timeseries=None, + **kwargs): + super(Response, self).__init__(**kwargs) + self.timeseries = timeseries + self._report = [] + + def _get_thickness(self, sc): + oil_thickness = 0.0 + substance = self._get_substance(sc) + if sc['area'].any() > 0: + volume_emul = (sc['mass'].mean() / substance.density_at_temp()) / (1.0 - sc['frac_water'].mean()) + oil_thickness = volume_emul / sc['area'].mean() + + return uc.convert('Length', 'meters', 'inches', oil_thickness) + + @property + def units(self): + return self._units + + @units.setter + def units(self, u_dict): + for prop, unit in u_dict.iteritems(): + if prop in self._units_type: + if unit not in self._units_type[prop][1]: + msg = ("{0} are invalid units for {1}." + "Ignore it".format(unit, prop)) + self.logger.error(msg) + raise uc.InvalidUnitError(msg) + + self._units[prop] = unit + + def get(self, attr, unit=None): + val = getattr(self, attr) + if unit is None: + if (attr not in self._si_units or + self._si_units[attr] == self.units[attr]): + return val + else: + unit = self._si_units[attr] + + if unit in self._units_type[attr][1]: + return uc.convert(self._units_type[attr][0], self.units[attr], + unit, val) + else: + ex = uc.InvalidUnitError((unit, self._units_type[attr][0])) + self.logger.error(str(ex)) + raise ex + + def set(self, attr, value, unit): + if unit not in self._units_type[attr][1]: + raise uc.InvalidUnitError((unit, self._units_type[attr][1])) + + setattr(self, attr, value) + self.units[attr] = unit + + def _is_active(self, model_time, time_step): + for t in self.timeseries: + if model_time >= t[0] and model_time + datetime.timedelta(seconds=time_step / 2) <= t[1]: + return True + + return False + + def _setup_report(self, sc): + if 'report' not in sc: + sc.report = {} + + sc.report[self.id] = [] + self.report = sc.report[self.id] + + def _get_substance(self, sc): + ''' + return a single substance - recovery options only know about the + total amount removed. Unclear how to assign this to multiple substances + for now, just log an error if more than one substance is present + ''' + substance = sc.get_substances(complete=False) + if len(substance) > 1: + self.logger.error('Found more than one type of oil ' + '- not supported. Results with be incorrect') + + return substance[0] + + def _remove_mass_simple(self, data, amount): + total_mass = data['mass'].sum() + rm_mass_frac = min(amount / total_mass, 1.0) + data['mass_components'] = \ + (1 - rm_mass_frac) * data['mass_components'] + data['mass'] = data['mass_components'].sum(1) + + def _remove_mass_indices(self, data, amounts, indices): + #removes mass from the mass components specified by an indices array + masses = data['mass'][indices] + rm_mass_frac = np.clip(amounts / masses, 0, 1) + old_mass = data['mass_components'][indices].sum(1) + data['mass_components'][indices] = (1 - rm_mass_frac)[:, np.newaxis] * data['mass_components'][indices] + new_mass = data['mass_components'][indices].sum(1) + data['mass'][indices] = data['mass_components'][indices].sum(1) + return old_mass - new_mass + + def index_of(self, time): + ''' + Returns the index of the timeseries entry that the time specified is within. + If it is not in one of the intervals, -1 will be returned + ''' + for i, t in enumerate(self.timeseries): + if time >= t[0] and time < t[-1]: + return i + return -1 + + def next_interval_index(self, time): + ''' + returns the index of the next interval, even if outside interval. + returns None if there is no next interval + ''' + if time >= self.timeseries[-1][-1]: + #off end + return None + if time < self.timeseries[0][0]: + #before start + return 0 + idx = self.index_of(time) + if idx > -1: + #inside valid interval + return idx + 1 if idx + 1 != len(self.timeseries) else None + if idx == -1: + #outside timeseries intervals + for i, t in enumerate(self.timeseries[0:-1]): + if time >= self.timeseries[i][-1] and time < self.timeseries[i+1][0]: + return i+1 + + def time_to_next_interval(self, time): + ''' + if within an interval, returns time left in the interval. + if between intervals, returns time until start of next interval + if past end, or response deactivated, return None + ''' + cur_idx = self.index_of(time) + if cur_idx == -1: + next_idx = self.next_interval_index(time) + if next_idx is None: + return None + else: + return self.timeseries[next_idx][0] - time + else: + return self.timeseries[cur_idx][-1] - time + + def is_operating(self, time): + return self.index_of(time) > -1 + +# def serialize(self, json_="webapi"): +# serial = super(Response, self).serialize(json_) +# if self.timeseries is not None: +# serial['timeseries'] = [] +# for v in self.timeseries: +# serial['timeseries'].append([v[0].isoformat(), v[1].isoformat()]) +# return serial +# +# @classmethod +# def deserialize(cls, json): +# schema = cls._schema() +# deserial = schema.deserialize(json) +# if 'timeseries' in json: +# deserial['timeseries'] = [] +# for v in json['timeseries']: +# deserial['timeseries'].append( +# (datetime.datetime.strptime(v[0], '%Y-%m-%dT%H:%M:%S'), +# datetime.datetime.strptime(v[1], '%Y-%m-%dT%H:%M:%S'))) + +# return deserial + + def _no_op_step(self): + self._time_remaining = 0; + +class PlatformUnitsSchema(MappingSchema): + def __init__(self, *args, **kwargs): + for k, v in Platform._attr.items(): + self.add(SchemaNode(String(), missing=drop, name=k, validator=OneOf(v[2]))) + super(PlatformUnitsSchema, self).__init__() + + +class PlatformSchema(base_schema.ObjType): + + def __init__(self, *args, **kwargs): + for k in Platform._attr.keys(): + self.add(SchemaNode(Float(), missing=drop, name=k)) + units = PlatformUnitsSchema() + units.missing = drop + units.name = 'units' + self.add(units) + super(PlatformSchema, self).__init__() + + +class Platform(Serializable): + + _attr = {"swath_width_max": ('ft', 'length', _valid_dist_units), + "swath_width": ('ft', 'length', _valid_dist_units), + "swath_width_min": ('ft', 'length', _valid_dist_units), + "reposition_speed": ('kts', 'velocity', _valid_vel_units), #non-boat + "application_speed_min": ('kts', 'velocity', _valid_vel_units), + "application_speed": ('kts', 'velocity', _valid_vel_units), + "application_speed_max": ('kts', 'velocity', _valid_vel_units), + "cascade_transit_speed_max_without_payload": ('kts', 'velocity', _valid_vel_units), #non-boat + "cascade_transit_speed_without_payload": ('kts', 'velocity', _valid_vel_units), #non-boat + "cascade_transit_speed_min_without_payload": ('kts', 'velocity', _valid_vel_units), #non-boat + "cascade_transit_speed_with_payload": ('kts', 'velocity', _valid_vel_units), #non-boat + "cascade_transit_speed_max_with_payload": ('kts', 'velocity', _valid_vel_units), #non-boat + "cascade_transit_speed_min_with_payload": ('kts', 'velocity', _valid_vel_units), #non-boat + "transit_speed_max": ('kts', 'velocity', _valid_vel_units), + "transit_speed_min": ('kts', 'velocity', _valid_vel_units), + "transit_speed": ('kts', 'velocity', _valid_vel_units), + "fuel_load": ('min', 'time', _valid_time_units), + "taxi_time_landing": ('min', 'time', _valid_time_units), + "staging_area_brief": ('min', 'time', _valid_time_units), + "dispersant_load": ('min', 'time', _valid_time_units), + "taxi_land_depart": ('min', 'time', _valid_time_units), + "taxi_time_takeoff": ('min', 'time', _valid_time_units), + "u_turn_time": ('min', 'time', _valid_time_units), + "max_op_time": ('hr', 'time', _valid_time_units), + "max_range_no_payload": ('nm', 'length', _valid_dist_units), + "max_range_with_payload": ('nm', 'length', _valid_dist_units), + "approach": ('nm', 'length', _valid_dist_units), + "departure": ('nm', 'length', _valid_dist_units), + "payload": ('gal', 'volume', _valid_vol_units), + "pump_rate_max": ('gal/min', 'discharge', _valid_dis_units), + "pump_rate_min": ('gal/min', 'discharge', _valid_dis_units)} + + _si_units = dict([(k, v[0]) for k, v in _attr.items()]) + + _units_type = dict([(k, (v[1], v[2])) for k, v in _attr.items()]) + + base_dir = os.path.dirname(__file__) + with open(os.path.join(base_dir, 'platforms.json'), 'r') as f: + js = json.load(f) + plat_types = dict(zip([t['name'] for t in js['vessel']], js['vessel'])) + plat_types.update(dict(zip([t['name'] for t in js['aircraft']], js['aircraft']))) + + _schema = PlatformSchema + + _state = copy.deepcopy(Serializable._state) + + _state += [Field(k, save=True, update=True) for k in _attr.keys()] + _state += [Field('units', save=True, update=True)] + + def __init__(self, + units=None, + **kwargs): + + if '_name' in kwargs.keys(): + kwargs = self.plat_types[kwargs.pop('_name')] + if units is None: + units = dict([(k, v[0]) for k, v in self._attr.items()]) + self.units = units + for k in Platform._attr.keys(): + setattr(self, k, kwargs.get(k, None)) + + self.disp_remaining = 0 + self.cur_pump_rate = 0 + if self.approach is None or self.departure is None: + self.is_boat = True + else: + self.is_boat = False + + super(Platform, self).__init__() + + def get(self, attr, unit=None): + val = getattr(self, attr) + if unit is None: + if (attr not in self._si_units or + self._si_units[attr] == self.units[attr]): + return val + else: + unit = self._si_units[attr] + + if unit in self._units_type[attr][1]: + return uc.convert(self._units_type[attr][0], self.units[attr], + unit, val) + else: + ex = uc.InvalidUnitError((unit, self._units_type[attr][0])) + self.logger.error(str(ex)) + raise ex + + def set(self, attr, value, unit): + if unit not in self._units_type[attr][0]: + raise uc.InvalidUnitError((unit, self._units_type[attr][0])) + + setattr(self, attr, value) + self.units[attr] = unit + + def release_rate(self, dosage, unit='gal/acre'): + '''return unit = gal/min''' + if unit != 'gal/acre': + dosage = uc.Convert('oilconcentration', 'unit', 'gal/acre', dosage) + a_s = self.get('application_speed', 'ft/min') + s_w = self.get('swadth_width', 'ft') + + return uc.convert('area', 'ft^2', 'acre', (dosage * a_s * s_w)) + + @classmethod + def new_from_dict(cls, dict_): + ''' + Need to override this, because what the default one does is insane + ''' + return cls(**dict_) + + def one_way_transit_time(self, dist, unit='nm', payload=False): + '''return unit = sec''' + t_s = self.get('transit_speed', 'kts') + t_l_d = self.get('taxi_land_depart', 'sec') if self.taxi_land_depart is not None else None + raw = dist / t_s * 3600 + if t_l_d is not None: + raw += t_l_d + return raw + + def max_dosage(self): + '''return unit = gal/acre''' + p_r_m = self.get('pump_rate_max', 'm^3/s') + a_s = self.get('application_speed', 'm/s') + s_w_m = self.get('swath_width_min', 'm') + dos = (p_r_m) / (a_s * s_w_m) + dos = uc.convert('length', 'm', 'micron', dos) + dos = uc.convert('oilconcentration', 'micron', 'gal/acre', dos) + return dos + + def min_dosage(self): + '''return unit = gal/acre''' + p_r_m = self.get('pump_rate_min', 'm^3/s') + a_s = self.get('application_speed', 'm/s') + s_w_m = self.get('swath_width_max', 'm') + dos = (p_r_m) / (a_s * s_w_m) + dos = uc.convert('length', 'm', 'micron', dos) + dos = uc.convert('oilconcentration', 'micron', 'gal/acre', dos) + return dos + + def cascade_time(self, dist, unit='nm', payload=False): + '''return unit = sec''' + dist = dist if unit == 'nm' else uc.convert('length', unit, 'nm', dist) + max_range = self.get('max_rage_with_payload', 'nm') if payload else self.get('max_range_no_payload', 'nm') + speed = self.get('cascade_transit_speed_with_payload', 'kts') if payload else self.get('cascade_transit_speed_without_payload', 'kts') + taxi_land_depart = self.get('taxi_land_depart', 'hr') + fuel_load = self.get('refuel', 'hr') + + cascade_time = 0 + if dist > max_range: + num_legs = dist / max_range + frac_leg = (num_legs * 1000) % 1000 + num_legs = int(num_legs) + cascade_time += taxi_land_depart + cascade_time += (num_legs * max_range) + inter_stop = (taxi_land_depart * 2 + fuel_load) + cascade_time += num_legs * inter_stop + cascade_time += frac_leg * (max_range / speed) + cascade_time += taxi_land_depart + else: + cascade_time += taxi_land_depart * 2 + cascade_time += dist / speed + return cascade_time * 3600 + + def max_onsite_time(self, dist, simul=False): + ''' + return time in sec + ''' + m_o_t = self.get('max_op_time', 'sec') + o_w_t_t = self.one_way_transit_time(dist) + r_r = self.refuel_reload(simul=simul) + rv = m_o_t - o_w_t_t * 2 - r_r +# if rv < 0: +# logging.warn('max onsite time is less than zero') +# else: +# pld = self.get('payload', 'gal') +# m_p_r = self.get('max_pump_rate', 'gal/hr') +# if rv < (pld / m_p_r): +# logging.warn("max onsite time is less than possible time to finsish spraying") + return rv + + def num_passes_possible(self, time, pass_len, pass_type): + ''' + In a given time (sec) compute maximum number of complete passes before + needing to return to base. + + A pass consists of an approach, spray, u-turn, and reposition. + ''' + +# rep = self.get('reposition_speed', 'm/s') + + return int(time.total_seconds() / int(self.pass_duration(pass_len, pass_type))) + + def refuel_reload(self, simul=False): + '''return unit = sec''' + rl = self.get('dispersant_load', 'sec') + rf = self.get('fuel_load', 'sec') + return max(rl, rf) if simul else rf + rl + + def pass_duration(self, pass_len, pass_type, units='nm'): + ''' + pass_len in nm + return in sec + ''' + times = self.pass_duration_tuple(pass_len, pass_type, units='nm') + if pass_type == 'bidirectional': + return sum(times) + else: + return sum(times) + + def pass_duration_tuple(self, pass_len, pass_type, units='nm'): + appr_dist = self.get('approach', 'm') if self.approach is not None else 0 + dep_dist = self.get('departure', 'm') if self.departure is not None else 0 + rep_speed = self.get('reposition_speed', 'm/s') if self.reposition_speed is not None else 1 + appr_time = appr_dist / rep_speed + dep_time = dep_dist / rep_speed + u_turn = self.get('u_turn_time', 'sec') if self.u_turn_time is not None else 0 + + pass_len = uc.convert('length', units, 'm', pass_len) + app_speed = self.get('application_speed', 'm/s') + spray_time = pass_len / app_speed + if pass_type == 'bidirectional': + return (appr_time, spray_time, u_turn, spray_time, dep_time) + else: + return (appr_time, spray_time, u_turn, dep_time) + + def sortie_possible(self, time_avail, transit, pass_len): + # assume already refueled/reloaded + # possible if able to complete transit, at least one pass, and transit back within time available + min_spray_time = self.pass_duration(pass_len, 'bidirectional') + tot_mission_time = self.one_way_transit_time(transit) * 2 + min_spray_time + return time_avail > datetime.timedelta(seconds=tot_mission_time) + + def eff_pump_rate(self, dosage, unit='gal/acre'): + ''' + given a dosage, determine the pump rate necessary given the airspeed and area covered in a pass + return value = m^3/s + ''' + dosage = uc.convert('oilconcentration', unit, 'micron', dosage) + dosage = uc.convert('length', 'micron', 'm', dosage) + app_speed = self.get('application_speed', 'm/s') + swath_width = self.get('swath_width', 'm') + eff_pr = dosage * app_speed * swath_width + max_pr = self.get('pump_rate_max', 'm^3/s') + min_pr = self.get('pump_rate_min', 'm^3/s') + if eff_pr > max_pr: + #log warning? + print 'computed pump rate is too high for this platform. using max instead' + return max_pr + elif eff_pr < min_pr: + print 'computed pump rate is too low for this platform. using min instead' + return min_pr + else: + return eff_pr + + def spray_time_fraction(self, pass_len, pass_type, units='nm'): + pass_len = uc.convert('length', units, 'm', pass_len) + app_speed = self.get('application_speed', 'm/s') + pass_dur = self.pass_duration(pass_len, pass_type, units) + spray_time = pass_len / app_speed + if pass_type == 'bidirectional': + return (spray_time * 2) / pass_dur + else: + return (spray_time) / pass_dur + + +class DisperseUnitsSchema(MappingSchema): + def __init__(self, *args, **kwargs): + for k, v in Disperse._attr.items(): + self.add(SchemaNode(String(), missing=drop, name=k, validator=OneOf(v[2]))) + super(DisperseUnitsSchema, self).__init__() + + +class DisperseSchema(ResponseSchema): + loading_type = SchemaNode(String(), validator=OneOf(['simultaneous', 'separate'])) + dosage_type = SchemaNode(String(), missing=drop, validator=OneOf(['auto', 'custom'])) + disp_oil_ratio = SchemaNode(Float(), missing=drop) + disp_eff = SchemaNode(Float(), missing=drop) + platform = PlatformSchema() + + def __init__(self, *args, **kwargs): + for k, v in Disperse._attr.items(): + self.add(SchemaNode(Float(), missing=drop, name=k)) + units = DisperseUnitsSchema() + units.missing = drop + units.name = 'units' + self.add(units) + super(DisperseSchema, self).__init__() + +class Disperse(Response): + + _attr = {'transit': ('nm', 'length', _valid_dist_units), + 'pass_length': ('nm', 'length', _valid_dist_units), + 'cascade_distance': ('nm', 'length', _valid_dist_units), + 'dosage': ('gal/acre', 'oilconcentration', _valid_oil_concentration_units)} + + _si_units = dict([(k, v[0]) for k, v in _attr.items()]) + + _units_type = dict([(k, (v[1], v[2])) for k, v in _attr.items()]) + + _schema = DisperseSchema + + _state = copy.deepcopy(Response._state) + +# _state += [Field(k, save=True, update=True) for k in _attr.keys()] + _state += [Field('units', save=True, update=True), + Field('disp_oil_ratio', save=True, update=True), + Field('disp_eff', save=True, update=True), + Field('platform', save=True, update=True), + Field('dosage_type', save=True, update=True), + Field('loading_type', save=True, update=True), + Field('report', save=False, update=False), + Field('wind', save=True, update=True, save_reference=True)] + + wind_eff_list = [15, 30, 45, 60, 70, 78, 80, 82, 83, 84, 84, 84, 84, 84, 83, 83, 82, 80, 79, 78, 77, 75, 73, 71, 69, 67, 65, 63, 60, 58, 55, 53, 50, 47, 44, 41, 38] + visc_eff_table = OrderedDict([(1, 68), (2, 71), (3, 72.5), (4, 74), (5, 75), (7, 77), (10, 78), (20, 80), (40, 83.5), (70, 85.5), (100, 87), (300, 89.5), (500, 90.5), (700, 91), (1000, 92), (2000, 91), (3000, 83), (5000, 52), (7000, 32), (10000, 17), (20000, 11), (30000, 8.5), (40000, 7), (50000, 6.5), (100000, 6), (1000000, 0)]) + + def __init__(self, + name=None, + transit=None, + pass_length=4, + dosage=None, + dosage_type='auto', + cascade_on=False, + cascade_distance=None, + loading_type='simultaneous', + pass_type='bidirectional', + disp_oil_ratio=None, + disp_eff=None, + platform=None, + units=None, + wind=None, + onsite_reload_refuel=False, + **kwargs): + super(Disperse, self).__init__(**kwargs) + self.name = name + self.transit = transit + self.pass_length = pass_length + self.dosage = dosage + self.dosage_type = dosage_type + self.cascade_on = cascade_on + self.cascade_distance = cascade_distance + self.loading_type = loading_type + self.pass_type = pass_type + self.disp_oil_ratio = 20 if disp_oil_ratio is None else disp_oil_ratio + self.disp_eff = disp_eff + self.onsite_reload_refuel = onsite_reload_refuel + if self.disp_eff is not None: + self._disp_eff_type = 'fixed' + else: + self._disp_eff_type = 'auto' + # time to next state + if platform is not None: + if isinstance(platform, basestring): + #find platform name + self.platform = Platform(_name=platform) + else: + #platform is defined as a dict + self.platform = Platform(**platform) + else: + self.platform = platform + if units is None: + units = dict([(k, v[0]) for k, v in self._attr.items()]) + self._units = units + self.wind = wind + self.cur_state = None + self.oil_treated_this_timestep = 0 + self._next_state_time = None + self._op_start = None + self._op_end = None + self._cur_sortie_num = 1 + self._cur_pass_num = 1 + self._area_this_ts = 0 + self._area_this_sortie = 0 + self._disp_sprayed_this_timestep = 0 + self._remaining_dispersant = None + self._pass_time_tuple = self.platform.pass_duration_tuple(self.pass_length, self.pass_type) + + if dosage is not None: + self._dosage_m = uc.convert('oilconcentration', self.units['dosage'], 'micron', self.dosage) + self._dosage_m = uc.convert('length', 'micron', 'meters', self._dosage_m) + self.report=[] + self.array_types.update({'area', 'density', 'viscosity'}) + + +# @property +# def next_state(self): +# if self.cur_state is None: +# return None +# if self.cur_state == 'cascade' or self.cur_state == 'rtb': +# return 'replenish' +# elif self.cur_state == 'replenish': +# return 'en_route' +# elif self.cur_state == 'en_route': +# return 'on_site' +# elif self.cur_state == 'inactive': +# return 'replenish' +# +# @property +# def cur_state_duration(self): +# if self.cur_state is None: +# raise ValueError('Current state of None has no duration') +# if self.cur_state == 'inactive': +# raise ValueError('inactive has special duration and should not be requested') +# if self.cur_state == 'cascade': +# return self.platform.cascade_time(self.cascade_distance) +# if self.cur_state == 'ready': +# return self.platform.refuel_reload(self.loading_type) +# if self.cur_state == 'en_route': +# return self.platform.one_way_transit_time(self.transit) +# if self.cur_state == 'on_site': +# return self.platform.max_onsite_time(self.transit) +# if self.cur_state == 'returning': +# return self.platform.one_way_transit_time(self.transit) + + def get_mission_data(self, + dosage=None, + area=None, + pass_len=None, + efficiency=None, + units=None): + ''' + Given a dosage and an area to spray, will return a tuple of information as follows: + Minimize number of passes by using high swath_width. If pump rate cannot get to the dosage necessary + reduce the swath width until it can. + Default units are ('gal/acre', 'm^3, 'nm', percent) + Return tuple is as below + (num_passes, disp/pass, oil/pass) + (number, gal, ft, gal/min) + ''' + if units is None: + units = {'dosage': 'gal/acre', + 'area': 'm^3', + 'pass_len': 'nm', + 'efficiency': 'percent'} + + # Efficiency determines how much of the pass length is + pass_area = self.get('swath_width', 'm') * uc.convert('length', units['pass_len'], 'm', pass_len) + pass_len = uc.convert('length', units['pass_len'], 'm', pass_len) + app_speed = self.get('application_speed', 'm/s') + spray_time = pass_len / app_speed + max_dos = (self.get('pump_rate_max', 'm^3/s') * spray_time / pass_area) + max_dos = uc.convert('length', 'm', 'micron', max_dos) + max_dos = uc.convert('oilconcentration', 'micron', 'gal/acre', max_dos) + + def prepare_for_model_run(self, sc): + self._setup_report(sc) + if self.on: + sc.mass_balance['chem_dispersed'] = 0.0 + if self.cascade_on: + self.cur_state = 'cascade' + else: + self.cur_state = 'retired' + self._remaining_dispersant = self.platform.get('payload', 'm^3') + self.oil_treated_this_timestep = 0 + if 'systems' not in sc.mass_balance: + sc.mass_balance['systems'] = {} + sc.mass_balance['systems'][self.id] = 0.0 + + def dosage_from_thickness(self, sc): + thickness = self._get_thickness(sc) # inches + self._dosage_m = uc.convert('length', 'inches', 'm', thickness) / self.disp_oil_ratio + self.dosage = uc.convert('length', 'inches', 'micron', thickness) + self.dosage = uc.convert('oilconcentration', 'micron', 'gal/acre', self.dosage) / self.disp_oil_ratio + + def get_disp_eff_avg(self, sc, model_time): + wind_eff_list = Disperse.wind_eff_list + visc_eff_table = Disperse.visc_eff_table + vel = self.wind.get_value(model_time) + spd = vel[0] + wind_eff = wind_eff_list[int(spd)] / 100. + idxs = self.dispersable_oil_idxs(sc) + avg_visc = np.mean(sc['viscosity'][idxs] * 1000000) if len(idxs) > 0 else 1000000 + visc_eff = visc_eff_table[visc_eff_table.keys()[np.searchsorted(visc_eff_table.keys(), avg_visc)]] / 100 + return wind_eff * visc_eff + + def get_disp_eff(self, sc, model_time): + wind_eff_list = Disperse.wind_eff_list + visc_eff_table = Disperse.visc_eff_table + vel = self.wind.get_value(model_time) + spd = vel[0] + wind_eff = wind_eff_list[int(spd)] / 100. + idxs = self.dispersable_oil_idxs(sc) + visc = sc['viscosity'][idxs] * 1000000 + visc_idxs = np.array([np.searchsorted(visc_eff_table.keys(), v) for v in visc]) + visc_eff = np.array([visc_eff_table[visc_eff_table.keys()[v]] for v in visc_idxs]) / 100 + return wind_eff * visc_eff + + def prepare_for_model_step(self, sc, time_step, model_time): + ''' + ''' + + if self._disp_eff_type != 'fixed': + self.disp_eff = self.get_disp_eff_avg(sc, model_time) +# print 'efficiency is ', self.disp_eff + slick_area = 'WHAT??' + + if not isinstance(time_step, datetime.timedelta): + time_step = datetime.timedelta(seconds=time_step) + + self._time_remaining = datetime.timedelta(seconds=time_step.total_seconds()) + zero = datetime.timedelta(seconds=0) + if self.cur_state is None: + # This is first step., setup inactivity if necessary + if self.next_interval_index(model_time) != 0: + raise ValueError('disperse time series begins before time of first step!') + else: + self.cur_state = 'retired' + + if self.cur_state == 'deactivated': + # do deactivated stuff + return + + if self.platform.is_boat: + self.simulate_boat(sc, time_step, model_time) + else: + self.simulate_plane(sc, time_step, model_time) + + def simulate_boat(self, sc, time_step, model_time): + zero = datetime.timedelta(seconds=0) + ttni = self.time_to_next_interval(model_time) + tte = self.timeseries[-1][-1] - model_time + if tte < zero: + return + while self._time_remaining > zero: + + if self.cur_state == 'retired': + if model_time < self.timeseries[0][0]: + tts = self.timeseries[0][0] - model_time + self._time_remaining -= min(self._time_remaining, tts) + model_time, time_step = self.update_time(self._time_remaining, model_time, time_step) + if self.time_remaining > 0: + #must just have started. Get ready + self.cur_state = 'ready' + self.report.append((model_time, 'Begin new operational period')) + else: + self.cur_state = 'ready' + self.report.append((model_time, 'Begin new operational period')) + + elif self.cur_state == 'ready': + if self.platform.sortie_possible(tte, self.transit, self.pass_length): + # sortie is possible, so start immediately + self.report.append((model_time, 'Starting sortie')) + self._next_state_time = model_time + datetime.timedelta(seconds=self.platform.one_way_transit_time(self.transit)) + self.cur_state = 'en_route' + self._area_sprayed_this_sortie = 0 + self._area_sprayed_this_ts = 0 + else: + # cannot sortie, so retire until next interval + self.cur_state = 'deactivated' + self.report.append((model_time, 'Deactivating due to insufficient time remaining to conduct sortie')) + print self.report[-1] + self._time_remaining -= min(self._time_remaining, ttni) + model_time, time_step = self.update_time(self._time_remaining, model_time, time_step) + + elif self.cur_state == 'en_route': + time_left = self._next_state_time - model_time + self._time_remaining -= min(self._time_remaining, time_left) + model_time, time_step = self.update_time(self._time_remaining, model_time, time_step) + if self._time_remaining > zero: + self.report.append((model_time, 'Reached slick')) + self._op_start = model_time + self._op_end = (self.timeseries[-1][-1] - datetime.timedelta(seconds=self.platform.one_way_transit_time(self.transit))) + self._cur_pass_num = 1 + self.cur_state = 'onsite' + dur = datetime.timedelta(hours=self.platform.get('max_op_time', 'hrs')) + self._next_state_time = model_time + dur + + elif self.cur_state == 'onsite': + remaining_op = self._op_end - model_time + if self.is_operating(model_time): + interval_remaining = self.time_to_next_interval(model_time) + spray_time = min(self._time_remaining, remaining_op, interval_remaining) + if self.dosage_type == 'auto': + self.dosage_from_thickness(sc) + dosage = self.dosage + disp_possible = spray_time.total_seconds() * self.platform.eff_pump_rate(dosage) + disp_actual = min(self._remaining_dispersant, disp_possible) + if disp_actual != disp_possible: + spray_time = datetime.timedelta(seconds=disp_actual / self.platform.eff_pump_rate(dosage)) + treated_possible = disp_actual * self.disp_oil_ratio + mass_treatable = np.mean(sc['density'][self.dispersable_oil_idxs(sc)]) * treated_possible + oil_avail = self.dispersable_oil_amount(sc, 'kg') + self.report.append((model_time, 'Oil available: ' + str(oil_avail) + ' Treatable mass: ' + str(mass_treatable) + ' Dispersant Sprayed: ' + str(disp_actual))) + self.report.append((model_time, 'Sprayed ' + str(disp_actual) + 'm^3 dispersant in ' + str(spray_time) + ' on ' + str(oil_avail) + ' kg of oil')) + print self.report[-1] + self._time_remaining -= spray_time + self._disp_sprayed_this_timestep += disp_actual + self._remaining_dispersant -= disp_actual + self.oil_treated_this_timestep += min(mass_treatable, oil_avail) + model_time, time_step = self.update_time(self._time_remaining, model_time, time_step) + if self._time_remaining > zero: #end of interval, end of operation, or out of dispersant/fuel + if self._remaining_dispersant == 0: + #go to reload + if self.onsite_reload_refuel: + self.cur_state = 'refuel_reload' + refuel_reload = datetime.timedelta(seconds=self.platform.refuel_reload(simul=self.loading_type)) + self._next_state_time = model_time + refuel_reload + self.report.append((model_time, 'Reloading/refueling')) + else: + #need to return to base + self.cur_state = 'rtb' + self._next_state_time = model_time + datetime.timedelta(seconds=self.platform.one_way_transit_time(self.transit)) + self.report.append((model_time, 'Out of dispersant, returning to base')) + elif model_time == self._op_end: + self.report.append((model_time, 'Operation complete, returning to base')) + self.cur_state = 'rtb' + self._next_state_time = model_time + datetime.timedelta(seconds=self.platform.one_way_transit_time(self.transit)) + else: + self._time_remaining -= min(self._time_remaining, remaining_op) + model_time, time_step = self.update_time(self._time_remaining, model_time, time_step) + if self._time_remaining > zero: + self.cur_state = 'rtb' + self.report.append((model_time, 'Operation complete, returning to base')) + self._next_state_time = model_time + datetime.timedelta(seconds=self.platform.one_way_transit_time(self.transit)) + + elif self.cur_state == 'rtb': + time_left = self._next_state_time - model_time + self._time_remaining -= min(self._time_remaining, time_left) + model_time, time_step = self.update_time(self._time_remaining, model_time, time_step) + if self._time_remaining > zero: + self.report.append((model_time, 'Returned to base')) + print self.report[-1] + refuel_reload = datetime.timedelta(seconds=self.platform.refuel_reload(simul=self.loading_type)) + self._next_state_time = model_time + refuel_reload + self.cur_state = 'refuel_reload' + + elif self.cur_state == 'refuel_reload': + time_left = self._next_state_time - model_time + self._time_remaining -= min(self._time_remaining, time_left) + model_time, time_step = self.update_time(self._time_remaining, model_time, time_step) + if self._time_remaining > zero: + self.report.append((model_time, 'Refuel/reload complete')) + print self.report[-1] + self._remaining_dispersant = self.platform.get('payload', 'm^3') + if self.onsite_reload_refuel: + self.cur_state = 'onsite' + else: + self.cur_state = 'ready' + + def simulate_plane(self, sc, time_step, model_time): + ttni = self.time_to_next_interval(model_time) + zero = datetime.timedelta(seconds=0) + while self._time_remaining > zero: + if ttni is None: + if self.cur_state not in ['retired', 'reload', 'ready']: + raise ValueError('Operation is being deactivated while platform is active!') + self.cur_state = 'deactivated' + self.report.append((model_time, 'Disperse operation has ended and is deactivated')) + print self.report[-1] + break + + if self.cur_state == 'retired': + if self.index_of(model_time) > -1 and self.timeseries[self.index_of(model_time)][0] == model_time: + #landed right on interval start, so ready immediately + self.cur_state = 'ready' + self.report.append((model_time, 'Begin new operational period')) + print self.report[-1] + continue + self._time_remaining -= min(self._time_remaining, ttni) + if self._time_remaining > zero: + model_time, time_step = self.update_time(self._time_remaining, model_time, time_step) + # hit interval boundary before ending timestep. + # If ending current interval or no remaining time, do nothing + # if start of next interval, set state to 'ready' + # entering new operational interval + # ending current interval + if self.index_of(model_time) > -1: + self.cur_state = 'ready' + self.report.append((model_time, 'Begin new operational period')) + print self.report[-1] + else: + interval_idx = self.index_of(model_time - time_step + self._time_remaining) + self.report.append((model_time, 'Ending current operational period')) + print self.report[-1] + + elif self.cur_state == 'ready': + if self.platform.sortie_possible(ttni, self.transit, self.pass_length): + # sortie is possible, so start immediately + self.report.append((model_time, 'Starting sortie')) + print self.report[-1] + self._next_state_time = model_time + datetime.timedelta(seconds=self.platform.one_way_transit_time(self.transit)) + self.cur_state = 'en_route' + self._area_sprayed_this_sortie = 0 + self._area_sprayed_this_ts = 0 + else: + # cannot sortie, so retire until next interval + self.cur_state = 'retired' + self.report.append((model_time, 'Retiring due to insufficient time remaining to conduct sortie')) + print self.report[-1] + self._time_remaining -= min(self._time_remaining, ttni) + model_time, time_step = self.update_time(self._time_remaining, model_time, time_step) + + elif self.cur_state == 'en_route': + time_left = self._next_state_time - model_time + self._time_remaining -= min(self._time_remaining, time_left) + model_time, time_step = self.update_time(self._time_remaining, model_time, time_step) + if self._time_remaining > zero: + self.report.append((model_time, 'Reached slick')) + print self.report[-1] + self._op_start = model_time + self._op_end = model_time + datetime.timedelta(seconds=self.platform.max_onsite_time(self.transit, self.loading_type)) + self._cur_pass_num = 1 + self.cur_state = 'approach' + dur = datetime.timedelta(seconds=self.platform.pass_duration_tuple(self.pass_length, self.pass_type)[0]) + self._next_state_time = model_time + dur + self.report.append((model_time, 'Starting approach for pass ' + str(self._cur_pass_num))) + print self.report[-1] + + elif self.cur_state == 'approach': + time_left = self._next_state_time - model_time + self._time_remaining -= min(self._time_remaining, time_left) + model_time, time_step = self.update_time(self._time_remaining, model_time, time_step) + if self._time_remaining > zero: + spray_time = self.platform.pass_duration_tuple(self.pass_length, self.pass_type)[1] + self._next_state_time = model_time + datetime.timedelta(seconds=spray_time) + self.cur_state = 'disperse_' + str(self._cur_pass_num) + self.report.append((model_time, 'Starting pass ' + str(self._cur_pass_num))) + + elif self.cur_state == 'u-turn': + if self.pass_type != 'bidirectional': + raise ValueError('u-turns should not happen in uni-directional passes') + time_left = self._next_state_time - model_time + self._time_remaining -= min(self._time_remaining, time_left) + model_time, time_step = self.update_time(self._time_remaining, model_time, time_step) + if self._time_remaining > zero: + spray_time = self.platform.pass_duration_tuple(self.pass_length, self.pass_type)[1] + self._next_state_time = model_time + datetime.timedelta(seconds=spray_time) + self.cur_state = 'disperse_' + str(self._cur_pass_num) + 'u' + self.report.append((model_time, 'Begin return pass of pass ' + str(self._cur_pass_num))) + + elif self.cur_state == 'departure': + time_left = self._next_state_time - model_time + self._time_remaining -= min(self._time_remaining, time_left) + model_time, time_step = self.update_time(self._time_remaining, model_time, time_step) + if self._time_remaining > zero: + self.report.append((model_time, 'Disperse pass ' + str(self._cur_pass_num) + ' completed')) + passes_possible = self.platform.num_passes_possible(self._op_end - model_time, self.pass_length, self.pass_type) + passes_possible_after_holding = self.platform.num_passes_possible(self._op_end - model_time + time_step, self.pass_length, self.pass_type) + o_w_t_t = datetime.timedelta(seconds=self.platform.one_way_transit_time(self.transit, payload=False)) + self._cur_pass_num += 1 + if self._remaining_dispersant == 0: + # no dispersant, so return to base + self.reset_for_return_to_base(model_time, 'No dispersant remaining, returning to base') + elif np.isclose(self.dispersable_oil_amount(sc, 'kg'), 0): + if passes_possible_after_holding > 0: + # no oil left, but can still do a pass after holding for one timestep + self.cur_state = 'holding' + self._next_state_time = model_time + datetime.timedelta(seconds=time_step) + else: + self.reset_for_return_to_base(model_time, 'No oil, no time for holding pattern, returning to base') + elif passes_possible == 0: + # no passes possible, so RTB + self.reset_for_return_to_base(model_time, 'No time for further passes, returning to base') + else: + # oil and payload still remaining. Spray again. + self.report.append((model_time, 'Starting disperse pass ' + str(self._cur_pass_num))) + print self.report[-1] + self.cur_state = 'disperse_' + str(self._cur_pass_num) + self._next_state_time = model_time + datetime.timedelta(seconds=self._pass_time_tuple[1]) + + elif self.cur_state == 'holding': + time_left = self._next_state_time - model_time + self._time_remaining -= min(self._time_remaining, time_left) + model_time, time_step = self.update_time(self._time_remaining, model_time, time_step) + self.cur_state = 'approach' + + elif 'disperse' in self.cur_state: + pass_dur = datetime.timedelta(seconds=self.platform.pass_duration_tuple(self.pass_length, self.pass_type)[1]) + time_left_in_pass = self._next_state_time - model_time + spray_time = min(self._time_remaining, time_left_in_pass) + if self.dosage_type == 'auto': + self.dosage_from_thickness(sc) + dosage = self.dosage + disp_possible = spray_time.total_seconds() * self.platform.eff_pump_rate(dosage) + disp_actual = min(self._remaining_dispersant, disp_possible) + treated_possible = disp_actual * self.disp_oil_ratio + mass_treatable = np.mean(sc['density'][self.dispersable_oil_idxs(sc)]) * treated_possible + oil_avail = self.dispersable_oil_amount(sc, 'kg') + self.report.append((model_time, 'Oil available: ' + str(oil_avail) + ' Treatable mass: ' + str(mass_treatable) + ' Dispersant Sprayed: ' + str(disp_actual))) + self.report.append((model_time, 'Sprayed ' + str(disp_actual) + 'm^3 dispersant in ' + str(spray_time) + ' seconds on ' + str(oil_avail) + ' kg of oil')) + print self.report[-1] + self._time_remaining -= spray_time + self._disp_sprayed_this_timestep += disp_actual + self._remaining_dispersant -= disp_actual + self.oil_treated_this_timestep += min(mass_treatable, oil_avail) + + model_time, time_step = self.update_time(self._time_remaining, model_time, time_step) + if self._time_remaining > zero: + # completed a spray. + if self.pass_type == 'bidirectional' and self._remaining_dispersant > 0 and self.cur_state[-1] != 'u': + self.cur_state = 'u-turn' + self.report.append((model_time, 'Doing u-turn')) + self._next_state_time = model_time + datetime.timedelta(seconds=self._pass_time_tuple[2]) + else: + self.cur_state = 'departure' + self._next_state_time = model_time + datetime.timedelta(seconds=self._pass_time_tuple[-1]) + + + elif self.cur_state == 'rtb': + time_left = self._next_state_time - model_time + self._time_remaining -= min(self._time_remaining, time_left) + model_time, time_step = self.update_time(self._time_remaining, model_time, time_step) + if self._time_remaining > zero: + self.report.append((model_time, 'Returned to base')) + print self.report[-1] + refuel_reload = datetime.timedelta(seconds=self.platform.refuel_reload(simul=self.loading_type)) + self._next_state_time = model_time + refuel_reload + self.cur_state = 'refuel_reload' + + elif self.cur_state == 'refuel_reload': + time_left = self._next_state_time - model_time + self._time_remaining -= min(self._time_remaining, time_left) + model_time, time_step = self.update_time(self._time_remaining, model_time, time_step) + if self._time_remaining > zero: + self.report.append((model_time, 'Refuel/reload complete')) + print self.report[-1] + self._remaining_dispersant = self.platform.get('payload', 'm^3') + self.cur_state = 'ready' + + elif self.cur_state == 'cascade': + if self._next_state_time is None: + self._next_state_time = model_time + datetime.timedelta(seconds=self.platform.cascade_time(self.cascade_distance, payload=False)) + time_left = self._next_state_time - model_time + self._time_remaining -= min(self._time_remaining, time_left) + model_time, time_step = self.update_time(self._time_remaining, model_time, time_step) + if self._time_remaining > zero: + self.report.append((model_time, 'Cascade complete')) + print self.report[-1] + self.cur_state = 'ready' + else: + raise ValueError('current state is not recognized: ' + self.cur_state) + + def reset_for_return_to_base(self, model_time, message): + self.report.append((model_time, message)) + print self.report[-1] + o_w_t_t = datetime.timedelta(seconds=self.platform.one_way_transit_time(self.transit, payload=False)) + self._next_state_time = model_time + o_w_t_t + self._op_start = self._op_end = None + self._cur_pass_num = 1 + self._disp_sprayed_this_timestep = 0 + self.cur_state = 'rtb' + + def update_time(self, time_remaining, model_time, time_step): + if time_remaining > datetime.timedelta(seconds=0): + return model_time + time_step - time_remaining, time_remaining + else: + return model_time, time_step + + def dispersable_oil_idxs(self, sc): + # LEs must have a low viscosity, have not been fully chem dispersed, and must have a mass > 0 + idxs = np.where(sc['viscosity'] * 1000000 < 1000000)[0] +# idxs = np.arange(0, len(sc['mass'])) + codes = sc['fate_status'][idxs] != bt_fate.disperse + idxs = idxs[codes] + nonzero_mass = sc['mass'][idxs] > 0 + idxs = idxs[nonzero_mass] + return idxs + + def dispersable_oil_amount(self, sc, units='gal'): + idxs = self.dispersable_oil_idxs(sc) + if units in _valid_vol_units: + tot_vol = np.sum(sc['mass'][idxs] / sc['density'][idxs]) + return max(0, uc.convert('m^3', units, tot_vol)) + else: + tot_mass = np.sum(sc['mass'][idxs]) + return max(0, tot_mass - self.oil_treated_this_timestep / np.mean(sc['density'][idxs])) + + def weather_elements(self, sc, time_step, model_time): + + idxs = self.dispersable_oil_idxs(sc) + if self.oil_treated_this_timestep != 0: + visc_eff_table = Disperse.visc_eff_table + wind_eff_list = Disperse.wind_eff_list +# visc_disp_eff_per_le = [visc_eff_table[visc_eff_table.keys()[np.searchsorted(visc_eff_table.keys(), le)]] / 100 for le in sc['viscosity'][idxs] * 1000000] +# vel = self.wind.get_value(model_time) +# spd = math.sqrt(vel[0]**2 + vel[1]**2) +# wind_disp_eff_per_le = wind_eff_list[int(self.wind.get_value(spd))] +# proportions = disp_eff_per_le / np.mean(disp_eff_per_le) + mass_proportions = sc['mass'][idxs] / np.sum(sc['mass'][idxs]) + eff_reductions = self.get_disp_eff(sc, model_time) + mass_to_remove = self.oil_treated_this_timestep * mass_proportions * eff_reductions + + org_mass = sc['mass'][idxs] + removed = self._remove_mass_indices(sc, mass_to_remove, idxs) + print 'index, original mass, removed mass, final mass' + masstab = np.column_stack((idxs, org_mass, mass_to_remove, sc['mass'][idxs])) + sc.mass_balance['chem_dispersed'] += sum(removed) + sc.mass_balance['systems'][self.id] += sum(removed) + sc.mass_balance['floating'] -= sum(removed) + zero_or_disp = np.isclose(sc['mass'][idxs], 0) + new_status = sc['fate_status'][idxs] + new_status[zero_or_disp] = bt_fate.disperse + sc['fate_status'][idxs] = new_status + self.oil_treated_this_timestep = 0 + self.disp_sprayed_this_timestep = 0 + + + +class BurnUnitsSchema(MappingSchema): + offset = SchemaNode(String(), + description='SI units for distance', + validator=OneOf(_valid_dist_units)) + + boom_length = SchemaNode(String(), + description='SI units for distance', + validator=OneOf(_valid_dist_units)) + + boom_draft = SchemaNode(String(), + description='SI units for distance', + validator=OneOf(_valid_dist_units)) + + speed = SchemaNode(String(), + description='SI units for speed', + validator=OneOf(_valid_vel_units)) + +class BurnSchema(ResponseSchema): + offset = SchemaNode(Integer()) + boom_length = SchemaNode(Integer()) + boom_draft = SchemaNode(Integer()) + speed = SchemaNode(Float()) + throughput = SchemaNode(Float()) + burn_efficiency_type = SchemaNode(String()) + units = BurnUnitsSchema() + +class Burn(Response): + _state = copy.deepcopy(Response._state) + _state += [Field('offset', save=True, update=True), + Field('boom_length', save=True, update=True), + Field('boom_draft', save=True, update=True), + Field('speed', save=True, update=True), + Field('throughput', save=True, update=True), + Field('burn_efficiency_type', save=True, update=True), + Field('units', save=True, update=True)] + + _schema = BurnSchema + + _si_units = {'offset': 'ft', + 'boom_length': 'ft', + 'boom_draft': 'in', + 'speed': 'kts', + '_boom_capacity_max': 'ft^3'} + + _units_type = {'offset': ('length', _valid_dist_units), + 'boom_length': ('length', _valid_dist_units), + 'boom_draft': ('length', _valid_dist_units), + 'speed': ('velocity', _valid_vel_units), + '_boom_capacity_max': ('volume', _valid_vol_units)} + + def __init__(self, + offset, + boom_length, + boom_draft, + speed, + throughput, + burn_efficiency_type=1, + units=_si_units, + **kwargs): + + super(Burn, self).__init__(**kwargs) + self.array_types.update({'mass': mass, + 'density': density, + 'frac_water': frac_water}) + + self.offset = offset + self._units = dict(self._si_units) + self.units = units + self.boom_length = boom_length + self.boom_draft = boom_draft + self.speed = speed + self.throughput = throughput + self.burn_efficiency_type = burn_efficiency_type + self._swath_width = None + self._area = None + self._boom_capacity_max = 0 + self._offset_time = None + + self._is_collecting = False + self._is_burning = False + self._is_boom_filled = False + self._is_transiting = False + self._is_cleaning = False + + self._time_collecting_in_sim = 0. + self._total_burns = 0. + self._time_burning = 0. + self._ts_burned = 0. + self._ts_collected = 0. + self._burn_time = None + self._burn_rate = None + + def prepare_for_model_run(self, sc): + self._setup_report(sc) + self._swath_width = 0.3 * self.get('boom_length') + self._area = self._swath_width * (0.4125 * self.get('boom_length') / 3) * 2 / 3 + self.set('_boom_capacity_max', self.get('boom_draft') / 36 * self._area, 'ft^3') + self._boom_capacity = self.get('_boom_capacity_max') + self._offset_time = (self.offset * 0.00987 / self.get('speed')) * 60 + self._area_coverage_rate = self._swath_width * self.get('speed') / 430 + + if self._swath_width > 1000: + self.report.append('Swaths > 1000 feet may not be achievable in the field') + + if self.get('speed') > 1.2: + self.report.append('Excessive entrainment of oil likely to occur at speeds greater than 1.2 knots.') + + if self.on: + sc.mass_balance['burned'] = 0.0 + if 'systems' not in sc.mass_balance: + sc.mass_balance['systems'] = {} + sc.mass_balance['systems'][self.id] = {'boomed': 0.0, + 'burned': 0.0, + 'time_burning': 0.0, + 'num_burns': 0, + 'area_covered': 0.0} + sc.mass_balance['boomed'] = 0.0 + + self._is_collecting = True + self._is_transiting = False + self._is_cleaning = False + self._is_burning = False + self._is_boom_full = False + + self._time_burning = 0 + + def prepare_for_model_step(self, sc, time_step, model_time): + ''' + 1. set 'active' flag based on timeseries and model_time + 2. Mark LEs to be burned, do them in order right now. assume all LEs + that are released together will be burned together since they would + be closer to each other in position. + ''' + + self._ts_collected = 0. + self._ts_burned = 0. + self._ts_num_burns = 0 + self._ts_area_covered = 0. + + if self._is_active(model_time, time_step) or self._is_burning: + self._active = True + else: + self._active = False + + if not self.active: + return + + self._time_remaining = time_step + + while self._time_remaining > 0.: + if self._is_collecting: + self._collect(sc, time_step, model_time) + + if self._is_transiting: + self._transit(sc, time_step, model_time) + + if self._is_burning: + self._burn(sc, time_step, model_time) + + if self._is_cleaning: + self._clean(sc, time_step, model_time) + + def _collect(self, sc, time_step, model_time): + # calculate amount collected this time_step + if self._burn_rate is None: + self._burn_rate = 0.14 * (1 - sc['frac_water'].mean()) + + oil_thickness = self._get_thickness(sc) + encounter_rate = 63.13 * self._swath_width * oil_thickness * self.get('speed') + emulsion_rr = encounter_rate * self.throughput + self._boomed_density = sc['density'].mean() + if oil_thickness > 0: + # old ROC equation + # time_to_fill = (self._boom_capacity_remaining / emulsion_rr) * 60 + # new ebsp equation + time_to_fill = uc.convert('Volume', 'ft^3', 'gal', self._boom_capacity) / emulsion_rr + #(self._boom_capacity * 0.17811) * 42 / emulsion_rr + else: + time_to_fill = 0. + + if time_to_fill > self._time_remaining: + # doesn't finish fill the boom in this time step + self._ts_collected = uc.convert('Volume', 'gal', 'ft^3', emulsion_rr * self._time_remaining) + self._boom_capacity -= self._ts_collected + self._ts_area_covered = encounter_rate * (self._time_remaining / 60) + self._time_collecting_in_sim += self._time_remaining + self._time_remaining = 0.0 + elif self._time_remaining > 0: + # finishes filling the boom in this time step any time remaining + # should be spend transiting to the burn position + self._ts_collected = uc.convert('Volume', 'gal', 'ft^3', emulsion_rr * time_to_fill) + self._ts_area_covered = encounter_rate * (time_to_fill / 60) + self._boom_capacity-= self._ts_collected + self._is_boom_full = True + self._time_remaining -= time_to_fill + self._time_collecting_in_sim += time_to_fill + self._offset_time_remaining = self._offset_time + self._is_collecting = False + self._is_transiting = True + + def _transit(self, sc, time_step, model_time): + # transiting to burn site + # does it arrive and start burning? + if self._time_remaining > self._offset_time_remaining: + self._time_remaining -= self._offset_time_remaining + self._offset_time_remaining = 0. + self._is_transiting = False + if self._is_boom_full: + self._is_burning = True + else: + self._is_collecting = True + elif self._time_remaining > 0: + self._offset_time_remaining -= self._time_remaining + self._time_remaining = 0. + + def _burn(self, sc, time_step, model_time): + # burning + if self._burn_time is None: + self._ts_num_burns = 1 + self._burn_time = (0.33 * self.get('boom_draft') / self._burn_rate) * 60 + self._burn_time_remaining = self._burn_time + if not np.isclose(self._boom_capacity, 0): + # this is a special case if the boom didn't fill up all the way + # due to lack of oil or somethig. + self._burn_time_remaining = self._burn_time * ((1 - self._boom_capacity) / self.get('_boom_capacity_max')) + + self._is_boom_full = False + if self._time_remaining > self._burn_time_remaining: + self._time_remaining -= self._burn_time_remaining + self._time_burning += self._burn_time_remaining + self._burn_time_remaining = 0. + burned = self.get('_boom_capacity_max') - self._boom_capacity + self._ts_burned = burned + self._is_burning = False + self._is_cleaning = True + self._cleaning_time_remaining = 3600 # 1hr in seconds + elif self._time_remaining > 0: + frac_burned = self._time_remaining / self._burn_time + burned = self.get('_boom_capacity_max') * frac_burned + self._boom_capacity += burned + self._ts_burned = burned + self._time_burning += self._time_remaining + self._burn_time_remaining -= self._time_remaining + self._time_remaining = 0. + + def _clean(self, sc, time_step, model_time): + # cleaning + self._burn_time = None + self._burn_rate = None + if self._time_remaining > self._cleaning_time_remaining: + self._time_remaining -= self._cleaning_time_remaining + self._cleaning_time_remaining = 0. + self._is_cleaning = False + self._is_transiting = True + self._offset_time_remaining = self._offset_time + elif self._time_remaining > 0: + self._cleaning_time_remaining -= self._time_remaining + self._time_remaining = 0. + + def weather_elements(self, sc, time_step, model_time): + ''' + Remove mass from each le equally for now, no flagging for not + just make sure it's from floating oil. + ''' + if not self.active or len(sc) == 0: + return + + les = sc.itersubstancedata(self.array_types) + for substance, data in les: + if len(data['mass']) is 0: + continue + + sc.mass_balance['systems'][self.id]['area_covered'] += self._ts_area_covered + sc.mass_balance['systems'][self.id]['num_burns'] += self._ts_num_burns + + if self._ts_collected > 0: + collected = uc.convert('Volume', 'ft^3', 'm^3', self._ts_collected) * self._boomed_density + sc.mass_balance['boomed'] += collected + sc.mass_balance['systems'][self.id]['boomed'] += collected + self._remove_mass_simple(data, collected) + + self.logger.debug('{0} amount boomed for {1}: {2}' + .format(self._pid, substance.name, collected)) + + if self._ts_burned > 0: + burned = uc.convert('Volume', 'ft^3', 'm^3', self._ts_burned) * self._boomed_density + sc.mass_balance['burned'] += burned + sc.mass_balance['boomed'] -= burned + sc.mass_balance['systems'][self.id]['burned'] += burned + sc.mass_balance['systems'][self.id]['time_burning'] = self._time_burning + + # make sure we didn't burn more than we boomed if so correct the amount + if sc.mass_balance['boomed'] < 0: + sc.mass_balance['burned'] += sc.mass_balance['boomed'] + sc.mass_balance['boomed'] = 0 + + self.logger.debug('{0} amount burned for {1}: {2}' + .format(self._pid, substance.name, burned)) + +class SkimUnitsSchema(MappingSchema): + storage = SchemaNode(String(), + description='SI units for onboard storage', + validator=OneOf(_valid_vol_units)) + + decant_pump = SchemaNode(String(), + description='SI units for decant', + validator=OneOf(_valid_dis_units)) + + nameplate_pump = SchemaNode(String(), + description='SI units for nameplate', + validator=OneOf(_valid_dis_units)) + + discharge_pump = SchemaNode(String(), + description='SI units for discharge', + validator=OneOf(_valid_dis_units)) + + speed = SchemaNode(String(), + description='SI units for speed', + validator=OneOf(_valid_vel_units)) + + swath_width = SchemaNode(String(), + description='SI units for length', + validator=OneOf(_valid_dist_units)) + +class SkimSchema(ResponseSchema): + units = SkimUnitsSchema() + speed = SchemaNode(Float()) + storage = SchemaNode(Float()) + swath_width = SchemaNode(Float()) + group = SchemaNode(String()) + throughput = SchemaNode(Float()) + nameplate_pump = SchemaNode(Float()) + skim_efficiency_type = SchemaNode(String()) + decant = SchemaNode(Float()) + decant_pump = SchemaNode(Float()) + rig_time = SchemaNode(TimeDelta()) + transit_time = SchemaNode(TimeDelta()) + offload_to = SchemaNode(String(), missing=drop) + discharge_pump = SchemaNode(Float()) + recovery = SchemaNode(String()) + recovery_ef = SchemaNode(Float()) + barge_arrival = SchemaNode(LocalDateTime(), + validator=validators.convertible_to_seconds, + missing=drop) + +class Skim(Response): + _state = copy.deepcopy(Response._state) + _state += [Field('units', save=True, update=True), + Field('speed', save=True, update=True), + Field('storage', save=True, update=True), + Field('swath_width', save=True, update=True), + Field('group', save=True, update=True), + Field('throughput', save=True, update=True), + Field('nameplate_pump', save=True, update=True), + Field('discharge_pump', save=True, update=True), + Field('skim_efficiency_type', save=True, update=True), + Field('decant', save=True, update=True), + Field('decant_pump', save=True, update=True), + Field('rig_time', save=True, update=True), + Field('transit_time', save=True, update=True), + Field('recovery', save=True, update=True), + Field('recovery_ef', save=True, update=True)] + + _schema = SkimSchema + + _si_units = {'storage': 'bbl', + 'decant_pump': 'gpm', + 'nameplate_pump': 'gpm', + 'speed': 'kts', + 'swath_width': 'ft', + 'discharge_pump': 'gpm'} + + _units_types = {'storage': ('storage', _valid_vol_units), + 'decant_pump': ('decant_pump', _valid_dis_units), + 'nameplate_pump': ('nameplate_pump', _valid_dis_units), + 'speed': ('speed', _valid_vel_units), + 'swath_width': ('swath_width', _valid_dist_units), + 'discharge_pump': ('discharge_pump', _valid_dis_units)} + + def __init__(self, + speed, + storage, + swath_width, + group, + throughput, + nameplate_pump, + skim_efficiency_type, + recovery, + recovery_ef, + decant, + decant_pump, + discharge_pump, + rig_time, + transit_time, + units=_si_units, + **kwargs): + + super(Skim, self).__init__(**kwargs) + + self.speed = speed + self.storage = storage + self.swath_width = swath_width + self.group = group + self.throughput = throughput + self.nameplate_pump = nameplate_pump + self.recovery = recovery + self.recovery_ef = recovery_ef + self.decant = decant + self.decant_pump = decant_pump + self.rig_time = rig_time + self.discharge_pump = discharge_pump + self.skim_efficiency_type = skim_efficiency_type + self.transit_time = transit_time + self._units = dict(self._si_units) + + self._is_collecting = False + self._is_transiting = False + self._is_offloading = False + self._is_rig_deriging = False + + def prepare_for_model_run(self, sc): + self._setup_report(sc) + self._storage_remaining = self.storage + self._coverage_rate = self.swath_width * self.speed * 0.00233 + self.offload = (self.storage * 42 / self.discharge_pump) * 60 + + if self.on: + sc.mass_balance['skimmed'] = 0.0 + sc.mass_balance[self.id] = {'fluid_collected': 0.0, + 'emulsion_collected': 0.0, + 'oil_collected': 0.0, + 'water_collected': 0.0, + 'water_decanted': 0.0, + 'water_retained': 0.0, + 'area_covered': 0.0, + 'storage_remaining': 0.0} + + self._is_collecting = True + + def prepare_for_model_step(self, sc, time_step, model_time): + if self._is_active(model_time, time_step): + self._active = True + else : + self._active = False + + if not self.active: + return + + self._time_remaining = time_step + + if hasattr(self, 'barge_arrival'): #type(self.barge_arrival) is datetime.date: + # if there's a barge so a modified cycle + while self._time_remaining > 0.: + if self._is_collecting: + self._collect(sc, time_step, model_time) + else: + while self._time_remaining > 0.: + if self._is_collecting: + self._collect(sc, time_step, model_time) + + if self._is_transiting: + self._transit(sc, time_step, model_time) + + if self._is_offloading: + self._offload(sc, time_step, model_time) + + + def _collect(self, sc, time_step, model_time): + thickness = self._get_thickness(sc) + if self.recovery_ef > 0 and self.throughput > 0 and thickness > 0: + self._maximum_effective_swath = self.nameplate_pump * self.recovery_ef / (63.13 * self.speed * thickness * self.throughput) + else: + self._maximum_effective_swath = 0 + + if self.swath_width > self._maximum_effective_swath: + swath = self._maximum_effective_swath; + else: + swath = self.swath_width + + if swath > 1000: + self.report.append('Swaths > 1000 feet may not be achievable in the field.') + + encounter_rate = thickness * self.speed * swath * 63.13 + rate_of_coverage = swath * self.speed * 0.00233 + if encounter_rate > 0: + recovery = self._getRecoveryEfficiency() + + if recovery > 0: + totalFluidRecoveryRate = encounter_rate * (self.throughput / recovery) + + if totalFluidRecoveryRate > self.nameplate_pump: + # total fluid recovery rate is greater than nameplate + # pump, recalculate the throughput efficiency and + # total fluid recovery rate again with the new throughput + throughput = self.nameplate_pump * recovery / encounter_rate + totalFluidRecoveryRate = encounter_rate * (throughput / recovery) + msg = ('{0.name} - Total Fluid Recovery Rate is greater than Nameplate \ + Pump Rate, recalculating Throughput Efficiency').format(self) + self.logger.warning(msg) + else: + throughput = self.throughput + + if throughput > 0: + emulsionRecoveryRate = encounter_rate * throughput + + waterRecoveryRate = (1 - recovery) * totalFluidRecoveryRate + waterRetainedRate = waterRecoveryRate * (1 - self.decant) + computedDecantRate = (totalFluidRecoveryRate - emulsionRecoveryRate) * self.decant + + decantRateDifference = 0. + if computedDecantRate > self.decant_pump: + decantRateDifference = computedDecantRate - self.decant_pump + + recoveryRate = emulsionRecoveryRate + waterRecoveryRate + retainRate = emulsionRecoveryRate + waterRetainedRate + decantRateDifference + oilRecoveryRate = emulsionRecoveryRate * (1 - sc['frac_water'].mean()) + + freeWaterRecoveryRate = recoveryRate - emulsionRecoveryRate + freeWaterRetainedRate = retainRate - emulsionRecoveryRate + freeWaterDecantRate = freeWaterRecoveryRate - freeWaterRetainedRate + + timeToFill = .7 * self._storage_remaining / retainRate * 60 + + if timeToFill * 60 > self._time_remaining: + # going to take more than this timestep to fill the storage + time_collecting = self._time_remaining + self._time_remaining = 0. + else: + # storage is filled during this timestep + time_collecting = timeToFill + self._time_remaining -= timeToFill + self._transit_remaining = self.transit_time + self._collecting = False + self._transiting = True + + self._ts_fluid_collected = retainRate * time_collecting + self._ts_emulsion_collected = emulsionRecoveryRate * time_collecting + self._ts_oil_collected = oilRecoveryRate * time_collecting + self._ts_water_collected = freeWaterRecoveryRate * time_collecting + self._ts_water_decanted = freeWaterDecantRate * time_collecting + self._ts_water_retained = freeWaterRetainedRate * time_collecting + self._ts_area_covered = rate_of_coverage * time_collecting + + self._storage_remaining -= uc.convert('gal', 'bbl', self._ts_fluid_collected) + + else: + self._no_op_step() + else: + self._no_op_step() + else: + self._no_op_step() + + + def _transit(self, sc, time_step, model_time): + # transiting back to shore to offload + if self._time_remaining > self._transit_remaining: + self._time_remaining -= self._transit_remaining + self._transit_remaining = 0. + self._is_transiting = False + if self._storage_remaining == 0.0: + self._is_offloading = True + else: + self._is_collecting = True + self._offload_remaining = self.offload + self.rig_time + else: + self._transit_remaining -= self._time_remaining + self._time_remaining = 0. + + def _offload(self, sc, time_step, model_time): + if self._time_remaining > self._offload_remaining: + self._time_remaining -= self._offload_remaining + self._offload_remaining = 0. + self._storage_remaining = self.storage + self._offloading = False + self._transiting = True + else: + self._offload_remaining -= self._time_remaining + self._time_remaining = 0. + + def weather_elements(self, sc, time_step, model_time): + ''' + Remove mass from each le equally for now, no flagging for now + just make sure the mass is from floating oil. + ''' + if not self.active or len(sc) == 0: + return + + les = sc.itersubstancedata(self.array_types) + for substance, data in les: + if len(data['mass']) is 0: + continue + + if hasattr(self, '_ts_oil_collected') and self._ts_oil_collected is not None: + sc.mass_balance['skimmed'] += self._ts_oil_collected + self._remove_mass_simple(data, self._ts_oil_collected) + + self.logger.debug('{0} amount boomed for {1}: {2}' + .format(self._pid, substance.name, self._ts_oil_collected)) + + platform_balance = sc.mass_balance[self.id] + platform_balance['fluid_collected'] += self._ts_fluid_collected + platform_balance['emulsion_collected'] += self._ts_emulsion_collected + platform_balance['oil_collected'] += self._ts_oil_collected + platform_balance['water_collected'] += self._ts_water_collected + platform_balance['water_retained'] += self._ts_water_retained + platform_balance['water_decanted'] += self._ts_water_decanted + platform_balance['area_covered'] += self._ts_area_covered + platform_balance['storage_remaining'] += self._storage_remaining + + + def _getRecoveryEfficiency(self): + # scaffolding method + # will eventually include logic for calculating + # recovery efficiency based on wind and oil visc. + + return self.recovery_ef + +if __name__ == '__main__': + print None + d = Disperse(name = 'test') + p = Platform(_name='Test Platform') + import pprint as pp + ser = p.serialize() + pp.pprint(ser) + deser = Platform.deserialize(ser) + + pp.pprint(deser) + + p2 = Platform.new_from_dict(deser) + ser2 = p2.serialize() + pp.pprint(ser2) + + print 'INCORRECT BELOW' + + for k, v in ser.items(): + if p2.serialize()[k] != v: + print p2.serialize()[k] + + pass diff --git a/py_gnome/gnome/weatherers/weathering_data.py b/py_gnome/gnome/weatherers/weathering_data.py index ce3b60828..2e1f436f2 100644 --- a/py_gnome/gnome/weatherers/weathering_data.py +++ b/py_gnome/gnome/weatherers/weathering_data.py @@ -55,6 +55,7 @@ def __init__(self, water, **kwargs): self.water = water self.array_types = {'fate_status', 'positions', 'status_codes', 'density', 'viscosity', 'mass_components', 'mass', + 'oil_density', 'oil_viscosity', 'init_mass', 'frac_water', 'frac_lost', 'age'} # following used to update viscosity @@ -152,6 +153,7 @@ def weather_elements(self, sc, time_step, model_time): .format(self._pid)) data['density'] = new_rho + data['oil_density'] = oil_rho # following implementation results in an extra array called # fw_d_fref but is easy to read @@ -165,6 +167,8 @@ def weather_elements(self, sc, time_step, model_time): np.exp(kv1 * data['frac_lost']) * (1 + (fw_d_fref / (1.187 - fw_d_fref))) ** 2.49 ) + data['oil_viscosity'] = (v0 * + np.exp(kv1 * data['frac_lost']) ) sc.update_from_fatedataview(fate='all') @@ -262,8 +266,10 @@ def _init_new_particles(self, mask, data, substance): self.logger.error(msg) data['density'][mask] = self.water.get('density') + data['oil_density'][mask] = self.water.get('density') else: data['density'][mask] = density + data['oil_density'][mask] = density # initialize mass_components - # sub-select mass_components array by substance.num_components. @@ -284,6 +290,7 @@ def _init_new_particles(self, mask, data, substance): if substance_kvis is not None: 'make sure we do not add NaN values' data['viscosity'][mask] = substance_kvis + data['oil_viscosity'][mask] = substance_kvis # initialize the fate_status array based on positions and status_codes self._init_fate_status(mask, data) diff --git a/py_gnome/log_config.json b/py_gnome/log_config.json deleted file mode 100644 index 29472f129..000000000 --- a/py_gnome/log_config.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "version": 1, - "disbale_existing_loggers": true, - "root": - { - "level": "INFO", - "handlers": ["console", "file"] - }, - "formatters": - { - "simple": - { - "format": "format=%(asctime)s - %(name)s - %(levelname)s - %(message)s", - "datefmt": "%Y-%m-%d %H:%M:%S" - }, - "brief": - { - "format": "%(levelname)-8s: %(name)-28s: %(message)s", - "datefmt": "%Y-%m-%d %H:%M:%S" - }, - "precise": - { - "format": "%(asctime)s %(name)-28s %(levelname)-8s %(message)s", - "datefmt": "%Y-%m-%d %H:%M:%S" - } - }, - "handlers": - { - "console": - { - "class": "logging.StreamHandler", - "level": "DEBUG", - "formatter": "brief" - }, - "file": - { - "class": "logging.handlers.RotatingFileHandler", - "formatter": "precise", - "filename": "logfile.log", - "maxBytes": 1000000, - "backupCount": 3 - } - } -} diff --git a/py_gnome/re_link_for_anaconda.py b/py_gnome/re_link_for_anaconda.py index 3dc3e885c..5ae850e58 100644 --- a/py_gnome/re_link_for_anaconda.py +++ b/py_gnome/re_link_for_anaconda.py @@ -42,7 +42,15 @@ def remove_local_dir_from_path(): def get_conda_lib_path(lib_name): - return os.path.join(sys.exec_prefix, 'lib', lib_name) + lib_path = os.path.join(sys.exec_prefix, 'lib', lib_name) + if not os.path.isfile(lib_path): + # if we are in an anaconda virtual environment, we might need to + # strip the last two folders (envs/) to get to the main + # anaconda lib file. + base_exec_prefix = os.sep.join(sys.exec_prefix.split(os.sep)[:-2]) + lib_path = os.path.join(base_exec_prefix, 'lib', lib_name) + + return lib_path def find_cy_gnome_library(shared_lib): diff --git a/py_gnome/scripts/script_TAP/script_new_TAP.py b/py_gnome/scripts/script_TAP/script_new_TAP.py index 510d84ebc..cfdbac374 100644 --- a/py_gnome/scripts/script_TAP/script_new_TAP.py +++ b/py_gnome/scripts/script_TAP/script_new_TAP.py @@ -15,17 +15,18 @@ from gnome.model import Model from gnome.map import MapFromBNA -from gnome.environment import Wind +from gnome.environment import Environment from gnome.spill import point_line_release_spill from gnome.movers import RandomMover, constant_wind_mover, GridCurrentMover, IceAwareRandomMover -from gnome.environment import IceAwareCurrent, IceAwareWind +from gnome.environment import IceAwareCurrent, IceAwareWind, GridCurrent from gnome.movers.py_wind_movers import PyWindMover -from gnome.movers.py_current_movers import PyGridCurrentMover +from gnome.movers.py_current_movers import PyCurrentMover from gnome.outputters import Renderer, NetCDFOutput from gnome.environment.vector_field import ice_field import gnome.utilities.profiledeco as pd +from gnome.environment.environment_objects import IceVelocity # define base directory base_dir = os.path.dirname(__file__) @@ -40,7 +41,7 @@ def make_model(images_dir=os.path.join(base_dir, 'images')): # 1/2 hr in seconds model = Model(start_time=start_time, duration=timedelta(days=4), - time_step=3600) + time_step=7200) # mapfile = get_datafile(os.path.join(base_dir, 'ak_arctic.bna')) mapfile = get_datafile('arctic_coast3.bna') @@ -77,6 +78,9 @@ def make_model(images_dir=os.path.join(base_dir, 'images')): fn = ['arctic_avg2_0001_gnome.nc', 'arctic_avg2_0002_gnome.nc'] +# fn = ['C:\\Users\\jay.hennen\\Documents\\Code\\pygnome\\py_gnome\\scripts\\script_TAP\\arctic_avg2_0001_gnome.nc', +# 'C:\\Users\\jay.hennen\\Documents\\Code\\pygnome\\py_gnome\\scripts\\script_TAP\\arctic_avg2_0002_gnome.nc'] + gt = {'node_lon': 'lon', 'node_lat': 'lat'} # fn='arctic_avg2_0001_gnome.nc' @@ -88,30 +92,42 @@ def make_model(images_dir=os.path.join(base_dir, 'images')): # draw_ontop can be 'uncertain' or 'forecast' # 'forecast' LEs are in black, and 'uncertain' are in red # default is 'forecast' LEs draw on top -# renderer = Renderer(mapfile, images_dir, image_size=(1024, 768)) -# model.outputters += renderer + renderer = Renderer(mapfile, images_dir, image_size=(1024, 768)) + model.outputters += renderer netcdf_file = os.path.join(base_dir, str(model.time_step / 60) + method + '.nc') scripting.remove_netcdf(netcdf_file) print 'adding movers' model.outputters += NetCDFOutput(netcdf_file, which_data='all') - load = False print 'loading entire current data' ice_aware_curr = IceAwareCurrent.from_netCDF(filename=fn, - grid_topology=gt, - load_all=load) - print 'loading entire wind data' + grid_topology=gt) + +# env1 = get_env_from_netCDF(filename) +# mov = PyCurrentMover.from_netCDF(filename) + + ice_aware_curr.ice_velocity.variables[0].dimension_ordering = ['time', 'x', 'y'] ice_aware_wind = IceAwareWind.from_netCDF(filename=fn, - ice_var=ice_aware_curr.ice_var, - ice_conc_var=ice_aware_curr.ice_conc_var, - grid=ice_aware_curr.grid, - load_all=load) - -# i_c_mover = PyGridCurrentMover(current=ice_aware_curr) -# i_c_mover = PyGridCurrentMover(current=ice_aware_curr, default_num_method='Euler') - i_c_mover = PyGridCurrentMover(current=ice_aware_curr, default_num_method=method, extrapolate=True) + ice_velocity=ice_aware_curr.ice_velocity, + ice_concentration=ice_aware_curr.ice_concentration, + grid=ice_aware_curr.grid) + + curr = GridCurrent.from_netCDF(filename=fn) +# GridCurrent.is_gridded() + +# import pprint as pp +# from gnome.utilities.orderedcollection import OrderedCollection +# model.environment = OrderedCollection(dtype=Environment) +# model.environment.add(ice_aware_curr) +# from gnome.environment import WindTS + + print 'loading entire wind data' + +# i_c_mover = PyCurrentMover(current=ice_aware_curr) +# i_c_mover = PyCurrentMover(current=ice_aware_curr, default_num_method='Euler') + i_c_mover = PyCurrentMover(current=ice_aware_curr, default_num_method=method, extrapolate=True) i_w_mover = PyWindMover(wind=ice_aware_wind, default_num_method=wind_method) # ice_aware_curr.grid.node_lon = ice_aware_curr.grid.node_lon[:]-360 @@ -120,16 +136,23 @@ def make_model(images_dir=os.path.join(base_dir, 'images')): model.movers += i_w_mover print 'adding an IceAwareRandomMover:' - model.movers += IceAwareRandomMover(ice_conc_var=ice_aware_curr.ice_conc_var, + model.movers += IceAwareRandomMover(ice_concentration=ice_aware_curr.ice_concentration, diffusion_coef=1000) # renderer.add_grid(ice_aware_curr.grid) # renderer.add_vec_prop(ice_aware_curr) -# renderer.set_viewport(((-190.9, 60), (-72, 89))) # curr_file = get_datafile(os.path.join(base_dir, 'COOPSu_CREOFS24.nc')) # c_mover = GridCurrentMover(curr_file) # model.movers += c_mover +# model.environment.add(WindTS.constant(10, 300)) +# print('Saving') +# model.environment[0].ice_velocity.variables[0].serialize() +# IceVelocity.deserialize(model.environment[0].ice_velocity.serialize()) +# model.save('.') +# from gnome.persist.save_load import load +# print('Loading') +# model2 = load('./Model.zip') return model diff --git a/py_gnome/scripts/script_TAP/script_old_TAP.py b/py_gnome/scripts/script_TAP/script_old_TAP.py index 9b8a934f5..4c9966177 100644 --- a/py_gnome/scripts/script_TAP/script_old_TAP.py +++ b/py_gnome/scripts/script_TAP/script_old_TAP.py @@ -23,7 +23,7 @@ from gnome.movers.py_wind_movers import PyWindMover from gnome.environment.property_classes import WindTS, IceAwareCurrent, IceAwareWind -from gnome.movers.py_current_movers import PyGridCurrentMover +from gnome.movers.py_current_movers import PyCurrentMover from gnome.outputters import Renderer, NetCDFOutput from gnome.environment.vector_field import ice_field @@ -118,9 +118,9 @@ def make_model(images_dir=os.path.join(base_dir, 'images')): # grid = ice_aware_curr.grid,) # method = 'Trapezoid' # -# # i_c_mover = PyGridCurrentMover(current=ice_aware_curr) -# # i_c_mover = PyGridCurrentMover(current=ice_aware_curr, default_num_method='Euler') -# i_c_mover = PyGridCurrentMover(current=ice_aware_curr, default_num_method=method) +# # i_c_mover = PyCurrentMover(current=ice_aware_curr) +# # i_c_mover = PyCurrentMover(current=ice_aware_curr, default_num_method='Euler') +# i_c_mover = PyCurrentMover(current=ice_aware_curr, default_num_method=method) # i_w_mover = PyWindMover(wind = ice_aware_wind, default_num_method=method) # # ice_aware_curr.grid.node_lon = ice_aware_curr.grid.node_lon[:]-360 diff --git a/py_gnome/scripts/script_animation_demo/script_animation_demo.py b/py_gnome/scripts/script_animation_demo/script_animation_demo.py index fc100f0be..5c859d4e5 100644 --- a/py_gnome/scripts/script_animation_demo/script_animation_demo.py +++ b/py_gnome/scripts/script_animation_demo/script_animation_demo.py @@ -54,7 +54,7 @@ def make_model(images_dir=os.path.join(base_dir, 'images')): renderer.set_timestamp_attrib(format='%a %c') renderer.graticule.set_DMS(True) # renderer.viewport = ((-124.25, 47.5), (-122.0, 48.70)) - + print 'adding outputters' model.outputters += renderer @@ -69,23 +69,23 @@ def make_model(images_dir=os.path.join(base_dir, 'images')): 0.0, 0.0), release_time=start_time) - + model.spills += spill1 print 'adding a RandomMover:' model.movers += RandomMover(diffusion_coef=50000) print 'adding a wind mover:' - + model.movers += constant_wind_mover(13, 270, units='m/s') print 'adding a current mover:' # curr_file = get_datafile(os.path.join(base_dir, 'COOPSu_CREOFS24.nc')) -# +# # # uncertain_time_delay in hours # c_mover = GridCurrentMover(curr_file) # c_mover.uncertain_cross = 0 # default is .25 -# +# # model.movers += c_mover return model @@ -107,4 +107,4 @@ def make_model(images_dir=os.path.join(base_dir, 'images')): print "step: %.4i -- memuse: %fMB" % (step['step_num'], utilities.get_mem_use()) print datetime.now() - startTime - pd.print_stats(5) \ No newline at end of file +# pd.print_stats(5) \ No newline at end of file diff --git a/py_gnome/scripts/script_ice/script_ice.py b/py_gnome/scripts/script_ice/script_ice.py index f3bbf1f8d..1ad55bc53 100644 --- a/py_gnome/scripts/script_ice/script_ice.py +++ b/py_gnome/scripts/script_ice/script_ice.py @@ -25,7 +25,7 @@ from gnome.environment import IceAwareCurrent, IceAwareWind from gnome.movers.py_wind_movers import PyWindMover -from gnome.movers.py_current_movers import PyGridCurrentMover +from gnome.movers.py_current_movers import PyCurrentMover from gnome.outputters import Renderer, NetCDFOutput from gnome.environment.vector_field import ice_field @@ -105,9 +105,9 @@ def make_model(images_dir=os.path.join(base_dir, 'images')): grid=ice_aware_curr.grid,) method = 'Trapezoid' -# i_c_mover = PyGridCurrentMover(current=ice_aware_curr) -# i_c_mover = PyGridCurrentMover(current=ice_aware_curr, default_num_method='Euler') - i_c_mover = PyGridCurrentMover(current=ice_aware_curr, default_num_method=method) +# i_c_mover = PyCurrentMover(current=ice_aware_curr) +# i_c_mover = PyCurrentMover(current=ice_aware_curr, default_num_method='Euler') + i_c_mover = PyCurrentMover(current=ice_aware_curr, default_num_method=method) i_w_mover = PyWindMover(wind=ice_aware_wind, default_num_method=method) ice_aware_curr.grid.node_lon = ice_aware_curr.grid.node_lon[:] - 360 diff --git a/py_gnome/scripts/script_ny_plume/script_ny_plume.py b/py_gnome/scripts/script_ny_plume/script_ny_plume.py index 576175482..6f71efbca 100644 --- a/py_gnome/scripts/script_ny_plume/script_ny_plume.py +++ b/py_gnome/scripts/script_ny_plume/script_ny_plume.py @@ -35,7 +35,7 @@ from gnome.outputters import Renderer from gnome.outputters import NetCDFOutput -from gnome.movers.py_current_movers import PyGridCurrentMover +from gnome.movers.py_current_movers import PyCurrentMover import gnome.utilities.profiledeco as pd # define base directory @@ -132,7 +132,7 @@ def make_model(images_dir=os.path.join(base_dir, 'images')): url = ('http://geoport.whoi.edu/thredds/dodsC/clay/usgs/users/jcwarner/Projects/Sandy/triple_nest/00_dir_NYB05.ncml') gc = GridCurrent.from_netCDF(url) - u_mover = PyGridCurrentMover(gc, default_num_method='Trapezoid') + u_mover = PyCurrentMover(gc, default_num_method='Trapezoid') model.movers += u_mover # print 'adding a wind mover:' diff --git a/py_gnome/scripts/script_ny_roms/script_ny_roms.py b/py_gnome/scripts/script_ny_roms/script_ny_roms.py index 318ebc312..101bd8c15 100644 --- a/py_gnome/scripts/script_ny_roms/script_ny_roms.py +++ b/py_gnome/scripts/script_ny_roms/script_ny_roms.py @@ -4,7 +4,7 @@ This script uses: - GridCurrent -- PyGridCurrentMover +- PyCurrentMover - rendering of GridCurrent using Renderer """ @@ -28,7 +28,7 @@ from gnome.movers import RandomMover, constant_wind_mover, GridCurrentMover from gnome.outputters import Renderer -from gnome.movers.py_current_movers import PyGridCurrentMover +from gnome.movers.py_current_movers import PyCurrentMover import gnome.utilities.profiledeco as pd # define base directory @@ -91,7 +91,7 @@ def make_model(images_dir=os.path.join(base_dir, 'images')): cf = GridCurrent.from_netCDF(url) renderer.add_grid(cf.grid) renderer.delay = 25 - u_mover = PyGridCurrentMover(cf, default_num_method='Euler') + u_mover = PyCurrentMover(cf, default_num_method='Euler') model.movers += u_mover # curr_file = get_datafile(os.path.join(base_dir, 'COOPSu_CREOFS24.nc')) diff --git a/py_gnome/scripts/script_plume/script_plume.py b/py_gnome/scripts/script_plume/script_plume.py index c915b97f2..cbadacd41 100644 --- a/py_gnome/scripts/script_plume/script_plume.py +++ b/py_gnome/scripts/script_plume/script_plume.py @@ -81,7 +81,8 @@ def make_model(images_dir=os.path.join(base_dir, 'images')): amount=90, # default volume_units=m^3 units='m^3', end_release_time=end_time, - density=600) + substance='oil_crude') + #density=600) model.spills += spill diff --git a/py_gnome/scripts/script_tamoc/script_arctic_tamoc.py b/py_gnome/scripts/script_tamoc/script_arctic_tamoc.py index 749b4e341..76a541fe2 100644 --- a/py_gnome/scripts/script_tamoc/script_arctic_tamoc.py +++ b/py_gnome/scripts/script_tamoc/script_arctic_tamoc.py @@ -37,7 +37,7 @@ RandomVerticalMover, SimpleMover, GridCurrentMover, - PyGridCurrentMover, + PyCurrentMover, constant_wind_mover, IceMover) diff --git a/py_gnome/scripts/script_tamoc/script_gulf_tamoc.py b/py_gnome/scripts/script_tamoc/script_gulf_tamoc.py index 72e3530d0..80230e2ac 100644 --- a/py_gnome/scripts/script_tamoc/script_gulf_tamoc.py +++ b/py_gnome/scripts/script_tamoc/script_gulf_tamoc.py @@ -38,7 +38,7 @@ RandomVerticalMover, SimpleMover, GridCurrentMover, - PyGridCurrentMover, + PyCurrentMover, constant_wind_mover, WindMover) diff --git a/py_gnome/scripts/script_tamoc/script_tamoc.py b/py_gnome/scripts/script_tamoc/script_tamoc.py index d5cb2575c..23d7457b2 100755 --- a/py_gnome/scripts/script_tamoc/script_tamoc.py +++ b/py_gnome/scripts/script_tamoc/script_tamoc.py @@ -36,7 +36,7 @@ RiseVelocityMover, RandomVerticalMover, SimpleMover, - PyGridCurrentMover) + PyCurrentMover) from gnome.outputters import Renderer from gnome.outputters import NetCDFOutput @@ -113,7 +113,7 @@ def make_model(images_dir=os.path.join(base_dir, 'images')): print 'adding a circular current and eastward current' # This is .3 m/s south - model.movers += PyGridCurrentMover(current=vg, + model.movers += PyCurrentMover(current=vg, default_num_method='Trapezoid', extrapolate=True) model.movers += SimpleMover(velocity=(0., -0.1, 0.)) diff --git a/py_gnome/scripts/script_weatherers/script_weatherers.py b/py_gnome/scripts/script_weatherers/script_weatherers.py index 0175e6b98..1da91f16e 100644 --- a/py_gnome/scripts/script_weatherers/script_weatherers.py +++ b/py_gnome/scripts/script_weatherers/script_weatherers.py @@ -57,21 +57,21 @@ def make_model(images_dir=os.path.join(base_dir, 'images')): uncertain=True) # mapfile = get_datafile(os.path.join(base_dir, './ak_arctic.bna')) -# +# # print 'adding the map' # model.map = MapFromBNA(mapfile, refloat_halflife=1) # seconds -# +# # # draw_ontop can be 'uncertain' or 'forecast' # # 'forecast' LEs are in black, and 'uncertain' are in red # # default is 'forecast' LEs draw on top # renderer = Renderer(mapfile, images_dir, size=(800, 600), # output_timestep=timedelta(hours=2), # draw_ontop='forecast') -# +# # print 'adding outputters' # model.outputters += renderer - model.outputters += WeatheringOutput() + model.outputters += WeatheringOutput('.\\') netcdf_file = os.path.join(base_dir, 'script_weatherers.nc') scripting.remove_netcdf(netcdf_file) @@ -98,11 +98,11 @@ def make_model(images_dir=os.path.join(base_dir, 'images')): model.spills += spill print 'adding a RandomMover:' - #model.movers += RandomMover(diffusion_coef=50000) + # model.movers += RandomMover(diffusion_coef=50000) print 'adding a wind mover:' - series = np.zeros((2, ), dtype=datetime_value_2d) + series = np.zeros((2,), dtype=datetime_value_2d) series[0] = (start_time, (20, 0)) series[1] = (start_time + timedelta(hours=23), (20, 0)) @@ -129,20 +129,20 @@ def make_model(images_dir=os.path.join(base_dir, 'images')): active_start=burn_start, efficiency=.2) chem_start = start_time + timedelta(hours=24) - c_disp = ChemicalDispersion(0.5, efficiency=0.4, - active_start=chem_start, - active_stop=chem_start + timedelta(hours=8)) +# c_disp = ChemicalDispersion(0.5, efficiency=0.4, +# active_start=chem_start, +# active_stop=chem_start + timedelta(hours=8)) - model.environment += [Water(280.928), wind, waves] + model.environment += [Water(280.928), wind, waves] - model.weatherers += Evaporation(water,wind) + model.weatherers += Evaporation(water, wind) model.weatherers += Emulsification(waves) - model.weatherers += NaturalDispersion(waves,water) + model.weatherers += NaturalDispersion(waves, water) model.weatherers += skimmer1 model.weatherers += skimmer2 model.weatherers += burn - model.weatherers += c_disp +# model.weatherers += c_disp return model @@ -151,3 +151,4 @@ def make_model(images_dir=os.path.join(base_dir, 'images')): scripting.make_images_dir() model = make_model() model.full_run() + model.save('.') diff --git a/py_gnome/tests/unit_tests/conftest.py b/py_gnome/tests/unit_tests/conftest.py index cf5703ddd..59d8409af 100644 --- a/py_gnome/tests/unit_tests/conftest.py +++ b/py_gnome/tests/unit_tests/conftest.py @@ -24,7 +24,7 @@ from gnome.spill_container import SpillContainer from gnome.movers import SimpleMover -from gnome.weatherers import Skimmer +# from gnome.weatherers import Skimmer from gnome.environment import constant_wind, Water, Waves from gnome.utilities.remote_data import get_datafile import gnome.array_types as gat @@ -190,7 +190,7 @@ def get_testdata(): curr_dir = os.path.join(s_data, 'currents') tide_dir = os.path.join(s_data, 'tides') wind_dir = os.path.join(s_data, 'winds') - testmap = os.path.join(base_dir, '../sample_data', 'MapBounds_Island.bna') + testmap = os.path.join(s_data, 'MapBounds_Island.bna') bna_sample = os.path.join(s_data, 'MapBounds_2Spillable2Islands2Lakes.bna') data = dict() @@ -522,8 +522,8 @@ def sample_sc_no_uncertainty(): spills = [gnome.spill.point_line_release_spill(num_elements, start_position, release_time, - water=water, - amount=10, units='l'), + amount=10, units='l', + water=water), gnome.spill.point_line_release_spill(num_elements, start_position, release_time_2, @@ -563,7 +563,8 @@ def sample_model(): # the image output map - mapfile = os.path.join(os.path.dirname(__file__), '../sample_data', + mapfile = os.path.join(os.path.dirname(__file__), + 'sample_data', 'MapBounds_Island.bna') # the land-water map @@ -677,7 +678,7 @@ def sample_model_weathering(sample_model_fcn, et = gnome.spill.elements.floating(substance=oil) start_time = model.start_time + timedelta(hours=1) - end_time = start_time + timedelta(seconds=model.time_step*3) + end_time = start_time + timedelta(seconds=model.time_step * 3) spill = gnome.spill.point_line_release_spill(num_les, rel_pos, start_time, diff --git a/py_gnome/tests/sample_data/MapBounds_Island.bna b/py_gnome/tests/unit_tests/sample_data/MapBounds_Island.bna similarity index 100% rename from py_gnome/tests/sample_data/MapBounds_Island.bna rename to py_gnome/tests/unit_tests/sample_data/MapBounds_Island.bna diff --git a/py_gnome/tests/unit_tests/sample_data/no_map_bounds.bna b/py_gnome/tests/unit_tests/sample_data/no_map_bounds.bna new file mode 100644 index 000000000..ed59284e3 --- /dev/null +++ b/py_gnome/tests/unit_tests/sample_data/no_map_bounds.bna @@ -0,0 +1,12 @@ +"land-1","1",5 +3, 10 +4, 10 +4, 11 +3, 11 +3, 10 +"SpillableArea","1",5 +3, 10 +6, 10 +6, 11 +3, 11 +3, 10 diff --git a/py_gnome/tests/sample_data/small_trigrid_example.nc b/py_gnome/tests/unit_tests/sample_data/small_trigrid_example.nc similarity index 100% rename from py_gnome/tests/sample_data/small_trigrid_example.nc rename to py_gnome/tests/unit_tests/sample_data/small_trigrid_example.nc diff --git a/py_gnome/tests/unit_tests/test_environment/sample_data/gen_analytical_datasets.py b/py_gnome/tests/unit_tests/test_environment/sample_data/gen_analytical_datasets.py new file mode 100644 index 000000000..962a790ed --- /dev/null +++ b/py_gnome/tests/unit_tests/test_environment/sample_data/gen_analytical_datasets.py @@ -0,0 +1,337 @@ +import numpy as np +import netCDF4 as nc4 + +from pysgrid import SGrid +from gnome.environment.grid_property import GriddedProp + +import os +from datetime import datetime, timedelta + + +from gnome import scripting +from gnome import utilities + + +from gnome.model import Model + +from gnome.spill import point_line_release_spill +from gnome.movers import RandomMover, constant_wind_mover, GridCurrentMover + +from gnome.environment import GridCurrent +from gnome.environment import PyGrid, PyGrid_U +from gnome.movers.py_current_movers import PyCurrentMover + +from gnome.outputters import Renderer, NetCDFOutput + +def gen_vortex_3D(filename=None): + x, y = np.mgrid[-30:30:61j, -30:30:61j] + y = np.ascontiguousarray(y.T) + x = np.ascontiguousarray(x.T) + x_size = 61 + y_size = 61 + g = PyGrid(node_lon=x, + node_lat=y) + g.build_celltree() + lin_nodes = g._trees['node'][1] + lin_faces = np.array([np.array([([lx, lx + x_size + 1, lx + 1], [lx, lx + x_size, lx + x_size + 1]) for lx in range(0, x_size - 1, 1)]) + ly * x_size for ly in range(0, y_size - 1)]) + lin_faces = lin_faces.reshape(-1, 3) + # y += np.sin(x) / 1 + # x += np.sin(x) / 5 + + t0 = datetime(2001, 1, 1, 0, 0) + tarr = [t0 + timedelta(hours=i) for i in range(0, 11)] + angs = -np.arctan2(y, x) + mag = np.sqrt(x ** 2 + y ** 2) + vx = np.cos(angs) * mag + vy = np.sin(angs) * mag + vx = vx[np.newaxis, :] * 20 + vy = vy[np.newaxis, :] * 20 + vw = -0.001 + + d_scale = [1, 0.5, 0, -0.5, -1] + t_scale = np.linspace(0, 1, 11) + + tvx = np.array([vx * t for t in t_scale]).squeeze() + tvy = np.array([vy * t for t in t_scale]).squeeze() + + dvx = np.array([vx * s for s in d_scale]).squeeze() + dvy = np.array([vy * s for s in d_scale]).squeeze() + + tdvx = np.array([dvx * t for t in t_scale]).squeeze() + tdvy = np.array([dvy * t for t in t_scale]).squeeze() + + lin_vx = vx.reshape(-1) + lin_vy = vy.reshape(-1) + + lin_tvx = np.array([lin_vx * t for t in t_scale]) + lin_tvy = np.array([lin_vy * t for t in t_scale]) + + lin_dvx = np.array([lin_vx * s for s in d_scale]) + lin_dvy = np.array([lin_vy * s for s in d_scale]) + + lin_tdvx = np.array([lin_dvx * t for t in t_scale]) + lin_tdvy = np.array([lin_dvy * t for t in t_scale]) + + ds = None + if filename is not None: + ds = nc4.Dataset(filename, 'w', diskless=True, persist=True) + ds.createDimension('y', y.shape[0]) + ds.createDimension('x', x.shape[1]) + ds.createDimension('time', len(tarr)) + ds.createDimension('depth', len(d_scale)) + ds.createVariable('x', 'f8', dimensions=('x', 'y')) + ds['x'][:] = x + ds.createVariable('y', 'f8', dimensions=('x', 'y')) + ds['y'][:] = y + ds.createVariable('time', 'f8', dimensions=('time')) + ds['time'][:] = nc4.date2num(tarr, 'hours since {0}'.format(t0)) + ds['time'].setncattr('units', 'hours since {0}'.format(t0)) + ds.createVariable('vx', 'f8', dimensions=('x', 'y')) + ds.createVariable('vy', 'f8', dimensions=('x', 'y')) + ds['vx'][:] = vx + ds['vy'][:] = vy + ds.createVariable('tvx', 'f8', dimensions=('time', 'x', 'y')) + ds.createVariable('tvy', 'f8', dimensions=('time', 'x', 'y')) + ds['tvx'][:] = tvx + ds['tvy'][:] = tvy + ds.createVariable('dvx', 'f8', dimensions=('depth', 'x', 'y')) + ds.createVariable('dvy', 'f8', dimensions=('depth', 'x', 'y')) + ds['dvx'][:] = dvx + ds['dvy'][:] = dvy + ds.createVariable('tdvx', 'f8', dimensions=('time', 'depth', 'x', 'y')) + ds.createVariable('tdvy', 'f8', dimensions=('time', 'depth', 'x', 'y')) + ds['tdvx'][:] = tdvx + ds['tdvy'][:] = tdvy + for v in ds.variables: + if 'v' in v: + ds[v].units = 'm/s' + + ds.createDimension('nv', lin_nodes.shape[0]) + ds.createDimension('nele', lin_faces.shape[0]) + ds.createDimension('two', 2) + ds.createDimension('three', 3) + ds.createVariable('nodes', 'f8', dimensions=('nv', 'two')) + ds.createVariable('faces', 'f8', dimensions=('nele', 'three')) + ds.createVariable('lin_vx', 'f8', dimensions=('nv')) + ds.createVariable('lin_vy', 'f8', dimensions=('nv')) + ds.createVariable('lin_tvx', 'f8', dimensions=('time', 'nv')) + ds.createVariable('lin_tvy', 'f8', dimensions=('time', 'nv')) + ds.createVariable('lin_dvx', 'f8', dimensions=('depth', 'nv')) + ds.createVariable('lin_dvy', 'f8', dimensions=('depth', 'nv')) + ds.createVariable('lin_tdvx', 'f8', dimensions=('time', 'depth', 'nv')) + ds.createVariable('lin_tdvy', 'f8', dimensions=('time', 'depth', 'nv')) + for k, v in {'nodes': lin_nodes, + 'faces': lin_faces, + 'lin_vx': lin_vx, + 'lin_vy': lin_vy, + 'lin_tvx': lin_tvx, + 'lin_tvy': lin_tvy, + 'lin_dvx': lin_dvx, + 'lin_dvy': lin_dvy, + 'lin_tdvx': lin_tdvx, + 'lin_tdvy': lin_tdvy + }.items(): + ds[k][:] = v + if 'lin' in k: + ds[k].units = 'm/s' + PyGrid._get_grid_type(ds, grid_topology={'node_lon': 'x', 'node_lat': 'y'}) + PyGrid._get_grid_type(ds) + ds.setncattr('grid_type', 'sgrid') + if ds is not None: + # Need to test the dataset... + from gnome.environment import GridCurrent + from gnome.environment.grid_property import GriddedProp + sgt = {'node_lon': 'x', 'node_lat': 'y'} + sg = PyGrid.from_netCDF(dataset=ds, grid_topology=sgt, grid_type='sgrid') + sgc1 = GridCurrent.from_netCDF(dataset=ds, varnames=['vx', 'vy'], grid_topology=sgt) + sgc2 = GridCurrent.from_netCDF(dataset=ds, varnames=['tvx', 'tvy'], grid_topology=sgt) + sgc3 = GridCurrent.from_netCDF(dataset=ds, varnames=['dvx', 'dvy'], grid_topology=sgt) + sgc4 = GridCurrent.from_netCDF(dataset=ds, varnames=['tdvx', 'tdvy'], grid_topology=sgt) + + ugt = {'nodes': 'nodes', 'faces': 'faces'} +# ug = PyGrid_U(nodes=ds['nodes'][:], faces=ds['faces'][:]) + ugc1 = GridCurrent.from_netCDF(dataset=ds, varnames=['lin_vx', 'lin_vy'], grid_topology=ugt) + ugc2 = GridCurrent.from_netCDF(dataset=ds, varnames=['lin_tvx', 'lin_tvy'], grid_topology=ugt) + ugc3 = GridCurrent.from_netCDF(dataset=ds, varnames=['lin_dvx', 'lin_dvy'], grid_topology=ugt) + ugc4 = GridCurrent.from_netCDF(dataset=ds, varnames=['lin_tdvx', 'lin_tdvy'], grid_topology=ugt) + + ds.close() + return {'sgrid': (x, y), + 'sgrid_vel': (dvx, dvy), + 'sgrid_depth_vel': (tdvx, tdvy), + 'ugrid': (lin_nodes, lin_faces), + 'ugrid_depth_vel': (lin_tdvx, lin_tdvy)} + + +def gen_sinusoid(filename=None): +# from mpl_toolkits.mplot3d import axes3d +# import matplotlib.pyplot as plt +# import numpy as np +# fig = plt.figure() +# ax = fig.add_subplot(111, projection='3d', zlim=[-2, 2], xlim=[0, 25], ylim=[-2, 2]) +# ax.autoscale(False) + + y, x = np.mgrid[-1:1:5j, 0:(6 * np.pi):25j] + y = y + np.sin(x / 2) + Z = np.zeros_like(x) + # abs(np.sin(x / 2)) + + vx = np.ones_like(x) + vy = np.cos(x / 2) / 2 + vz = np.zeros_like(x) +# ax.plot_wireframe(x, y, Z, rstride=1, cstride=1, color='blue') +# ax.quiver(x, y, Z, vx, vy, vz, length=0.5, arrow_length_ratio=0.2, color='darkblue', pivot='tail') +# ax.quiver(x, y, vx, vy, color='darkblue', pivot='tail', angles='xy', scale=1.5, scale_units='xy', width=0.0025) +# ax.plot(x[2], y[2]) + rho = {'r_grid': (x, y, Z), + 'r_vel': (vx, vy, vz)} + + yc, xc = np.mgrid[-0.75:0.75: 4j, 0.377:18.493:24j] + yc = yc + np.sin(xc / 2) + zc = np.zeros_like(xc) + 0.025 + vxc = np.ones_like(xc) + vyc = np.cos(xc / 2) / 2 + vzc = np.zeros_like(xc) +# ax.plot_wireframe(xc, yc, zc, rstride=1, cstride=1, color="red") +# ax.quiver(xc, yc, zc, vxc, vyc, vzc, length=0.3, arrow_length_ratio=0.2, color='darkred', pivot='tail') + psi = {'p_grid': (xc, yc, zc), + 'p_vel': (vxc, vyc, vzc)} + + yu, xu = np.mgrid[-1:1:5j, 0.377:18.493:24j] + yu = yu + np.sin(xu / 2) + zu = np.zeros_like(xu) + 0.05 + vxu = np.ones_like(xu) * 2 + vyu = np.zeros_like(xu) + vzu = np.zeros_like(xu) +# ax.plot_wireframe(xu, yu, zu, rstride=1, cstride=1, color="purple") +# ax.quiver(xu, yu, zu, vxu, vyu, vzu, length=0.3, arrow_length_ratio=0.2, color='indigo', pivot='tail') + u = {'u_grid': (xu, yu, zu), + 'u_vel': (vzu, vxu, vzu)} + + yv, xv = np.mgrid[-0.75:0.75: 4j, 0:18.87:25j] + yv = yv + np.sin(xv / 2) + zv = np.zeros_like(xv) + 0.075 + vxv = np.zeros_like(xv) + vyv = np.cos(xv / 2) / 2 + vzv = np.zeros_like(xv) +# ax.plot_wireframe(xv, yv, zv, rstride=1, cstride=1, color="y") +# ax.quiver(xv, yv, zv, vxv, vyv, vzv, length=0.3, arrow_length_ratio=0.2, color='olive', pivot='tail') + v = {'v_grid': (xv, yv, zv), + 'v_vel': (vyv, vzv, vzv)} + + angle = np.cos(x / 2) / 2 + + ds = None + if filename is not None: + ds = nc4.Dataset(filename, 'w', diskless=True, persist=True) + for k, v in {'eta_psi': 24, + 'xi_psi': 4, + 'eta_rho': 25, + 'xi_rho': 5}.items(): + ds.createDimension(k, v) + for k, v in {'lon_rho': ('xi_rho', 'eta_rho', x), + 'lat_rho': ('xi_rho', 'eta_rho', y), + 'lon_psi': ('xi_psi', 'eta_psi', xc), + 'lat_psi': ('xi_psi', 'eta_psi', yc), + 'lat_u': ('xi_rho', 'eta_psi', xu), + 'lon_u': ('xi_rho', 'eta_psi', yu), + 'lat_v': ('xi_psi', 'eta_rho', xv), + 'lon_v': ('xi_psi', 'eta_rho', yv), + }.items(): + ds.createVariable(k, 'f8', dimensions=v[0:2]) + ds[k][:] = v[2] + for k, v in {'u_rho': ('xi_rho', 'eta_rho', vx), + 'v_rho': ('xi_rho', 'eta_rho', vy), + 'u_psi': ('xi_psi', 'eta_psi', vxc), + 'v_psi': ('xi_psi', 'eta_psi', vyc), + 'u': ('xi_rho', 'eta_psi', vxu), + 'v': ('xi_psi', 'eta_rho', vyv)}.items(): + ds.createVariable(k, 'f8', dimensions=v[0:2]) + ds[k][:] = v[2] + ds[k].units = 'm/s' + ds.grid_type = 'sgrid' + ds.createVariable('angle', 'f8', dimensions=('xi_rho', 'eta_rho')) + ds['angle'][:] = angle + if ds is not None: + # Need to test the dataset... +# from gnome.environment import GridCurrent +# sg = PyGrid.from_netCDF(dataset=ds) +# sgc1 = GridCurrent.from_netCDF(dataset=ds, varnames=['u_rho', 'v_rho'], grid=sg) +# sgc1.angle = None +# sgc2 = GridCurrent.from_netCDF(dataset=ds, varnames=['u_psi', 'v_psi'], grid=sg) +# sgc2.angle = None +# sgc3 = GridCurrent.from_netCDF(dataset=ds, grid=sg) + ds.close() + +# plt.show() + + +def gen_ring(filename=None): +# import matplotlib.pyplot as plt + import matplotlib.tri as tri + import math + + n_angles = 36 + n_radii = 6 + min_radius = 0.25 + radii = np.linspace(min_radius, 1, n_radii) + + angles = np.linspace(0, 2 * math.pi, n_angles, endpoint=False) + angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1) + angles[:, 1::2] += math.pi / n_angles +# print angles.shape + + x = (radii * np.cos(angles)).flatten() + y = (radii * np.sin(angles)).flatten() + z = (np.cos(radii) * np.cos(angles * 3.0)).flatten() + + # Create the Triangulation; no triangles so Delaunay triangulation created. + triang = tri.Triangulation(x, y) + # Mask off unwanted triangles. + xmid = x[triang.triangles].mean(axis=1) + ymid = y[triang.triangles].mean(axis=1) + mask = np.where(xmid * xmid + ymid * ymid < min_radius * min_radius, 1, 0) + triang.set_mask(mask) + faces = triang.get_masked_triangles() + + vx = (np.ones_like(radii) * np.cos(angles + np.pi)).flatten() + vy = (np.ones_like(radii) * np.sin(angles)).flatten() + + if filename is not None: + ds = nc4.Dataset(filename, 'w', diskless=True, persist=True) + ds.createDimension('nv', x.shape[0]) + ds.createDimension('nele', faces.shape[0]) + ds.createDimension('three', 3) + for k, v in {'node_lon': ('nv', x), + 'node_lat': ('nv', y), + 'faces': ('nele', 'three', faces), + 'u': ('nv', vx), + 'v': ('nv', vy)}.items(): + ds.createVariable(k, 'f8', dimensions=v[0:-1]) + ds[k][:] = v[-1] + ds[k].units = 'm/s' + + if ds is not None: +# gc = GridCurrent.from_netCDF(dataset=ds) +# print gc.grid.node_lon.shape +# print gc.grid.faces.shape + ds.close() + + # tripcolor plot. +# plt.figure() +# plt.gca().set_aspect('equal') +# plt.triplot(triang, 'bo-') +# plt.quiver(x, y, vy, vx) +# plt.title('triplot of Delaunay triangulation') + + +def gen_all(path=None): + filenames = ['staggered_sine_channel.nc', '3D_circular.nc', 'tri_ring.nc'] + if path is not None: + filenames = [os.path.join(path, fn) for fn in filenames] + for fn, func in zip(filenames, (gen_sinusoid, gen_vortex_3D, gen_ring)): + func(fn) + +if __name__ == '__main__': + gen_sinusoid('staggered_sine_channel.nc') + gen_vortex_3D('3D_circular.nc') + gen_ring('tri_ring.nc') diff --git a/py_gnome/tests/unit_tests/test_environment/test_environment.py b/py_gnome/tests/unit_tests/test_environment/test_environment.py index fce379807..99a24ba7a 100644 --- a/py_gnome/tests/unit_tests/test_environment/test_environment.py +++ b/py_gnome/tests/unit_tests/test_environment/test_environment.py @@ -8,19 +8,14 @@ from gnome.environment import Water from gnome.environment import TemperatureTS - -def test_Water_init(): - w = Water() - # no idea what this is about ! - # t = TemperatureTS(name='test', - # units='K', - # time=w.time, - # data=np.array([[300]])) - # assert w.temperature == t - assert w.salinity == 35.0 - w = Water(temperature=273, salinity=0) - assert w.temperature == 273.0 - assert w.salinity == 0.0 +# def test_Water_init(): +# w = Water() +# t = TemperatureTS(name='test', units='K', time=[w.temperature.time], data=np.array([[300]])) +# assert w.temperature == t +# assert w.salinity == 35.0 +# w = Water(temperature=273, salinity=0) +# assert w.temperature == 273.0 +# assert w.salinity == 0.0 # currently salinity only have psu in there since there is no conversion from @@ -43,7 +38,7 @@ def test_exceptions(attr, unit): @pytest.mark.parametrize(("attr", "unit", "val", "si_val"), - [('temperature', 'C', 0, 273.16), + [('temperature', 'C', 0, 273.15), ('sediment', 'mg/l', 5, 0.005), ('sediment', 'percent', 0.005, 0.05), ('wave_height', 'cm', 100.0, 1.0), @@ -98,7 +93,7 @@ def test_Water_update_from_dict(): @pytest.mark.parametrize(("attr", "unit", "val", "exp_si"), - [('temperature', 'C', 0, 273.16), + [('temperature', 'C', 0, 273.15), ('sediment', 'mg/l', 5, 0.005), ('wave_height', 'km', .001, 1), ('fetch', 'km', .01, 10), diff --git a/py_gnome/tests/unit_tests/test_environment/test_grid.py b/py_gnome/tests/unit_tests/test_environment/test_grid.py new file mode 100644 index 000000000..db56a9e42 --- /dev/null +++ b/py_gnome/tests/unit_tests/test_environment/test_grid.py @@ -0,0 +1,138 @@ +import os +import pytest +import datetime as dt +import numpy as np +import datetime +import netCDF4 as nc +from gnome.environment.grid import PyGrid, PyGrid_U, PyGrid_S +from gnome.utilities.remote_data import get_datafile +import pprint as pp + +@pytest.fixture() +def sg_data(): + base_dir = os.path.dirname(__file__) + s_data = os.path.join(base_dir, 'sample_data') + filename = os.path.join(s_data, 'currents') + filename = get_datafile(os.path.join(filename, 'tbofs_example.nc')) + return filename, nc.Dataset(filename) + +@pytest.fixture() +def sg_topology(): + return {'node_lon': 'lonc', + 'node_lat': 'latc'} + +@pytest.fixture() +def sg(): + return PyGrid.from_netCDF(sg_data()[0], sg_data()[1], grid_topology=sg_topology()) + +@pytest.fixture() +def ug_data(): + base_dir = os.path.dirname(__file__) + s_data = os.path.join(base_dir, 'sample_data') + filename = os.path.join(s_data, 'currents') + filename = get_datafile(os.path.join(filename, 'ChesBay.nc')) + return filename, nc.Dataset(filename) + +@pytest.fixture() +def ug_topology(): + pass + +@pytest.fixture() +def ug(): + return PyGrid.from_netCDF(ug_data()[0], ug_data()[1], grid_topology=ug_topology()) + +class TestPyGrid_S: + def test_construction(self, sg_data, sg_topology): + filename = sg_data[0] + dataset = sg_data[1] + grid_topology = sg_topology + sg = PyGrid_S.from_netCDF(filename, dataset, grid_topology=grid_topology) + assert sg.filename == filename + + sg2 = PyGrid_S.from_netCDF(filename) + assert sg2.filename == filename + + sg3 = PyGrid.from_netCDF(filename, dataset, grid_topology=grid_topology) + sg4 = PyGrid.from_netCDF(filename) + print sg3.shape + print sg4.shape + assert sg == sg3 + assert sg2 == sg4 + + def test_serialize(self, sg, sg_data, sg_topology): + filename = sg_data[0] + dataset = sg_data[1] + grid_topology = sg_topology + sg2 = PyGrid_S.from_netCDF(filename, dataset, grid_topology=grid_topology) +# pytest.set_trace() + print sg.serialize()['filename'] + print sg2.serialize()['filename'] + assert sg.serialize()['filename'] == sg2.serialize()['filename'] + + def test_deserialize(self, sg, sg_data, sg_topology): + filename = sg_data[0] + dataset = sg_data[1] + grid_topology = sg_topology + sg2 = PyGrid_S.from_netCDF(filename, dataset, grid_topology=grid_topology) + d_sg = PyGrid_S.new_from_dict(sg.serialize()) + + pp.pprint(sg.serialize()) + pp.pprint(d_sg.serialize()) + + assert sg.name == d_sg.name +# fn1 = 'C:\\Users\\jay.hennen\\Documents\\Code\\pygnome\\py_gnome\\scripts\\script_TAP\\arctic_avg2_0001_gnome.nc' +# fn2 = 'C:\\Users\\jay.hennen\\Documents\\Code\\pygnome\\py_gnome\\scripts\\script_columbia_river\\COOPSu_CREOFS24.nc' +# sg = PyGrid.from_netCDF(fn1) +# ug = PyGrid.from_netCDF(fn2) +# sg.save(".\\testzip.zip", name="testjson.json") +# ug.save_as_netcdf("./testug.nc") +# # sg.save_as_netcdf("./testug.nc") +# k = PyGrid_U.from_netCDF("./testug.nc") +# k2 = PyGrid_U.from_netCDF(fn2) +# c1 = PyGrid_S.from_netCDF(fn1) +# ug4 = PyGrid.from_netCDF("./testug.nc") +# sg2 = PyGrid.from_netCDF("./testsg.nc") +# ug2 = PyGrid.from_netCDF(fn2) +# +# ug3 = PyGrid.new_from_dict(ug.serialize(json_='save')) + +class TestPyGrid_U: + def test_construction(self, ug_data, ug_topology): + filename = ug_data[0] + dataset = ug_data[1] + grid_topology = ug_topology + ug = PyGrid_U.from_netCDF(filename, dataset, grid_topology=grid_topology) +# assert ug.filename == filename +# assert isinstance(ug.node_lon, nc.Variable) +# assert ug.node_lon.name == 'lonc' + + ug2 = PyGrid_U.from_netCDF(filename) + assert ug2.filename == filename +# assert isinstance(ug2.node_lon, nc.Variable) +# assert ug2.node_lon.name == 'lon' + + ug3 = PyGrid.from_netCDF(filename, dataset, grid_topology=grid_topology) + ug4 = PyGrid.from_netCDF(filename) + print ug3.shape + print ug4.shape + assert ug == ug3 + assert ug2 == ug4 + + def test_serialize(self, ug, ug_data, ug_topology): + filename = ug_data[0] + dataset = ug_data[1] + grid_topology = ug_topology + ug2 = PyGrid_U.from_netCDF(filename, dataset, grid_topology=grid_topology) + assert ug.serialize()['filename'] == ug2.serialize()['filename'] + + def test_deserialize(self, ug, ug_data, ug_topology): + filename = ug_data[0] + dataset = ug_data[1] + grid_topology = ug_topology + ug2 = PyGrid_U.from_netCDF(filename, dataset, grid_topology=grid_topology) + d_ug = PyGrid_U.new_from_dict(ug.serialize()) + + pp.pprint(ug.serialize()) + pp.pprint(d_ug.serialize()) + + assert ug.name == d_ug.name diff --git a/py_gnome/tests/unit_tests/test_environment/test_property.py b/py_gnome/tests/unit_tests/test_environment/test_property.py index ed3e2b770..6658c504e 100644 --- a/py_gnome/tests/unit_tests/test_environment/test_property.py +++ b/py_gnome/tests/unit_tests/test_environment/test_property.py @@ -1,107 +1,109 @@ import os +import sys import pytest import datetime as dt import numpy as np import pysgrid import datetime from gnome.environment.property import Time -from gnome.environment.grid_property import GriddedProp, GridVectorProp +from gnome.environment import GriddedProp, GridVectorProp from gnome.environment.ts_property import TimeSeriesProp, TSVectorProp from gnome.environment.environment_objects import (VelocityGrid, VelocityTS, Bathymetry, - S_Depth) + S_Depth_T1) +from gnome.environment.grid import PyGrid, PyGrid_S, PyGrid_U from gnome.utilities.remote_data import get_datafile from unit_conversion import NotSupportedUnitError import netCDF4 as nc import unit_conversion - +import pprint as pp base_dir = os.path.dirname(__file__) +sys.path.append(os.path.join(base_dir, 'sample_data')) +from gen_analytical_datasets import gen_all + + ''' Need to hook this up to existing test data infrastructure ''' s_data = os.path.join(base_dir, 'sample_data') -curr_dir = os.path.join(s_data, 'currents') -curr_file = get_datafile(os.path.join(curr_dir, 'tbofs_example.nc')) -dataset = nc.Dataset(curr_file) -node_lon = dataset['lonc'] -node_lat = dataset['latc'] -grid_u = dataset['water_u'] -grid_v = dataset['water_v'] -grid_time = dataset['time'] -test_grid = pysgrid.SGrid(node_lon=node_lon, - node_lat=node_lat) - - -dates = np.array([dt.datetime(2000, 1, 1, 0), - dt.datetime(2000, 1, 1, 2), - dt.datetime(2000, 1, 1, 4)]) -dates2 = np.array([dt.datetime(2000, 1, 1, 0), - dt.datetime(2000, 1, 1, 2), - dt.datetime(2000, 1, 1, 4), - dt.datetime(2000, 1, 1, 6), - dt.datetime(2000, 1, 1, 8), ]) -uv_units = 'm/s' -u_data = np.array([2., 4., 6., 8., 10.]) -v_data = np.array([5., 7., 9., 11., 13.]) - -s_data = np.array([20, 30, 40]) - - -@pytest.fixture() -def ts(): - return Time(dates2) +gen_all(path=s_data) -class TestTime: +sinusoid = os.path.join(s_data, 'staggered_sine_channel.nc') +sinusoid = nc.Dataset(sinusoid) - def test_construction(self): - t1 = Time(dates2) - assert all(dates2 == t1.time) +circular_3D = os.path.join(s_data, '3D_circular.nc') +circular_3D = nc.Dataset(circular_3D) + +tri_ring = os.path.join(s_data, 'tri_ring.nc') +tri_ring = nc.Dataset(tri_ring) - t2 = Time(grid_time) - assert len(t2.time) == 54 - def test_offset(self): - t = Time(dates2.copy(), tz_offset=dt.timedelta(hours=1)) - assert t.time[0] == dt.datetime(2000, 1, 1, 1) +class TestTime: + time_var = circular_3D['time'] + time_arr = nc.num2date(time_var[:], units=time_var.units) + + def test_construction(self): - def test_extrapolation(self, ts): - before = dt.datetime(1999, 12, 31, 23) - after = dt.datetime(2000, 1, 1, 9) + t1 = Time(TestTime.time_var) + assert all(TestTime.time_arr == t1.time) + + t2 = Time(TestTime.time_arr) + assert all(TestTime.time_arr == t2.time) + + t = Time(TestTime.time_var, tz_offset=dt.timedelta(hours=1)) + print TestTime.time_arr + print t.time + print TestTime.time_arr[0] + dt.timedelta(hours=1) + assert t.time[0] == (TestTime.time_arr[0] + dt.timedelta(hours=1)) + + t = Time(TestTime.time_arr.copy(), tz_offset=dt.timedelta(hours=1)) + assert t.time[0] == TestTime.time_arr[0] + dt.timedelta(hours=1) + + def test_save_load(self): + t1 = Time(TestTime.time_var) + fn = 'time.txt' + t1._write_time_to_file('time.txt') + t2 = Time.from_file(fn) +# pytest.set_trace() + assert all(t1.time == t2.time) + os.remove(fn) + + def test_extrapolation(self): + ts = Time(TestTime.time_var) + before = TestTime.time_arr[0] - dt.timedelta(hours=1) + after = TestTime.time_arr[-1] + dt.timedelta(hours=1) assert ts.index_of(before, True) == 0 - assert ts.index_of(after, True) == 5 - assert ts.index_of(ts.time[-1], True) == 4 + assert ts.index_of(after, True) == 11 + assert ts.index_of(ts.time[-1], True) == 10 assert ts.index_of(ts.time[0], True) == 0 with pytest.raises(ValueError): ts.index_of(before, False) with pytest.raises(ValueError): ts.index_of(after, False) - assert ts.index_of(ts.time[-1], True) == 4 + assert ts.index_of(ts.time[-1], True) == 10 assert ts.index_of(ts.time[0], True) == 0 + @pytest.mark.parametrize('_json_', ['save', 'webapi']) + def test_serialization(self, _json_): + ts = Time(TestTime.time_var) + ser = ts.serialize(_json_) + if _json_ == 'webapi': + deser = Time.deserialize(ser) + t2 = Time.new_from_dict(deser) + assert all(ts.data == t2.data) + assert 'data' in ser + else: + assert 'data' in ser - -@pytest.fixture() -def u(): - return TimeSeriesProp(name='u', units='m/s', time=dates2, data=u_data) - -@pytest.fixture() -def v(): - return TimeSeriesProp(name='v', units='m/s', time=dates2, data=v_data) - -@pytest.fixture() -def vp(): - return TSVectorProp(name='vp', units='m/s', time=dates2, variables=[u_data, v_data]) - - -class TestS_Depth: +class TestS_Depth_T1: def test_construction(self): - test_grid = pysgrid.SGrid(node_lon=np.array([[0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3]]), + test_grid = PyGrid_S(node_lon=np.array([[0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3]]), node_lat=np.array([[0, 0, 0, 0], [1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3]])) u = np.zeros((3, 4, 4), dtype=np.float64) @@ -128,7 +130,7 @@ def test_construction(self): b = Bathymetry(name='bathymetry', data=bathy_data, grid=test_grid, time=None) - dep = S_Depth(bathymetry=b, terms=dict(zip(S_Depth.default_terms[0], [Cs_w, s_w, hc, Cs_r, s_rho])), dataset='dummy') + dep = S_Depth_T1(bathymetry=b, terms=dict(zip(S_Depth_T1.default_terms[0], [Cs_w, s_w, hc, Cs_r, s_rho])), dataset='dummy') assert dep is not None corners = np.array([[0, 0, 0], [0, 3, 0], [3, 3, 0], [3, 0, 0]], dtype=np.float64) @@ -165,82 +167,47 @@ def test_construction(self): v = None with pytest.raises(ValueError): # mismatched data and dates length - u = TimeSeriesProp('u', 'm/s', dates, u_data) - - assert u is None + dates = [] + u = TimeSeriesProp('u', 'm/s', [datetime.datetime.now(), datetime.datetime.now()], [5, ]) - u = TimeSeriesProp('u', 'm/s', dates2, u_data) + u = TimeSeriesProp('u', 'm/s', [datetime.datetime.now()], [5, ]) assert u is not None assert u.name == 'u' assert u.units == 'm/s' - print u.time == Time(dates2) - assert u.time == Time(dates2) - assert (u.data == u_data).all() v = None with pytest.raises(ValueError): - v = TimeSeriesProp('v', 'nm/hr', dates2, v_data) + v = TimeSeriesProp('v', 'nm/hr', [datetime.datetime.now()], [5, ]) assert v is None - def test_unit_conversion(self, u): + constant = TimeSeriesProp.constant('const', 'm/s', 5) + assert constant.data[0] == 5 + assert all(constant.at(np.array((0, 0)), datetime.datetime.now()) == 5) + + def test_unit_conversion(self): + u = TimeSeriesProp('u', 'm/s', [datetime.datetime.now()], [5, ]) t = u.in_units('km/hr') - assert t.data is not v_data - assert round(t.data[0], 2) == 7.2 + assert t.data is not u.data + assert round(t.data[0], 2) == 18.0 with pytest.raises(unit_conversion.NotSupportedUnitError): # mismatched data and dates length t = u.in_units('nm/hr') - def test_set_data(self, u): - u.data = v_data - assert (u.data == v_data).all() - - with pytest.raises(ValueError): - # mismatched data and time length - u.data = [5, 6, 7] - - def test_set_time(self, u): - with pytest.raises(ValueError): - # mismatched data and time length - u.time = dates - - u.time = dates2 - assert u.time == Time(dates2) - - def test_set_attr(self, u): - - # mismatched data and time length - with pytest.raises(ValueError): - u.set_attr(time=dates2, data=s_data) - - u.set_attr(name='v') - assert u.name == 'v' - - with pytest.raises(ValueError): - u.set_attr(time=dates, data=u_data) - - u.set_attr(time=dates, data=s_data) - assert u.data[0] == 20 + def test_at(self): - u.set_attr(data=[50, 60, 70]) - assert u.data[0] == 50 + dates2 = np.array([dt.datetime(2000, 1, 1, 0), + dt.datetime(2000, 1, 1, 2), + dt.datetime(2000, 1, 1, 4), + dt.datetime(2000, 1, 1, 6), + dt.datetime(2000, 1, 1, 8), ]) + u_data = np.array([2., 4., 6., 8., 10.]) + u = TimeSeriesProp(name='u', units='m/s', time=dates2, data=u_data) - u.set_attr(time=[datetime.datetime(2000, 1, 3, 1), - datetime.datetime(2000, 1, 3, 2), - datetime.datetime(2000, 1, 3, 3)]) - - u.set_attr(units='km/hr') - - assert u.units == 'km/hr' - - with pytest.raises(ValueError): - u.set_attr(units='nm/hr') - - def test_at(self, u): corners = np.array(((1, 1), (2, 2))) t1 = dt.datetime(1999, 12, 31, 23) t2 = dt.datetime(2000, 1, 1, 0) @@ -261,351 +228,203 @@ def test_at(self, u): assert (u.at(corners, t1, extrapolate=True) == np.array([2])).all() assert (u.at(corners, t5, extrapolate=True) == np.array([10])).all() -class TestTSVectorProp: - - def test_construction(self, u, v): - vp = None - vp = TSVectorProp(name='vp', units='m/s', time=dates2, variables=[u_data, v_data]) - - assert all(vp.variables[0].data == u_data) - - # 3 components - vp = TSVectorProp(name='vp', units='m/s', time=dates2, variables=[u_data, v_data, u_data]) - - # Using TimeSeriesProp - vp = TSVectorProp(name='vp', variables=[u, v]) - assert vp.time == vp.variables[0].time == vp.variables[1].time - - # SHORT TIME - with pytest.raises(ValueError): - vp = TSVectorProp(name='vp', units='m/s', time=dates, variables=[u_data, v_data]) - - # DIFFERENT LENGTH VARS - with pytest.raises(ValueError): - vp = TSVectorProp(name='vp', units='m/s', time=dates2, variables=[s_data, v_data]) - - # UNSUPPORTED UNITS - with pytest.raises(ValueError): - vp = TSVectorProp(name='vp', units='km/s', time=dates2, variables=[s_data, v_data, u_data]) - - def test_unit_conversion(self, vp): - nvp = vp.in_units('km/hr') - assert round(nvp.variables[0].data[0], 2) == 7.2 - - with pytest.raises(unit_conversion.NotSupportedUnitError): - # mismatched data and dates length - nvp = vp.in_units('nm/hr') - - assert nvp != vp - assert all(nvp.variables[0].data != vp.variables[0].data) - - def test_set_variables(self, vp): - print u_data - vp.variables = [u_data, v_data, u_data] - assert (vp._variables[0].data == u_data).all() - - with pytest.raises(ValueError): - # mismatched data and time length - vp.variables = [[5], [6], [7]] - - def test_set_attr(self, vp): - - # mismatched data and time length - with pytest.raises(ValueError): - vp.set_attr(time=dates2, variables=[s_data, s_data]) - - vp.set_attr(name='vp1') - assert vp.name == 'vp1' +# class TestTSVectorProp: +# +# def test_construction(self, u, v): +# vp = None +# vp = TSVectorProp(name='vp', units='m/s', time=dates2, variables=[u_data, v_data]) +# pytest.set_trace() +# assert vp.variables[0].data == u_data +# +# # 3 components +# vp = TSVectorProp(name='vp', units='m/s', time=dates2, variables=[u_data, v_data, u_data]) +# +# # Using TimeSeriesProp +# vp = TSVectorProp(name='vp', variables=[u, v]) +# assert vp.time == vp.variables[0].time == vp.variables[1].time +# +# # SHORT TIME +# with pytest.raises(ValueError): +# vp = TSVectorProp(name='vp', units='m/s', time=dates, variables=[u_data, v_data]) +# +# # DIFFERENT LENGTH VARS +# with pytest.raises(ValueError): +# vp = TSVectorProp(name='vp', units='m/s', time=dates2, variables=[s_data, v_data]) +# +# # UNSUPPORTED UNITS +# with pytest.raises(ValueError): +# vp = TSVectorProp(name='vp', units='km/s', time=dates2, variables=[s_data, v_data, u_data]) +# +# def test_unit_conversion(self, vp): +# nvp = vp.in_units('km/hr') +# assert round(nvp.variables[0].data[0], 2) == 7.2 +# +# with pytest.raises(unit_conversion.NotSupportedUnitError): +# # mismatched data and dates length +# nvp = vp.in_units('nm/hr') +# +# assert nvp != vp +# assert all(nvp.variables[0].data != vp.variables[0].data) + +# def test_set_variables(self, vp): +# print u_data +# vp.variables = [u_data, v_data, u_data] +# assert (vp._variables[0].data == u_data).all() +# +# with pytest.raises(ValueError): +# # mismatched data and time length +# vp.variables = [[5], [6], [7]] +# +# def test_set_attr(self, vp): +# +# # mismatched data and time length +# with pytest.raises(ValueError): +# vp.set_attr(time=dates2, variables=[s_data, s_data]) +# +# vp.set_attr(name='vp1') +# assert vp.name == 'vp1' +# +# with pytest.raises(ValueError): +# vp.set_attr(time=dates, variables=[u_data, v_data]) +# +# vp.set_attr(time=dates, variables=[s_data, s_data]) +# assert vp.variables[0].data[0] == 20 +# +# vp.set_attr(variables=[[50, 60, 70], s_data]) +# assert vp.variables[0].data[0] == 50 +# +# vp.set_attr(time=[datetime.datetime(2000, 1, 3, 1), +# datetime.datetime(2000, 1, 3, 2), +# datetime.datetime(2000, 1, 3, 3)]) +# +# vp.set_attr(units='km/hr') +# +# assert vp.units == 'km/hr' +# +# with pytest.raises(ValueError): +# vp.set_attr(units='nm/hr') +# +# def test_at(self, vp): +# corners = np.array(((1, 1, 0), (2, 2, 0))) +# t1 = dt.datetime(1999, 12, 31, 23) +# t2 = dt.datetime(2000, 1, 1, 0) +# t3 = dt.datetime(2000, 1, 1, 1) +# t4 = dt.datetime(2000, 1, 1, 8) +# t5 = dt.datetime(2000, 1, 1, 9) +# +# # No extrapolation. out of bounds time should fail +# with pytest.raises(ValueError): +# vp.at(corners, t1) +# +# print vp.name +# assert (vp.at(corners, t2) == np.array([2, 5])).all() +# assert (vp.at(corners, t3) == np.array([3, 6])).all() +# assert (vp.at(corners, t4) == np.array([10, 13])).all() +# with pytest.raises(ValueError): +# vp.at(corners, t5) +# +# # turn extrapolation on +# assert (vp.at(corners, t1, extrapolate=True) == np.array([2, 5])).all() +# assert (vp.at(corners, t5, extrapolate=True) == np.array([10, 13])).all() - with pytest.raises(ValueError): - vp.set_attr(time=dates, variables=[u_data, v_data]) - - vp.set_attr(time=dates, variables=[s_data, s_data]) - assert vp.variables[0].data[0] == 20 - - vp.set_attr(variables=[[50, 60, 70], s_data]) - assert vp.variables[0].data[0] == 50 - - vp.set_attr(time=[datetime.datetime(2000, 1, 3, 1), - datetime.datetime(2000, 1, 3, 2), - datetime.datetime(2000, 1, 3, 3)]) - - vp.set_attr(units='km/hr') - - assert vp.units == 'km/hr' - - with pytest.raises(ValueError): - vp.set_attr(units='nm/hr') - - def test_at(self, vp): - corners = np.array(((1, 1, 0), (2, 2, 0))) - t1 = dt.datetime(1999, 12, 31, 23) - t2 = dt.datetime(2000, 1, 1, 0) - t3 = dt.datetime(2000, 1, 1, 1) - t4 = dt.datetime(2000, 1, 1, 8) - t5 = dt.datetime(2000, 1, 1, 9) - - # No extrapolation. out of bounds time should fail - with pytest.raises(ValueError): - vp.at(corners, t1) - - print vp.name - assert (vp.at(corners, t2) == np.array([2, 5])).all() - assert (vp.at(corners, t3) == np.array([3, 6])).all() - assert (vp.at(corners, t4) == np.array([10, 13])).all() - with pytest.raises(ValueError): - vp.at(corners, t5) - - # turn extrapolation on - assert (vp.at(corners, t1, extrapolate=True) == np.array([2, 5])).all() - assert (vp.at(corners, t5, extrapolate=True) == np.array([10, 13])).all() - - -@pytest.fixture() -def gp(): - return GriddedProp(name='u', units='m/s', time=grid_time, data=grid_u, data_file=curr_file, grid=test_grid, grid_file=curr_file) +''' +Analytical cases: + +Triangular + grid shape: (nodes = nv, faces = nele) + data_shapes: (time, depth, nv), + (time, nv), + (depth, nv), + (nv) + depth types: (None), + (constant), + (sigma v1), + (sigma v2), + (levels) + test points: 2D surface (time=None, depth=None) + - nodes should be valid + - off grid should extrapolate with fill value or Error + - interpolation elsewhere + 2D surface (time=t, depth=None) + - as above, validate time interpolation + + + +Quad + grid shape: (nodes:(x,y)) + (nodes:(x,y), faces(xc, yc)) + (nodes:(x,y), faces(xc, yc), edge1(x, yc), edge2(xc, y)) + data_shapes: (time, depth, x, y), + (time, x, y), + (depth, x, y), + (x,y) + depth types: (None), + (constant), + (sigma v1), + (sigma v2), + (levels) -@pytest.fixture() -def gp2(): - return GriddedProp(name='v;', units='m/s', time=grid_time, data=grid_v, data_file=curr_file, grid=test_grid, grid_file=curr_file) +''' -@pytest.fixture() -def gvp(): - return GridVectorProp(name='velocity', units='m/s', time=grid_time, variables=[gp(), gp2()]) class TestGriddedProp: + def test_construction(self): + data = sinusoid['u'][:] + grid = PyGrid.from_netCDF(dataset=sinusoid) + time = None + u = GriddedProp(name='u', units='m/s', - data=grid_u, - grid=test_grid, - time=grid_time, - data_file='tbofs_example.nc', - grid_file='tbofs_example.nc') - with pytest.raises(ValueError): - u = GriddedProp(name='u', - units='m/s', - data=None, # NO DATA - grid=test_grid, - time=grid_time, - data_file='tbofs_example.nc', - grid_file='tbofs_example.nc') - with pytest.raises(ValueError): - u = GriddedProp(name='u', - units='m/s', - data=grid_u, - grid=None, # NO GRID - time=grid_time, - data_file='tbofs_example.nc', - grid_file='tbofs_example.nc') - with pytest.raises(ValueError): - u = GriddedProp(name='u', - units='m/s', - data=u_data, # BAD DATA SHAPE - grid=test_grid, - time=grid_time, - data_file='tbofs_example.nc', - grid_file='tbofs_example.nc') - with pytest.raises(ValueError): - u = GriddedProp(name='u', - units='m/s', - data=grid_u, - grid=test_grid, - time=dates2, # BAD TIME SHAPE - data_file='tbofs_example.nc', - grid_file='tbofs_example.nc') - - topology = {'node_lon': 'lonc', - 'node_lat': 'latc'} - k = GriddedProp.from_netCDF(name='u', - depth=None, - grid_file=curr_file, - grid_topology=topology, - data_file=curr_file, - varname='water_u') + data=data, + grid=grid, + time=time, + data_file='staggered_sine_channel.nc', + grid_file='staggered_sine_channel.nc') + + curr_file = os.path.join(s_data, 'staggered_sine_channel.nc') + k = GriddedProp.from_netCDF(filename=curr_file, varname='u', name='u') assert k.name == u.name - assert k.units == 'meter second-1' + assert k.units == 'm/s' # fixme: this was failing - #assert k.time == u.time - assert k.data[0, 0, 0] == u.data[0, 0, 0] - - def test_set_data(self, gp): - with pytest.raises(ValueError): - gp.data = np.array(v_data) - - def test_set_grid(self, gp): - with pytest.raises(ValueError): - gp.grid = np.array(v_data) - - def test_set_time(self, gp): - gp.time = grid_time - gt2 = gp.time.time.copy() - gt2[0] = datetime.datetime(1999, 12, 31, 23) - gp.time = gt2 - assert gp.time.time[0] == datetime.datetime(1999, 12, 31, 23) - - def test_set_attr(self, gp): - gp.set_attr(name='gridpropobj') - assert gp.name == 'gridpropobj' - - gp.set_attr(data=grid_v) - assert gp.data == grid_v - - gp.set_attr(grid=test_grid) - assert gp.grid == test_grid - assert gp.grid.infer_location(gp.data) == 'node' - - gp.set_attr(data=grid_u, grid=test_grid) - assert gp.data == grid_u - assert gp.grid.infer_location(gp.data) == 'node' - - gp.set_attr(data_file='f', grid_file='f') - assert gp.data_file == 'f' - - def test_at(self, gp): - - print type(gp.grid) - - print gp.at(np.array([[-82.8, 27.475, 0], ]), gp.time.time[2]) - assert gp.at(np.array([[-82.8, 27.475, 0], ]), gp.time.time[2]) != 0 - print gp.at(np.array([[0, 0, 0], ]), gp.time.time[2]) - assert gp.at(np.array([[0, 0, 0], ]), gp.time.time[2]) == 1.0e20 - - def test_extrapolation(self, gp, gp2): - gp._time.time = np.array([gp.time.time[0]]) - gp._data = gp.data[0, :, :] - assert gp.at(np.array([[-82.8, 27.475, 0], ]), dt.datetime.now()) != 0 + # assert k.time == u.time + assert k.data[0, 0] == u.data[0, 0] + + def test_at(self): + curr_file = os.path.join(s_data, 'staggered_sine_channel.nc') + u = GriddedProp.from_netCDF(filename=curr_file, varname='u_rho') + v = GriddedProp.from_netCDF(filename=curr_file, varname='v_rho') + + points = np.array(([0, 0, 0], [np.pi, 1, 0], [2 * np.pi, 0, 0])) + time = datetime.datetime.now() + + assert all(u.at(points, time) == [1, 1, 1]) + print np.cos(points[:, 0] / 2) / 2 + assert all(np.isclose(v.at(points, time), np.cos(points[:, 0] / 2) / 2)) class TestGridVectorProp: - def test_construction(self, gp, gp2): - # Grid_file and data_file missing - with pytest.raises(ValueError): - gvp = GridVectorProp(name='velocity', units='m/s', time=grid_time, variables=[grid_u, grid_v]) - # Units inconsistent with variables units - with pytest.raises(ValueError): - gvp = GridVectorProp(name='velocity', units='km/hr', time=grid_time, variables=[gp, gp2]) - gvp = GridVectorProp(name='velocity', units='m/s', time=grid_time, variables=[gp, gp2]) + def test_construction(self): + curr_file = os.path.join(s_data, 'staggered_sine_channel.nc') + u = GriddedProp.from_netCDF(filename=curr_file, varname='u_rho') + v = GriddedProp.from_netCDF(filename=curr_file, varname='v_rho') + gvp = GridVectorProp(name='velocity', units='m/s', time=u.time, variables=[u, v]) assert gvp.name == 'velocity' assert gvp.units == 'm/s' - assert gvp.time == Time(grid_time) - assert gvp._variables[0].data == grid_u - assert gvp._variables[0].name == 'u' - - -@pytest.fixture() -def vel(u, v): - return VelocityTS(name='vel', variables=[u, v]) -class TestVelocityTS: - def test_construction(self, u, v): - vel = None - vel = VelocityTS(name='vel', units='m/s', time=dates2, variables=[u_data, v_data], extrapolate=False) - - assert all(vel.variables[0].data == u_data) - - # Using TimeSeriesProp objects - vel = VelocityTS(name='vel', variables=[u, v]) - assert vel.time == vel.variables[0].time == vel.variables[1].time - # 3 components - with pytest.raises(ValueError): - vel = VelocityTS(name='vel', units='m/s', time=dates2, variables=[u_data, v_data, u_data], extrapolate=False) - - @pytest.mark.parametrize("json_", ('save', 'webapi')) - def test_serialization(self, vel, json_): - dict_ = vel.serialize(json_) - print dict_ - assert dict_[u'name'] == 'vel' - if json_ == 'webapi': - assert dict_[u'units'] == ('m/s', 'degrees') - assert dict_[u'varnames'] == ['magnitude', 'direction', 'u', 'v'] - if json_ == 'save': - print dict_['timeseries'] - assert dict_['timeseries'][0][1][0] == 2.0 - assert dict_[u'units'] == ['m/s'] - - @pytest.mark.parametrize("json_", ('save', 'webapi')) - def test_deserialize(self, vel, json_): - dict_ = vel.serialize(json_) - dser = VelocityTS.deserialize(dict_) - print dser - assert dser['name'] == 'vel' - assert all(dser['time'] == dates2) - assert all(np.isclose(dser['data'][0], u_data)) - - @pytest.mark.parametrize("json_", ('save', 'webapi')) - def test_new_from_dict(self, vel, json_): - deser = VelocityTS.deserialize(vel.serialize(json_)) - vel2 = VelocityTS.new_from_dict(deser) - assert vel == vel2 - - @pytest.mark.parametrize("json_", ('save', 'webapi')) - def test_update_from_dict(self, vel, json_): - deser = VelocityTS.deserialize(vel.serialize(json_)) - vel2 = VelocityTS.new_from_dict(deser) - deser['name'] = 'vel2' - vel.update_from_dict(deser) - vel2.name = 'vel2' - assert vel.name == 'vel2' - assert vel == vel2 - - -@pytest.fixture() -def g_vel(gp, gp2): - return VelocityGrid(name='g_vel', variables=[gp, gp2]) -class TestVelocityGrid: - def test_construction(self, gp, gp2): - g_vel = VelocityGrid(name='g_vel', variables=[gp, gp2]) - g_vel = VelocityGrid(name='g_vel', units='m/s', time=grid_time, grid=test_grid, variables=[grid_u, grid_v], grid_file=curr_file, data_file=curr_file) - pass - - @pytest.mark.parametrize("json_", ('save', 'webapi')) - def test_serialization(self, g_vel, json_): - dict_ = g_vel.serialize() - - @pytest.mark.parametrize("json_", ('save', 'webapi')) - def test_deserialize(self, g_vel, json_): - pass - - @pytest.mark.parametrize("json_", ('save', 'webapi')) - def test_new_from_dict(self, g_vel, json_): - pass - - @pytest.mark.parametrize("json_", ('save', 'webapi')) - def test_update_from_dict(self, g_vel, json_): - pass + assert gvp.varnames[0] == 'u_rho' +# pytest.set_trace() + + def test_at(self): + curr_file = os.path.join(s_data, 'staggered_sine_channel.nc') + gvp = GridVectorProp.from_netCDF(filename=curr_file, + varnames=['u_rho', 'v_rho']) + points = np.array(([0, 0, 0], [np.pi, 1, 0], [2 * np.pi, 0, 0])) + time = datetime.datetime.now() + + assert all(np.isclose(gvp.at(points, time)[:, 1], np.cos(points[:, 0] / 2) / 2)) if __name__ == "__main__": - import pprint - k = vp() - corners = np.array(((1, 1), (2, 2))) - t1 = dt.datetime(1999, 12, 31, 23) - t2 = dt.datetime(2000, 1, 1, 0) - t3 = dt.datetime(2000, 1, 1, 1) - t4 = dt.datetime(2000, 1, 1, 8) - t5 = dt.datetime(2000, 1, 1, 9) - - # No extrapolation. out of bounds time should fail - with pytest.raises(ValueError): - k.at(corners, t1) - - print k.name - assert (k.at(corners, t2) == np.array([2, 5])).all() - assert (k.at(corners, t3) == np.array([3, 6])).all() - assert (k.at(corners, t4) == np.array([10, 13])).all() - with pytest.raises(ValueError): - k.at(corners, t5) - - v = g_vel(gp(), gp2()) - dict_ = v.serialize() - pp.pprint(dict_) - deser = VelocityGrid.deserialize(dict_) - pp.pprint(deser) - v2 = VelocityGrid.new_from_dict(deser) - v == v2 - test_tsprop_construction() - test_tsprop_unit_conversion() - test_tsprop_set_attr() - test_gridprop_construction() + pass diff --git a/py_gnome/tests/unit_tests/test_map.py b/py_gnome/tests/unit_tests/test_map.py index 52f35dcf2..cfb3a3c82 100644 --- a/py_gnome/tests/unit_tests/test_map.py +++ b/py_gnome/tests/unit_tests/test_map.py @@ -16,15 +16,14 @@ from gnome.basic_types import oil_status, status_code_type from gnome.utilities.projections import NoProjection -from gnome.map import GnomeMap, MapFromBNA, RasterMap, MapFromUGrid +from gnome.map import GnomeMap, MapFromBNA, RasterMap # , MapFromUGrid from conftest import sample_sc_release basedir = os.path.dirname(__file__) -datadir = os.path.join(basedir, "../sample_data") -testbnamap = os.path.join(basedir, '../sample_data', 'MapBounds_Island.bna') -test_tri_grid = os.path.join(basedir, '../sample_data', - 'small_trigrid_example.nc') +datadir = os.path.normpath(os.path.join(basedir, "sample_data")) +testbnamap = os.path.join(datadir, 'MapBounds_Island.bna') +test_tri_grid = os.path.join(datadir, 'small_trigrid_example.nc') def test_in_water_resolution(): @@ -35,7 +34,6 @@ def test_in_water_resolution(): # Create an 500x500 pixel map, with an LE refloat half-life of 2 hours # (specified here in seconds). - m = gnome.map.MapFromBNA(filename=testbnamap, refloat_halflife=2, raster_size=500 * 500) @@ -921,11 +919,30 @@ def test_resurface_airborne_elements(): assert spill['next_positions'][:, 2].min() == 0. + +def test_bna_no_map_bounds(): + """ + tests that the map bounds will get expanded to include + the bounding box of the land and spillable area. + """ + test_no_bounds_bna = os.path.join(datadir, 'no_map_bounds.bna') + m = MapFromBNA(test_no_bounds_bna) + + assert np.array_equal(m.map_bounds, [(3., 10.), + (3., 11.), + (6., 11.), + (6., 10.), + ]) + + if __name__ == '__main__': - tester = Test_MapfromBNA() - print "running test" - # tester.test_map_on_land() - tester.test_map_spillable_lake() + + map = test_bna_no_map_bounds() + + # tester = Test_MapfromBNA() + # print "running test" + # # tester.test_map_on_land() + # tester.test_map_spillable_lake() # tester = Test_GnomeMap() # tester.test_on_map() diff --git a/py_gnome/tests/unit_tests/test_movers/test_random_vertical_mover.py b/py_gnome/tests/unit_tests/test_movers/test_random_vertical_mover.py index f33ad4ac5..27c261f68 100644 --- a/py_gnome/tests/unit_tests/test_movers/test_random_vertical_mover.py +++ b/py_gnome/tests/unit_tests/test_movers/test_random_vertical_mover.py @@ -63,7 +63,7 @@ def test_vertical_zero(): """ checks that there is no vertical movement """ - mv = RandomVerticalMover() # all defaults + mv = RandomVerticalMover(mixed_layer_depth=10.0) # mostly defaults num_elements = 100 @@ -73,9 +73,10 @@ def test_vertical_zero(): ) # set z positions: sc['positions'][:, 2] = np.linspace(0, 50, num_elements) + print sc['positions'] - mv.vertical_diffusion_coef_above_ml = 0; - mv.vertical_diffusion_coef_below_ml = 0; + mv.vertical_diffusion_coef_above_ml = 0.0 + mv.vertical_diffusion_coef_below_ml = 0.0 delta = mv.get_move(sc, time_step, @@ -98,7 +99,7 @@ def test_bottom_layer(): total_time = 60000 num_elements = 100 - num_timesteps = total_time // time_step + num_timesteps = total_time // time_step mv = RandomVerticalMover(vertical_diffusion_coef_below_ml=D_lower) # m @@ -146,7 +147,7 @@ def test_mixed_layer(): total_time = 600 num_elements = 1000 - num_timesteps = total_time // time_step + num_timesteps = total_time // time_step mv = RandomVerticalMover(vertical_diffusion_coef_above_ml=D_mixed, mixed_layer_depth=1000, # HUGE to avoid surface effects. @@ -193,7 +194,7 @@ def test_mixed_layer2(): total_time = 6000 num_elements = 1000 - num_timesteps = total_time // time_step + num_timesteps = total_time // time_step mv = RandomVerticalMover(vertical_diffusion_coef_above_ml=D_mixed, vertical_diffusion_coef_below_ml=0.0, diff --git a/py_gnome/tests/unit_tests/test_spill/test_spill.py b/py_gnome/tests/unit_tests/test_spill/test_spill.py index f03056cc2..0fa7016af 100644 --- a/py_gnome/tests/unit_tests/test_spill/test_spill.py +++ b/py_gnome/tests/unit_tests/test_spill/test_spill.py @@ -85,7 +85,8 @@ def test_amount_mass_vol(amount, units): assert spill.units == units if units in Spill.valid_vol_units: - exp_mass = (spill.substance.density_at_temp(water.temperature) * + # use 15C (288.15K) for mass<=>volume conversion + exp_mass = (spill.substance.density_at_temp(288.15) * uc.convert('Volume', units, 'm^3', spill.amount)) else: exp_mass = uc.convert('Mass', units, 'kg', spill.amount) diff --git a/py_gnome/tests/unit_tests/test_utilities/test_geometry/test_polygons.py b/py_gnome/tests/unit_tests/test_utilities/test_geometry/test_polygons.py index d870cb4a9..827604a03 100644 --- a/py_gnome/tests/unit_tests/test_utilities/test_geometry/test_polygons.py +++ b/py_gnome/tests/unit_tests/test_utilities/test_geometry/test_polygons.py @@ -89,14 +89,13 @@ def test_bounding_box(self): P = Polygon(p1) print P.bounding_box assert P.bounding_box == np.array([[1., 2.], [7., 8.]], - dtype=np.float) + dtype=np.float) def test_size_zero(self): - P = Polygon( (), ) + P = Polygon((), ) assert len(P) == 0 - class Test_PolygonSet: def test_append(self): @@ -136,6 +135,15 @@ def test_zero_length(self): assert len(poly_set) == 0 + def test_zero_length_false(self): + """ + A length-zero Polygon set should be Falsey + """ + poly_set = PolygonSet() + + assert not poly_set + + # def test_pop(self): # pass diff --git a/py_gnome/tests/unit_tests/test_utilities/test_weathering_algorithms.py b/py_gnome/tests/unit_tests/test_utilities/test_weathering_algorithms.py index 951f753f9..48c394f9b 100644 --- a/py_gnome/tests/unit_tests/test_utilities/test_weathering_algorithms.py +++ b/py_gnome/tests/unit_tests/test_utilities/test_weathering_algorithms.py @@ -75,3 +75,10 @@ def test_ding_farmer(): wave_height, k_w), 1.0) + + +def test_monohan(): + from gnome.utilities.weathering.monahan import Monahan + + assert np.isclose(Monahan.whitecap_decay_constant(0), 2.54) # fresh water + assert np.isclose(Monahan.whitecap_decay_constant(35), 3.85) # salt water diff --git a/py_gnome/tests/unit_tests/test_weatherers/test_dispersion.py b/py_gnome/tests/unit_tests/test_weatherers/test_dispersion.py index 66c318c95..25c481f8d 100644 --- a/py_gnome/tests/unit_tests/test_weatherers/test_dispersion.py +++ b/py_gnome/tests/unit_tests/test_weatherers/test_dispersion.py @@ -88,12 +88,14 @@ def test_dispersion_not_active(oil, temp, num_elems): assert np.all(sc.mass_balance['sedimentation'] == 0) -@pytest.mark.xfail +# the test oils don't match the data base, using so tests don't depend on db @pytest.mark.parametrize(('oil', 'temp', 'dispersed'), - [('ABU SAFAH', 288.7, 361.402), - ('ALASKA NORTH SLOPE (MIDDLE PIPELINE)', - 288.7, 552.632), - ('BAHIA', 288.7, 525.503) + [('ABU SAFAH', 288.7, 63.076), + #('ALASKA NORTH SLOPE (MIDDLE PIPELINE)', + ('oil_ans_mp', + 288.7, 592.887), + #('BAHIA', 288.7, 14.472) + ('oil_bahia', 288.7, 133.784) ] ) def test_full_run(sample_model_fcn2, oil, temp, dispersed): diff --git a/py_gnome/tests/unit_tests/test_weatherers/test_dissolution.py b/py_gnome/tests/unit_tests/test_weatherers/test_dissolution.py index 88a1900ca..990663d54 100644 --- a/py_gnome/tests/unit_tests/test_weatherers/test_dissolution.py +++ b/py_gnome/tests/unit_tests/test_weatherers/test_dissolution.py @@ -28,7 +28,7 @@ def test_init(): - 'test sort order for Dissolution weatherer' + 'test initialization' wind = constant_wind(15., 0) waves = Waves(wind, Water()) diss = Dissolution(waves) @@ -38,13 +38,13 @@ def test_init(): for at in ('mass', 'viscosity', 'density')]) -def test_sort_order(): - 'test sort order for Dissolution weatherer' - wind = constant_wind(15., 0) - waves = Waves(wind, Water()) - diss = Dissolution(waves) +# def test_sort_order(): +# 'test sort order for Dissolution weatherer' +# wind = constant_wind(15., 0) +# waves = Waves(wind, Water()) +# diss = Dissolution(waves) - assert weatherer_sort(diss) == 8 +# assert weatherer_sort(diss) == 10 def test_serialize_deseriailize(): @@ -126,9 +126,9 @@ def test_dissolution_k_ow(oil, temp, num_elems, k_ow, on): @pytest.mark.parametrize(('oil', 'temp', 'num_elems', 'drop_size', 'on'), [('oil_bahia', 311.15, 3, - [231.19e-6, 221.98e-6, 212.91e-6], True), + [239.92e-6, 231.33e-6, 222.85e-6], True), ('oil_ans_mp', 311.15, 3, - [234.08e-6, 221.6e-6, 213.08e-6], True), + [245.32e-6, 233.54e-6, 225.35e-6], True), ('oil_ans_mp', 311.15, 3, [0.0, 0.0, 0.0], False)]) def test_dissolution_droplet_size(oil, temp, num_elems, drop_size, on): @@ -190,10 +190,10 @@ def test_dissolution_droplet_size(oil, temp, num_elems, drop_size, on): ('oil_bahia', 288.15, 15., 3, 3.6288e-3, True), ('oil_bahia', 288.15, 20., 3, 6.1597e-3, True), # temperature trends - ('oil_bahia', 273.15, 15., 3, 2.6198e-3, True), - ('oil_bahia', 283.15, 15., 3, 3.3789e-3, True), - ('oil_bahia', 293.15, 15., 3, 3.8367e-3, True), - ('oil_bahia', 303.15, 15., 3, 4.1746e-3, True), + ('oil_bahia', 273.15, 15., 3, 3.62526e-3, True), + ('oil_bahia', 283.15, 15., 3, 3.6267e-3, True), + ('oil_bahia', 293.15, 15., 3, 3.6568e-3, True), + ('oil_bahia', 303.15, 15., 3, 3.71499e-3, True), ] @@ -263,7 +263,7 @@ def test_dissolution_mass_balance(oil, temp, wind_speed, .format(sc.mass_balance['dissolution'] / initial_amount) ) print sc.mass_balance['dissolution'], expected_mb - assert np.isclose(sc.mass_balance['dissolution'], expected_mb) + assert np.isclose(sc.mass_balance['dissolution'], expected_mb, rtol=1e-4) else: assert 'dissolution' not in sc.mass_balance @@ -277,8 +277,8 @@ def test_dissolution_mass_balance(oil, temp, wind_speed, @pytest.mark.parametrize(('oil', 'temp', 'expected_balance'), - [('oil_ans_mp', 288.7, 38.1926), - ('oil_bahia', 288.7, 136.751)]) + [('oil_ans_mp', 288.7, 38.632), + ('oil_bahia', 288.7, 137.88038)]) def test_full_run(sample_model_fcn2, oil, temp, expected_balance): ''' test dissolution outputs post step for a full run of model. Dump json @@ -327,12 +327,12 @@ def test_full_run(sample_model_fcn2, oil, temp, expected_balance): .format(dissolved[-1] / original_amount)) assert dissolved[0] == 0.0 - assert np.isclose(dissolved[-1], expected_balance) + assert np.isclose(dissolved[-1], expected_balance, rtol=1e-4) @pytest.mark.parametrize(('oil', 'temp', 'expected_balance'), # [(_sample_oils['benzene'], 288.7, 2.98716) - [('benzene', 288.7, 9729.56707)]) + [('benzene', 288.7, 9731.38174)]) def test_full_run_no_evap(sample_model_fcn2, oil, temp, expected_balance): ''' test dissolution outputs post step for a full run of model. Dump json diff --git a/py_gnome/tests/unit_tests/test_weatherers/test_roc.py b/py_gnome/tests/unit_tests/test_weatherers/test_roc.py new file mode 100644 index 000000000..b4b9340f6 --- /dev/null +++ b/py_gnome/tests/unit_tests/test_weatherers/test_roc.py @@ -0,0 +1,579 @@ +''' +tests for ROC +''' +from datetime import datetime, timedelta + +import numpy as np +from pytest import raises, mark, set_trace + +import unit_conversion as us + +from gnome.basic_types import oil_status, fate + +from gnome.weatherers.roc import (Burn, Disperse, Skim, Platform) +from gnome.persist import load +from gnome.weatherers import (WeatheringData, + FayGravityViscous, + weatherer_sort, + Emulsification, + Evaporation) +from gnome.spill_container import SpillContainer +from gnome.spill import point_line_release_spill +from gnome.utilities.inf_datetime import InfDateTime +from gnome.environment import Waves, constant_wind, Water + +from ..conftest import (test_oil, sample_model_weathering2) + +delay = 1. +time_step = 900 +rel_time = datetime(2012, 9, 15, 12, 0) +active_start = rel_time + timedelta(seconds=time_step) +active_stop = active_start + timedelta(hours=24.) +amount = 36000. +units = 'kg' +wind = constant_wind(15., 0) +water = Water() +waves = Waves(wind, water) + +class ROCTests: + @classmethod + def mk_objs(cls, sample_model_fcn2): + model = sample_model_weathering2(sample_model_fcn2, test_oil, 333.0) + model.set_make_default_refs(True) + model.environment += [waves, wind, water] + model.weatherers += Evaporation(wind=wind, water=water) + model.weatherers += Emulsification(waves=waves) + return (model.spills.items()[0], model) + + def prepare_test_objs(self, obj_arrays=None): + self.model.rewind() + self.model.rewind() + at = set() + + for wd in self.model.weatherers: + wd.prepare_for_model_run(self.sc) + at.update(wd.array_types) + + if obj_arrays is not None: + at.update(obj_arrays) + + self.sc.prepare_for_model_run(at) + + def reset_and_release(self, rel_time=None, time_step=900.0): + self.prepare_test_objs() + if rel_time is None: + rel_time = self.sc.spills[0].release_time + + num_rel = self.sc.release_elements(time_step, rel_time) + if num_rel > 0: + for wd in self.model.weatherers: + wd.initialize_data(self.sc, num_rel) + + def step(self, test_weatherer, time_step, model_time): + test_weatherer.prepare_for_model_step(self.sc, time_step, model_time) + self.model.step() + test_weatherer.weather_elements(self.sc, time_step, model_time) + +class TestRocGeneral(ROCTests): + burn = Burn(offset=50.0, + boom_length=250.0, + boom_draft=10.0, + speed=2.0, + throughput=0.75, + burn_efficiency_type=1, + timeseries=np.array([(rel_time, rel_time + timedelta(hours=12.))])) + + def test_get_thickness(self, sample_model_fcn2): + (self.sc, self.model) = ROCTests.mk_objs(sample_model_fcn2) + self.reset_and_release() + assert self.burn._get_thickness(self.sc) == 0.0 + self.model.step() +# assert self.burn._get_thickness(self.sc) == 0.16786582186002749 + self.model.step() +# assert self.burn._get_thickness(self.sc) == 0.049809899105767913 + +class TestROCBurn(ROCTests): + burn = Burn(offset=50.0, + boom_length=250.0, + boom_draft=10.0, + speed=2.0, + throughput=0.75, + burn_efficiency_type=1, + timeseries=np.array([(rel_time, rel_time + timedelta(hours=12.))])) + + def test_prepare_for_model_run(self, sample_model_fcn2): + (self.sc, self.model) = ROCTests.mk_objs(sample_model_fcn2) + self.reset_and_release() + self.burn.prepare_for_model_run(self.sc) + assert self.sc.mass_balance['burned'] == 0.0 + assert 'systems' in self.sc.mass_balance + assert self.burn.id in self.sc.mass_balance['systems'] + assert self.sc.mass_balance['systems'][self.burn.id]['boomed'] == 0.0 + assert self.sc.mass_balance['boomed'] == 0.0 + assert self.burn._swath_width == 75 + assert self.burn._area == 1718.75 + assert self.burn.boom_draft == 10 + assert self.burn._offset_time == 14.805 + assert round(self.burn._boom_capacity) == 477 + assert len(self.sc.report[self.burn.id]) == 1 + assert self.burn._area_coverage_rate == 0.3488372093023256 + assert len(self.burn.timeseries) == 1 + + def test_reports(self, sample_model_fcn2): + (self.sc, self.model) = ROCTests.mk_objs(sample_model_fcn2) + self.reset_and_release() + self.burn.boom_length = 3500.0 + self.burn.prepare_for_model_run(self.sc) + assert self.burn._swath_width == 1050 + assert len(self.burn.report) == 2 + + def test_serialize(self, sample_model_fcn2): + (self.sc, self.model) = ROCTests.mk_objs(sample_model_fcn2) + self.reset_and_release() + self.burn.serialize() + + def test_prepare_for_model_step(self, sample_model_fcn2): + (self.sc, self.model) = ROCTests.mk_objs(sample_model_fcn2) + self.reset_and_release() + + self.burn.prepare_for_model_run(self.sc) + self.burn.prepare_for_model_step(self.sc, time_step, active_start) + + assert self.burn._active == True + + def test_weather_elements(self, sample_model_fcn2): + (self.sc, self.model) = ROCTests.mk_objs(sample_model_fcn2) + self.model.time_step = 900 + self.reset_and_release() + burn = Burn(offset=6000.0, + boom_length=100.0, + boom_draft=10.0, + speed=2.0, + throughput=0.75, + burn_efficiency_type=1, + timeseries=np.array([(rel_time, rel_time + timedelta(hours=12.))])) + + self.model.weatherers.append(burn) + self.model.rewind() + self.model.step() + assert burn._is_burning == False + assert burn._is_collecting == True + assert self.sc.mass_balance['burned'] == 0 + self.model.step() + assert burn._is_burning == False + assert burn._boom_capacity == 0 + assert burn._is_transiting == True + assert burn._is_boom_full == True + assert burn._burn_rate == 0.14 + assert self.sc.mass_balance['burned'] == 0 + collected = self.sc.mass_balance['boomed'] + self.model.step() + assert burn._burn_time == 1414.2857142857142 + assert burn._burn_time_remaining <= burn._burn_time + assert np.isclose(collected, 1877.2886248344857, 0.001) + assert burn._is_collecting == False + assert burn._is_cleaning == False + assert burn._is_burning == True + self.model.step() + assert burn._is_transiting == False + assert burn._is_burning == True + assert burn._is_boom_full == False + self.model.step() + self.model.step() + assert burn._is_burning == False + assert burn._is_cleaning == True + assert np.isclose(self.sc.mass_balance['boomed'], 0) + assert self.sc.mass_balance['boomed'] >= 0 + #assert np.isclose(self.sc.mass_balance['burned'], collected) + self.model.step() + assert burn._is_burning == False + assert burn._is_cleaning == True + + self.model.rewind() + self.model.rewind() + for step in self.model: + print 'amount in boom', self.sc.mass_balance['boomed'] + assert self.sc.mass_balance['boomed'] >= 0 + print '===========', step['step_num'], '==============' + print 'collecting', burn._is_collecting + print 'transiting', burn._is_transiting + print 'burning', burn._is_burning + print 'cleaning', burn._is_cleaning + + def test_serialization(self): + b = TestROCBurn.burn + ser = b.serialize() + deser = Burn.deserialize(ser) + b2 = Burn.new_from_dict(deser) + ser2 = b2.serialize() + ser.pop('id') + ser2.pop('id') + assert ser == ser2 + + + def test_step(self, sample_model_fcn2): + (self.sc, self.model) = ROCTests.mk_objs(sample_model_fcn2) + self.reset_and_release() + self.model.step() + +class TestPlatform(ROCTests): + + def test_construction(self): + p = Platform() + assert p.units == dict([(k, v[0]) for k, v in Platform._attr.items()]) + p = Platform(_name = "Test Platform") + assert p.transit_speed == 150 + assert p.max_op_time == 4 + p = Platform(_name = "Test Platform", units = {'transit_speed': 'm/s'}) + assert p.units['transit_speed'] == 'm/s' + + def test_serialization(self): + p = Platform(_name='Test Platform') + + import pprint as pp + ser = p.serialize() + pp.pprint(ser) + deser = Platform.deserialize(ser) + + pp.pprint(deser) + + p2 = Platform.new_from_dict(deser) + ser2 = p2.serialize() + pp.pprint(ser2) + + print 'INCORRECT BELOW' + + ser.pop('id') + ser2.pop('id') + assert ser == ser2 + +class TestRocChemDispersion(ROCTests): + + disp = Disperse(name='test_disperse', + transit=100, + pass_length=4, +# dosage=1, + cascade_on=False, + cascade_distance=None, + timeseries=np.array([(rel_time, rel_time + timedelta(hours=12.))]), + loading_type='simultaneous', + pass_type='bidirectional', + disp_oil_ratio=None, + disp_eff=None, + platform='Test Platform', + units=None,) + + def test_construction(self): + d = Disperse(name='testname', + transit=100, + platform='Test Platform') + #payload in gallons, computation in gallons, so no conversion + + def test_serialization(self): + import pprint as pp + p = Disperse(name='test_disperse', + transit=100, + pass_length=4, +# dosage=1, + cascade_on=False, + cascade_distance=None, + timeseries=np.array([(rel_time, rel_time + timedelta(hours=12.))]), + loading_type='simultaneous', + pass_type='bidirectional', + disp_oil_ratio=None, + disp_eff=None, + platform='Test Platform', + units=None,) + ser = p.serialize() + print 'Ser' + pp.pprint(ser) + deser = Disperse.deserialize(ser) + + print '' + print 'deser' + pp.pprint(deser) + + p2 = Disperse.new_from_dict(deser) + ser2 = p2.serialize() + print + pp.pprint(ser2) + + ser.pop('id') + ser2.pop('id') + ser['platform'].pop('id') + ser2['platform'].pop('id') + assert ser['platform']['swath_width'] == 100.0 + assert ser2['platform']['swath_width'] == 100.0 + assert ser == ser2 + + def test_prepare_for_model_run(self, sample_model_fcn2): + (self.sc, self.model) = ROCTests.mk_objs(sample_model_fcn2) + self.reset_and_release() + self.disp.prepare_for_model_run(self.sc) +# assert self.sc.mass_balance[self.disp.id] == 0.0 + assert self.disp.cur_state == 'retired' + assert len(self.sc.report[self.disp.id]) == 0 + assert len(self.disp.timeseries) == 1 + + def test_prepare_for_model_step(self, sample_model_fcn2): + disp = Disperse(name='test_disperse', + transit=100, + pass_length=4, + dosage=1, + cascade_on=False, + cascade_distance=None, + timeseries=np.array([(rel_time, rel_time + timedelta(hours=12.))]), + loading_type='simultaneous', + pass_type='bidirectional', + disp_oil_ratio=None, + disp_eff=None, + platform='Test Platform', + units=None,) + (self.sc, self.model) = ROCTests.mk_objs(sample_model_fcn2) + self.model.weatherers += disp + self.model.spills[0].amount = 1000 + self.model.spills[0].units = 'gal' + self.reset_and_release() + disp.prepare_for_model_run(self.sc) + print self.model.start_time + print self.disp.timeseries + assert disp.cur_state == 'retired' + self.model.step() + print self.model.current_time_step + self.model.step() + print self.model.spills.items()[0]['viscosity'] + assert disp.cur_state == 'en_route' + print disp._next_state_time + self.model.step() + assert disp.cur_state == 'en_route' + print disp.transit + print disp.platform.transit_speed + print disp.platform.one_way_transit_time(disp.transit)/60 + while disp.cur_state == 'en_route': + self.model.step() + off = self.model.current_time_step * self.model.time_step + print self.model.start_time + timedelta(seconds=off) + print 'pump_rate ', disp.platform.eff_pump_rate(disp.dosage) + try: + for step in self.model: + off = self.model.current_time_step * self.model.time_step + print self.model.start_time + timedelta(seconds=off) + except StopIteration: + pass + + def test_prepare_for_model_step_cont(self, sample_model_fcn2): + disp = Disperse(name='test_disperse', + transit=100, + pass_length=4, +# dosage=1, + cascade_on=False, + cascade_distance=None, + timeseries=np.array([(rel_time, rel_time + timedelta(hours=12.))]), + loading_type='simultaneous', + pass_type='bidirectional', + disp_oil_ratio=None, + disp_eff=None, + platform='Test Platform', + units=None,) + (self.sc, self.model) = ROCTests.mk_objs(sample_model_fcn2) + self.model.weatherers += disp + self.model.spills[0].amount = 20000 + self.model.spills[0].units = 'gal' + self.model.spills[0].end_release_time = self.model.start_time + timedelta(hours=3) + self.reset_and_release() + disp.prepare_for_model_run(self.sc) + + try: + for step in self.model: + off = self.model.current_time_step * self.model.time_step + print self.model.start_time + timedelta(seconds=off) + except StopIteration: + pass +# print self.model.start_time +# print self.disp.timeseries +# assert self.disp.cur_state == 'retired' +# self.model.step() +# print self.model.current_time_step +# self.model.step() +# print self.model.spills.items()[0]['viscosity'] +# assert self.disp.cur_state == 'en_route' +# print self.disp._next_state_time +# self.model.step() +# assert self.disp.cur_state == 'en_route' +# print self.disp.transit +# print self.disp.platform.transit_speed +# print self.disp.platform.one_way_transit_time(self.disp.transit)/60 +# while self.disp.cur_state == 'en_route': +# self.model.step() +# off = self.model.current_time_step * self.model.time_step +# print self.model.start_time + timedelta(seconds=off) +# print 'pump_rate ', self.disp.platform.eff_pump_rate(self.disp.dosage) +# assert 'disperse' in self.disp.cur_state + try: + for step in self.model: + off = self.model.current_time_step * self.model.time_step +# print '********', self.model.start_time + timedelta(seconds=off) +# print self.sc['mass'] +# print self.sc.mass_balance['dispersed'] + assert all(self.sc['mass'] >= 0) + assert np.all(self.sc['mass_components'] >= 0) + assert self.sc.mass_balance['chem_dispersed'] + self.sc.mass_balance['evaporated'] < sum(self.sc['init_mass']) + except StopIteration: + pass + + def test_prepare_for_model_step_boat(self, sample_model_fcn2): + disp = Disperse(name='boat_disperse', + transit=20, + pass_length=4, +# dosage=1, + cascade_on=False, + cascade_distance=None, + timeseries=np.array([(rel_time, rel_time + timedelta(hours=12.))]), + loading_type='simultaneous', + pass_type='bidirectional', + disp_oil_ratio=None, + disp_eff=None, + platform='Typical Large Vessel', + units=None, + onsite_reload_refuel=True) + (self.sc, self.model) = ROCTests.mk_objs(sample_model_fcn2) + self.model.weatherers += disp + self.model.spills[0].amount = 20000 + self.model.spills[0].units = 'gal' + self.model.spills[0].end_release_time = self.model.start_time + timedelta(hours=3) + self.reset_and_release() + disp.prepare_for_model_run(self.sc) + print self.model.start_time + print self.disp.timeseries + assert disp.cur_state == 'retired' + self.model.step() + print self.model.current_time_step + self.model.step() + print self.model.spills.items()[0]['viscosity'] + assert disp.cur_state == 'en_route' + print disp._next_state_time + self.model.step() + assert disp.cur_state == 'en_route' + print disp.transit + print disp.platform.transit_speed + print disp.platform.one_way_transit_time(disp.transit)/60 + while disp.cur_state == 'en_route': + self.model.step() + off = self.model.current_time_step * self.model.time_step + print self.model.start_time + timedelta(seconds=off) + print 'pump_rate ', disp.platform.eff_pump_rate(disp.dosage) + try: + for step in self.model: + off = self.model.current_time_step * self.model.time_step + print self.model.start_time + timedelta(seconds=off) + except StopIteration: + pass + +# def test_reports(self, sample_model_fcn2): +# (self.sc, self.model) = ROCTests.mk_objs(sample_model_fcn2) +# self.reset_and_release() +# self.burn.boom_length = 3500.0 +# self.burn.prepare_for_model_run(self.sc) +# assert self.burn._swath_width == 1050 +# assert len(self.burn.report) == 2 +# +# def test_serialize(self, sample_model_fcn2): +# (self.sc, self.model) = ROCTests.mk_objs(sample_model_fcn2) +# self.reset_and_release() +# self.burn.serialize() +# +# def test_prepare_for_model_step(self, sample_model_fcn2): +# (self.sc, self.model) = ROCTests.mk_objs(sample_model_fcn2) +# self.reset_and_release() +# +# self.burn.prepare_for_model_run(self.sc) +# self.burn.prepare_for_model_step(self.sc, time_step, active_start) +# +# assert self.burn._active == True +# assert self.burn._ts_collected == 93576.38888888889 + + +# def test_inactive(self): +# d = Disperse(name='test', +# platform='Test Platform', +# timeseries=[(datetime(2000, 1, 1, 1, 0, 0), datetime(2000, 1, 1, 2, 0, 0))]) +# d.prepare_for_model_run() +# +# +class TestRocSkim(ROCTests): + skim = Skim(speed=2.0, + storage=2000.0, + swath_width=150, + group='A', + throughput=0.75, + nameplate_pump=100.0, + skim_efficiency_type='meh', + recovery=0.75, + recovery_ef=0.75, + decant=0.75, + decant_pump=150.0, + discharge_pump=1000.0, + rig_time=timedelta(minutes=30), + timeseries=[(datetime(2012, 9, 15, 12, 0), datetime(2012, 9, 16, 1, 0))], + transit_time=timedelta(hours=2)) + + def test_prepare_for_model_run(self, sample_model_fcn2): + (self.sc, self.model) = ROCTests.mk_objs(sample_model_fcn2) + self.reset_and_release() + self.skim.prepare_for_model_run(self.sc) + + def test_prepare_for_model_step(self, sample_model_fcn2): + (self.sc, self.model) = ROCTests.mk_objs(sample_model_fcn2) + self.reset_and_release() + + self.skim.prepare_for_model_run(self.sc) + self.skim.prepare_for_model_step(self.sc, time_step, active_start) + + assert self.skim._active == True + + def test_weather_elements(self, sample_model_fcn2): + (self.sc, self.model) = ROCTests.mk_objs(sample_model_fcn2) + self.reset_and_release() + skim = Skim(speed=2.0, + storage=2000.0, + swath_width=150, + group='A', + throughput=0.75, + nameplate_pump=100.0, + skim_efficiency_type='meh', + recovery=0.75, + recovery_ef=0.75, + decant=0.75, + decant_pump=150.0, + discharge_pump=1000.0, + rig_time=timedelta(minutes=30), + timeseries=[(datetime(2012, 9, 15, 12, 0), datetime(2012, 9, 16, 1, 0))], + transit_time=timedelta(hours=2)) + + self.model.weatherers.append(skim) + self.model.rewind() + self.model.step() + self.model.step() + self.model.step() + self.model.step() + + def test_serialization(self): + s = TestRocSkim.skim + + ser = s.serialize() + assert 'timeseries' in ser + deser = Skim.deserialize(ser) + s2 = Skim.new_from_dict(deser) + ser2 = s2.serialize() + ser.pop('id') + ser2.pop('id') + assert ser == ser2 + + def test_model_save(self, sample_model_fcn2): + s = TestRocSkim.skim + (self.sc, self.model) = ROCTests.mk_objs(sample_model_fcn2) + self.model.weatherers.append(s) + self.model.save('./') + + def test_model_load(self): + m = load('./Model.zip') diff --git a/py_gnome/tests/unit_tests/test_weatherers/test_weatherer.py b/py_gnome/tests/unit_tests/test_weatherers/test_weatherer.py index d1467d6e2..83fca5b2c 100644 --- a/py_gnome/tests/unit_tests/test_weatherers/test_weatherer.py +++ b/py_gnome/tests/unit_tests/test_weatherers/test_weatherer.py @@ -12,10 +12,32 @@ from gnome.spill.elements import floating from gnome.environment import Water -from gnome.weatherers import Weatherer, HalfLifeWeatherer + +from gnome.weatherers import * + from oil_library import get_oil_props from conftest import weathering_data_arrays, test_oil +from gnome.weatherers import (Weatherer, + HalfLifeWeatherer, + ChemicalDispersion, + Skimmer, + Burn, + ROC_Burn, + ROC_Disperse, + Beaching, + Evaporation, + NaturalDispersion, + # OilParticleAggregation, + Dissolution, + # Biodegradation, + Emulsification, + WeatheringData, + FayGravityViscous, + ConstantArea, + Langmuir, + ) + subs = get_oil_props(test_oil) rel_time = datetime(2012, 8, 20, 13) # yyyy/month/day/hr/min/sec @@ -58,3 +80,29 @@ def test_one_weather(self): print '\nsc["mass"]:\n', sc['mass'] assert np.allclose(0.5 * orig_mc.sum(1), sc['mass']) assert np.allclose(0.5 * orig_mc, sc['mass_components']) + +# # all the weatherers +# __all__ = [Weatherer, +# HalfLifeWeatherer, +# ChemicalDispersion, +# Skimmer, +# Burn, +# ROC_Burn, +# ROC_Disperse, +# Beaching, +# Evaporation, +# NaturalDispersion, +# # OilParticleAggregation, +# Dissolution, +# # Biodegradation, +# Emulsification, +# WeatheringData, +# FayGravityViscous, +# ConstantArea, +# Langmuir, + +def test_sort_order(): + assert weatherer_sort(Dissolution()) > weatherer_sort(NaturalDispersion()) + + + diff --git a/py_gnome/tests/unit_tests/test_weatherers/test_weathering_data.py b/py_gnome/tests/unit_tests/test_weatherers/test_weathering_data.py index c4704bf99..9c39ca5ab 100644 --- a/py_gnome/tests/unit_tests/test_weatherers/test_weathering_data.py +++ b/py_gnome/tests/unit_tests/test_weatherers/test_weathering_data.py @@ -149,7 +149,8 @@ def test_density_update_frac_water(self, vary_frac_water): exp_res = (wd.water.get('density') * sc['frac_water'] + (1 - sc['frac_water']) * init_dens) - assert np.all(sc['density'] == exp_res) + #assert np.all(sc['density'] == exp_res) + assert np.allclose(sc['density'], exp_res) assert np.all(sc['density'] > init_dens) assert np.all(sc['viscosity'] > init_visc) else: