├── flexure ├── tests │ ├── __init__.py │ └── test_flexure.py ├── __init__.py ├── cmd.py ├── cfuncs.pyx ├── funcs.py └── flexure.py ├── requirements.txt ├── recipe └── meta.yaml ├── README.rst ├── examples ├── example_point_load.py ├── example_two_point_load.py ├── example_loading_everywhere.py └── example_random_point_loads.py ├── LICENSE ├── .travis.yml ├── setup.py └── .gitignore /flexure/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | scipy 3 | landlab 4 | -------------------------------------------------------------------------------- /flexure/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | .. codeauthor:: Eric Hutton 4 | 5 | .. sectionauthor:: Eric Hutton 6 | """ 7 | __version__ = '0.1' 8 | 9 | from .flexure import Flexure 10 | from .funcs import (get_flexure_parameter, subside_point_load, 11 | subside_point_loads) 12 | 13 | __all__ = ['Flexure', 'get_flexure_parameter', 'subside_point_load', 14 | 'subside_point_loads'] 15 | -------------------------------------------------------------------------------- /recipe/meta.yaml: -------------------------------------------------------------------------------- 1 | {% set name = "flexure" %} 2 | {% set version = "0.1" %} 3 | 4 | package: 5 | name: {{ name }} 6 | version: {{ version }} 7 | 8 | source: 9 | path: .. 10 | 11 | requirements: 12 | build: 13 | - python 14 | - cython 15 | - numpy x.x 16 | - landlab 17 | run: 18 | - python 19 | - numpy x.x 20 | - landlab 21 | 22 | build: 23 | number: 0 24 | script: python setup.py install --single-version-externally-managed --record record.txt 25 | 26 | test: 27 | requirements: 28 | - nose 29 | imports: 30 | - flexure 31 | commands: 32 | - nosetests --with-doctest flexure 33 | 34 | about: 35 | home: https://github.com/landlab/flexure 36 | license: MIT 37 | summary: Deform the lithosphere with 1D or 2D flexure. 38 | dev_url: https://github.com/landlab/flexure 39 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://travis-ci.org/landlab/flexure-component.svg?branch=master 2 | :target: https://travis-ci.org/landlab/flexure-component 3 | 4 | Flexure 5 | ======= 6 | 7 | Deform the lithosphere with 1D or 2D flexure. Landlab component that 8 | implements a 1 and 2D lithospheric flexure model. 9 | 10 | 11 | The most current development version is always available from our git 12 | repository: 13 | 14 | http://github.com/landlab/flexure-component 15 | 16 | Install 17 | ------- 18 | 19 | To install *flexure* with `conda`: 20 | 21 | .. code:: 22 | 23 | > conda install flexure -c landlab 24 | 25 | Run 26 | --- 27 | 28 | To run *flexure* from the command line: 29 | 30 | .. code:: 31 | 32 | > flexure - --params=params.yaml < loads.txt 33 | 34 | For help: 35 | 36 | .. code:: 37 | 38 | > flexure -h 39 | -------------------------------------------------------------------------------- /examples/example_point_load.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | """ 3 | 4 | 5 | """ 6 | 7 | from landlab.components.flexure import Flexure 8 | from landlab import RasterModelGrid 9 | 10 | 11 | def add_load_to_middle_of_grid(grid, load): 12 | shape = grid.shape 13 | 14 | load_array = grid.field_values( 15 | 'node', 'lithosphere__overlying_pressure_increment').view() 16 | load_array.shape = shape 17 | load_array[shape[0] / 2, shape[1] / 2] = load 18 | 19 | 20 | def main(): 21 | (n_rows, n_cols) = (100, 100) 22 | (dy, dx) = (10e3, 10e3) 23 | 24 | grid = RasterModelGrid(n_rows, n_cols, dx) 25 | 26 | flex = Flexure(grid, method='flexure') 27 | 28 | add_load_to_middle_of_grid(grid, 1e7) 29 | 30 | flex.update() 31 | 32 | grid.imshow('node', 'lithosphere_surface__elevation_increment', 33 | symmetric_cbar=True, show=True) 34 | 35 | 36 | if __name__ == '__main__': 37 | main() 38 | -------------------------------------------------------------------------------- /flexure/cmd.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import argparse 3 | 4 | import numpy as np 5 | 6 | from landlab import RasterModelGrid, load_params 7 | from flexure import Flexure 8 | 9 | 10 | def main(): 11 | parser = argparse.ArgumentParser() 12 | parser.add_argument('loads', type=argparse.FileType('r'), 13 | help='loading file') 14 | parser.add_argument('--params', type=argparse.FileType('r'), 15 | help='parameter file') 16 | 17 | args = parser.parse_args() 18 | 19 | loads = np.loadtxt(args.loads) 20 | params = load_params(args.params) 21 | spacing = params.pop('spacing', [1., 1.]) 22 | 23 | grid = RasterModelGrid(loads.shape, spacing=spacing) 24 | grid.at_node['lithosphere__overlying_pressure_increment'] = loads 25 | 26 | flexure = Flexure(grid, **params) 27 | flexure.update() 28 | 29 | dz = grid.at_node['lithosphere_surface__elevation_increment'] 30 | 31 | np.savetxt(sys.stdout, dz.reshape(grid.shape)) 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Landlab 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: generic 2 | os: 3 | - linux 4 | - osx 5 | env: 6 | matrix: 7 | - TRAVIS_PYTHON_VERSION="2.7" NUMPY_VERSION="1.10" 8 | - TRAVIS_PYTHON_VERSION="2.7" NUMPY_VERSION="1.11" 9 | - TRAVIS_PYTHON_VERSION="3.4" NUMPY_VERSION="1.10" 10 | - TRAVIS_PYTHON_VERSION="3.4" NUMPY_VERSION="1.11" 11 | - TRAVIS_PYTHON_VERSION="3.5" NUMPY_VERSION="1.10" 12 | - TRAVIS_PYTHON_VERSION="3.5" NUMPY_VERSION="1.11" 13 | global: 14 | - CONDA_PREFIX=$HOME/miniconda 15 | - MINICONDA_URL_BASE="https://repo.continuum.io/miniconda/Miniconda3-latest" 16 | sudo: false 17 | before_install: 18 | - | 19 | if [[ $TRAVIS_OS_NAME == "osx" ]]; then 20 | brew remove --force $(brew list) 21 | brew cleanup -s 22 | rm -rf $(brew --cache) 23 | fi 24 | install: 25 | - | 26 | if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then 27 | OS="MacOSX-x86_64" 28 | else 29 | OS="Linux-x86_64" 30 | fi 31 | - curl $MINICONDA_URL_BASE-$OS.sh > $HOME/minconda.sh 32 | - bash $HOME/minconda.sh -b -p $CONDA_PREFIX 33 | - export PATH="$CONDA_PREFIX/bin:$PATH" 34 | - hash -r 35 | - conda config --set always_yes yes --set changeps1 no 36 | - conda install python=$TRAVIS_PYTHON_VERSION -c conda-forge 37 | - conda install -q conda-build anaconda-client coverage sphinx 38 | script: 39 | - conda build ./recipe -c landlab --numpy=$NUMPY_VERSION 40 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup, find_packages, Extension 4 | 5 | import numpy as np 6 | 7 | ext_modules = [ 8 | Extension('flexure.cfuncs', ['flexure/cfuncs.pyx', ]), 9 | ] 10 | 11 | 12 | setup(name='flexure', 13 | version='0.1', 14 | description='Landlab flexure component', 15 | author='Eric Hutton', 16 | author_email='eric.hutton@colorado.edu', 17 | url='https://github.com/landlab/flexure', 18 | classifiers=[ 19 | 'Intended Audience :: Science/Research', 20 | 'License :: OSI Approved :: MIT License', 21 | 'Operating System :: OS Independent', 22 | 'Programming Language :: Cython', 23 | 'Programming Language :: Python :: 2.6', 24 | 'Programming Language :: Python :: 2.7', 25 | 'Programming Language :: Python :: Implementation :: CPython', 26 | 'Topic :: Scientific/Engineering :: Physics' 27 | ], 28 | packages=find_packages(), 29 | install_requires=['scipy>=0.12', 30 | 'nose>=1.3', 31 | 'six', 32 | ], 33 | setup_requires=['cython'], 34 | entry_points={ 35 | 'console_scripts': [ 36 | 'flexure=flexure.cmd:main', 37 | ] 38 | }, 39 | include_dirs = [np.get_include()], 40 | ext_modules=ext_modules, 41 | ) 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | -------------------------------------------------------------------------------- /examples/example_two_point_load.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | import numpy as np 4 | 5 | from landlab.components.flexure import Flexure 6 | from landlab import RasterModelGrid 7 | 8 | 9 | SHAPE = (100, 100) 10 | SPACING = (10e3, 10e3) 11 | LOAD_LOCS = [ 12 | (SHAPE[0] / 2, SHAPE[1] / 2), 13 | (SHAPE[0] / 4, SHAPE[1] * 3 / 4), 14 | ] 15 | 16 | 17 | def put_two_point_loads_on_grid(grid): 18 | load = grid.field_values('node', 19 | 'lithosphere__overlying_pressure_increment') 20 | load = load.view() 21 | load.shape = grid.shape 22 | for loc in LOAD_LOCS: 23 | load[loc] = 10e6 24 | 25 | 26 | def create_lithosphere_elevation_with_bulge(grid): 27 | grid.add_zeros('node', 'lithosphere_surface__elevation') 28 | 29 | z = grid.field_values('node', 'lithosphere_surface__elevation').view() 30 | z.shape = grid.shape 31 | 32 | (y, x) = np.meshgrid(np.linspace(0, np.pi * .5, grid.shape[0]), 33 | np.linspace(0, np.pi * .5, grid.shape[1])) 34 | (x0, y0) = (np.pi / 3, np.pi / 8) 35 | np.sin((x - x0) ** 2 + (y - y0) ** 2, out=z) 36 | 37 | 38 | def main(): 39 | 40 | grid = RasterModelGrid(SHAPE[0], SHAPE[1], SPACING[0]) 41 | 42 | create_lithosphere_elevation_with_bulge(grid) 43 | 44 | flex = Flexure(grid, method='flexure') 45 | 46 | put_two_point_loads_on_grid(grid) 47 | 48 | flex.update() 49 | 50 | grid.at_node['lithosphere_surface__elevation'] += grid.at_node['lithosphere_surface__elevation_increment'] 51 | grid.imshow('node', 'lithosphere_surface__elevation', 52 | symmetric_cbar=False, show=True) 53 | 54 | 55 | if __name__ == '__main__': 56 | main() 57 | -------------------------------------------------------------------------------- /examples/example_loading_everywhere.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | import numpy as np 4 | 5 | from landlab.components.flexure import Flexure 6 | from landlab import RasterModelGrid 7 | 8 | 9 | def get_random_load_magnitudes(n_loads): 10 | return np.random.normal(1e3, 10e6, n_loads) 11 | 12 | 13 | def put_loads_on_grid(grid, load_sizes): 14 | load = grid.at_node['lithosphere__overlying_pressure_increment'] 15 | load[:] = load_sizes 16 | 17 | #for (loc, size) in zip(load_locations, load_sizes): 18 | # load.flat[loc] = size 19 | 20 | 21 | def main(): 22 | import argparse 23 | 24 | parser = argparse.ArgumentParser() 25 | parser.add_argument('--shape', type=int, default=200, 26 | help='Number rows and columns') 27 | parser.add_argument('--spacing', type=int, default=5e3, 28 | help='Spading between rows and columns (m)') 29 | parser.add_argument('--n-procs', type=int, default=1, 30 | help='Number of processors to use') 31 | parser.add_argument('--plot', action='store_true', default=False, 32 | help='Plot an image of the total deflection') 33 | 34 | args = parser.parse_args() 35 | 36 | shape = (args.shape, args.shape) 37 | spacing = (args.spacing, args.spacing) 38 | 39 | load_sizes = get_random_load_magnitudes(args.shape * args.shape) 40 | 41 | grid = RasterModelGrid(shape[0], shape[1], spacing[0]) 42 | 43 | flex = Flexure(grid, method='flexure') 44 | 45 | put_loads_on_grid(grid, load_sizes) 46 | 47 | flex.update(n_procs=args.n_procs) 48 | 49 | if args.plot: 50 | grid.imshow('node', 'lithosphere_surface__elevation_increment', 51 | symmetric_cbar=False, cmap='spectral', show=True) 52 | 53 | if __name__ == '__main__': 54 | main() 55 | -------------------------------------------------------------------------------- /examples/example_random_point_loads.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | import numpy as np 4 | 5 | from landlab.components.flexure import Flexure 6 | from landlab import RasterModelGrid 7 | 8 | 9 | def get_random_load_locations(shape, n_loads): 10 | return np.random.random_integers(0, shape[0] * shape[1] - 1, n_loads) 11 | 12 | 13 | def get_random_load_magnitudes(n_loads): 14 | return np.random.normal(1e3, 10e7, n_loads) 15 | 16 | 17 | def put_loads_on_grid(grid, load_locations, load_sizes): 18 | load = grid.at_node['lithosphere__overlying_pressure_increment'].view() 19 | for (loc, size) in zip(load_locations, load_sizes): 20 | load.flat[loc] = size 21 | 22 | 23 | def main(): 24 | import argparse 25 | 26 | parser = argparse.ArgumentParser() 27 | parser.add_argument('--n-loads', type=int, default=16, 28 | help='Number of loads to apply') 29 | parser.add_argument('--shape', type=int, default=200, 30 | help='Number rows and columns') 31 | parser.add_argument('--spacing', type=int, default=5e3, 32 | help='Spading between rows and columns (m)') 33 | parser.add_argument('--n-procs', type=int, default=1, 34 | help='Number of processors to use') 35 | parser.add_argument('--plot', action='store_true', default=False, 36 | help='Plot an image of the total deflection') 37 | 38 | args = parser.parse_args() 39 | 40 | shape = (args.shape, args.shape) 41 | spacing = (args.spacing, args.spacing) 42 | 43 | load_locs = get_random_load_locations(shape, args.n_loads) 44 | load_sizes = get_random_load_magnitudes(args.n_loads) 45 | 46 | grid = RasterModelGrid(shape[0], shape[1], spacing[0]) 47 | 48 | flex = Flexure(grid, method='flexure') 49 | 50 | put_loads_on_grid(grid, load_locs, load_sizes) 51 | 52 | flex.update(n_procs=args.n_procs) 53 | 54 | if args.plot: 55 | grid.imshow('node', 'lithosphere_surface__elevation_increment', 56 | symmetric_cbar=False, cmap='spectral', show=True) 57 | 58 | if __name__ == '__main__': 59 | main() 60 | -------------------------------------------------------------------------------- /flexure/tests/test_flexure.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | """ 3 | Unit tests for landlab.components.flexure.flexure 4 | """ 5 | from nose.tools import assert_equal, assert_true, assert_raises, with_setup 6 | try: 7 | from nose.tools import assert_is_instance 8 | except ImportError: 9 | from landlab.testing.tools import assert_is_instance 10 | import numpy as np 11 | 12 | from landlab import RasterModelGrid 13 | from landlab.components.flexure.flexure import Flexure 14 | 15 | 16 | (_SHAPE, _SPACING, _ORIGIN) = ((20, 20), (10e3, 10e3), (0., 0.)) 17 | _ARGS = (_SHAPE, _SPACING, _ORIGIN) 18 | 19 | 20 | def setup_grid(): 21 | from landlab import RasterModelGrid 22 | grid = RasterModelGrid((20, 20), spacing=10e3) 23 | flex = Flexure(grid) 24 | globals().update({ 25 | 'flex': Flexure(grid) 26 | }) 27 | 28 | 29 | @with_setup(setup_grid) 30 | def test_name(): 31 | assert_equal(flex.name, 'Flexure') 32 | 33 | 34 | @with_setup(setup_grid) 35 | def test_input_var_names(): 36 | assert_equal(flex.input_var_names, 37 | ('lithosphere__overlying_pressure_increment', )) 38 | 39 | 40 | @with_setup(setup_grid) 41 | def test_output_var_names(): 42 | assert_equal(flex.output_var_names, 43 | ('lithosphere_surface__elevation_increment',)) 44 | 45 | 46 | @with_setup(setup_grid) 47 | def test_var_units(): 48 | assert_equal(set(flex.input_var_names) | 49 | set(flex.output_var_names), 50 | set(dict(flex.units).keys())) 51 | 52 | assert_equal(flex.var_units('lithosphere_surface__elevation_increment'), 'm') 53 | assert_equal(flex.var_units('lithosphere__overlying_pressure_increment'), 54 | 'Pa') 55 | 56 | 57 | @with_setup(setup_grid) 58 | def test_grid_shape(): 59 | assert_equal(flex.grid.number_of_node_rows, _SHAPE[0]) 60 | assert_equal(flex.grid.number_of_node_columns, _SHAPE[1]) 61 | 62 | 63 | @with_setup(setup_grid) 64 | def test_grid_x_extent(): 65 | assert_equal(flex.grid.extent[1], (_SHAPE[1] - 1) * _SPACING[1]) 66 | 67 | 68 | @with_setup(setup_grid) 69 | def test_grid_y_extent(): 70 | assert_equal(flex.grid.extent[0], (_SHAPE[0] - 1) * _SPACING[0]) 71 | 72 | 73 | @with_setup(setup_grid) 74 | def test_field_getters(): 75 | for name in flex.grid['node']: 76 | field = flex.grid['node'][name] 77 | assert_is_instance(field, np.ndarray) 78 | assert_equal(field.shape, 79 | (flex.grid.number_of_node_rows * 80 | flex.grid.number_of_node_columns, )) 81 | 82 | assert_raises(KeyError, lambda: flex.grid['not_a_var_name']) 83 | 84 | 85 | @with_setup(setup_grid) 86 | def test_field_initialized_to_zero(): 87 | for name in flex.grid['node']: 88 | field = flex.grid['node'][name] 89 | assert_true(np.all(field == 0.)) 90 | -------------------------------------------------------------------------------- /flexure/cfuncs.pyx: -------------------------------------------------------------------------------- 1 | from multiprocessing import Pool 2 | 3 | import numpy as np 4 | cimport numpy as np 5 | cimport cython 6 | 7 | from libc.math cimport fabs 8 | from libc.stdlib cimport abs 9 | 10 | 11 | _RHO_MANTLE = 3300. 12 | _GRAVITY = 9.81 13 | 14 | 15 | DTYPE = np.double 16 | ctypedef np.double_t DTYPE_t 17 | 18 | 19 | @cython.boundscheck(False) 20 | def subside_parallel_row(np.ndarray[DTYPE_t, ndim=1] w, 21 | np.ndarray[DTYPE_t, ndim=1] load, 22 | np.ndarray[DTYPE_t, ndim=1] r, 23 | DTYPE_t alpha, 24 | DTYPE_t gamma_mantle): 25 | cdef int ncols = w.size 26 | cdef double inv_c = 1. / (2. * np.pi * gamma_mantle * alpha ** 2.) 27 | cdef double c 28 | cdef int i 29 | cdef int j 30 | 31 | for i in range(ncols): 32 | if fabs(load[i]) > 1e-6: 33 | c = load[i] * inv_c 34 | for j in range(ncols): 35 | w[j] += - c * r[abs(j - i)] 36 | 37 | 38 | def subside_grid(np.ndarray[DTYPE_t, ndim=2] w, 39 | np.ndarray[DTYPE_t, ndim=2] load, 40 | np.ndarray[DTYPE_t, ndim=2] r, 41 | DTYPE_t alpha, DTYPE_t gamma_mantle): 42 | cdef int nrows = w.shape[0] 43 | cdef int i 44 | cdef int j 45 | 46 | for i in range(nrows): 47 | for j in range(nrows): 48 | subside_parallel_row(w[j], load[i], r[abs(j - i)], alpha, gamma_mantle) 49 | 50 | 51 | def subside_grid_strip(np.ndarray[DTYPE_t, ndim=2] load, 52 | np.ndarray[DTYPE_t, ndim=2] r, 53 | DTYPE_t alpha, DTYPE_t gamma_mantle, strip_range): 54 | (start, stop) = strip_range 55 | 56 | cdef np.ndarray w = np.zeros((stop - start, load.shape[1]), dtype=DTYPE) 57 | cdef i 58 | 59 | for i in range(load.shape[0]): 60 | for j in range(start, stop): 61 | subside_parallel_row(w[j - start], load[i], r[abs(j - i)], alpha, 62 | gamma_mantle) 63 | 64 | return w, strip_range 65 | 66 | 67 | def tile_grid_into_strips(grid, n_strips): 68 | rows_per_strip = grid.shape[0] // n_strips 69 | 70 | starts = np.arange(0, grid.shape[0], rows_per_strip) 71 | stops = starts + rows_per_strip 72 | stops[-1] = grid.shape[0] 73 | 74 | return zip(starts, stops) 75 | 76 | 77 | def _subside_grid_strip_helper(args): 78 | return subside_grid_strip(*args) 79 | 80 | 81 | def subside_grid_in_parallel(np.ndarray[DTYPE_t, ndim=2] w, 82 | np.ndarray[DTYPE_t, ndim=2] load, 83 | np.ndarray[DTYPE_t, ndim=2] r, 84 | DTYPE_t alpha, DTYPE_t gamma_mantle, n_procs): 85 | if n_procs == 1: 86 | return subside_grid(w, load, r, alpha, gamma_mantle) 87 | 88 | strips = tile_grid_into_strips(w, n_procs) 89 | 90 | args = [(load, r, alpha, gamma_mantle, strip) for strip in strips] 91 | 92 | pool = Pool(processes=n_procs) 93 | 94 | results = pool.map(_subside_grid_strip_helper, args) 95 | for dz, strip in results: 96 | start, stop = strip 97 | w[start:stop] += dz 98 | -------------------------------------------------------------------------------- /flexure/funcs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import numpy as np 4 | import scipy.special 5 | from multiprocessing import Pool 6 | 7 | _POISSON = .25 8 | 9 | _N_PROCS = 4 10 | 11 | 12 | def get_flexure_parameter(h, E, n_dim, gamma_mantle=33000.): 13 | """ 14 | Calculate the flexure parameter based on some physical constants. *h* is 15 | the Effective elastic thickness of Earth's crust (m), *E* is Young's 16 | Modulus, and *n_dim* is the number of spatial dimensions for which the 17 | flexure parameter is used. The number of dimension must be either 1, or 18 | 2. 19 | 20 | Examples 21 | -------- 22 | >>> from __future__ import print_function 23 | >>> from landlab.components.flexure import get_flexure_parameter 24 | 25 | >>> eet = 65000. 26 | >>> youngs = 7e10 27 | >>> alpha = get_flexure_parameter(eet, youngs, 1) 28 | >>> print('%.3f' % round(alpha, 3)) 29 | 119965.926 30 | 31 | >>> alpha = get_flexure_parameter(eet, youngs, 2) 32 | >>> print('%.2f' % alpha) 33 | 84828.72 34 | """ 35 | D = E * pow(h, 3) / 12. / (1. - pow(_POISSON, 2)) 36 | 37 | assert(n_dim == 1 or n_dim == 2) 38 | 39 | if n_dim == 2: 40 | alpha = pow(D / gamma_mantle, .25) 41 | else: 42 | alpha = pow(4. * D / gamma_mantle, .25) 43 | 44 | return alpha 45 | 46 | 47 | def _calculate_distances(locs, coords): 48 | if isinstance(locs[0], (float, int)): 49 | return np.sqrt(pow(coords[0] - locs[0], 2) + 50 | pow(coords[1] - locs[1], 2)) 51 | else: 52 | r = pow(coords[0][:, np.newaxis] - locs[0], 2) 53 | r += pow(coords[1][:, np.newaxis] - locs[1], 2) 54 | return np.sqrt(r, out=r) 55 | 56 | 57 | def _calculate_deflections(load, locs, coords, alpha, out=None, 58 | gamma_mantle=33000.): 59 | c = - load / (2. * np.pi * gamma_mantle * pow(alpha, 2.)) 60 | r = _calculate_distances(locs, coords) / alpha 61 | 62 | if isinstance(c, (float, int)): 63 | return np.multiply(scipy.special.kei(r), c, out=out) 64 | else: 65 | scipy.special.kei(r, out=r) 66 | np.multiply(r, c[np.newaxis, :], out=r) 67 | return np.sum(r, axis=1, out=out) 68 | 69 | 70 | def subside_point_load(load, loc, coords, params=None, out=None): 71 | """Calculate deflection at points due a point load. 72 | 73 | Calculate deflections on a grid, defined by the points in the *coords* 74 | tuple, due to a point load of magnitude *load* applied at *loc*. 75 | 76 | *x* and *y* are the x and y coordinates of each node of the solution 77 | grid (in meters). The scalars *eet* and *youngs* define the crustal 78 | properties. 79 | 80 | Parameters 81 | ---------- 82 | load : float 83 | Magnitude of the point load. 84 | loc : float or tuple 85 | Location of the load as either a scalar or as (*x*, *y*) 86 | coords : ndarray 87 | Array of points to calculate deflections at 88 | params : dict-like 89 | Physical parameters used for deflection calculation. Valid keys are 90 | - *eet*: Effective elastic thickness 91 | - *youngs*: Young's modulus 92 | out : ndarray, optional 93 | Array to put deflections into. 94 | 95 | Returns 96 | ------- 97 | out : ndarray 98 | Array of deflections. 99 | 100 | Examples 101 | -------- 102 | 103 | >>> from landlab.components.flexure import subside_point_load 104 | 105 | >>> params = dict(eet=65000., youngs=7e10) 106 | >>> load = 1e9 107 | 108 | Define a unifrom rectilinear grid. 109 | 110 | >>> x = np.arange(0, 10000, 100.) 111 | >>> y = np.arange(0, 5000, 100.) 112 | >>> (x, y) = np.meshgrid(x, y) 113 | >>> x.shape = (x.size, ) 114 | >>> y.shape = (y.size, ) 115 | 116 | Calculate deflections due to a load applied at position (5000., 2500.). 117 | 118 | >>> import six 119 | >>> x = np.arange(0, 10000, 1000.) 120 | >>> y = np.arange(0, 5000, 1000.) 121 | >>> (x, y) = np.meshgrid(x, y) 122 | >>> x.shape = (x.size, ) 123 | >>> y.shape = (y.size, ) 124 | >>> dz = subside_point_load(load, (5000., 2500.), (x, y), params=params) 125 | >>> print('%.5g' % round(dz.sum(), 9)) 126 | 2.6267e-05 127 | >>> six.print_(round(dz.min(), 9)) 128 | 5.24e-07 129 | >>> six.print_(round(dz.max(), 9)) 130 | 5.26e-07 131 | 132 | >>> dz = subside_point_load((1e9, 1e9), ((5000., 5000.), (2500., 2500.)), 133 | ... (x, y), params=params) 134 | >>> six.print_(round(dz.min(), 9) / 2.) 135 | 5.235e-07 136 | >>> six.print_(round(dz.max(), 9) / 2.) 137 | 5.265e-07 138 | """ 139 | params = params or dict(eet=6500., youngs=7.e10) 140 | eet, youngs = params['eet'], params['youngs'] 141 | gamma_mantle = params.get('gamma_mantle', 33000.) 142 | 143 | assert(len(loc) in [1, 2]) 144 | assert(len(coords) == len(loc)) 145 | assert(len(coords[0].shape) == 1) 146 | 147 | if not isinstance(load, (int, float, np.ndarray)): 148 | load = np.array(load) 149 | 150 | if out is None: 151 | out = np.empty(coords[0].size, dtype=np.float) 152 | 153 | alpha = get_flexure_parameter(eet, youngs, len(loc), 154 | gamma_mantle=gamma_mantle) 155 | 156 | if len(loc) == 2: 157 | _calculate_deflections(load, loc, coords, alpha, out=out, 158 | gamma_mantle=gamma_mantle) 159 | else: 160 | c = load / (2. * alpha * gamma_mantle) 161 | r = abs(coords[0] - loc[0]) / alpha 162 | out[:] = c * np.exp(-r) * (np.cos(r) + np.sin(r)) 163 | 164 | return out 165 | 166 | 167 | def subside_point_loads(loads, locs, coords, params=None, deflection=None, 168 | n_procs=1): 169 | """Calculate deflection at points due multiple point loads. 170 | 171 | Calculate lithospheric deflections due to *loads* at coordinates 172 | specified by the *locs* tuple. *coords* is a tuple that gives the 173 | coordinates of each point where deflections are calculated; *locs* is 174 | positions of the applied loads. Since this function calculates the 1D 175 | or 2D flexure equation, *coords* and *locs* must have either one or two 176 | elements. 177 | 178 | Parameters 179 | ---------- 180 | load : array_like 181 | Magnitude of the point loads. 182 | loc : tuple of (loc_x, loc_y) 183 | Load locations. 184 | coords : ndarray 185 | Array of points to calculate deflections at 186 | params : dict-like 187 | Physical parameters used for deflection calculation. Valid keys are 188 | - *eet*: Effective elastic thickness 189 | - *youngs*: Young's modulus 190 | - *gamma_mantle*: Specific weight of the mantle 191 | out : ndarray, optional 192 | Array to put deflections into. 193 | 194 | Returns 195 | ------- 196 | out : ndarray 197 | Array of deflections. 198 | """ 199 | params = params or dict(eet=6500., youngs=7.e10) 200 | eet, youngs = params['eet'], params['youngs'] 201 | gamma_mantle = params.get('gamma_mantle', 33000.) 202 | 203 | if deflection is None: 204 | deflection = np.empty(coords[0].size, dtype=np.float) 205 | 206 | assert(len(coords) in [1, 2]) 207 | assert(len(locs) == len(coords)) 208 | assert(loads.size == locs[0].size) 209 | 210 | if n_procs > 1: 211 | _subside_in_parallel(deflection, loads, locs, coords, eet, youngs, 212 | gamma_mantle, n_procs=n_procs) 213 | else: 214 | for index in loads.nonzero()[0]: 215 | loc = [dim.flat[index] for dim in locs] 216 | deflection += subside_point_load(loads.flat[index], loc, 217 | coords, eet, youngs, 218 | gamma_mantle) 219 | return deflection 220 | 221 | 222 | def _subside_point_load_helper(args): 223 | return subside_point_load(*args) 224 | 225 | 226 | def _subside_in_parallel(dz, loads, locs, coords, eet, youngs, gamma_mantle, 227 | n_procs=4): 228 | args = [] 229 | for index in loads.nonzero()[0]: 230 | loc = (locs[0].flat[index], locs[1].flat[index]) 231 | args.append((loads.flat[index], loc, coords, eet, youngs, 232 | gamma_mantle)) 233 | 234 | pool = Pool(processes=n_procs) 235 | 236 | results = pool.map(_subside_point_load_helper, args) 237 | for result in results: 238 | try: 239 | dz += result 240 | except ValueError: 241 | result.shape = dz.shape 242 | dz += result 243 | 244 | 245 | if __name__ == '__main__': 246 | import doctest 247 | doctest.testmod() 248 | -------------------------------------------------------------------------------- /flexure/flexure.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Deform the lithosphere with 1D or 2D flexure. 3 | 4 | Landlab component that implements a 1 and 2D lithospheric flexure 5 | model. 6 | 7 | Examples 8 | -------- 9 | 10 | Create a grid on which we will run the flexure calculations. 11 | 12 | >>> from landlab import RasterModelGrid 13 | >>> from landlab.components.flexure import Flexure 14 | >>> grid = RasterModelGrid((5, 4), spacing=(1.e4, 1.e4)) 15 | 16 | Check the fields that are used as input to the flexure component. 17 | 18 | >>> Flexure.input_var_names # doctest: +NORMALIZE_WHITESPACE 19 | ('lithosphere__overlying_pressure_increment',) 20 | 21 | Check the units for the fields. 22 | 23 | >>> Flexure.var_units('lithosphere__overlying_pressure_increment') 24 | 'Pa' 25 | 26 | If you are not sure about one of the input or output variables, you can 27 | get help for specific variables. 28 | 29 | >>> Flexure.var_help('lithosphere__overlying_pressure_increment') 30 | name: lithosphere__overlying_pressure_increment 31 | description: 32 | Applied pressure to the lithosphere over a time step 33 | units: Pa 34 | at: node 35 | intent: in 36 | 37 | >>> flex = Flexure(grid) 38 | 39 | In creating the component, a field (initialized with zeros) was added to the 40 | grid. Reset the interior nodes for the loading. 41 | 42 | >>> dh = grid.at_node['lithosphere__overlying_pressure_increment'] 43 | >>> dh = dh.reshape(grid.shape) 44 | >>> dh[1:-1, 1:-1] = flex.gamma_mantle 45 | 46 | >>> flex.update() 47 | 48 | >>> flex.output_var_names 49 | ('lithosphere_surface__elevation_increment',) 50 | >>> flex.grid.at_node['lithosphere_surface__elevation_increment'] 51 | ... # doctest: +NORMALIZE_WHITESPACE 52 | array([ 0., 0., 0., 0., 53 | 0., 1., 1., 0., 54 | 0., 1., 1., 0., 55 | 0., 1., 1., 0., 56 | 0., 0., 0., 0.]) 57 | """ 58 | 59 | import numpy as np 60 | 61 | from landlab import Component 62 | from landlab.utils.decorators import use_file_name_or_kwds 63 | 64 | from .funcs import get_flexure_parameter 65 | 66 | 67 | class Flexure(Component): 68 | 69 | """Deform the lithosphere with 1D or 2D flexure. 70 | 71 | Landlab component that implements a 1 and 2D lithospheric flexure 72 | model. 73 | 74 | Construction:: 75 | 76 | Flexure(grid, eet=65e3, youngs=7e10, method='airy', rho_mantle=3300., 77 | gravity=9.80665) 78 | 79 | Parameters 80 | ---------- 81 | grid : RasterModelGrid 82 | A grid. 83 | eet : float, optional 84 | Effective elastic thickness (m). 85 | youngs : float, optional 86 | Young's modulus. 87 | method : {'airy', 'flexure'}, optional 88 | Method to use to calculate deflections. 89 | rho_mantle : float, optional 90 | Density of the mantle (kg / m^3). 91 | gravity : float, optional 92 | Acceleration due to gravity (m / s^2). 93 | 94 | Examples 95 | -------- 96 | >>> from landlab import RasterModelGrid 97 | >>> from landlab.components.flexure import Flexure 98 | >>> grid = RasterModelGrid((5, 4), spacing=(1.e4, 1.e4)) 99 | 100 | >>> flex = Flexure(grid) 101 | >>> flex.name 102 | 'Flexure' 103 | >>> flex.input_var_names 104 | ('lithosphere__overlying_pressure_increment',) 105 | >>> flex.output_var_names 106 | ('lithosphere_surface__elevation_increment',) 107 | >>> sorted(flex.units) # doctest: +NORMALIZE_WHITESPACE 108 | [('lithosphere__overlying_pressure_increment', 'Pa'), 109 | ('lithosphere_surface__elevation_increment', 'm')] 110 | 111 | >>> flex.grid.number_of_node_rows 112 | 5 113 | >>> flex.grid.number_of_node_columns 114 | 4 115 | >>> flex.grid is grid 116 | True 117 | 118 | >>> np.all(grid.at_node['lithosphere_surface__elevation_increment'] == 0.) 119 | True 120 | 121 | >>> np.all(grid.at_node['lithosphere__overlying_pressure_increment'] == 0.) 122 | True 123 | >>> flex.update() 124 | >>> np.all(grid.at_node['lithosphere_surface__elevation_increment'] == 0.) 125 | True 126 | 127 | >>> load = grid.at_node['lithosphere__overlying_pressure_increment'] 128 | >>> load[4] = 1e9 129 | >>> dz = grid.at_node['lithosphere_surface__elevation_increment'] 130 | >>> np.all(dz == 0.) 131 | True 132 | 133 | >>> flex.update() 134 | >>> np.all(grid.at_node['lithosphere_surface__elevation_increment'] == 0.) 135 | False 136 | """ 137 | 138 | _name = 'Flexure' 139 | 140 | _input_var_names = ( 141 | 'lithosphere__overlying_pressure_increment', 142 | ) 143 | 144 | _output_var_names = ( 145 | 'lithosphere_surface__elevation_increment', 146 | ) 147 | 148 | _var_units = { 149 | 'lithosphere__overlying_pressure_increment': 'Pa', 150 | 'lithosphere_surface__elevation_increment': 'm', 151 | } 152 | 153 | _var_mapping = { 154 | 'lithosphere__overlying_pressure_increment': 'node', 155 | 'lithosphere_surface__elevation_increment': 'node', 156 | } 157 | 158 | _var_doc = { 159 | 'lithosphere__overlying_pressure_increment': 160 | 'Applied pressure to the lithosphere over a time step', 161 | 'lithosphere_surface__elevation_increment': 162 | 'The change in elevation of the top of the lithosphere (the land ' 163 | 'surface) in one timestep', 164 | } 165 | 166 | @use_file_name_or_kwds 167 | def __init__(self, grid, eet=65e3, youngs=7e10, method='airy', 168 | rho_mantle=3300., gravity=9.80665, **kwds): 169 | """Initialize the flexure component. 170 | 171 | Parameters 172 | ---------- 173 | grid : RasterModelGrid 174 | A grid. 175 | eet : float, optional 176 | Effective elastic thickness (m). 177 | youngs : float, optional 178 | Young's modulus. 179 | method : {'airy', 'flexure'}, optional 180 | Method to use to calculate deflections. 181 | rho_mantle : float, optional 182 | Density of the mantle (kg / m^3). 183 | gravity : float, optional 184 | Acceleration due to gravity (m / s^2). 185 | """ 186 | if method not in ('airy', 'flexure'): 187 | raise ValueError( 188 | '{method}: method not understood'.format(method=method)) 189 | 190 | self._grid = grid 191 | 192 | self._youngs = youngs 193 | self._method = method 194 | self._rho_mantle = rho_mantle 195 | self._gravity = gravity 196 | self.eet = eet 197 | 198 | super(Flexure, self).__init__(grid, **kwds) 199 | 200 | for name in self._input_var_names: 201 | if name not in self.grid.at_node: 202 | self.grid.add_zeros('node', name, units=self._var_units[name]) 203 | 204 | for name in self._output_var_names: 205 | if name not in self.grid.at_node: 206 | self.grid.add_zeros('node', name, units=self._var_units[name]) 207 | 208 | self._r = self._create_kei_func_grid(self._grid.shape, 209 | (self.grid.dy, self.grid.dx), 210 | self.alpha) 211 | 212 | @property 213 | def eet(self): 214 | """Effective elastic thickness (m).""" 215 | return self._eet 216 | 217 | @eet.setter 218 | def eet(self, new_val): 219 | if new_val <= 0: 220 | raise ValueError('Effective elastic thickness must be positive.') 221 | self._eet = new_val 222 | self._r = self._create_kei_func_grid(self._grid.shape, 223 | (self.grid.dy, self.grid.dx), 224 | self.alpha) 225 | 226 | @property 227 | def youngs(self): 228 | """Young's modulus of lithosphere (Pa).""" 229 | return self._youngs 230 | 231 | @property 232 | def rho_mantle(self): 233 | """Density of mantle (kg/m^3).""" 234 | return self._rho_mantle 235 | 236 | @property 237 | def gamma_mantle(self): 238 | """Specific density of mantle (N/m^3).""" 239 | return self._rho_mantle * self._gravity 240 | 241 | @property 242 | def gravity(self): 243 | """Acceleration due to gravity (m/s^2).""" 244 | return self._gravity 245 | 246 | @property 247 | def method(self): 248 | """Name of method used to calculate deflections.""" 249 | return self._method 250 | 251 | @property 252 | def alpha(self): 253 | """Flexure parameter (m).""" 254 | return get_flexure_parameter(self._eet, self._youngs, 2, 255 | gamma_mantle=self.gamma_mantle) 256 | 257 | @staticmethod 258 | def _create_kei_func_grid(shape, spacing, alpha): 259 | from scipy.special import kei 260 | 261 | dx, dy = np.meshgrid(np.arange(shape[1]) * spacing[1], 262 | np.arange(shape[0]) * spacing[0]) 263 | 264 | return kei(np.sqrt(dx ** 2 + dy ** 2) / alpha) 265 | 266 | def update(self, n_procs=1): 267 | """Update fields with current loading conditions. 268 | 269 | Parameters 270 | ---------- 271 | n_procs : int, optional 272 | Number of processors to use for calculations. 273 | """ 274 | load = self.grid.at_node['lithosphere__overlying_pressure_increment'] 275 | deflection = self.grid.at_node['lithosphere_surface__elevation_increment'] 276 | 277 | new_load = load.copy() 278 | 279 | deflection.fill(0.) 280 | 281 | if self._method == 'airy': 282 | deflection[:] = new_load / self.gamma_mantle 283 | else: 284 | self.subside_loads(new_load, deflection=deflection, 285 | n_procs=n_procs) 286 | 287 | def subside_loads(self, loads, deflection=None, n_procs=1): 288 | """Subside surface due to multiple loads. 289 | 290 | Parameters 291 | ---------- 292 | loads : ndarray of float 293 | Loads applied to each grid node. 294 | deflection : ndarray of float, optional 295 | Buffer to place resulting deflection values. 296 | n_procs : int, optional 297 | Number of processors to use for calculations. 298 | 299 | Returns 300 | ------- 301 | ndarray of float 302 | Deflections caused by the loading. 303 | """ 304 | if deflection is None: 305 | deflection = np.empty(self.shape, dtype=np.float) 306 | 307 | from .cfuncs import subside_grid_in_parallel 308 | 309 | w = deflection.reshape(self._grid.shape) 310 | load = loads.reshape(self._grid.shape) 311 | 312 | subside_grid_in_parallel(w, load * self._grid.dx * self._grid.dy, 313 | self._r, self.alpha, self.gamma_mantle, 314 | n_procs) 315 | 316 | return deflection 317 | --------------------------------------------------------------------------------