├── .DS_Store ├── .gitattributes ├── .github └── workflows │ ├── Test.yml │ ├── black.yml │ ├── codcov.yml │ ├── publish-to-pypi.yml │ └── publish-to-test-pypi.yml ├── .gitignore ├── .readthedocs.yaml ├── .vscode └── settings.json ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── docs ├── .DS_Store ├── .nojekyll ├── Makefile ├── make.bat └── source │ ├── .DS_Store │ ├── API.rst │ ├── _static │ ├── .DS_Store │ ├── PISA_conic_function.png │ ├── coordinates_system.png │ ├── plots │ │ └── API_Qz_curve.png │ ├── py_API_clay │ │ └── Schematic_curves.jpg │ ├── py_API_sand │ │ ├── A_factor_def.jpg │ │ ├── C_coeffs_graph.jpg │ │ ├── formula01.jpg │ │ ├── k_vs_phi.jpg │ │ └── overview_struct.jpg │ ├── schematic_curves.png │ ├── usage │ │ ├── analyses_plots │ │ │ ├── deflection_results_plot.png │ │ │ ├── forces_results_plot.png │ │ │ ├── main_results_plot.png │ │ │ └── model_plot_with_soil.png │ │ └── pycurves │ │ │ └── api_sand_example_build.png │ └── validation │ │ ├── CowdenClay_D1_D2.png │ │ ├── GDSM_D2t.png │ │ └── GDSM_D2t_moment.png │ ├── conf.py │ ├── gettingstarted.rst │ ├── index.rst │ └── usage.rst ├── pyproject.toml ├── requirements.txt ├── resources ├── Computational_speed │ ├── Build_stiffness_matrix.ipynb │ └── SolveEq.ipynb └── Stiffness matrices │ ├── Stiffness_matrices.ipynb │ └── Stiffness_matrices2.ipynb ├── samples ├── Burd_et_al_G0_profile.png ├── CowdenClay_Byrne2020_D1.ipynb ├── Cowden_Clay_D1_and_D2_piles.png ├── GDSM_Burd2020_D2t.ipynb ├── GDSM_pile_D2t_Burd_et_al_2020.png ├── GDSM_pile_D2t_Burd_et_al_2020_M.png ├── api_clay_compare.ipynb ├── api_sand_kallehave.ipynb ├── check_kernel.ipynb ├── run_check_kernel.ipynb ├── run_usage.ipynb └── usage3.ipynb ├── setup.cfg ├── setup.py ├── src └── openpile │ ├── __init__.py │ ├── construct.py │ ├── core │ ├── __init__.py │ ├── _model_build.py │ ├── kernel.py │ ├── misc.py │ ├── txt.py │ └── validation.py │ ├── materials.py │ ├── soilmodels.py │ ├── utils │ ├── Hb_curves.py │ ├── Mb_curves.py │ ├── __init__.py │ ├── graphics.py │ ├── hooks.py │ ├── misc.py │ ├── mt_curves.py │ ├── py_curves.py │ ├── qz_curves.py │ └── tz_curves.py │ └── winkler.py ├── test ├── __init__.py ├── test_construct.py ├── test_hooks.py ├── test_pycurves.py └── test_utils_misc.py └── tox.ini /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TchilDill/openpile/fe16b26d01249b37edf9a8f16c72e01081ae8171/.DS_Store -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Jupyter notebook files reclassified as Python: 2 | *.ipynb linguist-language=Python -------------------------------------------------------------------------------- /.github/workflows/Test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ${{ matrix.platform }} 12 | strategy: 13 | matrix: 14 | platform: [ubuntu-latest, macos-latest, windows-latest] 15 | python-version: ['3.8','3.9','3.10'] 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Set up Python ${{ matrix.python-version }} 20 | uses: actions/setup-python@v4 21 | with: 22 | python-version: ${{ matrix.python-version }} 23 | - name: Install dependencies 24 | run: | 25 | python -m pip install --upgrade pip 26 | python -m pip install tox tox-gh-actions 27 | - name: Test with tox 28 | run: tox 29 | env: 30 | PLATFORM: ${{ matrix.platform }} -------------------------------------------------------------------------------- /.github/workflows/black.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | lint: 11 | 12 | runs-on: windows-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: psf/black@stable 16 | with: 17 | options: 18 | "--check --verbose" 19 | src: "./src" 20 | jupyter: true 21 | version: "~= 22.0" -------------------------------------------------------------------------------- /.github/workflows/codcov.yml: -------------------------------------------------------------------------------- 1 | name: Run tests and upload coverage 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | test: 11 | name: Run tests and collect coverage 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v3 16 | with: 17 | fetch-depth: 0 18 | 19 | - name: Set up Python 20 | uses: actions/setup-python@v4 21 | with: 22 | python-version: '3.10' 23 | 24 | - name: Install dependencies 25 | run: pip install pytest pytest-cov 26 | 27 | - name: Install OpenPile 28 | run: pip install . 29 | 30 | - name: Run tests 31 | run: pytest --cov-branch --cov-report=xml 32 | 33 | - name: Upload results to Codecov 34 | uses: codecov/codecov-action@v5 35 | with: 36 | token: ${{ secrets.CODECOV_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/publish-to-pypi.yml: -------------------------------------------------------------------------------- 1 | name: Publish Python 🐍 distributions 📦 to PyPI 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | build-n-publish: 10 | name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - name: Set up Python 15 | uses: actions/setup-python@v4 16 | with: 17 | python-version: "3.8" 18 | - name: Install pypa/build 19 | run: >- 20 | python -m 21 | pip install 22 | build 23 | --user 24 | - name: Build a binary wheel and a source tarball 25 | run: >- 26 | python -m 27 | build 28 | --sdist 29 | --wheel 30 | --outdir dist/ 31 | . 32 | - name: Publish distribution 📦 to Test PyPI 33 | uses: pypa/gh-action-pypi-publish@release/v1 34 | with: 35 | verbose: True 36 | password: ${{ secrets.TEST_PYPI_API_TOKEN }} 37 | repository-url: https://test.pypi.org/legacy/ 38 | skip_existing: True 39 | - name: Publish distribution 📦 to PyPI 40 | if: startsWith(github.ref, 'refs/tags') 41 | uses: pypa/gh-action-pypi-publish@release/v1 42 | with: 43 | password: ${{ secrets.PYPI_API_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/publish-to-test-pypi.yml: -------------------------------------------------------------------------------- 1 | name: Publish Python 🐍 distributions 📦 to TestPyPI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build-n-publish: 11 | name: Build and publish Python 🐍 distributions 📦 to TestPyPI 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Set up Python 16 | uses: actions/setup-python@v4 17 | with: 18 | python-version: "3.8" 19 | - name: Install pypa/build 20 | run: >- 21 | python -m 22 | pip install 23 | build 24 | --user 25 | - name: Build a binary wheel and a source tarball 26 | run: >- 27 | python -m 28 | build 29 | --sdist 30 | --wheel 31 | --outdir dist/ 32 | . 33 | - name: Publish distribution 📦 to Test PyPI 34 | uses: pypa/gh-action-pypi-publish@release/v1 35 | with: 36 | verbose: True 37 | password: ${{ secrets.TEST_PYPI_API_TOKEN }} 38 | repository-url: https://test.pypi.org/legacy/ 39 | skip_existing: True -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # MacOs 10 | .DS_Store 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | pip-wheel-metadata/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv* 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.ioo/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Set the version of Python and other tools you might need 9 | build: 10 | os: ubuntu-22.04 11 | tools: 12 | python: "3.8" 13 | # You can also specify other tool versions: 14 | # nodejs: "19" 15 | # rust: "1.64" 16 | # golang: "1.19" 17 | 18 | # Build documentation in the docs/source directory with Sphinx 19 | sphinx: 20 | configuration: docs/source/conf.py 21 | builder: html 22 | fail_on_warning: true 23 | 24 | 25 | # Optionally declare the Python requirements required to build your docs 26 | python: 27 | install: 28 | - method: pip 29 | path: . 30 | extra_requirements: 31 | - docs 32 | - method: setuptools 33 | path: . -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.testing.pytestArgs": [ 3 | "test" 4 | ], 5 | "python.testing.unittestEnabled": false, 6 | "python.testing.pytestEnabled": true, 7 | "python.formatting.provider": "black", 8 | "editor.inlineSuggest.showToolbar": "onHover" 9 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | --------- 3 | 4 | All notable changes to this project will be documented in this file. 5 | 6 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 7 | and this project adheres to [Semantic Versioning](http://semver.org/), 8 | and [PEP 440](https://www.python.org/dev/peps/pep-0440/). 9 | 10 | ## [1.0.1] - 2025-04-10 11 | 12 | ### Fixed 13 | - *Sørensen sand* and *Kallehave sand* py-curves implementation into `openpile.soilmodels.API_sand()` led to some minor bugs that are now corrected. 14 | 15 | ## [1.0.0] - 2025-04-04 16 | 17 | *OpenPile's kernel now accounts for axial soil springs when running a Winkler analysis. The axial soil springs are by default turned on (`base_axial` and `distributed_axial` arguments of `openpile.construct.Model`) and considered if an `axial_model` is fed to a `openpile.construct.Layer`* 18 | 19 | The minimum python version to use with Openpile >= 1.0.0 is python 3.8. Please also note that the major version of Numpy (v2.0) does not work with OpenPile since we use Numba, which at the time of writing this is imcompatible with Numpy 2.0+. 20 | 21 | ### Added 22 | - the `PileMaterial` class in the new `openpile.materials` module is now used to determine the material of the structure. Such material can now be customised by users by creating a new instance of `openpile.materials.PileMaterial`. 23 | - *Sørensen sand* and *Kallehave sand* py-curves are now available and require a setting to be turned on in `openpile.soilmodels.API_sand()`, the corresponding curves are also available in the module `openpile.utils.py_curves`. 24 | - extensions of API-type lateral soil models are now considered in the string output when printing out the model or the entire soil profile, i.e. when running `print(openpile.construct.Layer)` 25 | 26 | 27 | ### Modified 28 | 29 | - The coordinate system follows now a more traditional approach, which still is a right-hand system however, the x and z components are swapped, this was done in order for soil springs which are called 't-z' and 'Q-z' by convention to be aligned with the coordinate system used in setting the boundary conditions. 30 | - `openpile.construct.Model.get_py_springs()` and other related methods to extract springs have been updated to the following naming style: `.get_distributed_lateral_springs()`, see documentation for more details. 31 | - The API clay model available until v0.7.1 has been decoupled to form the `API_clay` and the mofidied_Matlock models, new function and new model can be seen here: `Openpile.utils.py_curves.modified_Matlock()` and `Openpile.soilmodels.Modified_Matlock_clay`. Such decision was made to make it clearer on what model is used when running an analysis. 32 | - The API sand model as well as API sand py curves now accepts a user-defined inital subgrade modulus value. If not provided, it reverts to the API definition based on friction angle. 33 | - functions found in `openpile.calculate` are now dependent on `openpile.construct.Pile` and `openpile.construct.SoilProfile` instead of `openpile.construct.Model`. 34 | - module `openpile.analyze` object renamed to `openpile.winkler`. 35 | - class `AnalyzeResult` object renamed to `WinklerResult`. 36 | - module `openpile.multipliers` object renamed to `openpile.hooks`. 37 | 38 | ### Deleted 39 | - The functions `openpile.analyze.simple_beam_analysis()` and `openpile.analyze.simple_winkler_analysis()` are now removed from the code. 40 | - The method `openpile.construct.Pile.set_I()` method has been deleted, the second moment of area of a given pile segment can be changed by calling a Custom PileSection Geometry. 41 | - The property `openpile.construct.Pile.E` cannot be overriden anymore, instead use the new feature where any material can be provided to the pile via `openpile.materials.PileMaterial`. 42 | - The method `openpile.construct.Model.get_pointload()` is now deprecated and cannot be used anymore. Use the property `openpile.construct.Model.boundary_conditions` instead to list all boundary conditions. 43 | 44 | ## [0.7.1] - 2023-11-21 45 | 46 | ### Fixed 47 | 48 | - maximum resistance in API_clay where Pmax was wrongly calculated based on depth instead of diameter, credits to Zhenghui Qiu for spotting this. 49 | 50 | 51 | ## [0.7.0] - 2023-11-12 52 | 53 | ### Added 54 | 55 | - Added soil models: 56 | - `openpile.soilmodels.Bothkennar_clay` from the PISA joint-industry project 57 | 58 | ## [0.6.0] - 2023-10-23 59 | 60 | ### Added 61 | - added properties to `Pile` object: 62 | - `openpile.construct.Pile.tip_area` and 63 | - `openpile.construct.Pile.tip_footprint` 64 | - added soil springs: 65 | - `openpile.utils.py_curves.custom_pisa_sand` and `openpile.utils.py_curves.custom_pisa_clay` 66 | - `openpile.utils.mt_curves.custom_pisa_sand` and `openpile.utils.mt_curves.custom_pisa_clay` 67 | - `openpile.utils.Hb_curves.custom_pisa_sand` and `openpile.utils.Hb_curves.custom_pisa_clay` 68 | - `openpile.utils.Mb_curves.custom_pisa_sand` and `openpile.utils.Mb_curves.custom_pisa_clay` 69 | - added soil models: 70 | - `openpile.soilmodels.Bothkennar_clay` from the PISA joint-industry project 71 | - `openpile.soilmodels.Custom_pisa_sand` and `openpile.soilmodels.Custom_pisa_clay`, these models can be used to customise PISA formulations based on external sources, such as an FE model. 72 | - new functions to calculate Dunkirk Sand and Cowden Clay normalized parameters, these functions are in the module: `openpile.utils.multipliers` and are the following: `get_cowden_clay_(..)_norm_param()` and `get_dunkirk_sand_(..)_norm_param()`. 73 | 74 | ### Fixed 75 | - Stress stiffness matrix modified for not yielding negative values due to axial force component. 76 | 77 | 78 | ## [0.5.0] - 2023-08-02 79 | - added soil spring `openpile.utils.py_curves.reese_weakrock` 80 | - added soil model `openpile.soilmodels.Reese_weakrock` 81 | 82 | ## [0.4.0] - 2023-07-30 83 | - Updates in documentation 84 | - new methods available for `openpile.analyze.Result` class: 85 | - new method `winkler()` replacing `simple_winkler_analysis()`, the use of the latter triggers a deprecation warning when used. 86 | - new method `beam()` replacing `simple_beam_analysis()`, the use of the latter triggers a deprecation warning when used. 87 | - new method `openpile.analyze.details()` that provides summary of an `AnalyzeResult` object. 88 | - new methods available for `openpile.construct.Model` class: 89 | - `openpile.construct.Model.get_py_springs()` 90 | - `openpile.construct.Model.get_mt_springs()` 91 | - `openpile.construct.Model.get_Hb_spring()` 92 | - `openpile.construct.Model.get_Mb_spring()` 93 | - new feature which allow user to enter a function in place of a float for springs multipliers when creating `SoilModel` objects. the function must take as input a PositiveFloat representing the depth below ground level, and as output the multiplier that shall be used by the soil spring for this depth. 94 | - new `openpile.utils.multipliers` module that stores validated functions for use in multipliers in SoilModels objects. 95 | 96 | ## [0.3.3] - 2023-05-19 97 | - fix error in Dunkirk_sand rotational springs 98 | - benchmarked Dunkirk sand soil model against literature from Burd et al (2020). 99 | 100 | ## [0.3.2] - 2023-05-18 101 | - fixed bug in the kernel when applying base springs. 102 | - clean up some part of the root directory 103 | - benchmarked Cowden Clay soil model against literature from Byrne et al (2020). 104 | 105 | ## [0.3.1] - 2023-05-16 106 | - fixed bug in kernel that was amplifying the soil resistance and yielding unrealistic forces in the 107 | pile. 108 | 109 | ## [0.3.0] - 2023-05-02 110 | - new method to retrieve the p-y mobilisation of springs in Results via the `.py_mobilization()` 111 | - update to the connectivity plot `openpile.construct.Model.plot()` that adds the soil profile to the plot 112 | if a soil profile is fed to the model. 113 | - tz- and Qz-curves following the well-known API standards are now included in `openpile.utils` 114 | - updates to the documentation 115 | - API p-y curves now officially unit tested 116 | 117 | ## [0.2.0] - 2023-04-24 118 | - new Pile constructor `openpile.construct.Pile.create_tubular` creating a 119 | circular and hollow steel pile of constant cross section. 120 | - new properties for `openpile.construct.Pile`: `weight` and `volume` 121 | - new `openpile.construct.Pile` method: `set_I()` to change the second moment of area of a given pile segment 122 | - new `SoilProfile.plot()` method to visualize the soil profile 123 | - API sand and API clay curves and models now accept `kind` instead of `Neq` arguments to differentiate between 124 | static and cyclic curves 125 | - create() methods in the construct module are now deprecated and should not be used anymore. Precisely, that is the 126 | case for `openpile. construct.Pile.create()` and `openpile.construct.Model.create()`. 127 | 128 | ## [0.1.0] - 2023-04-10 129 | ### Added 130 | - PISA sand and clay models (called Dunkirk_sand and Cowden_clay models) 131 | - Rotational springs and base springs (shear and moment), see `utils` module 132 | - New set of unit tests covering the `Construct` module, coverage is not 100%. 133 | 134 | ## [0.0.1] - 2023-03-31 135 | ### Notes 136 | - first release of openpile with simple beam and simple winkler analysis with lateral springs 137 | 138 | ### Added 139 | - `Construct` module with Pile, SoilProfile, Layer, and Model objects 140 | - `utils` module with py curves 141 | - `Analysis`modile with `simple_beam_analysis()` and `simple_winkler_analysis()` 142 | - `Result` class that provides the user with plotting and Pandas Dataframe overview of results. 143 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenPile 2 | 3 | Open-source PILE software. 4 | 5 | 6 | [![License: LGPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) 7 | [![Open Source? Yes!](https://badgen.net/badge/Open%20Source%20%3F/Yes%21/blue?icon=github)](https://github.com/Naereen/badges/) 8 | [![Downloads](https://static.pepy.tech/badge/openpile/month)](https://pepy.tech/project/openpile) 9 | 10 | ![Tests](https://github.com/TchilDill/openpile/actions/workflows/Test.yml/badge.svg) 11 | [![codecov](https://codecov.io/gh/TchilDill/Openpile/graph/badge.svg?token=HQERTZ09CV)](https://codecov.io/gh/TchilDill/Openpile) 12 | [![Documentation Status](https://readthedocs.org/projects/openpile/badge/?version=latest)](https://openpile.readthedocs.io/en/latest/?badge=latest) 13 | 14 | 15 | [![issues closed](https://img.shields.io/github/issues-closed/TchilDill/openpile)](https://github.com/TchilDill/openpile/issues) 16 | [![PRs closed](https://img.shields.io/github/issues-pr-closed/TchilDill/openpile)](https://github.com/TchilDill/openpile/pulls) 17 | [![GitHub last commit](https://img.shields.io/github/last-commit/TchilDill/openpile)](https://github.com/TchilDill/openpile/commits/master) 18 | [![Pull Requests Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat)](http://makeapullrequest.com) 19 | 20 | 21 | [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.10116667.svg)](https://doi.org/10.5281/zenodo.10116667) 22 | 23 | Please see [official documentation](https://openpile.readthedocs.io/en/latest/) for more information. 24 | 25 | TThis package is an open source python library that provides a toolbox for beam/pile calculations. It provides users with various objects to represent structural and geotechnical problems (such as piles, soil profiles, soil layers), and an assortment of routines for fast operations, including solving 1D FE beam models with or without Winkler approach. 26 | 27 | Few features at a glance: 28 | 29 | - Finite Element calculations with 1D Beam elements (following Euler-Bernoulli or Timishenko beam theories) to compute forces, deformations 30 | - Winkler’s approach of a beam supported by linear or non-linear springs 31 | - Soil springs over axial, lateral and rotational degrees of freedoms 32 | - Common soil spring formulations to model soil structure interaction with the structure (a beam or pile). 33 | 34 | This library supports the following versions of python: 3.8-3.10. 35 | Python 3.11 is not supported! 36 | 37 | ## Support 38 | 39 | This package takes time and effort. You can support by buying me a coffee. 40 | 41 | [!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/TchillDill) 42 | 43 | ## Installation Instructions 44 | 45 | **Prerequisites**: 46 | 47 | * a version of python is installed on your machine (supported versions: 3.8-3.10) 48 | * pip is installed in your environment. 49 | 50 | ```bash 51 | pip install openpile 52 | ``` 53 | 54 | ## Please share with the community 55 | 56 | This library relies on community interactions. Please consider sharing a post about `OpenPile` and the value it can provide for researcher, academia and professionals. 57 | 58 | [![GitHub Repo stars](https://img.shields.io/badge/share%20on-reddit-red?logo=reddit)](https://reddit.com/submit?url=https://github.com/TchilDill/openpile&title=openpile) 59 | [![GitHub Repo stars](https://img.shields.io/badge/share%20on-twitter-03A9F4?logo=twitter)](https://twitter.com/share?url=https://github.com/TchilDill/openpile&t=openpile) 60 | [![GitHub Repo stars](https://img.shields.io/badge/share%20on-linkedin-3949AB?logo=linkedin)](https://www.linkedin.com/shareArticle?url=https://github.com/TchilDill/openpile&title=openpile) -------------------------------------------------------------------------------- /docs/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TchilDill/openpile/fe16b26d01249b37edf9a8f16c72e01081ae8171/docs/.DS_Store -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/source/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TchilDill/openpile/fe16b26d01249b37edf9a8f16c72e01081ae8171/docs/source/.DS_Store -------------------------------------------------------------------------------- /docs/source/API.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _ApplicationProgrammingInterface: 3 | 4 | --- 5 | API 6 | --- 7 | 8 | .. contents:: API contents 9 | :depth: 2 10 | :backlinks: top 11 | 12 | .. automodule:: openpile.construct 13 | :members: 14 | :exclude-members: __init__, PydanticConfig, soil_and_pile_bottom_elevation_match, create, check_elevations, check_layers_elevations, 15 | AbstractPile, AbstractLayer, AbstractSoilProfile, AbstractModel 16 | 17 | 18 | .. automodule:: openpile.materials 19 | :members: 20 | :exclude-members: __init__, PydanticConfig, AbstractPileMaterial 21 | 22 | 23 | 24 | .. automodule:: openpile.soilmodels 25 | :members: 26 | :exclude-members: __init__, PydanticConfig, PydanticConfigFrozen, ConstitutiveModel, 27 | LateralModel, AxialModel, py_spring_fct, mt_spring_fct, Hb_spring_fct, 28 | Mb_spring_fct, spring_signature 29 | 30 | 31 | .. automodule:: openpile.winkler 32 | :members: 33 | :exclude-members: simple_winkler_analysis, simple_beam_analysis, PydanticConfig, structural_forces_to_df, springs_mob_to_df, reaction_forces_to_df, disp_to_df, __init__ 34 | 35 | 36 | .. `utils` module 37 | .. ============== 38 | 39 | .. automodule:: openpile.utils.py_curves 40 | :members: 41 | :exclude-members: random 42 | 43 | 44 | .. automodule:: openpile.utils.mt_curves 45 | :members: 46 | :exclude-members: random 47 | 48 | 49 | .. automodule:: openpile.utils.tz_curves 50 | :members: 51 | :exclude-members: random, kraft_modification 52 | 53 | 54 | .. automodule:: openpile.utils.qz_curves 55 | :members: 56 | :exclude-members: random 57 | 58 | 59 | .. automodule:: openpile.utils.hooks 60 | :members: 61 | :exclude-members: 62 | 63 | 64 | -------------------------------------------------------------------------------- /docs/source/_static/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TchilDill/openpile/fe16b26d01249b37edf9a8f16c72e01081ae8171/docs/source/_static/.DS_Store -------------------------------------------------------------------------------- /docs/source/_static/PISA_conic_function.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TchilDill/openpile/fe16b26d01249b37edf9a8f16c72e01081ae8171/docs/source/_static/PISA_conic_function.png -------------------------------------------------------------------------------- /docs/source/_static/coordinates_system.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TchilDill/openpile/fe16b26d01249b37edf9a8f16c72e01081ae8171/docs/source/_static/coordinates_system.png -------------------------------------------------------------------------------- /docs/source/_static/plots/API_Qz_curve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TchilDill/openpile/fe16b26d01249b37edf9a8f16c72e01081ae8171/docs/source/_static/plots/API_Qz_curve.png -------------------------------------------------------------------------------- /docs/source/_static/py_API_clay/Schematic_curves.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TchilDill/openpile/fe16b26d01249b37edf9a8f16c72e01081ae8171/docs/source/_static/py_API_clay/Schematic_curves.jpg -------------------------------------------------------------------------------- /docs/source/_static/py_API_sand/A_factor_def.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TchilDill/openpile/fe16b26d01249b37edf9a8f16c72e01081ae8171/docs/source/_static/py_API_sand/A_factor_def.jpg -------------------------------------------------------------------------------- /docs/source/_static/py_API_sand/C_coeffs_graph.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TchilDill/openpile/fe16b26d01249b37edf9a8f16c72e01081ae8171/docs/source/_static/py_API_sand/C_coeffs_graph.jpg -------------------------------------------------------------------------------- /docs/source/_static/py_API_sand/formula01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TchilDill/openpile/fe16b26d01249b37edf9a8f16c72e01081ae8171/docs/source/_static/py_API_sand/formula01.jpg -------------------------------------------------------------------------------- /docs/source/_static/py_API_sand/k_vs_phi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TchilDill/openpile/fe16b26d01249b37edf9a8f16c72e01081ae8171/docs/source/_static/py_API_sand/k_vs_phi.jpg -------------------------------------------------------------------------------- /docs/source/_static/py_API_sand/overview_struct.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TchilDill/openpile/fe16b26d01249b37edf9a8f16c72e01081ae8171/docs/source/_static/py_API_sand/overview_struct.jpg -------------------------------------------------------------------------------- /docs/source/_static/schematic_curves.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TchilDill/openpile/fe16b26d01249b37edf9a8f16c72e01081ae8171/docs/source/_static/schematic_curves.png -------------------------------------------------------------------------------- /docs/source/_static/usage/analyses_plots/deflection_results_plot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TchilDill/openpile/fe16b26d01249b37edf9a8f16c72e01081ae8171/docs/source/_static/usage/analyses_plots/deflection_results_plot.png -------------------------------------------------------------------------------- /docs/source/_static/usage/analyses_plots/forces_results_plot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TchilDill/openpile/fe16b26d01249b37edf9a8f16c72e01081ae8171/docs/source/_static/usage/analyses_plots/forces_results_plot.png -------------------------------------------------------------------------------- /docs/source/_static/usage/analyses_plots/main_results_plot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TchilDill/openpile/fe16b26d01249b37edf9a8f16c72e01081ae8171/docs/source/_static/usage/analyses_plots/main_results_plot.png -------------------------------------------------------------------------------- /docs/source/_static/usage/analyses_plots/model_plot_with_soil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TchilDill/openpile/fe16b26d01249b37edf9a8f16c72e01081ae8171/docs/source/_static/usage/analyses_plots/model_plot_with_soil.png -------------------------------------------------------------------------------- /docs/source/_static/usage/pycurves/api_sand_example_build.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TchilDill/openpile/fe16b26d01249b37edf9a8f16c72e01081ae8171/docs/source/_static/usage/pycurves/api_sand_example_build.png -------------------------------------------------------------------------------- /docs/source/_static/validation/CowdenClay_D1_D2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TchilDill/openpile/fe16b26d01249b37edf9a8f16c72e01081ae8171/docs/source/_static/validation/CowdenClay_D1_D2.png -------------------------------------------------------------------------------- /docs/source/_static/validation/GDSM_D2t.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TchilDill/openpile/fe16b26d01249b37edf9a8f16c72e01081ae8171/docs/source/_static/validation/GDSM_D2t.png -------------------------------------------------------------------------------- /docs/source/_static/validation/GDSM_D2t_moment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TchilDill/openpile/fe16b26d01249b37edf9a8f16c72e01081ae8171/docs/source/_static/validation/GDSM_D2t_moment.png -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # For the full list of built-in configuration values, see the documentation: 4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 5 | 6 | # -- Project information ----------------------------------------------------- 7 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 8 | 9 | project = "OpenPile" 10 | copyright = "2023, Guillaume Melin" 11 | author = "Guillaume Melin" 12 | 13 | import sys 14 | from pathlib import Path 15 | 16 | pypath = Path(__file__).parents[2] 17 | # add path 18 | sys.path.insert(0, str(Path(pypath / "src"))) 19 | from openpile import __version__ 20 | 21 | release = __version__ 22 | 23 | # -- General configuration --------------------------------------------------- 24 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 25 | 26 | extensions = [ 27 | "sphinx.ext.napoleon", # support for numpy and google docstrings 28 | "sphinx.ext.autodoc", 29 | "sphinx.ext.githubpages", 30 | "sphinx.ext.mathjax", 31 | "sphinx.ext.viewcode", 32 | "sphinx.ext.todo", 33 | "sphinx_copybutton", 34 | "sphinx.ext.doctest", 35 | "matplotlib.sphinxext.plot_directive", 36 | "sphinxcontrib.autodoc_pydantic", 37 | ] 38 | 39 | autodoc_default_options = { 40 | "member-order": "bysource", 41 | "special-members": "__init__", 42 | "undoc-members": True, 43 | "exclude-members": "__weakref__", 44 | "show-inheritance": False, 45 | } 46 | 47 | napoleon_numpy_docstring = True 48 | napoleon_custom_sections = ["Theory"] 49 | 50 | auoclass_content = "class" 51 | # Automatically extract typehints when specified and place them in 52 | # descriptions of the relevant function/method. 53 | autodoc_typehints = "description" 54 | 55 | # Don't show class signature with the class' name. 56 | autodoc_class_signature = "separated" 57 | 58 | templates_path = ["_templates"] 59 | exclude_patterns = [] 60 | 61 | # option for the copy button extension 62 | copybutton_prompt_text = r">>> |\.\.\. " 63 | copybutton_prompt_is_regexp = True 64 | 65 | # option for matplotlib extension 66 | plot_include_source = True 67 | plot_html_show_source_link = False 68 | plot_html_show_formats = False 69 | 70 | # -- Options for LaTeX output ------------------------------------------------ 71 | latex_engine = "pdflatex" 72 | numfig = True 73 | 74 | # -- Options for HTML output ------------------------------------------------- 75 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 76 | 77 | html_theme = "sphinx_rtd_theme" 78 | html_static_path = ["_static"] 79 | 80 | 81 | # pydantic autodoc 82 | autodoc_pydantic_validator_list_fields = False 83 | autodoc_pydantic_model_show_json = False 84 | autodoc_pydantic_settings_show_json = False 85 | autodoc_pydantic_model_show_config_summary = False 86 | autodoc_pydantic_model_show_validator_summary = False 87 | autodoc_pydantic_model_show_validator_members = False 88 | autodoc_pydantic_model_show_field_summary = False 89 | autodoc_pydantic_model_undoc_members = False 90 | autodoc_pydantic_model_signature_prefix = "Class" 91 | autodoc_pydantic_field_show_constraints = False 92 | -------------------------------------------------------------------------------- /docs/source/gettingstarted.rst: -------------------------------------------------------------------------------- 1 | --------------- 2 | Getting started 3 | --------------- 4 | 5 | Installation 6 | ------------ 7 | 8 | Installing OpenPile is as easy as installing well-known python packages such as Pandas or Numpy. This is done 9 | via the below pip command. 10 | 11 | .. code-block:: console 12 | 13 | pip install openpile 14 | 15 | 16 | **Below, you can find more help to get it installed on your local machine:** 17 | 18 | #. Go to the official Python website at https://www.python.org/downloads/ and 19 | download python for your operating system (Windows, macOS, or Linux). 20 | **Note that only python version ranging from 3.8 to 3.10 are valid for openpile to run.** 21 | #. Run the installer and follow the prompts to complete the installation process. 22 | #. After installing Python, open a terminal or command prompt and type python to verify that 23 | Python is installed correctly. You should see the Python version number. 24 | #. To start using Python packages, you can use the built-in package manager called pip. 25 | Type pip or pip3 (depending on your installation) in the terminal to see if it's installed correctly. 26 | #. To install the awesome OpenPile package, use the command: 27 | 28 | .. code-block:: 29 | 30 | pip install openpile 31 | 32 | This will download and install the package and all its dependencies. 33 | 34 | 35 | **Below, an alternative to the above should you want to use the Anaconda distribution:** 36 | 37 | #. Download and install the latest version of Anaconda from https://www.anaconda.com/products/individual. 38 | #. Open the Anaconda Navigator application and select the "Environments" tab. 39 | #. Click the "Create" button to create a new environment and enter a name for it (e.g. python38). 40 | #. Select the desired Python version from the dropdown menu (e.g. Python 3.8) and click the "Create" button. 41 | #. To switch to a specific environment, select it from the list and click the "Activate" button. 42 | #. To install a package in the environment using pip, open a terminal window by clicking the 43 | "Open Terminal" button in the "Environments" tab. 44 | #. Use the following command in the Anaconda command prompt or via the navigator to install a package using pip in the environment: 45 | 46 | .. code-block:: console 47 | 48 | pip install openpile 49 | 50 | 51 | First model in OpenPile 52 | ----------------------- 53 | 54 | Please refer to the :ref:`usage` section where examples can help you get started. 55 | 56 | Philosophy 57 | ---------- 58 | 59 | these calculations can be as simple/generic as it gets, e.g. calculating the weight of a beam, its volume, or perform more advanced calculations with 1D Finite Element Method. 60 | 61 | .. show a plot of the beam/pile 62 | 63 | .. ref to objects in API. 64 | 65 | 66 | System of units 67 | --------------- 68 | 69 | The unit system used in this library is the `International System of Units (SI) `_. 70 | The primary units used in OpenPile are kN (kilonewton) and m (meter). 71 | The documentation (e.g. docstrings) should inform the user just well enough. If there is any ambiguity, 72 | please create an issue so that we can solve this. 73 | 74 | 75 | Coordinates System 76 | ------------------ 77 | 78 | The coordinate system in OpenPile follows right hand system as shown below: 79 | 80 | .. figure:: _static/coordinates_system.png 81 | :width: 80% 82 | 83 | Coordinates system followed in OpenPile. 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. openpile documentation master file, created by 2 | sphinx-quickstart on Mon Jan 2 07:59:17 2023. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | ------------------------------------ 7 | Welcome to OpenPile's documentation! 8 | ------------------------------------ 9 | 10 | This package is an open source python library that provides a toolbox for beam/pile calculations. It provides users with various objects to represent structural and geotechnical problems (such as piles, soil profiles, soil layers), 11 | and an assortment of routines for fast operations, including solving 1D FE beam models with or without Winkler approach. 12 | 13 | Few features at a glance: 14 | 15 | * Finite Element calculations with 1D Beam elements (following Euler-Bernoulli or Timishenko beam theories) to compute forces, deformations 16 | * Winkler's approach of a beam supported by linear or non-linear springs 17 | * Soil springs over axial, lateral and rotational degrees of freedoms 18 | * Common soil spring formulations to model soil structure interaction with the structure (a beam or pile) 19 | 20 | More details on how to use the library can be found in :ref:`usage` 21 | 22 | .. important:: 23 | This library supports the following versions of python: 3.8-3.10. 24 | Python 3.11+ is not supported as one dependency of OpenPile (namely Numba) does not currently support Python 3.11+. 25 | 26 | .. raw:: html 27 | 28 | 29 | 30 | 31 | Cite OpenPile in your research paper 32 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 33 | 34 | You can refer to OpenPile in your research paper by citing the following: 35 | 36 | | *Guillaume M. (2023). TchilDill/openpile: OpenPile version 0.7.0 (v0.7.0). Zenodo. https://doi.org/10.5281/zenodo.10116667* 37 | 38 | .. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.10116667.svg 39 | :target: https://doi.org/10.5281/zenodo.10116667 40 | 41 | 42 | .. toctree:: 43 | :maxdepth: 2 44 | :caption: Contents: 45 | 46 | gettingstarted 47 | usage 48 | API 49 | 50 | 51 | License 52 | ^^^^^^^ 53 | 54 | Copyright (C) 2023 Guillaume Melin 55 | 56 | This program is free software: you can redistribute it and/or modify 57 | it under the terms of the GNU General Public License as published by 58 | the Free Software Foundation, either version 3 of the License, or 59 | (at your option) any later version. 60 | 61 | You should have received a copy of the GNU General Public License 62 | along with this program. If not, see . 63 | 64 | 65 | Disclaimer 66 | ^^^^^^^^^^ 67 | 68 | This open source package is primarily provided for educational and research purposes. 69 | It is not intended to replace professional engineering advice or analyses. 70 | The analyses performed using this package may not accurately 71 | represent real-world situations or account for all possible variables and conditions. 72 | 73 | Users of this package assume all responsibility and liability for any consequences 74 | resulting from its use. The developers and contributors of this package make no 75 | warranties or guarantees, expressed or implied, regarding its accuracy, reliability, 76 | or fitness for any particular purpose. 77 | 78 | Users should exercise caution and use their professional judgement when interpreting 79 | and applying the results generated by this package. In addition, users should be aware 80 | that the laws and regulations governing the use of finite element analyses may vary 81 | depending on their location and industry, and should comply with all 82 | applicable laws and regulations. 83 | 84 | By using this package, users acknowledge and agree to the terms of this disclaimer. 85 | 86 | 87 | Indices and tables 88 | ================== 89 | 90 | * :ref:`genindex` 91 | * :ref:`modindex` 92 | * :ref:`search` -------------------------------------------------------------------------------- /docs/source/usage.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _usage: 3 | 4 | ----- 5 | Usage 6 | ----- 7 | 8 | The package allows a quick input by the user (given in this section) and quick calculation. 9 | 10 | Jupyter Notebooks/IPython are recommended platforms to use openpile as it provides 11 | an interactive experience. 12 | 13 | .. _Ex1-create_a_pile: 14 | 15 | Example 1 - Create a pile 16 | ========================= 17 | 18 | A pile can be created in the simple following way in openpile. 19 | 20 | .. doctest:: 21 | 22 | >>> # import the Pile object from the construct module 23 | >>> from openpile.construct import Pile, CircularPileSection 24 | 25 | >>> # Create a Pile 26 | >>> pile = Pile(name = "", 27 | ... material='Steel', 28 | ... sections=[ 29 | ... CircularPileSection( 30 | ... top=0, 31 | ... bottom=-10, 32 | ... diameter=7.5, 33 | ... thickness=0.07 34 | ... ), 35 | ... CircularPileSection( 36 | ... top=-10, 37 | ... bottom=-40, 38 | ... diameter=7.5, 39 | ... thickness=0.08 40 | ... ), 41 | ... ] 42 | ... ) 43 | 44 | >>> # Print the pile data 45 | >>> print(pile) 46 | Elevation [m] Diameter [m] Wall thickness [m] Area [m2] I [m4] 47 | 0 0.0 7.5 0.07 1.633942 11.276204 48 | 1 -10.0 7.5 0.07 1.633942 11.276204 49 | 2 -10.0 7.5 0.08 1.864849 12.835479 50 | 3 -40.0 7.5 0.08 1.864849 12.835479 51 | 52 | Alternative methods can be used to create a Pile, these methods can shorten the lines of codes needed to create the pile. 53 | A pile can also be created with a custom material. 54 | 55 | For instance, the below snippet of code with another pile made of a custom material: 56 | 57 | .. doctest:: 58 | 59 | >>> # create a custom material 60 | >>> from openpile.materials import PileMaterial 61 | >>> my_concrete = PileMaterial( 62 | ... name="Concrete", 63 | ... uw=24, 64 | ... E=30e6, 65 | ... nu=0.2, 66 | ... ) 67 | 68 | >>> # create pile 69 | >>> p = Pile.create_tubular( 70 | ... name="", top_elevation=0, bottom_elevation=-40, diameter=10, wt=0.050, material=my_concrete 71 | ... ) 72 | >>> print(p) 73 | Elevation [m] Diameter [m] Wall thickness [m] Area [m2] I [m4] 74 | 0 0.0 10.0 0.05 1.562942 19.342388 75 | 1 -40.0 10.0 0.05 1.562942 19.342388 76 | 77 | 78 | Once the pile (object) is created, the user can use its properties and methods to interact with it. 79 | A simple view of the pile can be extracted by printing the object as below: 80 | 81 | 82 | The user can also extract easily the pile length, elevations and other properties. 83 | Please see the :py:class:`openpile.construct.Pile` 84 | 85 | 86 | As of now, only a circular pile can be modelled in openpile, however the user can bypass 87 | the construcutor by updating the pile's properties governing the pile's behaviour under 88 | axial or lateral loading. 89 | 90 | .. versionadded:: 1.0.0 91 | The user cannot anymore override the young modulus `E` but we can now create custom PileMaterial 92 | via :py:meth:`openpile.materials.PileMaterial.custom()` 93 | 94 | .. versionadded:: 1.0.0 95 | The user cannot anymore override the pile width or the second moment of area `I` but 96 | we can now create a custom PileSegment object by creating a subclass of the 97 | class :py:class:`openpile.materials.PileSegment`. 98 | 99 | .. todo: add a doctest with the PileSegment class 100 | .. todo: add a doctest with the PileMaterial class 101 | 102 | 103 | .. _Ex2-plot_a_pycurve: 104 | 105 | Example 2 - Calculate and plot a p-y curve 106 | ========================================== 107 | 108 | openpile allows for quick access to soil curves. The below example shows 109 | how one can quickly calculate a soil spring at a given elevation and plot it. 110 | 111 | The different curves available can be found in the below modules. 112 | 113 | * :py:mod:`openpile.utils.py_curves` (distributed lateral curves) 114 | * :py:mod:`openpile.utils.mt_curves` (distributed rotational curves) 115 | * :py:mod:`openpile.utils.tz_curves` (distributed axial curves) 116 | * :py:mod:`openpile.utils.qz_curves` (base axial curves) 117 | * :py:mod:`openpile.utils.Hb_curves` (base shear curves) 118 | * :py:mod:`openpile.utils.Mb_curves` (base moment curves) 119 | 120 | Here below is an example of how a static curve for the 121 | API sand model looks like. The `matplotlib` library can be used 122 | easily with OpenPile. 123 | 124 | .. plot:: 125 | 126 | # import p-y curve for api_sand from openpile.utils 127 | from openpile.utils.py_curves import api_sand 128 | 129 | y, p = api_sand(sig=50, # vertical stress in kPa 130 | X = 5, # depth in meter 131 | phi = 35, # internal angle of friction 132 | D = 5, # the pile diameter 133 | below_water_table=True, # use initial subgrade modulus under water 134 | kind="static", # static curve 135 | ) 136 | 137 | # create a plot of the results with Matplotlib 138 | import matplotlib.pyplot as plt 139 | 140 | # use matplotlib to visual the soil curve 141 | plt.plot(y,p) 142 | plt.ylabel('p [kN/m]') 143 | plt.xlabel('y [m]') 144 | 145 | 146 | 147 | .. _Ex3-create_a_layer: 148 | 149 | Example 3 - Create a soil profile's layer 150 | ========================================= 151 | 152 | The creation of a layer can be done with the below lines of code. 153 | A Lateral and/or Axial soil model can be assigned to a layer. 154 | 155 | .. doctest:: 156 | 157 | >>> from openpile.construct import Layer 158 | >>> from openpile.soilmodels import API_clay 159 | 160 | >>> # Create a layer 161 | >>> layer1 = Layer(name='Soft Clay', 162 | ... top=0, 163 | ... bottom=-10, 164 | ... weight=18, 165 | ... lateral_model=API_clay(Su=[30,35], eps50=[0.01, 0.02], kind="static"), ) 166 | 167 | >>> print(layer1) # doctest: +NORMALIZE_WHITESPACE 168 | Name: Soft Clay 169 | Elevation: (0.0) - (-10.0) m 170 | Weight: 18.0 kN/m3 171 | Lateral model: API clay 172 | Su = 30.0-35.0 kPa 173 | eps50 = 0.01-0.02 174 | static curves 175 | ext: None 176 | Axial model: None 177 | 178 | 179 | .. _Ex4-create_a_soilprofile: 180 | 181 | Example 4 - Create a soil profile 182 | ================================= 183 | 184 | .. doctest:: 185 | 186 | >>> from openpile.construct import SoilProfile, Layer 187 | >>> from openpile.soilmodels import API_sand, API_clay 188 | 189 | >>> # Create a 40m deep offshore Soil Profile with a 15m water column 190 | >>> sp = SoilProfile( 191 | ... name="Offshore Soil Profile", 192 | ... top_elevation=0, 193 | ... water_line=15, 194 | ... layers=[ 195 | ... Layer( 196 | ... name='medium dense sand', 197 | ... top=0, 198 | ... bottom=-20, 199 | ... weight=18, 200 | ... lateral_model= API_sand(phi=33, kind="cyclic") 201 | ... ), 202 | ... Layer( 203 | ... name='firm clay', 204 | ... top=-20, 205 | ... bottom=-40, 206 | ... weight=18, 207 | ... lateral_model= API_clay(Su=[50, 70], eps50=0.015, kind="cyclic") 208 | ... ), 209 | ... ] 210 | ... ) 211 | 212 | >>> print(sp) # doctest: +NORMALIZE_WHITESPACE 213 | Layer 1 214 | ------------------------------ 215 | Name: medium dense sand 216 | Elevation: (0.0) - (-20.0) m 217 | Weight: 18.0 kN/m3 218 | Lateral model: API sand 219 | phi = 33.0° 220 | cyclic curves 221 | ext: None 222 | Axial model: None 223 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 224 | Layer 2 225 | ------------------------------ 226 | Name: firm clay 227 | Elevation: (-20.0) - (-40.0) m 228 | Weight: 18.0 kN/m3 229 | Lateral model: API clay 230 | Su = 50.0-70.0 kPa 231 | eps50 = 0.015 232 | cyclic curves 233 | ext: None 234 | Axial model: None 235 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 236 | 237 | 238 | .. _Ex5-run_winkler: 239 | 240 | Example 5 - Run a lateral pile analysis 241 | ======================================= 242 | 243 | .. plot:: 244 | :context: 245 | 246 | >>> from openpile.construct import Pile, SoilProfile, Layer, Model 247 | >>> from openpile.soilmodels import API_clay, API_sand 248 | >>> 249 | >>> p = Pile.create_tubular( 250 | ... name="", top_elevation=0, bottom_elevation=-40, diameter=7.5, wt=0.075 251 | ... ) 252 | >>> 253 | >>> # Create a 40m deep offshore Soil Profile with a 15m water column 254 | >>> sp = SoilProfile( 255 | ... name="Offshore Soil Profile", 256 | ... top_elevation=0, 257 | ... water_line=15, 258 | ... layers=[ 259 | ... Layer( 260 | ... name="medium dense sand", 261 | ... top=0, 262 | ... bottom=-20, 263 | ... weight=18, 264 | ... lateral_model=API_sand(phi=33, kind="cyclic"), 265 | ... ), 266 | ... Layer( 267 | ... name="firm clay", 268 | ... top=-20, 269 | ... bottom=-40, 270 | ... weight=18, 271 | ... lateral_model=API_clay(Su=[50, 70], eps50=0.015, kind="cyclic"), 272 | ... ), 273 | ... ], 274 | ... ) 275 | >>> 276 | >>> # Create Model 277 | >>> M = Model(name="", pile=p, soil=sp) 278 | >>> 279 | >>> # Apply bottom fixity along z-axis 280 | >>> M.set_support(elevation=-40, Tz=True) 281 | >>> # Apply axial and lateral loads 282 | >>> M.set_pointload(elevation=0, Pz=-20e3, Py=5e3) 283 | >>> 284 | >>> # Run analysis 285 | >>> result = M.solve() 286 | Converged at iteration no. 2 287 | >>> 288 | >>> # plot the results 289 | >>> result.plot() 290 | 291 | .. _Ex6-check_model: 292 | 293 | Example 6 - Visualize a model 294 | ============================= 295 | 296 | If one would like to check the input of the model, a quick visual on this 297 | can be provided by plotting the model with the method: :meth:`openpile.construct.Model.plot`. 298 | 299 | .. plot:: 300 | :context: close-figs 301 | 302 | >>> # Create Model 303 | >>> M = Model(name="", pile=p, soil=sp) 304 | >>> # Apply bottom fixity along z-axis 305 | >>> M.set_support(elevation=-40, Tz=True) 306 | >>> # Apply axial and lateral loads 307 | >>> M.set_pointload(elevation=0, Pz=-20e3, Py=5e3) 308 | >>> # Plot the Model 309 | >>> M.plot() 310 | 311 | 312 | .. _Ex7-simple_beam_calculation: 313 | 314 | Example 7 - Run a simple beam calculation 315 | ========================================= 316 | 317 | .. plot:: 318 | 319 | #imports 320 | from openpile.construct import Pile, Model 321 | #create a tubular pile 322 | p = Pile.create_tubular(name="Simple tubular pile", top_elevation=10, bottom_elevation=0, diameter=0.1, wt=0.01) 323 | # create a model with this pile we just created 324 | m = Model(name="Beam calculation", pile=p, coarseness=0.2) 325 | # create boundary conditions 326 | m.set_support(10, Ty=True ) 327 | m.set_support(0, Tz=True, Ty=True) 328 | m.set_pointload(elevation=5, Py=1) 329 | #run solver and plot result 330 | result = m.solve() 331 | 332 | #closed form solution is max_deflection = PL^3/(48EI) 333 | normalized_deflection = result.deflection['Deflection [m]']*(48*p.E*p.sections[0].second_moment_of_area)/10**3 334 | import matplotlib.pyplot as plt 335 | _, axs = plt.subplots(nrows=1, ncols=2, figsize=(8,5)) 336 | m.plot(ax=axs[0]) 337 | axs[1].plot(normalized_deflection, result.deflection['Elevation [m]'] ) 338 | axs[1].set_xlabel("Normzalized Deflection $\delta_n=\dfrac{\delta \cdot 48 EI}{PL^3}$") 339 | axs[1].set_ylim(axs[0].get_ylim()) 340 | axs[1].set_title('Results against\nclosed-form solution') 341 | axs[1].grid() 342 | 343 | .. _Ex8-beam_calculation: 344 | 345 | Example 8 - A less simple beam calculation 346 | ========================================== 347 | 348 | .. plot:: 349 | 350 | #imports 351 | from openpile.construct import Pile, Model 352 | #create a tubular pile 353 | p = Pile.create_tubular(name="Simple tubular pile", top_elevation=10, bottom_elevation=0, diameter=1, wt=0.1) 354 | print(p) 355 | # create a model with this pile we just created 356 | m = Model(name="Beam calculation", pile=p) 357 | # create boundary conditions with fixed rotation 358 | m.set_support(10, Rx=True,Ty=True, ) 359 | m.set_support(0, Tz=True, Ty=True, Rx=True) 360 | m.set_pointload(elevation=5, Py=1) 361 | m.set_pointload(elevation=10, Pz=-1) 362 | m.plot() 363 | #run solver and plot result 364 | result = m.solve() 365 | result.plot() 366 | 367 | 368 | Example 9 - Calculate pile settlement (axial analysis) 369 | ====================================================== 370 | 371 | .. plot:: 372 | 373 | from openpile.construct import Pile, SoilProfile, Layer, Model 374 | from openpile.soilmodels import API_clay_axial, API_sand_axial, API_clay, API_sand 375 | # Create a 20m deep offshore XL pile with a 15m water column 376 | p = Pile.create_tubular( 377 | name="", top_elevation=0, bottom_elevation=-20, diameter=7.5, wt=0.075 378 | ) 379 | # Create a 20m deep offshore Soil Profile with a 15m water column 380 | sp = SoilProfile( 381 | name="Offshore Soil Profile", 382 | top_elevation=0, 383 | water_line=15, 384 | layers=[ 385 | Layer( 386 | name="medium dense sand", 387 | top=0, 388 | bottom=-20, 389 | weight=18, 390 | axial_model=API_sand_axial(delta=28), 391 | ), 392 | ], 393 | ) 394 | # Create Model 395 | M = Model(name="", pile=p, soil=sp) 396 | # Apply fixity along lateral axis 397 | M.set_support(elevation=-20, Ty=True) 398 | M.set_support(elevation=0, Ty=True) 399 | # Apply axial load 400 | M.set_pointdisplacement(elevation=0, Tz=-1) 401 | # Run analysis 402 | result = M.solve() 403 | result.plot_axial_results() 404 | 405 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ "setuptools >= 61.0.0", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [tool.pytest.ini_options] 6 | addopts = '--doctest-modules --doctest-glob="*.rst" --cov=openpile' 7 | testpaths = [ 8 | "test", "docs/source", 9 | ] 10 | 11 | [tool.black] 12 | line-length = 100 13 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | alabaster==0.7.13 2 | anyio==3.6.2 3 | appnope==0.1.3 4 | argon2-cffi==21.3.0 5 | argon2-cffi-bindings==21.2.0 6 | attrs==22.2.0 7 | autodoc-pydantic==1.9.0 8 | Babel==2.12.1 9 | backcall==0.2.0 10 | beautifulsoup4==4.12.2 11 | black==22.12.0 12 | bleach==6.0.0 13 | cachetools==5.3.0 14 | certifi==2022.12.7 15 | cffi==1.15.1 16 | chardet==5.1.0 17 | charset-normalizer==3.1.0 18 | click==8.1.3 19 | colorama==0.4.6 20 | coverage==7.2.2 21 | cycler==0.11.0 22 | debugpy==1.6.6 23 | decorator==5.1.1 24 | defusedxml==0.7.1 25 | distlib==0.3.6 26 | docutils==0.18.1 27 | entrypoints==0.4 28 | exceptiongroup==1.1.1 29 | fastjsonschema==2.16.3 30 | filelock==3.10.7 31 | fonttools==4.38.0 32 | fpdf2==2.7.1 33 | idna==3.4 34 | imagesize==1.4.1 35 | importlib-metadata==6.1.0 36 | importlib-resources==5.12.0 37 | iniconfig==2.0.0 38 | ipykernel==6.16.2 39 | ipython==7.34.0 40 | ipython-genutils==0.2.0 41 | jedi==0.18.2 42 | Jinja2==3.1.2 43 | jsonschema==4.17.3 44 | jupyter-client==7.4.9 45 | jupyter-core==4.12.0 46 | jupyter-server==1.24.0 47 | jupyterlab-pygments==0.2.2 48 | kiwisolver==1.4.4 49 | llvmlite==0.39.1 50 | MarkupSafe==2.1.2 51 | matplotlib==3.5.3 52 | matplotlib-inline==0.1.6 53 | mistune==2.0.5 54 | mypy-extensions==1.0.0 55 | nbclassic==0.5.5 56 | nbclient==0.7.3 57 | nbconvert==7.3.1 58 | nbformat==5.8.0 59 | nest-asyncio==1.5.6 60 | notebook==6.5.4 61 | notebook-shim==0.2.2 62 | numba==0.56.4 63 | numpy==1.21.6 64 | -e git+https://github.com/TchilDill/openpile.git@e8aac1b7152a3ccbf6955d7df6f435d7f1ea753a#egg=openpile 65 | packaging==23.0 66 | pandas==1.3.5 67 | pandocfilters==1.5.0 68 | parso==0.8.3 69 | pathspec==0.11.1 70 | pexpect==4.8.0 71 | pickleshare==0.7.5 72 | Pillow==9.5.0 73 | pkgutil-resolve-name==1.3.10 74 | platformdirs==3.2.0 75 | pluggy==1.0.0 76 | prometheus-client==0.16.0 77 | prompt-toolkit==3.0.38 78 | psutil==5.9.4 79 | ptyprocess==0.7.0 80 | pycparser==2.21 81 | pydantic==1.10.7 82 | Pygments==2.14.0 83 | pyparsing==3.0.9 84 | pyproject-api==1.5.1 85 | pyrsistent==0.19.3 86 | pytest==7.2.2 87 | pytest-cov==4.0.0 88 | pytest-sphinx==0.5.0 89 | python-dateutil==2.8.2 90 | pytz==2023.3 91 | pyzmq==24.0.1 92 | requests==2.28.2 93 | scipy==1.7.3 94 | Send2Trash==1.8.0 95 | six==1.16.0 96 | sniffio==1.3.0 97 | snowballstemmer==2.2.0 98 | soupsieve==2.4 99 | sphinx==5.3.0 100 | sphinx-copybutton==0.5.2 101 | sphinx-rtd-theme==1.3.0 102 | sphinxcontrib-applehelp==1.0.2 103 | sphinxcontrib-devhelp==1.0.2 104 | sphinxcontrib-htmlhelp==2.0.0 105 | sphinxcontrib-jquery==4.1 106 | sphinxcontrib-jsmath==1.0.1 107 | sphinxcontrib-qthelp==1.0.3 108 | sphinxcontrib-serializinghtml==1.1.5 109 | terminado==0.17.1 110 | tinycss2==1.2.1 111 | tomli==2.0.1 112 | tornado==6.2 113 | tox==4.4.8 114 | traitlets==5.9.0 115 | typed-ast==1.5.4 116 | typing-extensions==4.5.0 117 | urllib3==1.26.15 118 | virtualenv==20.21.0 119 | wcwidth==0.2.6 120 | webencodings==0.5.1 121 | websocket-client==1.5.1 122 | zipp==3.15.0 123 | -------------------------------------------------------------------------------- /resources/Stiffness matrices/Stiffness_matrices.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from sympy import init_printing, Symbol, Matrix, diff\n" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 2, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "init_printing()\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 3, 24 | "metadata": {}, 25 | "outputs": [], 26 | "source": [ 27 | "E = Symbol(\"E\")\n", 28 | "I = Symbol(\"I\")\n", 29 | "L = Symbol(\"L\")\n", 30 | "x = Symbol(\"x\")\n", 31 | "k1 = Symbol(\"k_i\")\n", 32 | "k2 = Symbol(\"k_i+1\")\n", 33 | "\n", 34 | "\n", 35 | "N1 = -6 / L**2 + 12 * x / L**3\n", 36 | "N2 = -4 / L + 6 * x / L**2\n", 37 | "N3 = 6 / L**2 - 12 * x / L**3\n", 38 | "N4 = -2 / L + 6 * x / L**2\n", 39 | "\n", 40 | "B = Matrix([[N1, N2, (-N2 - N4), N3, N4]])\n", 41 | "\n", 42 | "Bt = B.transpose()\n" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": 4, 48 | "metadata": {}, 49 | "outputs": [ 50 | { 51 | "data": { 52 | "text/latex": [ 53 | "$\\displaystyle \\left[\\begin{matrix}12 & 6 L & - 12 L & -12 & 6 L\\\\6 L & 4 L^{2} & - 6 L^{2} & - 6 L & 2 L^{2}\\\\- 12 L & - 6 L^{2} & 12 L^{2} & 12 L & - 6 L^{2}\\\\-12 & - 6 L & 12 L & 12 & - 6 L\\\\6 L & 2 L^{2} & - 6 L^{2} & - 6 L & 4 L^{2}\\end{matrix}\\right]$" 54 | ], 55 | "text/plain": [ 56 | "⎡ 12 6⋅L -12⋅L -12 6⋅L ⎤\n", 57 | "⎢ ⎥\n", 58 | "⎢ 2 2 2 ⎥\n", 59 | "⎢ 6⋅L 4⋅L -6⋅L -6⋅L 2⋅L ⎥\n", 60 | "⎢ ⎥\n", 61 | "⎢ 2 2 2⎥\n", 62 | "⎢-12⋅L -6⋅L 12⋅L 12⋅L -6⋅L ⎥\n", 63 | "⎢ ⎥\n", 64 | "⎢ -12 -6⋅L 12⋅L 12 -6⋅L ⎥\n", 65 | "⎢ ⎥\n", 66 | "⎢ 2 2 2 ⎥\n", 67 | "⎣ 6⋅L 2⋅L -6⋅L -6⋅L 4⋅L ⎦" 68 | ] 69 | }, 70 | "execution_count": 4, 71 | "metadata": {}, 72 | "output_type": "execute_result" 73 | } 74 | ], 75 | "source": [ 76 | "(Bt * E * I * B).integrate((x, 0, L)) * L**3 / (E * I)\n" 77 | ] 78 | }, 79 | { 80 | "attachments": {}, 81 | "cell_type": "markdown", 82 | "metadata": {}, 83 | "source": [ 84 | "### p-y spring consistent stiffness matrix" 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": 5, 90 | "metadata": {}, 91 | "outputs": [ 92 | { 93 | "data": { 94 | "text/latex": [ 95 | "$\\displaystyle \\left[\\begin{matrix}\\frac{2 L}{7} & \\frac{L^{2}}{28} & - \\frac{2 L^{2}}{105} & \\frac{9 L}{140} & - \\frac{L^{2}}{60}\\\\\\frac{L^{2}}{28} & \\frac{L^{3}}{168} & - \\frac{L^{3}}{420} & \\frac{L^{2}}{70} & - \\frac{L^{3}}{280}\\\\- \\frac{2 L^{2}}{105} & - \\frac{L^{3}}{420} & \\frac{L^{3}}{420} & \\frac{L^{2}}{420} & 0\\\\\\frac{9 L}{140} & \\frac{L^{2}}{70} & \\frac{L^{2}}{420} & \\frac{3 L}{35} & - \\frac{L^{2}}{60}\\\\- \\frac{L^{2}}{60} & - \\frac{L^{3}}{280} & 0 & - \\frac{L^{2}}{60} & \\frac{L^{3}}{280}\\end{matrix}\\right]$" 96 | ], 97 | "text/plain": [ 98 | "⎡ 2 2 2 ⎤\n", 99 | "⎢ 2⋅L L -2⋅L 9⋅L -L ⎥\n", 100 | "⎢ ─── ── ────── ─── ────⎥\n", 101 | "⎢ 7 28 105 140 60 ⎥\n", 102 | "⎢ ⎥\n", 103 | "⎢ 2 3 3 2 3 ⎥\n", 104 | "⎢ L L -L L -L ⎥\n", 105 | "⎢ ── ─── ──── ── ────⎥\n", 106 | "⎢ 28 168 420 70 280 ⎥\n", 107 | "⎢ ⎥\n", 108 | "⎢ 2 3 3 2 ⎥\n", 109 | "⎢-2⋅L -L L L ⎥\n", 110 | "⎢────── ──── ─── ─── 0 ⎥\n", 111 | "⎢ 105 420 420 420 ⎥\n", 112 | "⎢ ⎥\n", 113 | "⎢ 2 2 2 ⎥\n", 114 | "⎢ 9⋅L L L 3⋅L -L ⎥\n", 115 | "⎢ ─── ── ─── ─── ────⎥\n", 116 | "⎢ 140 70 420 35 60 ⎥\n", 117 | "⎢ ⎥\n", 118 | "⎢ 2 3 2 3 ⎥\n", 119 | "⎢ -L -L -L L ⎥\n", 120 | "⎢ ──── ──── 0 ──── ─── ⎥\n", 121 | "⎣ 60 280 60 280 ⎦" 122 | ] 123 | }, 124 | "execution_count": 5, 125 | "metadata": {}, 126 | "output_type": "execute_result" 127 | } 128 | ], 129 | "source": [ 130 | "# upper part of element to be multiplied by atiffnes\n", 131 | "\n", 132 | "N1 = 1 - 3 * x**2 / L**2 + 2 * x**3 / L**3\n", 133 | "N2 = x - 2 * x**2 / L + x**3 / L**2\n", 134 | "N3 = 3 * x**2 / L**2 - 2 * x**3 / L**3\n", 135 | "N4 = -(x**2) / L + x**3 / L**2\n", 136 | "\n", 137 | "B = Matrix([[N1, N2, (-N2 - N4), N3, N4]])\n", 138 | "\n", 139 | "\n", 140 | "Bt = B.transpose()\n", 141 | "\n", 142 | "\n", 143 | "(Bt * ((1 - (x / L))) * B).integrate((x, 0, L))\n" 144 | ] 145 | }, 146 | { 147 | "cell_type": "code", 148 | "execution_count": 6, 149 | "metadata": {}, 150 | "outputs": [ 151 | { 152 | "data": { 153 | "text/latex": [ 154 | "$\\displaystyle \\left[\\begin{matrix}\\frac{3 L}{35} & \\frac{L^{2}}{60} & - \\frac{L^{2}}{420} & \\frac{9 L}{140} & - \\frac{L^{2}}{70}\\\\\\frac{L^{2}}{60} & \\frac{L^{3}}{280} & 0 & \\frac{L^{2}}{60} & - \\frac{L^{3}}{280}\\\\- \\frac{L^{2}}{420} & 0 & \\frac{L^{3}}{420} & \\frac{2 L^{2}}{105} & - \\frac{L^{3}}{420}\\\\\\frac{9 L}{140} & \\frac{L^{2}}{60} & \\frac{2 L^{2}}{105} & \\frac{2 L}{7} & - \\frac{L^{2}}{28}\\\\- \\frac{L^{2}}{70} & - \\frac{L^{3}}{280} & - \\frac{L^{3}}{420} & - \\frac{L^{2}}{28} & \\frac{L^{3}}{168}\\end{matrix}\\right]$" 155 | ], 156 | "text/plain": [ 157 | "⎡ 2 2 2 ⎤\n", 158 | "⎢3⋅L L -L 9⋅L -L ⎥\n", 159 | "⎢─── ── ──── ─── ────⎥\n", 160 | "⎢ 35 60 420 140 70 ⎥\n", 161 | "⎢ ⎥\n", 162 | "⎢ 2 3 2 3 ⎥\n", 163 | "⎢ L L L -L ⎥\n", 164 | "⎢ ── ─── 0 ── ────⎥\n", 165 | "⎢ 60 280 60 280 ⎥\n", 166 | "⎢ ⎥\n", 167 | "⎢ 2 3 2 3 ⎥\n", 168 | "⎢-L L 2⋅L -L ⎥\n", 169 | "⎢──── 0 ─── ──── ────⎥\n", 170 | "⎢420 420 105 420 ⎥\n", 171 | "⎢ ⎥\n", 172 | "⎢ 2 2 2 ⎥\n", 173 | "⎢9⋅L L 2⋅L 2⋅L -L ⎥\n", 174 | "⎢─── ── ──── ─── ────⎥\n", 175 | "⎢140 60 105 7 28 ⎥\n", 176 | "⎢ ⎥\n", 177 | "⎢ 2 3 3 2 3 ⎥\n", 178 | "⎢-L -L -L -L L ⎥\n", 179 | "⎢──── ──── ──── ──── ─── ⎥\n", 180 | "⎣ 70 280 420 28 168 ⎦" 181 | ] 182 | }, 183 | "execution_count": 6, 184 | "metadata": {}, 185 | "output_type": "execute_result" 186 | } 187 | ], 188 | "source": [ 189 | "# lower part of element to be multiplied by atiffnes\n", 190 | "\n", 191 | "N1 = 1 - 3 * x**2 / L**2 + 2 * x**3 / L**3\n", 192 | "N2 = x - 2 * x**2 / L + x**3 / L**2\n", 193 | "N3 = 3 * x**2 / L**2 - 2 * x**3 / L**3\n", 194 | "N4 = -(x**2) / L + x**3 / L**2\n", 195 | "\n", 196 | "B = Matrix([[N1, N2, (-N2 - N4), N3, N4]])\n", 197 | "\n", 198 | "Bt = B.transpose()\n", 199 | "\n", 200 | "(Bt * ((x / L)) * B).integrate((x, 0, L))\n" 201 | ] 202 | }, 203 | { 204 | "cell_type": "code", 205 | "execution_count": 7, 206 | "metadata": {}, 207 | "outputs": [ 208 | { 209 | "data": { 210 | "text/latex": [ 211 | "$\\displaystyle \\left[\\begin{matrix}\\frac{13 L}{35} & \\frac{11 L^{2}}{210} & \\frac{9 L}{70} & - \\frac{13 L^{2}}{420}\\\\\\frac{11 L^{2}}{210} & \\frac{L^{3}}{105} & \\frac{13 L^{2}}{420} & - \\frac{L^{3}}{140}\\\\\\frac{9 L}{70} & \\frac{13 L^{2}}{420} & \\frac{13 L}{35} & - \\frac{11 L^{2}}{210}\\\\- \\frac{13 L^{2}}{420} & - \\frac{L^{3}}{140} & - \\frac{11 L^{2}}{210} & \\frac{L^{3}}{105}\\end{matrix}\\right]$" 212 | ], 213 | "text/plain": [ 214 | "⎡ 2 2 ⎤\n", 215 | "⎢ 13⋅L 11⋅L 9⋅L -13⋅L ⎥\n", 216 | "⎢ ──── ───── ─── ───────⎥\n", 217 | "⎢ 35 210 70 420 ⎥\n", 218 | "⎢ ⎥\n", 219 | "⎢ 2 3 2 3 ⎥\n", 220 | "⎢ 11⋅L L 13⋅L -L ⎥\n", 221 | "⎢ ───── ─── ───── ──── ⎥\n", 222 | "⎢ 210 105 420 140 ⎥\n", 223 | "⎢ ⎥\n", 224 | "⎢ 2 2 ⎥\n", 225 | "⎢ 9⋅L 13⋅L 13⋅L -11⋅L ⎥\n", 226 | "⎢ ─── ───── ──── ───────⎥\n", 227 | "⎢ 70 420 35 210 ⎥\n", 228 | "⎢ ⎥\n", 229 | "⎢ 2 3 2 3 ⎥\n", 230 | "⎢-13⋅L -L -11⋅L L ⎥\n", 231 | "⎢─────── ──── ─────── ─── ⎥\n", 232 | "⎣ 420 140 210 105 ⎦" 233 | ] 234 | }, 235 | "execution_count": 7, 236 | "metadata": {}, 237 | "output_type": "execute_result" 238 | } 239 | ], 240 | "source": [ 241 | "N1 = 1 - 3 * x**2 / L**2 + 2 * x**3 / L**3\n", 242 | "N2 = x - 2 * x**2 / L + x**3 / L**2\n", 243 | "N3 = 3 * x**2 / L**2 - 2 * x**3 / L**3\n", 244 | "N4 = -(x**2) / L + x**3 / L**2\n", 245 | "\n", 246 | "B = Matrix([[N1, N2, N3, N4]])\n", 247 | "\n", 248 | "Bt = B.transpose()\n", 249 | "\n", 250 | "M1 = (Bt * B).integrate((x, 0, L))\n", 251 | "M1\n" 252 | ] 253 | }, 254 | { 255 | "attachments": {}, 256 | "cell_type": "markdown", 257 | "metadata": {}, 258 | "source": [ 259 | "### Geometric stiffness matrix" 260 | ] 261 | }, 262 | { 263 | "cell_type": "code", 264 | "execution_count": 30, 265 | "metadata": {}, 266 | "outputs": [ 267 | { 268 | "data": { 269 | "text/latex": [ 270 | "$\\displaystyle \\left[\\begin{matrix}\\frac{3}{5 L} & \\frac{1}{10} & - \\frac{3}{5 L} & 0\\\\\\frac{1}{10} & \\frac{L}{30} & - \\frac{1}{10} & - \\frac{L}{60}\\\\- \\frac{3}{5 L} & - \\frac{1}{10} & \\frac{3}{5 L} & 0\\\\0 & - \\frac{L}{60} & 0 & \\frac{L}{10}\\end{matrix}\\right]$" 271 | ], 272 | "text/plain": [ 273 | "⎡ 3 -3 ⎤\n", 274 | "⎢─── 1/10 ─── 0 ⎥\n", 275 | "⎢5⋅L 5⋅L ⎥\n", 276 | "⎢ ⎥\n", 277 | "⎢ L -L ⎥\n", 278 | "⎢1/10 ── -1/10 ───⎥\n", 279 | "⎢ 30 60⎥\n", 280 | "⎢ ⎥\n", 281 | "⎢-3 3 ⎥\n", 282 | "⎢─── -1/10 ─── 0 ⎥\n", 283 | "⎢5⋅L 5⋅L ⎥\n", 284 | "⎢ ⎥\n", 285 | "⎢ -L L ⎥\n", 286 | "⎢ 0 ─── 0 ── ⎥\n", 287 | "⎣ 60 10 ⎦" 288 | ] 289 | }, 290 | "execution_count": 30, 291 | "metadata": {}, 292 | "output_type": "execute_result" 293 | } 294 | ], 295 | "source": [ 296 | "N1 = 1 - 3 * x**2 / L**2 + 2 * x**3 / L**3\n", 297 | "N2 = x - 2 * x**2 / L + x**3 / L**2\n", 298 | "N3 = 3 * x**2 / L**2 - 2 * x**3 / L**3\n", 299 | "N4 = -(x**2) / L + x**3 / L**2\n", 300 | "\n", 301 | "B = Matrix([[diff(N1, x), diff(N2, x), diff(N3, x), diff(N4, x)]])\n", 302 | "\n", 303 | "Bt = B.transpose()\n", 304 | "\n", 305 | "M1 = (Bt * B).integrate((x, 0, L))\n", 306 | "M2 = (Bt * (1 - x / L) * B).integrate((x, 0, L))\n", 307 | "M3 = (Bt * (x / L) * B).integrate((x, 0, L))\n", 308 | "\n", 309 | "M3\n" 310 | ] 311 | }, 312 | { 313 | "cell_type": "code", 314 | "execution_count": 9, 315 | "metadata": {}, 316 | "outputs": [ 317 | { 318 | "data": { 319 | "text/latex": [ 320 | "$\\displaystyle \\left[\\begin{matrix}- \\frac{6 x}{L^{2}} + \\frac{6 x^{2}}{L^{3}}\\\\1 - \\frac{4 x}{L} + \\frac{3 x^{2}}{L^{2}}\\\\\\frac{6 x}{L^{2}} - \\frac{6 x^{2}}{L^{3}}\\\\- \\frac{2 x}{L} + \\frac{3 x^{2}}{L^{2}}\\end{matrix}\\right]$" 321 | ], 322 | "text/plain": [ 323 | "⎡ 2 ⎤\n", 324 | "⎢ 6⋅x 6⋅x ⎥\n", 325 | "⎢ - ─── + ──── ⎥\n", 326 | "⎢ 2 3 ⎥\n", 327 | "⎢ L L ⎥\n", 328 | "⎢ ⎥\n", 329 | "⎢ 2⎥\n", 330 | "⎢ 4⋅x 3⋅x ⎥\n", 331 | "⎢1 - ─── + ────⎥\n", 332 | "⎢ L 2 ⎥\n", 333 | "⎢ L ⎥\n", 334 | "⎢ ⎥\n", 335 | "⎢ 2 ⎥\n", 336 | "⎢ 6⋅x 6⋅x ⎥\n", 337 | "⎢ ─── - ──── ⎥\n", 338 | "⎢ 2 3 ⎥\n", 339 | "⎢ L L ⎥\n", 340 | "⎢ ⎥\n", 341 | "⎢ 2 ⎥\n", 342 | "⎢ 2⋅x 3⋅x ⎥\n", 343 | "⎢ - ─── + ──── ⎥\n", 344 | "⎢ L 2 ⎥\n", 345 | "⎣ L ⎦" 346 | ] 347 | }, 348 | "execution_count": 9, 349 | "metadata": {}, 350 | "output_type": "execute_result" 351 | } 352 | ], 353 | "source": [ 354 | "Bt\n" 355 | ] 356 | }, 357 | { 358 | "attachments": {}, 359 | "cell_type": "markdown", 360 | "metadata": {}, 361 | "source": [ 362 | "T-z consistent stiffness matrix" 363 | ] 364 | }, 365 | { 366 | "cell_type": "code", 367 | "execution_count": 10, 368 | "metadata": {}, 369 | "outputs": [ 370 | { 371 | "data": { 372 | "text/latex": [ 373 | "$\\displaystyle \\left[\\begin{matrix}\\frac{L}{4} & \\frac{L}{12}\\\\\\frac{L}{12} & \\frac{L}{12}\\end{matrix}\\right]$" 374 | ], 375 | "text/plain": [ 376 | "⎡L L ⎤\n", 377 | "⎢─ ──⎥\n", 378 | "⎢4 12⎥\n", 379 | "⎢ ⎥\n", 380 | "⎢L L ⎥\n", 381 | "⎢── ──⎥\n", 382 | "⎣12 12⎦" 383 | ] 384 | }, 385 | "execution_count": 10, 386 | "metadata": {}, 387 | "output_type": "execute_result" 388 | } 389 | ], 390 | "source": [ 391 | "# upper part of element to be multiplied by atiffnes\n", 392 | "\n", 393 | "N1 = 1 - (x / L)\n", 394 | "N2 = x / L\n", 395 | "\n", 396 | "B = Matrix([[N1, N2]])\n", 397 | "\n", 398 | "\n", 399 | "Bt = B.transpose()\n", 400 | "\n", 401 | "\n", 402 | "(Bt * ((1 - (x / L))) * B).integrate((x, 0, L))\n" 403 | ] 404 | }, 405 | { 406 | "cell_type": "markdown", 407 | "metadata": {}, 408 | "source": [] 409 | }, 410 | { 411 | "cell_type": "code", 412 | "execution_count": 11, 413 | "metadata": {}, 414 | "outputs": [ 415 | { 416 | "data": { 417 | "text/latex": [ 418 | "$\\displaystyle \\left[\\begin{matrix}\\frac{L}{12} & \\frac{L}{12}\\\\\\frac{L}{12} & \\frac{L}{4}\\end{matrix}\\right]$" 419 | ], 420 | "text/plain": [ 421 | "⎡L L ⎤\n", 422 | "⎢── ──⎥\n", 423 | "⎢12 12⎥\n", 424 | "⎢ ⎥\n", 425 | "⎢L L ⎥\n", 426 | "⎢── ─ ⎥\n", 427 | "⎣12 4 ⎦" 428 | ] 429 | }, 430 | "execution_count": 11, 431 | "metadata": {}, 432 | "output_type": "execute_result" 433 | } 434 | ], 435 | "source": [ 436 | "# upper part of element to be multiplied by atiffnes\n", 437 | "\n", 438 | "N1 = 1 - (x / L)\n", 439 | "N2 = x / L\n", 440 | "\n", 441 | "B = Matrix([[N1, N2]])\n", 442 | "\n", 443 | "\n", 444 | "Bt = B.transpose()\n", 445 | "\n", 446 | "\n", 447 | "(Bt * (((x / L))) * B).integrate((x, 0, L))\n" 448 | ] 449 | }, 450 | { 451 | "cell_type": "code", 452 | "execution_count": null, 453 | "metadata": {}, 454 | "outputs": [], 455 | "source": [] 456 | } 457 | ], 458 | "metadata": { 459 | "kernelspec": { 460 | "display_name": "Python 3 (ipykernel)", 461 | "language": "python", 462 | "name": "python3" 463 | }, 464 | "language_info": { 465 | "codemirror_mode": { 466 | "name": "ipython", 467 | "version": 3 468 | }, 469 | "file_extension": ".py", 470 | "mimetype": "text/x-python", 471 | "name": "python", 472 | "nbconvert_exporter": "python", 473 | "pygments_lexer": "ipython3", 474 | "version": "3.7.8" 475 | } 476 | }, 477 | "nbformat": 4, 478 | "nbformat_minor": 2 479 | } 480 | -------------------------------------------------------------------------------- /resources/Stiffness matrices/Stiffness_matrices2.ipynb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TchilDill/openpile/fe16b26d01249b37edf9a8f16c72e01081ae8171/resources/Stiffness matrices/Stiffness_matrices2.ipynb -------------------------------------------------------------------------------- /samples/Burd_et_al_G0_profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TchilDill/openpile/fe16b26d01249b37edf9a8f16c72e01081ae8171/samples/Burd_et_al_G0_profile.png -------------------------------------------------------------------------------- /samples/Cowden_Clay_D1_and_D2_piles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TchilDill/openpile/fe16b26d01249b37edf9a8f16c72e01081ae8171/samples/Cowden_Clay_D1_and_D2_piles.png -------------------------------------------------------------------------------- /samples/GDSM_pile_D2t_Burd_et_al_2020.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TchilDill/openpile/fe16b26d01249b37edf9a8f16c72e01081ae8171/samples/GDSM_pile_D2t_Burd_et_al_2020.png -------------------------------------------------------------------------------- /samples/GDSM_pile_D2t_Burd_et_al_2020_M.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TchilDill/openpile/fe16b26d01249b37edf9a8f16c72e01081ae8171/samples/GDSM_pile_D2t_Burd_et_al_2020_M.png -------------------------------------------------------------------------------- /samples/api_sand_kallehave.ipynb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TchilDill/openpile/fe16b26d01249b37edf9a8f16c72e01081ae8171/samples/api_sand_kallehave.ipynb -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = openpile 3 | author = Guillaume Melin 4 | author_email = guillaume.melin@icloud.com 5 | license = GPLv3 6 | license_files = LICENSE.txt 7 | description = Library for geotechnical pile calculations. 8 | url = https://github.com/TchilDill/openpile 9 | keywords = PILE, Geotechnics, monopile, Geotechnical, calculations, PISA, winkler 10 | classifiers = 11 | License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+) 12 | License :: OSI Approved :: GNU General Public License v3 (GPLv3) 13 | Programming Language :: Python 14 | Programming Language :: Python :: 3.7 15 | Programming Language :: Python :: 3.8 16 | Programming Language :: Python :: 3.9 17 | Programming Language :: Python :: 3.10 18 | Operating System :: OS Independent 19 | [options] 20 | zip_safe = False 21 | include_package_data = True 22 | package_dir = 23 | = src 24 | packages = find: 25 | python_requires = >=3.7 26 | install_requires = 27 | pandas 28 | numpy < 2.0 29 | matplotlib 30 | numba 31 | scipy 32 | pydantic >= 2.0 33 | typing_extensions 34 | [options.extras_require] 35 | docs = 36 | Sphinx==5.3.0 37 | sphinx-rtd-theme==1.3.0 38 | sphinxcontrib-applehelp==1.0.2 39 | sphinxcontrib-devhelp==1.0.2 40 | sphinxcontrib-htmlhelp==2.0.0 41 | sphinxcontrib-jsmath==1.0.1 42 | sphinxcontrib-qthelp==1.0.3 43 | sphinxcontrib-serializinghtml==1.1.5 44 | sphinx-copybutton==0.5.2 45 | autodoc-pydantic==2.0.1 46 | pytest 47 | pytest-cov 48 | black==22.12.0 49 | tox==4.4.8 50 | 51 | [options.packages.find] 52 | where=src 53 | 54 | [build_sphinx] 55 | builder = html,latexpdf 56 | all-files = 1 57 | build-dir = docs/build 58 | source-dir = docs/source 59 | 60 | [tool:pytest] 61 | addopts == --doctest-modules -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | from pathlib import Path 3 | import re 4 | 5 | if __name__ == "__main__": 6 | version = re.findall( 7 | r'__version__ = "(\d+.\d+.\d+[^"]*)"', 8 | Path("src/openpile/__init__.py").read_text(encoding="utf-8"), 9 | )[0] 10 | 11 | setup( 12 | version=version, 13 | long_description=Path("README.md").read_text(encoding="utf-8"), 14 | long_description_content_type="text/markdown", 15 | project_urls={ 16 | "Documentation": "https://openpile.readthedocs.io/en/latest/", 17 | "Code": "https://github.com/TchilDill/openpile", 18 | "Issue tracker": "https://github.com/TchilDill/openpile/issues", 19 | }, 20 | ) 21 | -------------------------------------------------------------------------------- /src/openpile/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.0.1" 2 | -------------------------------------------------------------------------------- /src/openpile/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TchilDill/openpile/fe16b26d01249b37edf9a8f16c72e01081ae8171/src/openpile/core/__init__.py -------------------------------------------------------------------------------- /src/openpile/core/_model_build.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | 4 | 5 | def validate_bc(bc_list, bc_cls): 6 | """ 7 | helper function to validate boundary condition 8 | """ 9 | err = False 10 | flag = [] 11 | # check if bcs overlap 12 | bc_checklist = set([(bc.elevation, bc.x) for bc in bc_list if isinstance(bc, bc_cls)]) 13 | if len(bc_checklist) > 1: 14 | if len(set([bc[0] for bc in bc_checklist])) != len([bc[0] for bc in bc_checklist]): 15 | flag.append("x") 16 | err = True 17 | bc_checklist = set([(bc.elevation, bc.y) for bc in bc_list if isinstance(bc, bc_cls)]) 18 | if len(bc_checklist) > 1: 19 | if len(set([bc[0] for bc in bc_checklist])) != len([bc[0] for bc in bc_checklist]): 20 | flag.append("y") 21 | err = True 22 | bc_checklist = set([(bc.elevation, bc.z) for bc in bc_list if isinstance(bc, bc_cls)]) 23 | if len(bc_checklist) > 1: 24 | if len(set([bc[0] for bc in bc_checklist])) != len([bc[0] for bc in bc_checklist]): 25 | flag.append("z") 26 | err = True 27 | if err: 28 | raise ValueError( 29 | f"Multiple boundary conditions ({bc_cls.__name__}) are given along {', and '.join(flag)}. axis and at same elevation." 30 | ) 31 | 32 | 33 | def apply_bc(nodes_elevations, zglobal, yglobal, xglobal, bc_list, bc_cls, output_text): 34 | """ 35 | helper function to apply boundary condition 36 | """ 37 | # apply boundary condition 38 | for bc in bc_list: 39 | if isinstance(bc, bc_cls): 40 | check = np.isclose( 41 | nodes_elevations, np.tile(bc.elevation, nodes_elevations.shape), atol=0.001 42 | ) 43 | if any(check): 44 | # one node correspond, extract node 45 | node_idx = next((i for i, x in enumerate(check) if x == True), None) 46 | # apply loads at this node 47 | if bc.x: 48 | xglobal[node_idx] = bc.x 49 | if bc.y: 50 | yglobal[node_idx] = bc.y 51 | if bc.z: 52 | zglobal[node_idx] = bc.z 53 | else: 54 | if bc.elevation > nodes_elevations[0] or bc.elevation < nodes_elevations[-1]: 55 | print( 56 | f"{output_text} not applied! The chosen elevation is outside the mesh. The {output_text} must be applied on the structure." 57 | ) 58 | else: 59 | print( 60 | f"{output_text} not applied! The chosen elevation is not meshed as a node. Please include elevation in `x2mesh` variable when creating the Model." 61 | ) 62 | return zglobal, yglobal, xglobal 63 | 64 | 65 | def parameter2elements(objects: list, key: callable, elem_top: list, elem_bottom: list): 66 | """converts a list of pile sections into a list of elements 67 | 68 | Objects must be either a list of pile sections (openpile.construct.PileSection) or a list of soil layers (openpile.construct.Layer) 69 | 70 | """ 71 | 72 | elem_top = np.array(elem_top) 73 | elem_bottom = np.array(elem_bottom) 74 | # create a NaN array with same array as elem_z_top 75 | elem_param = np.full(elem_top.size, np.nan) 76 | 77 | for obj in objects: 78 | top_limit, bottom_limit = obj.top, obj.bottom 79 | idx = np.where((elem_top <= top_limit) & (elem_bottom >= bottom_limit))[0] 80 | elem_param[idx] = key(obj) 81 | 82 | return elem_param 83 | 84 | 85 | def get_tip_sig_v_eff( 86 | tip_elevation: float, 87 | water_elevation: float, 88 | layers: list, # List[openpile.construct.Layer] 89 | sig_v_mudline: float = 0, 90 | water_unit_weight: float = 10.0, 91 | ): 92 | """Calculates the effective vertical stress at the pile tip""" 93 | sig_v_tip = sig_v_mudline 94 | water_unit_weight = 10.0 95 | 96 | for layer in sorted(layers, key=lambda x: -x.top): 97 | buoyant_weight = layer.weight - water_unit_weight 98 | if tip_elevation <= layer.bottom: 99 | if water_elevation <= layer.bottom: 100 | w = layer.weight 101 | elif water_elevation < layer.top: 102 | w = ( 103 | layer.weight * (layer.top - water_elevation) 104 | + buoyant_weight * (water_elevation - layer.bottom) 105 | ) / (layer.top - layer.bottom) 106 | else: 107 | w = buoyant_weight 108 | 109 | sig_v_tip += w * (layer.top - layer.bottom) 110 | 111 | elif tip_elevation < layer.top and water_elevation > layer.bottom: 112 | if water_elevation <= tip_elevation: 113 | w = layer.weight 114 | elif water_elevation < layer.top: 115 | w = ( 116 | layer.weight * (layer.top - water_elevation) 117 | + buoyant_weight * (water_elevation - tip_elevation) 118 | ) / (layer.top - tip_elevation) 119 | else: 120 | w = buoyant_weight 121 | 122 | sig_v_tip += w * (layer.top - tip_elevation) 123 | 124 | return sig_v_tip 125 | 126 | 127 | def check_springs(arr): 128 | check_nan = np.isnan(arr).any() 129 | check_negative = (arr < 0).any() 130 | 131 | return check_nan or check_negative 132 | 133 | 134 | def get_coordinates(pile, soil, x2mesh, coarseness) -> pd.DataFrame: 135 | # Primary discretisation over z-axis 136 | z = np.array([], dtype=np.float16) 137 | # add get pile relevant sections 138 | z = np.append(z, pile.data["Elevation [m]"].values) 139 | # add soil relevant layers and others 140 | if soil is not None: 141 | soil_elevations = np.array( 142 | [x.top for x in soil.layers] + [x.bottom for x in soil.layers], 143 | dtype=float, 144 | ) 145 | if any(soil_elevations < pile.bottom_elevation): 146 | soil_elevations = np.append(pile.bottom_elevation, soil_elevations) 147 | soil_elevations = soil_elevations[soil_elevations >= pile.bottom_elevation] 148 | z = np.append(z, soil_elevations) 149 | # add user-defined elevation 150 | if x2mesh is None: 151 | x2mesh = [] 152 | z = np.append(z, x2mesh) 153 | 154 | # get unique values and sort in reverse order 155 | z = np.unique(z)[::-1] 156 | 157 | # Secondary discretisation over z-axis depending on coarseness factor 158 | z_secondary = np.array([], dtype=np.float16) 159 | for i in range(len(z) - 1): 160 | spacing = z[i] - z[i + 1] 161 | new_spacing = spacing 162 | divider = 1 163 | while new_spacing > coarseness: 164 | divider += 1 165 | new_spacing = spacing / divider 166 | new_z = z[i] - (np.arange(start=1, stop=divider) * np.tile(new_spacing, (divider - 1))) 167 | z_secondary = np.append(z_secondary, new_z) 168 | 169 | # assemble x- coordinates 170 | z = np.append(z, z_secondary) 171 | z = np.unique(z)[::-1] 172 | 173 | # dummy y- coordinates 174 | y = np.zeros(shape=z.shape) 175 | 176 | # create dataframe coordinates 177 | nodes = pd.DataFrame( 178 | data={ 179 | "z [m]": z, 180 | "y [m]": y, 181 | }, 182 | dtype=float, 183 | ).round(4) 184 | nodes.index.name = "Node no." 185 | 186 | element = pd.DataFrame( 187 | data={ 188 | "z_top [m]": z[:-1], 189 | "z_bottom [m]": z[1:], 190 | "y_top [m]": y[:-1], 191 | "y_bottom [m]": y[1:], 192 | }, 193 | dtype=float, 194 | ).round(4) 195 | element.index.name = "Element no." 196 | 197 | return nodes, element 198 | 199 | 200 | def get_soil_profile(soil) -> pd.DataFrame: 201 | top_elevations = [x.top for x in soil.layers] 202 | bottom_elevations = [x.bottom for x in soil.layers] 203 | soil_weights = [x.weight for x in soil.layers] 204 | 205 | idx_sort = np.argsort(top_elevations)[::-1] 206 | 207 | top_elevations = [top_elevations[i] for i in idx_sort] 208 | soil_weights = [soil_weights[i] for i in idx_sort] 209 | bottom_elevations = [bottom_elevations[i] for i in idx_sort] 210 | 211 | # elevation in model w.r.t to x axis 212 | x = top_elevations 213 | 214 | return pd.DataFrame( 215 | data={"Top soil layer [m]": x, "Unit Weight [kN/m3]": soil_weights}, 216 | dtype=np.float64, 217 | ) 218 | 219 | 220 | def get_soil_properties(pile, soil, x2mesh, coarseness): 221 | # dummy allocation 222 | soil_properties = None 223 | # create soil properties 224 | if soil is not None: 225 | element_coordinates = get_coordinates(pile, soil, x2mesh, coarseness)[1] 226 | soil_properties = pd.merge_asof( 227 | left=element_coordinates[["z_top [m]", "z_bottom [m]"]].sort_values(by=["z_top [m]"]), 228 | right=get_soil_profile(soil).sort_values(by=["Top soil layer [m]"]), 229 | left_on="z_top [m]", 230 | right_on="Top soil layer [m]", 231 | direction="forward", 232 | ).sort_values(by=["z_top [m]"], ascending=False) 233 | # add elevation of element w.r.t. ground level 234 | soil_properties["zg_top [m]"] = soil_properties["z_top [m]"] - soil.top_elevation 235 | soil_properties["zg_bottom [m]"] = soil_properties["z_bottom [m]"] - soil.top_elevation 236 | # add vertical stress at top and bottom of each element 237 | condition_below_water_table = soil_properties["z_top [m]"] <= soil.water_line 238 | soil_properties.loc[condition_below_water_table, "Unit Weight [kN/m3]"] = ( 239 | soil_properties["Unit Weight [kN/m3]"][condition_below_water_table] - 10.0 240 | ) 241 | s = (soil_properties["z_top [m]"] - soil_properties["z_bottom [m]"]) * soil_properties[ 242 | "Unit Weight [kN/m3]" 243 | ] 244 | soil_properties["sigma_v top [kPa]"] = np.insert( 245 | s.cumsum().values[:-1], 246 | np.where(soil_properties["z_top [m]"].values == soil.top_elevation)[0], 247 | 0.0, 248 | ) 249 | soil_properties["sigma_v bottom [kPa]"] = s.cumsum() 250 | # reset index 251 | soil_properties.reset_index(inplace=True, drop=True) 252 | 253 | return soil_properties 254 | -------------------------------------------------------------------------------- /src/openpile/core/misc.py: -------------------------------------------------------------------------------- 1 | # misc functions 2 | 3 | 4 | import random 5 | import numpy as np 6 | import pandas as pd 7 | import math as m 8 | 9 | from numba import njit 10 | import matplotlib.colors as mcolors 11 | 12 | 13 | def from_list2x_parse_top_bottom(var): 14 | """provide top and bottom values of layer based on float or list inputs""" 15 | if isinstance(var, float) or isinstance(var, int): 16 | top = var 17 | bottom = var 18 | elif isinstance(var, list) and len(var) == 1: 19 | top = var[0] 20 | bottom = var[0] 21 | elif isinstance(var, list) and len(var) == 2: 22 | top = var[0] 23 | bottom = var[1] 24 | else: 25 | print("Soil Layer variable is not a float nor a list") 26 | raise TypeError 27 | 28 | return top, bottom 29 | 30 | 31 | def get_value_at_current_depth(X, depth_from_top_of_layer, layer_height, depth_from_ground): 32 | if isinstance(X, type(lambda x: 2)): 33 | return X(depth_from_ground) 34 | else: 35 | xtop, xbot = from_list2x_parse_top_bottom(X) 36 | return xtop + (xbot - xtop) * depth_from_top_of_layer / layer_height 37 | 38 | 39 | def var_to_str(var): 40 | if isinstance(var, float) or isinstance(var, int): 41 | var_print = var 42 | elif isinstance(var, list): 43 | var_print = "-".join(str(v) for v in var) 44 | else: 45 | raise ValueError("not a float nor list") 46 | return var_print 47 | 48 | 49 | def generate_color_string(kind=None): 50 | if kind is None: 51 | colors = list(mcolors.CSS4_COLORS.values()) 52 | elif kind == "earth": 53 | colors = [ 54 | "#B4A390", 55 | "#927E75", 56 | "#796363", 57 | "#99A885", 58 | "#D4DBBA", 59 | "#FDEDCF", 60 | "#EEEAC3", 61 | "#F5D498", 62 | "#ECB992", 63 | "#DDA175", 64 | "#AB7B5E", 65 | "#8F6854", 66 | ] 67 | return colors[random.randint(0, len(colors) - 1)] 68 | 69 | 70 | def repeat_inner(arr): 71 | arr = arr.reshape(-1, 1) 72 | 73 | arr_inner = arr[1:-1] 74 | arr_inner = np.tile(arr_inner, (2)).reshape(-1) 75 | 76 | return np.hstack([arr[0], arr_inner, arr[-1]]) 77 | 78 | 79 | def get_distributed_soil_springs( 80 | springs: np.ndarray, elevations: np.ndarray, kind: str 81 | ) -> pd.DataFrame: 82 | """ 83 | Returns soil springs created for the given model in one DataFrame. 84 | 85 | Unit of the springs are [kN/m]/[kNm/m] for resistance and [m]/[rad] for displacement/rotation. 86 | 87 | Parameters 88 | ---------- 89 | springs : ndarray dim[nelem,2,2,spring_dim] 90 | Springs at top and bottom of element 91 | elevations : ndarray 92 | self.nodes_coordinates["z [m]"].values 93 | kind : str 94 | type of spring to extract. one of ["p-y", "m-t", "Hb-y", "Mb-t", "t-z"] 95 | 96 | Returns 97 | ------- 98 | pd.DataFrame 99 | Soil springs 100 | """ 101 | 102 | # get rid of the dimension dependent on the p-value, we keep max m-t spring 103 | if kind == "m-t": 104 | springs = springs.max(axis=3) 105 | 106 | spring_dim = springs.shape[-1] 107 | nelem = springs.shape[0] 108 | nnode = len(elevations) 109 | 110 | column_values_spring = [f"VAL {i}" for i in range(spring_dim)] 111 | 112 | id = np.repeat(np.arange(nelem + 1), 2) 113 | x = np.repeat(elevations, 2) 114 | 115 | influence = np.abs(np.gradient(elevations)) 116 | influence[0] = influence[0] / 2 117 | influence[-1] = influence[-1] / 2 118 | 119 | springs[:, 0, 0, :] = springs[:, 0, 0, :] * influence[:-1].reshape(-1, 1) 120 | springs[:, 1, 0, :] = springs[:, 1, 0, :] * influence[1:].reshape(-1, 1) 121 | 122 | reduced_springs = np.zeros((nnode * 2, spring_dim)) 123 | 124 | # first spring resistance and disp values 125 | reduced_springs[0, :] = springs[0, 0, 0, :] 126 | reduced_springs[1, :] = springs[0, 0, 1, :] 127 | # last spring resistance and disp values 128 | reduced_springs[-2, :] = springs[-1, 1, 0, :] 129 | reduced_springs[-1, :] = springs[-1, 1, 1, :] 130 | # calculation of weighted springs when node based 131 | j = 0 132 | for i in range(2, nelem * 2 - 1, 2): 133 | j += 1 134 | reduced_springs[i, :] = ( 135 | springs[j - 1, 1, 0, :] * influence[j - 1] + springs[j, 0, 0, :] * influence[j] 136 | ) / (influence[j - 1] + influence[j]) 137 | reduced_springs[i + 1, :] = ( 138 | springs[j - 1, 1, 1, :] * influence[j - 1] + springs[j, 0, 1, :] * influence[j] 139 | ) / (influence[j - 1] + influence[j]) 140 | 141 | df = pd.DataFrame( 142 | data={ 143 | "Node no.": id, 144 | "Elevation [m]": x, 145 | } 146 | ) 147 | 148 | df["type"] = kind.split("-") * len(elevations) 149 | df[column_values_spring] = reduced_springs 150 | 151 | return df 152 | 153 | 154 | def get_lumped_soil_springs(springs: np.ndarray, elevations: np.ndarray, kind: str) -> pd.DataFrame: 155 | """ 156 | Returns soil springs created for the given model in one DataFrame. 157 | 158 | Unit of the springs are [kN]/[kNm] for resistance and [m]/[rad] for displacement/rotation. 159 | 160 | Parameters 161 | ---------- 162 | springs : ndarray dim[nelem,2,2,spring_dim] 163 | Springs at top and bottom of element 164 | elevations : ndarray 165 | self.nodes_coordinates["z [m]"].values 166 | kind : str 167 | type of spring to extract. one of ["p-y", "m-t", "Hb-y", "Mb-t", "t-z"] 168 | 169 | Returns 170 | ------- 171 | pd.DataFrame 172 | Soil springs 173 | """ 174 | 175 | # get rid of the dimension dependent on the p-value, we keep max m-t spring 176 | if kind == "m-t": 177 | springs = springs.max(axis=3) 178 | 179 | spring_dim = springs.shape[-1] 180 | nelem = springs.shape[0] 181 | nnode = len(elevations) 182 | 183 | column_values_spring = [f"VAL {i}" for i in range(spring_dim)] 184 | 185 | id = np.repeat(np.arange(nelem + 1), 2) 186 | x = np.repeat(elevations, 2) 187 | 188 | influence = np.abs(np.gradient(elevations)) 189 | influence[0] = influence[0] / 2 190 | influence[-1] = influence[-1] / 2 191 | 192 | springs[:, 0, 0, :] = springs[:, 0, 0, :] * influence[:-1].reshape(-1, 1) 193 | springs[:, 1, 0, :] = springs[:, 1, 0, :] * influence[1:].reshape(-1, 1) 194 | 195 | reduced_springs = np.zeros((nnode * 2, spring_dim)) 196 | 197 | # first spring resistance and disp values 198 | reduced_springs[0, :] = springs[0, 0, 0, :] 199 | reduced_springs[1, :] = springs[0, 0, 1, :] 200 | # last spring resistance and disp values 201 | reduced_springs[-2, :] = springs[-1, 1, 0, :] 202 | reduced_springs[-1, :] = springs[-1, 1, 1, :] 203 | # calculation of weighted springs when node based 204 | j = 0 205 | for i in range(2, nelem * 2 - 1, 2): 206 | j += 1 207 | reduced_springs[i, :] = ( 208 | springs[j - 1, 1, 0, :] * influence[j - 1] + springs[j, 0, 0, :] * influence[j] 209 | ) 210 | reduced_springs[i + 1, :] = ( 211 | springs[j - 1, 1, 1, :] * influence[j - 1] + springs[j, 0, 1, :] * influence[j] 212 | ) 213 | 214 | df = pd.DataFrame( 215 | data={ 216 | "Node no.": id, 217 | "Elevation [m]": x, 218 | } 219 | ) 220 | 221 | df["type"] = kind.split("-") * len(elevations) 222 | df[column_values_spring] = reduced_springs 223 | 224 | return df 225 | 226 | 227 | @njit(cache=True) 228 | def conic( 229 | x_u: float, 230 | n: float, 231 | k: float, 232 | y_u: float, 233 | output_length: int, 234 | ): 235 | # if k is less than y_u/x_u, k overwritten to equate y_u/x_u 236 | k = max(y_u / x_u, k) 237 | 238 | # Create x vector with 10% extension 239 | x = np.array([0, 0.02, 0.05, 0.1]).astype(np.float32) * x_u 240 | x = np.append(x, np.linspace(0.2 * x_u, x_u, output_length - 5).astype(np.float32)) 241 | x = np.append(x, 1.1 * x_u) 242 | 243 | a = 1 - 2 * n 244 | 245 | y = np.zeros((len(x)), dtype=np.float32) 246 | 247 | for i in range(len(x)): 248 | if abs(x[i] - x_u) < 1e-2: 249 | y[i] = y_u 250 | elif x[i] < x_u: 251 | b = 2 * n * x[i] / x_u - (1 - n) * (1 + x[i] * k / y_u) 252 | c = x[i] * (k / y_u) * (1 - n) - n * (x[i] ** 2 / x_u**2) 253 | 254 | y[i] = y_u * 2 * c / (-b + (b**2 - 4 * a * c) ** 0.5) 255 | else: 256 | y[i] = y_u 257 | 258 | return x, y 259 | -------------------------------------------------------------------------------- /src/openpile/core/txt.py: -------------------------------------------------------------------------------- 1 | from openpile.construct import Pile 2 | 3 | 4 | def txt_pile(obj: Pile) -> str: 5 | """Function that creates several lines of text with the pile data. 6 | 7 | Parameters 8 | ---------- 9 | obj : openpile.construct.Pile 10 | Pile instance 11 | 12 | Returns 13 | ------- 14 | str 15 | output text with pile data 16 | """ 17 | 18 | # Create string to document pile 19 | txt = "{:-^80s}".format("") 20 | txt += "\n{:^80s}".format("Pile Input") 21 | txt += "\n{:-^80s}".format("") 22 | txt += f"\nPile Material: {obj.material.name}" 23 | txt += f"\tPile Type: {obj.type}" 24 | txt += f"\tYoung modulus: {obj._young_modulus/1000:.0f} MPa" 25 | txt += f"\tPoisson ratio = {obj.material.poisson:.2f}" 26 | txt += f"\nMaterial Unit Weight: {obj.material.unitweight:0.1f} kN/m3" 27 | txt += f"\n\nPile sections:\n" 28 | txt += obj.data.to_string(header=True, index=True) 29 | 30 | return txt 31 | -------------------------------------------------------------------------------- /src/openpile/core/validation.py: -------------------------------------------------------------------------------- 1 | """ 2 | The validation module gathers all utilised functions/class/objects serving 3 | the purpose of validation. 4 | """ 5 | 6 | import numpy as np 7 | 8 | 9 | class UserInputError(Exception): 10 | """Custom error for wrong inputs to openpile""" 11 | 12 | 13 | class InvalidBoundaryConditionsError(Exception): 14 | """Custom error for wrong inputs to boundary conditions""" 15 | 16 | 17 | # ---------------------------------------------------------------- 18 | # PILE 19 | # ---------------------------------------------------------------- 20 | 21 | 22 | def param_must_be_type(parameter, parameter_name, parameter_type, type_name): 23 | if not isinstance(parameter, parameter_type): 24 | raise UserInputError(f"Wrong type for {parameter_name}. Please provide a {type_name}") 25 | 26 | 27 | def str_must_be_one_of_those(param: str, param_name: str, accepted_values: list): 28 | param_must_be_type(param, param_name, str, "string") 29 | if param not in accepted_values: 30 | raise UserInputError( 31 | f"Value in {param_name} type must be one of the following: \n - " 32 | + "\n - ".join(accepted_values) 33 | ) 34 | 35 | 36 | def must_be_numbers_in_list(values, values_name): 37 | for value in values: 38 | if not isinstance(value, (float, int)): 39 | raise UserInputError(f"values in {values_name} can only be numbers") 40 | 41 | 42 | def check_boundary_conditions(model): 43 | """ 44 | Check if boundary conditions are satisfactory to solve the system of equations. 45 | 46 | Parameters 47 | ---------- 48 | model: openppile.construct.Model object 49 | object crated from openpile 50 | """ 51 | # rename vars 52 | restrained_dof = model.global_restrained 53 | loaded_dof = model.global_forces 54 | 55 | # count BC in [Translation over z-axis,Translation over y-axis,Rotation around x-axis] 56 | restrained_count_Rx = np.count_nonzero(restrained_dof["Rx"]) 57 | restrained_count_Ty = np.count_nonzero(restrained_dof["Ty"]) 58 | restrained_count_Tz = np.count_nonzero(restrained_dof["Tz"]) 59 | restrained_count_total = restrained_count_Rx + restrained_count_Ty + restrained_count_Tz 60 | 61 | loaded_count_Rx = np.count_nonzero(loaded_dof["Mx [kNm]"]) 62 | loaded_count_Ty = np.count_nonzero(loaded_dof["Py [kN]"]) 63 | loaded_count_Tz = np.count_nonzero(loaded_dof["Pz [kN]"]) 64 | loaded_count_total = loaded_count_Rx + loaded_count_Ty + loaded_count_Tz 65 | 66 | if restrained_count_total == 0 and model.soil is None: 67 | raise InvalidBoundaryConditionsError("No support conditions are provided.") 68 | 69 | if loaded_count_total == 0: 70 | raise InvalidBoundaryConditionsError("No load conditions are provided.") 71 | 72 | if model.soil is None: 73 | # normally loaded beam 74 | if loaded_count_Tz > 0 and restrained_count_Tz > 0: 75 | # support in x axis is given and load over x is given --> correct BC 76 | pass 77 | elif loaded_count_Tz > 0 and restrained_count_Tz == 0: 78 | # support in x axis is given and load over x is given --> correct BC 79 | raise InvalidBoundaryConditionsError( 80 | "Support conditions in normal direction not provided." 81 | ) 82 | 83 | # laterally-loaded beam 84 | if (restrained_count_Ty + restrained_count_Rx) >= 2 and loaded_count_Ty > 0: 85 | pass 86 | # support in y and z axes are given and load over y is given --> correct BC 87 | elif (restrained_count_Ty + restrained_count_Rx) >= 2 and loaded_count_Rx > 0: 88 | pass 89 | # support in y and z axes are given and load over z is given --> correct BC 90 | elif (loaded_count_Rx + loaded_count_Ty) == 0: 91 | # no trasnverse load --> no need to give any error 92 | pass 93 | else: 94 | raise InvalidBoundaryConditionsError("Support conditions against bending not provided.") 95 | else: 96 | raise InvalidBoundaryConditionsError("Soil in mesh not yet possible.") 97 | -------------------------------------------------------------------------------- /src/openpile/materials.py: -------------------------------------------------------------------------------- 1 | """ 2 | `Materials` module 3 | =================== 4 | 5 | This module can be used to create new materials for structure components, e.g. a Pile object. 6 | 7 | Example 8 | ------- 9 | 10 | .. doctest:: 11 | 12 | >>> from openpile.construct import Pile, CircularPileSection 13 | >>> from openpile.materials import PileMaterial 14 | 15 | >>> # Create a Pile 16 | >>> pile = Pile( 17 | ... name = "", 18 | ... material=PileMaterial.custom( 19 | ... name="concrete",unitweight=25, young_modulus=30e6, poisson_ratio=0.15 20 | ... ), 21 | ... sections=[ 22 | ... CircularPileSection( 23 | ... top=0, 24 | ... bottom=-10, 25 | ... diameter=1.0, 26 | ... thickness=0.05 27 | ... ), 28 | ... ] 29 | ... ) 30 | 31 | >>> pile.weight 32 | 37.30641276137878 33 | 34 | 35 | 36 | """ 37 | 38 | 39 | # Import libraries 40 | 41 | from pydantic import ( 42 | BaseModel, 43 | Field, 44 | ) 45 | 46 | from abc import ABC 47 | from pydantic import BaseModel, ConfigDict, Field 48 | 49 | 50 | class AbstractPileMaterial(BaseModel, ABC): 51 | model_config = ConfigDict(extra="forbid") 52 | 53 | 54 | class PileMaterial(AbstractPileMaterial): 55 | """A class to define the material of a pile. 56 | This class is used to define the material properties of a pile, including 57 | unit weight, Young's modulus, and Poisson's ratio. The class also provides 58 | methods to calculate the shear modulus and to create custom materials. 59 | 60 | Parameters 61 | ---------- 62 | name : str 63 | The name of the material. 64 | uw : float 65 | The unit weight of the material in kN/m³. 66 | E : float 67 | The Young's modulus of the material in kN/m². 68 | nu : float 69 | The Poisson's ratio of the material. Must be between -1 and 0.5. 70 | """ 71 | 72 | #: name of the material 73 | name: str = Field(min_length=1, max_length=20) 74 | #: unit weight [kN/m³] 75 | uw: float = Field(gt=0.0) 76 | #: Young's modulus [kN/m²] 77 | E: float = Field(gt=0.0) 78 | #: Poisson's ratio [-] 79 | nu: float = Field(gt=-1.0, le=0.5) 80 | 81 | @property 82 | def unitweight(self): 83 | """The unit weight of the material in kN/m³.""" 84 | return self.uw 85 | 86 | @property 87 | def young_modulus(self): 88 | """The Young's modulus of the material in kN/m².""" 89 | return self.E 90 | 91 | @property 92 | def poisson(self): 93 | """The Poisson's ratio of the material. Must be between -1 and 0.5.""" 94 | return self.nu 95 | 96 | @property 97 | def shear_modulus(self): 98 | """The shear modulus of the material in kN/m². Calculated from Young's modulus and Poisson's ratio.""" 99 | return self.young_modulus / (2 + 2 * self.poisson) 100 | 101 | @classmethod 102 | def custom( 103 | cls, unitweight: float, young_modulus: float, poisson_ratio: float, name: str = "Custom" 104 | ): 105 | """a redundant constructor to create a custom material with the given parameters provided. 106 | 107 | Parameters 108 | ---------- 109 | unitweight : float 110 | The unit weight of the material in kN/m³. 111 | young_modulus : float 112 | The Young's modulus of the material in kN/m². 113 | poisson_ratio : float 114 | The Poisson's ratio of the material. Must be between -1 and 0.5. 115 | name : str, optional 116 | the name of the material, by default "Custom" 117 | 118 | Returns 119 | ------- 120 | openpile.materials.PileMaterial 121 | """ 122 | return cls(name=name, uw=unitweight, E=young_modulus, nu=poisson_ratio) 123 | 124 | 125 | steel = PileMaterial(name="Steel", uw=78.0, E=210e6, nu=0.3) 126 | concrete = PileMaterial(name="Concrete", uw=24.0, E=30e6, nu=0.2) 127 | -------------------------------------------------------------------------------- /src/openpile/utils/Hb_curves.py: -------------------------------------------------------------------------------- 1 | """ 2 | `Hb_curves` module 3 | ------------------ 4 | 5 | """ 6 | 7 | # Import libraries 8 | import math as m 9 | import numpy as np 10 | from numba import njit, prange 11 | from random import random 12 | 13 | from openpile.core.misc import conic 14 | 15 | 16 | @njit(cache=True) 17 | def bothkennar_clay( 18 | X: float, 19 | Su: float, 20 | G0: float, 21 | D: float, 22 | L: float, 23 | output_length: int = 20, 24 | ): 25 | """ 26 | Creates the base shear spring from the PISA clay formulation 27 | published by Burd et al 2020 (see [BABH20]_) and calibrated based on Bothkennar clay 28 | response (a normally consolidated soft clay). 29 | 30 | Parameters 31 | ---------- 32 | X : float 33 | Depth below ground level [unit: m] 34 | Su : float 35 | Undrained shear strength [unit: kPa] 36 | G0 : float 37 | Small-strain shear modulus [unit: kPa] 38 | D : float 39 | Pile diameter [unit: m] 40 | L : float 41 | Embedded pile length [unit: m] 42 | output_length : int, optional 43 | Number of datapoints in the curve, by default 20 44 | 45 | Returns 46 | ------- 47 | 1darray 48 | y vector [unit: m] 49 | 1darray 50 | Hb vector [unit: kN] 51 | 52 | """ 53 | 54 | # Generalised Bothkennar clay Model parameters 55 | v_hu1 = 291.5 56 | v_hu2 = 0.00 57 | k_h1 = 3.008 58 | k_h2 = -0.2701 59 | n_h1 = 0.3113 60 | n_h2 = 0.04263 61 | p_u1 = 0.5279 62 | p_u2 = 0.06864 63 | 64 | # Depth variation parameters 65 | v_max = v_hu1 + v_hu2 * L / D 66 | k = k_h1 + k_h2 * L / D 67 | n = n_h1 + n_h2 * L / D 68 | p_max = p_u1 + p_u2 * L / D 69 | 70 | # calculate normsalised conic function 71 | y, p = conic(v_max, n, k, p_max, output_length) 72 | 73 | # return non-normalised curve 74 | return y * (Su * D / G0), p * (Su * D**2) 75 | 76 | 77 | @njit(cache=True) 78 | def dunkirk_sand( 79 | sig: float, 80 | X: float, 81 | Dr: float, 82 | G0: float, 83 | D: float, 84 | L: float, 85 | output_length: int = 20, 86 | ): 87 | """ 88 | Creates the base shear spring from the PISA sand formulation 89 | published by Burd et al (2020) (see [BTZA20]_). 90 | Also called the General Dunkirk Sand Model (GDSM). 91 | 92 | Parameters 93 | ---------- 94 | sig : float 95 | vertical/overburden effective stress [unit: kPa] 96 | X : float 97 | Depth below ground level [unit: m] 98 | Dr : float 99 | Sand relative density Value must be between 0 and 100 [unit: -] 100 | G0 : float 101 | Small-strain shear modulus [unit: kPa] 102 | D : float 103 | Pile diameter [unit: m] 104 | L : float 105 | Embedded pile length [unit: m] 106 | output_length : int, optional 107 | Number of datapoints in the curve, by default 20 108 | 109 | Returns 110 | ------- 111 | 1darray 112 | y vector [unit: m] 113 | 1darray 114 | Hb vector [unit: kN] 115 | """ 116 | # correct relative density for decimal value 117 | Dr = Dr / 100 118 | 119 | # Generalised Dunkirk Sand Model parameters 120 | v_hu1 = 0.5150 + 2.883 * Dr 121 | v_hu2 = 0.1695 - 0.7018 * Dr 122 | k_h1 = 6.505 - 2.985 * Dr 123 | k_h2 = -0.007969 - 0.4299 * Dr 124 | n_h1 = 0.09978 + 0.7974 * Dr 125 | n_h2 = 0.004994 - 0.07005 * Dr 126 | p_u1 = 0.09952 + 0.7996 * Dr 127 | p_u2 = 0.03988 - 0.1606 * Dr 128 | 129 | # Depth variation parameters 130 | v_max = v_hu1 + v_hu2 * L / D 131 | k = k_h1 + k_h2 * L / D 132 | n = n_h1 + n_h2 * L / D 133 | p_max = p_u1 + p_u2 * L / D 134 | 135 | # calculate normsalised conic function 136 | y, p = conic(v_max, n, k, p_max, output_length) 137 | 138 | # return non-normalised curve 139 | return y * (sig * D / G0), p * (sig * D**2) 140 | 141 | 142 | @njit(cache=True) 143 | def cowden_clay( 144 | X: float, 145 | Su: float, 146 | G0: float, 147 | D: float, 148 | L: float, 149 | output_length: int = 20, 150 | ): 151 | """ 152 | Creates the base shear spring from the PISA clay formulation 153 | published by Byrne et al 2020 (see [BHBG20]_) and calibrated based pile 154 | load tests at Cowden (north east coast of England). 155 | 156 | Parameters 157 | ---------- 158 | X : float 159 | Depth below ground level [unit: m] 160 | Su : float 161 | Undrained shear strength [unit: kPa] 162 | G0 : float 163 | Small-strain shear modulus [unit: kPa] 164 | D : float 165 | Pile diameter [unit: m] 166 | L : float 167 | Embedded pile length [unit: m] 168 | output_length : int, optional 169 | Number of datapoints in the curve, by default 20 170 | 171 | Returns 172 | ------- 173 | 1darray 174 | y vector [unit: m] 175 | 1darray 176 | Hb vector [unit: kN] 177 | 178 | """ 179 | 180 | # Generalised Cowden clay Model parameters 181 | v_hu1 = 235.7 182 | v_hu2 = 0.00 183 | k_h1 = 2.717 184 | k_h2 = -0.3575 185 | n_h1 = 0.8793 186 | n_h2 = -0.03150 187 | p_u1 = 0.4038 188 | p_u2 = 0.04812 189 | 190 | # Depth variation parameters 191 | v_max = v_hu1 + v_hu2 * L / D 192 | k = k_h1 + k_h2 * L / D 193 | n = n_h1 + n_h2 * L / D 194 | p_max = p_u1 + p_u2 * L / D 195 | 196 | # calculate normsalised conic function 197 | y, p = conic(v_max, n, k, p_max, output_length) 198 | 199 | # return non-normalised curve 200 | return y * (Su * D / G0), p * (Su * D**2) 201 | 202 | 203 | @njit(cache=True) 204 | def custom_pisa_sand( 205 | sig: float, 206 | G0: float, 207 | D: float, 208 | X_ult: float, 209 | n: float, 210 | k: float, 211 | Y_ult: float, 212 | output_length: int = 20, 213 | ): 214 | """ 215 | Creates a base shear spring with the PISA sand formulation and custom user inputs. 216 | 217 | Parameters 218 | ---------- 219 | sig : float 220 | vertical/overburden effective stress [unit: kPa] 221 | Dr : float 222 | Sand relative density Value must be between 0 and 100 [unit: -] 223 | G0 : float 224 | Small-strain shear modulus [unit: kPa] 225 | D : float 226 | Pile diameter [unit: m] 227 | X_ult : float 228 | Normalized displacement at maximum strength 229 | k : float 230 | Normalized stiffness parameter 231 | n : float 232 | Normalized curvature parameter, must be between 0 and 1 233 | Y_ult : float 234 | Normalized maximum strength parameter 235 | output_length : int, optional 236 | Number of datapoints in the curve, by default 20 237 | 238 | Returns 239 | ------- 240 | 1darray 241 | y vector [unit: m] 242 | 1darray 243 | Hb vector [unit: kN] 244 | """ 245 | 246 | # calculate normsalised conic function 247 | x, y = conic(X_ult, n, k, Y_ult, output_length) 248 | 249 | # return non-normalised curve 250 | return x * (sig * D / G0), y * (sig * D**2) 251 | 252 | 253 | @njit(cache=True) 254 | def custom_pisa_clay( 255 | Su: float, 256 | G0: float, 257 | D: float, 258 | X_ult: float, 259 | n: float, 260 | k: float, 261 | Y_ult: float, 262 | output_length: int = 20, 263 | ): 264 | """ 265 | Creates a base shear spring with the PISA clay formulation and custom user inputs. 266 | 267 | Parameters 268 | ---------- 269 | Su : float 270 | Undrained shear strength [unit: kPa] 271 | G0 : float 272 | Small-strain shear modulus [unit: kPa] 273 | D : float 274 | Pile diameter [unit: m] 275 | X_ult : float 276 | Normalized displacement at maximum strength 277 | k : float 278 | Normalized stiffness parameter 279 | n : float 280 | Normalized curvature parameter, must be between 0 and 1 281 | Y_ult : float 282 | Normalized maximum strength parameter 283 | output_length : int, optional 284 | Number of datapoints in the curve, by default 20 285 | 286 | Returns 287 | ------- 288 | 1darray 289 | y vector [unit: m] 290 | 1darray 291 | Hb vector [unit: kN] 292 | 293 | """ 294 | # calculate normsalised conic function 295 | x, y = conic(X_ult, n, k, Y_ult, output_length) 296 | 297 | # return non-normalised curve 298 | return x * (Su * D / G0), y * (Su * D**2) 299 | -------------------------------------------------------------------------------- /src/openpile/utils/Mb_curves.py: -------------------------------------------------------------------------------- 1 | """ 2 | `Mb_curves` module 3 | ------------------ 4 | 5 | """ 6 | 7 | # Import libraries 8 | import math as m 9 | import numpy as np 10 | from numba import njit, prange 11 | from random import random 12 | 13 | from openpile.core.misc import conic 14 | 15 | # SPRING FUNCTIONS -------------------------------------------- 16 | @njit(cache=True) 17 | def bothkennar_clay( 18 | X: float, 19 | Su: float, 20 | G0: float, 21 | D: float, 22 | L: float, 23 | output_length: int = 20, 24 | ): 25 | """ 26 | Create the base moment springs from the PISA clay formulation 27 | published by Burd et al 2020 (see [BABH20]_) and calibrated based on Bothkennar clay 28 | response (a normally consolidated soft clay). 29 | 30 | Parameters 31 | ---------- 32 | X : float 33 | Depth below ground level [unit: m] 34 | Su : float 35 | Undrained shear strength [unit: kPa] 36 | G0 : float 37 | Small-strain shear modulus [unit: kPa] 38 | D : float 39 | Pile diameter [unit: m] 40 | L : float 41 | Pile length [unit: m] 42 | output_length : int, optional 43 | Number of datapoints in the curve, by default 20 44 | 45 | Returns 46 | ------- 47 | 1darray 48 | t vector of length [unit: rad] 49 | 1darray 50 | Mb vector [unit: kN] 51 | 52 | """ 53 | 54 | # Bothkennar clay parameters 55 | k_m1 = 0.3409 56 | k_m2 = -0.01995 57 | n_m1 = 0.6990 58 | n_m2 = -0.1155 59 | m_m1 = 0.8756 60 | m_m2 = -0.09195 61 | psi_u = 187.0 62 | 63 | # Depth variation parameters 64 | k = k_m1 + k_m2 * L / D 65 | n = n_m1 + n_m2 * L / D 66 | m_max = m_m1 + m_m2 * L / D 67 | psi_max = psi_u 68 | 69 | # calculate normsalised conic function 70 | t, m = conic(psi_max, n, k, m_max, output_length) 71 | 72 | # return non-normalised curve 73 | return t * (Su / G0), m * (Su * D**3) 74 | 75 | 76 | @njit(cache=True) 77 | def cowden_clay( 78 | X: float, 79 | Su: float, 80 | G0: float, 81 | D: float, 82 | L: float, 83 | output_length: int = 20, 84 | ): 85 | """ 86 | Create the base moment springs from the PISA clay formulation 87 | published by Byrne et al 2020 (see [BHBG20]_) and calibrated based pile 88 | load tests at Cowden (north east coast of England). 89 | 90 | Parameters 91 | ---------- 92 | X : float 93 | Depth below ground level [unit: m] 94 | Su : float 95 | Undrained shear strength [unit: kPa] 96 | G0 : float 97 | Small-strain shear modulus [unit: kPa] 98 | D : float 99 | Pile diameter [unit: m] 100 | L : float 101 | Pile length [unit: m] 102 | output_length : int, optional 103 | Number of datapoints in the curve, by default 20 104 | 105 | Returns 106 | ------- 107 | 1darray 108 | t vector of length [unit: rad] 109 | 1darray 110 | Mb vector [unit: kN] 111 | 112 | """ 113 | 114 | # Cowden clay parameters 115 | k_m1 = 0.2146 116 | k_m2 = -0.002132 117 | n_m1 = 1.079 118 | n_m2 = -0.1087 119 | m_m1 = 0.8192 120 | m_m2 = -0.08588 121 | psi_u = 173.1 122 | 123 | # Depth variation parameters 124 | k = k_m1 + k_m2 * L / D 125 | n = n_m1 + n_m2 * L / D 126 | m_max = m_m1 + m_m2 * L / D 127 | psi_max = psi_u 128 | 129 | # calculate normsalised conic function 130 | t, m = conic(psi_max, n, k, m_max, output_length) 131 | 132 | # return non-normalised curve 133 | return t * (Su / G0), m * (Su * D**3) 134 | 135 | 136 | @njit(cache=True) 137 | def dunkirk_sand( 138 | sig: float, 139 | X: float, 140 | Dr: float, 141 | G0: float, 142 | D: float, 143 | L: float, 144 | output_length: int = 20, 145 | ): 146 | """ 147 | Create the base moment spring from the PISA sand 148 | formulation published by Burd et al (2020) (see [BTZA20]_). 149 | Also called the General Dunkirk Sand Model (GDSM) 150 | 151 | Parameters 152 | ---------- 153 | sig : float 154 | vertical/overburden effective stress [unit: kPa] 155 | X : float 156 | Depth below ground level [unit: m] 157 | Dr : float 158 | Sand relative density Value must be between 0 and 100 [unit: -] 159 | G0 : float 160 | Small-strain shear modulus [unit: kPa] 161 | D : float 162 | Pile diameter [unit: m] 163 | L : float 164 | Embedded pile length [unit: m] 165 | output_length : int, optional 166 | Number of datapoints in the curve, by default 20 167 | 168 | Returns 169 | ------- 170 | 1darray 171 | t vector of length [unit: rad] 172 | 1darray 173 | Mb vector [unit: kN] 174 | """ 175 | # correct relative density for decimal value 176 | Dr = Dr / 100 177 | 178 | # Generalised Dunkirk Sand Model parameters 179 | k_m1 = 0.3515 180 | k_m2 = 0.00 181 | n_m = 0.3 + 0.4986 * Dr 182 | m_u1 = 0.09981 + 0.3710 * Dr 183 | m_u2 = 0.01998 - 0.09041 * Dr 184 | psi_u = 44.89 185 | 186 | # Depth variation parameters 187 | k = k_m1 + k_m2 * L / D 188 | n = n_m 189 | m_max = m_u1 + m_u2 * L / D 190 | psi_max = psi_u 191 | 192 | # calculate normsalised conic function 193 | t, m = conic(psi_max, n, k, m_max, output_length) 194 | 195 | # return non-normalised curve 196 | return t * (sig / G0), m * (sig * D**3) 197 | 198 | 199 | @njit(cache=True) 200 | def custom_pisa_sand( 201 | sig: float, 202 | G0: float, 203 | D: float, 204 | X_ult: float, 205 | n: float, 206 | k: float, 207 | Y_ult: float, 208 | output_length: int = 20, 209 | ): 210 | """ 211 | Creates a base moment spring with the PISA sand formulation and custom user inputs. 212 | 213 | Parameters 214 | ---------- 215 | sig : float 216 | vertical/overburden effective stress [unit: kPa] 217 | Dr : float 218 | Sand relative density Value must be between 0 and 100 [unit: -] 219 | G0 : float 220 | Small-strain shear modulus [unit: kPa] 221 | D : float 222 | Pile diameter [unit: m] 223 | X_ult : float 224 | Normalized displacement at maximum strength 225 | k : float 226 | Normalized stiffness parameter 227 | n : float 228 | Normalized curvature parameter, must be between 0 and 1 229 | Y_ult : float 230 | Normalized maximum strength parameter 231 | output_length : int, optional 232 | Number of datapoints in the curve, by default 20 233 | 234 | Returns 235 | ------- 236 | 1darray 237 | y vector [unit: m] 238 | 1darray 239 | Hb vector [unit: kN] 240 | """ 241 | 242 | # calculate normsalised conic function 243 | x, y = conic(X_ult, n, k, Y_ult, output_length) 244 | 245 | # return non-normalised curve 246 | return x * (sig / G0), y * (sig * D**3) 247 | 248 | 249 | @njit(cache=True) 250 | def custom_pisa_clay( 251 | Su: float, 252 | G0: float, 253 | D: float, 254 | X_ult: float, 255 | n: float, 256 | k: float, 257 | Y_ult: float, 258 | output_length: int = 20, 259 | ): 260 | """ 261 | Creates a base moment spring with the PISA clay formulation and custom user inputs. 262 | 263 | Parameters 264 | ---------- 265 | Su : float 266 | Undrained shear strength [unit: kPa] 267 | G0 : float 268 | Small-strain shear modulus [unit: kPa] 269 | D : float 270 | Pile diameter [unit: m] 271 | X_ult : float 272 | Normalized displacement at maximum strength 273 | k : float 274 | Normalized stiffness parameter 275 | n : float 276 | Normalized curvature parameter, must be between 0 and 1 277 | Y_ult : float 278 | Normalized maximum strength parameter 279 | output_length : int, optional 280 | Number of datapoints in the curve, by default 20 281 | 282 | Returns 283 | ------- 284 | 1darray 285 | y vector [unit: m] 286 | 1darray 287 | Hb vector [unit: kN] 288 | 289 | """ 290 | # calculate normsalised conic function 291 | x, y = conic(X_ult, n, k, Y_ult, output_length) 292 | 293 | # return non-normalised curve 294 | return x * (Su / G0), y * (Su * D**3) 295 | -------------------------------------------------------------------------------- /src/openpile/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TchilDill/openpile/fe16b26d01249b37edf9a8f16c72e01081ae8171/src/openpile/utils/__init__.py -------------------------------------------------------------------------------- /src/openpile/utils/graphics.py: -------------------------------------------------------------------------------- 1 | # import libraries 2 | import numpy as np 3 | import matplotlib as mpl 4 | import matplotlib.pyplot as plt 5 | 6 | 7 | from matplotlib.patches import FancyArrowPatch, Rectangle 8 | from matplotlib.ticker import MultipleLocator, AutoMinorLocator 9 | from openpile.core.misc import generate_color_string 10 | 11 | mpl.rcParams["figure.subplot.wspace"] = 0.4 12 | 13 | 14 | def plot_deflection(result): 15 | fig, ax = plt.subplots() 16 | 17 | fig.suptitle(f"{result._name} - Pile Deflection") 18 | 19 | ax = U_plot(ax, result) 20 | 21 | return fig 22 | 23 | 24 | def plot_settlement(result): 25 | fig, (ax1, ax2) = plt.subplots(1, 2) 26 | 27 | fig.suptitle(f"{result._name} - Pile settlements") 28 | 29 | ax1 = settlement_plot(ax1, result) 30 | ax2 = F_plot(ax2, result, "N [kN]") 31 | 32 | ax2.set_yticklabels("") 33 | ax2.set_ylabel("") 34 | 35 | return fig 36 | 37 | 38 | def plot_forces(result): 39 | # create 4 subplots with (deflectiom, normal force, shear force, bending moment) 40 | 41 | fig, (ax1, ax2, ax3) = plt.subplots(1, 3) 42 | 43 | fig.suptitle(f"{result._name} - Sectional forces") 44 | 45 | ax1 = F_plot(ax1, result, "N [kN]") 46 | ax2 = F_plot(ax2, result, "V [kN]") 47 | ax3 = F_plot(ax3, result, "M [kNm]") 48 | 49 | for axis in [ax2, ax3]: 50 | axis.set_yticklabels("") 51 | axis.set_ylabel("") 52 | 53 | return fig 54 | 55 | 56 | def plot_results(result): 57 | # create 4 subplots with (deflectiom, normal force, shear force, bending moment) 58 | 59 | fig, (ax1, ax2, ax3, ax4) = plt.subplots(1, 4) 60 | 61 | fig.suptitle(f"{result._name} - Analysis results") 62 | 63 | ax1 = U_plot(ax1, result) 64 | ax2 = F_plot(ax2, result, "N [kN]") 65 | ax3 = F_plot(ax3, result, "V [kN]") 66 | ax4 = F_plot(ax4, result, "M [kNm]") 67 | 68 | for axis in [ax2, ax3, ax4]: 69 | axis.set_yticklabels("") 70 | axis.set_ylabel("") 71 | 72 | return fig 73 | 74 | 75 | def soil_plot(SoilProfile, ax=None): 76 | def add_soil_profile(SoilProfile, ax, pile=None): 77 | 78 | ax.set_title(label=f"Soil Profile overview - {SoilProfile.name}") 79 | ax.set_xlim(left=0, right=1) 80 | 81 | # make data 82 | offset = 5 83 | yBot = SoilProfile.bottom_elevation 84 | if pile is None: 85 | yTop = max(SoilProfile.top_elevation + offset, SoilProfile.water_line + offset) 86 | else: 87 | yTop = max( 88 | SoilProfile.top_elevation + offset, 89 | SoilProfile.water_line + offset, 90 | pile.top_elevation + offset, 91 | ) 92 | 93 | # axes 94 | ax.set_ylim(bottom=yBot, top=yTop) 95 | ax.set_ylabel("Elevation [m VREF]") 96 | ax.set_xticks([]) 97 | 98 | for layer in SoilProfile.layers: 99 | ax.add_patch( 100 | Rectangle( 101 | xy=(-100, layer.bottom), 102 | width=200, 103 | height=layer.top - layer.bottom, 104 | facecolor=layer.color, 105 | ) 106 | ) 107 | 108 | ax.text( 109 | 0.02, 110 | 0.5 * (layer.top + layer.bottom), 111 | layer.name, 112 | bbox={"facecolor": [0.98, 0.96, 0.85], "alpha": 1, "edgecolor": "none", "pad": 1}, 113 | ) 114 | 115 | ax.plot( 116 | np.array([-100, 0.1, 100]), 117 | SoilProfile.water_line + np.zeros((3)), 118 | mfc="dodgerblue", 119 | marker=7, 120 | linewidth=1, 121 | color="dodgerblue", 122 | ) 123 | 124 | # grid 125 | ax.minorticks_on() 126 | ax.grid() 127 | ax.grid(axis="y", which="minor", color=[0.75, 0.75, 0.75], linestyle="-", linewidth=0.5) 128 | 129 | ax.plot( 130 | np.array([-100, 0.1, 100]), 131 | SoilProfile.water_line + np.zeros((3)), 132 | mfc="dodgerblue", 133 | marker=7, 134 | linewidth=1, 135 | color="dodgerblue", 136 | ) 137 | 138 | return ax 139 | 140 | if ax is None: 141 | _, ax = plt.subplots() 142 | ax = add_soil_profile(SoilProfile, ax, pile=None) 143 | 144 | return ax 145 | 146 | 147 | def connectivity_plot(model, ax=None): 148 | # TODO docstring 149 | 150 | support_color = "b" 151 | # create 4 subplots with (deflectiom, normal force, shear force, bending moment) 152 | if ax is None: 153 | fig, ax = plt.subplots() 154 | ax.set_ylabel("z [m]") 155 | ax.set_xlabel("y [m]") 156 | ax.set_title(f"{model.name} - Connectivity plot") 157 | ax.axis("equal") 158 | ax.grid(which="both") 159 | 160 | # plot mesh with + scatter points to see nodes. 161 | z = model.nodes_coordinates["z [m]"] 162 | y = model.nodes_coordinates["y [m]"] 163 | ax.plot(y, z, "-k", marker="+") 164 | 165 | total_length = ( 166 | (model.nodes_coordinates["z [m]"].max() - model.nodes_coordinates["z [m]"].min()) ** 2 167 | + (model.nodes_coordinates["y [m]"].max() - model.nodes_coordinates["y [m]"].min()) ** 2 168 | ) ** (0.5) 169 | 170 | ylim = ax.get_ylim() 171 | 172 | # plots SUPPORTS 173 | # Plot supports along z 174 | support_along_z = model.global_restrained["Tz"].values 175 | support_along_z_down = np.copy(support_along_z) 176 | support_along_z_down[-1] = False 177 | support_along_z_up = np.copy(support_along_z) 178 | support_along_z_up[:-1] = False 179 | ax.scatter( 180 | y[support_along_z_down], 181 | z[support_along_z_down], 182 | color=support_color, 183 | marker=7, 184 | s=100, 185 | ) 186 | ax.scatter( 187 | y[support_along_z_up], 188 | z[support_along_z_up], 189 | color=support_color, 190 | marker=6, 191 | s=100, 192 | ) 193 | 194 | # Plot supports along y 195 | support_along_y = model.global_restrained["Ty"].values 196 | ax.scatter(y[support_along_y], z[support_along_y], color=support_color, marker=5, s=100) 197 | 198 | # Plot supports along z 199 | support_along_z = model.global_restrained["Rx"].values 200 | ax.scatter(y[support_along_z], z[support_along_z], color=support_color, marker="s", s=35) 201 | 202 | # plot LOADS 203 | arrows = [] 204 | 205 | normalized_arrow_size = ( 206 | 0.10 * total_length 207 | ) # max arrow length will be 20% of the total structure length 208 | 209 | load_max = model.global_forces["Py [kN]"].abs().max() 210 | for yval, zval, load in zip(z, y, model.global_forces["Py [kN]"]): 211 | if load == 0: 212 | pass 213 | else: 214 | style = "Simple, tail_width=1, head_width=5, head_length=3" 215 | kw = dict(arrowstyle=style, color="r") 216 | arrow_length = normalized_arrow_size * abs(load / load_max) 217 | if load > 0: 218 | arrows.append(FancyArrowPatch((-arrow_length, yval), (zval, yval), **kw)) 219 | elif load < 0: 220 | arrows.append(FancyArrowPatch((arrow_length, yval), (zval, yval), **kw)) 221 | 222 | load_max = model.global_forces["Pz [kN]"].abs().max() 223 | for idx, (yval, zval, load) in enumerate(zip(z, y, model.global_forces["Pz [kN]"])): 224 | if load == 0: 225 | pass 226 | else: 227 | style = "Simple, tail_width=1, head_width=5, head_length=3" 228 | kw = dict(arrowstyle=style, color="r") 229 | arrow_length = normalized_arrow_size * abs(load / load_max) 230 | if load > 0: 231 | if idx == len(z) - 1: 232 | arrows.append(FancyArrowPatch((zval, yval), (zval, yval + arrow_length), **kw)) 233 | else: 234 | arrows.append(FancyArrowPatch((zval, yval - arrow_length), (zval, yval), **kw)) 235 | elif load < 0: 236 | if idx == len(z) - 1: 237 | arrows.append(FancyArrowPatch((zval, yval), (zval, yval - arrow_length), **kw)) 238 | else: 239 | arrows.append(FancyArrowPatch((zval, yval + arrow_length), (zval, yval), **kw)) 240 | 241 | load_max = model.global_forces["Mx [kNm]"].abs().max() 242 | for idx, (yval, zval, load) in enumerate(zip(z, y, model.global_forces["Mx [kNm]"])): 243 | if load == 0: 244 | pass 245 | else: 246 | kw = dict(arrowstyle=style, color="r") 247 | arrow_length = normalized_arrow_size * abs(load / load_max) 248 | style = "Simple, tail_width=1, head_width=5, head_length=3" 249 | if load > 0: 250 | if idx == len(z) - 1: 251 | arrows.append( 252 | FancyArrowPatch( 253 | (arrow_length / 1.5, yval), 254 | (-arrow_length / 1.5, yval), 255 | connectionstyle="arc3,rad=0.5", 256 | **kw, 257 | ) 258 | ) 259 | else: 260 | arrows.append( 261 | FancyArrowPatch( 262 | (-arrow_length / 1.5, yval), 263 | (arrow_length / 1.5, yval), 264 | connectionstyle="arc3,rad=0.5", 265 | **kw, 266 | ) 267 | ) 268 | elif load < 0: 269 | if idx == len(z) - 1: 270 | arrows.append( 271 | FancyArrowPatch( 272 | (arrow_length / 1.5, yval), 273 | (-arrow_length / 1.5, yval), 274 | connectionstyle="arc3,rad=-0.5", 275 | **kw, 276 | ) 277 | ) 278 | else: 279 | arrows.append( 280 | FancyArrowPatch( 281 | (-arrow_length / 1.5, yval), 282 | (arrow_length / 1.5, yval), 283 | connectionstyle="arc3,rad=-0.5", 284 | **kw, 285 | ) 286 | ) 287 | 288 | ax.set_ylim(ylim[0] - 0.11 * total_length, ylim[1] + 0.11 * total_length) 289 | 290 | if model.soil is not None: 291 | 292 | ax.set_ylim( 293 | min(model.bottom, ylim[0]) - 0.11 * total_length, 294 | max(model.top, ylim[1]) + 0.11 * total_length, 295 | ) 296 | 297 | for layer in model.soil.layers: 298 | ax.add_patch( 299 | Rectangle( 300 | xy=(-2 * total_length, layer.bottom), 301 | width=4 * total_length, 302 | height=layer.top - layer.bottom, 303 | facecolor=layer.color, 304 | alpha=0.4, 305 | ) 306 | ) 307 | 308 | ax.text( 309 | -1 * total_length, 310 | 0.5 * (layer.top + layer.bottom) + 0.1 * (layer.top - layer.bottom), 311 | layer.name, 312 | bbox={"facecolor": [0.98, 0.96, 0.85], "alpha": 1, "edgecolor": "none", "pad": 1}, 313 | ) 314 | 315 | ax.plot( 316 | np.array([-2 * total_length, -0.6 * total_length, 2 * total_length]), 317 | model.soil.water_line + np.zeros((3)), 318 | mfc="dodgerblue", 319 | marker=7, 320 | linewidth=1, 321 | color="dodgerblue", 322 | ) 323 | ax.set_xlim((-0.7 * total_length, 0.3 * total_length)) 324 | 325 | else: 326 | ax.set_xlim((-0.5 * total_length, 0.5 * total_length)) 327 | 328 | for arrow in arrows: 329 | ax.add_patch(arrow) 330 | 331 | return ax 332 | 333 | 334 | def U_plot(axis: plt.axis, result): 335 | # TODO docstring 336 | 337 | axis.set_ylabel("Elevation [m VREF]", fontsize=8) 338 | axis.set_xlabel("Deflection [mm]", fontsize=8) 339 | axis.tick_params(axis="both", labelsize=8) 340 | axis.grid(which="both") 341 | 342 | y = result.displacements["Elevation [m]"].values 343 | x = np.zeros(shape=y.shape) 344 | deflection = result.displacements["Deflection [m]"] * 1e3 345 | 346 | axis.plot(x, y, color="0.4") 347 | axis.plot(deflection, y, color="0.0", lw=2) 348 | 349 | return axis 350 | 351 | 352 | def settlement_plot(axis: plt.axis, result): 353 | # TODO docstring 354 | 355 | axis.set_ylabel("Elevation [m VREF]", fontsize=8) 356 | axis.set_xlabel("Settlement [mm]", fontsize=8) 357 | axis.tick_params(axis="both", labelsize=8) 358 | axis.grid(which="both") 359 | 360 | y = result.displacements["Elevation [m]"].values 361 | x = np.zeros(shape=y.shape) 362 | settlement = result.settlement["Settlement [m]"] * 1e3 363 | 364 | axis.plot(x, y, color="0.4") 365 | axis.plot(settlement, y, color="0.0", lw=2) 366 | 367 | return axis 368 | 369 | 370 | def F_plot(axis: plt.axis, result, force: str): 371 | # TODO docstring 372 | 373 | # Define plot colors 374 | force_facecolor = "#E6DAA6" # beige 375 | force_edgecolor = "#AAA662" # khaki 376 | 377 | axis.set_ylabel("Elevation [m VREF]", fontsize=8) 378 | axis.set_xlabel(force, fontsize=8) 379 | axis.tick_params(axis="both", labelsize=8) 380 | axis.grid(which="both") 381 | 382 | f = result.forces[force] 383 | y = result.forces["Elevation [m]"] 384 | 385 | axis.fill_betweenx(y, f, edgecolor=force_edgecolor, facecolor=force_facecolor) 386 | axis.plot(np.zeros(shape=y.shape), y, color="0.4") 387 | 388 | axis.set_xlim( 389 | [ 390 | min(0, f.min() - 0.1 * abs(f.min() + 1.0)), 391 | max(0, f.max() + 0.1 * abs(f.max() + 1.0)), 392 | ] 393 | ) 394 | 395 | return axis 396 | -------------------------------------------------------------------------------- /src/openpile/utils/hooks.py: -------------------------------------------------------------------------------- 1 | """ 2 | `hooks` module 3 | -------------- 4 | 5 | 6 | 7 | """ 8 | 9 | import math as m 10 | 11 | 12 | def durkhop(D: float, ra: float = 0.3) -> object: 13 | """This function generates p-multipliers as per Durkhop et al (see [Duhr09]_) for application 14 | to cyclic curves of the `openpile.soilmodels.API_sand` model. 15 | 16 | The default behaviour, i.e. when the parameter `ra` = 0.3 is that the API sand cyclic curves are unchanged 17 | with a equivalent number of cycles equal to 100. 18 | When the parameter `ra` is equal to 1.0, the cyclic curves are approach the static curves. 19 | In between 0.3 and 1.0, the multipliers may be thought as cyclic curves with a lower number of equivalent cycles. 20 | 21 | Parameters 22 | ---------- 23 | D : float 24 | Pile diameter [m] 25 | ra : float, optional 26 | empirical factor dependent on the number of load cycles, 1.0 for monotonic loading and 27 | 0.3 for cyclic loading at 100 cycles, by default 0.3 28 | 29 | Returns 30 | ------- 31 | object 32 | callable for use as p_multipler in `openpile.soilmodels.API_sand` in cyclic mode. 33 | 34 | Example 35 | ------- 36 | 37 | .. plot:: 38 | 39 | import matplotlib.pyplot as plt 40 | from openpile.construct import Layer 41 | from openpile.utils import hooks 42 | from openpile.soilmodels import API_sand 43 | # settings for py curve generation 44 | kw = {'sig':50, 'X':5, 'layer_height':10, 'depth_from_top_of_layer':5, 'D':5, 'L':30} 45 | # a, b soil models are traditional cyclic and static API sand models 46 | a = API_sand( 47 | phi=33, 48 | kind="static", 49 | ) 50 | b = API_sand( 51 | phi=33, 52 | kind="cyclic", 53 | ) 54 | plt.plot(*a.py_spring_fct(**kw), label='static') 55 | plt.plot(*b.py_spring_fct(**kw), label='cyclic') 56 | # soil model c is the API sand model with `durkhop` multipliers 57 | for ra in [0.3,0.45,0.6,0.75,0.9]: 58 | c = API_sand( 59 | phi=33, 60 | kind="cyclic", 61 | p_multiplier=hooks.durkhop(D=7.0, ra=ra) 62 | ) 63 | plt.plot(*c.py_spring_fct(**kw), ':',label=f'Durkhop multipliers, ra={ra}') 64 | plt.legend() 65 | 66 | Reference 67 | --------- 68 | 69 | .. [Duhr09] Dührkop, J. (2009). *Zum Einfluss von Aufweitungen und zyklischen Lasten auf 70 | das Verformungsverhalten lateral 385 beanspruchter Pfähle in Sand*. Ph.D. Thesis, 71 | Institut für Geotechnik und Baubetrieb, Technische Universität Hamburg-Harburg, Vol. 20 (in German). 72 | 73 | """ 74 | 75 | func = lambda x: 1 / 0.9 * max(0.9, ra * (3 - 1.143 * x / D) + 0.343 * x / D) 76 | 77 | return func 78 | 79 | 80 | def durkhop_normalized(D: float, ra: float = 0.3) -> object: 81 | """This function generates multipliers that represent ratios the cyclic and monotonic curves of 82 | the traditional API sand model. 83 | 84 | 85 | Parameters 86 | ---------- 87 | D : float 88 | Pile diameter [m] 89 | ra : float, optional 90 | empirical factor dependent on the number of load cycles, 1.0 for monotonic loading and 91 | 0.3 for cyclic loading at 100 cycles, by default 0.3 92 | 93 | Returns 94 | ------- 95 | object 96 | callable for use as p_multipler in `openpile.soilmodels.LateralModel` or `openpile.soilmodels.AxialModel` 97 | The function input is the depth and the function output is the multiplier applied 98 | for the spring at the said depth. 99 | 100 | Example 101 | ------- 102 | 103 | 104 | See also 105 | -------- 106 | `openpile.utils.hooks.durkhop` 107 | """ 108 | 109 | func = lambda x: 0.9 / max(0.9, ra * (3 - 1.143 * x / D) + 0.343 * x / D) 110 | 111 | return func 112 | 113 | 114 | class PISA_depth_variation: 115 | r"""stores functions that returns the depth varying normalized parameters 116 | of the p-y, m-t, H-b and M-b conic formulations as per [BTZA20]_ and [BHBG20]_, and [BABH20]_. 117 | 118 | These functions can be then be used to model the original models, e.g. Cowden Clay, 119 | or make modifications to the original models. 120 | 121 | Example 122 | ------- 123 | 124 | .. plot:: 125 | 126 | import matplotlib.pyplot as plt 127 | from openpile.utils.py_curves import custom_pisa_sand, dunkirk_sand 128 | from openpile.utils.hooks import PISA_depth_variation 129 | 130 | # plot original dunkirk sand curve 131 | params = {'sig':50, 'X':5, 'Dr':75, 'G0':50e3, 'D':6, 'L':20} 132 | plt.plot(*dunkirk_sand(**params), label='Original Dunkirk Sand model', linestyle='-') 133 | 134 | # load PISA dunkirk sand depth variation functions 135 | funcs = PISA_depth_variation.dunkirk_sand_py_pisa_norm_param(D=6, L=20, Dr=75) 136 | 137 | # plot same curve with custom pisa sand curve 138 | params = {'sig':50, 'G0':50e3, 'D':6, 'X_ult':funcs['py_X'](5), 'Y_ult':funcs['py_Y'](5), 'n':funcs['py_n'](5), 'k':funcs['py_k'](5)} 139 | plt.plot(*custom_pisa_sand(**params), label='Custom Sand equivalent to Dunkirk sand model', linestyle='--') 140 | 141 | # plot an alrtered curve with custom pisa sand curve 142 | params['Y_ult'] /= 2 143 | plt.plot(*custom_pisa_sand(**params), label='Custom Sand with altered ultimate strength', linestyle='--') 144 | 145 | plt.title('Dunkirk Sand model vs. Custom PISA sand model') 146 | plt.legend() 147 | 148 | 149 | """ 150 | 151 | @staticmethod 152 | def dunkirk_sand_pisa_norm_param(D: float, L: float): 153 | """returns the depth variation functions for all normalized parameters 154 | of the dunkirk sand conic formulations as per [BTZA20]_. 155 | 156 | Parameters 157 | ---------- 158 | D : float 159 | pile diameter [m] 160 | L : float 161 | pile embedment [m] 162 | """ 163 | 164 | py = PISA_depth_variation.dunkirk_sand_py_pisa_norm_param(D=D) 165 | mt = PISA_depth_variation.dunkirk_sand_mt_pisa_norm_param(D=D) 166 | Hb = PISA_depth_variation.dunkirk_sand_Hb_pisa_norm_param(D=D, L=L) 167 | Mb = PISA_depth_variation.dunkirk_sand_Mb_pisa_norm_param(D=D, L=L) 168 | 169 | return {**py, **mt, **Hb, **Mb} 170 | 171 | @staticmethod 172 | def cowden_clay_pisa_norm_param(D: float, L: float): 173 | """returns the depth variation functions for all normalized parameters 174 | of the cowden_clay conic formulations as per [BHBG20]_. 175 | 176 | Parameters 177 | ---------- 178 | D : float 179 | pile diameter [m] 180 | L : float 181 | pile embedment [m] 182 | """ 183 | 184 | py = PISA_depth_variation.cowden_clay_py_pisa_norm_param(D=D) 185 | mt = PISA_depth_variation.cowden_clay_mt_pisa_norm_param(D=D) 186 | Hb = PISA_depth_variation.cowden_clay_Hb_pisa_norm_param(D=D, L=L) 187 | Mb = PISA_depth_variation.cowden_clay_Mb_pisa_norm_param(D=D, L=L) 188 | 189 | return {**py, **mt, **Hb, **Mb} 190 | 191 | @staticmethod 192 | def cowden_clay_py_pisa_norm_param(D: float): 193 | """returns the depth variation functions for the 4 normalized parameters 194 | of the cowden_clay p-y conic formulation as per [BHBG20]_. 195 | 196 | Parameters 197 | ---------- 198 | D : float 199 | pile diameter [m] 200 | 201 | Returns 202 | ------- 203 | dict 204 | dictionary of depth variation functions with keys['py_k','py_n','py_X', 'py_Y'] 205 | accepting one input argument, the depth from ground (strictly positive) [m] 206 | """ 207 | # normalized initial stiffness 208 | k_p1 = 10.6 209 | k_p2 = -1.650 210 | k_func = lambda x: max(0.0001, k_p1 + k_p2 * x / D) 211 | 212 | # normalized curvature 213 | n_p1 = 0.9390 214 | n_p2 = -0.03345 215 | n_func = lambda x: min(0.999, max(0, n_p1 + n_p2 * x / D)) 216 | 217 | # normalized displacement at peak resistance 218 | X_func = lambda x: 241.4 219 | 220 | # normalized peak resistance 221 | p_u1 = 10.7 222 | p_u2 = -7.101 223 | Y_func = lambda x: max(0.0001, p_u1 + p_u2 * m.exp(-0.3085 * x / D)) 224 | 225 | return {"py_k": k_func, "py_n": n_func, "py_X": X_func, "py_Y": Y_func} 226 | 227 | @staticmethod 228 | def cowden_clay_mt_pisa_norm_param(D: float): 229 | """returns the depth variation functions for the 4 normalized parameters 230 | of the cowden_clay rotational spring conic formulation as per [BHBG20]_. 231 | 232 | Parameters 233 | ---------- 234 | D : float 235 | pile diameter [m] 236 | 237 | Returns 238 | ------- 239 | dict 240 | dictionary of depth variation functions with keys['mt_k','mt_n','mt_X', 'mt_Y'] 241 | accepting one input argument, the depth from ground (strictly positive) [m] 242 | """ 243 | # normalized initial stiffness 244 | k_m1 = 1.420 245 | k_m2 = -0.09643 246 | k_func = lambda x: max(0.0001, k_m1 + k_m2 * x / D) 247 | 248 | # normalized curvature 249 | n_func = lambda x: 0.0 250 | 251 | # normalized peak resistance 252 | m_m1 = 0.2899 253 | m_m2 = -0.04775 254 | Y_func = lambda x: max(0.0001, m_m1 + m_m2 * x / D) 255 | 256 | # normalized displacement at peak resistance 257 | X_func = lambda x: max(0.0001, m_m1 + m_m2 * x / D) / max(0.0001, k_m1 + k_m2 * x / D) 258 | 259 | return {"mt_k": k_func, "mt_n": n_func, "mt_X": X_func, "mt_Y": Y_func} 260 | 261 | @staticmethod 262 | def cowden_clay_Hb_pisa_norm_param(D: float, L: float): 263 | """returns the depth variation functions for the 4 normalized parameters 264 | of the cowden_clay base shear spring conic formulation as per [BHBG20]_. 265 | 266 | Parameters 267 | ---------- 268 | D : float 269 | pile diameter [m] 270 | L : float 271 | pile embedment length [m] 272 | 273 | Returns 274 | ------- 275 | dict 276 | dictionary of depth variation functions with keys['Hb_k','Hb_n','Hb_X', 'Hb_Y'] 277 | accepting one input argument, the depth from ground (strictly positive) [m] 278 | """ 279 | # normalized initial stiffness 280 | k_h1 = 2.717 281 | k_h2 = -0.3575 282 | k_func = lambda x: max(0.0001, k_h1 + k_h2 * L / D) 283 | 284 | # normalized curvature 285 | n_h1 = 0.8793 286 | n_h2 = -0.03150 287 | n_func = lambda x: min(0.999, max(0, n_h1 + n_h2 * L / D)) 288 | 289 | # normalized peak resistance 290 | p_u1 = 0.4038 291 | p_u2 = 0.04812 292 | Y_func = lambda x: max(0.0001, p_u1 + p_u2 * L / D) 293 | 294 | # normalized displacement at peak resistance 295 | X_func = lambda x: 235.7 296 | 297 | return {"Hb_k": k_func, "Hb_n": n_func, "Hb_X": X_func, "Hb_Y": Y_func} 298 | 299 | @staticmethod 300 | def cowden_clay_Mb_pisa_norm_param(D: float, L: float): 301 | """returns the depth variation functions for the 4 normalized parameters 302 | of the cowden_clay base moment spring conic formulation as per [BHBG20]_. 303 | 304 | Parameters 305 | ---------- 306 | D : float 307 | pile diameter [m] 308 | L : float 309 | pile embedment length [m] 310 | 311 | Returns 312 | ------- 313 | dict 314 | dictionary of depth variation functions with keys['Mb_k','Mb_n','Mb_X', 'Mb_Y'] 315 | accepting one input argument, the depth from ground (strictly positive) [m] 316 | """ 317 | # normalized initial stiffness 318 | k_m1 = 0.2146 319 | k_m2 = -0.002132 320 | k_func = lambda x: max(0.0001, k_m1 + k_m2 * L / D) 321 | 322 | # normalized curvature 323 | n_m1 = 1.079 324 | n_m2 = -0.1087 325 | n_func = lambda x: min(0.999, max(0, n_m1 + n_m2 * L / D)) 326 | 327 | # normalized peak resistance 328 | m_m1 = 0.8192 329 | m_m2 = -0.08588 330 | Y_func = lambda x: max(0.0001, m_m1 + m_m2 * L / D) 331 | 332 | # normalized displacement at peak resistance 333 | X_func = lambda x: 173.1 334 | 335 | return {"Mb_k": k_func, "Mb_n": n_func, "Mb_X": X_func, "Mb_Y": Y_func} 336 | 337 | @staticmethod 338 | def dunkirk_sand_py_pisa_norm_param(D: float, L: float, Dr: float): 339 | """returns the depth variation functions for the 4 normalized parameters 340 | of the dunkirk_sand p-y conic formulation as per [BTZA20]_. 341 | 342 | Parameters 343 | ---------- 344 | D : float 345 | pile diameter [m] 346 | L : float 347 | pile embedment length [m] 348 | Dr : float 349 | sand relative density [%] 350 | 351 | Returns 352 | ------- 353 | dict 354 | dictionary of depth variation functions with keys['py_k','py_n','py_X', 'py_Y'] 355 | accepting one input argument, the depth from ground (strictly positive) [m] 356 | """ 357 | Dr = Dr / 100 358 | 359 | # normalized initial stiffness 360 | k_p1 = 8.731 - 0.6982 * Dr 361 | k_p2 = -0.9178 362 | k_func = lambda x: max(0.001, k_p1 + k_p2 * x / D) 363 | 364 | # normalized curvature 365 | n_func = lambda x: min(0.999, max(0, 0.917 + 0.06193 * Dr)) 366 | 367 | # normalized displacement at peak resistance 368 | X_func = lambda x: 146.1 - 92.11 * Dr 369 | 370 | # normalized peak resistance 371 | p_u1 = 0.3667 + 25.89 * Dr 372 | p_u2 = 0.3375 - 8.9 * Dr 373 | Y_func = lambda x: max(0.001, p_u1 + p_u2 * x / L) 374 | 375 | return {"py_k": k_func, "py_n": n_func, "py_X": X_func, "py_Y": Y_func} 376 | 377 | @staticmethod 378 | def dunkirk_sand_mt_pisa_norm_param(L: float, Dr: float): 379 | """returns the depth variation functions for the 4 normalized parameters 380 | of the dunkirk_sand rotational spring conic formulation as per [BTZA20]_. 381 | 382 | Parameters 383 | ---------- 384 | L : float 385 | pile embedment length [m] 386 | Dr : float 387 | sand relative density [%] 388 | 389 | Returns 390 | ------- 391 | dict 392 | dictionary of depth variation functions with keys['mt_k','mt_n','mt_X', 'mt_Y'] 393 | accepting one input argument, the depth from ground (strictly positive) [m] 394 | """ 395 | Dr = Dr / 100 396 | 397 | # normalized initial stiffness 398 | k_func = lambda x: 17.0 399 | 400 | # normalized curvature 401 | n_func = lambda x: 0.0 402 | 403 | # normalized peak resistance 404 | m_u1 = 0.2605 405 | m_u2 = -0.1989 + 0.2019 * Dr 406 | Y_func = lambda x: max(0.001, m_u1 + m_u2 * x / L) 407 | 408 | # normalized displacement at peak resistance 409 | X_func = lambda x: max(0.001, m_u1 + m_u2 * x / L) / 17.0 410 | 411 | return {"mt_k": k_func, "mt_n": n_func, "mt_X": X_func, "mt_Y": Y_func} 412 | 413 | @staticmethod 414 | def dunkirk_sand_Hb_pisa_norm_param(D: float, L: float, Dr: float): 415 | """returns the depth variation functions for the 4 normalized parameters 416 | of the dunkirk_sand base shear spring conic formulation as per [BTZA20]_. 417 | 418 | Parameters 419 | ---------- 420 | D : float 421 | pile diameter [m] 422 | L : float 423 | pile embedment length [m] 424 | Dr : float 425 | sand relative density [%] 426 | 427 | Returns 428 | ------- 429 | dict 430 | dictionary of depth variation functions with keys['Hb_k','Hb_n','Hb_X', 'Hb_Y'] 431 | accepting one input argument, the depth from ground (strictly positive) [m] 432 | """ 433 | Dr = Dr / 100 434 | 435 | # normalized initial stiffness 436 | k_h1 = 6.505 - 2.985 * Dr 437 | k_h2 = -0.007969 - 0.4299 * Dr 438 | k_func = lambda x: max(0.001, k_h1 + k_h2 * L / D) 439 | 440 | # normalized curvature 441 | n_h1 = 0.8793 442 | n_h2 = -0.03150 443 | n_func = lambda x: min(0.999, max(0.0, n_h1 + n_h2 * L / D)) 444 | 445 | # normalized peak resistance 446 | p_u1 = 0.4038 447 | p_u2 = 0.04812 448 | Y_func = lambda x: max(0.001, p_u1 + p_u2 * L / D) 449 | 450 | # normalized displacement at peak resistance 451 | X_func = lambda x: 235.7 452 | 453 | return {"Hb_k": k_func, "Hb_n": n_func, "Hb_X": X_func, "Hb_Y": Y_func} 454 | 455 | @staticmethod 456 | def dunkirk_sand_Mb_pisa_norm_param(D: float, L: float, Dr: float): 457 | """returns the depth variation functions for the 4 normalized parameters 458 | of the dunkirk_sand base moment spring conic formulation as per [BTZA20]_. 459 | 460 | Parameters 461 | ---------- 462 | D : float 463 | pile diameter [m] 464 | L : float 465 | pile embedment length [m] 466 | Dr : float 467 | sand relative density [%] 468 | 469 | Returns 470 | ------- 471 | dict 472 | dictionary of depth variation functions with keys['Mb_k','Mb_n','Mb_X', 'Mb_Y'] 473 | accepting one input argument, the depth from ground (strictly positive) [m] 474 | """ 475 | Dr = Dr / 100 476 | 477 | # normalized initial stiffness 478 | k_func = lambda x: 0.3515 479 | 480 | # normalized curvature 481 | n_h1 = 0.8793 482 | n_h2 = -0.03150 483 | n_func = lambda x: min(0.999, max(0.0, 0.3 + 0.4986 * Dr)) 484 | 485 | # normalized peak resistance 486 | m_u1 = 0.09981 + 0.3710 * Dr 487 | m_u2 = 0.01998 - 0.09041 * Dr 488 | Y_func = lambda x: max(0.001, m_u1 + m_u2 * L / D) 489 | 490 | # normalized displacement at peak resistance 491 | X_func = lambda x: 44.89 492 | 493 | return {"Mb_k": k_func, "Mb_n": n_func, "Mb_X": X_func, "Mb_Y": Y_func} 494 | 495 | 496 | class InitialSubgradeReaction: 497 | """This class stores functions that calculate the initial 498 | subgrade modulus of soil reaction curves. 499 | 500 | This class includes functions to calculate the initial subgrade reaction of py curves for sand. 501 | 502 | Example 503 | ------- 504 | 505 | .. plot:: 506 | 507 | # imports 508 | from matplotlib import pyplot as plt 509 | from openpile.utils.py_curves import api_sand 510 | from openpile.utils.hooks import InitialSubgradeReaction 511 | 512 | # sand parameters 513 | settings = {'phi':35,'X':5,'sig':50,'D':4, 'below_water_table':False} 514 | 515 | # create a plot 516 | _, ax = plt.subplots() 517 | ax.set_title("Varying the initial subgrade reaction modulus") 518 | # plot API sand 519 | ax.plot(*api_sand(**settings,), label='API sand') 520 | # plot API sand with Kallehave modification 521 | ax.plot(*api_sand(**settings, k=InitialSubgradeReaction.kallehave_sand(**settings)), label="Kallehave sand") 522 | ax.plot(*api_sand(**settings, k=InitialSubgradeReaction.sørensen2010_sand(**settings)), label="Sørensen sand") 523 | ax.legend() 524 | """ 525 | 526 | @staticmethod 527 | def api_sand(phi: float, below_water_table: bool, *args, **kwargs): 528 | """Calculates the initial subgrade modulus 'k' in the API sand p-y curve. 529 | The value calculated here is based on a visual fit. 530 | 531 | Parameters 532 | ---------- 533 | phi : float 534 | internal angle of friction in degrees 535 | below_water_table : bool 536 | whether the curve is below or above the water table 537 | 538 | Returns 539 | ------- 540 | float 541 | initial subgrade modulus [kN/m^3] 542 | """ 543 | if below_water_table: 544 | return max((0.1978 * phi**2 - 10.232 * phi + 136.82) * 1000, 5400) 545 | else: 546 | return max((0.2153 * phi**2 - 8.232 * phi + 63.657) * 1000, 5400) 547 | 548 | @staticmethod 549 | def kallehave_sand(phi: float, below_water_table: bool, X: float, D: float, *args, **kwargs): 550 | """Calculates the initial subgrade modulus based on modification of the API sand p-y curve, presented in #REF. 551 | 552 | Parameters 553 | ---------- 554 | phi : float 555 | internal angle of friction in degrees 556 | below_water_table : bool 557 | whether the curve is below or above the water table 558 | X : float 559 | depth from the ground surface [m] 560 | D : float 561 | pile diameter [m] 562 | 563 | Returns 564 | ------- 565 | float 566 | initial subgrade modulus [kN/m^3] 567 | """ 568 | return ( 569 | InitialSubgradeReaction.api_sand(phi, below_water_table) 570 | * (X / 2.5) ** 0.6 571 | * (D / 0.61) ** 0.5 572 | ) 573 | 574 | @staticmethod 575 | def sørensen2010_sand(phi: float, X: float, D: float, *args, **kwargs): 576 | """Calculates the initial subgrade modulus based on modification of the API sand p-y curve, 577 | presented in [SøIA10]_. 578 | 579 | Parameters 580 | ---------- 581 | phi : float 582 | internal angle of friction in degrees 583 | below_water_table : bool 584 | whether the curve is below or above the water table 585 | X : float 586 | depth from the ground surface [m] 587 | D : float 588 | pile diameter [m] 589 | 590 | Returns 591 | ------- 592 | float 593 | initial subgrade modulus [kN/m^3] 594 | """ 595 | Dref = 1.0 596 | Xref = 1.0 597 | X = max(X, 0.01) 598 | return 1 / X * 50e3 * (X / Xref) ** 0.6 * (D / Dref) ** 0.5 * m.radians(phi) ** 3.6 599 | -------------------------------------------------------------------------------- /src/openpile/utils/misc.py: -------------------------------------------------------------------------------- 1 | # Import libraries 2 | import math as m 3 | import numpy as np 4 | from numba import njit 5 | 6 | # maximum resistance values 7 | @njit(cache=True) 8 | def _Qmax_api_clay( 9 | Su: float, 10 | ) -> float: 11 | # Unit end-bearing [kPa] 12 | return 9 * Su 13 | 14 | 15 | @njit(cache=True) 16 | def _Qmax_api_sand( 17 | sig: float, 18 | delta: float, 19 | ) -> float: 20 | # important variables 21 | delta_table = np.array([0, 15, 20, 25, 30, 35, 100], dtype=np.float32) 22 | Nq_table = np.array([8, 8, 12, 20, 40, 50, 50], dtype=np.float32) 23 | Qmax_table = np.array([1900, 1900, 2900, 4800, 9600, 12000, 12000], dtype=np.float32) 24 | 25 | Nq = np.interp(delta, delta_table, Nq_table) 26 | Qmax = np.interp(delta, delta_table, Qmax_table) 27 | 28 | # Unit end-bearing [kPa] 29 | return min(Qmax, sig * Nq) 30 | 31 | 32 | @njit(cache=True) 33 | def _fmax_api_clay( 34 | sig: float, 35 | Su: float, 36 | alpha_limit: float, 37 | ) -> float: 38 | """Creates the maximum skin friction. 39 | 40 | The methdology follows the API clay method of axial capacity found in . 41 | 42 | Parameters 43 | ---------- 44 | sig : float 45 | vertical effcitve stress in kPa. 46 | Su : float 47 | undrained shear strength in kPa. 48 | alpha_limit : float 49 | limit value for the skin friction normalized to undrained shear strength. 50 | 51 | 52 | Returns 53 | ------- 54 | float 55 | unit skin friction in kPa. 56 | """ 57 | 58 | # important variables 59 | if sig == 0.0: 60 | psi = Su / 0.001 61 | else: 62 | psi = Su / sig 63 | 64 | if psi > 1.0: 65 | alpha = min(0.5 * psi ** (-0.25), alpha_limit) 66 | else: 67 | alpha = min(0.5 * psi ** (-0.5), alpha_limit) 68 | 69 | # Unit skin friction [kPa] 70 | return alpha * Su 71 | 72 | 73 | # SPRING FUNCTIONS -------------------------------------------- 74 | 75 | # API sand function 76 | @njit(cache=True) 77 | def _fmax_api_sand( 78 | sig: float, 79 | delta: float, 80 | K: float = 0.8, 81 | ) -> float: 82 | """Creates the maximum skin friction. 83 | 84 | The methdology follows the API sand method of axial capacity found in . 85 | 86 | Parameters 87 | ---------- 88 | sig : float 89 | vertical effcitve stress in kPa. 90 | delta: float 91 | interface friction angle in degrees 92 | K: float 93 | coefficient of lateral pressure. 94 | (0.8 for open-ended piles and 1.0 for cloased-ended) 95 | 96 | Returns 97 | ------- 98 | float 99 | unit skin friction in kPa. 100 | """ 101 | 102 | # important variables 103 | delta_table = np.array([0, 15, 20, 25, 30, 35, 100], dtype=np.float32) 104 | fs_max_table = np.array([47.8, 47.8, 67, 81.3, 95.7, 114.8, 114.8], dtype=np.float32) 105 | 106 | # limit unit skin friction according to API ref page 59 107 | fs_max = np.interp(delta, delta_table, fs_max_table) 108 | 109 | # Unit skin friction [kPa] 110 | return min(fs_max, K * sig * m.tan(delta * m.pi / 180.0)) 111 | -------------------------------------------------------------------------------- /src/openpile/utils/mt_curves.py: -------------------------------------------------------------------------------- 1 | """ 2 | `mt_curves` module 3 | ------------------ 4 | 5 | """ 6 | 7 | # Import libraries 8 | import math as m 9 | import numpy as np 10 | from numba import njit, prange 11 | from random import random 12 | 13 | from openpile.core.misc import conic 14 | 15 | # SPRING FUNCTIONS -------------------------------------------- 16 | @njit(cache=True) 17 | def bothkennar_clay( 18 | X: float, 19 | Su: float, 20 | G0: float, 21 | D: float, 22 | output_length: int = 20, 23 | ): 24 | """ 25 | Create the rotational springs from the PISA clay formulation 26 | published by Burd et al 2020 (see [BABH20]_) and calibrated based on Bothkennar clay 27 | response (a normally consolidated soft clay). 28 | 29 | Parameters 30 | ---------- 31 | X : float 32 | Depth below ground level [unit: m] 33 | Su : float 34 | Undrained shear strength [unit: kPa] 35 | G0 : float 36 | Small-strain shear modulus [unit: kPa] 37 | D : float 38 | Pile diameter [unit: m] 39 | output_length : int, optional 40 | Number of datapoints in the curve, by default 20 41 | 42 | Returns 43 | ------- 44 | 1darray 45 | t vector of length [unit: rad] 46 | 1darray 47 | m vector [unit: kN] 48 | 49 | """ 50 | 51 | # Bothkennar clay parameters 52 | k_m1 = 1.698 53 | k_m2 = -0.1576 54 | n_m1 = 0.00 55 | n_m2 = 0.00 56 | m_m1 = 0.4862 57 | m_m2 = -0.05674 58 | 59 | # Depth variation parameters 60 | k = k_m1 + k_m2 * X / D 61 | n = n_m1 + n_m2 * X / D 62 | m_max = m_m1 + m_m2 * X / D 63 | psi_max = m_max / k 64 | 65 | # calculate normsalised conic function 66 | t, m = conic(psi_max, n, k, m_max, output_length) 67 | 68 | # return non-normalised curve 69 | return t * (Su / G0), m * (Su * D**2) 70 | 71 | 72 | @njit(cache=True) 73 | def cowden_clay( 74 | X: float, 75 | Su: float, 76 | G0: float, 77 | D: float, 78 | output_length: int = 20, 79 | ): 80 | """ 81 | Create the rotational springs from the PISA clay formulation 82 | published by Byrne et al 2020 (see [BHBG20]_) and calibrated based pile 83 | load tests at Cowden (north east coast of England). 84 | 85 | Parameters 86 | ---------- 87 | X : float 88 | Depth below ground level [unit: m] 89 | Su : float 90 | Undrained shear strength [unit: kPa] 91 | G0 : float 92 | Small-strain shear modulus [unit: kPa] 93 | D : float 94 | Pile diameter [unit: m] 95 | output_length : int, optional 96 | Number of datapoints in the curve, by default 20 97 | 98 | Returns 99 | ------- 100 | 1darray 101 | t vector of length [unit: rad] 102 | 1darray 103 | m vector [unit: kN] 104 | 105 | """ 106 | 107 | # Cowden clay parameters 108 | k_m1 = 1.420 109 | k_m2 = -0.09643 110 | n_m1 = 0.00 111 | n_m2 = 0.00 112 | m_m1 = 0.2899 113 | m_m2 = -0.04775 114 | 115 | # Depth variation parameters 116 | k = k_m1 + k_m2 * X / D 117 | n = n_m1 + n_m2 * X / D 118 | m_max = m_m1 + m_m2 * X / D 119 | psi_max = m_max / k 120 | 121 | # calculate normsalised conic function 122 | t, m = conic(psi_max, n, k, m_max, output_length) 123 | 124 | # return non-normalised curve 125 | return t * (Su / G0), m * (Su * D**2) 126 | 127 | 128 | @njit(cache=True) 129 | def dunkirk_sand( 130 | sig: float, 131 | X: float, 132 | Dr: float, 133 | G0: float, 134 | p: float, 135 | D: float, 136 | L: float, 137 | output_length: int = 20, 138 | ): 139 | """ 140 | Create the rotational springs from the PISA sand 141 | formulation published by Burd et al (2020) (see [BTZA20]_). 142 | Also called the General Dunkirk Sand Model (GDSM) 143 | 144 | Parameters 145 | ---------- 146 | sig : float 147 | vertical/overburden effective stress [unit: kPa] 148 | X : float 149 | Depth below ground level [unit: m] 150 | Dr : float 151 | Sand relative density Value must be between 0 and 100 [unit: -] 152 | G0 : float 153 | Small-strain shear modulus [unit: kPa] 154 | p : float 155 | radial stress computed via p-y curves [unit: kN/m] 156 | D : float 157 | Pile diameter [unit: m] 158 | L : float 159 | Embedded pile length [unit: m] 160 | output_length : int, optional 161 | Number of datapoints in the curve, by default 20 162 | 163 | Returns 164 | ------- 165 | 1darray 166 | t vector of length [unit: rad] 167 | 1darray 168 | m vector [unit: kN] 169 | """ 170 | # correct relative density for decimal value 171 | Dr = Dr / 100 172 | 173 | # correct p value 174 | p = abs(p) 175 | 176 | # Generalised Dunkirk Sand Model parameters 177 | k_m1 = 17.00 178 | k_m2 = 0.00 179 | n_m = 0.0 180 | m_u1 = 0.2605 181 | m_u2 = -0.1989 + 0.2019 * Dr 182 | 183 | # Depth variation parameters 184 | k = k_m1 + k_m2 * X / D 185 | n = n_m 186 | m_max = m_u1 + m_u2 * X / L 187 | psi_max = m_max / k 188 | 189 | # calculate normsalised conic function 190 | t, m = conic(psi_max, n, k, m_max, output_length) 191 | 192 | # return non-normalised curve 193 | return t * (sig / G0), m * (p * D) 194 | 195 | 196 | @njit(cache=True) 197 | def custom_pisa_sand( 198 | sig: float, 199 | G0: float, 200 | p: float, 201 | D: float, 202 | X_ult: float, 203 | n: float, 204 | k: float, 205 | Y_ult: float, 206 | output_length: int = 20, 207 | ): 208 | """Creates a rotational spring with the PISA sand formulation and custom user inputs. 209 | 210 | Parameters 211 | ---------- 212 | sig : float 213 | vertical/overburden effective stress [unit: kPa] 214 | G0 : float 215 | Small-strain shear modulus [unit: kPa] 216 | p : float 217 | Radial stress computed via p-y curves [unit: kN/m] 218 | D : float 219 | Pile diameter [m] 220 | X_ult : float 221 | Normalized displacement at maximum strength 222 | k : float 223 | Normalized stiffness parameter 224 | n : float 225 | Normalized curvature parameter, must be between 0 and 1 226 | Y_ult : float 227 | Normalized maximum strength parameter 228 | output_length : int, optional 229 | Number of datapoints in the curve, by default 20 230 | 231 | Returns 232 | ------- 233 | 1darray 234 | y vector [unit: m] 235 | 1darray 236 | p vector [unit: kN/m] 237 | """ 238 | 239 | # calculate normsalised conic function 240 | x, y = conic(X_ult, n, k, Y_ult, output_length) 241 | 242 | # return non-normalised curve 243 | return x * (sig / G0), y * (p * D) 244 | 245 | 246 | @njit(cache=True) 247 | def custom_pisa_clay( 248 | Su: float, 249 | G0: float, 250 | D: float, 251 | X_ult: float, 252 | n: float, 253 | k: float, 254 | Y_ult: float, 255 | output_length: int = 20, 256 | ): 257 | """ 258 | Creates a rotational spring with the PISA clay formulation and custom user inputs. 259 | 260 | 261 | Parameters 262 | ---------- 263 | Su : float 264 | Undrained shear strength [unit: kPa] 265 | G0 : float 266 | Small-strain shear modulus [unit: kPa] 267 | D : float 268 | Pile diameter [unit: m] 269 | X_ult : float 270 | Normalized displacement at maximum strength 271 | k : float 272 | Normalized stiffness parameter 273 | n : float 274 | Normalized curvature parameter, must be between 0 and 1 275 | Y_ult : float 276 | Normalized maximum strength parameter 277 | output_length : int, optional 278 | Number of datapoints in the curve, by default 20 279 | 280 | Returns 281 | ------- 282 | 1darray 283 | y vector [unit: m] 284 | 1darray 285 | p vector [unit: kN/m] 286 | 287 | """ 288 | # calculate normsalised conic function 289 | x, y = conic(X_ult, n, k, Y_ult, output_length) 290 | 291 | # return non-normalised curve 292 | return x * (Su / G0), y * (Su * D**2) 293 | -------------------------------------------------------------------------------- /src/openpile/utils/qz_curves.py: -------------------------------------------------------------------------------- 1 | """ 2 | `qz_curves` module 3 | ------------------ 4 | 5 | """ 6 | 7 | # Import libraries 8 | import math as m 9 | import numpy as np 10 | from numba import njit, prange 11 | from random import random 12 | 13 | from openpile.utils.misc import _Qmax_api_clay, _Qmax_api_sand 14 | 15 | # SPRING FUNCTIONS -------------------------------------------- 16 | 17 | 18 | # API clay Q-z function 19 | def _backbone_api( 20 | output_length: int = 15, 21 | ): 22 | """ 23 | Creates the API Q.z curve backbone from relevant input. 24 | 25 | Parameters 26 | ---------- 27 | output_length : int, optional 28 | Number of discrete point along the springs, cannot be lower than 15, by default 15 29 | 30 | Returns 31 | ------- 32 | numpy 1darray 33 | z vector [unit: m] 34 | numpy 1darray 35 | Q vector [unit: kPa] 36 | """ 37 | # cannot have less than 8 38 | if output_length < 15: 39 | output_length = 15 40 | 41 | # piecewise function 42 | zlist = [ 43 | -0.2, 44 | -0.15, 45 | -0.1, 46 | -0.073, 47 | -0.042, 48 | -0.013, 49 | -0.002, 50 | 0.0, 51 | 0.002, 52 | 0.013, 53 | 0.042, 54 | 0.073, 55 | 0.100, 56 | 0.15, 57 | 0.200, 58 | ] 59 | Qlist = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.25, 0.50, 0.75, 0.90, 1.00, 1.00, 1.00] 60 | 61 | # determine z vector 62 | z = np.array(zlist, dtype=np.float32) 63 | 64 | # define t vector 65 | Q = np.array(Qlist, dtype=np.float32) 66 | 67 | add_values = output_length - len(z) 68 | add_z_values = np.zeros((add_values), dtype=np.float32) 69 | add_Q_values = np.zeros((add_values), dtype=np.float32) 70 | 71 | for i in range(add_values): 72 | add_z_values[i] = 0.1 + random() * 0.1 73 | add_Q_values[i] = 1.0 74 | 75 | z = np.append(z, add_z_values) 76 | Q = np.append(Q, add_Q_values) 77 | 78 | z = np.sort(z) 79 | z_id_sorted = np.argsort(z) 80 | 81 | Q = Q[z_id_sorted] 82 | 83 | return z, Q 84 | 85 | 86 | # API clay Q-z function 87 | def api_clay( 88 | Su: float, 89 | D: float, 90 | output_length: int = 15, 91 | ): 92 | r""" 93 | Creates the API clay Q.z curve from relevant input. 94 | 95 | Parameters 96 | ---------- 97 | Su : float 98 | Undrained shear strength [unit: kPa] 99 | D: float 100 | Pile diameter [unit: m] 101 | output_length : int, optional 102 | Number of discrete point along the springs, cannot be lower than 15, by default 15 103 | 104 | Returns 105 | ------- 106 | numpy 1darray 107 | z vector [unit: m] 108 | numpy 1darray 109 | Q vector [unit: kPa] 110 | 111 | Notes 112 | ----- 113 | 114 | The maximum resistance is calculated as follows: 115 | 116 | * API clay: :math:`Q_{max} = 9 S_u` 117 | 118 | where :math:`S_u` is the clay undrained shear strength. 119 | 120 | 121 | The backbone curve is computed via the piecewise formulation presented in [API2000]_. 122 | """ 123 | 124 | # unit toe reistance [kPa] 125 | f = _Qmax_api_clay(Su) 126 | 127 | # call backbone curve 128 | z, Q = _backbone_api(output_length) 129 | 130 | return z * D, Q * f 131 | 132 | 133 | # API sand function 134 | def api_sand( 135 | sig: float, 136 | delta: float, 137 | D: float, 138 | output_length: int = 15, 139 | ): 140 | r""" 141 | Creates the API sand Q.z curve from relevant input. 142 | 143 | Parameters 144 | ---------- 145 | sig : float 146 | Vertical effective stress [unit: kPa] 147 | delta : float 148 | interface friction angle [unit: degrees] 149 | D : float 150 | Pile diameter [unit: m] 151 | output_length : int, optional 152 | Number of discrete point along the springs, cannot be lower than 15, by default 15 153 | 154 | Returns 155 | ------- 156 | numpy 1darray 157 | z vector [unit: m] 158 | numpy 1darray 159 | Q vector [unit: kPa] 160 | 161 | Notes 162 | ----- 163 | 164 | The maximum resistance is calculated as follows: 165 | 166 | * API sand: :math:`Q_{max} = N_q \sigma^\prime_v` 167 | 168 | where :math:`\sigma_v^\prime` is the overburden effective stress and :math:`N_q` is 169 | the end-bearing factor depending on the interface friction angle :math:`\varphi`, see below table. 170 | 171 | +---------------------------+------+------+------+------+-------+ 172 | | :math:`\varphi` [degrees] | 15.0 | 20.0 | 25.0 | 30.0 | 35.0 | 173 | +---------------------------+------+------+------+------+-------+ 174 | | :math:`N_q` [kPa] | 8.0 | 12.0 | 20.0 | 40.0 | 50.0 | 175 | +---------------------------+------+------+------+------+-------+ 176 | | :math:`Q_{max}` [kPa] | 1900 | 2900 | 4800 | 9600 | 12000 | 177 | +---------------------------+------+------+------+------+-------+ 178 | 179 | 180 | The backbone curve is computed via the piecewise formulation presented in [API2000]_. 181 | """ 182 | 183 | # max tip resistance 184 | f = _Qmax_api_sand(sig, delta) 185 | 186 | # call backbone curve 187 | z, Q = _backbone_api(output_length) 188 | 189 | return z * D, Q * f 190 | -------------------------------------------------------------------------------- /src/openpile/utils/tz_curves.py: -------------------------------------------------------------------------------- 1 | """ 2 | `tz_curves` module 3 | ------------------ 4 | 5 | 6 | """ 7 | 8 | # Import libraries 9 | from openpile.utils.misc import _fmax_api_clay, _fmax_api_sand 10 | 11 | import math as m 12 | import numpy as np 13 | from numba import njit, prange 14 | from random import random 15 | 16 | # Kraft et al (1989) formulation aid to ease up implementation 17 | def kraft_modification( 18 | fmax: float, 19 | D: float, 20 | G0: float, 21 | residual: float = 1.0, 22 | tensile_factor: float = 1.0, 23 | RF: float = 0.9, 24 | zif: float = 10.0, 25 | output_length: int = 15, 26 | ): 27 | """ 28 | Creates the t-z curve from relevant input with the Kraft et al (1981) formulation. 29 | 30 | Parameters 31 | ---------- 32 | fmax: float 33 | unit skin friction [unit: kPa] 34 | D: float 35 | Pile diameter [unit: m] 36 | G0: float 37 | small-strain stiffness [unit: kPa] 38 | residual: float 39 | residual strength after peak strength 40 | tensile_factor: float 41 | strength factor for negative values of the curve 42 | RF: float 43 | curve fitting factor as per Kraft et al. (1981), by default 0.9 44 | zif: float 45 | dimensionless zone of influence as per Kraft et al (1981) that corresponds to the radius of the zone of influence divided by the pile radius, by default 10.0 46 | output_length : int, optional 47 | Number of discrete point along the springs, cannot be lower than 15, by default 15 48 | 49 | Returns 50 | ------- 51 | numpy 1darray 52 | t vector [unit: kPa] 53 | numpy 1darray 54 | z vector [unit: m] 55 | 56 | """ 57 | 58 | if output_length < 15: 59 | output_length = 15 60 | if output_length % 2 == 0: 61 | output_length += 1 62 | 63 | half_length = round(output_length / 2) - 2 64 | 65 | # define t till tmax 66 | tpos = np.linspace(0, fmax, half_length) 67 | # define z till zmax 68 | zpos = tpos * 0.5 * D / G0 * np.log((zif - RF * tpos / fmax) / (1 - RF * tpos / fmax)) 69 | # define z where t = tmax, a.k.a zmax here 70 | zmax = fmax * D / (2 * G0) * m.log((zif - RF) / (1 - RF)) 71 | # define z where z=tres, which is zmax + 5mm 72 | zres = zmax + 0.005 73 | 74 | z = np.append(zpos, [zres, zres + 0.005]) 75 | t = np.append(tpos, [residual * fmax, residual * fmax]) 76 | 77 | return np.append(-z[-1:0:-1], z), np.append(-t[-1:0:-1] * tensile_factor, t) 78 | 79 | 80 | # API clay backbone fct 81 | def backbone_api_clay( 82 | residual: float = 0.9, 83 | tensile_factor: float = 1.0, 84 | output_length: int = 15, 85 | ): 86 | """ 87 | Creates the API clay t-z curve backbone (i.e. normalized with strength and diameter) from relevant input as per [API2000]_. 88 | 89 | Parameters 90 | ---------- 91 | D: float 92 | Pile width [unit: m] 93 | residual: float 94 | residual strength after peak strength, according to API-RP-2A, 95 | this value is between 0.7 and 0.9, default to 0.9 96 | tensile_factor: float 97 | strength factor for negative values of the curve 98 | output_length : int, optional 99 | Number of discrete point along the springs, cannot be lower than 15, by default 15 100 | 101 | Returns 102 | ------- 103 | numpy 1darray 104 | t vector [unit: kPa] 105 | numpy 1darray 106 | z vector [unit: m] 107 | 108 | 109 | """ 110 | # cannot have less than 15 111 | if output_length < 15: 112 | output_length = 15 113 | 114 | # piecewise function 115 | zlist = [0.0, 0.0016, 0.0031, 0.0057, 0.0080, 0.0100, 0.0200, 0.0300] 116 | tlist = [0.0, 0.3, 0.5, 0.75, 0.90, 1.00, residual, residual] 117 | 118 | # determine z vector 119 | z = np.array(zlist, dtype=np.float32) 120 | z = np.concatenate((-z[-1:0:-1], z)) 121 | # define t vector 122 | t = np.array(tlist, dtype=np.float32) 123 | t = np.concatenate((-tensile_factor * t[-1:0:-1], t)) 124 | 125 | add_values = output_length - len(z) 126 | add_z_values = np.zeros((add_values), dtype=np.float32) 127 | add_t_values = np.zeros((add_values), dtype=np.float32) 128 | 129 | for i in range(add_values): 130 | add_z_values[i] = 0.02 + random() * 0.01 131 | add_t_values[i] = residual 132 | 133 | z = np.append(z, add_z_values) 134 | t = np.append(t, add_t_values) 135 | 136 | z = np.sort(z) 137 | z_id_sorted = np.argsort(z) 138 | 139 | t = t[z_id_sorted] 140 | 141 | return z, t 142 | 143 | 144 | # API clay function 145 | def api_clay( 146 | sig: float, 147 | Su: float, 148 | D: float, 149 | alpha_limit: float = 1.0, 150 | residual: float = 0.9, 151 | tensile_factor: float = 1.0, 152 | output_length: int = 15, 153 | ): 154 | r""" 155 | Creates the API clay t-z curve from relevant input as per [API2000]_. 156 | 157 | Parameters 158 | ---------- 159 | sig: float 160 | Vertical effective stress [unit: kPa] 161 | Su : float 162 | Undrained shear strength [unit: kPa] 163 | D: float 164 | Pile width [unit: m] 165 | alpha_limit: float 166 | maximum value of the alpha parameter, default to 1.0 167 | residual: float 168 | residual strength after peak strength, according to API-RP-2A, 169 | this value is between 0.7 and 0.9, default to 0.9 170 | tensile_factor: float 171 | strength factor for negative values of the curve 172 | output_length : int, optional 173 | Number of discrete point along the springs, cannot be lower than 15, by default 15 174 | 175 | Returns 176 | ------- 177 | numpy 1darray 178 | t vector [unit: kPa] 179 | numpy 1darray 180 | z vector [unit: m] 181 | 182 | Notes 183 | ----- 184 | 185 | The backbone curve is computed via the piecewise formulation by [API2000]_ or through 186 | Kraft's formulation that captures small-strain characteristics of the soil [KrRK81]_ in the backbone curve. 187 | 188 | .. note:: 189 | Kraft's formulation is accessed by the user by stipulating the small-strain shear 190 | stiffness of the soil, :math:`G_0` 191 | 192 | 193 | The API guidelines describe the axial soil springs in a manner that accounts for the undrained shear strength of the clay. 194 | The springs are characterized as follows: 195 | 196 | 1. **Unit Skin Friction** :math:`f_s`: For clay, this is based on the undrained shear strength :math:`S_u` of the soil and a factor that accounts for the adhesion between the clay and the pile. 197 | 198 | .. math:: 199 | 200 | f_s = \alpha \cdot S_u < f_{s,\text{max}} 201 | 202 | where: 203 | 204 | - :math:`\alpha` is the adhesion factor, which depends on the type of clay and the pile material. 205 | It typically ranges from 0.5 to 1.0 for soft clays and 0.3 to 0.6 for stiff clays. 206 | As per the API guidelines, this adhesion factor can be calculated as: 207 | - :math:`S_u` is the undrained shear strength of the clay. 208 | - Limit Skin Friction :math:`f_{s,\text{max}}` is the maximum unit skin friction for clay, 209 | which can be directly related to the undrained shear strength and the adhesion factor. 210 | In general, the limit skin friction is set to the undrained shear strength. 211 | 212 | 2. A backbone curve computed via the piecewise formulation seen in [API2000]_. 213 | 214 | """ 215 | # cannot have less than 15 216 | if output_length < 15: 217 | output_length = 15 218 | 219 | # unit skin friction 220 | f = _fmax_api_clay(sig, Su, alpha_limit) 221 | 222 | # call backbone curve 223 | z, t = backbone_api_clay(residual, tensile_factor, output_length) 224 | 225 | return z * D, t * f 226 | 227 | 228 | @njit(cache=True) 229 | def api_clay_kraft( 230 | sig: float, 231 | Su: float, 232 | D: float, 233 | G0: float, 234 | alpha_limit: float = 1.0, 235 | residual: float = 1.0, 236 | tensile_factor: float = 1.0, 237 | RF: float = 0.9, 238 | zif: float = 10.0, 239 | output_length: int = 15, 240 | ): 241 | r""" 242 | Creates the API clay t-z curve (see [API2000]_) with the Kraft et al (1981) formulation (see [KrRK81]_). 243 | 244 | Parameters 245 | ---------- 246 | sig: float 247 | Vertical effective stress [unit: kPa] 248 | Su : float 249 | Undrained shear strength [unit: kPa] 250 | D: float 251 | Pile diameter [unit: m] 252 | G0: float 253 | small-strain stiffness [unit: kPa] 254 | alpha_limit: float 255 | limit of the alpha parameter, by default 1.0 256 | residual: float 257 | residual strength after peak strength, by default 1.0 258 | tensile_factor: float 259 | strength factor for negative values of the curve, by default 1.0 260 | RF: float 261 | curve fitting factor as per Kraft et al. (1981), by default 0.9 262 | zif: float 263 | dimensionless zone of influence as per Kraft et al (1981) that corresponds to the radius of the zone of influence divided by the pile radius, by default 10.0 264 | output_length : int, optional 265 | Number of discrete point along the springs, cannot be lower than 15, by default 15 266 | 267 | Returns 268 | ------- 269 | numpy 1darray 270 | t vector [unit: kPa] 271 | numpy 1darray 272 | z vector [unit: m] 273 | 274 | See also 275 | -------- 276 | :py:func:`openpile.utils.tz_curves.api_clay` 277 | 278 | Notes 279 | ----- 280 | 281 | The backbone curve is computed via the piecewise formulation 282 | by [API2000]_ or through 283 | Kraft's formulation that captures small-strain 284 | characteristics of the soil [KrRK81]_ in the backbone curve. 285 | 286 | .. note:: 287 | Kraft's formulation is accessed by the user by stipulating the small-strain shear 288 | stiffness of the soil, :math:`G_0` 289 | 290 | 291 | For clay, the API guidelines describe the axial soil springs in a manner that accounts for the undrained shear strength of the clay. 292 | The springs are characterized as follows: 293 | 294 | 1. **Unit Skin Friction** :math:`f_s`: For clay, this is based on the undrained shear strength :math:`S_u` of the soil and a factor that accounts for the adhesion between the clay and the pile. 295 | 296 | .. math:: 297 | 298 | f_s = \alpha \cdot S_u < f_{s,\text{max}} 299 | 300 | where: 301 | 302 | - :math:`\alpha` is the adhesion factor, which depends on the type of clay and the pile material. 303 | It typically ranges from 0.5 to 1.0 for soft clays and 0.3 to 0.6 for stiff clays. 304 | As per the API guidelines, this adhesion factor can be calculated as: 305 | - :math:`S_u` is the undrained shear strength of the clay. 306 | - Limit Skin Friction :math:`f_{s,\text{max}}` is the maximum unit skin friction for clay, 307 | which can be directly related to the undrained shear strength and the adhesion factor. 308 | In general, the limit skin friction is set to the undrained shear strength. 309 | 310 | 2. A backbone curve computed via the piecewise formulation seen in [API2000]_. 311 | 312 | """ 313 | 314 | return kraft_modification( 315 | _fmax_api_clay(sig, Su, alpha_limit), 316 | D, 317 | G0, 318 | residual, 319 | tensile_factor, 320 | RF, 321 | zif, 322 | output_length, 323 | ) 324 | 325 | 326 | # API sand function 327 | def backbone_api_sand( 328 | tensile_factor: float = 1.0, 329 | output_length: int = 7, 330 | ): 331 | """ 332 | Creates the API sand t-z curve (see [API2000]_). 333 | 334 | Parameters 335 | ---------- 336 | tensile_factor: float 337 | strength factor for negative values of the curve 338 | output_length : int, optional 339 | Number of discrete point along the springs, cannot be lower than 7, by default 7 340 | 341 | Returns 342 | ------- 343 | numpy 1darray 344 | t vector [unit: kPa] 345 | numpy 1darray 346 | z vector [unit: m] 347 | 348 | 349 | """ 350 | # cannot have less than 7 351 | if output_length < 15: 352 | output_length = 15 353 | 354 | # piecewise function 355 | zlist = [0.0, 0.0254, 0.03, 0.032, 0.034, 0.036, 0.038, 0.04] 356 | tlist = [0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0] 357 | 358 | # determine z vector 359 | z = np.array(zlist, dtype=np.float32) 360 | z = np.concatenate((-z[-1:0:-1], z)) 361 | # define t vector 362 | t = np.array(tlist, dtype=np.float32) 363 | t = np.concatenate((-tensile_factor * t[-1:0:-1], t)) 364 | 365 | add_values = output_length - len(z) 366 | add_z_values = np.zeros((add_values), dtype=np.float32) 367 | add_t_values = np.zeros((add_values), dtype=np.float32) 368 | 369 | for i in range(add_values): 370 | add_z_values[i] = 0.038 + random() * 0.01 371 | add_t_values[i] = 1.0 372 | 373 | z = np.append(z, add_z_values) 374 | t = np.append(t, add_t_values) 375 | 376 | z = np.sort(z) 377 | z_id_sorted = np.argsort(z) 378 | 379 | t = t[z_id_sorted] 380 | 381 | return z, t 382 | 383 | 384 | # API sand function 385 | def api_sand( 386 | sig: float, 387 | delta: float, 388 | K: float = 0.8, 389 | tensile_factor: float = 1.0, 390 | output_length: int = 7, 391 | ): 392 | r""" 393 | Creates the API sand t-z curve (see [API2000]_). 394 | 395 | Parameters 396 | ---------- 397 | sig: float 398 | Vertical effective stress [unit: kPa] 399 | delta: float 400 | interface friction angle [unit: degrees] 401 | K: float 402 | coefficient of lateral pressure (0.8 for open-ended piles and 1.0 for cloased-ended) 403 | tensile_factor: float 404 | strength factor for negative values of the curve 405 | output_length : int, optional 406 | Number of discrete point along the springs, cannot be lower than 7, by default 7 407 | 408 | Returns 409 | ------- 410 | numpy 1darray 411 | t vector [unit: kPa] 412 | numpy 1darray 413 | z vector [unit: m] 414 | 415 | Notes 416 | ----- 417 | 418 | The backbone curve is computed via the piecewise formulation 419 | by [API2000]_ or through 420 | Kraft's formulation that captures small-strain 421 | characteristics of the soil [KrRK81]_ in the backbone curve. 422 | 423 | .. note:: 424 | Kraft's formulation is accessed by the user by stipulating the small-strain shear 425 | stiffness of the soil, :math:`G_0` 426 | 427 | The API guidelines provide methods to estimate the resistance offered by sandy soils along the pile. 428 | These springs are defined based on the following considerations: 429 | 430 | 1. **Unit Skin Friction** :math:`f_s`: This is the frictional resistance per unit area along the pile shaft. It depends on the effective overburden pressure and the soil-pile interface properties. 431 | 432 | .. math:: 433 | 434 | f_s = \sigma_v^\prime \cdot K \cdot tan(\delta) < f_{s,\text{max}} 435 | 436 | where: 437 | 438 | - \sigma_v^\prime is the effective vertical stress at the depth considered. 439 | - K is the coefficient of horizontal earth pressure (typically ranges from 0.4 to 1.0 for sands). 440 | - \delta is the angle of friction between the pile and the sand, often taken as a fraction of the soil's internal friction angle :math:`\varphi` (usually :math:`\delta = 0.7 \varphi` to :math:`\varphi`). 441 | - Limit Skin Friction :math:`f_{s,\text{max}}` is the maximum unit skin friction that can be mobilized. It is typically given by empirical correlations or laboratory tests. The following is assumed in OpenPile: 442 | 443 | .. list-table:: Correlation between interface friction angle and shaft friction 444 | :header-rows: 0 445 | 446 | * - :math:`\delta` [degrees] 447 | - 15 448 | - 20 449 | - 25 450 | - 30 451 | - 35 452 | * - :math:`f_{s,\texttt{max}}` [kPa] 453 | - 47.8 454 | - 67 455 | - 81.3 456 | - 95.7 457 | - 114.8 458 | 459 | 2. A backbone curve computed via the piecewise formulation seen in [API2000]_. 460 | 461 | """ 462 | 463 | # unit skin friction 464 | f = _fmax_api_sand(sig, delta, K) 465 | 466 | # call backbone curve 467 | z, t = backbone_api_sand(tensile_factor, output_length) 468 | 469 | return z, t * f 470 | 471 | 472 | @njit(cache=True) 473 | def api_sand_kraft( 474 | sig: float, 475 | delta: float, 476 | D: float, 477 | G0: float, 478 | K: float = 0.8, 479 | residual: float = 1.0, 480 | tensile_factor: float = 1.0, 481 | RF: float = 0.9, 482 | zif: float = 10.0, 483 | output_length: int = 15, 484 | ): 485 | r""" 486 | Creates the API sand t-z curve (see [API2000]_) with the Kraft et al (1981) formulation (see [KrRK81]_). 487 | 488 | Parameters 489 | ---------- 490 | sig: float 491 | Vertical effective stress [unit: kPa] 492 | delta: float 493 | interface friction angle [unit: degrees] 494 | D: float 495 | Pile width [unit: m] 496 | G0: float 497 | small-strain stiffness [unit: kPa] 498 | K: float 499 | coefficient of lateral pressure (0.8 for open-ended piles and 1.0 for cloased-ended), by default 0.8 500 | residual: float 501 | residual strength after peak strength, by default 1.0 502 | tensile_factor: float 503 | strength factor for negative values of the curve, by default 1.0 504 | RF: float 505 | curve fitting factor as per Kraft et al. (1981), by default 0.9 506 | zif: float 507 | dimensionless zone of influence as per Kraft et al (1981) that corresponds to the radius of the zone of influence divided by the pile radius, by default 10.0 508 | output_length : int, optional 509 | Number of discrete point along the springs, cannot be lower than 15, by default 15 510 | 511 | Returns 512 | ------- 513 | numpy 1darray 514 | t vector [unit: kPa] 515 | numpy 1darray 516 | z vector [unit: m] 517 | 518 | See also 519 | -------- 520 | :py:func:`openpile.utils.tz_curves.api_sand` 521 | 522 | Notes 523 | ----- 524 | 525 | The backbone curve is computed via the piecewise formulation 526 | by [API2000]_ or through 527 | Kraft's formulation that captures small-strain 528 | characteristics of the soil [KrRK81]_ in the backbone curve. 529 | 530 | .. note:: 531 | Kraft's formulation is accessed by the user by stipulating the small-strain shear 532 | stiffness of the soil, :math:`G_0` 533 | 534 | The API guidelines provide methods to estimate the resistance offered by sandy soils along the pile. 535 | These springs are defined based on the following considerations: 536 | 537 | 1. **Unit Skin Friction** :math:`f_s`: This is the frictional resistance per unit area along the pile shaft. It depends on the effective overburden pressure and the soil-pile interface properties. 538 | 539 | .. math:: 540 | 541 | f_s = \sigma_v^\prime \cdot K \cdot tan(\delta) < f_{s,\text{max}} 542 | 543 | where: 544 | 545 | - \sigma_v^\prime is the effective vertical stress at the depth considered. 546 | - K is the coefficient of horizontal earth pressure (typically ranges from 0.4 to 1.0 for sands). 547 | - \delta is the angle of friction between the pile and the sand, often taken as a fraction of the soil's internal friction angle :math:`\varphi` (usually :math:`\delta = 0.7 \varphi` to :math:`\varphi`). 548 | - Limit Skin Friction :math:`f_{s,\text{max}}` is the maximum unit skin friction that can be mobilized. It is typically given by empirical correlations or laboratory tests. The following is assumed in OpenPile: 549 | 550 | .. list-table:: Correlation between interface friction angle and shaft friction 551 | :header-rows: 0 552 | 553 | * - :math:`\delta` [degrees] 554 | - 15 555 | - 20 556 | - 25 557 | - 30 558 | - 35 559 | * - :math:`f_{s,\texttt{max}}` [kPa] 560 | - 47.8 561 | - 67 562 | - 81.3 563 | - 95.7 564 | - 114.8 565 | 566 | 2. A backbone curve computed via the piecewise formulation seen in [API2000]_. 567 | 568 | """ 569 | return kraft_modification( 570 | _fmax_api_sand(sig, delta, K), D, G0, residual, tensile_factor, RF, zif, output_length 571 | ) 572 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TchilDill/openpile/fe16b26d01249b37edf9a8f16c72e01081ae8171/test/__init__.py -------------------------------------------------------------------------------- /test/test_hooks.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from openpile.construct import Pile, SoilProfile, Layer, Model 3 | from openpile.soilmodels import API_sand 4 | from openpile.winkler import winkler 5 | from openpile.utils.hooks import durkhop 6 | 7 | import math as m 8 | 9 | 10 | @pytest.fixture 11 | def create_pile(): 12 | return Pile.create_tubular( 13 | name="", top_elevation=0, bottom_elevation=-40, diameter=7, wt=0.050 14 | ) 15 | 16 | 17 | @pytest.fixture 18 | def create_cyclic_soilprofile(): 19 | # Create a 40m deep offshore Soil Profile with a 15m water column 20 | sp = SoilProfile( 21 | name="Offshore Soil Profile", 22 | top_elevation=0, 23 | water_line=15, 24 | layers=[ 25 | Layer( 26 | name="medium dense sand", 27 | top=0, 28 | bottom=-40, 29 | weight=18, 30 | lateral_model=API_sand(phi=33, kind="cyclic"), 31 | ), 32 | ], 33 | ) 34 | return sp 35 | 36 | 37 | @pytest.fixture 38 | def create_static_soilprofile(): 39 | # Create a 40m deep offshore Soil Profile with a 15m water column 40 | sp = SoilProfile( 41 | name="Offshore Soil Profile", 42 | top_elevation=0, 43 | water_line=15, 44 | layers=[ 45 | Layer( 46 | name="medium dense sand", 47 | top=0, 48 | bottom=-40, 49 | weight=18, 50 | lateral_model=API_sand(phi=33, kind="static"), 51 | ), 52 | ], 53 | ) 54 | return sp 55 | 56 | 57 | @pytest.fixture 58 | def create_duhrkop_soilprofile(): 59 | # Create a 40m deep offshore Soil Profile with a 15m water column 60 | sp = SoilProfile( 61 | name="Offshore Soil Profile", 62 | top_elevation=0, 63 | water_line=15, 64 | layers=[ 65 | Layer( 66 | name="medium dense sand", 67 | top=0, 68 | bottom=-40, 69 | weight=18, 70 | lateral_model=API_sand(phi=33, kind="cyclic", p_multiplier=durkhop(D=7, ra=0.3)), 71 | ), 72 | ], 73 | ) 74 | return sp 75 | 76 | 77 | def test_APIcylic_equals_durkhop_ra_03( 78 | create_pile, create_cyclic_soilprofile, create_duhrkop_soilprofile 79 | ): 80 | 81 | # Create Model 1 82 | M1 = Model(name="", pile=create_pile, soil=create_cyclic_soilprofile) 83 | # Apply bottom fixity along x-axis 84 | M1.set_support(elevation=-40, Tz=True) 85 | # Apply axial and lateral loads 86 | M1.set_pointload(elevation=0, Mx=-300e3, Py=10e3) 87 | R1 = winkler(M1) 88 | 89 | # Create Model 2 90 | M2 = Model(name="", pile=create_pile, soil=create_duhrkop_soilprofile) 91 | # Apply bottom fixity along x-axis 92 | M2.set_support(elevation=-40, Tz=True) 93 | # Apply axial and lateral loads 94 | M2.set_pointload(elevation=0, Mx=-300e3, Py=10e3) 95 | R2 = winkler(M2) 96 | 97 | assert m.isclose( 98 | R1.details()["Max. deflection [m]"], R2.details()["Max. deflection [m]"], rel_tol=0.01 99 | ) 100 | 101 | 102 | def test_pisa_custom_sand_py(): 103 | 104 | from openpile.utils.py_curves import custom_pisa_sand, dunkirk_sand 105 | from openpile.utils.hooks import PISA_depth_variation 106 | 107 | # plot original dunkirk sand curve 108 | params = {"sig": 50, "X": 5, "Dr": 75, "G0": 50e3, "D": 6, "L": 20} 109 | y, p = dunkirk_sand(**params) 110 | # load PISA dunkirk sand depth variation functions 111 | funcs = PISA_depth_variation.dunkirk_sand_py_pisa_norm_param(D=6, L=20, Dr=75) 112 | # plot same curve with custom pisa sand curve 113 | params = { 114 | "sig": 50, 115 | "G0": 50e3, 116 | "D": 6, 117 | "X_ult": funcs["py_X"](5), 118 | "Y_ult": funcs["py_Y"](5), 119 | "n": funcs["py_n"](5), 120 | "k": funcs["py_k"](5), 121 | } 122 | y_custom, p_custom = custom_pisa_sand(**params) 123 | 124 | for i in range(len(y)): 125 | assert m.isclose(y[i], y_custom[i]) 126 | assert m.isclose(p[i], p_custom[i]) 127 | 128 | 129 | def test_pisa_custom_sand_mt(): 130 | 131 | from openpile.utils.mt_curves import custom_pisa_sand, dunkirk_sand 132 | from openpile.utils.hooks import PISA_depth_variation 133 | 134 | # plot original dunkirk sand curve 135 | params = {"sig": 50, "X": 5, "Dr": 75, "G0": 50e3, "D": 6, "L": 20, "p": 100} 136 | y, p = dunkirk_sand(**params) 137 | # load PISA dunkirk sand depth variation functions 138 | funcs = PISA_depth_variation.dunkirk_sand_mt_pisa_norm_param(L=20, Dr=75) 139 | # plot same curve with custom pisa sand curve 140 | params = { 141 | "sig": 50, 142 | "G0": 50e3, 143 | "D": 6, 144 | "p": 100, 145 | "X_ult": funcs["mt_X"](5), 146 | "Y_ult": funcs["mt_Y"](5), 147 | "n": funcs["mt_n"](5), 148 | "k": funcs["mt_k"](5), 149 | } 150 | y_custom, p_custom = custom_pisa_sand(**params) 151 | 152 | for i in range(len(y)): 153 | assert m.isclose(y[i], y_custom[i]) 154 | assert m.isclose(p[i], p_custom[i]) 155 | 156 | 157 | def test_pisa_custom_clay_py(): 158 | 159 | from openpile.utils.py_curves import custom_pisa_clay, cowden_clay 160 | from openpile.utils.hooks import PISA_depth_variation 161 | 162 | # plot original dunkirk sand curve 163 | params = {"X": 5, "Su": 75, "G0": 50e3, "D": 6} 164 | y, p = cowden_clay(**params) 165 | # load PISA dunkirk sand depth variation functions 166 | funcs = PISA_depth_variation.cowden_clay_py_pisa_norm_param(D=6) 167 | # plot same curve with custom pisa sand curve 168 | params = { 169 | "Su": 75, 170 | "G0": 50e3, 171 | "D": 6, 172 | "X_ult": funcs["py_X"](5), 173 | "Y_ult": funcs["py_Y"](5), 174 | "n": funcs["py_n"](5), 175 | "k": funcs["py_k"](5), 176 | } 177 | y_custom, p_custom = custom_pisa_clay(**params) 178 | 179 | for i in range(len(y)): 180 | assert m.isclose(y[i], y_custom[i]) 181 | assert m.isclose(p[i], p_custom[i]) 182 | 183 | 184 | def test_pisa_custom_clay_mt(): 185 | 186 | from openpile.utils.mt_curves import custom_pisa_clay, cowden_clay 187 | from openpile.utils.hooks import PISA_depth_variation 188 | 189 | # plot original dunkirk sand curve 190 | params = {"X": 5, "Su": 75, "G0": 50e3, "D": 6} 191 | y, p = cowden_clay(**params) 192 | # load PISA dunkirk sand depth variation functions 193 | funcs = PISA_depth_variation.cowden_clay_mt_pisa_norm_param(D=6) 194 | # plot same curve with custom pisa sand curve 195 | params = { 196 | "Su": 75, 197 | "G0": 50e3, 198 | "D": 6, 199 | "X_ult": funcs["mt_X"](5), 200 | "Y_ult": funcs["mt_Y"](5), 201 | "n": funcs["mt_n"](5), 202 | "k": funcs["mt_k"](5), 203 | } 204 | y_custom, p_custom = custom_pisa_clay(**params) 205 | 206 | for i in range(len(y)): 207 | assert m.isclose(y[i], y_custom[i]) 208 | assert m.isclose(p[i], p_custom[i]) 209 | -------------------------------------------------------------------------------- /test/test_pycurves.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import openpile.utils.py_curves as py 3 | import pytest 4 | import math as m 5 | import numpy as np 6 | 7 | import openpile.utils.py_curves as py 8 | 9 | from openpile.utils.hooks import InitialSubgradeReaction 10 | 11 | 12 | @pytest.fixture 13 | def make_pmax_api_sand(): 14 | def make( 15 | sig: float, 16 | X: float, 17 | phi: float, 18 | D: float, 19 | ): 20 | # Calculate Pmax (regular API) 21 | ## Factors according to Mosher and Dawkins 2008.(regular API) 22 | b = 0.4 23 | Beta = 45 + phi / 2 24 | rad = m.pi / 180 25 | C1 = ( 26 | (b * m.tan(phi * rad) * m.sin(Beta * rad)) 27 | / (m.tan((Beta - phi) * rad) * m.cos((phi / 2) * rad)) 28 | + ((m.tan(Beta * rad)) ** 2 * m.tan((phi / 2) * rad)) / (m.tan((Beta - phi) * rad)) 29 | + b 30 | * m.tan(Beta * rad) 31 | * (m.tan(phi * rad) * m.sin(Beta * rad) - m.tan((phi / 2) * rad)) 32 | ) 33 | C2 = m.tan(Beta * rad) / m.tan((Beta - phi) * rad) - (m.tan((45 - phi / 2) * rad)) ** 2 34 | C3 = b * m.tan(phi * rad) * (m.tan(Beta * rad)) ** 4 + ( 35 | m.tan((45 - phi / 2) * rad) 36 | ) ** 2 * ((m.tan(Beta * rad)) ** 8 - 1) 37 | 38 | ## Pmax for shallow and deep zones (regular API) 39 | return min(C3 * sig * D, C1 * sig * X + C2 * sig * D) 40 | 41 | return make 42 | 43 | 44 | @pytest.fixture 45 | def make_pmax_api_clay(): 46 | def make( 47 | sig: float, 48 | X: float, 49 | Su: float, 50 | D: float, 51 | J: float, 52 | ): 53 | 54 | return min(9 * Su * D, (3 * Su + sig) * D + J * Su * X) 55 | 56 | return make 57 | 58 | 59 | @pytest.mark.parametrize("xsigma", [0, 75, 200]) 60 | @pytest.mark.parametrize("xX", [0.1, 5, 20]) 61 | @pytest.mark.parametrize("xphi", [27, 35, 40]) 62 | @pytest.mark.parametrize("xD", [0.2, 1.5, 5.0, 10.0]) 63 | @pytest.mark.parametrize("xkind", ["static", "cyclic"]) 64 | @pytest.mark.parametrize("xwater", [True, False]) 65 | def test_api_sand(make_pmax_api_sand, xsigma, xX, xphi, xD, xkind, xwater): 66 | # create spring 67 | y, p = py.api_sand(sig=xsigma, X=xX, phi=xphi, D=xD, kind=xkind, below_water_table=xwater) 68 | # helper fct 69 | is_sorted = lambda a: np.all(a[:-1] <= a[1:]) 70 | # check if sorted 71 | assert is_sorted(y) 72 | assert is_sorted(p) 73 | # check if origin is (0,0) 74 | assert p[0] == 0.0 75 | assert y[0] == 0.0 76 | # check pmax 77 | if xkind == "cyclic": 78 | A = 0.9 79 | elif xkind == "static": 80 | A = max(0.9, 3 - 0.8 * xX / xD) 81 | assert m.isclose(p[-1], A * make_pmax_api_sand(xsigma, xX, xphi, xD), rel_tol=0.01, abs_tol=0.1) 82 | 83 | 84 | @pytest.mark.parametrize("xsigma", [0, 75, 200]) 85 | @pytest.mark.parametrize("xX", [0.1, 5, 20]) 86 | @pytest.mark.parametrize("xSu", [0.01, 50, 125, 300]) 87 | @pytest.mark.parametrize("xe50", [0.005, 0.0125, 0.03]) 88 | @pytest.mark.parametrize("xD", [0.2, 1.5, 5.0, 10.0]) 89 | @pytest.mark.parametrize("xJ", [0.25, 0.5]) 90 | @pytest.mark.parametrize("xkind", ["static", "cyclic"]) 91 | def test_api_clay(make_pmax_api_clay, xsigma, xX, xSu, xe50, xD, xJ, xkind): 92 | # create spring 93 | y, p = py.api_clay(sig=xsigma, X=xX, Su=xSu, eps50=xe50, D=xD, J=xJ, kind=xkind) 94 | # helper fct 95 | is_sorted = lambda a: np.all(a[:-1] <= a[1:]) 96 | # check if sorted 97 | assert is_sorted(y) 98 | # check if origin is (0,0) 99 | assert p[0] == 0.0 100 | assert y[0] == 0.0 101 | # check initial stiffness 102 | pmax = make_pmax_api_clay(xsigma, xX, xSu, xD, xJ) 103 | kini = 0.23 * pmax / (0.25 * xe50 * xD) 104 | assert m.isclose((p[1] - p[0]) / (y[1] - y[0]), kini, rel_tol=0.02) 105 | # calculate pmax 106 | # check residual and max p 107 | Xr = max((6 * xD) / (xsigma / xX * xD / xSu + xJ), 2.5 * xD) 108 | 109 | factor = min(1.0, xX / Xr) 110 | 111 | if xkind == "cyclic": 112 | pres = 0.7185 * pmax * factor 113 | pu = 0.7185 * pmax 114 | elif xkind == "static": 115 | pres = pmax 116 | pu = pmax 117 | assert m.isclose(p[-1], pres, rel_tol=0.01, abs_tol=0.1) 118 | assert m.isclose(np.max(p), pu, rel_tol=0.01, abs_tol=0.1) 119 | 120 | 121 | @pytest.mark.parametrize("xEi", [1, 75, 200]) 122 | @pytest.mark.parametrize("xqu", [1, 75, 200]) 123 | @pytest.mark.parametrize("xRQD", [0, 25, 50, 75, 100]) 124 | @pytest.mark.parametrize("xxr", [0, 5, 20]) 125 | @pytest.mark.parametrize("xD", [1, 3, 6]) 126 | def test_reese_weakrock(xEi, xqu, xRQD, xxr, xD): 127 | # create curve 128 | y, p = py.reese_weakrock(Ei=xEi, qu=xqu, RQD=xRQD, xr=xxr, D=xD) 129 | # helper fct 130 | is_sorted = lambda a: np.all(a[:-1] <= a[1:]) 131 | # check if sorted 132 | assert is_sorted(y) 133 | # check if origin is (0,0) 134 | assert p[0] == 0.0 135 | assert y[0] == 0.0 136 | 137 | alpha = 1 - 2 / 3 * xRQD / 100 138 | pu = min(alpha * xqu * xD * (1 + 1.4 * xxr / xD), 5.2 * alpha * xqu * xD) 139 | assert m.isclose(np.max(p), pu, rel_tol=0.01, abs_tol=0.1) 140 | 141 | 142 | def test_get_initial_subgrade_modulus(): 143 | 144 | for phi in [28, 30, 35, 40, 42]: 145 | 146 | y, p = py.api_sand(50, 5, phi, 4) 147 | y1, p1 = py.api_sand(50, 5, phi, 4, k=InitialSubgradeReaction.api_sand(phi, True)) 148 | assert y1[1] == y[1] 149 | assert p1[1] == p[1] 150 | 151 | y, p = py.api_sand(50, 5, phi, 4, below_water_table=False) 152 | y1, p1 = py.api_sand(50, 5, phi, 4, k=InitialSubgradeReaction.api_sand(phi, False)) 153 | assert y1[1] == y[1] 154 | assert p1[1] == p[1] 155 | -------------------------------------------------------------------------------- /test/test_utils_misc.py: -------------------------------------------------------------------------------- 1 | from openpile.core import misc 2 | 3 | 4 | def test_from_list2x_parse_top_bottom(): 5 | # check if integer is passed 6 | t, b = misc.from_list2x_parse_top_bottom(2) 7 | assert t == 2 8 | assert b == 2 9 | # check if integer is passed 10 | t, b = misc.from_list2x_parse_top_bottom(5.0) 11 | assert t == 5.0 12 | assert b == 5.0 13 | # check if valid list is passed 14 | t, b = misc.from_list2x_parse_top_bottom([0.4, 50.6]) 15 | assert t == 0.4 16 | assert b == 50.6 17 | 18 | 19 | def test_var_to_str(): 20 | print_out = misc.var_to_str(11) 21 | assert print_out == 11 22 | print_out = misc.var_to_str([11, 21.2]) 23 | assert print_out == "11-21.2" 24 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py{38,39,310}-{linux,macos,windows} 3 | 4 | [gh-actions:env] 5 | PLATFORM = 6 | ubuntu-latest: linux 7 | macos-latest: macos 8 | windows-latest: windows 9 | 10 | [testenv] 11 | deps = 12 | pytest 13 | pytest-cov 14 | pandas 15 | numpy < 2.0 16 | matplotlib 17 | numba 18 | scipy 19 | pydantic 20 | typing_extensions 21 | commands = pytest --------------------------------------------------------------------------------