├── .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 | [](https://turtlefsi2.readthedocs.io/en/latest/?badge=latest)
2 | [](https://github.com/KVSlab/turtleFSI/actions/workflows/test_turtle_conda.yml)
3 | [](https://github.com/KVSlab/turtleFSI/actions/workflows/test_turtle.yml)
4 | [](https://joss.theoj.org/papers/b7febdaa2709205d40b51227091c3b0b)
5 |
6 | # turtleFSI - a Fluid-Structure Interaction Solver
7 |
8 |
9 |
10 |
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 | { width=100% }\
45 | **Figure 1:**
46 | Fluid dynamics benchmark snapshot. Simulation executed with the command:
47 | ```
48 | turtleFSI --problem TF_cfd
49 | ```
50 |
51 | { width=100% }\
52 | **Figure 2:**
53 | Solid mechanics benchmark snapshots. Simulation executed with the command:
54 | ```
55 | turtleFSI --problem TF_csm
56 | ```
57 |
58 | { 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 *
--------------------------------------------------------------------------------