├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── feature_request.yml ├── actions │ └── conda-install │ │ └── action.yml └── workflows │ ├── test_turtle.yml │ └── test_turtle_conda.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── docker └── Dockerfile ├── docs ├── Makefile ├── examples │ ├── example.config │ └── example.yaml ├── make.bat └── source │ ├── acknow_ref.rst │ ├── conf.py │ ├── index.rst │ ├── installation.rst │ ├── known_issues.rst │ ├── new_features.rst │ ├── using_turtleFSI.rst │ └── verif_perf.rst ├── environment.yml ├── figs ├── Turtle_Flow_Pressure_Fields_t_2.5s.png ├── Turtle_boundaries.png ├── Turtle_boundaries_zoom.png ├── Turtle_inlet_vel.png ├── figure_hpc2.png ├── movie_36_tstep.gif ├── reuse_jac_iterations.png ├── turek_benchmark.gif └── turtleFSI_swim.gif ├── paper ├── cfd_illu.png ├── csm_illu.png ├── fsi_illu.png ├── paper.bib └── paper.md ├── setup.py ├── tests └── test_turtleFSI.py └── turtleFSI ├── __init__.py ├── mesh ├── TF_cfd.xml.gz ├── TF_csm.xml.gz ├── TF_fsi.xml.gz └── turtle_demo │ ├── mc.h5 │ ├── mc.xdmf │ ├── mf.h5 │ ├── mf.xdmf │ ├── paraview_cells.vtu │ ├── paraview_facets.vtu │ ├── turtle_mesh.h5 │ └── turtle_mesh.xdmf ├── modules ├── __init__.py ├── biharmonic.py ├── common.py ├── domain.py ├── elastic.py ├── fluid.py ├── laplace.py ├── newtonsolver.py ├── no_extrapolation.py ├── no_fluid.py ├── no_solid.py └── solid.py ├── monolithic.py ├── problems ├── MovingCylinder.py ├── RobinBC_validation.py ├── Stenosis_2D.py ├── TF_cfd.py ├── TF_csm.py ├── TF_fsi.py ├── TaylorGreen2D.py ├── Test_Cylinder │ ├── mesh │ │ └── artery_coarse_rescaled.h5 │ ├── problem_aneu.py │ ├── problem_aneu_2fluid.py │ └── problem_aneu_2solid.py ├── Test_Material │ ├── SimpleShearGent.py │ ├── SimpleShearMooneyRivlin.py │ ├── SimpleShearNeoHookean.py │ ├── SimpleShearSVK.py │ ├── SimpleShearSVKEnergy.py │ ├── UniaxialTensionGent.py │ ├── UniaxialTensionMooneyRivlin.py │ ├── UniaxialTensionSVK.py │ ├── UniaxialTensionSVKRelaxation.py │ ├── UniaxialTensionViscoelastic.py │ └── stress_strain.py ├── __init__.py └── turtle_demo.py ├── run_turtle.py └── utils ├── __init__.py └── argpar.py /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Create a report if you believe something is not working 3 | title: "[BUG]: " 4 | labels: ["bug"] 5 | 6 | body: 7 | - type: textarea 8 | id: description 9 | attributes: 10 | label: How to reproduce the bug 11 | description: Explain how to reproduce the issue you are having 12 | placeholder: I ran a demo or test that failed. The name of the demo is `demo_something.py` 13 | validations: 14 | required: true 15 | 16 | - type: textarea 17 | id: mwe-python 18 | attributes: 19 | label: Minimal Example (Python) 20 | description: Add (optionally) a minimal script that reproduces the bug 21 | render: Python 22 | 23 | validations: 24 | required: false 25 | 26 | - type: textarea 27 | id: output-python 28 | attributes: 29 | label: Output (Python) 30 | description: If you get an error message or any output, please add it here 31 | render: bash 32 | 33 | validations: 34 | required: false 35 | 36 | - type: dropdown 37 | id: version 38 | attributes: 39 | label: Version 40 | description: What version of turtleFSI are you running? 41 | options: 42 | - master branch 43 | - 2.4 44 | - 2.3 45 | - 2.2 46 | - 2.1 47 | - 2.0 48 | validations: 49 | required: true 50 | 51 | - type: textarea 52 | id: output 53 | attributes: 54 | label: Installation 55 | description: How did you install turtleFSI? 56 | placeholder: i.e. "I used the Docker images on a Windows 11" or "I installed turtleFSI with conda on a MacBook Air with the M1 chip. Here are the steps to reproduce my installation ..." 57 | 58 | - type: textarea 59 | id: extra 60 | attributes: 61 | label: Additional information 62 | description: If you have any additional information, please add it here. 63 | placeholder: You can drag and drop files here to attach them -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Add a request for a new/missing feature 3 | labels: ["enhancement"] 4 | 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for taking the time to fill out this feature request! 10 | - type: textarea 11 | id: description 12 | attributes: 13 | label: Describe new/missing feature 14 | description: Explain what feature you are missing, and how you would like it to work. 15 | placeholder: I would like to add a `subtraction` subroutine... 16 | validations: 17 | required: true 18 | - type: textarea 19 | id: code-block 20 | attributes: 21 | label: Suggestion user interface 22 | description: Please create a minimal (Python) code that shows how you would like the feature to work. 23 | render: python3 24 | placeholder: No 3x``` encapsulation of code required -------------------------------------------------------------------------------- /.github/actions/conda-install/action.yml: -------------------------------------------------------------------------------- 1 | name: Install turtle dependencies via conda 2 | 3 | inputs: 4 | python-version: 5 | description: Python version to install 6 | required: true 7 | type: string 8 | default: "3.10" 9 | 10 | runs: 11 | using: composite 12 | steps: 13 | # cache ref https://github.com/conda-incubator/setup-miniconda#caching-packages 14 | 15 | - name: Setup conda-forge 16 | uses: conda-incubator/setup-miniconda@v2 17 | with: 18 | miniforge-variant: Mambaforge 19 | miniforge-version: latest 20 | activate-environment: turtleFSI 21 | python-version: ${{ inputs.python-version }} 22 | use-mamba: true 23 | # uncomment to install env in one go (without caching) 24 | # environment-file: environment.yml 25 | 26 | - name: Modify environment file 27 | run: | 28 | sed -i'' -e "s/python>3.7/python=${{ inputs.python-version }}/g" environment.yml 29 | shell: bash 30 | 31 | - name: Get Date 32 | id: get-date 33 | run: echo "today=$(/bin/date -u '+%Y%m%d')" >> "$GITHUB_OUTPUT" 34 | shell: bash 35 | 36 | - name: Cache conda env 37 | uses: actions/cache@v3 38 | id: cache-env 39 | with: 40 | path: ${{ env.CONDA }}/envs/turtleFSI 41 | key: 42 | conda-env-${{ steps.get-date.outputs.today }}-${{ inputs.python-version }}-${{ hashFiles('environment.yml') }}-${{ hashFiles('.github/actions/install-dependencies/**') }} 43 | 44 | - name: Clear package cache 45 | # package cache seems to be stale 46 | run: 47 | mamba clean -y --index-cache 48 | 49 | shell: bash -el {0} 50 | - name: Update environment 51 | if: steps.cache-env.outputs.cache-hit != 'true' 52 | run: 53 | mamba env update -n turtleFSI -f environment.yml 54 | shell: bash -el {0} 55 | 56 | - name: List environment 57 | run: 58 | mamba list -n turtleFSI 59 | shell: bash -el {0} -------------------------------------------------------------------------------- /.github/workflows/test_turtle.yml: -------------------------------------------------------------------------------- 1 | name: Test against FEniCS master branch 2 | 3 | on: 4 | push: 5 | # The CI is executed on every push on every branch 6 | branches: 7 | - master 8 | pull_request: 9 | # The CI is executed on every pull request to the main branch 10 | branches: 11 | - master 12 | 13 | schedule: 14 | # The CI is executed every day at 8am 15 | - cron: "0 8 * * *" 16 | jobs: 17 | test-code: 18 | runs-on: ubuntu-22.04 19 | # Runs against FEniCS main branch 20 | container: ghcr.io/scientificcomputing/fenics:2023-04-21 21 | steps: 22 | # This action sets the current path to the root of your github repo 23 | - uses: actions/checkout@v3 24 | 25 | 26 | - name: "Install code" 27 | run: python3 setup.py install 28 | - name: Run tests 29 | run: | 30 | python3 -m pytest -------------------------------------------------------------------------------- /.github/workflows/test_turtle_conda.yml: -------------------------------------------------------------------------------- 1 | name: Test turtle against FEniCS stable 2 | 3 | on: 4 | workflow_dispatch: 5 | workflow_call: 6 | pull_request: 7 | branches: ["master"] 8 | 9 | env: 10 | DEB_PYTHON_INSTALL_LAYOUT: deb_system 11 | IPP_NONINTERACTIVE: "1" 12 | 13 | defaults: 14 | run: 15 | shell: bash -el {0} 16 | 17 | jobs: 18 | test-conda: 19 | runs-on: ${{ matrix.os }} 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | os: ["ubuntu-latest", "macos-latest"] 24 | python-version: ["3.8", "3.9", "3.10", "3.11"] 25 | 26 | steps: 27 | 28 | - uses: actions/checkout@v3 29 | 30 | - name: Install turtle dependencies via conda 31 | uses: ./.github/actions/conda-install 32 | with: 33 | python-version: ${{ matrix.python-version }} 34 | 35 | 36 | - name: "Install code" 37 | run: python3 -m pip install --no-deps . 38 | 39 | - name: Run tests 40 | run: | 41 | python3 -m pip install pytest 42 | python3 -m pytest -xvs -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.pyc 3 | 4 | # Temporary files 5 | *.vtp 6 | *.particles 7 | *.swp 8 | *~ 9 | 10 | # IDE files 11 | .idea 12 | .idea/workspace.xml 13 | 14 | # Setuptools distribution folder. 15 | /dist/ 16 | 17 | # Python egg metadata, regenerated from source files by setuptools. 18 | /*.egg-info 19 | 20 | # Generated probe files 21 | turtleFSI/utils/probe/.rendered.probe11.cpp 22 | turtleFSI/utils/probe/*.so 23 | 24 | #Results or mesh 25 | *.xdmf 26 | *.h5 27 | *.pickle 28 | *.vtu 29 | *.xml.gz 30 | *.txt 31 | *.pvd 32 | 33 | # Distribution / packaging 34 | lib/ 35 | build/ 36 | bin/ 37 | 38 | # OS specific files 39 | .DS_Store -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to turtleFSI 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | ### Testing 7 | 8 | Please provide unit tests for the new code you create, testing the main functionality or feature to be submitted. We can always use more test coverage! 9 | 10 | ### Submitting changes 11 | 12 | In order to submit you changes, please send a [GitHub Pull Request to turtleFSI](https://github.com/KVSlab/turtleFSI/pull/new/master) with a clear list of what you've done (read more about [pull requests](http://help.github.com/pull-requests/)). Please follow our coding conventions (below) and make sure all of your commits are atomic (one feature per commit). 13 | 14 | Always write a clear commit message when submitting your changes. One-line messages are fine for small changes, but bigger changes should look like this: 15 | 16 | > $ git commit -m "A brief summary of the commit 17 | > 18 | > A paragraph describing what changed and its impact." 19 | 20 | ### Coding conventions 21 | 22 | #### Formatting 23 | * Avoid inline comments. 24 | * Break long lines after 120 characters. 25 | * Delete trailing whitespace. 26 | * Don't include spaces after `(`, `[` or before `]`, `)`. 27 | * Don't misspell. 28 | * Use 4 space indentation. 29 | * Use an empty line between methods. 30 | * Use spaces around operators, except for unary operators, such as `!`. 31 | * Use spaces after commas, after colons and semicolons, around `{` and before 32 | `}`. 33 | 34 | #### Naming 35 | 36 | * Avoid abbreviations. 37 | * Use snake case for variables and methods. 38 | * Use camel case for classes. 39 | * Name variables, methods, and classes to reveal intent. 40 | 41 | #### Organization 42 | 43 | * Order methods so that caller methods are earlier in the file than the methods 44 | they call. 45 | * Place methods receiving command line arguments at the bottom of the file, but above the top-level script environment check. 46 | * Separate local and global imports of modules. 47 | 48 | ### Code of Conduct 49 | 50 | ### Our Pledge 51 | 52 | In the interest of fostering an open and welcoming environment, we as 53 | contributors and maintainers pledge to making participation in our project and 54 | our community a harassment-free experience for everyone, regardless of age, body 55 | size, disability, ethnicity, sex characteristics, gender identity and expression, 56 | level of experience, education, socio-economic status, nationality, personal 57 | appearance, race, religion, or sexual identity and orientation. 58 | 59 | ### Our Standards 60 | 61 | Examples of behavior that contributes to creating a positive environment 62 | include: 63 | 64 | * Using welcoming and inclusive language 65 | * Being respectful of differing viewpoints and experiences 66 | * Gracefully accepting constructive criticism 67 | * Focusing on what is best for the community 68 | * Showing empathy towards other community members 69 | 70 | Examples of unacceptable behavior by participants include: 71 | 72 | * The use of sexualized language or imagery and unwelcome sexual attention or 73 | advances 74 | * Trolling, insulting/derogatory comments, and personal or political attacks 75 | * Public or private harassment 76 | * Publishing others' private information, such as a physical or electronic 77 | address, without explicit permission 78 | * Other conduct which could reasonably be considered inappropriate in a 79 | professional setting 80 | 81 | ### Our Responsibilities 82 | 83 | Project maintainers are responsible for clarifying the standards of acceptable 84 | behavior and are expected to take appropriate and fair corrective action in 85 | response to any instances of unacceptable behavior. 86 | 87 | Project maintainers have the right and responsibility to remove, edit, or 88 | reject comments, commits, code, wiki edits, issues, and other contributions 89 | that are not aligned to this Code of Conduct, or to ban temporarily or 90 | permanently any contributor for other behaviors that they deem inappropriate, 91 | threatening, offensive, or harmful. 92 | 93 | ### Scope 94 | 95 | This Code of Conduct applies both within project spaces and in public spaces 96 | when an individual is representing the project or its community. Examples of 97 | representing a project or community include using an official project e-mail 98 | address, posting via an official social media account, or acting as an appointed 99 | representative at an online or offline event. Representation of a project may be 100 | further defined and clarified by project maintainers. 101 | 102 | ### Enforcement 103 | 104 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 105 | reported by contacting the project team at alban@simula.no. All complaints will be reviewed and 106 | investigated and will result in a response that is deemed necessary and 107 | appropriate to the circumstances. The project team is obligated to maintain 108 | confidentiality with regard to the reporter of an incident. Further details of 109 | specific enforcement policies may be posted separately. 110 | 111 | Project maintainers who do not follow or enforce the Code of Conduct in good 112 | faith may face temporary or permanent repercussions as determined by other 113 | members of the project's leadership. 114 | 115 | ### Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 118 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 119 | 120 | [homepage]: https://www.contributor-covenant.org 121 | 122 | For answers to common questions about this code of conduct, see 123 | https://www.contributor-covenant.org/faq 124 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include turtleFSI/mesh * 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Documentation Status](https://readthedocs.org/projects/turtlefsi2/badge/?version=latest)](https://turtlefsi2.readthedocs.io/en/latest/?badge=latest) 2 | [![Actions FEniCS stable](https://github.com/KVSLab/turtleFSI/actions/workflows/test_turtle_conda.yml/badge.svg)](https://github.com/KVSlab/turtleFSI/actions/workflows/test_turtle_conda.yml) 3 | [![Actions FEniCS master](https://github.com/KVSLab/turtleFSI/actions/workflows/test_turtle.yml/badge.svg)](https://github.com/KVSlab/turtleFSI/actions/workflows/test_turtle.yml) 4 | [![status](https://joss.theoj.org/papers/b7febdaa2709205d40b51227091c3b0b/status.svg)](https://joss.theoj.org/papers/b7febdaa2709205d40b51227091c3b0b) 5 | 6 | # turtleFSI - a Fluid-Structure Interaction Solver 7 | 8 |

9 | turtleFSI_swim 10 | turtleFSI_swim 11 |

12 |

13 | To the left we show a turtle swimming (in turtleFSI), and to the right, the classical Turek benchmark (FSI2). 14 |

15 | 16 | 17 | Description 18 | ----------- 19 | turtleFSI is a monolithic fluid-structure interaction solver written in FEniCS, and has out-of-the-box high performance capabilities. The goal of turtleFSI is to provide research groups, and other individuals, with a simple, but robust solver to investigate fluid structure interaction problems. 20 | 21 | 22 | Authors 23 | ------- 24 | turtleFSI is developed by: 25 | 26 | * Andreas Slyngstad 27 | * Sebastian Gjertsen 28 | * Aslak W. Bergersen 29 | * Alban Souche 30 | * Kristian Valen-Sendstad 31 | 32 | 33 | Licence 34 | ------- 35 | turtleFSI is licensed under the GNU GPL, version 3 or (at your option) any 36 | later version. turtleFSI is Copyright (2016-2019) by the authors. 37 | 38 | 39 | Documentation 40 | ------------- 41 | For an introduction to turtleFSI, and tutorials, please refer to the [documentation](https://turtlefsi2.readthedocs.io/en/latest/). 42 | 43 | If you wish to use turtleFSI for journal publications, please refer to the [JOSS publication](https://joss.theoj.org/papers/10.21105/joss.02089#): 44 | 45 | Bergersen et al., (2020). turtleFSI: A Robust and Monolithic FEniCS-based Fluid-Structure Interaction Solver. Journal of Open Source Software, 5(50), 2089, https://doi.org/10.21105/joss.02089 46 | 47 | Installation 48 | ------------ 49 | turtleFSI is build upon the open source Finite Elements FEniCS project (version 2018.1.0 or 2019.1.0). 50 | Please refer to the respective FEniCS documentation for installing the dependencies on your system. 51 | 52 | However, if you are using Linux or MaxOSX you can install turtleFSI through anaconda:: 53 | 54 | conda create -n your_environment -c conda-forge turtleFSI 55 | 56 | You can then activate your environment by runing ``source activate your_environment``. 57 | You are now all set, and can start running fluid-structure interaction simulations. 58 | 59 | Use 60 | --- 61 | Run turtleFSI with all the default parameters:: 62 | ``turtleFSI`` 63 | 64 | See all the command line parameters run the following command:: 65 | ``turtleFSI -h`` 66 | 67 | Run a specific problem file:: 68 | ``turtleFSI --problem [path_to_problem]`` 69 | 70 | When calling a specific problem file, turtleFSI will first look for the file name locally, then check if the file name is present in the directory "/turtleFSI/problems/". 71 | Please refere to the [documentation](https://turtlefsi2.readthedocs.io/en/latest/) to learn how to define a new problem file and for a more complete description of usage. 72 | 73 | 74 | Contact 75 | ------- 76 | The latest version of this software can be obtained from 77 | 78 | https://github.com/KVSlab/turtleFSI 79 | 80 | Please report bugs and other issues through the issue tracker at: 81 | 82 | https://github.com/KVSlab/turtleFSI/issues 83 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM condaforge/mambaforge 2 | 3 | 4 | WORKDIR /tmp/ 5 | 6 | ENV DEBIAN_FRONTEND=noninteractive 7 | 8 | # Install ssh (missing dependency to run conda envs) 9 | RUN apt-get update && \ 10 | apt-get install -y ssh build-essential 11 | 12 | # Upgrade mamba 13 | RUN mamba upgrade -y mamba 14 | 15 | # Copy environment and requirements files into docker env 16 | COPY . turtleFSI 17 | 18 | # Update environment file with new environment name 19 | RUN mamba env update --file ./turtleFSI/environment.yml --name dockerenv 20 | SHELL ["mamba", "run", "-n", "dockerenv", "/bin/bash", "-c"] 21 | 22 | RUN python3 -m pip install ./turtleFSI pytest 23 | 24 | # Test turtleFSI 25 | RUN python3 -m pytest ./turtleFSI/tests 26 | 27 | RUN echo "source activate dockerenv" > ~/.bashrc -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = turtleFSI 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) -------------------------------------------------------------------------------- /docs/examples/example.config: -------------------------------------------------------------------------------- 1 | # Configuration file for turtleFSI 2 | ################################################################################ 3 | # Define solver, numerics, and problem file 4 | ################################################################################ 5 | 6 | # Name of problem file to solve. Could either be located in the turtleFSI 7 | # repository (TF_cfd, TF_csm, TF_fsi, turtle_demo) or it could be a problem 8 | # file you have created locally. 9 | problem="turtle_demo" 10 | 11 | # Setting temporal integration. 12 | # (theta=0 : first order explicit forward Euler scheme) 13 | # (theta=1 : first order implicit backward Euler scheme) 14 | # (theta=0.5 : second-order Crank-Nicolson scheme) 15 | # (theta=0.5+dt : gives a better long-term numerical stability while keeping 16 | # the second order accuracy of the Crank-Nicolson scheme) 17 | theta=0.501 18 | 19 | ################################################################################ 20 | # Set fluid, solid, and extrapolation 21 | ################################################################################ 22 | 23 | # Turn on/off solving of the fluid problem ('fluid', 'no_fluid') 24 | fluid=fluid 25 | 26 | # Turn on/off solving of the solid problem ('solid', 'no_solid') 27 | solid=solid 28 | 29 | # Use Robin boundary conditions for solid 30 | robin_bc=False 31 | 32 | # Set approach for extrapolating the deformation into the fluid domain 33 | # ('laplace', 'elastic', 'biharmonic', 'no_extrapolation') 34 | extrapolation=laplace 35 | 36 | # Set the sub type of the extrapolation method ('constant'," 'small_constant', 37 | # 'volume', 'volume_change', 'constrained_disp', 'constrained_disp_vel') 38 | extrapolation-sub-type=constant 39 | 40 | # List of boundary ids for the weak formulation of the biharmonic mesh lifting 41 | # operator with 'constrained_disp_vel' 42 | #bc_ids=[] 43 | 44 | ################################################################################ 45 | # Material settings / physical constants 46 | ################################################################################ 47 | 48 | # Maximum velocity at inlet 49 | Um=0.8 50 | 51 | # Density of the fluid 52 | rho-f=1.0E3 53 | 54 | # Fluid dynamic viscosity 55 | mu-f=1.0 56 | 57 | # Density of the solid 58 | rho-s=1.0E3 59 | 60 | # Shear modulus or 2nd Lame Coef. for the solid 61 | mu-s=5.0E4 62 | 63 | # Poisson ratio in the solid 64 | nu-s=0.45 65 | 66 | # 1st Lame Coef. for the solid 67 | lambda-s=4.5E5 68 | 69 | # Elastic response necessary for RobinBC 70 | k_s=0.0 71 | 72 | # Viscoelastic response necessary for RobinBC 73 | c_s=0.0 74 | 75 | # Gravitational force on the solid 76 | #gravity=None 77 | 78 | ################################################################################ 79 | # Domain settings 80 | ################################################################################ 81 | 82 | # Domain id of the fluid domain 83 | dx-f-id=1 84 | 85 | # Domain id of the solid domain 86 | dx-s-id=2 87 | 88 | # Domain id of the solid boundary necessary for RobinBC 89 | #ds_s_id=None 90 | 91 | ################################################################################ 92 | # Solver settings 93 | ################################################################################ 94 | 95 | # Selected linear solver for each Newton iteration, to see a complete list 96 | # run list_linear_solvers() 97 | linear-solver=mumps 98 | 99 | # Absolute error tolerance for the Newton iterations 100 | atol=1e-7 101 | 102 | # Relative error tolerance for the Newton iterations 103 | rtol=1e-7 104 | 105 | # Maximum number of iterations in the Newton solver 106 | max-it=50 107 | 108 | # Relaxation factor in the Netwon solver 109 | lmbda=1.0 110 | 111 | # How often to recompute the Jacobian over Newton iterations 112 | recompute=5 113 | 114 | # How often to recompute the Jacobian over time steps. 115 | recompute-tstep=1 116 | 117 | # Update the default values of the compiler arguments by providing a key=value, 118 | # e.g. optimize=False. You can provide multiple key=value pairs seperated by a 119 | # whitespace 120 | #compiler-parameters=None 121 | 122 | ################################################################################ 123 | # Output settings 124 | ################################################################################ 125 | 126 | # Turn on/off verbose printing 127 | verbose=True 128 | 129 | # Set FEniCS loglevel 130 | loglevel=20 131 | 132 | # Saving frequency of the files defined in the problem file 133 | save-step=10 134 | 135 | # Degree of the functions saved for visualisation. '1':P1, '2':P2, etc... 136 | save-deg=1 137 | 138 | # How often to store a checkpoint (use to later restart a simulation) 139 | checkpoint-step=500 140 | 141 | # Path to store the results. You can store multiple simulations in one folder 142 | folder=results 143 | 144 | # Over write the standard 1, 2, 3 name of the sub folders 145 | #sub-folder=None 146 | 147 | # Path to subfolder to restart from 148 | #restart-folder=None 149 | 150 | ################################################################################ 151 | # Set spatial and temporal resolution 152 | ################################################################################ 153 | 154 | # Set timestep, dt 155 | time-step=0.001 156 | 157 | # Set end time 158 | end-time=1 159 | 160 | # Set degree of pressure 161 | p-deg=1 162 | 163 | # Set degree of velocity 164 | v-deg=2 165 | 166 | # Set degree of deformation 167 | d-deg=2 168 | 169 | ################################################################################ 170 | # Misc settings 171 | ################################################################################ 172 | 173 | # Stop simulations cleanly after the given number of seconds 174 | #killtime=None 175 | -------------------------------------------------------------------------------- /docs/examples/example.yaml: -------------------------------------------------------------------------------- 1 | # Configuration file for turtleFSI 2 | ################################################################################ 3 | # Define solver, numerics, and problem file 4 | ################################################################################ 5 | 6 | # Name of problem file to solve. Could either be located in the turtleFSI 7 | # repository (TF_cfd, TF_csm, TF_fsi, turtle_demo) or it could be a problem 8 | # file you have created locally. 9 | problem: turtle_demo 10 | 11 | # Setting temporal integration. 12 | # (theta=0 : first order explicit forward Euler scheme) 13 | # (theta=1 : first order implicit backward Euler scheme) 14 | # (theta=0.5 : second-order Crank-Nicolson scheme) 15 | # (theta=0.5+dt : gives a better long-term numerical stability while keeping 16 | # the second order accuracy of the Crank-Nicolson scheme) 17 | theta: 0.501 18 | 19 | ################################################################################ 20 | # Set fluid, solid, and extrapolation 21 | ################################################################################ 22 | 23 | # Turn on/off solving of the fluid problem ('fluid', 'no_fluid') 24 | fluid: fluid 25 | 26 | # Turn on/off solving of the solid problem ('solid', 'no_solid') 27 | solid: solid 28 | 29 | # Use Robin boundary conditions for solid 30 | robin_bc: False 31 | 32 | # Set approach for extrapolating the deformation into the fluid domain 33 | # ('laplace', 'elastic', 'biharmonic', 'no_extrapolation') 34 | extrapolation: laplace 35 | 36 | # Set the sub type of the extrapolation method ('constant'," 'small_constant', 37 | # 'volume', 'volume_change', 'constrained_disp', 'constrained_disp_vel') 38 | extrapolation-sub-type: constant 39 | 40 | # List of boundary ids for the weak formulation of the biharmonic mesh lifting 41 | # operator with 'constrained_disp_vel' 42 | #bc-ids: [] 43 | 44 | ################################################################################ 45 | # Material settings / physical constants 46 | ################################################################################ 47 | 48 | # Maximum velocity at inlet 49 | Um: 0.8 50 | 51 | # Density of the fluid 52 | rho-f: 1.0E3 53 | 54 | # Fluid dynamic viscosity 55 | mu-f: 1.0 56 | 57 | # Density of the solid 58 | rho-s: 1.0E3 59 | 60 | # Shear modulus or 2nd Lame Coef. for the solid 61 | mu-s: 5.0E4 62 | 63 | # Poisson ratio in the solid 64 | nu-s: 0.45 65 | 66 | # 1st Lame Coef. for the solid 67 | lambda-s: 4.5E5 68 | 69 | # Elastic response necessary for RobinBC 70 | k_s: 0.0 71 | 72 | # Viscoelastic response necessary for RobinBC 73 | c_s: 0.0 74 | 75 | # Gravitational force on the solid 76 | #gravity: None 77 | 78 | ################################################################################ 79 | # Domain settings 80 | ################################################################################ 81 | 82 | # Domain id of the fluid domain 83 | dx-f-id: 1 84 | 85 | # Domain id of the solid domain 86 | dx-s-id: 2 87 | 88 | # Domain id of the solid boundary necessary for RobinBC 89 | #ds_s_id: None 90 | 91 | ################################################################################ 92 | # Solver settings 93 | ################################################################################ 94 | 95 | # Selected linear solver for each Newton iteration, to see a complete list 96 | # run list_linear_solvers() 97 | linear-solver: mumps 98 | 99 | # Absolute error tolerance for the Newton iterations 100 | atol: 1e-7 101 | 102 | # Relative error tolerance for the Newton iterations 103 | rtol: 1e-7 104 | 105 | # Maximum number of iterations in the Newton solver 106 | max-it: 50 107 | 108 | # Relaxation factor in the Netwon solver 109 | lmbda: 1.0 110 | 111 | # How often to recompute the Jacobian over Newton iterations 112 | recompute: 5 113 | 114 | # How often to recompute the Jacobian over time steps. 115 | recompute-tstep: 1 116 | 117 | # Update the default values of the compiler arguments by providing a key=value, 118 | # e.g. optimize=False. You can provide multiple key=value pairs seperated by a 119 | # whitespace 120 | #compiler-parameters: None 121 | 122 | ################################################################################ 123 | # Output settings 124 | ################################################################################ 125 | 126 | # Turn on/off verbose printing 127 | verbose: True 128 | 129 | # Set FEniCS loglevel 130 | loglevel: 20 131 | 132 | # Saving frequency of the files defined in the problem file 133 | save-step: 10 134 | 135 | # Degree of the functions saved for visualisation. '1':P1, '2':P2, etc... 136 | save-deg: 1 137 | 138 | # How often to store a checkpoint (use to later restart a simulation) 139 | checkpoint-step: 500 140 | 141 | # Path to store the results. You can store multiple simulations in one folder 142 | folder: results 143 | 144 | # Over write the standard 1, 2, 3 name of the sub folders 145 | #sub-folder: None 146 | 147 | # Path to subfolder to restart from 148 | #restart-folder: None 149 | 150 | ################################################################################ 151 | # Set spatial and temporal resolution 152 | ################################################################################ 153 | 154 | # Set timestep, dt 155 | time-step: 0.001 156 | 157 | # Set end time 158 | end-time: 1 159 | 160 | # Set degree of pressure 161 | p-deg: 1 162 | 163 | # Set degree of velocity 164 | v-deg: 2 165 | 166 | # Set degree of deformation 167 | d-deg: 2 168 | 169 | ################################################################################ 170 | # Misc settings 171 | ################################################################################ 172 | 173 | # Stop simulations cleanly after the given number of seconds 174 | #killtime: None 175 | -------------------------------------------------------------------------------- /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 | set SPHINXPROJ=turtleFSI 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 20 | echo.installed, then set the SPHINXBUILD environment variable to point 21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 22 | echo.may add the Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /docs/source/acknow_ref.rst: -------------------------------------------------------------------------------- 1 | .. title:: Acknowledgements and references 2 | 3 | .. _acknow_ref: 4 | 5 | =============================== 6 | Acknowledgements and references 7 | =============================== 8 | 9 | We would like to acknowledge the open-source project `FEniCS `_, 10 | with is the basis of turtleFSI. 11 | 12 | The numerical schemes in turtleFSI is presented and tested in Slyngstad [1]_ and Gjertsen [2]_. 13 | The input problem set up for the TF_cfd, TF_csm, and TF_fsi is taken from the Turek et al. [3]_ benchmark 14 | paper. 15 | 16 | .. [1] Slyngstad, Andreas S. Verification and Validation of a Monolithic Fluid-Structure Interaction Solver in FEniCS. A comparison of mesh lifting operators. MS thesis. 2017. 17 | .. [2] Gjertsen, Sebastian. Development of a Verified and Validated Computational Framework for Fluid-Structure Interaction: Investigating Lifting Operators and Numerical Stability. MS thesis. 2017. 18 | .. [3] Turek, Stefan, and Jaroslav Hron. "Proposal for numerical benchmarking of fluid-structure interaction between an elastic object and laminar incompressible flow." Fluid-structure interaction. Springer, Berlin, Heidelberg, 2006. 371-385. 19 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/master/config 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | # 15 | # import os 16 | # import sys 17 | # sys.path.insert(0, os.path.abspath('.')) 18 | 19 | 20 | # -- Project information ----------------------------------------------------- 21 | 22 | project = 'turtleFSI' 23 | copyright = '2019, A. Sylngstad, S. Gjertsen, A. Bergersen, A. Souche, and K. Valen-Sendstad' 24 | author = 'A. Sylngstad, S. Gjertsen, A. Bergersen, A. Souche, and K. Valen-Sendstad' 25 | 26 | # The short X.Y version 27 | version = 'v1.2' 28 | # The full version, including alpha/beta/rc tags 29 | release = 'v1.2.0' 30 | 31 | 32 | # -- General configuration --------------------------------------------------- 33 | 34 | # If your documentation needs a minimal Sphinx version, state it here. 35 | # 36 | # needs_sphinx = '1.0' 37 | 38 | # Add any Sphinx extension module names here, as strings. They can be 39 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 40 | # ones. 41 | extensions = [ 42 | 'sphinx.ext.autodoc', 43 | 'sphinx.ext.doctest', 44 | 'sphinx.ext.intersphinx', 45 | 'sphinx.ext.todo', 46 | 'sphinx.ext.coverage', 47 | 'sphinx.ext.mathjax', 48 | 'sphinx.ext.ifconfig', 49 | 'sphinx.ext.viewcode', 50 | 'sphinx.ext.napoleon', 51 | ] 52 | 53 | # Add any paths that contain templates here, relative to this directory. 54 | #templates_path = ['_templates'] 55 | 56 | # The suffix(es) of source filenames. 57 | # You can specify multiple suffix as a list of string: 58 | # 59 | # source_suffix = ['.rst', '.md'] 60 | source_suffix = '.rst' 61 | 62 | # The master toctree document. 63 | master_doc = 'index' 64 | 65 | # General information about the project. 66 | project = u'turtleFSI' 67 | copyright = u'2019, Aslak W. Bergersen & Sebastian Gjertsen & Alban Souche & Andreas Slyngstad' 68 | author = u'Aslak W. Bergersen & Sebastian Gjertsen & Alban Souche & Andreas Slyngstad' 69 | 70 | # The language for content autogenerated by Sphinx. Refer to documentation 71 | # for a list of supported languages. 72 | # 73 | # This is also used if you do content translation via gettext catalogs. 74 | # Usually you set "language" from the command line for these cases. 75 | language = None 76 | 77 | # List of patterns, relative to source directory, that match files and 78 | # directories to ignore when looking for source files. 79 | # This pattern also affects html_static_path and html_extra_path . 80 | exclude_patterns = [] 81 | autodoc_mock_imports = ["numpy", "dolfin", "turtleFSI"] 82 | 83 | # The name of the Pygments (syntax highlighting) style to use. 84 | pygments_style = 'sphinx' 85 | 86 | 87 | # -- Options for HTML output ------------------------------------------------- 88 | 89 | # The theme to use for HTML and HTML Help pages. See the documentation for 90 | # a list of builtin themes. 91 | # 92 | html_theme = 'sphinx_rtd_theme' 93 | # 'alabaster' 94 | 95 | # Theme options are theme-specific and customize the look and feel of a theme 96 | # further. For a list of options available for each theme, see the 97 | # documentation. 98 | # 99 | # html_theme_options = {} 100 | 101 | # Add any paths that contain custom static files (such as style sheets) here, 102 | # relative to this directory. They are copied after the builtin static files, 103 | # so a file named "default.css" will overwrite the builtin "default.css". 104 | html_static_path = ['_static'] 105 | 106 | # Custom sidebar templates, must be a dictionary that maps document names 107 | # to template names. 108 | # 109 | # The default sidebars (for documents that don't match any pattern) are 110 | # defined by theme itself. Builtin themes are using these templates by 111 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 112 | # 'searchbox.html']``. 113 | # 114 | # html_sidebars = {} 115 | 116 | 117 | # -- Options for HTMLHelp output --------------------------------------------- 118 | 119 | # Output file base name for HTML help builder. 120 | htmlhelp_basename = 'turtleFSIdoc' 121 | 122 | 123 | # -- Options for LaTeX output ------------------------------------------------ 124 | 125 | latex_elements = { 126 | # The paper size ('letterpaper' or 'a4paper'). 127 | # 128 | # 'papersize': 'letterpaper', 129 | 130 | # The font size ('10pt', '11pt' or '12pt'). 131 | # 132 | # 'pointsize': '10pt', 133 | 134 | # Additional stuff for the LaTeX preamble. 135 | # 136 | # 'preamble': '', 137 | 138 | # Latex figure (float) alignment 139 | # 140 | # 'figure_align': 'htbp', 141 | } 142 | 143 | # Grouping the document tree into LaTeX files. List of tuples 144 | # (source start file, target name, title, 145 | # author, documentclass [howto, manual, or own class]). 146 | latex_documents = [ 147 | (master_doc, 'turtleFSI.tex', 'turtleFSI Documentation', 148 | 'Aslak W. Bergersen & Sebastian Gjertsen & Alban Souche & Andreas Slyngstad', 'manual'), 149 | ] 150 | 151 | 152 | # -- Options for manual page output ------------------------------------------ 153 | 154 | # One entry per manual page. List of tuples 155 | # (source start file, name, description, authors, manual section). 156 | man_pages = [ 157 | (master_doc, 'turtlefsi', 'turtleFSI Documentation', 158 | [author], 1) 159 | ] 160 | 161 | 162 | # -- Options for Texinfo output ---------------------------------------------- 163 | 164 | # Grouping the document tree into Texinfo files. List of tuples 165 | # (source start file, target name, title, author, 166 | # dir menu entry, description, category) 167 | texinfo_documents = [ 168 | (master_doc, 'turtleFSI', 'turtleFSI Documentation', 169 | author, 'turtleFSI', 'One line description of project.', 170 | 'Miscellaneous'), 171 | ] 172 | 173 | 174 | # -- Extension configuration ------------------------------------------------- 175 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. turtleFSI documentation master file, created by 2 | sphinx-quickstart on Tue Mar 26 14:24:22 2019. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | .. title:: turtleFSI 7 | 8 | ===================================== 9 | Welcome to turtleFSI's documentation! 10 | ===================================== 11 | 12 | .. toctree:: 13 | :maxdepth: 3 14 | 15 | installation 16 | using_turtleFSI 17 | verif_perf 18 | new_features 19 | known_issues 20 | acknow_ref 21 | -------------------------------------------------------------------------------- /docs/source/installation.rst: -------------------------------------------------------------------------------- 1 | .. title:: Installation 2 | 3 | .. _installation: 4 | 5 | ============ 6 | Installation 7 | ============ 8 | 9 | Compatibility and Dependencies 10 | ============================== 11 | The dependencies of turtleFSI are: 12 | 13 | * FEniCS 2019.1.0 14 | * Numpy >1.1X 15 | * Python >=3.7 16 | 17 | Basic Installation 18 | ================== 19 | If you have a MacOX or Linux operating system we recommend that you 20 | install turtleFSI through Anaconda. First, install `Anaconda or Miniconda `_, 21 | depending on your need. For just installing turtleFSI we recommend Miniconda. 22 | Then execute the following command in a terminal window:: 23 | 24 | $ conda create -n your_environment -c conda-forge turtleFSI 25 | 26 | You can then activate your environment by running ``source activate your_environment``. 27 | Now you are all set, and can start using turtleFSI. A detailed explanation for usage of 28 | turtleFSI can be found `here `_. 29 | 30 | If you are using turtleFSI on a high performance computing (HPC) cluster we always 31 | recommend that you build from source, as described below. This is in accordance 32 | with the guidelines provided by the `FEniCS project `_ 33 | users to install FEniCS from source when on a HPC cluster. 34 | 35 | Development version 36 | =================== 37 | 38 | Downloading 39 | ~~~~~~~~~~~ 40 | The latest development version of turtleFSI can be found on the official 41 | `turtleFSI git repository `_ on Github. 42 | To clone the turtleFSI repository, open a terminal, navigate to the directory where you wish 43 | turtleFSI to be stored, type the following command, and press Enter:: 44 | 45 | $ git clone https://github.com/KVSlab/turtleFSI 46 | 47 | After the source distribution has been downloaded, all the files will be located 48 | in the newly created ``turtleFSI`` folder. 49 | 50 | Building 51 | ~~~~~~~~ 52 | In order to build and install turtleFSI, navigate into the ``turtleFSI`` folder, where a ``setup.py`` 53 | file will be located. First, make sure that all dependencies are installed. 54 | Then, you can install turtleFSI be executing the following:: 55 | 56 | $ python setup.py install 57 | 58 | If you are installing turtleFSI somewhere you do not have root access, typically on a cluster, you can add 59 | ``--user`` to install locally. -------------------------------------------------------------------------------- /docs/source/known_issues.rst: -------------------------------------------------------------------------------- 1 | .. title:: Known issues 2 | 3 | .. _known_issues: 4 | 5 | ============ 6 | Known issues 7 | ============ 8 | 9 | MUMPS failure error message 10 | ============= 11 | When running a large problem, typically a 3D problem with many number of degrees of freedom, with several tens of processors, 12 | the MUMPS solver (our default linear solver) may fail with the following error message:: 13 | 14 | *** ------------------------------------------------------------------------- 15 | *** Error: Unable to solve linear system using PETSc Krylov solver. 16 | *** Reason: Solution failed to converge in 0 iterations (PETSc reason DIVERGED_PC_FAILED, residual norm ||r|| = 0.000000e+00). 17 | *** Where: This error was encountered inside PETScKrylovSolver.cpp. 18 | *** Process: 11 19 | *** 20 | *** DOLFIN version: 2019.2.0.dev0 21 | *** Git changeset: 43642bad27866a5bf4e8a117c87c0f6ba777b196 22 | *** ------------------------------------------------------------------------- 23 | 24 | While the specific reason for this error message can vary, it may occur even when the Newton iteration appears to converge correctly. 25 | In such a case, it is likely that the MUMPS does not hold enough memory to solve the linear system. 26 | To provide more useful information to the user and reduce the likelihood of encountering this error message, 27 | we have added two lines of code to the ``newtonsolver.py`` file. 28 | These lines will (1) print the actual error message from the MUMPS solver and (2) allocate more memory to MUMPS. 29 | Specifically, the following code has been added:: 30 | 31 | PETScOptions.set("mat_mumps_icntl_4", 1) 32 | PETScOptions.set("mat_mumps_icntl_14", 400) 33 | 34 | The first line of code will print the actual error message from the MUMPS solver, and the second line of code will allocate more memory to MUMPS. 35 | For detailed information about the parameters of MUMPS, please refer to `here `_. -------------------------------------------------------------------------------- /docs/source/new_features.rst: -------------------------------------------------------------------------------- 1 | .. title:: New features 2 | 3 | .. _new_features: 4 | 5 | ============ 6 | New features 7 | ============ 8 | 9 | The existing methods provide many degrees of freedom, however, if you need a specific method 10 | or functionality, please do not hesitate to propose enhancements in the 11 | `issue tracker `_, or create a pull request with new features. 12 | Our only request is that you follow our 13 | `guidelines for contributing `_. 14 | -------------------------------------------------------------------------------- /docs/source/verif_perf.rst: -------------------------------------------------------------------------------- 1 | .. title:: Solver verification and performance 2 | 3 | .. _verif_perf: 4 | 5 | ========================================= 6 | Newton solver convergence and performance 7 | ========================================= 8 | 9 | We illustrate the solver convergence and performance of turtleFSI with a 3D FSI pipe flow problem 10 | (animation below). We impose a constant Dirichlet plug flow at one end of the fluid domain and 11 | compute the resulting structure deformation and fluid flow along the pipe. 12 | 13 | .. figure:: ../../figs/movie_36_tstep.gif 14 | :width: 600px 15 | :align: center 16 | 17 | Animation of the problem used for benchmarking convergence and performance. 18 | 19 | Solver convergence 20 | ~~~~~~~~~~~~~~~~~~ 21 | The robustness of turtleFSI relies on the use of a direct solver (here, MUMPS) within each Newton 22 | iterations. Using a direct solver for large 3D problems can rapidly become computationally 23 | demanding. Indeed, most of the computational cost of turtleFSI is spent to perform the factorization 24 | of the linear system at each Newton iterations. To mitigate this limitation, we can reuse the same 25 | factorization over several iterations by not updating the Jacobian matrix of the problem. This can 26 | typically be done in simulations where the Newton iterations exhibit “good” converge behavior. In 27 | turtleFSI, the reuse of the Jacobian matrix can be set by the flags ``recompute``, which takes 28 | an integer and controls how many iterations reusing the same Jacobian matrix in the same timestep. 29 | ``recompute_tstep``, does the same, but controls how many time steps to take reusing the same 30 | Jacobian matrix. 31 | 32 | .. note:: 33 | Any increase of the relative or absolute residual will trigger turtleFSI to recompute the 34 | Jacobian matrix, irrespective of the prescribed user values set by the ``recompute`` and 35 | ``recompute_tstep``. 36 | 37 | Figure 1 illustrates the convergence of the solver using the full Newton procedure, updating the 38 | Jacobian matrix at each iteration step versus reusing the Jacobian matrix over the iterations 39 | (and the time steps). Reusing the Jacobian matrix leads to a larger number of iterations per time 40 | steps, typically ~10 iterations instead of ~5 when updating the Jacobian matrix, but the compute 41 | time is drastically reduced from ca. 20 min. to only 25 s. per time step. The results were produced 42 | with an AMD CPU Ryzen 5 1600, using 12 threads. 43 | 44 | .. figure:: ../../figs/reuse_jac_iterations.png 45 | :width: 600px 46 | :align: center 47 | 48 | **Figure 1**: Comparison of the convergence behavior of the Newton procedure when updating or 49 | reusing the Jacobian matrix. The residuals are plotted for the time steps 31 to 36 of the 3D 50 | FSI pipe flow problem (see above animation). The average execution time for each time step with 51 | updated Jacobian is 1170 seconds and 25 seconds for the reused Jacobian. 52 | 53 | 54 | HPC performance 55 | ~~~~~~~~~~~~~~~ 56 | turtleFSI benefits from the high-performance computing (HPC) functionality of FEniCS and the solver 57 | can be executed with MPI parallel tasks as follow without any addition to the code:: 58 | 59 | mpirun -np 4 turtleFSI 60 | 61 | We performed a strong scaling of a 3D FSI pipe flow to illustrate the behavior of the solver using a 62 | relatively large number of parallel MPI tasks. We present the results obtained at the second time step 63 | of the simulation starting from initial rest. We demonstrate an adequate scaling using up to 64 cores 64 | of one cluster node, both executing turtleFSI from a module installation or within a docker container 65 | (Figure 2c). A direct consequence of splitting the geometry in several MPI domains (Figure 2b) is an 66 | increase of the system size associated with the handling of the degree of freedoms along the inner 67 | split boundaries. We illustrate this effect in Figure d where the total memory usage is monitored as 68 | function of the number of MPI tasks used to solve the problem. In our example, we reuse the Jacobian 69 | matrix and the factorization of the direct solver over five Newton’s iterations. As shown in Figure 2c, 70 | the total execution time for computing the Jacobian matrix once and factorization of the system is about 71 | two orders of magnitude larger than solving five iteration steps by reusing the factorization. 72 | 73 | .. figure:: ../../figs/figure_hpc2.png 74 | :width: 600px 75 | :align: center 76 | 77 | **Figure 2**: Strong scaling of a 3D FSI pipe flow problem. a) Meshing of the inner fluid domain, and 78 | outer solid pipe with a total of 63 thousand elements. b) Split of the geometry in 16 MPI domains. 79 | c) Total time spent for one time step (jac.: Jacobian matrix evaluation, fact.: direct solver 80 | factorization step, it.: direct solver solve steps) as function of the number of MPI tasks. d) 81 | System memory usage as function of the number of MPI tasks for three different mesh discretizations 82 | of the problem illustrated in panel a). 83 | 84 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | 2 | name: turtleFSI 3 | channels: 4 | - conda-forge 5 | dependencies: 6 | - fenics-dolfin 7 | - metis=5.1.0 8 | - hdf5=1.12.2 9 | - mpi4py=3.1.4 10 | - python>3.7 11 | - pip 12 | - git 13 | - scipy 14 | - configargparse 15 | - numpy 16 | - pyyaml -------------------------------------------------------------------------------- /figs/Turtle_Flow_Pressure_Fields_t_2.5s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KVSlab/turtleFSI/5fa413853b1c89283674ccaa7053f664570206ff/figs/Turtle_Flow_Pressure_Fields_t_2.5s.png -------------------------------------------------------------------------------- /figs/Turtle_boundaries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KVSlab/turtleFSI/5fa413853b1c89283674ccaa7053f664570206ff/figs/Turtle_boundaries.png -------------------------------------------------------------------------------- /figs/Turtle_boundaries_zoom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KVSlab/turtleFSI/5fa413853b1c89283674ccaa7053f664570206ff/figs/Turtle_boundaries_zoom.png -------------------------------------------------------------------------------- /figs/Turtle_inlet_vel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KVSlab/turtleFSI/5fa413853b1c89283674ccaa7053f664570206ff/figs/Turtle_inlet_vel.png -------------------------------------------------------------------------------- /figs/figure_hpc2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KVSlab/turtleFSI/5fa413853b1c89283674ccaa7053f664570206ff/figs/figure_hpc2.png -------------------------------------------------------------------------------- /figs/movie_36_tstep.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KVSlab/turtleFSI/5fa413853b1c89283674ccaa7053f664570206ff/figs/movie_36_tstep.gif -------------------------------------------------------------------------------- /figs/reuse_jac_iterations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KVSlab/turtleFSI/5fa413853b1c89283674ccaa7053f664570206ff/figs/reuse_jac_iterations.png -------------------------------------------------------------------------------- /figs/turek_benchmark.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KVSlab/turtleFSI/5fa413853b1c89283674ccaa7053f664570206ff/figs/turek_benchmark.gif -------------------------------------------------------------------------------- /figs/turtleFSI_swim.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KVSlab/turtleFSI/5fa413853b1c89283674ccaa7053f664570206ff/figs/turtleFSI_swim.gif -------------------------------------------------------------------------------- /paper/cfd_illu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KVSlab/turtleFSI/5fa413853b1c89283674ccaa7053f664570206ff/paper/cfd_illu.png -------------------------------------------------------------------------------- /paper/csm_illu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KVSlab/turtleFSI/5fa413853b1c89283674ccaa7053f664570206ff/paper/csm_illu.png -------------------------------------------------------------------------------- /paper/fsi_illu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KVSlab/turtleFSI/5fa413853b1c89283674ccaa7053f664570206ff/paper/fsi_illu.png -------------------------------------------------------------------------------- /paper/paper.bib: -------------------------------------------------------------------------------- 1 | @Article{Moin:1998, 2 | Title = {Direct numerical simulation: a tool in turbulence research.}, 3 | author = {{Moin}, P. and {Mahesh}, K.}, 4 | Journal = {Annual review of fluid mechanics}, 5 | Year = {1998}, 6 | Number = {1}, 7 | Pages = {539 -- 578}, 8 | Volume = {30}, 9 | Doi = {10.1146/annurev.fluid.30.1.539}, 10 | publisher = {} 11 | } 12 | 13 | @Article{Holzapfel:2002, 14 | Title = {Nonlinear solid mechanics: a continuum approach for engineering science.}, 15 | author = {{Holzapfel}, G. A.}, 16 | Journal = {Meccanica}, 17 | Year = {2002}, 18 | Number = {4}, 19 | Pages = {489 -- 490}, 20 | Volume = {37}, 21 | Doi = {10.1023/A:1020843529530}, 22 | publisher = {} 23 | } 24 | 25 | @Article{LeTallec:2001, 26 | Title = {Fluid structure interaction with large structural displacements.}, 27 | author = {{Le Tallec}, P. and {Mouro}, J.}, 28 | Journal = {Computer methods in applied mechanics and engineering}, 29 | Year = {2001}, 30 | Number = {24}, 31 | Pages = {3039 -- 3067}, 32 | Volume = {190}, 33 | Doi = {10.1016/S0045-7825(00)00381-9}, 34 | publisher = {} 35 | } 36 | 37 | @Article{Gjertsen:2017, 38 | Title = {Development of a Verified and Validated Computational Framework for Fluid-Structure Interaction: Investigating Lifting Operators and Numerical Stability}, 39 | author = {{Gjertsen}, Sebastian}, 40 | Journal = {MSc Thesis, University of Oslo}, 41 | Year = {2017}, 42 | Number = {}, 43 | Pages = {}, 44 | Volume = {}, 45 | Doi = {}, 46 | publisher = {}, 47 | Adsurl = {https://www.duo.uio.no/handle/10852/57788} 48 | } 49 | 50 | @Article{Slyngstad:2017, 51 | Title = {Verification and Validation of a Monolithic Fluid-Structure Interaction Solver in FEniCS. A comparison of mesh lifting operators.}, 52 | author = {{Slyngstad}, Andreas}, 53 | Journal = {MSc Thesis, University of Oslo}, 54 | Year = {2017}, 55 | Adsurl = {https://www.duo.uio.no/handle/10852/60349}, 56 | Number = {}, 57 | Pages = {}, 58 | Volume = {}, 59 | Doi = {}, 60 | publisher = {} 61 | } 62 | 63 | @Article{Wick:2011, 64 | Title = {Fluid-structure interactions using different mesh motion techniques.}, 65 | author = {{Wick}, Thomas}, 66 | Journal = {Computers & Structures}, 67 | Year = {2011}, 68 | Number = {89}, 69 | Pages = {1456 -- 1467}, 70 | Volume = {13}, 71 | Doi = {10.1016/j.compstruc.2011.02.019}, 72 | publisher = {} 73 | } 74 | 75 | @book{Logg:2012, 76 | Adsurl = {https://fenicsproject.org/book/}, 77 | Author = {{Logg}, A. and {Mardal}, K-A. and {Wells}, G.}, 78 | Title = {Automated solution of differential equations by the finite element method: The FEniCS book.}, 79 | Publisher = {Springer Science & Business Media}, 80 | Volume = {84}, 81 | Year = {2012}, 82 | Doi = {10.1007/978-3-642-23099-8} 83 | } 84 | 85 | @incollection{Turek:2006, 86 | Title={Proposal for numerical benchmarking of fluid-structure interaction between an elastic object and laminar incompressible flow.}, 87 | author={{Turek}, Stefan and {Hron}, Jaroslav}, 88 | booktitle={Fluid-structure interaction}, 89 | pages={371--385}, 90 | year={2006}, 91 | Doi = {10.1007/3-540-34596-5_15}, 92 | publisher={Springer} 93 | } 94 | 95 | @article{Malinen:2013, 96 | title={Elmer finite element solver for multiphysics and multiscale problems}, 97 | author={Malinen, Mika and R{\aa}back, P}, 98 | journal={Multiscale Model. Methods Appl. Mater. Sci.}, 99 | volume={19}, 100 | pages={101--113}, 101 | year={2013}, 102 | publisher={Schriften des Forschungszentrums Julich, IAS Series Julich, Germany} 103 | } 104 | 105 | @incollection{Heil:2006, 106 | title={oomph-lib--an object-oriented multi-physics finite-element library}, 107 | author={Heil, Matthias and Hazel, Andrew L}, 108 | booktitle={Fluid-structure interaction}, 109 | pages={19--49}, 110 | year={2006}, 111 | doi={10.1007/3-540-34596-5_2}, 112 | publisher={Springer} 113 | } 114 | 115 | @inproceedings{Jasak:2007, 116 | title={OpenFOAM: A C++ library for complex physics simulations}, 117 | author={Jasak, Hrvoje and Jemcov, Aleksandar and Tukovic, Zeljko and others}, 118 | booktitle={International workshop on coupled methods in numerical dynamics}, 119 | volume={1000}, 120 | pages={1--20}, 121 | year={2007}, 122 | organization={IUC Dubrovnik Croatia} 123 | } 124 | 125 | -------------------------------------------------------------------------------- /paper/paper.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'turtleFSI: A Robust and Monolithic FEniCS-based Fluid-Structure Interaction Solver' 3 | tags: 4 | - fluid-structure interaction 5 | - FEniCS 6 | - finite-elements 7 | - numerical methods 8 | authors: 9 | - name: Aslak W. Bergersen 10 | orcid: 0000-0001-5063-3680 11 | affiliation: 1 12 | - name: Andreas Slyngstad 13 | affiliation: 1 14 | - name: Sebastian Gjertsen 15 | affiliation: 1 16 | - name: Alban Souche 17 | orcid: 0000-0001-7547-7979 18 | affiliation: 1 19 | - name: Kristian Valen-Sendstad 20 | orcid: 0000-0002-2907-0171 21 | affiliation: 1 22 | affiliations: 23 | - name: Department of Computational Physiology, Simula Research Laboratory, Fornebu, Norway 24 | index: 1 25 | date: 15 June 2020 26 | bibliography: paper.bib 27 | --- 28 | 29 | # Summary 30 | 31 | It is often sufficient to study fluids [@Moin:1998] and solids [@Holzapfel:2002] in isolation to gain fundamental insights into a physical problem, as other factors may play a secondary role and can be neglected. On the other hand, there are certain phenomena or situations where the stresses on or by a fluid or a solid can lead to large deformations, and the interaction between fluids and solids are essential [@LeTallec:2001]. Computational fluid-structure interaction (FSI) is an active field of research with much focus on numerical accuracy, stability, and convergence rates. At the same time, there is also a sweet spot in between these areas of research where there is a need to experiment with FSI without having an in-depth, bottom-up mathematical understanding of the problem, but where a physical insight might suffice. Therefore, the aim was to develop a fully monolithic and robust entry-level research code with ease-of-use targeted towards students, educators, and researchers. 32 | 33 | FEniCS [@Logg:2012] has emerged as one of the leading platforms for development of scientific software due to the close connection between mathematical notation and compact computer implementation, where highly efficient C++ code is compiled during execution of a program. Combined with the out-of-the-box entry-level high-performance computing capabilities, FEniCS was a natural choice of computing environment. Compared to other open-source FSI solvers [@Malinen:2013; @Heil:2006; @Jasak:2007], turtleFSI is written in only a couple of hundred lines of high-level Python code, in contrast to tens of thousands of lines of low-level C++ code. This provides full transparency and a unique opportunity for researchers and educators to modify and experiment with the code, while still providing out of the box entry-level high-performance computing capabilities. Furthermore, because of the close resemblance between mathematics and code in FEniCS, users can make additions or modifications with ease. 34 | 35 | The turtleFSI solver relies on a fully monolithic approach in the classical arbitrary Lagrangian-Eulerian formulation, and we used the generalized theta scheme for temporal discretization and P2P1P2 elements for velocity, pressure, and displacement, respectively. We implemented and evaluated four different mesh lifting operators, ranging from a simple and efficient second-order Laplace equation, most suitable for small deformations, to more sophisticated and computationally expensive 4th order bi-harmonic equations that can handle larger mesh deformations. We used The Method of Manufactured Solutions to verify the implementation. The obtained results are formally second-order accurate (L2) in space and time [@Wick:2011], respectively, and we demonstrate that all building blocks of code exhibit desired properties. The solver's validity was confirmed using the classical Turek Flag benchmark case [@Turek:2006] with a good agreement – including a diverged numerical solution for long term evolution under certain conditions, as expected. For a complete justification of computational approaches and further details, we refer to [@Slyngstad:2017; @Gjertsen:2017]. We demonstrate adequate strong scaling up to 64 cores (from one cluster node), although the latter is problem size-dependent. In the online documentation, we provide benchmarks, tutorials, and simple demos. The naive FEniCS implementation provides full transparency with compact code, which can easily be adapted to other 2D or 3D FSI problems. 36 | 37 | In conclusion, turtleFSI is not a superior FSI solver in terms of speed, but it is a robust entry-level FSI solver and performs exactly as designed and intended; ‘slow and steady wins the race’. 38 | 39 | 40 | # turtleFSI in Action 41 | 42 | turtleFSI comes with several problem files, found under /turtleFSI/problems/, to illustrate the usage and document the Turek flag benchmarks used to validate the implementation of the solver. Here are some illustrations of the execution and outputs expected from the solver. 43 | 44 | ![Fluid_Turek*='#center'](./cfd_illu.png){ width=100% }\ 45 | **Figure 1:** 46 | Fluid dynamics benchmark snapshot. Simulation executed with the command: 47 | ``` 48 | turtleFSI --problem TF_cfd 49 | ``` 50 | 51 | ![Solid_Turek*='#center'](./csm_illu.png){ width=100% }\ 52 | **Figure 2:** 53 | Solid mechanics benchmark snapshots. Simulation executed with the command: 54 | ``` 55 | turtleFSI --problem TF_csm 56 | ``` 57 | 58 | ![FSI_Turek*='#center'](./fsi_illu.png){ width=100% }\ 59 | **Figure 3:** 60 | Full fluid-structure interaction benchmark snapshot. Simulation executed with the command: 61 | ``` 62 | turtleFSI --problem TF_fsi 63 | ``` 64 | 65 | # Acknowledgements 66 | The study was supported by The Research Council of Norway through the Center for Biomedical Computing (grant 179578), the Centre for Cardiological Innovation (grant number 203489), and the SIMMIS project (grant number 262827). Simulations were performed on the Abel Cluster (University of Oslo and the Norwegian metacenter for High Performance Computing (NOTUR), project nn9316k), and the Experimental Infrastructure for Exploration of Exascale Computing (eX3) cluster (Norwegian Research Council grant 270053). 67 | 68 | # References 69 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open("README.md", "r") as fh: 4 | long_description = fh.read() 5 | 6 | DEPENDENCIES = ['configargparse', "fenics-dolfin", 7 | "scipy", "numpy", "pyyaml"] 8 | TEST_DEPENDENCIES = ['pytest'] 9 | 10 | VERSION = "2.4" 11 | URL = "https://github.com/KVSlab/turtleFSI.git" 12 | 13 | setuptools.setup( 14 | name="turtleFSI", 15 | version=VERSION, 16 | license="GPL", 17 | author="", 18 | author_email="", 19 | url=URL, 20 | project_urls={ 21 | "Documentation": "https://turtlefsi.readthedocs.io/", 22 | "Source Code": URL, 23 | }, 24 | description="turtleFSI - Fluid-structure interaction", 25 | long_description=long_description, 26 | long_description_content_type="text/markdown", 27 | 28 | # Dependencies 29 | install_requires=DEPENDENCIES, 30 | tests_require=TEST_DEPENDENCIES, 31 | 32 | classifiers=[ 33 | 'Intended Audience :: Developers', 34 | 'Intended Audience :: Science/Research', 35 | "Programming Language :: Python :: 3", 36 | ], 37 | packages=["turtleFSI", 38 | "turtleFSI.modules", 39 | "turtleFSI.problems", 40 | "turtleFSI.utils"], 41 | package_dir={"turtleFSI": "turtleFSI"}, 42 | include_package_data=True, 43 | entry_points={'console_scripts': ['turtleFSI=turtleFSI.run_turtle:main']}, 44 | 45 | ) 46 | -------------------------------------------------------------------------------- /tests/test_turtleFSI.py: -------------------------------------------------------------------------------- 1 | # File under GNU GPL (v3) licence, see LICENSE file for details. 2 | # This software is distributed WITHOUT ANY WARRANTY; without even 3 | # the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 4 | # PURPOSE. 5 | 6 | import pytest 7 | import numpy as np 8 | from pathlib import Path 9 | import subprocess 10 | 11 | 12 | def test_cfd(): 13 | cmd = ("turtleFSI --problem TF_cfd -dt 0.01 -T 0.05 --verbose True" + 14 | " --folder tmp --sub-folder 1") 15 | subprocess.run(cmd, shell=True, check=True) 16 | 17 | drag = np.loadtxt(Path.cwd().joinpath("tmp/1/Drag.txt"))[-1] 18 | lift = np.loadtxt(Path.cwd().joinpath("tmp/1/Lift.txt"))[-1] 19 | drag_reference = 4.503203576965564 20 | lift_reference = -0.03790359084395478 21 | 22 | assert np.isclose(drag, drag_reference) 23 | assert np.isclose(lift, lift_reference) 24 | 25 | 26 | def test_csm(): 27 | cmd = ("turtleFSI --problem TF_csm -dt 0.01 -T 0.05 --verbose True" + 28 | " --folder tmp --sub-folder 2") 29 | subprocess.run(cmd, shell=True, check=True) 30 | 31 | distance_x = np.loadtxt("tmp/2/dis_x.txt")[-1] 32 | distance_y = np.loadtxt("tmp/2/dis_y.txt")[-1] 33 | distance_x_reference = -3.313014369394714527e-05 34 | distance_y_reference = -3.770127311444726199e-03 35 | 36 | assert np.isclose(distance_x, distance_x_reference) 37 | assert np.isclose(distance_y, distance_y_reference) 38 | 39 | 40 | @pytest.mark.parametrize("num_p", [1, 2]) 41 | def test_fsi(num_p): 42 | cmd = ("mpirun -np {} turtleFSI --problem TF_fsi -dt 0.01 -T 0.05 --verbose True" + 43 | " --theta 0.51 --folder tmp --sub-folder 3") 44 | subprocess.run(cmd.format(num_p), shell=True, check=True) 45 | 46 | drag = np.loadtxt("tmp/3/Drag.txt")[-1] 47 | lift = np.loadtxt("tmp/3/Lift.txt")[-1] 48 | distance_x = np.loadtxt("tmp/3/dis_x.txt")[-1] 49 | distance_y = np.loadtxt("tmp/3/dis_y.txt")[-1] 50 | distance_x_reference = -6.896013956339182e-06 51 | distance_y_reference = 1.876355330341896e-09 52 | drag_reference = 4.407481239804155 53 | lift_reference = -0.005404703556977697 54 | 55 | assert np.isclose(distance_x, distance_x_reference) 56 | assert np.isclose(distance_y, distance_y_reference) 57 | assert np.isclose(drag, drag_reference) 58 | assert np.isclose(lift, lift_reference) 59 | 60 | 61 | @pytest.mark.parametrize("extrapolation_sub_type", ["volume", "volume_change", 62 | "constant", "small_constant"]) 63 | def test_laplace(extrapolation_sub_type): 64 | cmd = ("turtleFSI --problem TF_fsi -dt 0.01 -T 0.05 --verbose True --theta 0.51" + 65 | " --extrapolation laplace --extrapolation-sub-type {}" + 66 | " --folder tmp --sub-folder 4") 67 | subprocess.run(cmd.format(extrapolation_sub_type), shell=True, check=True) 68 | drag = np.loadtxt("tmp/4/Drag.txt")[-1] 69 | lift = np.loadtxt("tmp/4/Lift.txt")[-1] 70 | distance_x = np.loadtxt("tmp/4/dis_x.txt")[-1] 71 | distance_y = np.loadtxt("tmp/4/dis_y.txt")[-1] 72 | distance_x_reference = -6.896013956339182e-06 73 | distance_y_reference = 1.876355330341896e-09 74 | drag_reference = 4.407481239804155 75 | lift_reference = -0.005404703556977697 76 | 77 | assert np.isclose(distance_x, distance_x_reference) 78 | assert np.isclose(distance_y, distance_y_reference) 79 | assert np.isclose(drag, drag_reference) 80 | assert np.isclose(lift, lift_reference, rtol=1e-4) 81 | 82 | 83 | @pytest.mark.parametrize("extrapolation_sub_type", 84 | ["constrained_disp", "constrained_disp_vel"]) 85 | def test_biharmonic(extrapolation_sub_type): 86 | cmd = ("turtleFSI --problem TF_fsi -dt 0.01 -T 0.05 --verbose True --theta 0.51" + 87 | " --extrapolation biharmonic --extrapolation-sub-type {}" + 88 | " --folder tmp --sub-folder 5") 89 | subprocess.run(cmd.format(extrapolation_sub_type), shell=True, check=True) 90 | 91 | drag = np.loadtxt("tmp/5/Drag.txt")[-1] 92 | lift = np.loadtxt("tmp/5/Lift.txt")[-1] 93 | distance_x = np.loadtxt("tmp/5/dis_x.txt")[-1] 94 | distance_y = np.loadtxt("tmp/5/dis_y.txt")[-1] 95 | distance_x_reference = -6.896013956339182e-06 96 | distance_y_reference = 1.876355330341896e-09 97 | drag_reference = 4.407481239804155 98 | lift_reference = -0.005404703556977697 99 | 100 | assert np.isclose(distance_x, distance_x_reference) 101 | assert np.isclose(distance_y, distance_y_reference) 102 | assert np.isclose(drag, drag_reference) 103 | assert np.isclose(lift, lift_reference) 104 | 105 | 106 | def test_elastic(): 107 | cmd = ("turtleFSI --problem TF_fsi -dt 0.01 -T 0.05 --verbose True --theta 0.51" + 108 | " -e elastic -et constant --folder tmp --sub-folder 6") 109 | subprocess.run(cmd, shell=True, check=True) 110 | 111 | drag = np.loadtxt("tmp/6/Drag.txt")[-1] 112 | lift = np.loadtxt("tmp/6/Lift.txt")[-1] 113 | distance_x = np.loadtxt("tmp/6/dis_x.txt")[-1] 114 | distance_y = np.loadtxt("tmp/6/dis_y.txt")[-1] 115 | distance_x_reference = -6.896144755254494e-06 116 | distance_y_reference = 1.868651990487361e-09 117 | drag_reference = 4.407488867909029 118 | lift_reference = -0.005404616050528832 119 | 120 | assert np.isclose(distance_x, distance_x_reference) 121 | assert np.isclose(distance_y, distance_y_reference) 122 | assert np.isclose(drag, drag_reference) 123 | assert np.isclose(lift, lift_reference) 124 | 125 | 126 | def test_save_deg2(): 127 | """simple test if the save_deg 2 works""" 128 | cmd = ("turtleFSI --problem TF_fsi -dt 0.01 -T 0.05 --theta 0.51 --save-deg 2" + 129 | " --save-step 1 --folder tmp --sub-folder 7") 130 | subprocess.run(cmd, shell=True, check=True) 131 | 132 | d_path = Path.cwd().joinpath("tmp/7/Visualization/displacement.xdmf") 133 | v_path = Path.cwd().joinpath("tmp/7/Visualization/velocity.xdmf") 134 | p_path = Path.cwd().joinpath("tmp/7/Visualization/pressure.xdmf") 135 | 136 | assert d_path.is_file() 137 | assert v_path.is_file() 138 | assert p_path.is_file() -------------------------------------------------------------------------------- /turtleFSI/__init__.py: -------------------------------------------------------------------------------- 1 | from .run_turtle import main 2 | -------------------------------------------------------------------------------- /turtleFSI/mesh/TF_cfd.xml.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KVSlab/turtleFSI/5fa413853b1c89283674ccaa7053f664570206ff/turtleFSI/mesh/TF_cfd.xml.gz -------------------------------------------------------------------------------- /turtleFSI/mesh/TF_csm.xml.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KVSlab/turtleFSI/5fa413853b1c89283674ccaa7053f664570206ff/turtleFSI/mesh/TF_csm.xml.gz -------------------------------------------------------------------------------- /turtleFSI/mesh/TF_fsi.xml.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KVSlab/turtleFSI/5fa413853b1c89283674ccaa7053f664570206ff/turtleFSI/mesh/TF_fsi.xml.gz -------------------------------------------------------------------------------- /turtleFSI/mesh/turtle_demo/mc.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KVSlab/turtleFSI/5fa413853b1c89283674ccaa7053f664570206ff/turtleFSI/mesh/turtle_demo/mc.h5 -------------------------------------------------------------------------------- /turtleFSI/mesh/turtle_demo/mc.xdmf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | mc.h5:/data0 6 | 7 | 8 | mc.h5:/data1 9 | 10 | 11 | mc.h5:/data2 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /turtleFSI/mesh/turtle_demo/mf.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KVSlab/turtleFSI/5fa413853b1c89283674ccaa7053f664570206ff/turtleFSI/mesh/turtle_demo/mf.h5 -------------------------------------------------------------------------------- /turtleFSI/mesh/turtle_demo/mf.xdmf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | mf.h5:/data0 6 | 7 | 8 | mf.h5:/data1 9 | 10 | 11 | mf.h5:/data2 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /turtleFSI/mesh/turtle_demo/turtle_mesh.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KVSlab/turtleFSI/5fa413853b1c89283674ccaa7053f664570206ff/turtleFSI/mesh/turtle_demo/turtle_mesh.h5 -------------------------------------------------------------------------------- /turtleFSI/mesh/turtle_demo/turtle_mesh.xdmf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | turtle_mesh.h5:/data0 6 | 7 | 8 | turtle_mesh.h5:/data1 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /turtleFSI/modules/__init__.py: -------------------------------------------------------------------------------- 1 | from .common import * 2 | -------------------------------------------------------------------------------- /turtleFSI/modules/biharmonic.py: -------------------------------------------------------------------------------- 1 | # File under GNU GPL (v3) licence, see LICENSE file for details. 2 | # This software is distributed WITHOUT ANY WARRANTY; without even 3 | # the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 4 | # PURPOSE. 5 | 6 | from dolfin import inner, grad 7 | 8 | 9 | def extrapolate_setup(F_fluid_linear, extrapolation_sub_type, d_, w_, phi, beta, dx_f, 10 | dx_f_id_list, ds, n, bc_ids, **namespace): 11 | """ 12 | Biharmonic lifting operator. Should be used for large deformations. 13 | 14 | alpha * laplace^2(d) = 0 in the fluid domain 15 | 16 | By introducing w = - grad(d) we obtain the equivalent system of equations: 17 | w = - alpha * laplace(d) 18 | - alpha * grad(w) = 0 19 | 20 | Two types of boundary conditions can be setup for this problem: 21 | 22 | - "constrained_disp" with conditions only on (d): 23 | d(d)/dn = 0 on the fluid boundaries other than FSI interface 24 | d = solid_def on the FSI interface 25 | 26 | - "constrained_disp_vel" with conditions on (d) and (w): 27 | d(d(x))/dn = 0 and d(w(x))/dn = 0 on the inlet and outlet fluid boundaries 28 | d(d(y))/dn = 0 and d(w(y))/dn = 0 on the FSI interface 29 | """ 30 | 31 | alpha_u = 0.01 32 | F_ext1 = 0 33 | F_ext2 = 0 34 | for fluid_region in range(len(dx_f_id_list)): # for all fluid regions 35 | F_ext1 += alpha_u * inner(w_["n"], beta) * dx_f[fluid_region] - alpha_u * inner(grad(d_["n"]), grad(beta)) * dx_f[fluid_region] 36 | F_ext2 += alpha_u * inner(grad(w_["n"]), grad(phi)) * dx_f[fluid_region] 37 | 38 | if extrapolation_sub_type == "constrained_disp_vel": 39 | for i in bc_ids: 40 | F_ext1 += alpha_u*inner(grad(d_["n"]) * n, beta) * ds(i) 41 | F_ext2 -= alpha_u*inner(grad(w_["n"]) * n, phi) * ds(i) 42 | 43 | F_fluid_linear += F_ext1 + F_ext2 44 | 45 | return dict(F_fluid_linear=F_fluid_linear) 46 | -------------------------------------------------------------------------------- /turtleFSI/modules/domain.py: -------------------------------------------------------------------------------- 1 | # File under GNU GPL (v3) licence, see LICENSE file for details. 2 | # This software is distributed WITHOUT ANY WARRANTY; without even 3 | # the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 4 | # PURPOSE. 5 | 6 | from turtleFSI.modules import * 7 | from dolfin import ds, MPI 8 | from dolfin.cpp.mesh import MeshFunctionSizet 9 | from typing import Union 10 | 11 | """ 12 | Last update: 2023-06-15 13 | Kei Yamamoto added docstring for assign_domain_properties function and added comments in the function. 14 | """ 15 | 16 | def assign_domain_properties(dx: ufl.measure.Measure, dx_f_id: Union[int, list], rho_f: Union[float, list], 17 | mu_f: Union[float, list], fluid_properties: Union[list, dict], dx_s_id: Union[int, list], 18 | material_model: str, rho_s: Union[float, list], mu_s: Union[float, list], lambda_s: Union[float, list], 19 | solid_properties: Union[list, dict], domains: MeshFunctionSizet, ds_s_id: Union[int, list], 20 | boundaries: MeshFunctionSizet, robin_bc: bool, **namespace): 21 | """ 22 | Assigns solid and fluid properties to each region. 23 | 24 | Args: 25 | dx: Measure of the domain 26 | dx_f_id: ID of the fluid region, or list of IDs of multiple fluid regions 27 | rho_f: Density of the fluid, or list of densities of multiple fluid regions 28 | mu_f: Viscosity of the fluid, or list of viscosities of multiple fluid regions 29 | fluid_properties: Dictionary of fluid properties, or list of dictionaries of fluid properties for multiple fluid regions 30 | dx_s_id: ID of the solid region, or list of IDs of multiple solid regions 31 | material_model: Material model of the solid, or list of material models of multiple solid regions 32 | rho_s: Density of the solid, or list of densities of multiple solid regions 33 | mu_s: Shear modulus or 2nd Lame Coef. of the solid, or list of shear modulus of multiple solid regions 34 | lambda_s: First Lame parameter of the solid, or list of first Lame parameters of multiple solid regions 35 | solid_properties: Dictionary of solid properties, or list of dictionaries of solid properties for multiple solid regions 36 | domains: MeshFunction of the domains 37 | ds_s_id: ID of the solid boundary, or list of IDs of multiple solid boundaries where Robin boundary conditions are applied 38 | boundaries: MeshFunction of the boundaries 39 | robin_bc: True if Robin boundary conditions are used, False otherwise 40 | 41 | Returns: 42 | dx_f: Measure of the fluid domain for each fluid region 43 | dx_f_id_list: List of IDs of single/multiple fluid regions 44 | ds_s_ext_id_list: List of IDs of single/multiple solid boundaries where Robin boundary conditions are applied 45 | ds_s: Measure of the solid boundary for each solid region 46 | fluid_properties: List of dictionaries of fluid properties for single/multiple fluid regions 47 | dx_s: Measure of the solid domain for each solid region 48 | dx_s_id_list: List of IDs of single/multiple solid regions 49 | solid_properties: List of dictionaries of solid properties for single/multiple solid regions 50 | 51 | """ 52 | # DB, May 2nd, 2022: All these conversions to lists seem a bit cumbersome, but this allows the solver to be backwards compatible. 53 | # Work on fluid domain 54 | dx_f = {} 55 | # In case there are multiple fluid regions, we assume that dx_f_id is a list 56 | if isinstance(dx_f_id, list): 57 | for fluid_region in range(len(dx_f_id)): 58 | dx_f[fluid_region] = dx(dx_f_id[fluid_region], subdomain_data=domains) # Create dx_f for each fluid domain 59 | dx_f_id_list=dx_f_id 60 | # In case there is only one fluid region, we assume that dx_f_id is an int 61 | else: 62 | dx_f[0] = dx(dx_f_id, subdomain_data=domains) 63 | dx_f_id_list=[dx_f_id] 64 | # Check if fluid_porperties is empty and if so, create fluid_properties to each region, 65 | if len(fluid_properties) == 0: 66 | if isinstance(dx_f_id, list): 67 | for fluid_region in range(len(dx_f_id)): 68 | fluid_properties.append({"dx_f_id":dx_f_id[fluid_region],"rho_f":rho_f[fluid_region],"mu_f":mu_f[fluid_region]}) 69 | else: 70 | fluid_properties.append({"dx_f_id":dx_f_id,"rho_f":rho_f,"mu_f":mu_f}) 71 | # If fluid_properties is not empty, assume that fluid_properties is given and convert it to a list if it is not a list 72 | elif isinstance(fluid_properties, dict): 73 | fluid_properties = [fluid_properties] 74 | else: 75 | assert isinstance(fluid_properties, list), "fluid_properties must be a list of dictionaries" 76 | 77 | # Work on solid domain and boundary (boundary is only needed if Robin boundary conditions are used) 78 | dx_s = {} 79 | # In case there are multiple solid regions, we assume that dx_s_id is a list 80 | if isinstance(dx_s_id, list): 81 | for solid_region in range(len(dx_s_id)): 82 | dx_s[solid_region] = dx(dx_s_id[solid_region], subdomain_data=domains) # Create dx_s for each solid domain 83 | dx_s_id_list=dx_s_id 84 | else: 85 | dx_s[0] = dx(dx_s_id, subdomain_data=domains) 86 | dx_s_id_list=[dx_s_id] 87 | 88 | # Assign material properties to each solid region 89 | # NOTE: len(solid_properties) == 0 only works for St. Venant-Kirchhoff material model. 90 | # For other material models, solid_properties must be given from config file or inside the problem file. 91 | if len(solid_properties) == 0: 92 | if isinstance(dx_s_id, list): 93 | for solid_region in range(len(dx_s_id)): 94 | if isinstance(material_model, list): 95 | solid_properties.append({"dx_s_id":dx_s_id[solid_region],"material_model":material_model[solid_region],"rho_s":rho_s[solid_region],"mu_s":mu_s[solid_region],"lambda_s":lambda_s[solid_region]}) 96 | else: 97 | solid_properties.append({"dx_s_id":dx_s_id[solid_region],"material_model":material_model,"rho_s":rho_s[solid_region],"mu_s":mu_s[solid_region],"lambda_s":lambda_s[solid_region]}) 98 | else: 99 | solid_properties.append({"dx_s_id":dx_s_id,"material_model":material_model,"rho_s":rho_s,"mu_s":mu_s,"lambda_s":lambda_s}) 100 | elif isinstance(solid_properties, dict): 101 | solid_properties = [solid_properties] 102 | else: 103 | assert isinstance(solid_properties, list), "solid_properties must be a list of dictionaries" 104 | 105 | # Create solid boundary differentials for Robin boundary conditions. 106 | if robin_bc: 107 | ds_s = {} 108 | # In case there are multiple solid boundaries, we assume that ds_s_id is a list and create ds_s for each solid boundary 109 | if isinstance(ds_s_id, list): 110 | for i, solid_boundaries in enumerate(ds_s_id): 111 | ds_s[i] = ds(solid_boundaries, subdomain_data=boundaries) 112 | ds_s_ext_id_list=ds_s_id 113 | else: 114 | ds_s[0] = ds(ds_s_id, subdomain_data=boundaries) 115 | ds_s_ext_id_list=[ds_s_id] 116 | # If Robin boundary conditions are not used, set ds_s and ds_s_ext_id_list to None. 117 | else: 118 | ds_s = None 119 | ds_s_ext_id_list = None 120 | 121 | return dict(dx_f=dx_f, dx_f_id_list=dx_f_id_list, ds_s_ext_id_list=ds_s_ext_id_list, ds_s=ds_s, fluid_properties=fluid_properties, dx_s=dx_s, dx_s_id_list=dx_s_id_list, solid_properties=solid_properties) 122 | -------------------------------------------------------------------------------- /turtleFSI/modules/elastic.py: -------------------------------------------------------------------------------- 1 | # File under GNU GPL (v3) licence, see LICENSE file for details. 2 | # This software is distributed WITHOUT ANY WARRANTY; without even 3 | # the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 4 | # PURPOSE. 5 | 6 | from turtleFSI.modules import * 7 | from dolfin import CellVolume, inner, grad, inv 8 | 9 | 10 | def extrapolate_setup(F_fluid_linear, mesh, d_, phi, gamma, dx_f, dx_f_id_list, **namespace): 11 | """ 12 | Elastic lifting operator solving the equation of linear elasticity. 13 | 14 | div(sigma(d)) = 0 in the fluid domain 15 | d = 0 on the fluid boundaries other than FSI interface 16 | d = solid_d on the FSI interface 17 | """ 18 | E_y = 1.0 / CellVolume(mesh) 19 | nu = 0.25 20 | alpha_lam = nu * E_y / ((1.0 + nu) * (1.0 - 2.0 * nu)) 21 | alpha_mu = E_y / (2.0 * (1.0 + nu)) 22 | F_extrapolate = 0 23 | for fluid_region in range(len(dx_f_id_list)): # for all fluid regions 24 | F_extrapolate += inner(J_(d_["n"]) * S_linear(d_["n"], alpha_mu, alpha_lam) * 25 | inv(F_(d_["n"])).T, grad(phi))*dx_f[fluid_region] 26 | 27 | F_fluid_linear += F_extrapolate 28 | 29 | return dict(F_fluid_linear=F_fluid_linear) 30 | -------------------------------------------------------------------------------- /turtleFSI/modules/fluid.py: -------------------------------------------------------------------------------- 1 | # File under GNU GPL (v3) licence, see LICENSE file for details. 2 | # This software is distributed WITHOUT ANY WARRANTY; without even 3 | # the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 4 | # PURPOSE. 5 | 6 | from turtleFSI.modules import * 7 | from dolfin import Constant, inner, inv, grad, div 8 | 9 | 10 | def fluid_setup(v_, p_, d_, psi, gamma, dx_f, dx_f_id_list, fluid_properties, k, theta, **namespace): 11 | """ 12 | ALE formulation (theta-scheme) of the incompressible Navier-Stokes flow problem: 13 | 14 | du/dt + u * grad(u - w) = grad(p) + nu * div(grad(u)) 15 | div(u) = 0 16 | """ 17 | 18 | theta0 = Constant(theta) 19 | theta1 = Constant(1 - theta) 20 | 21 | F_fluid_linear = 0 22 | F_fluid_nonlinear = 0 23 | 24 | for fluid_region in range(len(dx_f_id_list)): 25 | rho_f = fluid_properties[fluid_region]["rho_f"] 26 | mu_f = fluid_properties[fluid_region]["mu_f"] 27 | 28 | # Note that we here split the equation into a linear and nonlinear part for faster 29 | # computation of the Jacobian matrix. 30 | 31 | # Temporal derivative 32 | F_fluid_nonlinear += rho_f / k * inner(J_(d_["n"]) * theta0 * (v_["n"] - v_["n-1"]), psi) * dx_f[fluid_region] 33 | F_fluid_linear += rho_f / k * inner(J_(d_["n-1"]) * theta1 * (v_["n"] - v_["n-1"]), psi) * dx_f[fluid_region] 34 | 35 | # Convection 36 | F_fluid_nonlinear += theta0 * rho_f * inner(grad(v_["n"]) * inv(F_(d_["n"])) * 37 | J_(d_["n"]) * v_["n"], psi) * dx_f[fluid_region] 38 | F_fluid_linear += theta1 * rho_f * inner(grad(v_["n-1"]) * inv(F_(d_["n-1"])) * 39 | J_(d_["n-1"]) * v_["n-1"], psi) * dx_f[fluid_region] 40 | 41 | # Stress from pressure 42 | F_fluid_nonlinear += inner(J_(d_["n"]) * sigma_f_p(p_["n"], d_["n"]) * 43 | inv(F_(d_["n"])).T, grad(psi)) * dx_f[fluid_region] 44 | 45 | # Stress from velocity 46 | F_fluid_nonlinear += theta0 * inner(J_(d_["n"]) * sigma_f_u(v_["n"], d_["n"], mu_f) * 47 | inv(F_(d_["n"])).T, grad(psi)) * dx_f[fluid_region] 48 | F_fluid_linear += theta1 * inner(J_(d_["n-1"]) * sigma_f_u(v_["n-1"], d_["n-1"], mu_f) 49 | * inv(F_(d_["n-1"])).T, grad(psi)) * dx_f[fluid_region] 50 | 51 | # Divergence free term 52 | F_fluid_nonlinear += inner(div(J_(d_["n"]) * inv(F_(d_["n"])) * v_["n"]), gamma) * dx_f[fluid_region] 53 | 54 | # ALE term 55 | F_fluid_nonlinear -= rho_f / k * inner(J_(d_["n"]) * grad(v_["n"]) * inv(F_(d_["n"])) * 56 | (d_["n"] - d_["n-1"]), psi) * dx_f[fluid_region] 57 | 58 | return dict(F_fluid_linear=F_fluid_linear, F_fluid_nonlinear=F_fluid_nonlinear) 59 | -------------------------------------------------------------------------------- /turtleFSI/modules/laplace.py: -------------------------------------------------------------------------------- 1 | # File under GNU GPL (v3) licence, see LICENSE file for details. 2 | # This software is distributed WITHOUT ANY WARRANTY; without even 3 | # the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 4 | # PURPOSE. 5 | 6 | from dolfin import inner, inv, grad, CellVolume 7 | from turtleFSI.modules import * 8 | 9 | 10 | def extrapolate_setup(F_fluid_linear, extrapolation_sub_type, mesh, d_, phi, 11 | dx_f, dx_f_id_list, **namespace): 12 | """ 13 | Laplace lifting operator. Can be used for small to moderate deformations. 14 | The diffusion parameter "alfa", which is specified by "extrapolation_sub_type", 15 | can be used to control the deformation field from the wall boundaries to the 16 | fluid domain. "alfa" is assumed constant within elements. 17 | 18 | - alfa * laplace(d) = 0 in the fluid domain 19 | d = 0 on the fluid boundaries other than FSI interface 20 | d = solid_def on the FSI interface 21 | 22 | References: 23 | 24 | Slyngstad, Andreas Strøm. Verification and Validation of a Monolithic 25 | Fluid-Structure Interaction Solver in FEniCS. A comparison of mesh lifting 26 | operators. MS thesis. 2017. 27 | 28 | Gjertsen, Sebastian. Development of a Verified and Validated Computational 29 | Framework for Fluid-Structure Interaction: Investigating Lifting Operators 30 | and Numerical Stability. MS thesis. 2017. 31 | """ 32 | 33 | if extrapolation_sub_type == "volume_change": 34 | alfa = 1.0 / (J_(d_["n"])) 35 | elif extrapolation_sub_type == "volume": 36 | alfa = 1.0 / CellVolume(mesh) 37 | elif extrapolation_sub_type == "small_constant": 38 | alfa = 0.01 * (mesh.hmin())**2 39 | elif extrapolation_sub_type == "constant": 40 | alfa = 1.0 41 | else: 42 | raise RuntimeError("Could not find extrapolation method {}".format(extrapolation_sub_type)) 43 | 44 | for fluid_region in range(len(dx_f_id_list)): # for all fluid regions 45 | F_extrapolate = alfa * inner(grad(d_["n"]), grad(phi)) * dx_f[fluid_region] 46 | F_fluid_linear += F_extrapolate 47 | 48 | return dict(F_fluid_linear=F_fluid_linear) 49 | -------------------------------------------------------------------------------- /turtleFSI/modules/newtonsolver.py: -------------------------------------------------------------------------------- 1 | # File under GNU GPL (v3) licence, see LICENSE file for details. 2 | # This software is distributed WITHOUT ANY WARRANTY; without even 3 | # the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 4 | # PURPOSE. 5 | 6 | from dolfin import assemble, derivative, TrialFunction, Matrix, norm, MPI, PETScOptions 7 | from numpy import isnan 8 | 9 | PETScOptions.set("mat_mumps_icntl_4", 1) # If negatvie or zero, MUMPS will suppress diagnositc printining, statistics, and warning messages. 10 | PETScOptions.set("mat_mumps_icntl_14", 400) # allocate more memory to mumps 11 | 12 | 13 | def solver_setup(F_fluid_linear, F_fluid_nonlinear, F_solid_linear, F_solid_nonlinear, 14 | DVP, dvp_, up_sol, compiler_parameters, **namespace): 15 | """ 16 | Pre-assemble the system of equations for the Jacobian matrix for the Newton solver 17 | """ 18 | F_lin = F_fluid_linear + F_solid_linear 19 | F_nonlin = F_solid_nonlinear + F_fluid_nonlinear 20 | F = F_lin + F_nonlin 21 | 22 | chi = TrialFunction(DVP) 23 | J_linear = derivative(F_lin, dvp_["n"], chi) 24 | J_nonlinear = derivative(F_nonlin, dvp_["n"], chi) 25 | 26 | A_pre = assemble(J_linear, form_compiler_parameters=compiler_parameters, 27 | keep_diagonal=True) 28 | A = Matrix(A_pre) 29 | b = None 30 | 31 | return dict(F=F, J_nonlinear=J_nonlinear, A_pre=A_pre, A=A, b=b, up_sol=up_sol) 32 | 33 | 34 | def newtonsolver(F, J_nonlinear, A_pre, A, b, bcs, lmbda, recompute, recompute_tstep, compiler_parameters, 35 | dvp_, up_sol, dvp_res, rtol, atol, max_it, counter, first_step_num, verbose, **namespace): 36 | """ 37 | Solve the non-linear system of equations with Newton scheme. The standard is to compute the Jacobian 38 | every time step, however this is computationally costly. We have therefore added two parameters for 39 | re-computing only every 'recompute' iteration, or for every 'recompute_tstep' time step. Setting 'recompute' 40 | to != 1 is faster, but can impact the convergence rate. Altering 'recompute_tstep' is considered an advanced option, 41 | and should be used with care. 42 | """ 43 | # Initial values 44 | iter = 0 45 | residual = 10**8 46 | rel_res = 10**8 47 | 48 | # Capture if residual increases from last iteration 49 | last_rel_res = residual 50 | last_residual = rel_res 51 | up_sol.set_operator(A) 52 | while rel_res > rtol and residual > atol and iter < max_it: 53 | # Check if recompute Jacobian from 'recompute_tstep' (time step) 54 | recompute_for_timestep = iter == 0 and (counter % recompute_tstep == 0) 55 | 56 | # Check if recompute Jacobian from 'recompute' (iteration) 57 | recompute_frequency = iter > 0 and iter % recompute == 0 58 | 59 | # Recompute Jacobian due to increased residual 60 | recompute_residual = iter > 0 and last_residual < residual 61 | 62 | # Recompute Jacobian on first step of simulation (important if restart is used) 63 | recompute_initialize = iter == 0 and counter == first_step_num 64 | 65 | if recompute_for_timestep or recompute_frequency or recompute_residual or recompute_initialize: 66 | if MPI.rank(MPI.comm_world) == 0 and verbose: 67 | print("Compute Jacobian matrix") 68 | # Assemble non-linear part of Jacobian, keep sparsity pattern (keep_diagonal=True) 69 | # Here, we assume that A is already assembled with the linear part of the Jacobian, and not None type 70 | if A is None: 71 | A = assemble(J_nonlinear, 72 | form_compiler_parameters=compiler_parameters, 73 | keep_diagonal=True) 74 | else: 75 | assemble(J_nonlinear, tensor=A, 76 | form_compiler_parameters=compiler_parameters, 77 | keep_diagonal=True) 78 | # Add non-linear and linear part of Jacobian 79 | A.axpy(1.0, A_pre, True) 80 | # Insert ones on diagonal to make sure the matrix is non-singular (related to solid pressure being zero) 81 | A.ident_zeros() 82 | [bc.apply(A) for bc in bcs] 83 | 84 | # Aseemble right hand side vector 85 | if b is None: 86 | b = assemble(-F) 87 | else: 88 | assemble(-F, tensor=b) 89 | 90 | # Apply boundary conditions before solve 91 | [bc.apply(b, dvp_["n"].vector()) for bc in bcs] 92 | # Solve the linear system A * x = b where A is the Jacobian matrix, x is the Newton increment and b is the -residual 93 | up_sol.solve(dvp_res.vector(), b) 94 | # Update solution using the Newton increment 95 | dvp_["n"].vector().axpy(lmbda, dvp_res.vector()) 96 | # After adding the residual to the solution, we need to re-apply the boundary conditions 97 | # because the residual (dvp_res.vector) is not guaranteed to satisfy the boundary conditions 98 | [bc.apply(dvp_["n"].vector()) for bc in bcs] 99 | 100 | # Reset residuals 101 | last_residual = residual 102 | last_rel_res = rel_res 103 | 104 | # Check residual 105 | residual = b.norm('l2') 106 | rel_res = norm(dvp_res, 'l2') 107 | if rel_res > 1E20 or residual > 1E20 or isnan(rel_res) or isnan(residual): 108 | raise RuntimeError("Error: The simulation has diverged during the Newton solve with residual = %.3e and relative residual = %.3e" % (residual, rel_res)) 109 | 110 | if MPI.rank(MPI.comm_world) == 0 and verbose: 111 | print("Newton iteration %d: r (atol) = %.3e (tol = %.3e), r (rel) = %.3e (tol = %.3e) " 112 | % (iter, residual, atol, rel_res, rtol)) 113 | iter += 1 114 | 115 | return dict(up_sol=up_sol, A=A, b=b) 116 | -------------------------------------------------------------------------------- /turtleFSI/modules/no_extrapolation.py: -------------------------------------------------------------------------------- 1 | # File under GNU GPL (v3) licence, see LICENSE file for details. 2 | # This software is distributed WITHOUT ANY WARRANTY; without even 3 | # the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 4 | # PURPOSE. 5 | 6 | 7 | def extrapolate_setup(**namespace): 8 | """ 9 | Do not move mesh. 10 | If only solving for solid or fluid, use this to also remove the mesh lifting 11 | operator. 12 | """ 13 | 14 | return {} 15 | -------------------------------------------------------------------------------- /turtleFSI/modules/no_fluid.py: -------------------------------------------------------------------------------- 1 | # File under GNU GPL (v3) licence, see LICENSE file for details. 2 | # This software is distributed WITHOUT ANY WARRANTY; without even 3 | # the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 4 | # PURPOSE. 5 | 6 | from dolfin import Constant, inner 7 | 8 | 9 | def fluid_setup(psi, phi, dx_f, dx_f_id_list, mesh, **namespace): 10 | F_fluid_linear = 0 11 | F_fluid_nonlinear = 0 12 | for fluid_region in range(len(dx_f_id_list)): 13 | # No contribution from the fluid, for when solving only the structure equation. 14 | F_fluid_linear += inner(Constant(tuple([0] * mesh.geometry().dim())), psi) * dx_f[fluid_region] 15 | F_fluid_nonlinear += inner(Constant(tuple([0] * mesh.geometry().dim())), phi) * dx_f[fluid_region] 16 | 17 | return dict(F_fluid_linear=F_fluid_linear, F_fluid_nonlinear=F_fluid_nonlinear) 18 | -------------------------------------------------------------------------------- /turtleFSI/modules/no_solid.py: -------------------------------------------------------------------------------- 1 | # File under GNU GPL (v3) licence, see LICENSE file for details. 2 | # This software is distributed WITHOUT ANY WARRANTY; without even 3 | # the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 4 | # PURPOSE. 5 | 6 | from dolfin import Constant, inner 7 | 8 | 9 | def solid_setup(psi, phi, dx_s, dx_s_id_list, mesh, **namespace): 10 | F_solid_linear = 0 11 | F_solid_nonlinear = 0 12 | for solid_region in range(len(dx_s_id_list)): 13 | # No contribution from the structure, for when solving only the fluid equation. 14 | F_solid_linear += inner(Constant(tuple([0] * mesh.geometry().dim())), psi) * dx_s[solid_region] 15 | F_solid_nonlinear += inner(Constant(tuple([0] * mesh.geometry().dim())), phi) * dx_s[solid_region] 16 | 17 | return dict(F_solid_linear=F_solid_linear, F_solid_nonlinear=F_solid_nonlinear) 18 | -------------------------------------------------------------------------------- /turtleFSI/modules/solid.py: -------------------------------------------------------------------------------- 1 | # File under GNU GPL (v3) licence, see LICENSE file for details. 2 | # This software is distributed WITHOUT ANY WARRANTY; without even 3 | # the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 4 | # PURPOSE. 5 | 6 | from collections.abc import Iterable 7 | 8 | from turtleFSI.modules import * 9 | from turtleFSI.problems import info_blue 10 | from dolfin import Constant, inner, grad, MPI 11 | 12 | def solid_setup(d_, v_, phi, psi, dx_s, ds_s, dx_s_id_list, ds_s_ext_id_list, solid_properties, k, theta, 13 | gravity, mesh, robin_bc, k_s, c_s, **namespace): 14 | 15 | # DB added gravity in 3d functionality and multi material capability 16/3/21 16 | # 17 | # 18 | 19 | """ 20 | ALE formulation (theta-scheme) of the non-linear elastic problem: 21 | dv/dt - f + div(sigma) = 0 with v = d(d)/dt 22 | """ 23 | 24 | # From the equation defined above we have to include the equation v - d(d)/dt = 0. This 25 | # ensures both that the variable d and v is well defined in the solid equation, but also 26 | # that there is continuity of the velocity at the boundary. Since this is imposed weakly 27 | # we 'make this extra important' by multiplying with a large number delta. 28 | 29 | delta = 1.0E7 30 | 31 | # Theta scheme constants 32 | theta0 = Constant(theta) 33 | theta1 = Constant(1 - theta) 34 | 35 | F_solid_linear = 0 36 | F_solid_nonlinear = 0 37 | for solid_region in range(len(dx_s_id_list)): 38 | rho_s = solid_properties[solid_region]["rho_s"] 39 | 40 | ## Temporal term and convection 41 | F_solid_linear += (rho_s/k * inner(v_["n"] - v_["n-1"], psi)*dx_s[solid_region] 42 | + delta * rho_s * (1 / k) * inner(d_["n"] - d_["n-1"], phi) * dx_s[solid_region] 43 | - delta * rho_s * inner(theta0 * v_["n"] + theta1 * v_["n-1"], phi) * dx_s[solid_region]) # Maybe we could add viscoelasticity with v["n"] term instead of (1 / k) * inner(d_["n"] - d_["n-1"], phi) 44 | 45 | # Viscoelasticity (rate dependant portion of the stress) 46 | if "viscoelasticity" in solid_properties[solid_region]: 47 | if solid_properties[solid_region]["viscoelasticity"] == "Form1": # This version (using velocity directly) seems to work best. 48 | F_solid_nonlinear += theta0 * inner(F_(d_["n"])*Svisc_D(v_["n"], solid_properties[solid_region]), grad(psi)) * dx_s[solid_region] 49 | F_solid_linear += theta1 * inner(F_(d_["n-1"])*Svisc_D(v_["n-1"], solid_properties[solid_region]), grad(psi)) * dx_s[solid_region] 50 | elif solid_properties[solid_region]["viscoelasticity"] == "Form2": # This version (using displacements to calculate linearized derivative) doesnt work as well. 51 | # (1/k) can come outside because all operators are linear. (v_[n]+v_[n-1])/2 = (d_[n]-d_[n-1])/k where k is the timestep. 52 | F_solid_nonlinear += (1/k) * inner(F_(d_["n"])*theta0*Svisc_D(d_["n"] - d_["n-1"], solid_properties[solid_region]), grad(psi)) * dx_s[solid_region] 53 | F_solid_linear += (1/k) * inner(F_(d_["n-1"])*theta1*Svisc_D(d_["n"] - d_["n-1"], solid_properties[solid_region]), grad(psi)) * dx_s[solid_region] 54 | else: 55 | if MPI.rank(MPI.comm_world) == 0: 56 | print("Invalid/No entry for viscoelasticity, assuming no viscoelasticity.") 57 | else: 58 | if MPI.rank(MPI.comm_world) == 0: 59 | print("No entry for viscoelasticity, assuming no viscoelasticity.") 60 | 61 | # Stress (Note that if viscoelasticity is used, Piola1() is no longer the total stress, it is the non-rate dependant (elastic) component of the stress) 62 | F_solid_nonlinear += theta0 * inner(Piola1(d_["n"], solid_properties[solid_region]), grad(psi)) * dx_s[solid_region] 63 | F_solid_linear += theta1 * inner(Piola1(d_["n-1"], solid_properties[solid_region]), grad(psi)) * dx_s[solid_region] 64 | # Gravity - y direction only 65 | if gravity is not None and mesh.geometry().dim() == 2: 66 | F_solid_linear -= inner(Constant((0, -gravity * rho_s)), psi)*dx_s[solid_region] 67 | elif gravity is not None and mesh.geometry().dim() == 3: 68 | F_solid_linear -= inner(Constant((0, -gravity * rho_s,0)), psi)*dx_s[solid_region] 69 | 70 | # Robin BC 71 | """ 72 | The derivation comes from the eq.(9) in the followling paper: 73 | Moireau, P., Xiao, N., Astorino, M. et al. External tissue support and fluid–structure simulation in blood flows. 74 | Biomech Model Mechanobiol 11, 1–18 (2012). https://doi.org/10.1007/s10237-011-0289-z 75 | """ 76 | if robin_bc: 77 | info_blue("Robin BC is used for the solid domain.") 78 | assert isinstance(k_s, Iterable), "k_s should be an iterable (e.g., list, tuple, etc.)." 79 | assert isinstance(c_s, Iterable), "c_s should be an iterable (e.g., list, tuple, etc.)." 80 | assert len(k_s) == len(c_s) == len(ds_s_ext_id_list), "k_s, c_s, and ds_s_ext_id_list should have the same length." 81 | for solid_boundaries in range(len(ds_s_ext_id_list)): 82 | if MPI.rank(MPI.comm_world) == 0: 83 | print(f"solid_boundaries: {solid_boundaries}, ds_s_ext_id_list: {ds_s_ext_id_list[solid_boundaries]}") 84 | print(f"k_s: {k_s[solid_boundaries]}, c_s: {c_s[solid_boundaries]}") 85 | F_solid_linear += theta0 * inner((k_s[solid_boundaries] * d_["n"] + c_s[solid_boundaries] * v_["n"]), psi)*ds_s[solid_boundaries] 86 | F_solid_linear += theta1 * inner((k_s[solid_boundaries] * d_["n-1"] + c_s[solid_boundaries] * v_["n-1"]), psi)*ds_s[solid_boundaries] 87 | 88 | 89 | return dict(F_solid_linear=F_solid_linear, F_solid_nonlinear=F_solid_nonlinear) 90 | -------------------------------------------------------------------------------- /turtleFSI/monolithic.py: -------------------------------------------------------------------------------- 1 | # File under GNU GPL (v3) licence, see LICENSE file for details. 2 | # This software is distributed WITHOUT ANY WARRANTY; without even 3 | # the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 4 | # PURPOSE. 5 | 6 | """ 7 | This module implements the monolithic Fluid-Structure Interaction (FSI) solver 8 | used in the turtleFSI package. 9 | """ 10 | 11 | from dolfin import * 12 | from pathlib import Path 13 | import json 14 | import time 15 | from pprint import pprint 16 | 17 | from turtleFSI.utils import * 18 | from turtleFSI.problems import * 19 | 20 | # Get user input 21 | args = parse() 22 | 23 | # Import the problem 24 | if Path.cwd().joinpath(args.problem+'.py').is_file(): 25 | exec("from {} import *".format(args.problem)) 26 | else: 27 | try: 28 | exec("from turtleFSI.problems.{} import *".format(args.problem)) 29 | except ImportError: 30 | raise ImportError("""Can not find the problem file. Make sure that the 31 | problem file is specified in the current directory or in the solver 32 | turtleFSI/problems/... directory.""") 33 | 34 | # Get problem specific parameters 35 | default_variables.update(set_problem_parameters(**vars())) 36 | 37 | # Update variables from commandline 38 | for key, value in list(args.__dict__.items()): 39 | if value is None: 40 | args.__dict__.pop(key) 41 | 42 | # If restart folder is given, read previous settings 43 | default_variables.update(args.__dict__) 44 | if default_variables["restart_folder"] is not None: 45 | restart_folder = Path(default_variables["restart_folder"]) 46 | restart_folder = restart_folder if "Checkpoint" in restart_folder.__str__() else restart_folder.joinpath("Checkpoint") 47 | with open(restart_folder.joinpath("default_variables.json"), "r") as f: 48 | restart_dict = json.load(f) 49 | default_variables.update(restart_dict) 50 | default_variables["restart_folder"] = restart_folder 51 | 52 | # Set variables in global namespace 53 | vars().update(default_variables) 54 | 55 | # Print out variables 56 | if MPI.rank(MPI.comm_world) == 0 and verbose: 57 | pprint(default_variables) 58 | 59 | # Create folders 60 | vars().update(create_folders(**vars())) 61 | 62 | # Get mesh information 63 | mesh, domains, boundaries = get_mesh_domain_and_boundaries(**vars()) 64 | 65 | # Save mesh, domains, and boundaries for post-processing 66 | if restart_folder is None: 67 | h5_mesh_path = results_folder.joinpath("Mesh", "mesh.h5") 68 | with HDF5File(mesh.mpi_comm(), h5_mesh_path.__str__(), "w") as hdf: 69 | hdf.write(mesh, "/mesh") 70 | hdf.write(boundaries, "/boundaries") 71 | hdf.write(domains, "/domains") 72 | 73 | # Control FEniCS output 74 | set_log_level(loglevel) 75 | 76 | # Finite Elements for deformation (de), velocity (ve), and pressure (pe) 77 | de = VectorElement('CG', mesh.ufl_cell(), d_deg) 78 | ve = VectorElement('CG', mesh.ufl_cell(), v_deg) 79 | pe = FiniteElement('CG', mesh.ufl_cell(), p_deg) 80 | 81 | # Define coefficients 82 | k = Constant(dt) 83 | n = FacetNormal(mesh) 84 | 85 | # Define function space 86 | # When using a biharmonic mesh lifting operator, we have to add a fourth function space. 87 | if extrapolation == "biharmonic": 88 | Elem = MixedElement([de, ve, pe, de]) 89 | else: 90 | Elem = MixedElement([de, ve, pe]) 91 | 92 | DVP = FunctionSpace(mesh, Elem) 93 | 94 | # Create one function for time step n, n-1, and n-2 95 | dvp_ = {} 96 | d_ = {} 97 | v_ = {} 98 | p_ = {} 99 | w_ = {} 100 | 101 | times = ["n-2", "n-1", "n"] 102 | for time_ in times: 103 | dvp = Function(DVP) 104 | dvp_[time_] = dvp 105 | dvp_list = split(dvp) 106 | 107 | d_[time_] = dvp_list[0] 108 | v_[time_] = dvp_list[1] 109 | p_[time_] = dvp_list[2] 110 | if extrapolation == "biharmonic": 111 | w_[time_] = dvp_list[3] 112 | 113 | if extrapolation == "biharmonic": 114 | phi, psi, gamma, beta = TestFunctions(DVP) 115 | else: 116 | phi, psi, gamma = TestFunctions(DVP) 117 | 118 | # Differentials 119 | ds = Measure("ds", subdomain_data=boundaries) 120 | dS = Measure("dS", subdomain_data=boundaries) 121 | dx = Measure("dx", subdomain_data=domains) 122 | 123 | # Domains 124 | exec("from turtleFSI.modules.domain import assign_domain_properties") 125 | vars().update(assign_domain_properties(**vars())) 126 | if MPI.rank(MPI.comm_world) == 0: 127 | print("{} solid region(s) found, using following parameters".format(len(dx_s_id_list))) 128 | for solid_region in solid_properties: 129 | print(solid_region) 130 | print("{} fluid region(s) found, using following parameters".format(len(dx_f_id_list))) 131 | for fluid_region in fluid_properties: 132 | print(fluid_region) 133 | 134 | # Define solver 135 | # Adding the Matrix() argument is a FEniCS 2018.1.0 hack 136 | up_sol = LUSolver(Matrix(), linear_solver) 137 | 138 | # Get variation formulations 139 | exec("from turtleFSI.modules.{} import fluid_setup".format(fluid)) 140 | vars().update(fluid_setup(**vars())) 141 | exec("from turtleFSI.modules.{} import solid_setup".format(solid)) 142 | vars().update(solid_setup(**vars())) 143 | exec("from turtleFSI.modules.{} import extrapolate_setup".format(extrapolation)) 144 | vars().update(extrapolate_setup(**vars())) 145 | 146 | # Any action before the simulation starts, e.g., initial conditions or overwriting parameters from restart 147 | vars().update(initiate(**vars())) 148 | 149 | # Create boundary conditions 150 | vars().update(create_bcs(**vars())) 151 | 152 | # Set up Newton solver 153 | exec("from turtleFSI.modules.{} import solver_setup, newtonsolver".format(solver)) 154 | vars().update(solver_setup(**vars())) 155 | 156 | # Functions for residuals 157 | dvp_res = Function(DVP) 158 | chi = TrialFunction(DVP) 159 | 160 | # Set initial conditions from restart folder 161 | if restart_folder is not None: 162 | start_from_checkpoint(**vars()) 163 | 164 | timer = Timer("Total simulation time") 165 | timer.start() 166 | previous_t = 0.0 167 | stop = False 168 | first_step_num = counter # This is so that the solver will recompute the jacobian on the first step of the simulation 169 | while t <= T + dt / 10 and not stop: # + dt / 10 is a hack to ensure that we take the final time step t == T 170 | t += dt 171 | # Pre solve hook 172 | tmp_dict = pre_solve(**vars()) 173 | if tmp_dict is not None: 174 | vars().update(tmp_dict) 175 | 176 | # Solve 177 | vars().update(newtonsolver(**vars())) 178 | 179 | # Update vectors 180 | for i, t_tmp in enumerate(times[:-1]): 181 | dvp_[t_tmp].vector().zero() 182 | dvp_[t_tmp].vector().axpy(1, dvp_[times[i+1]].vector()) 183 | 184 | # After solve hook 185 | tmp_dict = post_solve(**vars()) 186 | if tmp_dict is not None: 187 | vars().update(tmp_dict) 188 | 189 | # Checkpoint 190 | if counter % checkpoint_step == 0: 191 | checkpoint(**vars()) 192 | 193 | # Store results 194 | if counter % save_step == 0: 195 | vars().update(save_files_visualization(**vars())) 196 | 197 | # Update the time step counter 198 | counter += 1 199 | 200 | # Print time per time step 201 | if MPI.rank(MPI.comm_world) == 0: 202 | previous_t = print_information(**vars()) 203 | 204 | # pause simulation if pauseturtle exists 205 | pauseturtle = check_if_pause(results_folder) 206 | while pauseturtle: 207 | time.sleep(5) 208 | pauseturtle = check_if_pause(results_folder) 209 | 210 | # stop simulation cleanly if killturtle exists 211 | killturtle = check_if_kill(results_folder, killtime, timer) 212 | if killturtle: 213 | checkpoint(**vars()) 214 | stop = True 215 | 216 | # Print total time 217 | timer.stop() 218 | if MPI.rank(MPI.comm_world) == 0: 219 | if verbose: 220 | print("Total simulation time {0:f}".format(timer.elapsed()[0])) 221 | else: 222 | print("\nTotal simulation time {0:f}".format(timer.elapsed()[0])) 223 | 224 | # Merge visualization files 225 | if restart_folder is not None and MPI.rank(MPI.comm_world) == 0: 226 | print("Merging visualization files") 227 | merge_visualization_files(**vars()) 228 | 229 | # Post-processing of simulation 230 | finished(**vars()) 231 | -------------------------------------------------------------------------------- /turtleFSI/problems/MovingCylinder.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from dolfin import * 4 | from turtleFSI.problems import * 5 | from turtleFSI.modules import * 6 | import numpy as np 7 | 8 | """ 9 | This problem is used to test the fluid solver with a moving domain. 10 | There is no fluid-solid interaction, but the cylinder is moving in the fluid, hence fluid is affected by the motion of the cylinder. 11 | The file is based on the code developed by Henrik A. Kjeldsberg with the following version: 12 | https://github.com/KVSlab/OasisMove/blob/c19d0982576aa4a472b325ade8032a25bf60629d/src/oasismove/problems/NSfracStep/MovingCylinder.py 13 | Since dt is set to be small, you will most likely need to run the simulation on a cluster to get the results in a meaningful way. 14 | """ 15 | 16 | 17 | comm = MPI.comm_world 18 | 19 | 20 | def set_problem_parameters(default_variables, **namespace): 21 | """ 22 | Problem file for running CFD simulation for the oscillating cylinder in a rectangular 2D domain, as described 23 | by Blackburn and Henderson [1].The cylinder is prescribed an oscillatory motion and is placed in a free stream, 24 | with a diameter of D cm. The kinetmatic viscosity is computed from the free stream velocity of 1 m/s for a Reynolds 25 | number of 500, which is well above the critical value for vortex shedding. Moreover, the oscillation is mainly 26 | controlled by the amplitude ratio A_ratio, the Strouhal number St, and the frequency ratio F. 27 | 28 | [1] Blackburn, H. M., & Henderson, R. D. (1999). A study of two-dimensional flow past an oscillating cylinder. 29 | Journal of Fluid Mechanics, 385, 255-286. 30 | """ 31 | D=0.1 # Diameter in [m] 32 | Re=500 # Reynolds number 33 | u_inf=1.0 # Free-stream flow velocity in [m/s] 34 | rho_f = 1000 35 | mu_f = rho_f * u_inf * D / Re 36 | factor = 1 / 2 * rho_f * u_inf ** 2 * D 37 | # Default parameters 38 | default_variables.update(dict( 39 | # Geometrical parameters 40 | Re=Re, # Reynolds number 41 | D=D, # Diameter in [m] 42 | u_inf=u_inf, # Free-stream flow velocity in [m/s] 43 | A_ratio=0.25, # Amplitude ratio 44 | St=0.2280, # Strouhal number 45 | F_r=1.0, # Frequency ratio 46 | factor=factor, 47 | # Simulation parameters 48 | T=5, # End time 49 | dt=0.000125, # Time step 50 | 51 | folder="results_moving_cylinder", 52 | # fluid parameters 53 | rho_f=rho_f, 54 | mu_f=mu_f, 55 | 56 | # solid parameters 57 | solid="no_solid", 58 | 59 | # Extraplotation parameters 60 | extrapolation="laplace", 61 | extrapolation_sub_type="constant", 62 | 63 | # Solver parameters 64 | theta=0.500125, # shifted Crank-Nicolson, theta = 0.5 + dt 65 | 66 | checkpoint_step=500, 67 | save_step = 100, 68 | recompute=25, 69 | recompute_tstep=100, 70 | d_deg=1, 71 | v_deg=1, 72 | p_deg=1)) 73 | 74 | return default_variables 75 | 76 | 77 | def get_mesh_domain_and_boundaries(D, **namespace): 78 | # Import mesh 79 | mesh = Mesh() 80 | with XDMFFile(MPI.comm_world, "mesh/MovingCylinder/mesh4.xdmf") as infile: 81 | infile.read(mesh) 82 | domains = MeshFunction("size_t", mesh, mesh.geometry().dim()) 83 | domains.set_all(1) 84 | Allboundaries = DomainBoundary() 85 | boundaries = MeshFunction("size_t", mesh, mesh.geometry().dim() - 1) 86 | Allboundaries.mark(boundaries, 0) 87 | 88 | # Reference domain from Blackburn & Henderson [1] 89 | # Cylinder centered in (0,0) 90 | H = 30 * D / 2 # Height 91 | L1 = -10 * D # Length 92 | L2 = 52 * D # Length 93 | 94 | # Mark geometry 95 | inlet = AutoSubDomain(lambda x, b: b and x[0] <= L1 + DOLFIN_EPS) 96 | walls = AutoSubDomain(lambda x, b: b and (near(x[1], -H) or near(x[1], H))) 97 | circle = AutoSubDomain(lambda x, b: b and (-H / 2 <= x[1] <= H / 2) and (L1 / 2 <= x[0] <= L2 / 2)) 98 | outlet = AutoSubDomain(lambda x, b: b and (x[0] > L2 - DOLFIN_EPS * 1000)) 99 | 100 | inlet.mark(boundaries, 1) 101 | walls.mark(boundaries, 2) 102 | circle.mark(boundaries, 3) 103 | outlet.mark(boundaries, 4) 104 | 105 | return mesh, domains, boundaries 106 | 107 | class MovingCylinder(UserExpression): 108 | def __init__(self, *args, **kwargs): 109 | super().__init__(*args, **kwargs) 110 | self.t = 0 111 | self.y_max = 0 112 | self.f_o = 0 113 | 114 | def eval(self, value, x): 115 | value[0] = 0 116 | value[1] = self.y_max * sin(2 * pi * self.f_o * self.t) 117 | 118 | def value_shape(self): 119 | return (2,) 120 | 121 | def create_bcs(DVP, D, u_inf, St, F_r, A_ratio, boundaries, **namespace): 122 | info_red("Creating boundary conditions") 123 | 124 | f_v = St * u_inf / D # Fixed-cylinder vortex shredding frequency 125 | f_o = F_r * f_v # Frequency of harmonic oscillation 126 | y_max = A_ratio * D # Max displacement (Amplitude) 127 | if MPI.rank(comm) == 0: 128 | print("Frequency is %.4f" % f_o) 129 | print("Amplitude is %.4f " % y_max) 130 | 131 | # cylinder_motion = MovingCylinder(t=0, f_o=f_o, y_max=y_max) 132 | cylinder_motion = MovingCylinder() 133 | cylinder_motion.f_o = f_o 134 | cylinder_motion.y_max = y_max 135 | # Define boundary conditions for the velocity and the pressure 136 | bcu_inlet = DirichletBC(DVP.sub(1), Constant((u_inf, 0)), boundaries, 1) 137 | bcu_wall = DirichletBC(DVP.sub(1), Constant((u_inf, 0)), boundaries, 2) 138 | bcu_circle = DirichletBC(DVP.sub(1), Constant((0, 0)), boundaries, 3) 139 | bcp_outlet = DirichletBC(DVP.sub(2), Constant(0), boundaries, 4) 140 | 141 | bcd_inlet = DirichletBC(DVP.sub(0), Constant((0, 0)), boundaries, 1) 142 | bcd_wall = DirichletBC(DVP.sub(0), Constant((0, 0)), boundaries, 2) 143 | bcd_circle = DirichletBC(DVP.sub(0), cylinder_motion, boundaries, 3) 144 | bcd_outlet = DirichletBC(DVP.sub(0), Constant((0, 0)), boundaries, 4) 145 | 146 | bcs = [bcu_inlet, bcu_wall, bcu_circle, bcp_outlet, bcd_inlet, bcd_wall, bcd_circle, bcd_outlet] 147 | 148 | return dict(bcs=bcs, cylinder_motion=cylinder_motion) 149 | 150 | def initiate(**namespace): 151 | # Lists to hold displacement, forces, and time 152 | drag_list = [] 153 | lift_list = [] 154 | time_list = [] 155 | 156 | return dict(drag_list=drag_list, lift_list=lift_list, time_list=time_list) 157 | 158 | def pre_solve(cylinder_motion, t, boundaries, **namespace): 159 | cylinder_motion.t = t 160 | ds_circle = Measure("ds", domain=boundaries.mesh(), subdomain_data=boundaries, subdomain_id=3) 161 | return dict(cylinder_motion=cylinder_motion, ds_circle=ds_circle) 162 | 163 | def post_solve(t, n, dvp_, results_folder, drag_list, lift_list, time_list, factor, mu_f, ds_circle, **namespace): 164 | # Compute drag and lift coefficients 165 | 166 | d = dvp_["n"].sub(0, deepcopy=True) 167 | v = dvp_["n"].sub(1, deepcopy=True) 168 | p = dvp_["n"].sub(2, deepcopy=True) 169 | 170 | # Compute forces 171 | force = dot(sigma(v, p, d, mu_f), n) 172 | drag_list.append(-assemble(force[0]*ds_circle)) 173 | lift_list.append(-assemble(force[1]*ds_circle)) 174 | time_list.append(t) 175 | 176 | # Store forces to file 177 | if MPI.rank(MPI.comm_world) == 0: 178 | drag_coeff = drag_list[-1]/ factor 179 | lift_coeff = lift_list[-1] / factor 180 | data = [t, drag_coeff, lift_coeff] 181 | 182 | data_path = os.path.join(results_folder, "forces.txt") 183 | 184 | with open(data_path, "ab") as f: 185 | np.savetxt(f, data, fmt=" %.16f ", newline=' ') 186 | f.write(b'\n') -------------------------------------------------------------------------------- /turtleFSI/problems/RobinBC_validation.py: -------------------------------------------------------------------------------- 1 | from dolfin import * 2 | from turtleFSI.problems import * 3 | import numpy as np 4 | from scipy.integrate import odeint 5 | import matplotlib.pyplot as plt 6 | 7 | """ 8 | This problem is a validation of the Robin BC implementation in the solid solver. 9 | The validation is done by using a mass-spring-damper system and comparing the results. 10 | We use cylinder mesh which is subjected to a constant gravity force in y-direction. 11 | This sciprt is meant to run with a single core, but can be easily parallelized. 12 | 13 | Mesh can be found in the following link: 14 | https://drive.google.com/drive/folders/1roV_iE_16Q847AQ_0tEsznIT-6EICX4o?usp=sharing 15 | """ 16 | 17 | # Set compiler arguments 18 | parameters["form_compiler"]["quadrature_degree"] = 6 19 | parameters["form_compiler"]["optimize"] = True 20 | # The "ghost_mode" has to do with the assembly of form containing the facet normals n('+') within interior boundaries (dS). For 3D mesh the value should be "shared_vertex", for 2D mesh "shared_facet", the default value is "none". 21 | parameters["ghost_mode"] = "shared_vertex" #3D case 22 | _compiler_parameters = dict(parameters["form_compiler"]) 23 | 24 | 25 | def set_problem_parameters(default_variables, **namespace): 26 | # Overwrite default values 27 | E_s_val = 1E6 # Young modulus (elasticity) [Pa] 28 | nu_s_val = 0.45 # Poisson ratio (compressibility) 29 | mu_s_val = E_s_val / (2 * (1 + nu_s_val)) # Shear modulus 30 | lambda_s_val = nu_s_val * 2. * mu_s_val / (1. - 2. * nu_s_val) 31 | 32 | # define and set problem variables values 33 | default_variables.update(dict( 34 | T=0.1, # Simulation end time 35 | dt=0.0005, # Time step size 36 | theta=0.501, # Theta scheme (implicit/explicit time stepping): 0.5 + dt 37 | atol=1e-10, # Absolute tolerance in the Newton solver 38 | rtol=1e-10, # Relative tolerance in the Newton solver 39 | mesh_file="cylinder", # Mesh file name 40 | inlet_id=2, # inlet id 41 | outlet_id1=3, # outlet id 42 | inlet_outlet_s_id=1011, # solid inlet and outlet id 43 | fsi_id=1022, # fsi Interface 44 | rigid_id=1011, # "rigid wall" id for the fluid and mesh problem 45 | outer_wall_id=1033, # outer surface / external id 46 | ds_s_id=[1033], # ID of solid external wall (where we want to test Robin BC) 47 | rho_f=1.025E3, # Fluid density [kg/m3] 48 | mu_f=3.5E-3, # Fluid dynamic viscosity [Pa.s] 49 | rho_s=1.0E3, # Solid density [kg/m3] 50 | mu_s=mu_s_val, # Solid shear modulus or 2nd Lame Coef. [Pa] 51 | nu_s=nu_s_val, # Solid Poisson ratio [-] 52 | lambda_s=lambda_s_val, # Solid 1rst Lamé coef. [Pa] 53 | robin_bc = True, # Robin BC 54 | k_s = [1.0E5], # elastic response necesary for RobinBC 55 | c_s = [1.0E2], # viscoelastic response necesary for RobinBC 56 | dx_f_id=1, # ID of marker in the fluid domain 57 | dx_s_id=1002, # ID of marker in the solid domain 58 | extrapolation="laplace", # laplace, elastic, biharmonic, no-extrapolation 59 | extrapolation_sub_type="constant", # constant, small_constant, volume, volume_change, bc1, bc2 60 | recompute=30, # Number of iterations before recompute Jacobian. 61 | recompute_tstep=100, # Number of time steps before recompute Jacobian. 62 | save_step=1, # Save frequency of files for visualisation 63 | folder="robinbc_validation", # Folder where the results will be stored 64 | checkpoint_step=50, # checkpoint frequency 65 | kill_time=100000, # in seconds, after this time start dumping checkpoints every timestep 66 | save_deg=1, # Default could be 1. 1 saves the nodal values only while 2 takes full advantage of the mide side nodes available in the P2 solution. P2 for nice visualisations 67 | gravity=2.0, # Gravitational force [m/s^2] 68 | fluid="no_fluid" # Do not solve for the fluid 69 | )) 70 | 71 | return default_variables 72 | 73 | 74 | def get_mesh_domain_and_boundaries(mesh_file, **namespace): 75 | #Import mesh file 76 | mesh = Mesh() 77 | hdf = HDF5File(mesh.mpi_comm(), "mesh/" + mesh_file + ".h5", "r") 78 | hdf.read(mesh, "/mesh", False) 79 | boundaries = MeshFunction("size_t", mesh, 2) 80 | hdf.read(boundaries, "/boundaries") 81 | domains = MeshFunction("size_t", mesh, 3) 82 | hdf.read(domains, "/domains") 83 | 84 | #Set all solid 85 | domains.set_all(1002) 86 | 87 | return mesh, domains, boundaries 88 | 89 | def create_bcs(**namespace): 90 | """ 91 | In this problem we use Robin boundary condition which is implemented in the solid.py file. 92 | Thus, we do not need specify any boundary conditions in this function. 93 | """ 94 | return dict(bcs=[]) 95 | 96 | def _mass_spring_damper_system_ode(x, t, params_dict): 97 | # Umpack parameters 98 | F = params_dict['F'] # Volume of the domain 99 | A = params_dict['A'] # Area of the external surface (where Robin BC is applied) 100 | c = params_dict['c'] # Damping constant 101 | k = params_dict['k'] # Stiffness of the spring 102 | m = params_dict['m'] # Mass of the domain 103 | # Solve the system of ODEs 104 | dx1dt = x[1] 105 | dx2dt = (F - c*x[1]*A - k*x[0]*A)/m 106 | 107 | dxdt = [dx1dt, dx2dt] 108 | return dxdt 109 | 110 | def initiate(mesh, **namespace): 111 | # Position to probe 112 | x_coordinate = mesh.coordinates()[:, 0] 113 | y_coordinate = mesh.coordinates()[:, 1] 114 | z_coordinate = mesh.coordinates()[:, 2] 115 | 116 | x_middle = (x_coordinate.max() + x_coordinate.min())/2 117 | y_middle = (y_coordinate.max() + y_coordinate.min())/2 118 | z_middle = (z_coordinate.max() + z_coordinate.min())/2 119 | 120 | middle_point = np.array([x_middle, y_middle, z_middle]) 121 | d_list = [] 122 | return dict(d_list=d_list, middle_point=middle_point) 123 | 124 | 125 | def post_solve(dvp_, d_list, middle_point, **namespace): 126 | d = dvp_["n"].sub(0, deepcopy=True) 127 | d_eval = d(middle_point)[1] 128 | d_list.append(d_eval) 129 | 130 | return dict(d_list=d_list) 131 | 132 | 133 | def finished(T, dt, mesh, rho_s, k_s, c_s, boundaries, gravity, d_list, **namespace): 134 | # Define time step and initial conditions 135 | t_analytical = np.linspace(0, T, int(T/dt) + 1) 136 | analytical_solution_init = [0,0] 137 | # Define parameters for the analytical solution 138 | volume = assemble(1*dx(mesh)) 139 | ds_robin = Measure("ds", domain=mesh, subdomain_data=boundaries, subdomain_id=1033) 140 | params_dict = dict() 141 | params_dict["m"] = volume*rho_s 142 | params_dict["k"] = k_s[0] 143 | params_dict["c"] = c_s[0] 144 | params_dict["A"] = assemble(1*ds_robin) 145 | params_dict["F"] = -gravity*volume*rho_s 146 | # Solve the ode to compute the analytical solution 147 | analytical_solution = odeint(_mass_spring_damper_system_ode, analytical_solution_init, t_analytical, args=(params_dict,)) 148 | analytical_displacement = analytical_solution[:,0] 149 | # Plot both numerical and analytical solutions for a comparison 150 | plt.plot(d_list, label="turtleFSI") 151 | plt.plot(analytical_displacement, label="analytical") 152 | plt.legend() 153 | plt.show() 154 | 155 | -------------------------------------------------------------------------------- /turtleFSI/problems/TF_cfd.py: -------------------------------------------------------------------------------- 1 | # File under GNU GPL (v3) licence, see LICENSE file for details. 2 | # This software is distributed WITHOUT ANY WARRANTY; without even 3 | # the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 4 | # PURPOSE. 5 | 6 | """Problem file for running the "CFD" benchmarks in [1]. The problem is a channel flow 7 | with a circle and a flag attached to it. For the CFD problem both the circle and flag is rigid. 8 | 9 | [1] Turek, Stefan, and Jaroslav Hron. "Proposal for numerical benchmarking of fluid-structure interaction 10 | between an elastic object and laminar incompressible flow." Fluid-structure interaction. 11 | Springer, Berlin, Heidelberg, 2006. 371-385.""" 12 | 13 | from dolfin import * 14 | import numpy as np 15 | from os import path 16 | 17 | from turtleFSI.problems import * 18 | from turtleFSI.modules import * 19 | 20 | 21 | def set_problem_parameters(default_variables, **namespace): 22 | # Overwrite or add new variables to 'default_variables' 23 | default_variables.update(dict( 24 | # Temporal variables 25 | T=30, # End time [s] 26 | dt=0.01, # Time step [s] 27 | theta=0.5, # Temporal scheme 28 | 29 | # Physical constants 30 | rho_f=1.0E3, # Fluid density [kg/m3] 31 | mu_f=1.0, # Fluid dynamic viscosity [Pa.s] 32 | Um=2.0, # Max. velocity inlet (CDF3: 2.0) [m/s] 33 | 34 | # Problem specific 35 | folder="TF_cfd_results", # Name of the results folder 36 | solid="no_solid", # Do not solve for the solid 37 | extrapolation="no_extrapolation", # No displacement to extrapolate 38 | 39 | # Geometric variables 40 | H=0.41, # Total height 41 | L=2.5)) # Length of domain 42 | 43 | return default_variables 44 | 45 | 46 | def get_mesh_domain_and_boundaries(L, H, **namespace): 47 | # Load and refine mesh 48 | mesh = Mesh(path.join(path.dirname(path.abspath(__file__)), "..", "mesh", "TF_cfd.xml.gz")) 49 | mesh = refine(mesh) 50 | 51 | # Define the boundaries 52 | Inlet = AutoSubDomain(lambda x: near(x[0], 0)) 53 | Outlet = AutoSubDomain(lambda x: near(x[0], L)) 54 | Walls = AutoSubDomain(lambda x: near(x[1], 0) or near(x[1], H)) 55 | 56 | # Mark the boundaries 57 | Allboundaries = DomainBoundary() 58 | boundaries = MeshFunction("size_t", mesh, mesh.geometry().dim() - 1) 59 | boundaries.set_all(0) 60 | Allboundaries.mark(boundaries, 4) # Circle and flag 61 | Inlet.mark(boundaries, 1) 62 | Walls.mark(boundaries, 2) 63 | Outlet.mark(boundaries, 3) 64 | 65 | # Define the domain 66 | domains = MeshFunction("size_t", mesh, mesh.geometry().dim()) 67 | domains.set_all(1) 68 | 69 | return mesh, domains, boundaries 70 | 71 | 72 | def initiate(**namespace): 73 | # Lists to hold displacement, forces, and time 74 | drag_list = [] 75 | lift_list = [] 76 | time_list = [] 77 | 78 | return dict(drag_list=drag_list, lift_list=lift_list, time_list=time_list) 79 | 80 | 81 | class Inlet(UserExpression): 82 | def __init__(self, Um, H, **kwargs): 83 | self.Um = Um * 1.5 84 | self.H = H 85 | self.factor = 0 86 | super().__init__(**kwargs) 87 | 88 | def update(self, t): 89 | if t < 2: 90 | self.factor = 0.5 * (1 - np.cos(t * np.pi / 2)) * self.Um 91 | else: 92 | self.factor = self.Um 93 | 94 | def eval(self, value, x): 95 | value[0] = self.factor * x[1] * (self.H - x[1]) / (self.H / 2.0)**2 96 | value[1] = 0 97 | 98 | def value_shape(self): 99 | return (2,) 100 | 101 | 102 | def create_bcs(DVP, Um, H, v_deg, boundaries, **namespace): 103 | # Create inlet expression 104 | inlet = Inlet(Um, H, degree=v_deg) 105 | 106 | # Fluid velocity conditions 107 | u_inlet = DirichletBC(DVP.sub(1), inlet, boundaries, 1) 108 | u_wall = DirichletBC(DVP.sub(1), ((0.0, 0.0)), boundaries, 2) 109 | u_flag = DirichletBC(DVP.sub(1), ((0.0, 0.0)), boundaries, 4) 110 | 111 | # Pressure Conditions 112 | p_out = DirichletBC(DVP.sub(2), 0, boundaries, 3) 113 | 114 | return dict(bcs=[u_wall, u_flag, u_inlet, p_out], inlet=inlet) 115 | 116 | 117 | def pre_solve(t, inlet, **namespace): 118 | """Update boundary conditions""" 119 | inlet.update(t) 120 | 121 | 122 | def post_solve(t, dvp_, n, drag_list, lift_list, time_list, mu_f, verbose, ds, **namespace): 123 | # Get deformation, velocity, and pressure 124 | d = dvp_["n"].sub(0, deepcopy=True) 125 | v = dvp_["n"].sub(1, deepcopy=True) 126 | p = dvp_["n"].sub(2, deepcopy=True) 127 | 128 | # Compute forces 129 | force = dot(sigma(v, p, d, mu_f), n) 130 | drag_list.append(-assemble(force[0]*ds(4))) 131 | lift_list.append(-assemble(force[1]*ds(4))) 132 | time_list.append(t) 133 | 134 | # Print results 135 | if MPI.rank(MPI.comm_world) == 0 and verbose: 136 | print("Drag: {:e}".format(drag_list[-1])) 137 | print("Lift: {:e}".format(lift_list[-1])) 138 | 139 | 140 | def finished(drag_list, lift_list, time_list, results_folder, **namespace): 141 | # Store results when the computation is finished 142 | if MPI.rank(MPI.comm_world) == 0: 143 | np.savetxt(path.join(results_folder, 'Lift.txt'), lift_list, delimiter=',') 144 | np.savetxt(path.join(results_folder, 'Drag.txt'), drag_list, delimiter=',') 145 | np.savetxt(path.join(results_folder, 'Time.txt'), time_list, delimiter=',') 146 | -------------------------------------------------------------------------------- /turtleFSI/problems/TF_csm.py: -------------------------------------------------------------------------------- 1 | # File under GNU GPL (v3) licence, see LICENSE file for details. 2 | # This software is distributed WITHOUT ANY WARRANTY; without even 3 | # the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 4 | # PURPOSE. 5 | 6 | """Problem file for running the "CSM" benchmarks in [1]. The problem is beam under load. 7 | 8 | [1] Turek, Stefan, and Jaroslav Hron. "Proposal for numerical benchmarking of fluid-structure interaction 9 | between an elastic object and laminar incompressible flow." Fluid-structure interaction. 10 | Springer, Berlin, Heidelberg, 2006. 371-385.""" 11 | 12 | from dolfin import * 13 | import numpy as np 14 | from os import path 15 | from mpi4py import MPI as pyMPI 16 | 17 | from turtleFSI.problems import * 18 | 19 | 20 | def set_problem_parameters(default_variables, **namespace): 21 | # Parameters 22 | default_variables.update(dict( 23 | # Temporal variables 24 | T=10, # End time [s] 25 | dt=0.01, # Time step [s] 26 | theta=0.51, # Temporal scheme 27 | 28 | # Physical constants 29 | rho_s=1.0e3, # Solid density[kg/m3] 30 | mu_s=0.5e6, # Shear modulus, 2nd Lame Coef. CSM3: 0.5E6 [Pa] 31 | nu_s=0.4, # Solid Poisson ratio [-] 32 | gravity=2.0, # Gravitational force [m/s^2] 33 | lambda_s=2e6, # Solid 1st Lame Coef. [Pa] 34 | 35 | # Problem specific 36 | dx_f_id=0, # Id of the fluid domain 37 | dx_s_id=1, # Id of the solid domain 38 | folder="TF_csm_results", # Folder to store the results 39 | fluid="no_fluid", # Do not solve for the fluid 40 | extrapolation="no_extrapolation", # No displacement to extrapolate 41 | 42 | # Geometric variables 43 | R=0.05, # Radius of the circle 44 | c_x=0.2, # Center of the circle x-direction 45 | c_y=0.2, # Center of the circle y-direction 46 | f_L=0.35)) # Length of the flag 47 | 48 | return default_variables 49 | 50 | 51 | def get_mesh_domain_and_boundaries(c_x, c_y, R, **namespace): 52 | # Read mesh 53 | mesh = Mesh(path.join(path.dirname(path.abspath(__file__)), "..", "mesh", "TF_csm.xml.gz")) 54 | mesh = refine(mesh) 55 | 56 | # Mark boundaries 57 | Barwall = AutoSubDomain(lambda x: ((x[0] - c_x)**2 + (x[1] - c_y)**2 < R**2 + DOLFIN_EPS * 1e5)) 58 | boundaries = MeshFunction("size_t", mesh, mesh.geometry().dim() - 1) 59 | boundaries.set_all(0) 60 | Barwall.mark(boundaries, 1) 61 | 62 | # Mark domain 63 | domains = MeshFunction("size_t", mesh, mesh.geometry().dim()) 64 | domains.set_all(1) 65 | 66 | return mesh, domains, boundaries 67 | 68 | 69 | def initiate(f_L, R, c_x, c_y, **namespace): 70 | # Coordinate for sampling statistics 71 | coord = [c_x + R + f_L, c_y] 72 | 73 | # Lists to hold results 74 | displacement_x_list = [] 75 | displacement_y_list = [] 76 | time_list = [] 77 | 78 | return dict(displacement_x_list=displacement_x_list, displacement_y_list=displacement_y_list, 79 | time_list=time_list, coord=coord) 80 | 81 | 82 | def create_bcs(DVP, boundaries, **namespace): 83 | # Clamp on the left hand side 84 | u_barwall = DirichletBC(DVP.sub(0), ((0.0, 0.0)), boundaries, 1) 85 | v_barwall = DirichletBC(DVP.sub(1), ((0.0, 0.0)), boundaries, 1) 86 | 87 | return dict(bcs=[u_barwall, v_barwall]) 88 | 89 | ################################################################################ 90 | # the function mpi4py_comm and peval are used to overcome FEniCS limitation of 91 | # evaluating functions at a given mesh point in parallel. 92 | # https://fenicsproject.discourse.group/t/problem-with-evaluation-at-a-point-in 93 | # -parallel/1188 94 | 95 | 96 | def mpi4py_comm(comm): 97 | '''Get mpi4py communicator''' 98 | try: 99 | return comm.tompi4py() 100 | except AttributeError: 101 | return comm 102 | 103 | 104 | def peval(f, x): 105 | '''Parallel synced eval''' 106 | try: 107 | yloc = f(x) 108 | except RuntimeError: 109 | yloc = np.inf*np.ones(f.value_shape()) 110 | 111 | comm = mpi4py_comm(f.function_space().mesh().mpi_comm()) 112 | yglob = np.zeros_like(yloc) 113 | comm.Allreduce(yloc, yglob, op=pyMPI.MIN) 114 | 115 | return yglob 116 | ################################################################################ 117 | 118 | 119 | def post_solve(t, dvp_, coord, displacement_x_list, displacement_y_list, time_list, verbose, **namespace): 120 | # Add time 121 | time_list.append(t) 122 | 123 | # Add displacement 124 | d = dvp_["n"].sub(0, deepcopy=True) 125 | d_eval = peval(d, coord) 126 | dsx = d_eval[0] 127 | dsy = d_eval[1] 128 | displacement_x_list.append(dsx) 129 | displacement_y_list.append(dsy) 130 | 131 | if MPI.rank(MPI.comm_world) == 0 and verbose: 132 | print("Distance x: {:e}".format(dsx)) 133 | print("Distance y: {:e}".format(dsy)) 134 | 135 | 136 | def finished(results_folder, displacement_x_list, displacement_y_list, time_list, **namespace): 137 | # Store results when the computation is finished 138 | if MPI.rank(MPI.comm_world) == 0: 139 | np.savetxt(path.join(results_folder, 'Time.txt'), time_list, delimiter=',') 140 | np.savetxt(path.join(results_folder, 'dis_x.txt'), displacement_x_list, delimiter=',') 141 | np.savetxt(path.join(results_folder, 'dis_y.txt'), displacement_y_list, delimiter=',') -------------------------------------------------------------------------------- /turtleFSI/problems/TaylorGreen2D.py: -------------------------------------------------------------------------------- 1 | import pickle 2 | from os import path 3 | import sys 4 | try: 5 | import pygmsh 6 | except ImportError: 7 | pass 8 | 9 | from dolfin import * 10 | from turtleFSI.problems import * 11 | 12 | """ 13 | Taylor-Green vortex in 2D with fixed domain 14 | This problem can be used to test the accuracy of the fluid solver 15 | The mesh can be either structured or unstructured based on the user's choice and availability of pygmsh 16 | """ 17 | 18 | # Override some problem specific parameters 19 | def set_problem_parameters(default_variables, **namespace): 20 | default_variables.update(dict( 21 | mu_f=0.01, # dynamic viscosity of fluid, 0.01 as kinematic viscosity 22 | T=1, 23 | dt=0.01, 24 | theta=0.5, # Crank-Nicolson 25 | rho_f = 1, # density of fluid 26 | folder="tg2d_results", 27 | solid = "no_solid", # no solid 28 | extrapolation="no_extrapolation", # no extrapolation since the domain is fixed 29 | save_step=500, 30 | checkpoint_step=500, 31 | L = 2., 32 | v_deg=2, 33 | p_deg=1, 34 | atol=1e-10, 35 | rtol=1e-10, 36 | total_error_v = 0, 37 | total_error_p = 0, 38 | mesh_size=0.25, # mesh size for pygmsh, if you use unit square mesh from FEniCS 39 | mesh_type="structured", # structured or unstructured 40 | external_mesh=False, # you could also read mesh from file if you have one 41 | N=40, # number of points along x or y axis when creating structured mesh 42 | recompute=100, 43 | recompute_tstep=100, 44 | )) 45 | 46 | return default_variables 47 | 48 | def create2Dmesh(msh): 49 | """ 50 | Given a pygmsh mesh, create a dolfin mesh 51 | Args: 52 | msh: pygmsh mesh 53 | Returns: 54 | mesh: dolfin mesh 55 | """ 56 | # remove z coordinate 57 | msh.points = msh.points[:, :2] 58 | nodes = msh.points 59 | cells = msh.cells_dict["triangle"].astype(np.uintp) 60 | mesh = Mesh() 61 | editor = MeshEditor() 62 | # point, interval, triangle, quadrilateral, hexahedron 63 | editor.open(mesh, "triangle", 2, 2) 64 | editor.init_vertices(len(nodes)) 65 | editor.init_cells(len(cells)) 66 | [editor.add_vertex(i, n) for i, n in enumerate(nodes)] 67 | [editor.add_cell(i, n) for i, n in enumerate(cells)] 68 | editor.close() 69 | return mesh 70 | 71 | def unitsquare_mesh(mesh_size): 72 | """ 73 | Create unstructured mesh using pygmsh 74 | Args: 75 | mesh_size: scaling factor for mesh size in gmsh. The lower the value, the finer the mesh. 76 | Returns: 77 | mesh: pygmsh mesh 78 | """ 79 | with pygmsh.geo.Geometry() as geom: 80 | geom.add_rectangle( 81 | -1, 1, -1, 1, 0.0, mesh_size=mesh_size 82 | ) 83 | mesh = geom.generate_mesh() 84 | mesh = create2Dmesh(mesh) 85 | return mesh 86 | 87 | class Wall(SubDomain): 88 | def inside(self, x, on_boundary): 89 | return on_boundary 90 | 91 | def get_mesh_domain_and_boundaries(mesh_size, mesh_type, external_mesh, N,**namespace): 92 | """ 93 | Here, you have three options to create mesh: 94 | 1. Use external mesh from file (e.g. .xdmf) 95 | 2. Use pygmsh to create unstructured mesh (on the fly) 96 | 3. Use dolfin to create structured mesh (on the fly) 97 | 98 | If pygmsh is not installed, we use default mesh from dolfin, which is structured. 99 | 100 | args: 101 | mesh_size: scaling factor for mesh size in gmsh. The lower the value, the finer the mesh. 102 | mesh_type: structured or unstructured 103 | external_mesh: True or False 104 | N: number of points along x or y axis when creating structured mesh 105 | returns: 106 | mesh 107 | domains 108 | boundaries 109 | 110 | """ 111 | if external_mesh: 112 | mesh = Mesh() 113 | with XDMFFile("mesh/UnitSquare/unitsquare.xdmf") as infile: 114 | infile.read(mesh) 115 | info_blue("Loaded external mesh") 116 | elif "pygmsh" in sys.modules and mesh_type == "unstructured": 117 | info_blue("Creating unstructured mesh") 118 | mesh = unitsquare_mesh(mesh_size) 119 | # In case of MPI, redistribute the mesh to all processors 120 | MeshPartitioning.build_distributed_mesh(mesh) 121 | else: 122 | info_blue("Creating structured mesh") 123 | mesh = RectangleMesh(Point(-1, -1), Point(1, 1), N, N, "right") 124 | 125 | # Mark the boundaries 126 | Allboundaries = DomainBoundary() 127 | boundaries = MeshFunction("size_t", mesh, mesh.geometry().dim() - 1) 128 | Allboundaries.mark(boundaries, 0) 129 | wall = Wall() 130 | wall.mark(boundaries, 1) 131 | domains = MeshFunction("size_t", mesh, mesh.geometry().dim()) 132 | domains.set_all(1) 133 | 134 | return mesh, domains, boundaries 135 | 136 | class analytical_velocity(UserExpression): 137 | def __init__(self, *args, **kwargs): 138 | super().__init__(*args, **kwargs) 139 | self.t = 0 140 | self.nu = 0.01 141 | 142 | def eval(self, value, x): 143 | value[0] = -sin(pi*x[1])*cos(pi*x[0])*exp(-2.*pi*pi*self.nu*self.t) 144 | value[1] = sin(pi*x[0])*cos(pi*x[1])*exp(-2.*pi*pi*self.nu*self.t) 145 | 146 | def value_shape(self): 147 | return (2,) 148 | 149 | class analytical_pressure(UserExpression): 150 | def __init__(self, *args, **kwargs): 151 | super().__init__(*args, **kwargs) 152 | self.t = 0 153 | self.nu = 0.01 154 | 155 | def eval(self, value, x): 156 | value[0] = -(cos(2*pi*x[0])+cos(2*pi*x[1]))*exp(-4.*pi*pi*self.nu*self.t)/4. 157 | 158 | def value_shape(self): 159 | return () 160 | 161 | def top_right_point(x, on_boundary): 162 | """ 163 | Since we only have Neuman BC for the pressure, we need to apply Dirichlet BC at one point to get unique solution. 164 | In this case, we apply Dirichlet BC at the top right point since it only has one degree of freedom. 165 | """ 166 | tol = DOLFIN_EPS 167 | return near(x[0], 1.0, tol) and near(x[1], 1.0, tol) 168 | 169 | 170 | def create_bcs(DVP, boundaries, **namespace): 171 | """ 172 | Apply pure DirichletBC for deformation, velocity using analytical solution. 173 | """ 174 | bcs = [] 175 | velocity = analytical_velocity() 176 | p_bc_val = analytical_pressure() 177 | 178 | u_bc = DirichletBC(DVP.sub(1), velocity, boundaries, 1) 179 | p_bc = DirichletBC(DVP.sub(2), p_bc_val, top_right_point, method="pointwise") 180 | 181 | bcs.append(u_bc) 182 | bcs.append(p_bc) 183 | 184 | return dict(bcs=bcs, velocity=velocity, p_bc_val=p_bc_val) 185 | 186 | def initiate(dvp_, DVP, **namespace): 187 | """ 188 | Initialize solution using analytical solution. 189 | """ 190 | inital_velocity = analytical_velocity() 191 | inital_pressure = analytical_pressure() 192 | # generate functions of the initial solution from expressions 193 | ui = interpolate(inital_velocity, DVP.sub(1).collapse()) 194 | pi = interpolate(inital_pressure, DVP.sub(2).collapse()) 195 | # assign the initial solution to dvp_ 196 | assign(dvp_["n"].sub(1), ui) 197 | assign(dvp_["n-1"].sub(1), ui) 198 | assign(dvp_["n"].sub(2), pi) 199 | assign(dvp_["n-1"].sub(2), pi) 200 | 201 | return dict(dvp_=dvp_) 202 | 203 | def pre_solve(t, velocity, p_bc_val, **namespace): 204 | """ 205 | update the boundary condition as boundary condition is time-dependent 206 | """ 207 | velocity.t = t 208 | p_bc_val.t = t 209 | 210 | return dict(velocity=velocity, p_bc_val=p_bc_val) 211 | 212 | def post_solve(DVP, dt, dvp_, total_error_v, total_error_p, velocity, p_bc_val, **namespace): 213 | """ 214 | Compute errors after solving 215 | """ 216 | # Get velocity, and pressure 217 | v = dvp_["n"].sub(1, deepcopy=True) 218 | p = dvp_["n"].sub(2, deepcopy=True) 219 | 220 | ve = interpolate(velocity, DVP.sub(1).collapse()) 221 | pe = interpolate(p_bc_val, DVP.sub(2).collapse()) 222 | E_v = errornorm(ve, v, norm_type="L2") 223 | E_p = errornorm(pe, p, norm_type="L2") 224 | 225 | total_error_v += E_v*dt 226 | total_error_p += E_p*dt 227 | 228 | if MPI.rank(MPI.comm_world) == 0: 229 | print("velocity error:", E_v) 230 | print("pressure error:", E_p) 231 | 232 | return dict(total_error_v=total_error_v, total_error_p=total_error_p) 233 | 234 | def finished(total_error_v, total_error_p, mesh_size, dt, results_folder, **namespace): 235 | """ 236 | print the total error and save the results 237 | """ 238 | if MPI.rank(MPI.comm_world) == 0: 239 | print(f"total error for the velocity: {total_error_v:2.6e}") 240 | print(f"total error for the pressure: {total_error_p:2.6e}") 241 | 242 | save_data = dict(total_error_v=total_error_v, total_error_p=total_error_p, mesh_size=mesh_size, dt=dt) 243 | file_name = "taylorgreen_results.pkl" 244 | with open(path.join(results_folder, file_name), 'wb') as f: 245 | pickle.dump(save_data, f) -------------------------------------------------------------------------------- /turtleFSI/problems/Test_Cylinder/mesh/artery_coarse_rescaled.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KVSlab/turtleFSI/5fa413853b1c89283674ccaa7053f664570206ff/turtleFSI/problems/Test_Cylinder/mesh/artery_coarse_rescaled.h5 -------------------------------------------------------------------------------- /turtleFSI/problems/Test_Cylinder/problem_aneu_2fluid.py: -------------------------------------------------------------------------------- 1 | from dolfin import * 2 | import os 3 | from turtleFSI.problems import * 4 | import numpy as np 5 | from os import path, makedirs, getcwd 6 | 7 | # BM will look at the IDs. Oringinal drawing :) Discuss drawing as well... 8 | # Thangam will clean up terminology, innerP, prestress and that everything works. Make sure that the code prints logically and chronologically. BCs in sep file@ 9 | # David B, add stress code and xdmf for viz 10 | 11 | # set compiler arguments 12 | parameters["form_compiler"]["quadrature_degree"] = 6 # Not investigated thorougly. See MSc theses of Gjertsen. 13 | parameters["form_compiler"]["optimize"] = True 14 | # The "ghost_mode" has to do with the assembly of form containing the facet 15 | # normals n('+') within interior boundaries (dS). for 3D mesh the value should 16 | # be "shared_vertex", for 2D mesh "shared_facet", the default value is "none" 17 | parameters["ghost_mode"] = "shared_vertex" 18 | _compiler_parameters = dict(parameters["form_compiler"]) 19 | 20 | 21 | def set_problem_parameters(default_variables, **namespace): 22 | # Overwrite default values 23 | E_s_val = 1E6 # Young modulus (Pa) 24 | nu_s_val = 0.45 25 | mu_s_val = E_s_val / (2 * (1 + nu_s_val)) # 0.345E6 26 | lambda_s_val = nu_s_val * 2. * mu_s_val / (1. - 2. * nu_s_val) 27 | 28 | default_variables.update(dict( 29 | T=0.02, # Simulation end time 30 | dt=0.001, # Timne step size 31 | theta=0.501, # Theta scheme (implicit/explicit time stepping) 32 | atol=1e-5, # Absolute tolerance in the Newton solver 33 | rtol=1e-5,# Relative tolerance in the Newton solver 34 | mesh_file="artery_coarse_rescaled", # duh 35 | inlet_id=2, # inlet id 36 | outlet_id1=3, # outlet nb 37 | inlet_outlet_s_id=11, # also the "rigid wall" id for the stucture problem. MB: Clarify :) 38 | fsi_id=22, # fsi Interface 39 | rigid_id=11, # "rigid wall" id for the fluid and mesh problem 40 | outer_wall_id=33, # outer surface / external id 41 | rho_f=[1.025E3,1.025E3], # Fluid density [kg/m3] 42 | mu_f=[3.5E-3,35E-3], # Fluid dynamic viscosity [Pa.s] 43 | 44 | rho_s=1.0E3, # Solid density [kg/m3] 45 | mu_s=mu_s_val, # Solid shear modulus or 2nd Lame Coef. [Pa] 46 | nu_s=nu_s_val, # Solid Poisson ratio [-] 47 | lambda_s=lambda_s_val, # Solid 1st Lame Coef. [Pa] 48 | 49 | dx_f_id=[1,1001], # ID of marker in the fluid domain. When reading the mesh, the fuid domain is assigned with a 1. 50 | dx_s_id=2, # ID of marker in the solid domain 51 | extrapolation="laplace", # laplace, elastic, biharmonic, no-extrapolation 52 | # ["constant", "small_constant", "volume", "volume_change", "bc1", "bc2"] 53 | extrapolation_sub_type="constant", 54 | recompute=3000, # Number of iterations before recompute Jacobian. 55 | recompute_tstep=1, # Number of time steps before recompute Jacobian. 56 | save_step=1, # Save frequency of files for visualisation 57 | folder="More_Viscous_Half", 58 | checkpoint_step=50, 59 | kill_time=100000, # in seconds, after this time start dumping checkpoints every timestep 60 | save_deg=1 # Default could be 1. 1 saves the nodal values only while 2 takes full advantage of the mide side nodes available in the P2 solution. P2 f0r nice visualisations :) 61 | # probe point(s) 62 | )) 63 | 64 | return default_variables 65 | 66 | 67 | def get_mesh_domain_and_boundaries(mesh_file, fsi_id, rigid_id, outer_wall_id, folder, **namespace): 68 | print("Obtaining mesh, domains and boundaries...") 69 | mesh = Mesh() 70 | hdf = HDF5File(mesh.mpi_comm(), "mesh/" + mesh_file + ".h5", "r") 71 | hdf.read(mesh, "/mesh", False) 72 | boundaries = MeshFunction("size_t", mesh, 2) 73 | hdf.read(boundaries, "/boundaries") 74 | domains = MeshFunction("size_t", mesh, 3) 75 | hdf.read(domains, "/domains") 76 | 77 | # Only consider FSI in domain within this cylindrical region # Corrected sphere for the rescaled artery! 78 | center_y = -6.299931555986404e-06 79 | extent_FSI = 0.01 # This makes the whole cylinder deformable 80 | min_y=center_y-extent_FSI 81 | max_y=center_y+extent_FSI 82 | print("The FSI region extends from y = {} to y = {}".format(min_y, max_y)) 83 | 84 | i = 0 85 | for submesh_facet in facets(mesh): 86 | idx_facet = boundaries.array()[i] 87 | if idx_facet == fsi_id or idx_facet == outer_wall_id: 88 | mid = submesh_facet.midpoint() 89 | mid_y = mid.y() 90 | if mid_y < min_y or mid_y > max_y: 91 | boundaries.array()[i] = rigid_id 92 | i += 1 93 | 94 | # Make the middle of the cylinder stiffer 95 | print("The more viscous region extends from y > {}".format(center_y)) 96 | 97 | i = 0 98 | for submesh_cell in cells(mesh): 99 | idx_cell = domains.array()[i] 100 | if idx_cell == 1: 101 | mid = submesh_cell.midpoint() 102 | mid_y = mid.y() 103 | if mid_y > center_y: 104 | domains.array()[i] = 1001 # 1001 is more viscous region 105 | i += 1 106 | 107 | 108 | print("Obtained mesh, domains and boundaries.") 109 | ff = File("domains/domains.pvd") 110 | ff << domains 111 | ff = File("domains/boundaries.pvd") 112 | ff << boundaries 113 | return mesh, domains, boundaries 114 | 115 | 116 | class VelInPara(UserExpression): 117 | def __init__(self, t, t_ramp, n, dsi, mesh, **kwargs): 118 | self.t = t 119 | self.t1 = t_ramp 120 | self.n = n 121 | self.dsi = dsi 122 | self.d = mesh.geometry().dim() 123 | self.x = SpatialCoordinate(mesh) 124 | # Compute area of boundary tesselation by integrating 1.0 over all facets 125 | self.A = assemble(Constant(1.0, name="one")*self.dsi) 126 | # Compute barycenter by integrating x components over all facets 127 | self.c = [assemble(self.x[i]*self.dsi) / self.A for i in range(self.d)] 128 | # Compute radius by taking max radius of boundary points 129 | self.r = np.sqrt(self.A / np.pi) 130 | super().__init__(**kwargs) 131 | 132 | def update(self, t): 133 | self.t = t 134 | 135 | def eval(self, value, x): 136 | 137 | if self.t < 0.1: 138 | interp_PA = self.t*(0.74/0.1) # 0.77 m/s mmHg rise in 0.1s 139 | else: 140 | interp_PA = self.t*0.9666667 + 0.64333333 # lower slope 141 | 142 | r2 = (x[0]-self.c[0])**2 + (x[1]-self.c[1])**2 + (x[2]-self.c[2])**2 # radius**2 143 | fact_r = 1 - (r2/self.r**2) 144 | 145 | value[0] = -self.n[0] * (interp_PA) *fact_r # *self.t 146 | value[1] = -self.n[1] * (interp_PA) *fact_r # *self.t 147 | value[2] = -self.n[2] * (interp_PA) *fact_r # *self.t 148 | 149 | def value_shape(self): 150 | return (3,) 151 | 152 | class InnerP(UserExpression): 153 | def __init__(self, t, **kwargs): 154 | self.t = t 155 | super().__init__(**kwargs) 156 | 157 | def eval(self, value, x): 158 | 159 | if self.t < 0.1: 160 | value[0] = self.t*(10932.4/0.1) # 9333 mmHg rise in 0.1s 161 | else: 162 | value[0] = self.t*10023.84667 + 9930.01533 # lower slope 163 | 164 | def value_shape(self): 165 | return () 166 | 167 | 168 | def create_bcs(dvp_, DVP, mesh, boundaries, domains, mu_f, 169 | fsi_id, outlet_id1, inlet_id, inlet_outlet_s_id, 170 | rigid_id, psi, F_solid_linear, **namespace): 171 | 172 | if MPI.rank(MPI.comm_world) == 0: 173 | print("Create bcs") 174 | 175 | # Define the pressure condition necessary to create the variational form (Neumann BCs) 176 | 177 | p_out_bc_val = InnerP(t=0.0, degree=2) 178 | 179 | dSS = Measure("dS", domain=mesh, subdomain_data=boundaries) 180 | n = FacetNormal(mesh) 181 | F_solid_linear += p_out_bc_val * inner(n('+'), psi('+'))*dSS(fsi_id) # defined on the reference domain 182 | 183 | # Fluid velocity BCs 184 | dsi = ds(inlet_id, domain=mesh, subdomain_data=boundaries) 185 | n = FacetNormal(mesh) 186 | ndim = mesh.geometry().dim() 187 | ni = np.array([assemble(n[i]*dsi) for i in range(ndim)]) 188 | n_len = np.sqrt(sum([ni[i]**2 for i in range(ndim)])) # Should always be 1!? 189 | normal = ni/n_len 190 | 191 | # new Parabolic profile 192 | u_inflow_exp = VelInPara(t=0.0, t_ramp=0.0, n=normal, dsi=dsi, mesh=mesh, degree=3) 193 | u_inlet = DirichletBC(DVP.sub(1), u_inflow_exp, boundaries, inlet_id) 194 | u_inlet_s = DirichletBC(DVP.sub(1), ((0.0, 0.0, 0.0)), boundaries, inlet_outlet_s_id) 195 | 196 | # Solid Displacement BCs 197 | d_inlet = DirichletBC(DVP.sub(0), ((0.0, 0.0, 0.0)), boundaries, inlet_id) 198 | d_inlet_s = DirichletBC(DVP.sub(0), ((0.0, 0.0, 0.0)), boundaries, inlet_outlet_s_id) 199 | d_rigid = DirichletBC(DVP.sub(0), ((0.0, 0.0, 0.0)), boundaries, rigid_id) 200 | 201 | # Assemble boundary conditions 202 | bcs = [u_inlet, d_inlet, u_inlet_s, d_inlet_s, d_rigid] 203 | 204 | 205 | return dict(bcs=bcs, u_inflow_exp=u_inflow_exp, p_out_bc_val=p_out_bc_val, 206 | F_solid_linear=F_solid_linear) 207 | 208 | 209 | def pre_solve(t, u_inflow_exp, p_out_bc_val, **namespace): 210 | # Update the time variable used for the inlet boundary condition 211 | u_inflow_exp.update(t) 212 | p_out_bc_val.t = t 213 | return dict(u_inflow_exp=u_inflow_exp, p_out_bc_val=p_out_bc_val) 214 | 215 | -------------------------------------------------------------------------------- /turtleFSI/problems/Test_Material/SimpleShearGent.py: -------------------------------------------------------------------------------- 1 | # File under GNU GPL (v3) licence, see LICENSE file for details. 2 | # This software is distributed WITHOUT ANY WARRANTY; without even 3 | # the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 4 | # PURPOSE. 5 | 6 | """Problem file for running the "CSM" benchmarks in [1]. The problem is beam under load. 7 | 8 | [1] Turek, Stefan, and Jaroslav Hron. "Proposal for numerical benchmarking of fluid-structure interaction 9 | between an elastic object and laminar incompressible flow." Fluid-structure interaction. 10 | Springer, Berlin, Heidelberg, 2006. 371-385.""" 11 | 12 | from dolfin import * 13 | import numpy as np 14 | from os import path 15 | import stress_strain as StrStr 16 | 17 | from turtleFSI.problems import * 18 | 19 | # set compiler arguments 20 | parameters["form_compiler"]["quadrature_degree"] = 6 # Not investigated thorougly. See MSc theses of Gjertsen. Doesnt affect the speed 21 | parameters["reorder_dofs_serial"] = False 22 | 23 | def set_problem_parameters(default_variables, **namespace): 24 | # Overwrite default values 25 | E_s_val = 1E6 # Young modulus (Pa) 26 | nu_s_val = 0.45 27 | mu_s_val = E_s_val / (2 * (1 + nu_s_val)) # 0.345E6 28 | lambda_s_val = nu_s_val * 2. * mu_s_val / (1. - 2. * nu_s_val) 29 | 30 | 31 | default_variables.update(dict( 32 | # Temporal variables 33 | T=0.2, # End time [s] 34 | dt=0.0005, # Time step [s] 35 | checkpoint_step=1000, # Checkpoint frequency 36 | theta=0.50, # Temporal scheme 37 | save_step=1, 38 | 39 | # Physical constants 40 | rho_f=1.0e3, # Fluid density [kg/m3] 41 | mu_f=1.0, # Fluid dynamic viscosity [Pa.s] 42 | 43 | rho_s=1.0E3, # Solid density [kg/m3] 44 | mu_s=mu_s_val, # Solid shear modulus or 2nd Lame Coef. [Pa] 45 | Jm=8.0, # Gent material model constant 46 | material_model="Gent", 47 | gravity=None, # Gravitational force [m/s**2] 48 | 49 | # Problem specific 50 | dx_f_id=0, # Id of the fluid domain 51 | dx_s_id=1, # Id of the solid domain 52 | folder="Simple_Shear_Gent", # Folder to store the results 53 | fluid="no_fluid", # Do not solve for the fluid 54 | extrapolation="no_extrapolation", # No displacement to extrapolate 55 | solid_vel=0.1, # this is the velocity of the wall with prescribed displacement 56 | 57 | # Geometric variables 58 | leftEnd=0.001, 59 | rightEnd=0.099)) 60 | 61 | return default_variables 62 | 63 | 64 | def get_mesh_domain_and_boundaries(leftEnd, rightEnd, **namespace): 65 | # Read mesh 66 | negEnd=0.001, 67 | posEnd=0.099 68 | a=Point(0.0, 0.0, 0.0) 69 | b=Point(0.1, 0.1, 0.1) 70 | mesh = BoxMesh(a, b, 6, 6, 6) 71 | #mesh = BoxMesh(a, b, 1, 1, 1) 72 | 73 | #print(dir(mesh)) 74 | # Mark boundaries 75 | Lwall = AutoSubDomain(lambda x: (x[0]< negEnd)) 76 | Rwall = AutoSubDomain(lambda x: (x[0]> posEnd)) 77 | sideY = AutoSubDomain(lambda x: (x[1] < negEnd or x[1] > posEnd)) 78 | sideZ = AutoSubDomain(lambda x: (x[2] < negEnd or x[2] > posEnd)) 79 | 80 | boundaries = MeshFunction("size_t", mesh, mesh.geometry().dim() - 1) 81 | boundaries.set_all(0) 82 | Lwall.mark(boundaries, 1) 83 | Rwall.mark(boundaries, 2) 84 | sideY.mark(boundaries, 3) 85 | sideZ.mark(boundaries, 4) 86 | 87 | 88 | # Mark domain 89 | domains = MeshFunction("size_t", mesh, mesh.geometry().dim()) 90 | domains.set_all(1) 91 | 92 | return mesh, domains, boundaries 93 | 94 | class PrescribedDisp(UserExpression): 95 | def __init__(self, solid_vel, **kwargs): 96 | self.solid_vel = solid_vel 97 | self.factor = 0 98 | 99 | super().__init__(**kwargs) 100 | 101 | def update(self, t): 102 | self.factor = t * self.solid_vel 103 | print('displacement = ', self.factor) 104 | 105 | def eval(self, value,x): 106 | value[0] = 0 107 | value[1] = self.factor * x[0]/0.1 108 | value[2] = 0 109 | #print('eval',x) 110 | 111 | def value_shape(self): 112 | return (3,) 113 | 114 | 115 | def create_bcs(DVP,d_deg,solid_vel, boundaries, **namespace): 116 | # Clamp on the left hand side 117 | u_lwall = DirichletBC(DVP.sub(0), ((0.0, 0.0, 0.0)), boundaries, 1) 118 | u_sideZ = DirichletBC(DVP.sub(0).sub(2), ((0.0)), boundaries, 4) # no out of plane deformation 119 | d_t = PrescribedDisp(solid_vel,degree=d_deg) 120 | u_sideY = DirichletBC(DVP.sub(0), d_t, boundaries, 3) # keep wall rigid 121 | u_rwall = DirichletBC(DVP.sub(0), d_t, boundaries, 2) # keep right wall rigid 122 | 123 | bcs = [u_lwall, u_rwall,u_sideY,u_sideZ] 124 | 125 | return dict(bcs=bcs,d_t=d_t) 126 | 127 | def pre_solve(t, d_t, **namespace): 128 | """Update boundary conditions""" 129 | d_t.update(t) 130 | 131 | 132 | def post_solve(t, dvp_, verbose,counter,save_step, visualization_folder,material_parameters, mesh,dx_s, **namespace): 133 | 134 | if counter % save_step == 0: 135 | 136 | return_dict=StrStr.calculate_stress_strain(t, dvp_, verbose, visualization_folder,material_parameters, mesh,dx_s, **namespace) 137 | 138 | return return_dict -------------------------------------------------------------------------------- /turtleFSI/problems/Test_Material/SimpleShearMooneyRivlin.py: -------------------------------------------------------------------------------- 1 | # File under GNU GPL (v3) licence, see LICENSE file for details. 2 | # This software is distributed WITHOUT ANY WARRANTY; without even 3 | # the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 4 | # PURPOSE. 5 | 6 | """Problem file for running the "CSM" benchmarks in [1]. The problem is beam under load. 7 | 8 | [1] Turek, Stefan, and Jaroslav Hron. "Proposal for numerical benchmarking of fluid-structure interaction 9 | between an elastic object and laminar incompressible flow." Fluid-structure interaction. 10 | Springer, Berlin, Heidelberg, 2006. 371-385.""" 11 | 12 | from dolfin import * 13 | import numpy as np 14 | from os import path 15 | import stress_strain as StrStr 16 | 17 | from turtleFSI.problems import * 18 | 19 | # set compiler arguments 20 | parameters["form_compiler"]["quadrature_degree"] = 6 # Not investigated thorougly. See MSc theses of Gjertsen. Doesnt affect the speed 21 | parameters["reorder_dofs_serial"] = False 22 | 23 | def set_problem_parameters(default_variables, **namespace): 24 | # Overwrite default values 25 | E_s_val = 1E6 # Young modulus (Pa) 26 | nu_s_val = 0.45 27 | mu_s_val = E_s_val / (2 * (1 + nu_s_val)) # 0.345E6 28 | lambda_s_val = nu_s_val * 2. * mu_s_val / (1. - 2. * nu_s_val) 29 | 30 | 31 | default_variables.update(dict( 32 | # Temporal variables 33 | T=0.2, # End time [s] 34 | dt=0.0005, # Time step [s] 35 | checkpoint_step=1000, # Checkpoint frequency 36 | theta=0.50, # Temporal scheme 37 | save_step=1, 38 | 39 | # Physical constants 40 | rho_f=1.0e3, # Fluid density [kg/m3] 41 | mu_f=1.0, # Fluid dynamic viscosity [Pa.s] 42 | 43 | rho_s=1.0E3, # Solid density [kg/m3] 44 | mu_s=mu_s_val, # Solid shear modulus or 2nd Lame Coef. [Pa] 45 | nu_s=nu_s_val, # Solid Poisson ratio [-] 46 | lambda_s=lambda_s_val, # Solid 1st Lame Coef. [Pa] 47 | C01=mu_s_val/5, # Mooney Rivlin constant C01 (C10 is auomatically determined from mu_s) 48 | material_model="MooneyRivlin", 49 | gravity=None, # Gravitational force [m/s**2] 50 | 51 | # Problem specific 52 | dx_f_id=0, # Id of the fluid domain 53 | dx_s_id=1, # Id of the solid domain 54 | folder="Simple_Shear_MooneyRivlin", # Folder to store the results 55 | fluid="no_fluid", # Do not solve for the fluid 56 | extrapolation="no_extrapolation", # No displacement to extrapolate 57 | solid_vel=0.1, # this is the velocity of the wall with prescribed displacement 58 | 59 | # Geometric variables 60 | leftEnd=0.001, 61 | rightEnd=0.099)) 62 | 63 | return default_variables 64 | 65 | 66 | def get_mesh_domain_and_boundaries(leftEnd, rightEnd, **namespace): 67 | # Read mesh 68 | negEnd=0.001, 69 | posEnd=0.099 70 | a=Point(0.0, 0.0, 0.0) 71 | b=Point(0.1, 0.1, 0.1) 72 | mesh = BoxMesh(a, b, 6, 6, 6) 73 | #mesh = BoxMesh(a, b, 1, 1, 1) 74 | 75 | #print(dir(mesh)) 76 | # Mark boundaries 77 | Lwall = AutoSubDomain(lambda x: (x[0]< negEnd)) 78 | Rwall = AutoSubDomain(lambda x: (x[0]> posEnd)) 79 | sideY = AutoSubDomain(lambda x: (x[1] < negEnd or x[1] > posEnd)) 80 | sideZ = AutoSubDomain(lambda x: (x[2] < negEnd or x[2] > posEnd)) 81 | 82 | boundaries = MeshFunction("size_t", mesh, mesh.geometry().dim() - 1) 83 | boundaries.set_all(0) 84 | Lwall.mark(boundaries, 1) 85 | Rwall.mark(boundaries, 2) 86 | sideY.mark(boundaries, 3) 87 | sideZ.mark(boundaries, 4) 88 | 89 | 90 | # Mark domain 91 | domains = MeshFunction("size_t", mesh, mesh.geometry().dim()) 92 | domains.set_all(1) 93 | 94 | return mesh, domains, boundaries 95 | 96 | class PrescribedDisp(UserExpression): 97 | def __init__(self, solid_vel, **kwargs): 98 | self.solid_vel = solid_vel 99 | self.factor = 0 100 | 101 | super().__init__(**kwargs) 102 | 103 | def update(self, t): 104 | self.factor = t * self.solid_vel 105 | print('displacement = ', self.factor) 106 | 107 | def eval(self, value,x): 108 | value[0] = 0 109 | value[1] = self.factor * x[0]/0.1 110 | value[2] = 0 111 | #print('eval',x) 112 | 113 | def value_shape(self): 114 | return (3,) 115 | 116 | 117 | def create_bcs(DVP,d_deg,solid_vel, boundaries, **namespace): 118 | # Clamp on the left hand side 119 | u_lwall = DirichletBC(DVP.sub(0), ((0.0, 0.0, 0.0)), boundaries, 1) 120 | u_sideZ = DirichletBC(DVP.sub(0).sub(2), ((0.0)), boundaries, 4) # no out of plane deformation 121 | d_t = PrescribedDisp(solid_vel,degree=d_deg) 122 | u_sideY = DirichletBC(DVP.sub(0), d_t, boundaries, 3) # keep wall rigid 123 | u_rwall = DirichletBC(DVP.sub(0), d_t, boundaries, 2) # keep right wall rigid 124 | 125 | bcs = [u_lwall, u_rwall,u_sideY,u_sideZ] 126 | 127 | return dict(bcs=bcs,d_t=d_t) 128 | 129 | def pre_solve(t, d_t, **namespace): 130 | """Update boundary conditions""" 131 | d_t.update(t) 132 | 133 | 134 | def post_solve(t, dvp_, verbose,counter,save_step, visualization_folder,material_parameters, mesh,dx_s, **namespace): 135 | 136 | if counter % save_step == 0: 137 | 138 | return_dict=StrStr.calculate_stress_strain(t, dvp_, verbose, visualization_folder,material_parameters, mesh,dx_s, **namespace) 139 | 140 | return return_dict -------------------------------------------------------------------------------- /turtleFSI/problems/Test_Material/SimpleShearNeoHookean.py: -------------------------------------------------------------------------------- 1 | # File under GNU GPL (v3) licence, see LICENSE file for details. 2 | # This software is distributed WITHOUT ANY WARRANTY; without even 3 | # the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 4 | # PURPOSE. 5 | 6 | """Problem file for running the "CSM" benchmarks in [1]. The problem is beam under load. 7 | 8 | [1] Turek, Stefan, and Jaroslav Hron. "Proposal for numerical benchmarking of fluid-structure interaction 9 | between an elastic object and laminar incompressible flow." Fluid-structure interaction. 10 | Springer, Berlin, Heidelberg, 2006. 371-385.""" 11 | 12 | from dolfin import * 13 | import numpy as np 14 | from os import path 15 | import stress_strain as StrStr 16 | 17 | from turtleFSI.problems import * 18 | 19 | # set compiler arguments 20 | parameters["form_compiler"]["quadrature_degree"] = 6 # Not investigated thorougly. See MSc theses of Gjertsen. Doesnt affect the speed 21 | parameters["reorder_dofs_serial"] = False 22 | 23 | def set_problem_parameters(default_variables, **namespace): 24 | # Overwrite default values 25 | E_s_val = 1E6 # Young modulus (Pa) 26 | nu_s_val = 0.45 27 | mu_s_val = E_s_val / (2 * (1 + nu_s_val)) # 0.345E6 28 | lambda_s_val = nu_s_val * 2. * mu_s_val / (1. - 2. * nu_s_val) 29 | 30 | 31 | default_variables.update(dict( 32 | # Temporal variables 33 | T=0.2, # End time [s] 34 | dt=0.0005, # Time step [s] 35 | checkpoint_step=1000, # Checkpoint frequency 36 | theta=0.50, # Temporal scheme 37 | save_step=1, 38 | 39 | # Physical constants 40 | rho_f=1.0e3, # Fluid density [kg/m3] 41 | mu_f=1.0, # Fluid dynamic viscosity [Pa.s] 42 | 43 | rho_s=1.0E3, # Solid density [kg/m3] 44 | mu_s=mu_s_val, # Solid shear modulus or 2nd Lame Coef. [Pa] 45 | nu_s=nu_s_val, # Solid Poisson ratio [-] 46 | lambda_s=lambda_s_val, # Solid 1st Lame Coef. [Pa] 47 | material_model="NeoHookean", 48 | gravity=None, # Gravitational force [m/s**2] 49 | 50 | # Problem specific 51 | dx_f_id=0, # Id of the fluid domain 52 | dx_s_id=1, # Id of the solid domain 53 | folder="Simple_Shear_NeoHookean", # Folder to store the results 54 | fluid="no_fluid", # Do not solve for the fluid 55 | extrapolation="no_extrapolation", # No displacement to extrapolate 56 | solid_vel=0.1, # this is the velocity of the wall with prescribed displacement 57 | 58 | # Geometric variables 59 | leftEnd=0.001, 60 | rightEnd=0.099)) 61 | 62 | return default_variables 63 | 64 | 65 | def get_mesh_domain_and_boundaries(leftEnd, rightEnd, **namespace): 66 | # Read mesh 67 | negEnd=0.001, 68 | posEnd=0.099 69 | a=Point(0.0, 0.0, 0.0) 70 | b=Point(0.1, 0.1, 0.1) 71 | mesh = BoxMesh(a, b, 6, 6, 6) 72 | #mesh = BoxMesh(a, b, 1, 1, 1) 73 | 74 | #print(dir(mesh)) 75 | # Mark boundaries 76 | Lwall = AutoSubDomain(lambda x: (x[0]< negEnd)) 77 | Rwall = AutoSubDomain(lambda x: (x[0]> posEnd)) 78 | sideY = AutoSubDomain(lambda x: (x[1] < negEnd or x[1] > posEnd)) 79 | sideZ = AutoSubDomain(lambda x: (x[2] < negEnd or x[2] > posEnd)) 80 | 81 | boundaries = MeshFunction("size_t", mesh, mesh.geometry().dim() - 1) 82 | boundaries.set_all(0) 83 | Lwall.mark(boundaries, 1) 84 | Rwall.mark(boundaries, 2) 85 | sideY.mark(boundaries, 3) 86 | sideZ.mark(boundaries, 4) 87 | 88 | 89 | # Mark domain 90 | domains = MeshFunction("size_t", mesh, mesh.geometry().dim()) 91 | domains.set_all(1) 92 | 93 | return mesh, domains, boundaries 94 | 95 | class PrescribedDisp(UserExpression): 96 | def __init__(self, solid_vel, **kwargs): 97 | self.solid_vel = solid_vel 98 | self.factor = 0 99 | 100 | super().__init__(**kwargs) 101 | 102 | def update(self, t): 103 | self.factor = t * self.solid_vel 104 | print('displacement = ', self.factor) 105 | 106 | def eval(self, value,x): 107 | value[0] = 0 108 | value[1] = self.factor * x[0]/0.1 109 | value[2] = 0 110 | #print('eval',x) 111 | 112 | def value_shape(self): 113 | return (3,) 114 | 115 | 116 | def create_bcs(DVP,d_deg,solid_vel, boundaries, **namespace): 117 | # Clamp on the left hand side 118 | u_lwall = DirichletBC(DVP.sub(0), ((0.0, 0.0, 0.0)), boundaries, 1) 119 | u_sideZ = DirichletBC(DVP.sub(0).sub(2), ((0.0)), boundaries, 4) # no out of plane deformation 120 | d_t = PrescribedDisp(solid_vel,degree=d_deg) 121 | u_sideY = DirichletBC(DVP.sub(0), d_t, boundaries, 3) # keep wall rigid 122 | u_rwall = DirichletBC(DVP.sub(0), d_t, boundaries, 2) # keep right wall rigid 123 | 124 | bcs = [u_lwall, u_rwall,u_sideY,u_sideZ] 125 | 126 | return dict(bcs=bcs,d_t=d_t) 127 | 128 | def pre_solve(t, d_t, **namespace): 129 | """Update boundary conditions""" 130 | d_t.update(t) 131 | 132 | 133 | def post_solve(t, dvp_, verbose,counter,save_step, visualization_folder,material_parameters, mesh,dx_s, **namespace): 134 | 135 | if counter % save_step == 0: 136 | 137 | return_dict=StrStr.calculate_stress_strain(t, dvp_, verbose, visualization_folder,material_parameters, mesh,dx_s, **namespace) 138 | 139 | return return_dict -------------------------------------------------------------------------------- /turtleFSI/problems/Test_Material/SimpleShearSVK.py: -------------------------------------------------------------------------------- 1 | # File under GNU GPL (v3) licence, see LICENSE file for details. 2 | # This software is distributed WITHOUT ANY WARRANTY; without even 3 | # the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 4 | # PURPOSE. 5 | 6 | """Problem file for running the "CSM" benchmarks in [1]. The problem is beam under load. 7 | 8 | [1] Turek, Stefan, and Jaroslav Hron. "Proposal for numerical benchmarking of fluid-structure interaction 9 | between an elastic object and laminar incompressible flow." Fluid-structure interaction. 10 | Springer, Berlin, Heidelberg, 2006. 371-385.""" 11 | 12 | from dolfin import * 13 | import numpy as np 14 | from os import path 15 | import stress_strain as StrStr 16 | 17 | from turtleFSI.problems import * 18 | 19 | # set compiler arguments 20 | parameters["form_compiler"]["quadrature_degree"] = 6 # Not investigated thorougly. See MSc theses of Gjertsen. Doesnt affect the speed 21 | parameters["reorder_dofs_serial"] = False 22 | 23 | def set_problem_parameters(default_variables, **namespace): 24 | # Overwrite default values 25 | E_s_val = 1E6 # Young modulus (Pa) 26 | nu_s_val = 0.45 27 | mu_s_val = E_s_val / (2 * (1 + nu_s_val)) # 0.345E6 28 | lambda_s_val = nu_s_val * 2. * mu_s_val / (1. - 2. * nu_s_val) 29 | 30 | 31 | default_variables.update(dict( 32 | # Temporal variables 33 | T=0.2, # End time [s] 34 | dt=0.0005, # Time step [s] 35 | checkpoint_step=1000, # Checkpoint frequency 36 | theta=0.50, # Temporal scheme 37 | save_step=1, 38 | 39 | # Physical constants 40 | rho_f=1.0e3, # Fluid density [kg/m3] 41 | mu_f=1.0, # Fluid dynamic viscosity [Pa.s] 42 | 43 | rho_s=1.0E3, # Solid density [kg/m3] 44 | mu_s=mu_s_val, # Solid shear modulus or 2nd Lame Coef. [Pa] 45 | nu_s=nu_s_val, # Solid Poisson ratio [-] 46 | lambda_s=lambda_s_val, # Solid 1st Lame Coef. [Pa] 47 | gravity=None, # Gravitational force [m/s**2] 48 | 49 | # Problem specific 50 | dx_f_id=0, # Id of the fluid domain 51 | dx_s_id=1, # Id of the solid domain 52 | folder="Simple_Shear_SVK", # Folder to store the results 53 | fluid="no_fluid", # Do not solve for the fluid 54 | extrapolation="no_extrapolation", # No displacement to extrapolate 55 | solid_vel=0.1, # this is the velocity of the wall with prescribed displacement 56 | 57 | # Geometric variables 58 | leftEnd=0.001, 59 | rightEnd=0.099)) 60 | 61 | return default_variables 62 | 63 | 64 | def get_mesh_domain_and_boundaries(leftEnd, rightEnd, **namespace): 65 | # Read mesh 66 | negEnd=0.001, 67 | posEnd=0.099 68 | a=Point(0.0, 0.0, 0.0) 69 | b=Point(0.1, 0.1, 0.1) 70 | mesh = BoxMesh(a, b, 6, 6, 6) 71 | #mesh = BoxMesh(a, b, 1, 1, 1) 72 | 73 | #print(dir(mesh)) 74 | # Mark boundaries 75 | Lwall = AutoSubDomain(lambda x: (x[0]< negEnd)) 76 | Rwall = AutoSubDomain(lambda x: (x[0]> posEnd)) 77 | sideY = AutoSubDomain(lambda x: (x[1] < negEnd or x[1] > posEnd)) 78 | sideZ = AutoSubDomain(lambda x: (x[2] < negEnd or x[2] > posEnd)) 79 | 80 | boundaries = MeshFunction("size_t", mesh, mesh.geometry().dim() - 1) 81 | boundaries.set_all(0) 82 | Lwall.mark(boundaries, 1) 83 | Rwall.mark(boundaries, 2) 84 | sideY.mark(boundaries, 3) 85 | sideZ.mark(boundaries, 4) 86 | 87 | 88 | # Mark domain 89 | domains = MeshFunction("size_t", mesh, mesh.geometry().dim()) 90 | domains.set_all(1) 91 | 92 | return mesh, domains, boundaries 93 | 94 | class PrescribedDisp(UserExpression): 95 | def __init__(self, solid_vel, **kwargs): 96 | self.solid_vel = solid_vel 97 | self.factor = 0 98 | 99 | super().__init__(**kwargs) 100 | 101 | def update(self, t): 102 | self.factor = t * self.solid_vel 103 | print('displacement = ', self.factor) 104 | 105 | def eval(self, value,x): 106 | value[0] = 0 107 | value[1] = self.factor * x[0]/0.1 108 | value[2] = 0 109 | #print('eval',x) 110 | 111 | def value_shape(self): 112 | return (3,) 113 | 114 | 115 | def create_bcs(DVP,d_deg,solid_vel, boundaries, **namespace): 116 | # Clamp on the left hand side 117 | u_lwall = DirichletBC(DVP.sub(0), ((0.0, 0.0, 0.0)), boundaries, 1) 118 | u_sideZ = DirichletBC(DVP.sub(0).sub(2), ((0.0)), boundaries, 4) # no out of plane deformation 119 | d_t = PrescribedDisp(solid_vel,degree=d_deg) 120 | u_sideY = DirichletBC(DVP.sub(0), d_t, boundaries, 3) # keep wall rigid 121 | u_rwall = DirichletBC(DVP.sub(0), d_t, boundaries, 2) # keep right wall rigid 122 | 123 | bcs = [u_lwall, u_rwall,u_sideY,u_sideZ] 124 | 125 | return dict(bcs=bcs,d_t=d_t) 126 | 127 | def pre_solve(t, d_t, **namespace): 128 | """Update boundary conditions""" 129 | d_t.update(t) 130 | 131 | 132 | def post_solve(t, dvp_, verbose,counter,save_step, visualization_folder,material_parameters, mesh,dx_s, **namespace): 133 | 134 | if counter % save_step == 0: 135 | 136 | return_dict=StrStr.calculate_stress_strain(t, dvp_, verbose, visualization_folder,material_parameters, mesh,dx_s, **namespace) 137 | 138 | return return_dict -------------------------------------------------------------------------------- /turtleFSI/problems/Test_Material/SimpleShearSVKEnergy.py: -------------------------------------------------------------------------------- 1 | # File under GNU GPL (v3) licence, see LICENSE file for details. 2 | # This software is distributed WITHOUT ANY WARRANTY; without even 3 | # the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 4 | # PURPOSE. 5 | 6 | """Problem file for running the "CSM" benchmarks in [1]. The problem is beam under load. 7 | 8 | [1] Turek, Stefan, and Jaroslav Hron. "Proposal for numerical benchmarking of fluid-structure interaction 9 | between an elastic object and laminar incompressible flow." Fluid-structure interaction. 10 | Springer, Berlin, Heidelberg, 2006. 371-385.""" 11 | 12 | from dolfin import * 13 | import numpy as np 14 | from os import path 15 | import stress_strain as StrStr 16 | 17 | from turtleFSI.problems import * 18 | 19 | # set compiler arguments 20 | parameters["form_compiler"]["quadrature_degree"] = 6 # Not investigated thorougly. See MSc theses of Gjertsen. Doesnt affect the speed 21 | parameters["reorder_dofs_serial"] = False 22 | 23 | def set_problem_parameters(default_variables, **namespace): 24 | # Overwrite default values 25 | E_s_val = 1E6 # Young modulus (Pa) 26 | nu_s_val = 0.45 27 | mu_s_val = E_s_val / (2 * (1 + nu_s_val)) # 0.345E6 28 | lambda_s_val = nu_s_val * 2. * mu_s_val / (1. - 2. * nu_s_val) 29 | 30 | 31 | default_variables.update(dict( 32 | # Temporal variables 33 | T=0.2, # End time [s] 34 | dt=0.0005, # Time step [s] 35 | checkpoint_step=1000, # Checkpoint frequency 36 | theta=0.50, # Temporal scheme 37 | save_step=1, 38 | 39 | # Physical constants 40 | rho_f=1.0e3, # Fluid density [kg/m3] 41 | mu_f=1.0, # Fluid dynamic viscosity [Pa.s] 42 | 43 | rho_s=1.0E3, # Solid density [kg/m3] 44 | mu_s=mu_s_val, # Solid shear modulus or 2nd Lame Coef. [Pa] 45 | nu_s=nu_s_val, # Solid Poisson ratio [-] 46 | lambda_s=lambda_s_val, # Solid 1st Lame Coef. [Pa] 47 | material_model="StVenantKirchoffEnergy", 48 | gravity=None, # Gravitational force [m/s**2] 49 | 50 | # Problem specific 51 | dx_f_id=0, # Id of the fluid domain 52 | dx_s_id=1, # Id of the solid domain 53 | folder="Simple_Shear_SVK_Energy", # Folder to store the results 54 | fluid="no_fluid", # Do not solve for the fluid 55 | extrapolation="no_extrapolation", # No displacement to extrapolate 56 | solid_vel=0.1, # this is the velocity of the wall with prescribed displacement 57 | 58 | # Geometric variables 59 | leftEnd=0.001, 60 | rightEnd=0.099)) 61 | 62 | return default_variables 63 | 64 | 65 | def get_mesh_domain_and_boundaries(leftEnd, rightEnd, **namespace): 66 | # Read mesh 67 | negEnd=0.001, 68 | posEnd=0.099 69 | a=Point(0.0, 0.0, 0.0) 70 | b=Point(0.1, 0.1, 0.1) 71 | mesh = BoxMesh(a, b, 6, 6, 6) 72 | #mesh = BoxMesh(a, b, 1, 1, 1) 73 | 74 | #print(dir(mesh)) 75 | # Mark boundaries 76 | Lwall = AutoSubDomain(lambda x: (x[0]< negEnd)) 77 | Rwall = AutoSubDomain(lambda x: (x[0]> posEnd)) 78 | sideY = AutoSubDomain(lambda x: (x[1] < negEnd or x[1] > posEnd)) 79 | sideZ = AutoSubDomain(lambda x: (x[2] < negEnd or x[2] > posEnd)) 80 | 81 | boundaries = MeshFunction("size_t", mesh, mesh.geometry().dim() - 1) 82 | boundaries.set_all(0) 83 | Lwall.mark(boundaries, 1) 84 | Rwall.mark(boundaries, 2) 85 | sideY.mark(boundaries, 3) 86 | sideZ.mark(boundaries, 4) 87 | 88 | 89 | # Mark domain 90 | domains = MeshFunction("size_t", mesh, mesh.geometry().dim()) 91 | domains.set_all(1) 92 | 93 | return mesh, domains, boundaries 94 | 95 | class PrescribedDisp(UserExpression): 96 | def __init__(self, solid_vel, **kwargs): 97 | self.solid_vel = solid_vel 98 | self.factor = 0 99 | 100 | super().__init__(**kwargs) 101 | 102 | def update(self, t): 103 | self.factor = t * self.solid_vel 104 | print('displacement = ', self.factor) 105 | 106 | def eval(self, value,x): 107 | value[0] = 0 108 | value[1] = self.factor * x[0]/0.1 109 | value[2] = 0 110 | #print('eval',x) 111 | 112 | def value_shape(self): 113 | return (3,) 114 | 115 | 116 | def create_bcs(DVP,d_deg,solid_vel, boundaries, **namespace): 117 | # Clamp on the left hand side 118 | u_lwall = DirichletBC(DVP.sub(0), ((0.0, 0.0, 0.0)), boundaries, 1) 119 | u_sideZ = DirichletBC(DVP.sub(0).sub(2), ((0.0)), boundaries, 4) # no out of plane deformation 120 | d_t = PrescribedDisp(solid_vel,degree=d_deg) 121 | u_sideY = DirichletBC(DVP.sub(0), d_t, boundaries, 3) # keep wall rigid 122 | u_rwall = DirichletBC(DVP.sub(0), d_t, boundaries, 2) # keep right wall rigid 123 | 124 | bcs = [u_lwall, u_rwall,u_sideY,u_sideZ] 125 | 126 | return dict(bcs=bcs,d_t=d_t) 127 | 128 | def pre_solve(t, d_t, **namespace): 129 | """Update boundary conditions""" 130 | d_t.update(t) 131 | 132 | 133 | def post_solve(t, dvp_, verbose,counter,save_step, visualization_folder,material_parameters, mesh,dx_s, **namespace): 134 | 135 | if counter % save_step == 0: 136 | 137 | return_dict=StrStr.calculate_stress_strain(t, dvp_, verbose, visualization_folder,material_parameters, mesh,dx_s, **namespace) 138 | 139 | return return_dict -------------------------------------------------------------------------------- /turtleFSI/problems/Test_Material/UniaxialTensionGent.py: -------------------------------------------------------------------------------- 1 | # File under GNU GPL (v3) licence, see LICENSE file for details. 2 | # This software is distributed WITHOUT ANY WARRANTY; without even 3 | # the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 4 | # PURPOSE. 5 | 6 | """Problem file for running the "CSM" benchmarks in [1]. The problem is beam under load. 7 | 8 | [1] Turek, Stefan, and Jaroslav Hron. "Proposal for numerical benchmarking of fluid-structure interaction 9 | between an elastic object and laminar incompressible flow." Fluid-structure interaction. 10 | Springer, Berlin, Heidelberg, 2006. 371-385.""" 11 | 12 | from dolfin import * 13 | import numpy as np 14 | from os import path 15 | import stress_strain as StrStr 16 | 17 | from turtleFSI.problems import * 18 | parameters["form_compiler"]["quadrature_degree"] = 6 # Not investigated thorougly. See MSc theses of Gjertsen. 19 | 20 | def set_problem_parameters(default_variables, **namespace): 21 | # Overwrite default values 22 | E_s_val = 0.1E6 # Young modulus (Pa) 23 | nu_s_val = 0.45 24 | mu_s_val = E_s_val / (2 * (1 + nu_s_val)) # 0.345E6 25 | lambda_s_val = nu_s_val * 2. * mu_s_val / (1. - 2. * nu_s_val) 26 | 27 | 28 | default_variables.update(dict( 29 | # Temporal variables 30 | T=0.2, # End time [s] 31 | dt=0.001, # Time step [s] 32 | checkpoint_step=1000, # Checkpoint frequency 33 | theta=0.5, # Temporal scheme 34 | save_step=1, 35 | 36 | # Physical constants 37 | rho_f=1.0e3, # Fluid density [kg/m3] 38 | mu_f=1.0, # Fluid dynamic viscosity [Pa.s] 39 | 40 | rho_s=1.0E3, # Solid density [kg/m3] 41 | mu_s=mu_s_val, # Solid shear modulus or 2nd Lame Coef. [Pa] 42 | Jm=0.5, # Gent material model constant 43 | material_model="Gent", 44 | gravity=None, # Gravitational force [m/s**2] 45 | 46 | # Problem specific 47 | dx_f_id=0, # Id of the fluid domain 48 | dx_s_id=1, # Id of the solid domain 49 | folder="Uniaxial_Tension_Gent", # Folder to store the results 50 | fluid="no_fluid", # Do not solve for the fluid 51 | extrapolation="no_extrapolation", # No displacement to extrapolate 52 | solid_vel=0.2, # this is the velocity of the wall with prescribed displacement 53 | 54 | # Geometric variables 55 | leftEnd=0.001, 56 | rightEnd=0.099)) 57 | 58 | return default_variables 59 | 60 | 61 | def get_mesh_domain_and_boundaries(leftEnd, rightEnd, **namespace): 62 | # Read mesh 63 | 64 | a=Point(0.0, 0.0, 0.0) 65 | b=Point(0.1, 0.1, 0.1) 66 | mesh = BoxMesh(a, b, 3, 3, 3) 67 | 68 | #print(dir(mesh)) 69 | # Mark boundaries 70 | Lwall = AutoSubDomain(lambda x: (x[0]< leftEnd)) 71 | Rwall = AutoSubDomain(lambda x: (x[0]> rightEnd)) 72 | u_sideY = AutoSubDomain(lambda x: (x[1] < 0.001)) 73 | u_sideZ = AutoSubDomain(lambda x: (x[2] < 0.001)) 74 | 75 | boundaries = MeshFunction("size_t", mesh, mesh.geometry().dim() - 1) 76 | 77 | boundaries.set_all(0) 78 | 79 | Lwall.mark(boundaries, 1) 80 | Rwall.mark(boundaries, 2) 81 | u_sideY.mark(boundaries, 3) 82 | u_sideZ.mark(boundaries, 4) 83 | 84 | # Mark domain 85 | domains = MeshFunction("size_t", mesh, mesh.geometry().dim()) 86 | domains.set_all(1) 87 | 88 | return mesh, domains, boundaries, 89 | 90 | class PrescribedDisp(UserExpression): 91 | def __init__(self, solid_vel, **kwargs): 92 | self.solid_vel = solid_vel 93 | self.factor = 0 94 | 95 | super().__init__(**kwargs) 96 | 97 | def update(self, t): 98 | self.factor = t * self.solid_vel 99 | print('displacement = ', self.factor) 100 | 101 | def eval(self, value,x): 102 | value[0] = self.factor 103 | 104 | 105 | 106 | def create_bcs(DVP,d_deg,solid_vel, boundaries, **namespace): 107 | # Sliding contact on 3 sides 108 | u_lwallX = DirichletBC(DVP.sub(0).sub(0), ((0.0)), boundaries, 1) 109 | u_CornerY = DirichletBC(DVP.sub(0).sub(1), ((0.0)), boundaries, 3) 110 | u_CornerZ = DirichletBC(DVP.sub(0).sub(2), ((0.0)), boundaries, 4) 111 | 112 | # Displacement on the right hand side (unconstrained in Y and Z) 113 | d_t = PrescribedDisp(solid_vel,degree=d_deg) 114 | u_rwall = DirichletBC(DVP.sub(0).sub(0), d_t, boundaries, 2) 115 | 116 | 117 | bcs = [u_lwallX, u_rwall,u_CornerY,u_CornerZ] 118 | 119 | return dict(bcs=bcs,d_t=d_t) 120 | 121 | def pre_solve(t, d_t, **namespace): 122 | """Update boundary conditions""" 123 | d_t.update(t) 124 | 125 | def post_solve(t, dvp_, verbose,counter,save_step, visualization_folder,material_parameters, mesh,dx_s, **namespace): 126 | 127 | if counter % save_step == 0: 128 | 129 | return_dict=StrStr.calculate_stress_strain(t, dvp_, verbose, visualization_folder,material_parameters, mesh,dx_s, **namespace) 130 | 131 | return return_dict -------------------------------------------------------------------------------- /turtleFSI/problems/Test_Material/UniaxialTensionMooneyRivlin.py: -------------------------------------------------------------------------------- 1 | # File under GNU GPL (v3) licence, see LICENSE file for details. 2 | # This software is distributed WITHOUT ANY WARRANTY; without even 3 | # the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 4 | # PURPOSE. 5 | 6 | """ 7 | This file is a problem file for the uniaxial tension test of a Mooney-Rivlin material. 8 | """ 9 | 10 | from dolfin import * 11 | import turtleFSI.problems.Test_Material.stress_strain as StrStr 12 | 13 | from turtleFSI.problems import * 14 | parameters["form_compiler"]["quadrature_degree"] = 6 # Not investigated thorougly. See MSc theses of Gjertsen. 15 | 16 | def set_problem_parameters(default_variables, **namespace): 17 | 18 | E_s_val = 1E6 19 | nu_s_val = 0.45 20 | mu_s_val = E_s_val/(2*(1+nu_s_val)) # 0.345E6 21 | lambda_s_val = nu_s_val*2.*mu_s_val/(1. - 2.*nu_s_val) 22 | 23 | default_variables.update(dict( 24 | # Temporal variables 25 | T=0.3, # End time [s] 26 | dt=0.001, # Time step [s] 27 | checkpoint_step=1000, # Checkpoint frequency 28 | theta=0.5, # Temporal scheme 29 | save_step=1, 30 | 31 | # Physical constants 32 | rho_f=1.0e3, # Fluid density [kg/m3] 33 | mu_f=1.0, # Fluid dynamic viscosity [Pa.s] 34 | 35 | solid_properties={"dx_s_id":1,"material_model":"MooneyRivlin","rho_s":1.0E3,"mu_s":mu_s_val,"lambda_s":lambda_s_val,"C01":0.02e6,"C10":0.0,"C11":1.8e6}, 36 | gravity=None, # Gravitational force [m/s**2] 37 | 38 | # Problem specific 39 | dx_f_id=0, # Id of the fluid domain 40 | dx_s_id=1, # Id of the solid domain 41 | folder="Uniaxial_Tension_MooneyRivlin", # Folder to store the results 42 | fluid="no_fluid", # Do not solve for the fluid 43 | extrapolation="no_extrapolation", # No displacement to extrapolate 44 | solid_vel=0.1, # this is the velocity of the wall with prescribed displacement 45 | 46 | # Geometric variables 47 | leftEnd=0.001, 48 | rightEnd=0.099)) 49 | 50 | return default_variables 51 | 52 | 53 | def get_mesh_domain_and_boundaries(leftEnd, rightEnd, **namespace): 54 | # Read mesh 55 | 56 | a=Point(0.0, 0.0, 0.0) 57 | b=Point(0.1, 0.1, 0.1) 58 | mesh = BoxMesh(a, b, 3, 3, 3) 59 | 60 | #print(dir(mesh)) 61 | # Mark boundaries 62 | Lwall = AutoSubDomain(lambda x: (x[0]< leftEnd)) 63 | Rwall = AutoSubDomain(lambda x: (x[0]> rightEnd)) 64 | u_sideY = AutoSubDomain(lambda x: (x[1] < 0.001)) 65 | u_sideZ = AutoSubDomain(lambda x: (x[2] < 0.001)) 66 | 67 | boundaries = MeshFunction("size_t", mesh, mesh.geometry().dim() - 1) 68 | 69 | boundaries.set_all(0) 70 | 71 | Lwall.mark(boundaries, 1) 72 | Rwall.mark(boundaries, 2) 73 | u_sideY.mark(boundaries, 3) 74 | u_sideZ.mark(boundaries, 4) 75 | 76 | # Mark domain 77 | domains = MeshFunction("size_t", mesh, mesh.geometry().dim()) 78 | domains.set_all(1) 79 | 80 | return mesh, domains, boundaries, 81 | 82 | class PrescribedDisp(UserExpression): 83 | def __init__(self, solid_vel, **kwargs): 84 | self.solid_vel = solid_vel 85 | self.factor = 0 86 | 87 | super().__init__(**kwargs) 88 | 89 | def update(self, t): 90 | self.factor = t * self.solid_vel 91 | print('displacement = ', self.factor) 92 | 93 | def eval(self, value,x): 94 | value[0] = self.factor 95 | 96 | 97 | def create_bcs(DVP,d_deg,solid_vel, boundaries, **namespace): 98 | # Sliding contact on 3 sides 99 | u_lwallX = DirichletBC(DVP.sub(0).sub(0), ((0.0)), boundaries, 1) 100 | u_CornerY = DirichletBC(DVP.sub(0).sub(1), ((0.0)), boundaries, 3) 101 | u_CornerZ = DirichletBC(DVP.sub(0).sub(2), ((0.0)), boundaries, 4) 102 | 103 | # Displacement on the right hand side (unconstrained in Y and Z) 104 | d_t = PrescribedDisp(solid_vel,degree=d_deg) 105 | u_rwall = DirichletBC(DVP.sub(0).sub(0), d_t, boundaries, 2) 106 | 107 | 108 | bcs = [u_lwallX, u_rwall,u_CornerY,u_CornerZ] 109 | 110 | return dict(bcs=bcs,d_t=d_t) 111 | 112 | 113 | def pre_solve(t, d_t, **namespace): 114 | """Update boundary conditions""" 115 | d_t.update(t) 116 | 117 | 118 | def post_solve(t, dvp_, verbose,counter,save_step, visualization_folder,solid_properties, mesh, dx_s, **namespace): 119 | 120 | if counter % save_step == 0: 121 | 122 | return_dict=StrStr.calculate_stress_strain(t, dvp_, verbose, visualization_folder,solid_properties[0], mesh,dx_s[0], **namespace) 123 | 124 | return return_dict -------------------------------------------------------------------------------- /turtleFSI/problems/Test_Material/UniaxialTensionSVK.py: -------------------------------------------------------------------------------- 1 | # File under GNU GPL (v3) licence, see LICENSE file for details. 2 | # This software is distributed WITHOUT ANY WARRANTY; without even 3 | # the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 4 | # PURPOSE. 5 | 6 | """Problem file for running the "CSM" benchmarks in [1]. The problem is beam under load. 7 | 8 | [1] Turek, Stefan, and Jaroslav Hron. "Proposal for numerical benchmarking of fluid-structure interaction 9 | between an elastic object and laminar incompressible flow." Fluid-structure interaction. 10 | Springer, Berlin, Heidelberg, 2006. 371-385.""" 11 | 12 | from dolfin import * 13 | import numpy as np 14 | from os import path 15 | import stress_strain as StrStr 16 | 17 | from turtleFSI.problems import * 18 | parameters["form_compiler"]["quadrature_degree"] = 6 # Not investigated thorougly. See MSc theses of Gjertsen. 19 | 20 | def set_problem_parameters(default_variables, **namespace): 21 | # Overwrite default values 22 | E_s_val = 1E6 # Young modulus (Pa) 23 | nu_s_val = 0.45 24 | mu_s_val = E_s_val / (2 * (1 + nu_s_val)) # 0.345E6 25 | lambda_s_val = nu_s_val * 2. * mu_s_val / (1. - 2. * nu_s_val) 26 | 27 | 28 | default_variables.update(dict( 29 | # Temporal variables 30 | T=0.3, # End time [s] 31 | dt=0.001, # Time step [s] 32 | checkpoint_step=1000, # Checkpoint frequency 33 | theta=0.5, # Temporal scheme 34 | save_step=1, 35 | 36 | # Physical constants 37 | rho_f=1.0e3, # Fluid density [kg/m3] 38 | mu_f=1.0, # Fluid dynamic viscosity [Pa.s] 39 | 40 | rho_s=1.0E3, # Solid density [kg/m3] 41 | mu_s=mu_s_val, # Solid shear modulus or 2nd Lame Coef. [Pa] 42 | nu_s=nu_s_val, # Solid Poisson ratio [-] 43 | lambda_s=lambda_s_val, # Solid 1st Lame Coef. [Pa] 44 | gravity=None, # Gravitational force [m/s**2] 45 | #solid_properties={"nu_visc_s":1e3}, 46 | solid_properties={"dx_s_id":1,"material_model":"StVenantKirchoff","rho_s":1.0E3,"mu_s":mu_s_val,"lambda_s":lambda_s_val,"nu_visc_s":1e3}, 47 | 48 | # Problem specific 49 | dx_f_id=0, # Id of the fluid domain 50 | dx_s_id=1, # Id of the solid domain 51 | folder="Uniaxial_Tension_StVenantKirchoff", # Folder to store the results 52 | fluid="no_fluid", # Do not solve for the fluid 53 | extrapolation="no_extrapolation", # No displacement to extrapolate 54 | solid_vel=0.1, # this is the velocity of the wall with prescribed displacement 55 | 56 | # Geometric variables 57 | leftEnd=0.001, 58 | rightEnd=0.099)) 59 | 60 | return default_variables 61 | 62 | 63 | def get_mesh_domain_and_boundaries(leftEnd, rightEnd, **namespace): 64 | # Read mesh 65 | 66 | a=Point(0.0, 0.0, 0.0) 67 | b=Point(0.1, 0.1, 0.1) 68 | mesh = BoxMesh(a, b, 3, 3, 3) 69 | 70 | #print(dir(mesh)) 71 | # Mark boundaries 72 | Lwall = AutoSubDomain(lambda x: (x[0]< leftEnd)) 73 | Rwall = AutoSubDomain(lambda x: (x[0]> rightEnd)) 74 | u_sideY = AutoSubDomain(lambda x: (x[1] < 0.001)) 75 | u_sideZ = AutoSubDomain(lambda x: (x[2] < 0.001)) 76 | 77 | boundaries = MeshFunction("size_t", mesh, mesh.geometry().dim() - 1) 78 | 79 | boundaries.set_all(0) 80 | 81 | Lwall.mark(boundaries, 1) 82 | Rwall.mark(boundaries, 2) 83 | u_sideY.mark(boundaries, 3) 84 | u_sideZ.mark(boundaries, 4) 85 | 86 | # Mark domain 87 | domains = MeshFunction("size_t", mesh, mesh.geometry().dim()) 88 | domains.set_all(1) 89 | 90 | return mesh, domains, boundaries, 91 | 92 | class PrescribedDisp(UserExpression): 93 | def __init__(self, solid_vel, **kwargs): 94 | self.solid_vel = solid_vel 95 | self.factor = 0 96 | 97 | super().__init__(**kwargs) 98 | 99 | def update(self, t): 100 | self.factor = t * self.solid_vel 101 | print('displacement = ', self.factor) 102 | 103 | def eval(self, value,x): 104 | value[0] = self.factor 105 | 106 | 107 | 108 | def create_bcs(DVP,d_deg,solid_vel, boundaries, **namespace): 109 | # Sliding contact on 3 sides 110 | u_lwallX = DirichletBC(DVP.sub(0).sub(0), ((0.0)), boundaries, 1) 111 | u_CornerY = DirichletBC(DVP.sub(0).sub(1), ((0.0)), boundaries, 3) 112 | u_CornerZ = DirichletBC(DVP.sub(0).sub(2), ((0.0)), boundaries, 4) 113 | 114 | # Displacement on the right hand side (unconstrained in Y and Z) 115 | d_t = PrescribedDisp(solid_vel,degree=d_deg) 116 | u_rwall = DirichletBC(DVP.sub(0).sub(0), d_t, boundaries, 2) 117 | 118 | 119 | bcs = [u_lwallX, u_rwall,u_CornerY,u_CornerZ] 120 | 121 | return dict(bcs=bcs,d_t=d_t) 122 | 123 | def pre_solve(t, d_t, **namespace): 124 | """Update boundary conditions""" 125 | d_t.update(t) 126 | 127 | def post_solve(t, dvp_, verbose,counter,save_step, visualization_folder,solid_properties, mesh,dx_s, **namespace): 128 | 129 | if counter % save_step == 0: 130 | 131 | return_dict=StrStr.calculate_stress_strain(t, dvp_, verbose, visualization_folder,solid_properties[0], mesh, dx_s[0], **namespace) 132 | 133 | return return_dict -------------------------------------------------------------------------------- /turtleFSI/problems/Test_Material/UniaxialTensionSVKRelaxation.py: -------------------------------------------------------------------------------- 1 | # File under GNU GPL (v3) licence, see LICENSE file for details. 2 | # This software is distributed WITHOUT ANY WARRANTY; without even 3 | # the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 4 | # PURPOSE. 5 | 6 | """Problem file for running the "CSM" benchmarks in [1]. The problem is beam under load. 7 | 8 | [1] Turek, Stefan, and Jaroslav Hron. "Proposal for numerical benchmarking of fluid-structure interaction 9 | between an elastic object and laminar incompressible flow." Fluid-structure interaction. 10 | Springer, Berlin, Heidelberg, 2006. 371-385.""" 11 | 12 | from dolfin import * 13 | import numpy as np 14 | from os import path 15 | import stress_strain as StrStr 16 | 17 | from turtleFSI.problems import * 18 | parameters["form_compiler"]["quadrature_degree"] = 6 # Not investigated thorougly. See MSc theses of Gjertsen. 19 | 20 | def set_problem_parameters(default_variables, **namespace): 21 | # Overwrite default values 22 | E_s_val = 1E6 # Young modulus (Pa) 23 | nu_s_val = 0.45 24 | mu_s_val = E_s_val / (2 * (1 + nu_s_val)) # 0.345E6 25 | lambda_s_val = nu_s_val * 2. * mu_s_val / (1. - 2. * nu_s_val) 26 | 27 | 28 | default_variables.update(dict( 29 | # Temporal variables 30 | T=0.3, # End time [s] 31 | dt=0.001, # Time step [s] 32 | checkpoint_step=1000, # Checkpoint frequency 33 | theta=0.5, # Temporal scheme 34 | save_step=1, 35 | 36 | # Physical constants 37 | rho_f=1.0e3, # Fluid density [kg/m3] 38 | mu_f=1.0, # Fluid dynamic viscosity [Pa.s] 39 | 40 | rho_s=1.0E3, # Solid density [kg/m3] 41 | mu_s=mu_s_val, # Solid shear modulus or 2nd Lame Coef. [Pa] 42 | nu_s=nu_s_val, # Solid Poisson ratio [-] 43 | lambda_s=lambda_s_val, # Solid 1st Lame Coef. [Pa] 44 | gravity=None, # Gravitational force [m/s**2] 45 | #solid_properties={"nu_visc_s":1e3}, 46 | #solid_properties={"dx_s_id":1,"material_model":"StVenantKirchoff","rho_s":1.0E3,"mu_s":mu_s_val,"lambda_s":lambda_s_val,"nu_visc_s":1e5, "delta_visc_s":1e6}, 47 | #solid_properties={"dx_s_id":1,"material_model":"StVenantKirchoff","rho_s":1.0E3,"mu_s":mu_s_val,"lambda_s":lambda_s_val,"nu_visc_s":1e5, "delta_visc_s":1e6}, 48 | #solid_properties={"dx_s_id":1,"material_model":"StVenantKirchoff","rho_s":1.0E3,"mu_s":mu_s_val,"lambda_s":lambda_s_val,"viscoelasticity":"None","nu_visc_s":0.01, "delta_visc_s":0.1}, 49 | solid_properties={"dx_s_id":1,"material_model":"StVenantKirchoff","rho_s":1.0E3,"mu_s":mu_s_val,"lambda_s":lambda_s_val,"viscoelasticity":"Form1","nu_visc_s":1e4, "delta_visc_s":1e4}, 50 | 51 | # Problem specific 52 | dx_f_id=0, # Id of the fluid domain 53 | dx_s_id=1, # Id of the solid domain 54 | folder="Uniaxial_Tension_StVenantKirchoff_Relax_Visc", # Folder to store the results 55 | fluid="no_fluid", # Do not solve for the fluid 56 | extrapolation="no_extrapolation", # No displacement to extrapolate 57 | solid_vel=0.5, # this is the velocity of the wall with prescribed displacement 58 | 59 | # Geometric variables 60 | leftEnd=0.001, 61 | rightEnd=0.099)) 62 | 63 | return default_variables 64 | 65 | 66 | def get_mesh_domain_and_boundaries(leftEnd, rightEnd, **namespace): 67 | # Read mesh 68 | 69 | a=Point(0.0, 0.0, 0.0) 70 | b=Point(0.1, 0.1, 0.1) 71 | mesh = BoxMesh(a, b, 3, 3, 3) 72 | 73 | #print(dir(mesh)) 74 | # Mark boundaries 75 | Lwall = AutoSubDomain(lambda x: (x[0]< leftEnd)) 76 | Rwall = AutoSubDomain(lambda x: (x[0]> rightEnd)) 77 | u_sideY = AutoSubDomain(lambda x: (x[1] < 0.001)) 78 | u_sideZ = AutoSubDomain(lambda x: (x[2] < 0.001)) 79 | 80 | boundaries = MeshFunction("size_t", mesh, mesh.geometry().dim() - 1) 81 | 82 | boundaries.set_all(0) 83 | 84 | Lwall.mark(boundaries, 1) 85 | Rwall.mark(boundaries, 2) 86 | u_sideY.mark(boundaries, 3) 87 | u_sideZ.mark(boundaries, 4) 88 | 89 | # Mark domain 90 | domains = MeshFunction("size_t", mesh, mesh.geometry().dim()) 91 | domains.set_all(1) 92 | 93 | return mesh, domains, boundaries, 94 | 95 | class PrescribedDisp(UserExpression): 96 | def __init__(self, solid_vel, **kwargs): 97 | self.solid_vel = solid_vel 98 | self.factor = 0 99 | 100 | super().__init__(**kwargs) 101 | 102 | def update(self, t): 103 | if t<0.05: 104 | self.factor = t * self.solid_vel 105 | else: 106 | self.factor = 0.05 * self.solid_vel 107 | print('displacement = ', self.factor) 108 | 109 | def eval(self, value,x): 110 | value[0] = self.factor 111 | 112 | 113 | 114 | def create_bcs(DVP,d_deg,solid_vel, boundaries, **namespace): 115 | # Sliding contact on 3 sides 116 | u_lwallX = DirichletBC(DVP.sub(0).sub(0), ((0.0)), boundaries, 1) 117 | u_CornerY = DirichletBC(DVP.sub(0).sub(1), ((0.0)), boundaries, 3) 118 | u_CornerZ = DirichletBC(DVP.sub(0).sub(2), ((0.0)), boundaries, 4) 119 | 120 | # Displacement on the right hand side (unconstrained in Y and Z) 121 | d_t = PrescribedDisp(solid_vel,degree=d_deg) 122 | u_rwall = DirichletBC(DVP.sub(0).sub(0), d_t, boundaries, 2) 123 | 124 | 125 | bcs = [u_lwallX, u_rwall,u_CornerY,u_CornerZ] 126 | 127 | return dict(bcs=bcs,d_t=d_t) 128 | 129 | def pre_solve(t, d_t, **namespace): 130 | """Update boundary conditions""" 131 | d_t.update(t) 132 | 133 | def post_solve(t, dvp_, verbose,counter,save_step, visualization_folder,solid_properties, mesh,dx_s,dt, **namespace): 134 | 135 | if counter % save_step == 0: 136 | 137 | return_dict=StrStr.calculate_stress_strain(t, dvp_, verbose, visualization_folder,solid_properties[0], mesh, dx_s[0],dt, **namespace) 138 | 139 | return return_dict -------------------------------------------------------------------------------- /turtleFSI/problems/Test_Material/UniaxialTensionViscoelastic.py: -------------------------------------------------------------------------------- 1 | # File under GNU GPL (v3) licence, see LICENSE file for details. 2 | # This software is distributed WITHOUT ANY WARRANTY; without even 3 | # the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 4 | # PURPOSE. 5 | 6 | """Problem file for running the "CSM" benchmarks in [1]. The problem is beam under load. 7 | 8 | [1] Turek, Stefan, and Jaroslav Hron. "Proposal for numerical benchmarking of fluid-structure interaction 9 | between an elastic object and laminar incompressible flow." Fluid-structure interaction. 10 | Springer, Berlin, Heidelberg, 2006. 371-385.""" 11 | 12 | from dolfin import * 13 | import numpy as np 14 | from os import path 15 | import stress_strain as StrStr 16 | 17 | from turtleFSI.problems import * 18 | parameters["form_compiler"]["quadrature_degree"] = 6 # Not investigated thorougly. See MSc theses of Gjertsen. 19 | 20 | def set_problem_parameters(default_variables, **namespace): 21 | # Overwrite default values 22 | E_s_val = 1E6 # Young modulus (Pa) 23 | nu_s_val = 0.45 24 | mu_s_val = E_s_val / (2 * (1 + nu_s_val)) # 0.345E6 25 | lambda_s_val = nu_s_val * 2. * mu_s_val / (1. - 2. * nu_s_val) 26 | 27 | 28 | default_variables.update(dict( 29 | # Temporal variables 30 | T=0.3, # End time [s] 31 | dt=0.001, # Time step [s] 32 | checkpoint_step=1000, # Checkpoint frequency 33 | theta=0.50, # Temporal scheme 34 | save_step=1, 35 | 36 | # Physical constants 37 | rho_f=1.0e3, # Fluid density [kg/m3] 38 | mu_f=1.0, # Fluid dynamic viscosity [Pa.s] 39 | 40 | rho_s=1.0E3, # Solid density [kg/m3] 41 | mu_s=mu_s_val, # Solid shear modulus or 2nd Lame Coef. [Pa] 42 | nu_s=nu_s_val, # Solid Poisson ratio [-] 43 | lambda_s=lambda_s_val, # Solid 1st Lame Coef. [Pa] 44 | gravity=None, # Gravitational force [m/s**2] 45 | #solid_properties={"dx_s_id":1,"material_model":"StVenantKirchoff","rho_s":1.0E3,"mu_s":mu_s_val,"lambda_s":lambda_s_val,"viscoelasticity":"Form1","mu_visc_s":0.0, "lambda_visc_s":5e3}, 46 | 47 | # Problem specific 48 | dx_f_id=0, # Id of the fluid domain 49 | dx_s_id=1, # Id of the solid domain 50 | folder="Visc_Strain_Rates", # Folder to store the results 51 | #fluid="no_fluid", # Do not solve for the fluid 52 | #extrapolation="no_extrapolation", # No displacement to extrapolate 53 | solid_vel=0.10, # this is the velocity of the wall with prescribed displacement 54 | cutoff_t=0.2, 55 | # Geometric variables 56 | leftEnd=0.001, 57 | rightEnd=0.099)) 58 | 59 | return default_variables 60 | 61 | 62 | def get_mesh_domain_and_boundaries(leftEnd, rightEnd, **namespace): 63 | # Read mesh 64 | 65 | a=Point(0.0, 0.0, 0.0) 66 | b=Point(0.1, 0.1, 0.1) 67 | mesh = BoxMesh(a, b, 3, 3, 3) 68 | 69 | #print(dir(mesh)) 70 | # Mark boundaries 71 | Lwall = AutoSubDomain(lambda x: (x[0]< leftEnd)) 72 | Rwall = AutoSubDomain(lambda x: (x[0]> rightEnd)) 73 | u_sideY = AutoSubDomain(lambda x: (x[1] < 0.001)) 74 | u_sideZ = AutoSubDomain(lambda x: (x[2] < 0.001)) 75 | 76 | boundaries = MeshFunction("size_t", mesh, mesh.geometry().dim() - 1) 77 | 78 | boundaries.set_all(0) 79 | 80 | Lwall.mark(boundaries, 1) 81 | Rwall.mark(boundaries, 2) 82 | u_sideY.mark(boundaries, 3) 83 | u_sideZ.mark(boundaries, 4) 84 | 85 | # Mark domain 86 | domains = MeshFunction("size_t", mesh, mesh.geometry().dim()) 87 | domains.set_all(1) 88 | 89 | return mesh, domains, boundaries, 90 | 91 | class PrescribedDisp(UserExpression): 92 | def __init__(self, solid_vel,cutoff_t, **kwargs): 93 | self.solid_vel = solid_vel 94 | self.factor = 0 95 | self.cutoff_t = cutoff_t 96 | 97 | super().__init__(**kwargs) 98 | 99 | def update(self, t): 100 | if t<=self.cutoff_t: 101 | self.factor = t * self.solid_vel 102 | else: 103 | self.factor = self.cutoff_t * self.solid_vel 104 | print('displacement = ', self.factor) 105 | 106 | def eval(self, value,x): 107 | value[0] = self.factor 108 | 109 | class PrescribedVel(UserExpression): 110 | def __init__(self, solid_vel,cutoff_t, **kwargs): 111 | self.solid_vel = solid_vel 112 | self.factor = 0 113 | self.cutoff_t = cutoff_t 114 | 115 | super().__init__(**kwargs) 116 | 117 | def update(self, t): 118 | if t<=self.cutoff_t: 119 | self.factor = self.solid_vel 120 | else: 121 | self.factor = 0.0 122 | print('velocity = ', self.factor, " m/s") 123 | 124 | def eval(self, value,x): 125 | value[0] = self.factor 126 | 127 | 128 | def create_bcs(DVP,d_deg,solid_vel,cutoff_t , boundaries, **namespace): 129 | # Sliding contact on 3 sides 130 | u_lwallX = DirichletBC(DVP.sub(0).sub(0), ((0.0)), boundaries, 1) 131 | u_CornerY = DirichletBC(DVP.sub(0).sub(1), ((0.0)), boundaries, 3) 132 | u_CornerZ = DirichletBC(DVP.sub(0).sub(2), ((0.0)), boundaries, 4) 133 | v_lwallX = DirichletBC(DVP.sub(1).sub(0), ((0.0)), boundaries, 1) 134 | v_CornerY = DirichletBC(DVP.sub(1).sub(1), ((0.0)), boundaries, 3) 135 | v_CornerZ = DirichletBC(DVP.sub(1).sub(2), ((0.0)), boundaries, 4) 136 | # Displacement on the right hand side (unconstrained in Y and Z) 137 | d_t = PrescribedDisp(solid_vel,cutoff_t,degree=d_deg) 138 | u_rwall = DirichletBC(DVP.sub(0).sub(0), d_t, boundaries, 2) 139 | v_t = PrescribedVel(solid_vel,cutoff_t,degree=d_deg) 140 | v_rwall = DirichletBC(DVP.sub(1).sub(0), v_t, boundaries, 2) 141 | 142 | bcs = [u_lwallX, u_rwall,u_CornerY,u_CornerZ,v_lwallX,v_rwall,v_CornerY,v_CornerZ] 143 | 144 | return dict(bcs=bcs,d_t=d_t,v_t=v_t) 145 | 146 | def pre_solve(t, d_t,v_t, **namespace): 147 | """Update boundary conditions""" 148 | d_t.update(t) 149 | v_t.update(t) 150 | return dict(d_t=d_t,v_t=v_t) 151 | 152 | def post_solve(t, dvp_, verbose,counter,save_step, visualization_folder,solid_properties, mesh,dx_s,dt, **namespace): 153 | 154 | if counter % save_step == 0: 155 | 156 | return_dict=StrStr.calculate_stress_strain(t, dvp_, verbose, visualization_folder,solid_properties[0], mesh, dx_s[0],dt, **namespace) 157 | 158 | return return_dict# -------------------------------------------------------------------------------- /turtleFSI/problems/Test_Material/stress_strain.py: -------------------------------------------------------------------------------- 1 | from dolfin import * 2 | from turtleFSI.modules import common 3 | import numpy as np 4 | import ufl # ufl module 5 | from os import path 6 | #from turtleFSI.problems import * 7 | 8 | def project_solid(tensorForm, fxnSpace, dx_s): 9 | # 10 | # This function projects a UFL tensor equation (tensorForm) using a tensor function space (fxnSpace) 11 | # on only the solid part of the mesh, given by the differential operator for the solid domain (dx_s) 12 | # 13 | # This is basically the same as the inner workings of the built-in "project()" function, but it 14 | # allows us to calculate on a specific domain rather than the whole mesh 15 | # 16 | v = TestFunction(fxnSpace) 17 | u = TrialFunction(fxnSpace) 18 | a=inner(u,v)*dx_s # bilinear form 19 | L=inner(tensorForm,v)*dx_s # linear form 20 | tensorProjected=Function(fxnSpace) # output tensor-valued function 21 | 22 | # Alternate way that doesnt work on MPI (may be faster on PC) 23 | #quadDeg = 4 # Need to set quadrature degree for integration, otherwise defaults to many points and is very slow 24 | #solve(a==L, tensorProjected,form_compiler_parameters = {"quadrature_degree": quadDeg}) 25 | 26 | ''' 27 | From "Numerical Tours of Continuum Mechanics using FEniCS", the stresses can be computed using a LocalSolver 28 | Since the stress function space is a DG space, element-wise projection is efficient 29 | ''' 30 | solver = LocalSolver(a, L) 31 | solver.factorize() 32 | solver.solve_local_rhs(tensorProjected) 33 | 34 | return tensorProjected 35 | 36 | def calculate_stress_strain(t, dvp_, verbose, visualization_folder, solid_properties, mesh,dx_s,dt, **namespace): 37 | 38 | # Files for storing extra outputs (stresses and strains) 39 | if not "ep_file" in namespace.keys(): 40 | sig_file = XDMFFile(MPI.comm_world, str(visualization_folder.joinpath("TrueStress.xdmf"))) 41 | pk1_file = XDMFFile(MPI.comm_world, str(visualization_folder.joinpath("PK1Stress.xdmf"))) 42 | ep_file = XDMFFile(MPI.comm_world, str(visualization_folder.joinpath("InfinitesimalStrain.xdmf"))) 43 | d_out_file = XDMFFile(MPI.comm_world, str(visualization_folder.joinpath("displacement_out.xdmf"))) 44 | v_out_file = XDMFFile(MPI.comm_world, str(visualization_folder.joinpath("velocity_out.xdmf"))) 45 | for tmp_t in [sig_file,ep_file,pk1_file,d_out_file,v_out_file]: 46 | tmp_t.parameters["flush_output"] = True 47 | tmp_t.parameters["rewrite_function_mesh"] = False 48 | 49 | return_dict = dict(ep_file=ep_file,sig_file=sig_file, pk1_file=pk1_file,d_out_file=d_out_file,v_out_file=v_out_file) 50 | 51 | namespace.update(return_dict) 52 | 53 | else: 54 | return_dict = {} 55 | 56 | # Split function 57 | d = (dvp_["n-1"].sub(0, deepcopy=True) + dvp_["n-2"].sub(0, deepcopy=True))/2 58 | v = (dvp_["n-1"].sub(1, deepcopy=True) + dvp_["n-2"].sub(1, deepcopy=True) )/2# from n-2 to n is one timestep. End velcoity has been verified for single element case 59 | 60 | Ve = VectorElement("CG", mesh.ufl_cell(), 2) 61 | Vect = FunctionSpace(mesh, Ve) 62 | d_ = project(d,Vect) 63 | v_ = project(v,Vect) 64 | d_.rename("Displacement", "d") 65 | v_.rename("Velocity", "v") 66 | 67 | # Write results 68 | #namespace["d_out_file"].write(d_, t) 69 | namespace["v_out_file"].write(v_, t) 70 | 71 | # Create tensor function space for stress and strain (this is necessary to evaluate tensor valued functions) 72 | ''' 73 | Strain/stress are in L2, therefore we use a discontinuous function space with a degree of 1 for P2P1 elements 74 | Could also use a degree = 0 to get a constant-stress representation in each element 75 | For more info see the Fenics Book (P62, or P514-515), or 76 | https://comet-fenics.readthedocs.io/en/latest/demo/viscoelasticity/linear_viscoelasticity.html?highlight=DG#A-mixed-approach 77 | https://fenicsproject.org/qa/10363/what-is-the-most-accurate-way-to-recover-the-stress-tensor/ 78 | https://fenicsproject.discourse.group/t/why-use-dg-space-to-project-stress-strain/3768 79 | ''' 80 | 81 | Te = TensorElement("DG", mesh.ufl_cell(), 1) 82 | Tens = FunctionSpace(mesh, Te) 83 | 84 | 85 | #Ve = VectorElement("CG", mesh.ufl_cell(), 2) 86 | #Vect = FunctionSpace(mesh, Ve) 87 | 88 | # Deformation Gradient and first Piola-Kirchoff stress (PK1) 89 | deformationF = common.F_(d) # calculate deformation gradient from displacement 90 | 91 | # Cauchy (True) Stress and Infinitesimal Strain (Only accurate for small strains, ask DB for True strain calculation...) 92 | epsilon = common.eps(d) # Form for Infinitesimal strain (need polar decomposition if we want to calculate logarithmic/Hencky strain) 93 | ep = project_solid(epsilon,Tens,dx_s) # Calculate stress tensor 94 | #P_ = common.Piola1(d, solid_properties) # Form for second PK stress (using St. Venant Kirchoff Model) 95 | if "viscoelasticity" in solid_properties: 96 | if solid_properties["viscoelasticity"] == "Form1" or solid_properties["viscoelasticity"] == "Form2": 97 | S_ = common.S(d, solid_properties) + common.Svisc_D(v, solid_properties) 98 | P_ = common.F_(d)*S_ 99 | print("using form 1 or 2 for viscoelasticity") 100 | else: 101 | S_ = common.S(d, solid_properties) # Form for second PK stress (using St. Venant Kirchoff Model) 102 | P_ = common.F_(d)*S_ 103 | print("invalid/no entry for viscoelasticity") 104 | else: 105 | S_ = common.S(d, solid_properties) # Form for second PK stress (using St. Venant Kirchoff Model) 106 | P_ = common.F_(d)*S_ 107 | print("invalid/no entry for viscoelasticity") 108 | 109 | sigma = (1/common.J_(d))*deformationF*S_*deformationF.T # Form for Cauchy (true) stress 110 | 111 | sig = project_solid(sigma,Tens,dx_s) # Calculate stress tensor 112 | print("projected True stress tensor") 113 | PK1 = project_solid(P_,Tens,dx_s) # Calculate stress tensor 114 | print("projected PK1 stress tensor") 115 | # Name function 116 | ep.rename("InfinitesimalStrain", "ep") 117 | sig.rename("TrueStress", "sig") 118 | PK1.rename("PK1Stress", "PK1") 119 | 120 | print("Writing Additional Viz Files for Stresses and Strains!") 121 | # Write results 122 | namespace["ep_file"].write(ep, t) 123 | namespace["sig_file"].write(sig, t) 124 | namespace["pk1_file"].write(PK1, t) 125 | 126 | return return_dict -------------------------------------------------------------------------------- /turtleFSI/problems/turtle_demo.py: -------------------------------------------------------------------------------- 1 | # File under GNU GPL (v3) licence, see LICENSE file for details. 2 | # This software is distributed WITHOUT ANY WARRANTY; without even 3 | # the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 4 | # PURPOSE. 5 | 6 | """ 7 | Demo file to illustrate the essential features needed to set up a problem file 8 | using turtleFSI. The following problem simulate the deformation of an elastic 9 | turtle immersed in a pulsative flow. The turtle's head and tail are kept still 10 | while the rest of the body, wings and legs are free to move with the flow. 11 | The inlet flow (left to right) is initially gradually increased to a maximum value 12 | to, then, fluctuates as a sine function with time. 13 | 14 | The fluid flow is approximated by solving the incompressible Navier-Stokes equations. 15 | The elastic deformation of the turtle is solved assuming a nonlinear elastic 16 | Saint Venant-Kirchhoff constitutive model. 17 | 18 | Note: this setup has no aim to reproduce any realistic or physical problem. 19 | """ 20 | 21 | from dolfin import * 22 | import numpy as np 23 | from os import path 24 | from turtleFSI.problems import * 25 | 26 | 27 | def set_problem_parameters(default_variables, **namespace): 28 | # Overwrite default values 29 | E_s_val = 1E6 30 | nu_s_val = 0.45 31 | mu_s_val = E_s_val/(2*(1+nu_s_val)) # 0.345E6 32 | lambda_s_val = nu_s_val*2.*mu_s_val/(1. - 2.*nu_s_val) 33 | 34 | default_variables.update(dict( 35 | T=0.2, # End time [s] (set T to several seconds to simulate several swimming cycles) 36 | dt=0.005, # Time step [s] 37 | theta=0.505, # Theta value (0.5 + dt), shifted Crank-Nicolson scheme 38 | Um=1.0, # Max. velocity inlet [m/s] 39 | rho_f=1.0E3, # Fluid density [kg/m3] 40 | mu_f=1.0, # Fluid dynamic viscosity [Pa.s] 41 | rho_s=1.0E3, # Solid density [kg/m3] 42 | mu_s=5.0E4, # Solid shear modulus or 2nd Lame Coef. [Pa] 43 | lambda_s=4.5E5, # Solid 1st Lame Coef. [Pa] 44 | nu_s=0.45, # Solid Poisson ratio [-] 45 | dx_f_id=1, # ID of marker in the fluid domain 46 | dx_s_id=2, # ID of marker in the solid domain 47 | extrapolation="biharmonic", # Laplace, elastic, biharmonic, no-extrapolation 48 | extrapolation_sub_type="constrained_disp", # ["constant", "small_constant", "volume", "volume_change", "constrained_disp", "constrained_disp_vel"] 49 | recompute=15, # Recompute the Jacobian matrix every "recompute" Newton iterations 50 | checkpoint_step=10, # Store results for restart every 10th timestep 51 | folder="turtle_demo_results"), # Mame of the folder to save the data 52 | save_step=1 # Frequency of data saving 53 | ) 54 | 55 | return default_variables 56 | 57 | 58 | def get_mesh_domain_and_boundaries(args, **namespace): 59 | mesh_folder = path.join(path.dirname(path.abspath(__file__)), "..", "mesh", "turtle_demo") 60 | 61 | # In this example, the mesh and markers are stored in the 3 following files 62 | mesh_path = path.join(mesh_folder, "turtle_mesh.xdmf") # mesh geometry 63 | domains_marker_path = path.join(mesh_folder, "mc.xdmf") # marker over the elements (domains) 64 | boundaries_marker_path = path.join(mesh_folder, "mf.xdmf") # markers of the segments (boundaries) 65 | 66 | # "mesh" collects the mesh geometry of the entire domain (fluid + solid). 67 | # In this example, we import a mesh stored in a .xdmf file, but other formats 68 | # are supported such as .xml files. 69 | mesh = Mesh() 70 | xdmf = XDMFFile(MPI.comm_world, mesh_path) 71 | xdmf.read(mesh) 72 | 73 | # "domains" collects the element markers of the fluid domain (marked as 1) 74 | # and the solid domain (marked as 2). 75 | domains = MeshFunction("size_t", mesh, mesh.geometry().dim()) 76 | xdmf = XDMFFile(MPI.comm_world, domains_marker_path) 77 | xdmf.read(domains) 78 | 79 | # "boundaries" collects the boundary markers that are used to apply the 80 | # Dirichlet boundary conditions on both the fluid and solid domains. 81 | # Marker values ranging from 11 to 15. 82 | mesh_collection = MeshValueCollection("size_t", mesh, mesh.geometry().dim() - 1) 83 | xdmf = XDMFFile(MPI.comm_world, boundaries_marker_path) 84 | xdmf.read(mesh_collection) 85 | boundaries = cpp.mesh.MeshFunctionSizet(mesh, mesh_collection) 86 | 87 | return mesh, domains, boundaries 88 | 89 | 90 | class Inlet(UserExpression): 91 | def __init__(self, Um, **kwargs): 92 | self.t = 0.0 93 | self.t_ramp = 0.5 # time to ramp-up to max inlet velocity (from 0 to Um) 94 | self.Um = Um # Max. velocity inlet [m/s] 95 | super().__init__(**kwargs) 96 | 97 | def update(self, t): 98 | self.t = t 99 | if self.t < self.t_ramp: 100 | self.value = self.Um * np.abs(np.cos(self.t/self.t_ramp*np.pi)-1)/2 # ramp-up the inlet velocity 101 | else: 102 | Um_min = self.Um/6 # lower velocity during oscillations 103 | self.value = (self.Um-Um_min) * np.abs(np.cos(self.t/self.t_ramp*np.pi)-1)/2 + Um_min 104 | 105 | def eval(self, value, x): 106 | value[0] = self.value 107 | value[1] = 0 108 | 109 | def value_shape(self): 110 | return (2,) 111 | 112 | 113 | def create_bcs(DVP, boundaries, Um, v_deg, extrapolation_sub_type, verbose, **namespace): 114 | if MPI.rank(MPI.comm_world) == 0 and verbose: 115 | print("Create bcs") 116 | 117 | inlet = Inlet(Um, degree=v_deg) 118 | noslip = ((0.0, 0.0)) 119 | 120 | # Segments indices (make sure of the consistency with the boundary file) 121 | bottom_id = 11 # segments at the bottom of the model 122 | outlet_id = 12 # segments at the outlet (right wall) of the model 123 | top_id = 13 # segments at the top (right wall) of the model 124 | inlet_id = 14 # segments at the inlet (left wall) of the model 125 | turtle_head_tail_id = 15 # segments along the head and tail of the turtle 126 | 127 | # Fluid velocity boundary conditions 128 | u_inlet = DirichletBC(DVP.sub(1), inlet, boundaries, inlet_id) 129 | u_bot = DirichletBC(DVP.sub(1).sub(1), (0.0), boundaries, bottom_id) 130 | u_top = DirichletBC(DVP.sub(1).sub(1), (0.0), boundaries, top_id) 131 | u_head_tail = DirichletBC(DVP.sub(1), noslip, boundaries, turtle_head_tail_id) 132 | 133 | # Pressure boundary conditions 134 | p_outlet = DirichletBC(DVP.sub(2), (0.0), boundaries, outlet_id) 135 | 136 | bcs = [u_bot, u_top, u_inlet, p_outlet, u_head_tail] 137 | 138 | # Mesh uplifting boundary conditions 139 | d_inlet = DirichletBC(DVP.sub(0), noslip, boundaries, inlet_id) 140 | d_bot = DirichletBC(DVP.sub(0), noslip, boundaries, bottom_id) 141 | d_top = DirichletBC(DVP.sub(0), noslip, boundaries, top_id) 142 | d_outlet = DirichletBC(DVP.sub(0), noslip, boundaries, outlet_id) 143 | d_head_tail = DirichletBC(DVP.sub(0), noslip, boundaries, turtle_head_tail_id) 144 | 145 | for i in [d_bot, d_top, d_outlet, d_inlet, d_head_tail]: 146 | bcs.append(i) 147 | 148 | return dict(bcs=bcs, inlet=inlet) 149 | 150 | 151 | def pre_solve(t, inlet, **namespace): 152 | # Update the time variable used for the inlet boundary condition 153 | inlet.update(t) 154 | -------------------------------------------------------------------------------- /turtleFSI/run_turtle.py: -------------------------------------------------------------------------------- 1 | # File under GNU GPL (v3) licence, see LICENSE file for details. 2 | # This software is distributed WITHOUT ANY WARRANTY; without even 3 | # the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 4 | # PURPOSE. 5 | 6 | """ 7 | Entry point for the setup.py. This small wrapper function makes it possible to run 8 | turtleFSI from any location. Inspired by github.com/mikaem/Oasis 9 | """ 10 | 11 | import sys 12 | import os 13 | 14 | sys.path.append(os.getcwd()) 15 | 16 | 17 | def main(): 18 | from turtleFSI import monolithic 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /turtleFSI/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .argpar import * --------------------------------------------------------------------------------