Sfepy Manual
Sfepy Manual
1 Documentation 3
1.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2 Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2.1 Supported Platforms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2.2 Requirements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.2.3 Installing SfePy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.2.4 Using SfePy Docker Images . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.2.5 Installing SfePy from Sources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.2.6 Testing Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.2.7 Debugging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.2.8 Using IPython . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.2.9 Notes on Multi-platform Python Distributions . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.2.10 Notes on Installing SfePy Dependencies on Various Platforms . . . . . . . . . . . . . . . . 9
1.3 Tutorial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.3.1 Basic SfePy Usage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.3.2 Basic Notions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.3.3 Running a Simulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.3.4 Example Problem Description File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.3.5 Interactive Example: Linear Elasticity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.4 Userβs Guide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
1.4.1 Running a Simulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
1.4.2 Visualization of Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
1.4.3 Problem Description File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
1.4.4 Building Equations in SfePy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
1.4.5 Term Evaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
1.4.6 Solution Postprocessing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
1.4.7 Probing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
1.4.8 Postprocessing filters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
1.4.9 Solvers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
1.4.10 Solving Problems in Parallel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
1.4.11 Isogeometric Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
1.5 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
1.5.1 Primer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
1.5.2 Using Salome with SfePy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
1.5.3 Preprocessing: FreeCAD/OpenSCAD + Gmsh . . . . . . . . . . . . . . . . . . . . . . . . . 85
1.5.4 Material Identification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
1.5.5 Mesh parametrization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
1.5.6 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
1.5.7 Example Applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 586
1.6 Useful Code Snippets and FAQ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 586
i
1.6.1 Miscellaneous . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 586
1.6.2 Mesh-Related Tasks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 588
1.6.3 Regions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 589
1.6.4 Material Parameters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 590
1.7 Theoretical Background . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 591
1.7.1 Notes on solving PDEs by the Finite Element Method . . . . . . . . . . . . . . . . . . . . . 591
1.7.2 Implementation of Essential Boundary Conditions . . . . . . . . . . . . . . . . . . . . . . . 593
1.8 Term Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 595
1.8.1 Term Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 595
1.8.2 Term Table . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 596
2 Development 621
2.1 General Information . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 621
2.2 Possible Topics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 621
2.3 Developer Guide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 622
2.3.1 Retrieving the Latest Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 622
2.3.2 SfePy Directory Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 623
2.3.3 Exploring the Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 624
2.3.4 How to Contribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 624
2.3.5 How to Regenerate Documentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 629
2.3.6 How to Implement a New Term . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 630
2.3.7 Multi-linear Terms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 636
2.3.8 How To Make a Release . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 638
2.3.9 Module Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 640
Bibliography 1033
Index 1039
ii
SfePy Documentation, Release version: 2022.1+git.f9873484
β’ genindex
β’ modindex
β’ search
CONTENTS 1
SfePy Documentation, Release version: 2022.1+git.f9873484
2 CONTENTS
CHAPTER
ONE
DOCUMENTATION
1.1 Introduction
SfePy (http://sfepy.org) is a software for solving systems of coupled partial differential equations (PDEs) by the finite
element method in 1D, 2D and 3D. It can be viewed both as black-box PDE solver, and as a Python package which can
be used for building custom applications. The word βsimpleβ means that complex FEM problems can be coded very
easily and rapidly.
There is also a preliminary support for the isogeometric analysis, outlined in Isogeometric Analysis.
The code is written almost entirely in Python, with exception of the most time demanding routines - those are written
in C and wrapped by Cython or written directly in Cython.
SfePy is a free software released under the New BSD License. It relies on NumPy and SciPy (an excellent collection
of tools for scientific computations in Python). It is a multi-platform software that should work on Linux, Mac OS X
and Windows.
SfePy was originally developed as a flexible framework to quickly implement and test the mathematical models devel-
oped during our various research projects. It has evolved, however, to a rather full-featured (yet small) finite element
code. Many terms have been implemented that can be used to build the PDEs, see Term Overview. SfePy comes also
with a number of examples that can get you started, check Examples, Gallery and Tutorial. Some more advanced
features are discussed in Primer.
1.2 Installation
SfePy is known to work on various flavors of recent Linux, Intel-based MacOS and Windows. SfePy requires Python
3. The release 2019.4 was the last with Python 2.7 support.
Note: Depending on Python installation and OS used, replacing python by python3 might be required in all the
commands below (e.g. in Compilation of C Extension Modules) in order to use Python 3.
3
SfePy Documentation, Release version: 2022.1+git.f9873484
1.2.2 Requirements
4 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
β’ MUMPS library for using MUMPS linear direct solver (real and complex arithmetic, parallel factorization)
SfePy should work with any recent Python 3.x (in long-term view Python 3.6+ is recommended). It is only matter of
taste to use either native OS Python installation or any other suitable distribution. We could recommend the following
distributions to use:
β’ Linux: OS native installation (See Notes on Installing SfePy Dependencies on Various Platforms for further
details.)
β’ macOS: multi-platform scientific Python distributions Anaconda (See Notes on Multi-platform Python Distri-
butions for further details.)
β’ Windows: use free versions of commercial multi-platform scientific Python distributions Anaconda or Enthought
Canopy (see Notes on Multi-platform Python Distributions for further details). In addition a completely free
open-source portable distribution WinPython can be used.
On any supported platform we could recommend Anaconda distribution as easy-to-use, stable and up-to-date Python
distribution with all the required dependencies (including pre-built sfepy package).
Note: all SfePy releases are regularly tested on recent Linux distributions (Debian and (K)Ubuntu) using OS Python
installation and Anaconda, macOS 10.12+ using Anaconda and Windows 8.1+ using Anaconda.
For Anaconda and .deb based Linux distributions (Debian, (K)Ubuntu), pre-built SfePy packages are available. You
may directly install them with:
β’ Anaconda distribution: install sfepy from conda-forge channel:
There are no further steps required to install/configure SfePy (see Notes on Multi-platform Python Distributions for
additional notes).
Besides the classical installation we also provide experimental Docker images with ready-to-run Anaconda and Sfepy
installation.
Before you start using SfePy images, you need to first install and configure Docker on your computer. To do this follow
official Docker documentation.
Currently available images are:
β’ sfepy/sfepy-notebook - basic command line interface and web browser access to Jupyter notebook/JupyterLab
interface,
β’ sfepy/sfepy-x11vnc-desktop - optimized Ubuntu desktop environment accessible via standard web browser or
VNC client.
1.2. Installation 5
SfePy Documentation, Release version: 2022.1+git.f9873484
For available runtime options and further information see sfepy-docker project on Github.
As a convenience, use the following Docker compose file, which will start the SfePy image, run Jupyter Lab, and map
the contents of the local directory to the SfePy home directory within the image. Just create an empty folder and add
the following to a file named docker-compose.yml. Then, run docker-compose up in the same directory.
The latest stable release can be obtained from the download page. Otherwise, download the development version of
the code from SfePy git repository:
In case you wish to use a specific release instead of the latest master version, use:
git tag -l
to see the available releases - the release tags have form release_<year>.<int>.
See the download page for additional download options.
β’ for installation:
Installation
SfePy can be used without any installation by running its main scripts and examples from the top-level directory of the
distribution or can be installed locally or system-wide:
β’ system-wide (may require root privileges):
6 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
After building and/or installing SfePy you should check if all the functions are working properly by running the auto-
mated tests.
in the SfePy top-level directory in case of the in-place build and anywhere else when testing the installed package.
The testing function is based on pytest. Additional pytest options can be passed as arguments to sfepy.test(), for
example:
1.2.7 Debugging
If something goes wrong, edit the site_cfg.py config file and set debug_flags = '-DDEBUG_FMF' to turn on
bound checks in the low level C functions, and recompile the code:
Then re-run your code and report the output to the SfePy mailing list.
We generally recommend to use (a customized) IPython interactive shell over the regular Python interpreter when
following Tutorial or Primer (or even for any regular interactive work with SfePy).
Install IPython (as a generic part of your selected distribution) and then customize it to your choice.
Depending on your IPython usage, you can customize your default profile or create a SfePy specific new one as follows:
1. Create a new SfePy profile:
2. Open the ~/.ipython/profile_sfepy/ipython_config.py file in a text editor and add/edit after the c =
get_config() line:
exec_lines = [
'import numpy as nm',
'import matplotlib as mpl',
'mpl.use("WXAgg")',
#
# Add your preferred SfePy customization here...
(continues on next page)
1.2. Installation 7
SfePy Documentation, Release version: 2022.1+git.f9873484
c.InteractiveShellApp.exec_lines = exec_lines
c.TerminalIPythonApp.gui = 'wx'
c.TerminalInteractiveShell.colors = 'Linux' # NoColor, Linux, or LightBG
Please note, that generally it is not recommended to use star (*) imports here.
3. Run the customized IPython shell:
ipython --profile=sfepy
Anaconda
Once the conda-forge channel has been enabled, SfePy can be installed with:
It is possible to list all of the versions of SfePy available on your platform with:
2. installing the SfePy dependencies only - then proceed with the Installing SfePy from Sources instructions.
In this case, install the missing/required packages using built-in conda package manager:
8 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
or try:
To build SfePy extension modules, included mingw-w32/64 compiler tools should work fine. If you encounter any
problems, we recommend to install and use Microsoft Visual C++ Build Tools instead (see Anaconda FAQ).
The following information has been provided by users of the listed platforms and may become obsolete over time. The
generic installation instructions above should work in any case, provided the required dependencies are installed.
Gentoo
Archlinux
Instructions
Edit Makefile and change all references from python to python2. Edit scripts and change shebangs to point to python2.
Debian
1.2. Installation 9
SfePy Documentation, Release version: 2022.1+git.f9873484
(K)Ubuntu
Λβpython-sparse
1.3 Tutorial
This section introduces the basics of running SfePy from the command line.
The script simple.py is the most basic starting point in SfePy. It can be invoked in many (similar) ways which de-
pends on used OS, Python distribution and SfePy build method (see Installing SfePy for further info). All (working)
alternatives described below are interchangeable, so donβt panic and feel free to pick your preferred choice (see Basic
Usage for further explanation and more usage examples).
Depending on selected build method and OS used we recommend for:
β’ In-place build
Use the top-level directory of SfePy source tree as your working directory and use:
./simple.py <problem_description_file>
10 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
<path/to/installed/simple.py> <problem_description_file>
You can also use the simple SfePy command-wrapper (ensure that SfePy installation executable directory is
included in your PATH):
Please note, that improper mixing of in-place and install builds on single command line may result in strange runtime
errors.
All functions of SfePy package can be also used interactively (see Interactive Example: Linear Elasticity for instance).
We recommend to use the IPython interactive shell for the best fluent user experience. You can customize your IPython
startup profile as described in Using IPython.
The simplest way of using SfePy is to solve a system of PDEs defined in a problem description file, also referred to
as input file. In such a file, the problem is described using several keywords that allow one to define the equations,
variables, finite element approximations, solvers and solution domain and subdomains (see Problem Description File
for a full list of those keywords).
The syntax of the problem description file is very simple yet powerful, as the file itself is just a regular Python module
that can be normally imported β no special parsing is necessary. The keywords mentioned above are regular Python
variables (usually of the dict type) with special names.
Below we show:
β’ how to solve a problem given by a problem description file, and
β’ explain the elements of the file on several examples.
But let us begin with a slight detour. . .
1.3. Tutorial 11
SfePy Documentation, Release version: 2022.1+git.f9873484
end of the tutorial an example demonstrating the interactive creation of the Problem instance is shown, see Interactive
Example: Linear Elasticity.
Now let us continue with running a simulation.
The following commands should be run in the top-level directory of the SfePy source tree after compiling the C extension
files. See Installation for full installation instructions.
β’ Download sfepy/examples/diffusion/poisson_short_syntax.py. It represents our sample SfePy Prob-
lem Description File, which defines the problem to be solved in terms SfePy can understand.
β’ Use the downloaded file in place of <problem_description_file.py> and run simple.py as described above. The
successful execution of the command creates output file cylinder.vtk in the SfePy top-level directory.
β’ The postproc.py script can be used for quick postprocessing and visualization of the SfePy output files. It requires
mayavi installed on your system.
β’ As a simple example, try:
./postproc.py cylinder.vtk
12 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
β holding shift and the left mouse button pans the view,
β holding control and the left mouse button rotates about the screen normal axis,
β the right mouse button controls the zoom.
where Ξ β β¦ is a subset of the domain β¦ boundary. For simplicity, we set π β‘ 0, but we still work with the material
constant π even though it has no influence on the solution in this case. We also assume zero fluxes over πβ¦ β Ξ, i.e.
ππ = 0 there. The particular boundary conditions used below are π = 2 on the left side of the cylindrical domain
ππ
Comparing the above integral term with the long table in Term Overview, we can see that SfePy contains this term
under name dw_laplace. We are now ready to proceed to the actual problem definition.
Open the sfepy/examples/diffusion/poisson_short_syntax.py file in your favorite text editor. Note that the
file is a regular Python source code.
The filename_mesh variable points to the file containing the mesh for the particular problem. SfePy supports a variety
of mesh formats.
materials = {
'coef': ({'val' : 1.0},),
}
Here we define just a constant coefficient π of the Poisson equation, using the βvaluesβ attribute. Other possible attribute
is βfunctionβ for material coefficients computed/obtained at runtime.
Many finite element problems require the definition of material parameters. These can be handled in SfePy with material
variables which associate the material parameters with the corresponding region of the mesh.
regions = {
'Omega' : 'all', # or 'cells of group 6'
'Gamma_Left' : ('vertices in (x < 0.00001)', 'facet'),
'Gamma_Right' : ('vertices in (x > 0.099999)', 'facet'),
}
Regions assign names to various parts of the finite element mesh. The region names can later be referred to, for
example when specifying portions of the mesh to apply boundary conditions to. Regions can be specified in a variety
of ways, including by element or by node. Here, βOmegaβ is the elemental domain over which the PDE is solved and
βGamma_Leftβ and βGamma_Rightβ define surfaces upon which the boundary conditions will be applied.
1.3. Tutorial 13
SfePy Documentation, Release version: 2022.1+git.f9873484
fields = {
'temperature': ('real', 1, 'Omega', 1)
}
A field is used mainly to define the approximation on a (sub)domain, i.e. to define the discrete spaces πβ , where we
seek the solution.
The Poisson equation can be used to compute e.g. a temperature distribution, so let us call our field βtemperatureβ. On
the region βOmegaβ it will be approximated using linear finite elements.
A field in a given region defines the finite element approximation. Several variables can use the same field, see below.
variables = {
't': ('unknown field', 'temperature', 0),
's': ('test field', 'temperature', 't'),
}
One field can be used to generate discrete degrees of freedom (DOFs) of several variables. Here the unknown variable
(the temperature) is called βtβ, itβs associated DOF name is βt.0β β this will be referred to in the Dirichlet boundary
section (ebc). The corresponding test variable of the weak formulation is called βsβ. Notice that the βdualβ item of a
test variable must specify the unknown it corresponds to.
For each unknown (or state) variable there has to be a test (or virtual) variable defined, as usual in weak formulation of
PDEs.
ebcs = {
't1': ('Gamma_Left', {'t.0' : 2.0}),
't2', ('Gamma_Right', {'t.0' : -2.0}),
}
Integrals specify which numerical scheme to use. Here we are using a 2nd order quadrature over a 3 dimensional space.
equations = {
'Temperature' : """dw_laplace.i.Omega( coef.val, s, t ) = 0"""
}
The equation above directly corresponds to the discrete version of (1.2), namely: Find π‘ β πβ , such that
β«οΈ
π
π ( π πΊπ πΊ)π‘ = 0, βπ β πβ0 ,
Ξ©β
where βπ’ β πΊπ’.
The equations block is the heart of the SfePy problem description file. Here, we are specifying that the Laplacian of the
temperature (in the weak formulation) is 0, where coef.val is a material constant. We are using the βiβ integral defined
previously, over the domain specified by the region βOmegaβ.
The above syntax is useful for defining custom integrals with user-defined quadrature points and weights, see Integrals.
The above uniform integration can be more easily achieved by:
14 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
equations = {
'Temperature' : """dw_laplace.2.Omega( coef.val, s, t ) = 0"""
}
The integration order is specified directly in place of the integral name. The integral definition is superfluous in this
case.
solvers = {
'ls' : ('ls.scipy_direct', {}),
'newton' : ('nls.newton',
{'i_max' : 1,
'eps_a' : 1e-10,
}),
}
Here, we specify the linear and nonlinear solver kinds and options. See sfepy.solvers.ls, sfepy.solvers.nls
and sfepy.solvers.ts_solvers for available solvers and their parameters.. Even linear problems are solved by a
nonlinear solver (KISS rule) β only one iteration is needed and the final residual is obtained for free. Note that we do
not need to define a time-stepping solver here - the problem is stationary and the default 'ts.stationary' solver is
created automatically.
options = {
'nls' : 'newton',
'ls' : 'ls',
}
The solvers to use are specified in the options block. We can define multiple solvers with different convergence param-
eters.
Thatβs it! Now it is possible to proceed as described in Invoking SfePy from the Command Line.
This example shows how to use SfePy interactively, but also how to make a custom simulation script. We will use
IPython interactive shell which allows more flexible and intuitive work (but you can use standard Python shell as well).
We wish to solve the following linear elasticity problem:
ππππ (π’)
β + ππ = 0 in β¦, π’ = 0 on Ξ1 , Β―1 on Ξ2 ,
π’1 = π’ (1.3)
ππ₯π
ππ’
where the stress is defined as πππ = 2ππππ +ππππ πΏππ , π, π are the LamΓ©βs constants, the strain is πππ (π’) = 21 ( ππ₯
ππ’π
π
+ ππ₯ππ )
and π are volume forces. This can be written in general form as πππ (π’) = π·ππππ πππ (π’), where in our case π·ππππ =
π(πΏππ πΏππ + πΏππ πΏππ ) + π πΏππ πΏππ .
In the weak form the equation (1.3) is
β«οΈ β«οΈ
π·ππππ πππ (π’)πππ (π£) + π π π£π = 0 , (1.4)
Ξ© Ξ©
where π£ is the test function, and both π’, π£ belong to a suitable function space.
Hint: Whenever you create a new object (e.g. a Mesh instance, see below), try to print it using the print statement β it
will give you insight about the object internals.
The whole example summarized in a script is available below in Complete Example as a Script.
In the SfePy top-level directory run
1.3. Tutorial 15
SfePy Documentation, Release version: 2022.1+git.f9873484
ipython
Define the regions β the whole domain β¦, where the solution is sought, and Ξ1 , Ξ2 , where the boundary conditions will
be applied. As the domain is rectangular, we first get a bounding box to get correct bounds for selecting the boundary
edges.
Next we define the actual finite element approximation using the Field class.
Using the field fu, we can define both the unknown variable π’ and the test variable π£.
Before we can define the terms to build the equation of linear elasticity, we have to create also the materials, i.e. define
the (constitutive) parameters. The linear elastic material m will be defined using the two LamΓ© constants π = 1, π = 1.
The volume forces will be defined also as a material as a constant (column) vector [0.02, 0.01]π .
One more thing needs to be defined β the numerical quadrature that will be used to integrate each term over its domain.
Now we are ready to define the two terms and build the equations.
16 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
The equations have to be completed by boundary conditions. Let us clamp the left edge Ξ1 , and shift the right edge Ξ2
in the π₯ direction a bit, depending on the π¦ coordinate.
The last thing to define before building the problem are the solvers. Here we just use a sparse direct SciPy solver and
the SfePy Newton solver with default parameters. We also wish to store the convergence statistics of the Newton solver.
As the problem is linear it should converge in one iteration.
In [31]: ls = ScipyDirect({})
In [32]: nls_status = IndexedStruct()
In [33]: nls = Newton({}, lin_solver=ls, status=nls_status)
The Problem has several handy methods for debugging. Let us try saving the regions into a VTK file.
In [35]: pb.save_regions_as_groups('regions')
1.3. Tutorial 17
SfePy Documentation, Release version: 2022.1+git.f9873484
Finally, we set the boundary conditions and the top-level solver , solve the problem, save and view the results. For
stationary problems, the top-level solver needs not to be a time-stepping solver - when a nonlinear solver is set instead,
the default 'ts.stationary' time-stepping solver is created automatically.
18 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
The default view is not very fancy. Let us show the displacements by shifting the mesh. Close the previous window
and do:
1.3. Tutorial 19
SfePy Documentation, Release version: 2022.1+git.f9873484
5 import sys
6 sys.path.append('.')
7
19
26 return val
27
28 helps = {
29 'show' : 'show the results figure',
30 }
31
32 def main():
33 from sfepy import data_dir
34
35 parser = ArgumentParser()
36 parser.add_argument('--version', action='version', version='%(prog)s')
37 parser.add_argument('-s', '--show',
38 action="store_true", dest='show',
39 default=False, help=helps['show'])
40 options = parser.parse_args()
41
20 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
66 t1 = Term.new('dw_lin_elastic(m.D, v, u)',
67 integral, omega, m=m, v=v, u=u)
68 t2 = Term.new('dw_volume_lvf(f.val, v)', integral, omega, f=f, v=v)
69 eq = Equation('balance', t1 + t2)
70 eqs = Equations([eq])
71
78 ls = ScipyDirect({})
79
80 nls_status = IndexedStruct()
81 nls = Newton({}, lin_solver=ls, status=nls_status)
82
83 pb = Problem('elasticity', equations=eqs)
84 pb.save_regions_as_groups('regions')
85
86 pb.set_bcs(ebcs=Conditions([fix_u, shift_u]))
87
88 pb.set_solver(nls)
89
90 status = IndexedStruct()
91 variables = pb.solve(status=status)
92
96 pb.save_state('linear_elasticity.vtk', variables)
97
98 if options.show:
99 view = Viewer('linear_elasticity.vtk')
(continues on next page)
1.3. Tutorial 21
SfePy Documentation, Release version: 2022.1+git.f9873484
The following should be run in the top-level directory of the SfePy source tree after compiling the C extension files.
See Installation for full installation instructions info. The $ indicates the command prompt of your terminal.
Basic Usage
β’ $ ./simple.py sfepy/examples/diffusion/poisson_short_syntax.py
β Creates cylinder.vtk
β’ $ ./simple.py sfepy/examples/navier_stokes/stokes.py
β Creates channels_symm944t.vtk
Applications
β’ Phononic Materials
β $ ./phonon.py -p sfepy/examples/phononic/band_gaps.py
β see sfepy/examples/phononic/output/
All top-level SfePy scripts (applications) can be run via single sfepy-run wrapper:
$ ./sfepy-run
usage: sfepy-run [command] [options]
positional arguments:
{extractor,phonon,postproc,probe,simple}
Available SfePy command(s).
options Additional options passed directly to selected
[command].
(continues on next page)
22 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
optional arguments:
-h, --help show this help message and exit
-v, --version show program's version number and exit
-w, --window use alternative (pythonw) interpreter
Notes
β’ This is a βnewβ supported method. Any SfePy script can be still run as stand-alone (as mentioned above).
β’ Both βinplaceβ and βsystem-wideβ installations are supported.
Running Tests
The example problems in the examples directory can be computed by the script simple.py which is in the top-level
directory of the SfePy distribution. If it is run without arguments, a help message is printed:
$ ./simple.py
Usage: simple.py [options] filename_in
Both normal and parametric study runs are supported. A parametric study
allows repeated runs for varying some of the simulation parameters - see
``sfepy/examples/diffusion/poisson_parametric_study.py`` file.
Options:
--version show program's version number and exit
-h, --help show this help message and exit
-c "key : value, ...", --conf="key : value, ..."
override problem description file items, written as
python dictionary without surrounding braces
-O "key : value, ...", --options="key : value, ..."
override options item of problem description, written
as python dictionary without surrounding braces
-d "key : value, ...", --define="key : value, ..."
pass given arguments written as python dictionary
without surrounding braces to define() function of
problem description file
(continues on next page)
$ python sfepy/examples/large_deformation/compare_elastic_materials.py
$ ./simple.py sfepy/examples/diffusion/poisson_parametric_study.py
Common Tasks
β’ Run a simulation:
./simple.py sfepy/examples/diffusion/poisson_short_syntax.py
./simple.py sfepy/examples/diffusion/poisson_short_syntax.py -o some_results # ->β£
Λβproduces some_results.vtk
./simple.py --list=terms
24 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
β Run:
and break the computation after a while (hit Ctrl-C). The mode --save-restart=-1 is currently the only
supported mode. It saves a restart file for each time step, and only the last computed time step restart file is
kept.
β A file named 'unit_ball.restart-??.h5' should be created, where '??' indicates the last stored time
step. Let us assume it is 'unit_ball.restart-04.h5', i.e. the fifth step.
β Restart the simulation by:
The simulation should continue from the next time step. Verify that by running:
./simple.py sfepy/examples/large_deformation/balloon.py
resview.py β PyVista
Quick visualisation of the SfePy results can be done by resview.py script, which uses PyVista visualisation toolkit
(need to be installed).
The help message of the script is:
Examples
--------
The examples assume that
``python -c "import sfepy; sfepy.test('--output-dir=output-tests')"``
has been run successfully and the resulting data files are present.
positional arguments:
filenames
optional arguments:
-h, --help show this help message and exit
-f field_spec [field_spec ...], --fields field_spec [field_spec ...]
fields to plot, options separated by ":" are possible:
"cX" - plot only Xth field component; "e" - print
edges; "fX" - scale factor for warp/glyphs, see
--factor option; "g - glyphs (for vector fields only),
scale by factor; "tX" - plot X streamlines, gradient
employed for scalar fields; "mX" - plot cells with
mat_id=X; "oX" - set opacity to X; "pX" - plot in slot
X; "r" - recalculate cell data to point data; "sX" -
plot data in step X; "vX" - plotting style: s=surface,
w=wireframe, p=points; "wX" - warp mesh by vector
field X, scale by factor
--fields-map map [map ...]
map fields and cell groups, e.g. 1:u1,p1 2:u2,p2
-s step, --step step select data in a given time step
(continues on next page)
26 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
./resview.py output-tests/test_navier_stokes.vtk
produces:
the output is split into plots plot:0 and plot:1, where these plots contain:
β’ plot:0: field p, mesh edges are switched on
β’ plot:1: magnitude of vector field u displayed as the surface with opacity set to 0.4; glyphs related to field u and
scaled by factor 2e-2
28 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
postproc.py β Mayavi2
The postproc.py script can be used for quick postprocessing and visualization of the SfePy results. It requires mayavi2
installed on your system.
The help message of the script is:
usage: postproc.py [-h] [--version] [--debug] [-o filename]
[--output-dir directory] [-n] [--no-offscreen]
[-a <ffmpeg-supported format>]
[--ffmpeg-options <ffmpeg options>] [--step step]
[--time time] [-w] [--all] [--only-names list of names]
[-l] [--ranges name1,min1,max1:name2,min2,max2:...]
[-r resolution] [--layout layout] [--3d]
[--view angle,angle[,distance[,focal_point]]]
[--roll angle] [--parallel-projection] [--fgcolor R,G,B]
[--bgcolor R,G,B] [--colormap colormap]
[--anti-aliasing value] [-b] [--wireframe]
[--group-names name1,...,nameN:...]
[--subdomains mat_id_name,threshold_limits,single_color]
[-d "var_name0,function_name0,par0=val0,par1=val1,...:var_name1,..."]
[--scalar-mode mode] [--vector-mode mode] [-s scale]
[--clamping] [--opacity opacity] [--rel-text-width width]
filenames [filenames ...]
Examples
--------
The examples assume that
``python -c "import sfepy; sfepy.test('--output-dir=output-tests')"``
has been run successfully and the resulting data files are present.
30 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
positional arguments:
filenames
optional arguments:
-h, --help show this help message and exit
--version show program's version number and exit
--debug automatically start debugger when an exception is
raised
Output Options:
-o filename, --output filename
view image file name [default: "view.png"]
--output-dir directory
output directory for saving view images; ignored when
-o option is given, as the directory part of the
filename is taken instead [default: "."]
-n, --no-show do not call mlab.show()
--no-offscreen force no offscreen rendering for --no-show
-a <ffmpeg-supported format>, --animation <ffmpeg-supported format>
if set to a ffmpeg-supported format (e.g. mov, avi,
mpg), ffmpeg is installed and results of multiple time
steps are given, an animation is created in the same
directory as the view images
--ffmpeg-options <ffmpeg options>
ffmpeg animation encoding options (enclose in
"")[default: "-framerate 10"]
Data Options:
--step step set the time step. Negative indices are allowed, -1
means the last step. The closest higher step is used
if the desired one is not available. Has precedence
over --time. [default: the first step]
--time time set the time. The closest higher time is used if the
desired one is not available. [default: None]
-w, --watch watch the results file for changes (single file mode
only)
--all draw all data (normally, node_groups and mat_id are
omitted)
--only-names list of names
draw only named data
(continues on next page)
View Options:
-r resolution, --resolution resolution
image resolution in NxN format [default: shorter axis:
600; depends on layout: for rowcol it is 800x600]
--layout layout layout for multi-field plots, one of: rowcol, colrow,
row, col, row#n,col#n, where #n is the number of plots
in the specified direction [default: rowcol]
--3d 3d plot mode
--view angle,angle[,distance[,focal_point]]
camera azimuth, elevation angles, and optionally also
distance and focal point coordinates (without []) as
in `mlab.view()` [default: if --3d is True: "45,45",
else: "0,0"]
--roll angle camera roll angle [default: 0.0]
--parallel-projection
use parallel projection
--fgcolor R,G,B foreground color, that is the color of all text
annotation labels (axes, orientation axes, scalar bar
labels) [default: 0.0,0.0,0.0]
--bgcolor R,G,B background color [default: 1.0,1.0,1.0]
--colormap colormap mayavi2 colormap name [default: blue-red]
--anti-aliasing value
value of anti-aliasing [default: mayavi2 default]
Mayavi Options:
--scalar-mode mode mode for plotting scalars with --3d, one of:
cut_plane, iso_surface, both [default: iso_surface]
--vector-mode mode mode for plotting vectors, one of: arrows, norm,
arrows_norm, warp_norm [default: arrows_norm]
-s scale, --scale-glyphs scale
relative scaling of glyphs (vector field
visualization) [default: 0.05]
--clamping glyph clamping mode
--opacity opacity opacity in [0.0, 1.0]. Can be given either globally as
a single float, or per module, e.g.
"wireframe=0.1,scalar_cut_plane=0.5". Possible
(continues on next page)
32 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
Here we discuss the basic items that users have to specify in their input files. For complete examples, see the problem
description files in the sfepy/examples/ directory of SfePy.
Long Syntax
Besides the short syntax described below there is (due to history) also a long syntax which is explained in prob-
lem_desc_file_long. The short and long syntax can be mixed together in one description file.
FE Mesh
filename_mesh = 'meshes/3d/cylinder.vtk'
The VTK and HDF5 formats can be used for storing the results. The format can be selected in options, see Miscella-
neous.
The following geometry elements are supported:
34 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
Note the orientation of the vertices matters, the figure displays the correct orientation when interpreted in a right-handed
coordinate system.
Regions
Regions serve to select a certain part of the computational domain using topological entities of the FE mesh. They are
used to define the boundary conditions, the domains of terms and materials etc.
Let us denote D the maximal dimension of topological entities. For volume meshes it is also the dimension of space
the domain is embedded in. Then the following topological entities can be defined on the mesh (notation follows
[Logg2012]):
If D = 2, faces are not defined and facets are edges. If D = 3, facets are faces.
Following the above definitions, a region can be of different kind:
β’ cell, facet, face, edge, vertex - entities of higher dimension are not included.
β’ cell_only, facet_only, face_only, edge_only, vertex_only - only the specified entities are included,
other entities are empty sets, so that set-like operators still work, see below.
β’ The cell kind is the most general and should be used with volume terms. It is also the default if the kind is not
specified in region definition.
β’ The facet kind (same as edge in 2D and face in 3D) is to be used with boundary (surface integral) terms.
β’ The vertex (same as vertex_only) kind can be used with point-wise defined terms (e.g. point loads).
The kinds allow a clear distinction between regions of different purpose (volume integration domains, surface domains,
etc.) and could be uses to lower memory usage.
A region definition involves topological entity selections combined with set-like operators. The set-like operators can
result in intermediate regions that have the cell kind. The desired kind is set to the final region, removing unneeded
entities. Most entity selectors are defined in terms of vertices and cells - the other entities are computed as needed.
2 Only if mesh format supports reading boundary condition vertices as vertex sets.
3 <expr> is a logical expression like (y <= 0.1) & (x < 0.2). In 2D use x, y, in 3D use x, y and z. & stands for logical and, | stands for
logical or.
4 <function> is a function with signature fun(coors, domain=None), where coors are coordinates of mesh vertices.
5 <efunction> is a function with signature fun(coors, domain=None), where coors are coordinates of mesh cell centroids.
36 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
regions = {
<name> : (<selection>, [<kind>], [<parent>], [{<misc. options>}]),
}
or:
regions = {
<name> : <selection>,
}
Example definitions:
regions = {
'Omega' : 'all',
'Right' : ('vertices in (x > 0.99)', 'facet'),
'Gamma1' : ("""(cells of group 1 *v cells of group 2)
+v r.Right""", 'facet', 'Omega'),
'Omega_B' : 'vertices by get_ball',
}
The Omega_B region illustrates the selection by a function (see Topological Entity Selection). In this example, the
function is:
import numpy as nm
return flag
functions = {
'get_ball' : (get_ball,),
}
regions = {
'Top': ('r.Y *v r.Surf1', 'facet', 'Y', {'mirror_region': 'Bottom'}),
'Bottom': ('r.Y *v r.Surf2', 'facet', 'Y', {'mirror_region': 'Top'}),
}
Fields
fields = {
<name> : (<data_type>, <shape>, <region_name>, <approx_order>)
}
where
β’ <data_type> is a numpy type (float64 or complex128) or βrealβ or βcomplexβ
β’ <shape> is the number of DOFs per node: 1 or (1,) or βscalarβ, space dimension (2, or (2,) or 3 or (3,)) or
βvectorβ; it can be other positive integer than just 1, 2, or 3
β’ <region_name> is the name of region where the field is defined
β’ <approx_order> is the FE approximation order, e.g. 0, 1, 2, β1Bβ (1 with bubble)
Example: scalar P1 elements in 2D on a region Omega:
fields = {
'temperature' : ('real', 1, 'Omega', 1),
}
38 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
Variables
variables = {
<name> : (<kind>, <field_name>, <spec>, [<history>])
}
where
β’ <kind> - βunknown fieldβ, βtest fieldβ or βparameter fieldβ
β’ <spec> - in case of: primary variable - order in the global vector of unknowns, dual variable - name of
primary variable
β’ <history> - number of time steps to remember prior to current step
Example:
variables = {
't' : ('unknown field', 'temperature', 0, 1),
's' : ('test field', 'temperature', 't'),
}
Integrals
Define the integral type and quadrature rule. This keyword is optional, as the integration orders can be specified directly
in equations (see below):
integrals = {
<name> : <order>
}
where
β’ <name> - the integral name - it has to begin with βiβ!
β’ <order> - the order of polynomials to integrate, or βcustomβ for integrals with explicitly given values and
weights
Example:
import numpy as nm
N = 2
integrals = {
'i1' : 2,
'i2' : ('custom', zip(nm.linspace( 1e-10, 0.5, N ),
nm.linspace( 1e-10, 0.5, N )),
[1./N] * N),
}
The essential boundary conditions set values of DOFs in some regions, while the constraints constrain or transform
values of DOFs in some regions.
The Dirichlet, or essential, boundary conditions apply in a given region given by its name, and, optionally, in selected
times. The times can be given either using a list of tuples (t0, t1) making the condition active for t0 <= t < t1, or by a
name of a function taking the time argument and returning True or False depending on whether the condition is active
at the given time or not.
Dirichlet (essential) boundary conditions:
ebcs = {
<name> : (<region_name>, [<times_specification>,]
{<dof_specification> : <value>[,
<dof_specification> : <value>, ...]})
}
Example:
ebcs = {
'u1' : ('Left', {'u.all' : 0.0}),
'u2' : ('Right', [(0.0, 1.0)], {'u.0' : 0.1}),
'phi' : ('Surface', {'phi.all' : 0.0}),
'u_yz' : ('Gamma', {'u.[1,2]' : 'rotate_yz'}),
}
The u_yz condition illustrates calculating the condition value by a function. In this example, it is a function of coordi-
nates coors of region nodes:
import numpy as nm
mtx = rotation_matrix2d(angle)
vec_rotated = nm.dot(vec, mtx)
return displacement
functions = {
'rotate_yz' : (rotate_yz,),
}
40 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
The periodic boundary conditions tie DOFs of a single variable in two regions that have matching nodes. Can be used
with functions in sfepy.discrete.fem.periodic.
Periodic boundary conditions:
epbcs = {
<name> : ((<region1_name>, <region2_name>), [<times_specification>,]
{<dof_specification> : <dof_specification>[,
<dof_specification> : <dof_specification>, ...]},
<match_function_name>)
}
Example:
epbcs = {
'up1' : (('Left', 'Right'), {'u.all' : 'u.all', 'p.0' : 'p.0'},
'match_y_line'),
}
The linear combination boundary conditions (LCBCs) are more general than the Dirichlet BCs or periodic BCs. They
can be used to substitute one set of DOFs in a region by another set of DOFs, possibly in another region and of another
variable. The LCBCs can be used only in FEM with nodal (Lagrange) basis.
Available LCBC kinds:
β’ 'rigid' - in linear elasticity problems, a region moves as a rigid body;
β’ 'no_penetration' - in flow problems, the velocity vector is constrained to the plane tangent to the surface;
β’ 'normal_direction' - the velocity vector is constrained to the normal direction;
β’ 'edge_direction' - the velocity vector is constrained to the mesh edge direction;
β’ 'integral_mean_value' - all DOFs in a region are summed to a single new DOF;
β’ 'shifted_periodic' - generalized periodic BCs that work with two different variables and can have a non-zero
mutual shift.
Only the 'shifted_periodic' LCBC needs the second region and the DOF mapping function, see below.
Linear combination boundary conditions:
lcbcs = {
'shifted' : (('Left', 'Right'),
{'u1.all' : 'u2.all'},
'match_y_line', 'shifted_periodic',
'get_shift'),
'mean' : ('Middle', {'u1.all' : None}, None, 'integral_mean_value'),
}
Initial Conditions
Initial conditions are applied prior to the boundary conditions - no special care must be used for the boundary dofs:
ics = {
<name> : (<region_name>, {<dof_specification> : <value>[,
<dof_specification> : <value>, ...]},...)
}
Example:
ics = {
'ic' : ('Omega', {'T.0' : 5.0}),
}
Materials
Materials are used to define constitutive parameters (e.g. stiffness, permeability, or viscosity), and other non-field
arguments of terms (e.g. known traction or volume forces). Depending on a particular term, the parameters can be
constants, functions defined over FE mesh nodes, functions defined in the elements, etc.
Example:
material = {
'm' : ({'val' : [0.0, -1.0, 0.0]},),
'm2' : 'get_pars',
'm3' : (None, 'get_pars'), # Same as the above line.
}
The functions for defining material parameters can work in two modes, distinguished by the mode argument. The two
modes are βqpβ and βspecialβ. The first mode is used for usual functions that define parameters in quadrature points
(hence βqpβ), while the second one can be used for special values like various flags.
The shape and type of data returned in the βspecialβ mode can be arbitrary (depending on the term used). On the other
hand, in the βqpβ mode all the data have to be numpy float64 arrays with shape (n_coor, n_row, n_col), where n_coor
is the number of quadrature points given by the coors argument, n_coor = coors.shape[0], and (n_row, n_col) is the
shape of a material parameter in each quadrature point. For example, for scalar parameters, the shape is (n_coor, 1, 1).
The shape is determined by each term.
Example:
42 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
functions = {
'get_pars' : (get_pars,),
}
If a material parameter has the same value in all quadrature points, than it is not necessary to repeat the constant and
the array can be with shape (1, n_row, n_col).
Examples
equations = {
'Temperature' : """dw_laplace.i.Omega( coef.val, s, t ) = 0"""
}
equations = {
'Temperature' : """dw_laplace.2.Omega( coef.val, s, t ) = 0"""
}
equations = {
'Temperature' : """dw_laplace.a.Omega( coef.val, s, t ) = 0"""
}
β’ Navier-Stokes equations:
equations = {
'balance' :
"""+ dw_div_grad.i2.Omega( fluid.viscosity, v, u )
+ dw_convect.i2.Omega( v, u )
- dw_stokes.i1.Omega( v, p ) = 0""",
'incompressibility' :
"""dw_stokes.i1.Omega( u, q ) = 0""",
}
Configuring Solvers
In SfePy, a non-linear solver has to be specified even when solving a linear problem. The linear problem is/should be
then solved in one iteration of the nonlinear solver.
Linear and nonlinear solver:
solvers = {
'ls' : ('ls.scipy_direct', {}),
'newton' : ('nls.newton',
{'i_max' : 1}),
}
Solver selection:
options = {
'nls' : 'newton',
'ls' : 'ls',
}
For the case that a chosen linear solver is not available, it is possible to define the fallback option of the chosen solver
which specifies a possible alternative:
solvers = {
'ls': ('ls.mumps', {'fallback': 'ls2'}),
'ls2': ('ls.scipy_umfpack', {}),
'newton': ('nls.newton', {
'i_max' : 1,
'eps_a' : 1e-10,
}),
}
Another possibility is to use a βvirtualβ solver that ensures an automatic selection of an available solver, see Virtual
Linear Solvers with Automatic Selection.
Functions
Functions are a way of customizing SfePy behavior. They make it possible to define material properties, boundary
conditions, parametric sweeps, and other items in an arbitrary manner. Functions are normal Python functions declared
in the Problem Definition file, so they can invoke the full power of Python. In order for SfePy to make use of the
functions, they must be declared using the function keyword. See the examples below, and also the corresponding
sections above.
Examples
44 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
functions = {
'get_circle' : (get_circle,),
}
functions = {
'get_p_edge' : (get_p_edge,),
}
ebcs = {
'p' : ('Gamma', {'p.0' : 'get_p_edge'}),
}
The values can be given by a function of time, coordinates and possibly other data, for example:
ebcs = {
'f1' : ('Gamma1', {'u.0' : 'get_ebc_x'}),
'f2' : ('Gamma2', {'u.all' : 'get_ebc_all'}),
}
functions = {
'get_ebc_x' : (lambda ts, coors, bc, problem, **kwargs:
get_ebc_x(coors, 5.0),),
'get_ebc_all' : (lambda ts, coors, bc, problem, **kwargs:
get_ebc_all(ts, coors),),
}
Note that when setting more than one component as in get_ebc_all() above, the function should return either
an array of shape (coors.shape[0], n_components), or the same array flattened to 1D row-by-row (i.e. node-by-
node), where n_components corresponds to the number of components in the boundary condition definition. For
example, with βu.[0, 1]β, n_components is 2.
β’ function for defining usual material parameters:
functions = {
'get_pars' : (get_pars,),
}
The keyword arguments contain both additional use-specified arguments, if any, and the following: equations,
term, problem, for cases when the function needs access to the equations, problem, or term instances that
requested the parameters that are being evaluated. The full signature of the function is:
def get_pars(ts, coors, mode=None,
equations=None, term=None, problem=None, **kwargs)
functions = {
'get_pars1' : (lambda ts, coors, mode=None, **kwargs:
get_pars_special(ts, coors, mode,
extra_arg='hello!'),),
}
if mode == 'special':
val = coors[:,1]
val.shape = (coors.shape[0], 1, 1)
46 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
return out
functions = {
'get_pars_both' : (get_pars_both,),
}
functions = {
'get_load_variable' : (get_load_variable,)
}
Miscellaneous
The options can be used to select solvers, output file format, output directory, to register functions to be called at various
phases of the solution (the hooks), and for other settings.
Additional options (including solver selection):
options = {
# int >= 0, uniform mesh refinement level
'refinement_level : 0',
# save a restart file for each time step, only the last computed time
# step restart file is kept.
'save_restart' : -1,
β’ post_process_hook enables computing derived quantities, like stress or strain, from the primary unknown
48 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
Equations in SfePy are built using terms, which correspond directly to the integral forms of weak formulation of a
problem to be solved. As an example, let us consider the Laplace equation in time interval π‘ β [0, π‘final ]:
ππ
+ πβπ = 0 in β¦, π (π‘) = πΒ―(π‘) on Ξ . (1.5)
ππ‘
The weak formulation of (1.5) is: Find π β π , such that
β«οΈ β«οΈ
ππ
π + π βπ : βπ = 0, βπ β π0 , (1.6)
Ξ© ππ‘ Ξ©
where we assume no fluxes over πβ¦ β Ξ. In the syntax used in SfePy input files, this can be written as:
which directly corresponds to the discrete version of (1.6): Find π β πβ , such that
β«οΈ β«οΈ
ππ
π π ( ππ π) + π π ( π πΊπ πΊ)π = 0, βπ β πβ0 ,
Ξ©β ππ‘ Ξ©β
where π’ β ππ’, βπ’ β πΊπ’ for π’ β {π , π }. The integrals over the discrete domain β¦β are approximated by a numerical
quadrature, that is named i in our case.
where <i> denotes an integral name (i.e. a name of numerical quadrature to use) and <r> marks a region (domain
of the integral). In the following, <virtual> corresponds to a test function, <state> to a unknown function and
<parameter> to a known function arguments.
When solving, the individual terms in equations are evaluated in the βweakβ mode. The evaluation modes are described
in the next section.
β’ βevalβ : The evaluation mode integrates the term (= integral) over a region. The result has the same dimension
as the quantity being integrated. This mode can be used, for example, to compute some global quantities during
postprocessing such as fluxes or total values of extensive quantities (mass, volume, energy, . . . ).
β’ βel_evalβ : The element evaluation mode results in an array of a quantity integrated over each element of a region.
β’ βel_avgβ : The element average mode results in an array of a quantity averaged in each element of a region. This
is the mode for postprocessing.
β’ βqpβ : The quadrature points mode results in an array of a quantity interpolated into quadrature points of each
element in a region. This mode is used when further point-wise calculations with the result are needed. The
same element type and number of quadrature points in each element are assumed.
Not all terms support all the modes - consult the documentation of the individual terms. There are, however, certain
naming conventions:
β’ βdw_*β terms support βweakβ mode
β’ βdq_*β terms support βqpβ mode
β’ βd_*β, βdi_*β terms support βevalβ and βel_evalβ modes
β’ βev_*β terms support βevalβ, βel_evalβ, βel_avgβ and βqpβ modes
Note that the naming prefixes are due to history when the mode argument to Problem.evaluate() and Term.
evaluate() was not available. Now they are often redundant, but are kept around to indicate the evaluation purpose
of each term.
Several examples of using the Problem.evaluate() function are shown below.
A solution to equations given in a problem description file is given by the variables of the βunknown fieldβ kind, that
are set in the solution procedure. By default, those are the only values that are stored into a results file. The solution
postprocessing allows computing additional, derived, quantities, based on the primary variables values, as well as any
other quantities to be stored in the results.
Let us illustrate this using several typical examples. Let us assume that the postprocessing function is
called βpost_process()β, and is added to options as discussed in Miscellaneous, see βpost_process_hookβ and
βpost_process_hook_finalβ. Then:
β’ compute stress and strain given the displacements (variable u):
Parameters
----------
out : dict
The output dictionary, where this function will store additional
data.
problem : Problem instance
The current Problem instance.
variables : Variables instance
The computed state, containing FE coefficients of all the unknown
variables.
extend : bool
(continues on next page)
50 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
Returns
-------
out : dict
The updated output dictionary.
"""
from sfepy.base.base import Struct
return out
return out
mu = pb.evaluate('ev_integrate_mat.2.Omega(nonlinear.mu, u)',
mode='el_avg', copy_materials=False, verbose=False)
out['mu'] = Struct(name='mu', mode='cell', data=mu, dofs=None)
return out
volume = problem.evaluate('ev_volume.2.Omega(u)')
1.4.7 Probing
Probing applies interpolation to output the solution along specified paths. There are two ways of probing:
β’ VTK probes: It is the simple way of probing using the βpost_process_hookβ. It generates matplotlib figures
with the probing results and previews of the mesh with the probe paths. See Primer or linear_elasticity-its2D_5
example.
β’ SfePy probes: As mentioned in Miscellaneous, it relies on defining two additional functions, namely the
βgen_probesβ function, that should create the required probes (see sfepy.discrete.probes), and the
βprobe_hookβ function that performs the actual probing of the results for each of the probes. This function can re-
turn the probing results, as well as a handle to a corresponding matplotlib figure. See linear_elasticity/its2D_4.py
for additional explanation.
Using sfepy.discrete.probes allows correct probing of fields with the approximation order greater than one,
see Interactive Example in Primer or linear_elasticity/its2D_interactive.py for an example of interactive use.
The following postprocessing functions based on the VTK filters are available:
β’ βget_vtk_surfaceβ: extract mesh surface
β’ βget_vtk_edgesβ: extract mesh edges
β’ βget_vtk_by_groupβ: extract domain by a material ID
β’ βtetrahedralize_vtk_meshβ: 3D cells are converted to tetrahedral meshes, 2D cells to triangles
The following code demonstrates the use of the postprocessing filters:
mesh = problem.domain.mesh
mesh_name = mesh.name[mesh.name.rfind(osp.sep) + 1:]
matrix_surf = get_vtk_surface(matrix)
matrix_surf_tri = tetrahedralize_vtk_mesh(matrix_surf)
write_vtk_to_file('%s_mat1_surface.vtk' % mesh_name, matrix_surf_tri)
matrix_edges = get_vtk_edges(matrix)
write_vtk_to_file('%s_mat1_edges.vtk' % mesh_name, matrix_edges)
52 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
1.4.9 Solvers
This section describes the time-stepping, nonlinear, linear, eigenvalue and optimization solvers available in SfePy.
There are many internal and external solvers in the sfepy.solvers package that can be called using a uniform interface.
Time-stepping solvers
All PDEs that can be described in a problem description file are solved internally by a time-stepping solver. This holds
even for stationary problems, where the default single-step solver ('ts.stationary') is created automatically. In this
way, all problems are treated in a uniform way. The same holds when building a problem interactively, or when writing
a script, whenever the Problem.solve() function is used for a problem solution.
The following solvers are available:
β’ ts.adaptive: Implicit time stepping solver with an adaptive time step.
β’ ts.bathe: Solve elastodynamics problems by the Bathe method.
β’ ts.euler: Simple forward euler method
β’ ts.generalized_alpha: Solve elastodynamics problems by the generalized πΌ method.
β’ ts.multistaged: Explicit time stepping solver with multistage solve_step method
β’ ts.newmark: Solve elastodynamics problems by the Newmark method.
β’ ts.runge_kutta_4: Classical 4th order Runge-Kutta method,
β’ ts.simple: Implicit time stepping solver with a fixed time step.
β’ ts.stationary: Solver for stationary problems without time stepping.
β’ ts.tvd_runge_kutta_3: 3rd order Total Variation Diminishing Runge-Kutta method
β’ ts.velocity_verlet: Solve elastodynamics problems by the velocity-Verlet method.
See sfepy.solvers.ts_solvers for available time-stepping solvers and their options.
Nonlinear Solvers
Almost every problem, even linear, is solved in SfePy using a nonlinear solver that calls a linear solver in each iteration.
This approach unifies treatment of linear and non-linear problems, and simplifies application of Dirichlet (essential)
boundary conditions, as the linear system computes not a solution, but a solution increment, i.e., it always has zero
boundary conditions.
The following solvers are available:
β’ nls.newton: Solves a nonlinear system π (π₯) = 0 using the Newton method.
β’ nls.oseen: The Oseen solver for Navier-Stokes equations.
β’ nls.petsc: Interface to PETSc SNES (Scalable Nonlinear Equations Solvers).
β’ nls.scipy_broyden_like: Interface to Broyden and Anderson solvers from scipy.optimize.
β’ nls.semismooth_newton: The semi-smooth Newton method.
See sfepy.solvers.nls, sfepy.solvers.oseen and sfepy.solvers.semismooth_newton for all available
nonlinear solvers and their options.
Linear Solvers
Choosing a suitable linear solver is key to solving efficiently stationary as well as transient PDEs. SfePy allows using
a number of external solvers with a unified interface.
The following solvers are available:
β’ ls.cm_pb: Conjugate multiple problems.
β’ ls.mumps: Interface to MUMPS solver.
β’ ls.mumps_par: Interface to MUMPS parallel solver.
β’ ls.petsc: PETSc Krylov subspace solver.
β’ ls.pyamg: Interface to PyAMG solvers.
β’ ls.pyamg_krylov: Interface to PyAMG Krylov solvers.
β’ ls.schur_mumps: Mumps Schur complement solver.
β’ ls.scipy_direct: Direct sparse solver from SciPy.
β’ ls.scipy_iterative: Interface to SciPy iterative solvers.
β’ ls.scipy_superlu: SuperLU - direct sparse solver from SciPy.
β’ ls.scipy_umfpack: UMFPACK - direct sparse solver from SciPy.
See sfepy.solvers.ls for all available linear solvers and their options.
A βvirtualβ solver can be used in case it is not clear which external linear solvers are available. Each βvirtualβ solver
selects the first available solver from a pre-defined list.
The following solvers are available:
β’ ls.auto_direct: The automatically selected linear direct solver.
β’ ls.auto_iterative: The automatically selected linear iterative solver.
See sfepy.solvers.auto_fallback for all available virtual solvers.
54 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
Optimization Solvers
The PETSc-based nonlinear equations solver 'nls.petsc' and linear system solver 'ls.petsc' can be used for
parallel computations, together with the modules in sfepy.parallel package. This feature is very preliminary, and can
be used only with the commands for interactive use - problem description files are not supported (yet). The key module
is sfepy.parallel.parallel that takes care of the domain and field DOFs distribution among parallel tasks, as well
as parallel assembling to PETSc vectors and matrices.
β’ The partitioning of the domain and fields DOFs is not done in parallel and all tasks need to load the whole mesh
and define the global fields - those must fit into memory available to each task.
β’ While all KSP and SNES solver are supported, in principle, most of their options have to be passed using the
command-line parameters of PETSc - they are not supported yet in the SfePy solver parameters.
β’ There are no performance statistics yet. The code was tested on a single multi-cpu machine only.
β’ The global solution is gathered to task 0 and saved to disk serially.
β’ The vertices of surface region selector does not work in parallel, because the region definition is applied
to a task-local domain.
Examples
The examples demonstrating the use parallel problem solving in SfePy are:
β’ diffusion/poisson_parallel_interactive.py
β’ multi_physics/biot_parallel_interactive.py
See their help messages for further information.
Isogeometric analysis (IGA) is a recently developed computational approach that allows using the NURBS-based do-
main description from CAD design tools also for approximation purposes similar to the finite element method.
The implementation is SfePy is based on Bezier extraction of NURBS as developed in1 . This approach allows reusing
the existing finite element assembling routines, as still the evaluation of weak forms occurs locally in βelementsβ and
the local contributions are then assembled to the global system.
Current Implementation
The IGA code is still very preliminary and some crucial components are missing. The current implementation is also
very slow, as it is in pure Python.
The following already works:
β’ single patch tensor product domain support in 2D and 3D
β’ region selection based on topological Bezier mesh, see below
β’ Dirichlet boundary conditions using projections for non-constant values
β’ evaluation in arbitrary point in the physical domain
β’ both scalar and vector volume terms work
β’ term integration over the whole domain as well as a volume subdomain
β’ simple linearization (output file generation) based on sampling the results with uniform parametric vectors
β’ basic domain generation with script/gen_iga_patch.py based on igakit
The following is not implemented yet:
β’ tests
β’ theoretical convergence rate verification
β’ surface terms
β’ other boundary conditions
β’ proper (adaptive) linearization for post-processing
β’ support for multiple NURBS patches
Domain Description
The domain description is in custom HDF5-based files with .iga extension. Such a file contains:
β’ NURBS patch data (knots, degrees, control points and weights). Those can either be generated using igakit,
created manually or imported from other tools.
β’ Bezier extraction operators and corresponding DOF connectivity (computed by SfePy).
β’ Bezier mesh control points, weights and connectivity (computed by SfePy).
The Bezier mesh is used to create a topological Bezier mesh - a subset of the Bezier mesh containing the Bezier
element corner vertices only. Those vertices are interpolatory (are on the exact geometry) and so can be used for region
selections.
1 Michael J. Borden, Michael A. Scott, John A. Evans, Thomas J. R. Hughes: Isogeometric finite element data structures based on Bezier
extraction of NURBS, Institute for Computational Engineering and Sciences, The University of Texas at Austin, Austin, Texas, March 2010.
56 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
Region Selection
The domain description files contain vertex sets for regions corresponding to the patch sides, named 'xiIJ', where I
is the parametric axis (0, 1, or 2) and J is 0 or 1 for the beginning and end of the axis knot span. Other regions can be
defined in the usual way, using the topological Bezier mesh entities.
Examples
fields = {
't1' : ('real', 1, 'Omega', None, 'H1', 'iga'),
't2' : ('real', 1, 'Omega', 'iga', 'H1', 'iga'),
't3' : ('real', 1, 'Omega', 'iga+%d', 'H1', 'iga'),
}
The approximation order in the first definition is None as it is given by the NURBS degrees in the domain
description. The second definition is equivalent to the first one. The third definition, where %d should be a non-
negative integer, illustrates how to increase the fieldβs NURBS degrees (while keeping the continuity) w.r.t. the
domain NURBS description. It is applied in the navier_stokes/navier_stokes2d_iga.py example to the velocity
field.
1.5 Examples
This section contains domain-specific tutorials as well as the automatically generated list of the standard examples that
come with SfePy.
1.5.1 Primer
Introduction
This primer presents a step-by-step walk-through of the process to solve a simple mechanics problem. The typical
process to solve a problem using SfePy is followed: a model is meshed, a problem definition file is drafted, SfePy is run
to solve the problem and finally the results of the analysis are visualised.
1.5. Examples 57
SfePy Documentation, Release version: 2022.1+git.f9873484
Problem statement
A popular test to measure the tensile strength of concrete or asphalt materials is the indirect tensile strength (ITS) test
pictured below. In this test a cylindrical specimen is loaded across its diameter to failure. The test is usually run by
loading the specimen at a constant deformation rate of 50 mm/minute (say) and measuring the load response. When
the tensile stress that develops in the specimen under loading exceeds its tensile strength then the specimen will fail.
To model this problem using finite elements the indirect tensile test can be simplified to represent a diametrically point
loaded disk as shown in the schematic.
The tensile and compressive stresses that develop in the specimen as a result of the point loads P are a function of the
diameter π· and thickness π‘ of the cylindrical specimen. At the centre of the specimen, the compressive stress is 3 times
the tensile stress and the analytical formulation for these are, respectively:
2π
ππ‘ = (1.7)
ππ‘π·
6π
ππ = (1.8)
ππ‘π·
These solutions may be approximated using finite element methods. To solve this problem using SfePy the first step is
meshing a suitable model.
Meshing
Assuming plane strain conditions, the indirect tensile test may be modelled using a 2D finite element mesh. Further-
more, the geometry of the model is symmetrical about the x- and y-axes passing through the centre of the circle. To
take advantage of this symmetry only one quarter of the 2D model will be meshed and boundary conditions will be
established to indicate this symmetry. The meshing program Gmsh is used here to very quickly mesh the model. Follow
these steps to model the ITS:
1. The ITS specimen has a diameter of 150 mm. Using Gmsh add three new points (geometry elementary entities)
at the following coordinates: (0.00.0), (75.0, 0.0) and (0.0, 75.0).
2. Next add two straight lines connecting the points.
58 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
3. Next add a Circle arc connecting two of the points to form the quarter circle segment.
4. Still under Geometry add a ruled surface.
5. With the geometry of the model defined, add a mesh by clicking on the 2D button under the Mesh functions.
The figures that follow show the various stages in the model process.
Thatβs the meshing done. Save the mesh in a format that SfePy recognizes. For now use the medit .mesh format e.g.
its2D.mesh.
Hint: Check the drop down in the Save As dialog for the different formats that Gmsh can save to.
1.5. Examples 59
SfePy Documentation, Release version: 2022.1+git.f9873484
If you open the its2D.mesh file using a text editor youβll notice that Gmsh saves the mesh in a 3D format and includes
some extra geometry items that should be deleted. Reformatted the mesh file to a 2D format and delete the Edges block.
Note that when you do this the file cannot be reopened by Gmsh so it is always a good idea to also save your meshes in
Gmshβs native format as well (Shift-Ctrl-S). Click here to download the reformatted mesh file that will be used in the
tutorial.
Youβll notice that the mesh contains 55 vertices (nodes) and 83 triangle elements. The mesh file provides the coordinates
of the nodes and the element connectivity. It is important to note that node and element numbering in SfePy start at 0
and not 1 as is the case in Gmsh and some other meshing programs.
To view .mesh files you can use a demo of medit. After loading your mesh file with medit you can see the node and
element numbering by pressing P and F respectively. The numbering in medit starts at 1 as shown. Thus the node
at the center of the model in SfePy numbering is 0, and elements 76 and 77 are connected to this node. Node and
element numbers can also be viewed in Gmsh β under the mesh option under the Visibility tab enable the node and
surface labels. Note that the surface labels as numbered in Gmsh follow on from the line numbering. So to get the
corresponding element number in SfePy youβll need to subtract the number of lines in the Gmsh file + 1. Confused yet?
Luckily, SfePy provides some useful mesh functions to indicate which elements are connected to which nodes. Nodes
and elements can also be identified by defining regions, which is addressed later.
Another open source python option to view .mesh files is the appropriately named Python Mesh Viewer.
The next step in the process is coding the SfePy problem definition file.
Problem description
The programming of the problem description file is well documented in the SfePy Userβs Guide. The problem descrip-
tion file used in the tutorial follows:
r"""
Diametrically point loaded 2-D disk. See :ref:`sec-primer`.
.. math::
\int_{\Omega} D_{ijkl}\ e_{ij}(\ul{v}) e_{kl}(\ul{u})
= 0
\;, \quad \forall \ul{v} \;,
60 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
.. math::
D_{ijkl} = \mu (\delta_{ik} \delta_{jl}+\delta_{il} \delta_{jk}) +
\lambda \ \delta_{ij} \delta_{kl}
\;.
"""
from __future__ import absolute_import
from sfepy.mechanics.matcoefs import stiffness_from_youngpoisson
from sfepy.discrete.fem.utils import refine_mesh
from sfepy import data_dir
# Fix the mesh file name if you run this file outside the SfePy directory.
filename_mesh = data_dir + '/meshes/2d/its2D.mesh'
refinement_level = 0
filename_mesh = refine_mesh(filename_mesh, refinement_level)
output_dir = '.' # set this to a valid directory you have write access to
options = {
'output_dir' : output_dir,
}
regions = {
'Omega' : 'all',
'Left' : ('vertices in (x < 0.001)', 'facet'),
'Bottom' : ('vertices in (y < 0.001)', 'facet'),
'Top' : ('vertex 2', 'vertex'),
}
materials = {
'Asphalt' : ({'D': stiffness_from_youngpoisson(2, young, poisson)},),
'Load' : ({'.val' : [0.0, -1000.0]},),
}
fields = {
'displacement': ('real', 'vector', 'Omega', 1),
}
equations = {
'balance_of_forces' :
"""dw_lin_elastic.2.Omega(Asphalt.D, v, u)
= dw_point_load.0.Top(Load.val, v)""",
}
variables = {
'u' : ('unknown field', 'displacement', 0),
'v' : ('test field', 'displacement', 'u'),
(continues on next page)
1.5. Examples 61
SfePy Documentation, Release version: 2022.1+git.f9873484
ebcs = {
'XSym' : ('Bottom', {'u.1' : 0.0}),
'YSym' : ('Left', {'u.0' : 0.0}),
}
solvers = {
'ls' : ('ls.scipy_direct', {}),
'newton' : ('nls.newton', {
'i_max' : 1,
'eps_a' : 1e-6,
}),
}
Download the Problem description file and open it in your favourite Python editor. Note that you may wish to
change the location of the output directory to somewhere on your drive. You may also need to edit the mesh file name.
For the analysis we will assume that the material of the test specimen is linear elastic and isotropic. We define two
material constants i.e. Youngβs modulus and Poissonβs ratio. The material is assumed to be asphalt concrete having a
Youngβs modulus of 2,000 MPa and a Poissonβs ration of 0.4.
Note: Be consistent in your choice and use of units. In the tutorial we are using Newton (N), millimeters (mm) and
megaPascal (MPa). The sfepy.mechanics.units module might help you in determining which derived units correspond
to given basic units.
The following block of code defines regions on your mesh:
regions = {
'Omega' : 'all',
'Left' : ('vertices in (x < 0.001)', 'facet'),
'Bottom' : ('vertices in (y < 0.001)', 'facet'),
'Top' : ('vertex 2', 'vertex'),
}
ebcs = {
'XSym' : ('Bottom', {'u.1' : 0.0}),
'YSym' : ('Left', {'u.0' : 0.0}),
}
Now the power of the regions entity becomes apparent. To ensure symmetry about the x-axis, the vertical or y-
displacement of the nodes in the βBottomβ region are prevented or set to zero. Similarly, for symmetry about the
y-axis, any horizontal or displacement in the x-direction of the nodes in the βLeftβ region or y-axis is prevented.
The load is specified in terms of the βLoadβ material as follows:
62 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
materials = {
'Asphalt' : ({
'lam' : lame_from_youngpoisson(young, poisson)[0],
'mu' : lame_from_youngpoisson(young, poisson)[1],
},),
'Load' : ({'.val' : [0.0, -1000.0]},),
}
Note the dot in β.valβ β this denotes a special material value, i.e., a value that is not to be evaluated in quadrature points.
The load is then applied in equations using the βdw_point_load.0.Top(Load.val, v)β term in the topmost node (region
βTopβ).
We provided the material constants in terms of Youngβs modulus and Poissonβs ratio, but the linear elastic isotropic
equation used requires as input LamΓ©βs parameters. The lame_from_youngpoisson() function is thus used for conver-
sion. Note that to use this function it was necessary to import the function into the code, which was done up front:
Hint: Check out the sfepy.mechanics.matcoefs module for other useful material related functions.
Thatβs it β we are now ready to solve the problem.
Running SfePy
One option to solve the problem is to run the SfePy simple.py script from the command line:
./simple.py its2D_1.py
Note: For the purpose of this tutorial it is assumed that the problem description file (its2D_1.py) is in the same
directory as the simple.py script. If you have the its2D_1.py file in another directory then make sure you include the
path to this file as well.
SfePy solves the problem and outputs the solution to the output path (output_dir) provided in the script. The output file
will be in the VTK format by default if this is not explicitly specified and the name of the output file will be the same
as that used for the mesh file except with the β.vtkβ extension i.e. its2D.vtk.
The VTK format is an ASCII format. Open the file using a text editor. Youβll notice that the output file includes separate
sections:
β’ POINTS (these are the model nodes),
β’ CELLS (the model element connectivity),
β’ VECTORS (the node displacements in the x-, y- and z- directions).
SfePy provides a script (postproc.py) to quickly view the solution. To run this script you need to have Mayavi installed.
From the command line issue the following (assuming the correct paths):
./postproc.py its2D.vtk
The postproc.py script generates the image shown below, which shows by default the displacements in the model as
arrows and their magnitude as color scale. Cool, but we are more interested in the stresses. To get these we need to
modify the problem description file and do some post-processing.
1.5. Examples 63
SfePy Documentation, Release version: 2022.1+git.f9873484
Post-processing
SfePy provides functions to calculate stresses and strains. Weβll include a function to calculate these and update the
problem material definition and options to call this function as a post_process_hook(). Save this file as its2D_2.py.
r"""
Diametrically point loaded 2-D disk with postprocessing. See
:ref:`sec-primer`.
.. math::
\int_{\Omega} D_{ijkl}\ e_{ij}(\ul{v}) e_{kl}(\ul{u})
= 0
\;, \quad \forall \ul{v} \;,
where
.. math::
D_{ijkl} = \mu (\delta_{ik} \delta_{jl}+\delta_{il} \delta_{jk}) +
\lambda \ \delta_{ij} \delta_{kl}
\;.
"""
ev = pb.evaluate
strain = ev('ev_cauchy_strain.2.Omega(u)', mode='el_avg')
stress = ev('ev_cauchy_stress.2.Omega(Asphalt.D, u)', mode='el_avg',
(continues on next page)
64 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
return out
asphalt = materials['Asphalt'][0]
asphalt.update({'D' : stiffness_from_youngpoisson(2, young, poisson)})
options.update({'post_process_hook' : 'stress_strain',})
The updated file imports all of the previous definitions in its2D_1.py. The stress function (de_cauchy_stress())
requires as input the stiffness tensor β thus it was necessary to update the materials accordingly. The problem options
were also updated to call the stress_strain() function as a post_process_hook().
Run SfePy to solve the updated problem and view the solution (assuming the correct paths):
./simple.py its2D_2.py
./postproc.py its2D.vtk -b
In addition to the node displacements, the VTK output shown below now also includes the stresses and strains averaged
in the elements:
Remember the objective was to determine the stresses at the centre of the specimen under a load π . The solution as
currently derived is expressed in terms of a global displacement vector π’. The global (residual) force vector π is a
function of the global displacement vector and the global stiffness matrix πΎ as: π = πΎπ’. Letβs determine the force
vector interactively.
1.5. Examples 65
SfePy Documentation, Release version: 2022.1+git.f9873484
In addition to solving problems using the simple.py script you can also run SfePy interactively (we will use IPython
interactive shell in following examples).
In the SfePy top-level directory run
ipython
The problem is solved and the problem definition and solution are provided in the pb and variables variables respec-
tively. The solution, or in this case, the global displacement vector π’, contains the x- and y-displacements at the nodes
in the 2D model:
In [3]: u = variables()
In [4]: u
Out[4]:
array([ 0. , 0. , 0.37376671, ..., -0.19923848,
0.08820237, -0.11201528])
In [5]: u.shape
Out[5]: (110,)
In [7]: u
Out[7]:
array([[ 0. , 0. ],
[ 0.37376671, 0. ],
[ 0. , -1.65318152],
...,
[ 0.08716448, -0.23069047],
[ 0.27741356, -0.19923848],
[ 0.08820237, -0.11201528]])
Note: We have used the fact, that the state vector contains only one variable (u). In general, the following can be used:
In [8]: u = variables.get_state_parts()['u']
In [9]: u
Out[9]:
array([[ 0. , 0. ],
[ 0.37376671, 0. ],
[ 0. , -1.65318152],
...,
[ 0.08716448, -0.23069047],
[ 0.27741356, -0.19923848],
[ 0.08820237, -0.11201528]])
66 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
Both variables() and variables.get_state_parts() return a view of the DOF vector, that is why in Out[8] the vector is
reshaped according to Out[6]. It is thus possible to set the values of state variables by manipulating the state vector,
but shape changes such as the one above are not advised (see In [15] below) - work on a copy instead.
From the above it can be seen that u holds the displacements at the 55 nodes in the model and that the displacement
at node 2 (on which the load is applied) is (0, β1.65318152). The global stiffness matrix is saved in pb as a sparse
matrix:
In [10]: K = pb.mtx_a
In [11]: K
Out[11]:
<94x94 sparse matrix of type '<type 'numpy.float64'>'
with 1070 stored elements in Compressed Sparse Row format>
In [12]: print(K)
(0, 0) 2443.95959851
(0, 7) -2110.99917491
(0, 14) -332.960423597
(0, 15) 1428.57142857
(1, 1) 2443.95959852
(1, 13) -2110.99917492
(1, 32) 1428.57142857
(1, 33) -332.960423596
(2, 2) 4048.78343529
(2, 3) -1354.87004384
(2, 52) -609.367453538
(2, 53) -1869.0018791
(2, 92) -357.41672785
(2, 93) 1510.24654193
(3, 2) -1354.87004384
(3, 3) 4121.03202907
(3, 4) -1696.54911732
(3, 48) 76.2400806561
(3, 49) -1669.59247304
(3, 52) -1145.85294856
(3, 53) 2062.13955556
(4, 3) -1696.54911732
(4, 4) 4410.17902905
(4, 5) -1872.87344838
(4, 42) -130.515009576
: :
(91, 81) -1610.0550578
(91, 86) -199.343680224
(91, 87) -2330.41406097
(91, 90) -575.80373408
(91, 91) 7853.23899229
(92, 2) -357.41672785
(92, 8) 1735.59411191
(92, 50) -464.976034459
(92, 51) -1761.31189004
(92, 52) -3300.45367361
(92, 53) 1574.59387937
(92, 88) -250.325600254
(continues on next page)
1.5. Examples 67
SfePy Documentation, Release version: 2022.1+git.f9873484
In [13]: K.shape
Out[13]: (94, 94)
One would expect the shape of the global stiffness matrix πΎ to be (110, 110) i.e. to have the same number of rows and
columns as u. This matrix has been reduced by the fixed degrees of freedom imposed by the boundary conditions set
at the nodes on symmetry axes. To restore the matrix, temporarily remove the imposed boundary conditions:
In [14]: pb.remove_bcs()
In [15]: f = pb.evaluator.eval_residual(u)
ValueError: shape mismatch: value array of shape (55,2) could not be broadcast toβ£
Λβindexing result of shape (110,)
In [17]: f = pb.evaluator.eval_residual(u)
In [18]: f.shape
Out[18]: (110,)
In [19]: f
Out[19]:
array([ -4.73618436e+01, 1.42752386e+02, 1.56921124e-13, ...,
-2.06057393e-13, 2.13162821e-14, -2.84217094e-14])
Remember to restore the original boundary conditions previously removed in step [14]:
In [20]: pb.time_update()
To view the residual force vector, we can save it to a VTK file. This requires setting f to (a copy of) the variables as
follows:
68 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
Running the postproc.py script on file.vtk displays the average nodal forces as shown below:
Great, we have an almost zero residual vertical load or force apparent at node 2 i.e. -1.13686838e-13 Newton. Let us
now check the stress at node 0, the centre of the specimen.
Previously we had calculated the stresses in the model but these were averaged from those calculated at Gauss quadrature
points within the elements. It is possible to provide custom integrals to allow the calculation of stresses with the Gauss
quadrature points at the element nodes. This will provide us a more accurate estimate of the stress at the centre of the
specimen located at node 0. The code below outlines one way to achieve this.
r"""
Diametrically point loaded 2-D disk with nodal stress calculation. See
:ref:`sec-primer`.
.. math::
\int_{\Omega} D_{ijkl}\ e_{ij}(\ul{v}) e_{kl}(\ul{u})
= 0
\;, \quad \forall \ul{v} \;,
where
.. math::
D_{ijkl} = \mu (\delta_{ik} \delta_{jl}+\delta_{il} \delta_{jk}) +
(continues on next page)
1.5. Examples 69
SfePy Documentation, Release version: 2022.1+git.f9873484
gdata = geometry_data['2_3']
nc = len(gdata.coors)
# Point load.
mat = pb.get_materials()['Load']
P = 2.0 * mat.get_data('special', 'val')[1]
print('\n==================================================================')
print('Given load = %.2f N' % -P)
print('\nAnalytical solution')
print('===================')
print('Horizontal tensile stress = %.5e MPa/mm' % (-2.*P/(nm.pi*150.)))
print('Vertical compressive stress = %.5e MPa/mm' % (-6.*P/(nm.pi*150.)))
print('\nFEM solution')
print('============')
print('Horizontal tensile stress = %.5e MPa/mm' % (svar()[0]))
print('Vertical compressive stress = %.5e MPa/mm' % (-svar()[1]))
print('==================================================================')
return out
asphalt = materials['Asphalt'][0]
(continues on next page)
70 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
integrals = {
'ivn' : ('custom', gdata.coors, [gdata.volume / nc] * nc),
}
The output:
==================================================================
Given load = 2000.00 N
Analytical solution
===================
Horizontal tensile stress = 8.48826e+00 MPa/mm
Vertical compressive stress = 2.54648e+01 MPa/mm
FEM solution
============
Horizontal tensile stress = 7.57220e+00 MPa/mm
Vertical compressive stress = 2.58660e+01 MPa/mm
==================================================================
Not bad for such a coarse mesh! Re-running the problem using a finer mesh provides a more accurate solution:
==================================================================
Given load = 2000.00 N
Analytical solution
===================
Horizontal tensile stress = 8.48826e+00 MPa/mm
Vertical compressive stress = 2.54648e+01 MPa/mm
FEM solution
============
Horizontal tensile stress = 8.50042e+00 MPa/mm
Vertical compressive stress = 2.54300e+01 MPa/mm
To see how the FEM solution approaches the analytical one, try to play with the uniform mesh refinement level in the
Problem description file, namely lines 25, 26:
refinement_level = 0
filename_mesh = refine_mesh(filename_mesh, refinement_level)
1.5. Examples 71
SfePy Documentation, Release version: 2022.1+git.f9873484
Probing
As a bonus for sticking to the end of this tutorial see the following Problem description file that provides SfePy
functions to quickly and neatly probe the solution.
r"""
Diametrically point loaded 2-D disk with postprocessing and probes. See
:ref:`sec-primer`.
.. math::
\int_{\Omega} D_{ijkl}\ e_{ij}(\ul{v}) e_{kl}(\ul{u})
= 0
\;, \quad \forall \ul{v} \;,
where
.. math::
D_{ijkl} = \mu (\delta_{ik} \delta_{jl}+\delta_{il} \delta_{jk}) +
\lambda \ \delta_{ij} \delta_{kl}
\;.
(continues on next page)
72 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
import os
from six.moves import range
ev = pb.evaluate
strain = ev('ev_cauchy_strain.2.Omega(u)', mode='el_avg')
stress = ev('ev_cauchy_stress.2.Omega(Asphalt.D, u)', mode='el_avg')
labels = ['%s -> %s' % (p0, p1) for p0, p1 in zip(ps0, ps1)]
probes = []
for ip in range(len(ps0)):
p0, p1 = ps0[ip], ps1[ip]
probes.append('line%d' % ip)
probe.add_line_probe('line%d' % ip, p0, p1, n_point)
1.5. Examples 73
SfePy Documentation, Release version: 2022.1+git.f9873484
plt.subplot(312)
pars, vals = probe(ip, 'cauchy_strain')
for ii in range(vals.shape[1]):
plt.plot(pars, vals[:, ii], label=r'$e_{%s}$' % sym_labels[ii],
lw=1, ls='-', marker='+', ms=3)
plt.ylabel('Cauchy strain')
plt.xlabel('probe %s' % label, fontsize=8)
plt.legend(loc='best', prop=fm.FontProperties(size=8))
plt.subplot(313)
pars, vals = probe(ip, 'cauchy_stress')
for ii in range(vals.shape[1]):
plt.plot(pars, vals[:, ii], label=r'$\sigma_{%s}$' % sym_labels[ii],
lw=1, ls='-', marker='+', ms=3)
plt.ylabel('Cauchy stress')
plt.xlabel('probe %s' % label, fontsize=8)
plt.legend(loc='best', prop=fm.FontProperties(size=8))
opts = pb.conf.options
filename_results = os.path.join(opts.get('output_dir'),
'its2D_probe_%s.png' % ip)
fig.savefig(filename_results)
return out
options.update({
'post_process_hook' : 'stress_strain',
})
Probing applies interpolation to output the solution along specified paths. For the tutorial, line probing is done along
the x- and y-axes of the model.
Run SfePy to solve the problem and apply the probes:
./simple.py its2D_5.py
The probing function will generate the following figures that show the displacements, normal stresses and strains as
well as shear stresses and strains along the probe paths. Note that you need matplotlib installed to run this example.
74 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
The probing function also generates previews of the mesh with the probe paths.
1.5. Examples 75
SfePy Documentation, Release version: 2022.1+git.f9873484
Interactive Example
SfePy can be used also interactively by constructing directly the classes that corresponds to the keywords in the problem
description files. The following listing shows a script with the same (and more) functionality as the above examples:
#!/usr/bin/env python
"""
Diametrically point loaded 2-D disk, using commands for interactive use. See
:ref:`sec-primer`.
The script combines the functionality of all the ``its2D_?.py`` examples and
allows setting various simulation parameters, namely:
- material parameters
- displacement field approximation order
- uniform mesh refinement level
In the SfePy top-level directory the following command can be used to get usage
information::
python sfepy/examples/linear_elasticity/its2D_interactive.py -h
Notes
-----
The ``--probe`` and ``--show`` options work simultaneously only if Mayavi and
Matplotlib use the same backend type (for example wx).
"""
from __future__ import absolute_import
import sys
from six.moves import range
sys.path.append('.')
from argparse import ArgumentParser, RawDescriptionHelpFormatter
import numpy as nm
(continues on next page)
76 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
def gen_lines(problem):
"""
Define two line probes.
labels = ['%s -> %s' % (p0, p1) for p0, p1 in zip(ps0, ps1)]
probes = []
for ip in range(len(ps0)):
p0, p1 = ps0[ip], ps1[ip]
probes.append(LineProbe(p0, p1, n_point))
1.5. Examples 77
SfePy Documentation, Release version: 2022.1+git.f9873484
plt.subplot(312)
pars, vals = results['cauchy_strain']
for ic in range(vals.shape[1]):
plt.plot(pars, vals[:,ic], label=r'$e_{%s}$' % sym_indices[ic],
lw=1, ls='-', marker='+', ms=3)
plt.ylabel('Cauchy strain')
plt.xlabel('probe %s' % label, fontsize=8)
plt.legend(loc='best', fontsize=10)
plt.subplot(313)
pars, vals = results['cauchy_stress']
for ic in range(vals.shape[1]):
plt.plot(pars, vals[:,ic], label=r'$\sigma_{%s}$' % sym_indices[ic],
lw=1, ls='-', marker='+', ms=3)
plt.ylabel('Cauchy stress')
plt.xlabel('probe %s' % label, fontsize=8)
plt.legend(loc='best', fontsize=10)
helps = {
'young' : "the Young's modulus [default: %(default)s]",
'poisson' : "the Poisson's ratio [default: %(default)s]",
'load' : "the vertical load value (negative means compression)"
" [default: %(default)s]",
'order' : 'displacement field approximation order [default: %(default)s]',
'refine' : 'uniform mesh refinement level [default: %(default)s]',
'probe' : 'probe the results',
'show' : 'show the results figure',
}
def main():
from sfepy import data_dir
parser = ArgumentParser(description=__doc__,
formatter_class=RawDescriptionHelpFormatter)
parser.add_argument('--version', action='version', version='%(prog)s')
parser.add_argument('--young', metavar='float', type=float,
(continues on next page)
78 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
output('using values:')
output(" Young's modulus:", options.young)
output(" Poisson's ratio:", options.poisson)
output(' vertical load:', options.load)
output('uniform mesh refinement level:', options.refine)
if options.refine > 0:
for ii in range(options.refine):
output('refine %d...' % ii)
domain = domain.refine()
output('... %d nodes %d elements'
% (domain.shape.n_nod, domain.shape.n_el))
1.5. Examples 79
SfePy Documentation, Release version: 2022.1+git.f9873484
t1 = Term.new('dw_lin_elastic(Asphalt.D, v, u)',
integral, omega, Asphalt=asphalt, v=v, u=u)
t2 = Term.new('dw_point_load(Load.val, v)',
integral0, top, Load=load, v=v)
eq = Equation('balance', t1 - t2)
eqs = Equations([eq])
ls = AutoDirect({})
nls_status = IndexedStruct()
nls = Newton({}, lin_solver=ls, status=nls_status)
pb = Problem('elasticity', equations=eqs)
pb.set_bcs(ebcs=Conditions([xsym, ysym]))
pb.set_solver(nls)
gdata = geometry_data['2_3']
nc = len(gdata.coors)
if options.probe:
(continues on next page)
80 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
ev = pb.evaluate
order = 2 * (options.order - 1)
strain_qp = ev('ev_cauchy_strain.%d.Omega(u)' % order, mode='qp')
stress_qp = ev('ev_cauchy_stress.%d.Omega(Asphalt.D, u)' % order,
mode='qp', copy_materials=False)
all_results = []
for ii, probe in enumerate(probes):
fig, results = probe_results(u, strain, stress, probe, labels[ii])
fig.savefig('its2D_interactive_probe_%d.png' % ii)
all_results.append(results)
if options.show:
# Show the solution. If the approximation order is greater than 1, the
# extra DOFs are simply thrown away.
from sfepy.postprocess.viewer import Viewer
view = Viewer('its2D_interactive.vtk')
view(vector_mode='warp_norm', rel_scaling=1,
is_scalar_bar=True, is_wireframe=True)
if __name__ == '__main__':
main()
1.5. Examples 81
SfePy Documentation, Release version: 2022.1+git.f9873484
The script can be run from the SfePy top-level directory, assuming the in-place build, as follows:
python sfepy/examples/linear_elasticity/its2D_interactive.py
The script allows setting several parameters that influence the solution, see:
python sfepy/examples/linear_elasticity/its2D_interactive.py -h
for the complete list. Besides the material parameters, a uniform mesh refinement level and the displacement field
approximation order can be specified. The script demonstrates how to
β’ project a derived quantity, that is evaluated in quadrature points (e.g. a strain or stress), into a field variable;
β’ probe the solution defined in the field variables.
Using sfepy.discrete.probes allows correct probing of fields with the approximation order greater than one.
The end.
Introduction
Salome is a powerful open-source tool for generating meshes for numerical simulation and post processing the results.
This is a short tutorial on using Salome as a preprocessor for preparing meshes for use with SfePy.
Tutorial prerequisites
This tutorial assumes that you have a working copy of Salome. It is possible to build Salome from source code.
Fortunately, for the less brave, many pre-compiled binaries for different platforms are available at the Salome download
page. Registration for a free account may be required to download from the preceding site.
In addition, this tutorial assumes you have a working copy of SfePy with MED read support. See the Installation for
help. Note that it is not actually necessary to βinstallβ SfePy; one may run the code from the source directory (see
notation below) after compilation of the C extension modules (again, see the installation notes if you are confused).
Salome has its own set of tutorials and community resources. It is suggested you look around on Salome web site to
familiarize yourself with the available resources.
This tutorial follows the EDF Exercise 1 available from the Salome Tutorial Site. Go ahead and complete this tutorial
now. We will use the result from there in the following.
This is the mesh you should end up with:
82 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
In the Salome MESH module, right click on the mesh object Mesh_Partition_Hexa you created in the Salome EDF
Exercise 1 Tutorial and click Export to MED file. Save the file as Mesh_Partition_Hexa.med in your working
directory <work_dir>.
In this tutorial, we will assume that we need to solve a linear elasticity problem on the mesh generated by Salome. Since
the Salome mesh looks a bit like a fish, we will try to simulate the fish waving its tail.
Copy the file <sfepy_root>/sfepy/examples/linear_elasticity/linear_elastic.py to <work_dir>. Use
your favorite python editor to load this file. We will customize this file for our purposes.
Mesh specification
The first thing we have to do is tell SfePy to use our new mesh. Change the line
to
filename_mesh = 'Mesh_Partition_Hexa.med'
1.5. Examples 83
SfePy Documentation, Release version: 2022.1+git.f9873484
Region specification
Next, we have to define sensible Regions for the mesh. We will apply a displacement to the Tail and keep the Top and
Bottom of the fish fixed. Change the lines
regions = {
'Omega' : 'all',
'Left' : ('vertices in (x < 0.001)', 'facet'),
'Right' : ('vertices in (x > 0.099)', 'facet'),
'SomewhereTop' : ('vertices in (z > 0.017) & (x > 0.03) & (x < 0.07)', 'vertex'),
}
to
regions = {
'Omega' : 'all',
'Tail' : ('vertices in (x < -94)', 'facet'),
'TopFixed' : ('vertices in (z > 9.999) & (x > 54)', 'facet'),
'BotFixed' : ('vertices in (z < 0.001) & (x > 54)', 'facet'),
}
Field specification
The Salome mesh uses hexahedral linear order elements; in SfePy notation these are called 3_8, see Userβs Guide.
Just keep the lines
fields = {
'displacement': ('real', 'vector', 'Omega', 1),
}
In this section, we tell SfePy to fix the top and bottom parts of the βheadβ of the fish and move the tail 10 units to the
side (z direction).
Change the lines
ebcs = {
'Fixed' : ('Left', {'u.all' : 0.0}),
'Displaced' : ('Right', {'u.0' : 0.01, 'u.[1,2]' : 0.0}),
'PerturbedSurface' : ('SomewhereTop', {'u.2' : 0.005}),
}
to
ebcs = {
'TopFixed' : ('TopFixed', {'u.all' : 0.0}),
'BotFixed' : ('BotFixed', {'u.all' : 0.0}),
'Displaced' : ('Tail', {'u.2' : 10, 'u.[0,1]' : 0.0}),
}
84 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
Save your changes to linear_elastic.py. Now itβs time to run the SfePy calculation. In your <work_dir> in your
terminal type:
./simple.py linear_elastic.py
This will run the SfePy calculation. Some progress information is printed to your screen and the residual (a measure
of the convergence of the solution) is printed for each iteration of the solver. The solver terminates when this residual
is less than a certain value. It should only take 1 iteration since we are solving a linear problem. The results will be
saved to Mesh_Partition_Hexa.vtk.
Now we can view the results of our work. In your terminal, type:
You should get the following plot. The undeformed mesh is displayed with a wireframe for comparison. Notice how
the fish is bending its tail in response to the applied displacement.
Now you should be able to use meshes created in Salome with SfePy!
Introduction
There are several open source tools for preparing 2D and 3D finite element meshes like Salome, FreeCAD, Gmsh,
Netgen, etc. Most of them are GUI based geometrical modeling and meshing environments/tools but they also usually
allow using their libraries in user scripts. Some of the above mentioned tools are handy for solid modeling, some
of them are great for meshing. This tutorial shows how to combine solid geometry modeling functions provided by
FreeCAD or OpenSCAD with meshing functions of Gmsh.
The collaboration of modeling, meshing and conversion tools and the workflow are illustrated in the following scheme.
1.5. Examples 85
SfePy Documentation, Release version: 2022.1+git.f9873484
Functionalities of FreeCAD are accessible to Python and can be used to define geometrical models in simple Python
scripts. There is a tutorial related to Python scripting in FreeCAD.
The first step in creating a Python script is to set up a path to the FreeCAD libraries and import all required modules:
1 import sys
2 FREECADPATH = '/usr/lib/freecad/lib/'
3 sys.path.append(FREECADPATH)
4
doc = newDocument()
All new objects describing the geometry will be added to this document.
In the following lines a geometrical model of a screwdriver handle will be created. Letβs start by defining a sphere and
a cylinder and join these objects into the one called uni:
1 radius = 0.01
2 height = 0.1
3
Create a polygon, revolve it around the z-axis to create a solid and use the result as the cutting tool applied to uni
object:
86 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
Create a cylinder, make a polar array of the cylinder objects and subtract it from the previous result:
Finally, recompute the geometry, export the part to the STEP file and save the document in FreeCAD format (not really
needed for subsequent mesh generation, but may be useful for visualization and geometry check):
1 doc.recompute()
2
3 Part.export([dif3], 'screwdriver_handle.step')
4
5 doc.saveAs('screwdriver_handle.FCStd')
A finite element mesh can be generated directly in FreeCAD using MeshPart module:
1 import MeshPart
2
The meshing function of MeshPart module is limited to triangular grids so it is better to use Gmsh mesh generator
which can provide triangular and quadrilateral meshes in 2D or tetrahedral and hexahedral meshes in 3D. Gmsh allows
to control the meshing process through a wide range of parameters. Meshing by Gmsh will be described in section
Gmsh - generating finite element mesh.
1.5. Examples 87
SfePy Documentation, Release version: 2022.1+git.f9873484
The alternative tool for solid geometrical modeling is OpenSCAD - βThe Programmers Solid 3D CAD Modellerβ.
It has its own description language based on functional programming that is used to construct solid models using
geometrical primitives similar to FreeCAD. Solid geometries can be exported to several file formats including STL and
CSG. OpenSCAD allows solid modeling based on Constructive Solid Geometry (CSG) principles and extrusion of 2D
objects into 3D. The model of a screwdriver handle presented in the previous section can be defined in OpenSCAD by
the following code (screwdriver_handle.scad):
1 radius = 0.01;
2 height = 0.1;
3 $fn = 50;
4
5 difference() {
6 difference() {
7 difference() {
8 union() {
9 cylinder(center=false, h=height, r=radius);
10 sphere(radius);
(continues on next page)
88 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
15 }
16 cylinder(center=false, h=1.1*height, r=0.3*radius);
17 }
18 for (i = [1:6]) {
19 rotate([0, 0, 360/6*i])
20 translate([-1.1*radius, 0.0, -0.2*height])
21 cylinder(center=false, h=1.1*height, r=0.2*radius);
22 }
23 }
To generate a finite element mesh of the solid geometry the model must be exported to a suitable file format. OpenSCAD
has limited export options, but by using FreeCAD import/export functions, it is possible to find a workaround. The
OpenSCAD model can be exported to the CSG file format and FreeCAD can be used as a mesh converter to the STEP
format:
1 import sys
2 sys.path.append('/usr/lib/freecad/lib/')
3 sys.path.append('/usr/lib/freecad/Mod/OpenSCAD/')
4
5 import FreeCAD
6 import Part
7 import importCSG
8
9 importCSG.open('screwdriver_handle.csg')
10 Part.export([FreeCAD.ActiveDocument.Objects[-1]], 'screwdriver_handle.step')
1.5. Examples 89
SfePy Documentation, Release version: 2022.1+git.f9873484
Gmsh can create finite element meshes using geometrical models imported from STEP, IGES and BRep files (has to
be compiled with OpenCASCADE support).
The following GEO file imports screwdriver_handle.step file and defines a field controlling the mesh size
(screwdriver_handle.geo):
1 Merge "screwdriver_handle.step";
2
3 Field[1] = MathEval;
4 Field[1].F = "0.002";
5 Background Field = 1;
Now, run Gmsh generator and export the mesh into the MSH format in which all surface and volumetric elements are
stored:
By converting the MSH file into the VTK format using script/convert_mesh.py:
the surface elements are discarded and only the volumetric mesh is preserved.
90 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
that can be described by this Gmsh code, the mesh generator can be called as follows:
This, however is not enough to create a truly 2D mesh - the created mesh vertices still have the third, π§, component
which is equal to zero. In order to remove the third component, use:
Now, in the resulting circle_in_square.h5, each vertex has only two coordinates. Another way of generating the
2D mesh is to use the legacy VTK format as follows:
This is due to the fact that the legacy VTK does not support 2D vertices and so the VTKMeshIO reader tries to detect
the planar geometry by comparing the π§ components to zero - the --2d option of script/convert_mesh.py is not
needed in this case.
Multipart models
Meshing models composed of parts with different material groups is a little bit tricky task. But there are some more or
less general ways of doing that. Here, the method using functions of Gmsh for periodic meshes will be shown.
The screwdriver handle example is extended by adding a screwdriver shank. The new part is composed of a cylinder
trimmed at one end:
1.5. Examples 91
SfePy Documentation, Release version: 2022.1+git.f9873484
The handle and shank are exported to the STEP file as two separated parts:
1 doc.recompute()
2
1 Merge "screwdriver_full.step";
2
92 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
9 Field[1] = MathEval;
10 Field[1].F = "0.0015";
11 Background Field = 1;
where the first pair of periodic surfaces corresponds to the common circle faces (bottom of the shank) and the second
pair to the common cylindrical surfaces. See Gmsh Reference manual for details on periodic meshing.
Using the above stated GEO file, Gmsh creates a mesh containing duplicate vertices on the handle/shank interface.
These duplicate vertices can be removed during the conversion to the VTK format by giving --merge (or just -m)
argument to convert_mesh.py script:
In order to extract the cells by the physical groups use the conversion script with --save-per-mat argument:
It produces screwdriver.vtk contaning the original mesh and screwdriver_matid_1.vtk, screwdriver_matid_2.vtk files
containing only the cells of a given physical group and all vertices of the original mesh.
1 radius = 0.01;
2 height = 0.1;
3 $fn = 50;
4
5 module tip() {
6 rotate([0, -10, 0])
7 translate([0, -radius, -3*radius])
8 cube([radius, 2*radius, 3*radius], center=false);
9 }
10
11 difference() {
12 difference() {
13 difference() {
(continues on next page)
1.5. Examples 93
SfePy Documentation, Release version: 2022.1+git.f9873484
21 }
22 cylinder(center=false, h=height, r=0.3*radius);
23 }
24 for (i = [1:6]) {
25 rotate([0, 0, 360/6*i])
26 translate([-1.1*radius, 0.0, -0.2*height])
27 cylinder(center=false, h=1.1*height, r=0.2*radius);
28 }
29 }
30
31 union() {
32 difference() {
33 translate([0, 0, height])
34 cylinder(center=false, h=height, r=0.3*radius);
35 translate([0, 0, 1.71*height + 3*radius])
36 union() {
37 tip();
38 mirror ([1, 0, 0]) tip();
39 }
40 }
41 cylinder(center=false, h=height, r=0.3*radius);
42 }
1 importCSG.open('screwdriver_full.csg')
2 top_group = FreeCAD.ActiveDocument.Objects[-1]
3 Part.export(top_group.OutList, 'screwdriver_full.step')
Since the different tools for geometry definition have been used, the numbering of geometric objects may differ and the
surface and edge numbers have to be changed in the GEO file:
Note: The numbering of objects may vary between FreeCAD, OpenSCAD and Gmsh versions.
94 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
Introduction
This tutorial shows identification of material parameters of a composite structure using data (force-displacement curves)
obtained by a standard tensile test.
Composite structure
The unidirectional long fiber carbon-epoxy composite is considered. Its microstructure was analysed by the scanning
electron microscopy and the data, volume fractions and fibers cross-sections, were used to generate a periodic finite
element mesh (representative volume element - RVE) representing the random composite structure at the microscopic
level (the random structure generation algorithm is described in1 ):
This RVE is used in the micromechanical FE analysis which is based on the two-scale homogenization method.
Material testing
Several carbon-expoxy specimens with different fiber orientations (0, 30, 60 and 90 degrees) were subjected to the
tensile test in order to obtain force-elongation dependencies, see2 . The slopes of the linearized dependencies were used
in an objective function of the identification process.
1 Lubachevsky B. D., How to Simulate Billiards and Similar Systems, Journal of Computational Physics, 94(2), 1991. http://arxiv.org/PS_cache/
cond-mat/pdf/0503/0503627v2.pdf
2 SrbovΓ‘ H., Kroupa T., ZemΔΓk R., Identification of the Material Parameters of a Unidirectional Fiber Composite Using a Micromodel, Materiali
1.5. Examples 95
SfePy Documentation, Release version: 2022.1+git.f9873484
Numerical simulation
The linear isotropic material model is used for both components (fiber and matrix) of the composite so only four material
parameters (Youngβs modulus and Poissonβs ratio for each component) are necessary to fully describe the mechanical
behavior of the structure.
The numerical simulations of the tensile tests are based on the homogenization method applied to the linear elastic prob-
lem3 . The homogenization procedure results in the microscopic problem solved within the RVE and the macroscopic
problem that involves the homogenized elastic coefficients.
3 Pinho-da-Cruz L., Oliveira J. A. and Teixeira-Dias F., Asymptotic homogenization in linear elasticity. Part I: Mathematical formulation and
96 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
Homogenized coefficients
The problem at the microscopic level is formulated in terms of characteristic response functions and its solution is used
to evaluate the homogenized elasticity tensor. The microscopic problem has to be solved with the periodic boundary
conditions.
The following SfePy description file is used for definition of the microscopic problem: homogenization_opt_src.
In the case of the identification process function get_mat() obtains the material parameters (Youngβs modules, Poissonβs
ratios) from the outer identification loop. Otherwise these parameters are given by values.
Notice the use of parametric_hook (Miscellaneous) to pass around the optimization parameters.
Macroscopic simulation
The homogenized elasticity problem is solved for the unknown macroscopic displacements and the elongation of the
composite specimen is evaluated for a given loading. These values are used to determine the slopes of the calculated
force-elongation dependencies which are required by the objective function.
The SfePy description file for the macroscopic analysis: linear_elasticity_opt_src.
Identification procedure
The identification of material parameters, i.e. the Youngβs modulus and Poissonβs ratio, of the epoxy matrix (πΈπ , ππ )
and carbon fibers (πΈπ , ππ ) can be formulated as a minimization of the following objective function:
(οΈ )οΈ2
π
βοΈ πππππ (x)
Ξ¦(x) = 1β π
, (1.9)
πππ₯π
πβ{0,30,60,90}
where πππππ
π
and πππ₯π
π
are the computed and measured slopes of the force-elongation tangent lines for a given fiber ori-
entation. This function is minimized using scipy.optimize.fmin_tnc(), considering bounds of the identified parameters.
Tho following steps are performed in each iteration of the optimization loop:
1. Solution of the microscopic problem, evaluation of the homogenized elasticity tensor.
2. Solution of the macroscopic problems for different fiber orientations (0, 30, 60, 90), this is incorporated by
appropriate rotation of the elasticity tensor.
3. Evaluation of the objective function.
Python script for material identification: material_opt_src.
Run the script from the command shell as (from the top-level directory of SfePy):
$ python sfepy/examples/homogenization/material_opt.py
The iteration process is monitored using graphs where the values of the objective function and material parameters are
plotted.
1.5. Examples 97
SfePy Documentation, Release version: 2022.1+git.f9873484
The resulting values of πΈπ , ππ , πΈπ , ππ can be found at the end of the script output:
So that:
πΈπ = 171.13 GPa
ππ = 3.21
πΈπ = 2.34 GPa
ππ = 0.20
Note: The results may vary across SciPy versions and related libraries.
Introduction
When dealing with shape optimization we usually need to modify a FE mesh using a few optimization parameters
describing the mesh geometry. The B-spline parametrization offers an efficient way to do that. A mesh region (2D or
3D) that is to be parametrized is enclosed in the so called spline-box and the positions of all vertices inside the box can
be changed by moving the control points of the B-spline curves.
There are two different classes for the B-spline parametrization implemented in SfePy (module sfepy.mesh.
splinebox): SplineBox and SplineRegion2D. The first one defines a rectangular parametrization box in 2D or
3D while the second one allows to set up an arbitrary shaped region of parametrization in 2D.
98 Chapter 1. Documentation
SfePy Documentation, Release version: 2022.1+git.f9873484
SplineBox
the first parameter defines the range of the box in each dimension, the second parameter is the array of coordinates
(vertices) to be parametrized and the last one (optional) determines the number of control points in each dimension.
The number of the control points (πππ) is calculated as:
where ππππππ is the degree of the B-spline curve (default value: 3 = cubic spline) and ππ π is the number of the spline
segments (default value: [1,1(,1)] = 4 control points for all dimensions).
The position of the vertices can be modified by moving the control points:
spb.move_control_point(<cpoint>, <val>)
where <cpoint> is the index or position of the control point, for explanation see the following figure.
The displacement is given by <val>. The modified coordinates of the vertices are evaluated by:
new_coors = spb.evaluate()
Example
β’ Create a new 2D SplineBox with the left bottom corner at [-1,-1] and the right top corner at [1, 0.6] which has
5 control points in x-direction and 4 control points in y-direction:
4 mesh = Mesh.from_file('meshes/2d/square_tri1.mesh')
5 spb = SplineBox([[-1, 1], [-1, 0.6]], mesh.coors, nsg=[2,1])
β’ Modify the position of mesh coordinates by moving three control points (with indices 1,2 and 3):
1.5. Examples 99
SfePy Documentation, Release version: 2022.1+git.f9873484
mesh.cmesh.coors[:] = spb.evaluate()
β’ Write the deformed mesh and the spline control net (the net of control points) into vtk files:
spb.write_control_net('square_tri1_spbox.vtk')
mesh.write('square_tri1_deform.vtk')
The following figures show the undeformed (left) and deformed (right) mesh and the control net.
SplineRegion2D
In this case, the region (only in 2D) of parametrization is defined by four B-spline curves:
The curves must form a closed loop, must be oriented counterclockwise and the opposite curves (<bspl1>, <bspl3>
and <bspl2>, <bspl4>) must have the same number of control points and the same knot vectors, see the figure below,
on the left.
The position of the selected vertices, depicted in the figure on the right, are driven by the control points in the same
way as explained above for SplineBox.
Note: Initializing SplineRegion2D may be time consuming due to the fact that for all vertex coordinates the spline
parameters have to be found using an optimization method in which the B-spline basis is repeatedly evaluated.
Example
β’ First of all, define four B-spline curves (the default degree of the spline curve is 3) representing the boundary of
a parametrization area:
7 sp_l = BSpline()
8 sp_l.approximate(line_l, ncp=4)
9 kn_lr = sp_l.get_knot_vector()
10
11 sp_r = BSpline()
12 sp_r.approximate(line_r, knots=kn_lr)
13
18 sp_b = BSpline()
19 sp_b.approximate(line_b, ncp=5)
20 kn_bt = sp_b.get_knot_vector()
21
22 sp_t = BSpline()
23 sp_t.approximate(line_t, knots=kn_bt)
mesh.cmesh.coors[:] = spb.evaluate()
The figures below show the undeformed (left) and deformed (right) mesh and the control net.
1.5.6 Examples
acoustics
acoustics/acoustics.py
Description
Acoustic pressure distribution.
This example shows how to solve a problem in complex numbers, note the βaccoustic_pressureβ field definition.
Find π such that:
β«οΈ β«οΈ β«οΈ β«οΈ
π2 βπ Β· βπ β π€2 ππ β ππ€π ππ = ππ€π2 ππ£π π, βπ .
Ξ© Ξ© Ξππ’π‘ Ξππ
source code
r"""
Acoustic pressure distribution.
This example shows how to solve a problem in complex numbers, note the
'accoustic_pressure' field definition.
.. math::
c^2 \int_{\Omega} \nabla q \cdot \nabla p
- w^2 \int_{\Omega} q p
- i w c \int_{\Gamma_{out}} q p
= i w c^2 \rho v_n \int_{\Gamma_{in}} q
\;, \quad \forall q \;.
"""
from __future__ import absolute_import
from sfepy import data_dir
options = {
'nls' : 'newton',
'ls' : 'ls',
}
materials = {
'one' : ({'one' : 1.0},),
}
regions = {
'Omega' : 'all',
'Gamma_in' : ('vertices in (x < 0.01)', 'facet'),
'Gamma_out' : ('vertices in (x > 0.99)', 'facet'),
}
fields = {
'accoustic_pressure' : ('complex', 1, 'Omega', 1),
}
variables = {
'p' : ('unknown field', 'accoustic_pressure', 0),
'q' : ('test field', 'accoustic_pressure', 'p'),
}
ebcs = {
}
integrals = {
'i' : 2,
}
equations = {
'Acoustic pressure' :
"""%s * dw_laplace.i.Omega( one.one, q, p )
- %s * dw_dot.i.Omega( q, p )
- %s * dw_dot.i.Gamma_out( q, p )
= %s * dw_integrate.i.Gamma_in( q )"""
% (c*c, w*w, 1j*w*c, 1j*w*c*c*rho*v_n)
}
solvers = {
'ls' : ('ls.scipy_direct', {}),
'newton' : ('nls.newton', {
'i_max' : 1,
'eps_a' : 1e-1,
'eps_r' : 1.0,
'macheps' : 1e-16,
'lin_red' : 1e-1, # Linear system error < (eps_a * lin_red).
(continues on next page)
acoustics/acoustics3d.py
Description
Acoustic pressure distribution in 3D.
Two Laplace equations, one in β¦1 , other in β¦2 , connected on the interface region Ξ12 using traces of variables.
Find two complex acoustic pressures π1 , π2 such that:
β«οΈ β«οΈ
π 2 ππ β βπ Β· βπ
Ξ©
β«οΈ β«οΈ β«οΈ Ξ©
βππ€/π ππ + ππ€π/π π(π2 β π1 ) + ππ€π/π π(π1 β π2 )
Ξππ’π‘ Ξ2
β«οΈ Ξ1
= ππ€π π£π π , βπ .
Ξππ
source code
r"""
Acoustic pressure distribution in 3D.
.. math::
\int_{\Omega} k^2 q p - \int_{\Omega} \nabla q \cdot \nabla p \\
- i w/c \int_{\Gamma_{out}} q p
+ i w \rho/Z \int_{\Gamma_2} q (p_2 - p_1)
+ i w \rho/Z \int_{\Gamma_1} q (p_1 - p_2) \\
= i w \rho \int_{\Gamma_{in}} v_n q
\;, \quad \forall q \;.
"""
freq = 1200
v_n = 1.0 # m/s
c = 343.0 # m/s
rho = 1.55 # kg/m^3
R = 1000
w = 2.0 * freq
k1 = w / c
rhoc1 = rho * c
k2 = k1 * coef_k
rhoc2 = rhoc1 * coef_r
# acoustic impedance
Z = rho * c / por * (0.006 + 1j * k1 * (tw + 0.375 * dh
* (1 + rhoc2/rhoc1 * k2/k1)))
regions = {
'Omega' : 'all',
'Omega_1' : 'cells of group 1',
'Omega_2' : 'cells of group 2',
'Gamma_12' : ('r.Omega_1 *v r.Omega_2', 'facet'),
'Gamma_12_1' : ('copy r.Gamma_12', 'facet', 'Omega_1'),
'Gamma_12_2' : ('copy r.Gamma_12', 'facet', 'Omega_2'),
'Gamma_in' : ('vertices in (z < 0.001)', 'facet'),
'Gamma_out' : ('vertices in (z > 0.157)', 'facet'),
}
materials = {
}
fields = {
'accoustic_pressure_1' : ('complex', 'scalar', 'Omega_1', 1),
'accoustic_pressure_2' : ('complex', 'scalar', 'Omega_2', 1),
}
variables = {
'p_1' : ('unknown field', 'accoustic_pressure_1'),
'q_1' : ('test field', 'accoustic_pressure_1', 'p_1'),
'p_2' : ('unknown field', 'accoustic_pressure_2'),
(continues on next page)
ebcs = {
}
integrals = {
'i' : 2,
}
equations = {
'Acoustic pressure' :
"""%s * dw_dot.i.Omega_1(q_1, p_1)
+ %s * dw_dot.i.Omega_2(q_2, p_2)
- dw_laplace.i.Omega_1(q_1, p_1)
- dw_laplace.i.Omega_2(q_2, p_2)
- %s * dw_dot.i.Gamma_out(q_1, p_1)
+ %s * dw_jump.i.Gamma_12_1(q_1, p_1, tr(p_2))
+ %s * dw_jump.i.Gamma_12_2(q_2, p_2, tr(p_1))
= %s * dw_integrate.i.Gamma_in(q_1)"""
% (k1*k1, k2*k2,
1j*k1,
1j*k1*rhoc1 / Z, 1j*k2*rhoc2 / Z,
1j*k1*rhoc1 * v_n)
}
options = {
'nls': 'newton',
'ls': 'ls',
'file_per_var': True,
}
solvers = {
'ls' : ('ls.scipy_direct', {}),
'newton' : ('nls.newton', {
'i_max' : 1,
'eps_a' : 1e-10,
'eps_r' : 1.0,
'macheps' : 1e-16,
'lin_red' : 1e-1,
'ls_red' : 0.1,
'ls_red_warp' : 0.001,
'ls_on' : 1.1,
'ls_min' : 1e-5,
'check' : 0,
'delta' : 1e-6,
})
}
acoustics/vibro_acoustic3d.py
Description
Vibro-acoustic problem
3D acoustic domain with 2D perforated deforming interface.
Master problem: defined in 3D acoustic domain (vibro_acoustic3d.py)
Slave subproblem: 2D perforated interface (vibro_acoustic3d_mid.py)
Master 3D problem - find π (acoustic pressure) and π (transversal acoustic velocity) such that:
β«οΈ β«οΈ β«οΈ β«οΈ β«οΈ β«οΈ
π2 βπ Β· βπ β π 2 ππ + πππ ππ + πππ ππ β πππ2 (π + β π β )π = 2πππ π πΒ― , βπ ,
Ξ© Ξ© Ξππ Ξππ’π‘ Ξ0 Ξππ
β«οΈ β«οΈ β«οΈ
βππ π (π+ β πβ ) β π 2 πΉ π π + π2 πΆπ π€ = 0 , βπ ,
Ξ0 Ξ0 Ξ0
source code
r"""
Vibro-acoustic problem
.. math::
c^2 \int_{\Omega} \nabla q \cdot \nabla p
- \omega^2 \int_{\Omega} q p
+ i \omega c \int_{\Gamma_{in}} q p
+ i \omega c \int_{\Gamma_{out}} q p
- i \omega c^2 \int_{\Gamma_0} (q^+ - q^-) g
= 2i \omega c \int_{\Gamma_{in}} q \bar{p}
\;, \quad \forall q \;,
.. math::
\omega^2 \int_{\Gamma_0} C z g
- \omega^2 \int_{\Gamma_0} S z w
+ \int_{\Gamma_0} \nabla z \cdot \ull{G} \cdot \nabla w
- \int_{\Gamma_0} \ul{\theta} \cdot \ull{G} \cdot \nabla z
= 0
\;, \quad \forall z \;,
sound_speed = 343.0
wave_num = 5.5
p_inc = 300
c = sound_speed
c2 = c**2
w = wave_num * c
w2 = w**2
wc = w * c
wc2 = w * c2
regions = {
'Omega1': 'cells of group 1',
'Omega2': 'cells of group 2',
'GammaIn': ('vertices of group 1', 'face'),
'GammaOut': ('vertices of group 2', 'face'),
'Gamma_aux': ('r.Omega1 *v r.Omega2', 'face'),
'Gamma0_1': ('copy r.Gamma_aux', 'face', 'Omega1'),
'Gamma0_2': ('copy r.Gamma_aux', 'face', 'Omega2'),
'aux_Left': ('vertices in (x < 0.001)', 'face'),
'aux_Right': ('vertices in (x > 0.299)', 'face'),
'Gamma0_1_Left': ('r.Gamma0_1 *v r.aux_Left', 'edge'),
'Gamma0_1_Right': ('r.Gamma0_1 *v r.aux_Right', 'edge'),
}
(continues on next page)
fields = {
'pressure1': ('complex', 'scalar', 'Omega1', 1),
'pressure2': ('complex', 'scalar', 'Omega2', 1),
'tvelocity': ('complex', 'scalar', 'Gamma0_1', 1),
'deflection': ('complex', 'scalar', 'Gamma0_1', 1),
}
variables = {
'p1': ('unknown field', 'pressure1', 0),
'q1': ('test field', 'pressure1', 'p1'),
'p2': ('unknown field', 'pressure2', 1),
'q2': ('test field', 'pressure2', 'p2'),
'g0': ('unknown field', 'tvelocity', 2),
'f0': ('test field', 'tvelocity', 'g0'),
'w': ('unknown field', 'deflection', 3),
'z': ('test field', 'deflection', 'w'),
}
ebcs = {
'fixed_l': ('Gamma0_1_Left', {'w.0': 0.0}),
'fixed_r': ('Gamma0_1_Right', {'w.0': 0.0}),
}
options = {
'file_per_var': True,
}
functions = {
}
materials = {
'ac' : ({'F': -2.064e+00, 'c': -1.064e+00}, ),
}
equations = {
'eq_1' : """
%e * dw_laplace.5.Omega1(q1, p1)
+ %e * dw_laplace.5.Omega2(q2, p2)
- %e * dw_dot.5.Omega1(q1, p1)
- %e * dw_dot.5.Omega2(q2, p2)
+ %s * dw_dot.5.GammaIn(q1, p1)
+ %s * dw_dot.5.GammaOut(q2, p2)
- %s * dw_dot.5.Gamma0_1(q1, g0)
+ %s * dw_dot.5.Gamma0_2(q2, tr(g0))
= %s * dw_integrate.5.GammaIn(q1)"""\
% (c2, c2, w2, w2,
1j * wc, 1j * wc,
1j * wc2, 1j * wc2,
2j * wc * p_inc),
'eq_2' : """
- %s * dw_dot.5.Gamma0_1(f0, p1)
(continues on next page)
solvers = {
'ls': ('ls.cm_pb',
{'others': [base_dir
+ '/examples/acoustics/vibro_acoustic3d_mid.py'],
'coupling_variables': ['g0', 'w'],
}),
'nls': ('nls.newton', {
'i_max' : 1,
'eps_a' : 1e-6,
'eps_r' : 1e-6,
})
}
dg
dg/advection_1D.py
Description
Transient advection equation in 1D solved using discontinous galerkin method.
ππ
+ π Β· ππ/ππ₯ = 0
ππ‘
π(π‘, 0) = π(π‘, 1)
Usage Examples
To view animated results use script/dg_plot_1D.py specifing name of the output in output/ folder, default is
dg/advection_1D:
source code
r"""
Transient advection equation in 1D solved using discontinous galerkin method.
p(t,0) = p(t,1)
Usage Examples
--------------
Run with simple.py script::
"""
from sfepy.examples.dg.example_dg_common import *
from sfepy.discrete.dg.limiters import MomentLimiter1D
dim = 1
def define(filename_mesh=None,
approx_order=2,
adflux=0.0,
limit=True,
cw=None,
diffcoef=None,
diffscheme="symmetric",
cfl=0.4,
dt=None,
t1=0.1
):
t0 = 0
transient = True
mstart = 0
mend = 1
diffcoef = None
cw = None
example_name = "advection_1D"
dim = 1
if filename_mesh is None:
filename_mesh = get_gen_1D_mesh_hook(0, 1, 100)
materials = {
'a': ({'val': [1.0], '.flux': adflux},),
regions = {
'Omega': 'all',
'Gamma': ('vertices of surface', 'facet'),
'left': ('vertices in x == 0', 'vertex'),
'right': ('vertices in x == 1', 'vertex')
}
fields = {
(continues on next page)
variables = {
'p': ('unknown field', 'f', 0, 1),
'v': ('test field', 'f', 'p'),
}
dgebcs = {
'u_left': ('left', {'p.all': 0}),
'u_righ': ('right', {'p.all': 0}),
}
dgepbc_1 = {
'name' : 'u_rl',
'region': ['right', 'left'],
'dofs': {'p.all': 'p.all'},
'match': 'match_y_line',
}
integrals = {
'i': 2 * approx_order,
}
equations = {
'Advection': """
dw_dot.i.Omega(v, p)
- dw_s_dot_mgrad_s.i.Omega(a.val, p[-1], v)
+ dw_dg_advect_laxfrie_flux.i.Omega(a.flux, a.val, v, p[-1]) = 0
"""
}
solvers = {
"tss": ('ts.tvd_runge_kutta_3',
{"t0" : t0,
"t1" : t1,
'limiters': {"f": MomentLimiter1D} if limit else {}
}),
'nls': ('nls.newton', {}),
'ls' : ('ls.scipy_direct', {})
}
options = {
'ts' : 'tss',
'nls' : 'newton',
'ls' : 'ls',
'save_times' : 100,
'active_only' : False,
'pre_process_hook': get_cfl_setup(cfl)
if dt is None else
get_cfl_setup(dt=dt),
'output_dir' : 'output/dg/' + example_name,
(continues on next page)
functions = {}
def local_register_function(fun):
try:
functions.update({fun.__name__: (fun,)})
return fun
def four_step_p(x):
"""
piecewise constant (-inf, 1.8],(1.8, a + 4](a+4, a + 5](a + 5, inf)
"""
return nm.piecewise(x,
[x <= mstart,
x <= mstart + .4,
mstart + .4 < x,
mstart + .5 <= x],
[0, 0, .5, 0])
@local_register_function
def get_ic(x, ic=None):
return four_step_p(x)
@local_register_function
def sol_fun(ts, coors, mode="qp", **kwargs):
t = ts.time
if mode == "qp":
return {"p": analytic_sol(coors, t)[..., None, None]}
ics = {
'ic': ('Omega', {'p.0': 'get_ic'}),
}
return locals()
globals().update(define())
dg/advection_2D.py
Description
Transient advection equation in 2D solved by discontinous Galerkin method.
ππ
+ π Β· ππππ π = 0
ππ‘
Usage Examples
Results are saved to output/dg/advection_2D folder by default as .msh files, the best way to view them is through GMSH
(http://gmsh.info/) version 4.6 or newer. Start GMSH and use File | Open menu or Crtl + O shortcut, navigate to the
output folder, select all .msh files and hit Open, all files should load as one item in Post-processing named p_cell_nodes.
GMSH is capable of rendering high order approximations in individual elements, to modify fidelity of rendering, double
click the displayed mesh, quick options menu should pop up, click on All view options.... This brings up the
Options window with View [0] selected in left column. Under the tab General ensure that Adapt visualization
grid is ticked, then you can adjust Maximum recursion depth and `Target visualization error to tune the
visualization. To see visualization elements (as opposed to mesh elements) go to Visibility tab and tick Draw
element outlines, this option is also available from quick options menu as View element outlines or under
shortcut Alt+E. In the quick options menu, you can also modify normal raise by clicking View Normal Raise to see
solution rendered as surface above the mesh. Note that for triangular meshes normal raise -1 produces expected raise
above the mesh. This is due to the opposite orientation of the reference elements in GMSH and Sfepy and might get
patched in the future.
source code
r"""
Transient advection equation in 2D solved by discontinous Galerkin method.
Usage Examples
--------------
def define(filename_mesh=None,
approx_order=2,
adflux=0,
limit=True,
cw=None,
diffcoef=None,
diffscheme="symmetric",
cfl=0.4,
dt=None,
t1=0.01
):
example_name = "advection_2D"
dim = 2
diffcoef = None
cw = None
if filename_mesh is None:
filename_mesh = get_gen_block_mesh_hook((1., 1.), (20, 20), (.5, .5))
t0 = 0.
angle = 0
# get_common(approx_order, cfl, t0, t1, None, get_ic)
rotm = nm.array([[nm.cos(angle), -nm.sin(angle)],
(continues on next page)
regions = {
'Omega' : 'all',
'left': ('vertices in x == 0', 'edge'),
'right': ('vertices in x == 1', 'edge'),
'top': ('vertices in y == 1', 'edge'),
'bottom': ('vertices in y == 0', 'edge')
}
fields = {
'f': ('real', 'scalar', 'Omega', str(approx_order) + 'd', 'DG', 'legendre') #
}
variables = {
'p': ('unknown field', 'f', 0, 1),
'v': ('test field', 'f', 'p'),
}
def gsmooth(x):
"""
.. :math: C_0^{\inf}
"""
return .3 * nm.piecewise(x, [x <= 0.1, x >= 0.1, .3 < x],
[0, lambda x:
nm.exp(1 / ((10 * (x - .2)) ** 2 - 1) + 1),
0])
@local_register_function
def sol_fun(ts, coors, mode="qp", **kwargs):
t = ts.time
if mode == "qp":
return {"p": analytic_sol(coors, t)[..., None, None]}
ics = {
'ic': ('Omega', {'p.0': 'get_ic'}),
}
dgepbc_1 = {
'name': 'u_rl',
'region': ['right', 'left'],
'dofs': {'p.all': 'p.all'},
'match': 'match_y_line',
}
integrals = {
'i': 3 * approx_order,
}
equations = {
'Advection': """
dw_dot.i.Omega(v, p)
- dw_s_dot_mgrad_s.i.Omega(a.val, p[-1], v)
+ dw_dg_advect_laxfrie_flux.i.Omega(a.flux, a.val, v, p[-1]) = 0
"""
}
solvers = {
"tss": ('ts.tvd_runge_kutta_3',
{"t0" : t0,
"t1" : t1,
'limiters': {"f": MomentLimiter2D} if limit else {}}),
'nls': ('nls.newton',{}),
'ls' : ('ls.scipy_direct', {})
}
options = {
'ts' : 'tss',
'nls' : 'newton',
'ls' : 'ls',
'save_times' : 100,
'active_only' : False,
'output_dir' : 'output/dg/' + example_name,
'output_format' : 'msh',
'file_format' : 'gmsh-dg',
'pre_process_hook': get_cfl_setup(cfl) if dt is None else get_cfl_setup(dt=dt)
}
return locals()
globals().update(define())
dg/advection_diffusion_2D.py
Description
Static advection-diffusion equation in 2D solved by discontinous Galerkin method.
π Β· ππππ π β πππ£(ππππ π) = 0
Based on
Antonietti, P., & Quarteroni, A. (2013). Numerical performance of discontinuous and stabilized continuous
Galerkin methods for convection-diffusion problems.
Usage Examples
Results are saved to output/dg/advection_diffusion_2D folder by default as ` .msh` files, the best way to view them is
through GMSH (http://gmsh.info/) version 4.6 or newer. Start GMSH and use File | Open menu or Crtl + O shortcut,
navigate to the output folder, select all .msh files and hit Open, all files should load as one item in Post-processing named
p_cell_nodes.
GMSH is capable of rendering high order approximations in individual elements, to modify fidelity of rendering, double
click the displayed mesh, quick options menu should pop up, click on All view options.... This brings up the
Options window with View [0] selected in left column. Under the tab General ensure that Adapt visualization
grid is ticked, then you can adjust Maximum recursion depth and `Target visualization error to tune the
visualization. To see visualization elements (as opposed to mesh elements) go to Visibility tab and tick Draw
element outlines, this option is also available from quick options menu as View element outlines or under
shortcut Alt+E. In the quick options menu, you can also modify normal raise by clicking View Normal Raise to see
solution rendered as surface above the mesh. Note that for triangular meshes normal raise -1 produces expected raise
above the mesh. This is due to the opposite orientation of the reference elements in GMSH and Sfepy and might get
patched in the future.
source code
r"""
Static advection-diffusion equation in 2D solved by discontinous Galerkin method.
Based on
Usage Examples
--------------
def define(filename_mesh=None,
approx_order=3,
adflux=0,
limit=False,
cw=1000,
diffcoef=1,
diffscheme="symmetric",
cfl=None,
dt=None,
):
cfl = None
dt = None
functions = {}
def local_register_function(fun):
try:
functions.update({fun.__name__: (fun,)})
example_name = "advection_diffusion_2D"
dim = 2
if filename_mesh is None:
filename_mesh = get_gen_block_mesh_hook((1., 1.), (20, 20), (.5, .5))
regions = {
'Omega' : 'all',
'left' : ('vertices in x == 0', 'edge'),
'right': ('vertices in x == 1', 'edge'),
'top' : ('vertices in y == 1', 'edge'),
'bottom': ('vertices in y == 0', 'edge')
}
fields = {
'f': ('real', 'scalar', 'Omega', str(approx_order) + 'd', 'DG', 'legendre')
}
variables = {
'p': ('unknown field', 'f', 0),
'v': ('test field', 'f', 'p'),
}
integrals = {
'i': 2 * approx_order,
}
@local_register_function
def bc_funs(ts, coors, bc, problem):
# return 2*coors[..., 1]
t = ts.dt*ts.step
x_1 = coors[..., 0]
x_2 = coors[..., 1]
res = nm.zeros(nm.shape(x_1))
sin = nm.sin
cos = nm.cos
exp = nm.exp
pi = nm.pi
if bc.diff == 0:
if "left" in bc.name:
(continues on next page)
elif bc.diff == 1:
if "left" in bc.name:
res = nm.stack((-2*pi*(x_2**2 - x_2),
res),
axis=-2)
elif "right" in bc.name:
res = nm.stack((-2*pi*(x_2**2 - x_2), res,),
axis=-2)
elif "bot" in bc.name:
res = nm.stack((res,
sin(2*pi*x_1)),
axis=-2)
elif "top" in bc.name:
res = nm.stack((res,
-sin(2*pi*x_1)),
axis=-2)
return res
@local_register_function
def source_fun(ts, coors, mode="qp", **kwargs):
# t = ts.dt * ts.step
eps = diffcoef
sin = nm.sin
cos = nm.cos
exp = nm.exp
sqrt = nm.sqrt
pi = nm.pi
if mode == "qp":
x_1 = coors[..., 0]
x_2 = coors[..., 1]
res = -2*pi*(x_2**2 - x_2)*cos(2*pi*x_1)\
- 2*(2*pi**2*(x_2**2 - x_2)*sin(2*pi*x_1) - sin(2*pi*x_1))*eps\
- (2*x_2 - 1)*sin(2*pi*x_1)
return {"val": res[..., None, None]}
@local_register_function
def sol_fun(ts, coors, mode="qp", **kwargs):
t = ts.time
if mode == "qp":
return {"p": analytic_sol(coors, t)[..., None, None]}
dgebcs = {
'u_left' : ('left', {'p.all': "bc_funs", 'grad.p.all' : "bc_funs"}),
'u_top' : ('top', {'p.all': "bc_funs", 'grad.p.all' : "bc_funs"}),
'u_bot' : ('bottom', {'p.all': "bc_funs", 'grad.p.all' : "bc_funs"}),
'u_right': ('right', {'p.all': "bc_funs", 'grad.p.all' : "bc_funs"}),
}
materials = {
'a' : ({'val': [velo], '.flux': adflux},),
'D' : ({'val': [diffcoef], '.cw': cw},),
'g' : 'source_fun'
}
equations = {
'balance': """
- dw_s_dot_mgrad_s.i.Omega(a.val, p, v)
+ dw_dg_advect_laxfrie_flux.i.Omega(a.flux, a.val, v, p)
"""
+
" + dw_laplace.i.Omega(D.val, v, p) " +
diffusion_schemes_implicit[diffscheme] +
" + dw_dg_interior_penalty.i.Omega(D.val, D.cw, v, p)" +
" - dw_volume_lvf.i.Omega(g.val, v)" +
"= 0"
}
solver_0 = {
'name' : 'ls',
'kind' : 'ls.scipy_direct',
}
solver_1 = {
'name' : 'newton',
'kind' : 'nls.newton',
'i_max' : 5,
'eps_a' : 1e-8,
'eps_r' : 1.0,
'macheps' : 1e-16,
'lin_red' : 1e-2, # Linear system error < (eps_a * lin_red).
'ls_red' : 0.1,
'ls_red_warp' : 0.001,
(continues on next page)
options = {
'nls' : 'newton',
'ls' : 'ls',
'output_dir' : 'output/dg/' + example_name,
'output_format' : 'msh',
'file_format' : 'gmsh-dg'
}
return locals()
globals().update(define())
pass
dg/burgers_2D.py
Description
Burgers equation in 2D solved using discontinous Galerkin method
ππ
+ πππ£ π (π) β πππ£(ππππ π) = 0
ππ‘
Based on
KuΔera, V. (n.d.). Higher order methods for the solution of compressible flows. Charles University. p. 21 eq. (1.39)
Usage Examples
Results are saved to output/dg/burgers_2D folder by default as .msh files, the best way to view them is through GMSH
(http://gmsh.info/) version 4.6 or newer. Start GMSH and use File | Open menu or Crtl + O shortcut, navigate to the
output folder, select all .msh files and hit Open, all files should load as one item in Post-processing named p_cell_nodes.
GMSH is capable of rendering high order approximations in individual elements, to modify fidelity of rendering, double
click the displayed mesh, quick options menu should pop up, click on All view options.... This brings up the
Options window with View [0] selected in left column. Under the tab General ensure that Adapt visualization
grid is ticked, then you can adjust Maximum recursion depth and `Target visualization error to tune the
visualization. To see visualization elements (as opposed to mesh elements) go to Visibility tab and tick Draw
element outlines, this option is also available from quick options menu as View element outlines or under
shortcut Alt+E. In the quick options menu, you can also modify normal raise by clicking View Normal Raise to see
solution rendered as surface above the mesh. Note that for triangular meshes normal raise -1 produces expected raise
above the mesh. This is due to the opposite orientation of the reference elements in GMSH and Sfepy and might get
patched in the future.
source code
r"""
Burgers equation in 2D solved using discontinous Galerkin method
Based on
KuΔera, V. (n.d.). Higher order methods for the solution of compressible flows.
Charles University. p. 21 eq. (1.39)
Usage Examples
--------------
mesh_center = (0, 0)
mesh_size = (2, 2)
def define(filename_mesh=None,
approx_order=2,
(continues on next page)
adflux=0,
limit=False,
cw=10,
diffcoef=0.002,
diffscheme="symmetric",
cfl=None,
dt=1e-5,
t1=0.01
):
functions = {}
def local_register_function(fun):
try:
functions.update({fun.__name__: (fun,)})
return fun
example_name = "burgers_2D"
dim = 2
if filename_mesh is None:
filename_mesh = data_dir + "/meshes/2d/square_tri2.mesh"
t0 = 0.
if dt is None and cfl is None:
dt = 1e-5
angle = 0 # - nm.pi / 5
rotm = nm.array([[nm.cos(angle), -nm.sin(angle)],
[nm.sin(angle), nm.cos(angle)]])
velo = nm.sum(rotm.T * nm.array(velo), axis=-1)[:, None]
burg_velo = velo.T / nm.linalg.norm(velo)
regions = {
'Omega': 'all',
'left' : ('vertices in x == -1', 'edge'),
'right': ('vertices in x == 1', 'edge'),
'top' : ('vertices in y == 1', 'edge'),
'bottom': ('vertices in y == -1', 'edge')
}
fields = {
(continues on next page)
variables = {
'p': ('unknown field', 'f', 0, 1),
'v': ('test field', 'f', 'p'),
}
integrals = {
'i': 5,
}
@local_register_function
def sol_fun(ts, coors, mode="qp", **kwargs):
t = ts.time
if mode == "qp":
return {"p": analytic_sol(coors, t)[..., None, None]}
@local_register_function
def bc_funs(ts, coors, bc, problem):
# return 2*coors[..., 1]
t = ts.dt*ts.step
x_1 = coors[..., 0]
x_2 = coors[..., 1]
sin = nm.sin
cos = nm.cos
exp = nm.exp
if bc.diff == 0:
if "left" in bc.name:
res = -(exp(-t) - 1)*(sin(-5*x_2) + sin(8*x_2 - 4))
elif "bottom" in bc.name:
res = -(exp(-t) - 1) * (sin(-5 * x_1) + sin(8 * x_1 - 4))
elif "right" in bc.name:
res = -(exp(-t) - 1)*(sin(4) + sin(5*x_2))
elif "top" in bc.name:
res = -(exp(-t) - 1)*(sin(4) + sin(5*x_1))
elif bc.diff == 1:
if "left" in bc.name:
res = nm.stack(((4*(x_2 - 1)*cos(4) - 5*x_2*cos(5*x_2))*
(exp(-t) - 1),
-5*(exp(-t) - 1)*cos(5*x_2)),
(continues on next page)
return res
@local_register_function
def source_fun(ts, coors, mode="qp", **kwargs):
if mode == "qp":
t = ts.dt * ts.step
x_1 = coors[..., 0]
x_2 = coors[..., 1]
sin = nm.sin
cos = nm.cos
exp = nm.exp
res = (
+ (5 * x_1 * cos(5 * x_1 * x_2)
- 4 * (x_1 - 1) * cos(4 * x_1 * x_2 - 4 * x_1 - 4 * x_2)) *
(exp(-t) - 1) ** 2 * (sin(5 * x_1 * x_2)
- sin(4 * x_1 * x_2 - 4 * x_1 - 4 * x_2))
+ (5 * x_2 * cos(5 * x_1 * x_2)
- 4 * (x_2 - 1) * cos(4 * x_1 * x_2 - 4 * x_1 - 4 * x_2)) *
(exp(-t) - 1) ** 2 * (sin(5 * x_1 * x_2)
- sin(4 * x_1 * x_2 - 4 * x_1 - 4 * x_2))
- diffcoef *
((25 * x_1 ** 2 * sin(5 * x_1 * x_2) - 16 * (x_1 - 1) ** 2 *
sin(4 * x_1 * x_2 - 4 * x_1 - 4 * x_2)) * (exp(-t) - 1)
+ (25 * x_2 ** 2 * sin(5 * x_1 * x_2) - 16 * (x_2 - 1) ** 2 *
sin(4 * x_1 * x_2 - 4 * x_1 - 4 * x_2)) * (exp(-t) - 1))
+ (sin(5 * x_1 * x_2) - sin(4 * x_1 * x_2 - 4 * x_1 - 4 * x_2))*
exp(-t)
)
return {"val": res[..., None, None]}
def adv_fun(p):
vu = velo.T * p[..., None]
return vu
def burg_fun(p):
vu = .5*burg_velo * p[..., None] ** 2
return vu
def burg_fun_d(p):
v1 = burg_velo * p[..., None]
return v1
materials = {
'a' : ({'val': [velo], '.flux':adflux},),
'D' : ({'val': [diffcoef], '.Cw': cw},),
'g' : 'source_fun'
}
ics = {
'ic': ('Omega', {'p.0': 0}),
}
dgebcs = {
'u_left' : ('left', {'p.all': 'bc_funs', 'grad.p.all': 'bc_funs'}),
'u_right' : ('right', {'p.all': 'bc_funs', 'grad.p.all': 'bc_funs'}),
'u_bottom' : ('bottom', {'p.all': 'bc_funs', 'grad.p.all': 'bc_funs'}),
'u_top' : ('top', {'p.all': 'bc_funs', 'grad.p.all': 'bc_funs'}),
}
equations = {
'balance':
"dw_dot.i.Omega(v, p)" +
# non-linear hyperbolic terms
" - dw_ns_dot_grad_s.i.Omega(burg_fun, burg_fun_d, p[-1], v)" +
" + dw_dg_nonlinear_laxfrie_flux.i.Omega(a.flux, burg_fun, burg_fun_d, v, p[-1])
Λβ" +
# diffusion
" + dw_laplace.i.Omega(D.val, v, p[-1])" +
diffusion_schemes_explicit[diffscheme] +
" - dw_dg_interior_penalty.i.Omega(D.val, D.Cw, v, p[-1])"
# source
+ " - dw_volume_lvf.i.Omega(g.val, v)"
" = 0"
}
solvers = {
"tss.tvd_runge_kutta_3": ('ts.tvd_runge_kutta_3',
{"t0": t0,
"t1": t1,
'limiters': {
"f": MomentLimiter2D} if limit else {}}),
(continues on next page)
options = {
'ts' : 'tss.euler',
'nls' : 'nls.newton',
'ls' : 'ls.mumps',
'save_times' : 100,
'output_dir' : 'output/dg/' + example_name,
'output_format' : 'msh',
'file_format' : 'gmsh-dg',
'pre_process_hook': get_cfl_setup(CFL=cfl, dt=dt)
}
return locals()
globals().update(define())
dg/example_dg_common.py
Description
Functions common to DG examples
source code
"""
Functions common to DG examples
"""
import os
from glob import glob
import numpy as nm
diffusion_schemes_implicit = {
"symmetric":
" + dw_dg_diffusion_flux.i.Omega(D.val, p, v)"
+ " + dw_dg_diffusion_flux.i.Omega(D.val, v, p)",
"non-symmetric":
" + dw_dg_diffusion_flux.i.Omega(D.val, p, v)"
+ " - dw_dg_diffusion_flux.i.Omega(D.val, v, p)",
(continues on next page)
diffusion_schemes_explicit = {
"symmetric":
" - dw_dg_diffusion_flux.i.Omega(D.val, p[-1], v)"
+ " - dw_dg_diffusion_flux.i.Omega(D.val, v, p[-1])",
"non-symmetric":
" - dw_dg_diffusion_flux.i.Omega(D.val, p[-1], v)"
+ " + dw_dg_diffusion_flux.i.Omega(D.val, v, p[-1])",
"incomplete":
" - dw_dg_diffusion_flux.i.Omega(D.val, p[-1], v)"}
functions = {}
def local_register_function(fun):
try:
functions.update({fun.__name__: (fun,)})
return fun
Parameters
----------
CFL : float, optional
dt: float, optional
Returns
-------
setup_cfl_condition : callable
expects sfepy.discrete.problem as argument
"""
def setup_cfl_condition(problem):
"""
Sets up CFL condition for problem ts_conf in problem
Parameters
----------
(continues on next page)
try:
# make this more general?
# maybe require material name in parameter
diffusion = problem.conf_materials['material_D__0'].values["val"]
max_diffusion = nm.max(nm.linalg.norm(diffusion))
except KeyError:
max_diffusion = None
dx = nm.min(problem.domain.mesh.cmesh.get_volumes(dim))
if dt is None:
adv_dt = get_cfl_advection(max_velo, dx, approx_order, CFL)
diff_dt = get_cfl_diffusion(max_diffusion, dx, approx_order, CFL)
_dt = min(adv_dt, diff_dt)
else:
output("CFL coefficient {0} ignored, dt specified directly"
.format(CFL))
_dt = dt
ts_conf.dt = _dt
ts_conf.n_step = tn
ts_conf.cour = max_velo * dtdx
output("Time divided into {0} nodes, {1} steps, step size is {2}"
.format(tn - 1, tn, _dt))
output("Courant number c = max(norm(a)) * dt/dx = {0}"
(continues on next page)
return setup_cfl_condition
Parameters
----------
max_velo : float
dx : float
approx_order : int
CFL : CFL
Returns
-------
dt : float
"""
order_corr = 1. / (2 * approx_order + 1)
if not (nm.isfinite(dt)):
dt = 1
output(("CFL advection: CFL coefficient was {0} " +
"and order correction 1/{1} = {2}")
.format(CFL, (2 * approx_order + 1), order_corr))
output("CFL advection: resulting dt={}".format((dt)))
return dt
Parameters
----------
max_diffusion : float
dx : float
approx_order : int
CFL : float
do_order_corr : bool
Returns
-------
dt : float
"""
(continues on next page)
if do_order_corr:
order_corr = 1. / (2 * approx_order + 1)
else:
order_corr = 1
if not (nm.isfinite(dt)):
dt = 1
output(("CFL diffusion: CFL coefficient was {0} " +
"and order correction 1/{1} = {2}")
.format(CFL, (2 * approx_order + 1), order_corr))
output("CFL diffusion: resulting dt={}".format(dt))
return dt
Parameters
----------
XS : float
leftmost coordinate
XE : float
rightmost coordinate
n_nod : int
number of nodes, number of cells is then n_nod - 1
Returns
-------
mio : UserMeshIO instance
"""
def mesh_hook(mesh, mode):
"""
Generate the 1D mesh.
"""
if mode == 'read':
mio = UserMeshIO(mesh_hook)
return mio
Parameters
----------
dims : array of 2 or 3 floats
Dimensions of the block.
shape : array of 2 or 3 ints
Shape (counts of nodes in x, y, z) of the block mesh.
centre : array of 2 or 3 floats
Centre of the block.
mat_id : int, optional
The material id of all elements.
name : string
Mesh name.
verbose : bool
If True, show progress of the mesh generation.
Returns
-------
mio : UserMeshIO instance
"""
def mesh_hook(mesh, mode):
"""
Generate the 1D mesh.
"""
if mode == 'read':
mio = UserMeshIO(mesh_hook)
return mio
Parameters
----------
clear_format : str
confirm : bool
(continues on next page)
Returns
-------
deleted_anything :
True if there was something to delete
"""
files = glob(clear_format)
if confirm:
for file in files:
output("Will delete file {}".format(file))
doit = input("--------------\nDelete files [Y/n]? ").strip() == "Y"
if doit:
for file in files:
os.remove(file)
return bool(files)
dg/imperative_burgers_1D.py
Description
Burgers equation in 1D solved using discontinous Galerkin method
source code
#!/usr/bin/env python
"""
Burgers equation in 1D solved using discontinous Galerkin method
"""
import argparse
import sys
sys.path.append('.')
from os.path import join as pjoin
import numpy as nm
# sfepy imports
from sfepy.base.base import IndexedStruct
from sfepy.base.base import Struct, configure_output, output
from sfepy.discrete import (FieldVariable, Material, Integral, Function,
Equation, Equations, Problem)
from sfepy.discrete.conditions import InitialCondition, EssentialBC, Conditions
from sfepy.discrete.dg.fields import DGField
from sfepy.discrete.dg.limiters import MomentLimiter1D
from sfepy.discrete.fem import FEDomain
from sfepy.solvers.ls import ScipyDirect
(continues on next page)
def parse_args(argv=None):
if argv is None:
argv = sys.argv
parser = argparse.ArgumentParser(
description='Solve Burgers equation and display animated results, '
'change script code to modify the problem.',
epilog='(c) 2019 T. Zitka , Man-machine Interaction at NTC UWB')
parser.add_argument('-o', '--output-dir', default='.',
help='output directory')
parser.add_argument('-p', '--plot',
action='store_true', dest='plot',
default=False, help='plot animated results')
options = parser.parse_args(argv[1:])
return options
def main(argv=None):
options = parse_args(argv=argv)
# vvvvvvvvvvvvvvvv #
approx_order = 2
# ^^^^^^^^^^^^^^^^ #
domain_name = "domain_1D"
problem_name = "iburgers_1D"
output_folder = pjoin(outputs_folder, problem_name, str(approx_order))
output_format = "vtk"
save_timestn = 100
clear_folder(pjoin(output_folder, "*." + output_format))
configure_output({'output_screen': True,
'output_log_name':
pjoin(output_folder,
f"last_run_{problem_name}_{approx_order}.txt")})
# ------------
# | Get mesh |
# ------------
X1 = 0.
XN = 1.
n_nod = 100
n_el = n_nod - 1
mesh = get_gen_1D_mesh_hook(X1, XN, n_nod).read(None)
# -----------------------------
# | Create problem components |
(continues on next page)
velo = nm.array(1.0)
def adv_fun(u):
vu = velo.T * u[..., None]
return vu
def adv_fun_d(u):
v1 = velo.T * nm.ones(u.shape + (1,))
return v1
def burg_fun(u):
vu = burg_velo * u[..., None] ** 2
return vu
def burg_fun_d(u):
v1 = 2 * burg_velo * u[..., None]
return v1
# ------------------------------
# | Create boundary conditions |
# ------------------------------
left_fix_u = EssentialBC('left_fix_u', left, {'u.all': 1.0})
right_fix_u = EssentialBC('right_fix_u', right, {'u.all': 0.0})
# ----------------------------
# | Create initial condition |
# ----------------------------
def ghump(x):
"""
Nice gaussian.
"""
return nm.exp(-200 * x ** 2)
# ------------------
# | Create problem |
# ------------------
pb = Problem(problem_name,
equations=eqs,
conf=Struct(options={"save_times": save_timestn},
ics={}, ebcs={}, epbcs={}, lcbcs={},
materials={}),
active_only=False)
pb.setup_output(output_dir=output_folder, output_format=output_format)
pb.set_ics(Conditions([ics]))
# ------------------
# | Create limiter |
# ------------------
limiter = MomentLimiter1D
# ---------------------------
# | Set time discretization |
(continues on next page)
# ------------------
# | Create solver |
# ------------------
ls = ScipyDirect({})
nls_status = IndexedStruct()
nls = Newton({'is_linear': True}, lin_solver=ls, status=nls_status)
tss = TVDRK3StepSolver(tss_conf,
nls=nls, context=pb, verbose=True)
# ---------
# | Solve |
# ---------
pb.set_solver(tss)
state_end = pb.solve()
# ----------
# | Plot 1D|
# ----------
if options.plot:
(continues on next page)
if __name__ == '__main__':
main()
dg/laplace_2D.py
Description
Laplace equation solved in 2d by discontinous Galerkin method
βπππ£(ππππ π) = 0
on rectangle
p = 0 p_y = 0
[0,b]ββββββββββ[a, b]
|
|
p_x = -a | p(x,y) | p_x = 0 p = 0 | | p = 0
|
[0,0]ββββββββββ[a, 0] p_y = b p = 0
solution to this is
Usage Examples
Results are saved to output/dg/laplace_2D folder by default as .msh files, the best way to view them is through GMSH
(http://gmsh.info/) version 4.6 or newer. Start GMSH and use File | Open menu or Crtl + O shortcut, navigate to the
output folder, select all .msh files and hit Open, all files should load as one item in Post-processing named p_cell_nodes.
GMSH is capable of rendering high order approximations in individual elements, to modify fidelity of rendering, double
click the displayed mesh, quick options menu should pop up, click on All view options.... This brings up the
Options window with View [0] selected in left column. Under the tab General ensure that Adapt visualization
grid is ticked, then you can adjust Maximum recursion depth and `Target visualization error to tune the
visualization. To see visualization elements (as opposed to mesh elements) go to Visibility tab and tick Draw
element outlines, this option is also available from quick options menu as View element outlines or under
shortcut Alt+E. In the quick options menu, you can also modify normal raise by clicking View Normal Raise to see
solution rendered as surface above the mesh. Note that for triangular meshes normal raise -1 produces expected raise
above the mesh. This is due to the opposite orientation of the reference elements in GMSH and Sfepy and might get
patched in the future.
source code
r"""
Laplace equation solved in 2d by discontinous Galerkin method
.. math:: - div(grad\,p) = 0
on rectangle
p = 0
p_y = 0
[0,b]-----------------------------[a, b]
| |
| |
p_x = -a | p(x,y) | p_x = 0
p = 0 | | p = 0
| |
[0,0]-----------------------------[a, 0]
p_y = b
p = 0
solution to this is
.. math:: p(x,y) = 1/2*x**2 - 1/2*y**2 - a*x + b*y
Usage Examples
--------------
def define(filename_mesh=None,
approx_order=2,
adflux=None,
limit=False,
cw=100,
diffcoef=1,
diffscheme="symmetric",
cfl=None,
dt=None,
):
cfl = None
dt = None
functions = {}
def local_register_function(fun):
try:
functions.update({fun.__name__: (fun,)})
return fun
example_name = "laplace_2D"
dim = 2
if filename_mesh is None:
filename_mesh = get_gen_block_mesh_hook((1., 1.), (16, 16), (.5, .5))
a = 1
b = 1
c = 0
regions = {
'Omega' : 'all',
'left' : ('vertices in x == 0', 'edge'),
'right': ('vertices in x == 1', 'edge'),
'top' : ('vertices in y == 1', 'edge'),
'bottom': ('vertices in y == 0', 'edge')
}
fields = {
'f': ('real', 'scalar', 'Omega', str(approx_order) + 'd', 'DG', 'legendre') #
}
variables = {
(continues on next page)
@local_register_function
def sol_fun(ts, coors, mode="qp", **kwargs):
t = ts.time
if mode == "qp":
return {"p": analytic_sol(coors, t)[..., None, None]}
@local_register_function
def bc_funs(ts, coors, bc, problem):
t = ts.time
x_1, x_2 = coors[..., 0], coors[..., 1]
res = nm.zeros(x_1.shape)
if bc.diff == 0:
res[:] = analytic_sol(coors, t)
elif bc.diff == 1:
res = nm.stack((x_1 - a, -x_2 + b),
axis=-2)
return res
materials = {
'D' : ({'val': [diffcoef], '.Cw': cw},),
}
dgebcs = {
'u_left' : ('left', {'p.all': "bc_funs", 'grad.p.all': "bc_funs"}),
'u_right' : ('right', {'p.all': "bc_funs", 'grad.p.all': "bc_funs"}),
'u_bottom' : ('bottom', {'p.all': "bc_funs", 'grad.p.all': "bc_funs"}),
'u_top' : ('top', {'p.all': "bc_funs", 'grad.p.all': "bc_funs"}),
integrals = {
'i': 2 * approx_order,
}
equations = {
'laplace': " dw_laplace.i.Omega(D.val, v, p) " +
diffusion_schemes_implicit[diffscheme] +
" + dw_dg_interior_penalty.i.Omega(D.val, D.Cw, v, p)" +
" = 0"
}
solver_1 = {
'name' : 'newton',
'kind' : 'nls.newton',
# 'i_max' : 5,
# 'eps_a' : 1e-8,
# 'eps_r' : 1.0,
# 'macheps' : 1e-16,
# 'lin_red' : 1e-2, # Linear system error < (eps_a * lin_red).
# 'ls_red' : 0.1,
# 'ls_red_warp' : 0.001,
# 'ls_on' : 0.99999,
# 'ls_min' : 1e-5,
# 'check' : 0,
# 'delta' : 1e-6,
}
options = {
'nls' : 'newton',
'ls' : 'ls',
'output_dir' : 'output/dg/' + example_name,
'output_format' : 'msh',
'file_format' : 'gmsh-dg',
# 'pre_process_hook': get_cfl_setup(cfl)
}
return locals()
globals().update(define())
diffusion
diffusion/cube.py
Description
Laplace equation (e.g. temperature distribution) on a cube geometry with different boundary condition values on the
cube sides. This example was used to create the SfePy logo.
Find π such that:
β«οΈ
πβπ Β· βπ = 0 , βπ .
Ξ©
source code
r"""
Laplace equation (e.g. temperature distribution) on a cube geometry with
different boundary condition values on the cube sides. This example was
used to create the SfePy logo.
.. math::
\int_{\Omega} c \nabla s \cdot \nabla T
= 0
\;, \quad \forall s \;.
"""
from __future__ import absolute_import
from sfepy import data_dir
############# Laplace.
material_1 = {
(continues on next page)
field_1 = {
'name' : 'temperature',
'dtype' : 'real',
'shape' : (1,),
'region' : 'Omega',
'approx_order' : 1,
}
if filename_mesh.find('cube_medium_hexa.mesh') >= 0:
region_1000 = {
'name' : 'Omega',
'select' : 'cells of group 0',
}
integral_1 = {
'name' : 'i',
'order' : 1,
}
solver_0 = {
'name' : 'ls',
'kind' : 'ls.scipy_direct',
}
'method' : 'cg',
'i_max' : 1000,
'eps_r' : 1e-12,
}
variable_1 = {
'name' : 'T',
'kind' : 'unknown field',
'field' : 'temperature',
'order' : 0, # order in the global vector of unknowns
}
(continues on next page)
variable_2 = {
'name' : 's',
'kind' : 'test field',
'field' : 'temperature',
'dual' : 'T',
}
region_0 = {
'name' : 'Surface',
'select' : 'vertices of surface',
'kind' : 'facet',
}
region_1 = {
'name' : 'Bottom',
'select' : 'vertices in (z < -0.4999999)',
'kind' : 'facet',
}
region_2 = {
'name' : 'Top',
'select' : 'vertices in (z > 0.4999999)',
'kind' : 'facet',
}
region_03 = {
'name' : 'Left',
'select' : 'vertices in (x < -0.4999999)',
'kind' : 'facet',
}
ebc_1 = {
'name' : 'T0',
'region' : 'Surface',
'dofs' : {'T.0' : -3.0},
}
ebc_4 = {
'name' : 'T1',
'region' : 'Top',
'dofs' : {'T.0' : 1.0},
}
ebc_3 = {
'name' : 'T2',
'region' : 'Bottom',
'dofs' : {'T.0' : -1.0},
}
ebc_2 = {
'name' : 'T3',
'region' : 'Left',
'dofs' : {'T.0' : 2.0},
}
equations = {
'nice_equation' : """dw_laplace.i.Omega( coef.val, s, T ) = 0""",
(continues on next page)
solver_1 = {
'name' : 'newton',
'kind' : 'nls.newton',
'i_max' : 1,
'eps_a' : 1e-10,
'eps_r' : 1.0,
'macheps' : 1e-16,
'lin_red' : 1e-2, # Linear system error < (eps_a * lin_red).
'ls_red' : 0.1,
'ls_red_warp' : 0.001,
'ls_on' : 1.1,
'ls_min' : 1e-5,
'check' : 0,
'delta' : 1e-6,
}
diffusion/darcy_flow_multicomp.py
Description
Each of the two equations describes a flow in one compartment of a porous medium. The equations are based on the
Darcy flow and the i-th compartment is defined in β¦π .
β«οΈ β«οΈ βοΈ β«οΈ
πΎ π βππ Β· βπ π + Β― π ππ β ππ π π = π π ππ ,
(οΈ )οΈ
πΊπΌ
Ξ©π Ξ©π π Ξ©π
βπ π β ππ , π, π = 1, 2 and π ΜΈ= π,
where πΎ is the local permeability of the i-th compartment, πΊπΌ
π Β― π = πΊπ is the perfusion coefficient related to the
π
compartments π and π, π π are sources or sinks which represent the external flow into the i-th compartment and ππ is the
pressure in the i-th compartment.
source code
r"""
Each of the two equations describes a flow in one compartment of a porous
medium. The equations are based on the Darcy flow and the i-th compartment is
defined in :math:`\Omega_{i}`.
.. math::
\int_{\Omega_{i}} K^{i} \nabla p^{i} \cdot \nabla q^{i}+\int_{\Omega_{i}}
\sum_{j} \bar{G}\alpha_{k} \left( p^{i}-p^{j} \right)q^{i}
= \int_{\Omega_{i}} f^{i} q^{i},
.. math::
\forall q^{i} \in Q^{i}, \quad i,j=1,2 \quad \mbox{and} \quad i\neq j,
materials = {
'mat': ('mat_fun')
}
regions = {
'Omega': 'cells of group 0',
'Sigma_1': ('vertex 0', 'vertex'),
'Omega1': ('copy r.Omega', 'cell', 'Omega'),
'Omega2': ('copy r.Omega', 'cell', 'Omega'),
'Source': 'cell 24',
'Sink': 'cell 1',
}
fields = {
'pressure':('real', 1, 'Omega', 1)
}
variables = {
'p1': ('unknown field', 'pressure'),
'q1': ('test field', 'pressure', 'p1'),
'p2': ('unknown field', 'pressure'),
'q2': ('test field', 'pressure', 'p2'),
}
ebcs = {
'P1': ('Sigma_1', {'p1.0' : 0.0}),
}
equations = {
'komp1': """dw_diffusion.5.Omega1(mat.K, q1, p1)
+ dw_dot.5.Omega1(mat.G_alfa, q1, p1)
- dw_dot.5.Omega1(mat.G_alfa, q1, p2)
= dw_integrate.5.Source(mat.f_1, q1)""",
solvers = {
'ls': ('ls.scipy_direct', {}),
'newton': ('nls.newton',
{'i_max' : 1,
(continues on next page)
return out
functions = {
'mat_fun': (mat_fun,),
}
options = {
'post_process_hook': 'postproc',
}
diffusion/laplace_1d.py
Description
Laplace equation in 1D with a variable coefficient.
Because the mesh is trivial in 1D, it is generated by mesh_hook(), and registered using UserMeshIO.
Find π‘ such that:
β«οΈ
dπ dπ‘
π(π₯) =0, βπ ,
Ξ© dπ₯ dπ₯
source code
r"""
Laplace equation in 1D with a variable coefficient.
.. math::
\int_{\Omega} c(x) \tdiff{s}{x} \tdiff{t}{x}
= 0
\;, \quad \forall s \;,
filename_mesh = UserMeshIO(mesh_hook)
materials = {
'coef' : 'get_coef',
}
functions = {
'get_coef' : (get_coef,),
}
regions = {
'Omega' : 'all',
'Gamma_Left' : ('vertices in (x < 0.00001)', 'facet'),
'Gamma_Right' : ('vertices in (x > 0.99999)', 'facet'),
}
(continues on next page)
fields = {
'temperature' : ('real', 1, 'Omega', 1),
}
variables = {
't' : ('unknown field', 'temperature', 0),
's' : ('test field', 'temperature', 't'),
}
ebcs = {
't1' : ('Gamma_Left', {'t.0' : 0.3}),
't2' : ('Gamma_Right', {'t.0' : -0.3}),
}
integrals = {
'i' : 2,
}
equations = {
'Temperature' : """dw_laplace.i.Omega(coef.val, s, t) = 0"""
}
solvers = {
'ls' : ('ls.scipy_direct', {}),
'newton' : ('nls.newton', {
'i_max' : 1,
'eps_a' : 1e-10,
}),
}
options = {
'nls' : 'newton',
'ls' : 'ls',
}
diffusion/laplace_coupling_lcbcs.py
Description
Two Laplace equations with multiple linear combination constraints.
The two equations are coupled by a periodic-like boundary condition constraint with a shift, given as a non-
homogeneous linear combination boundary condition.
where π(π₯), π(π¦) are given functions (shifts), π is the periodic coordinate mapping and π11 , π12 and π2 are unknown
constant values - the unknown DOFs in β¦π11 , β¦π12 and β¦π2 are replaced by the integral mean values.
View the results using:
source code
r"""
Two Laplace equations with multiple linear combination constraints.
.. math::
\int_{\Omega_1} \nabla v_1 \cdot \nabla u_1
= 0
\;, \quad \forall v_1 \;,
options = {
(continues on next page)
return val
return val
functions = {
'get_shift1' : (get_shift1,),
'get_shift2' : (get_shift2,),
'match_y_line' : (per.match_y_line,),
'match_x_line' : (per.match_x_line,),
}
fields = {
'scalar1': ('real', 1, 'Omega1', 1),
'scalar2': ('real', 1, 'Omega2', 1),
}
materials = {
}
variables = {
'u1' : ('unknown field', 'scalar1', 0),
'v1' : ('test field', 'scalar1', 'u1'),
'u2' : ('unknown field', 'scalar2', 1),
'v2' : ('test field', 'scalar2', 'u2'),
}
regions = {
'Omega1' : 'cells of group 1',
'Omega2' : 'cells of group 2',
'Omega_m1' : 'r.Omega1 -v (r.Gamma +s vertices of surface)',
'Omega_m11' : 'r.Omega_m1 *v vertices in (x < 0)',
'Omega_m12' : 'r.Omega_m1 *v vertices in (x > 0)',
'Omega_m2' : 'r.Omega2 -v (r.Gamma +s vertices of surface)',
'Left' : ('vertices in (x < -0.499)', 'facet'),
'Right' : ('vertices in (x > 0.499)', 'facet'),
'Bottom' : ('vertices in ((y < -0.499))', 'facet'),
'Top' : ('vertices in ((y > 0.499))', 'facet'),
'Gamma' : ('r.Omega1 *v r.Omega2 -v vertices of surface', 'facet'),
'Gamma1' : ('copy r.Gamma', 'facet', 'Omega1'),
'Gamma2' : ('copy r.Gamma', 'facet', 'Omega2'),
}
(continues on next page)
ebcs = {
'fix1' : ('Top', {'u2.all' : 1.0}),
'fix2' : ('Bottom', {'u1.all' : 0.0}),
}
lcbcs = {
'shifted1' : (('Gamma1', 'Gamma2'),
{'u1.all' : 'u2.all'},
'match_x_line', 'shifted_periodic',
'get_shift1'),
'shifted2' : (('Left', 'Right'),
{'u1.all' : 'u1.all'},
'match_y_line', 'shifted_periodic',
'get_shift2'),
'mean11' : ('Omega_m11', {'u1.all' : None}, None, 'integral_mean_value'),
'mean12' : ('Omega_m12', {'u1.all' : None}, None, 'integral_mean_value'),
'mean2' : ('Omega_m2', {'u2.all' : None}, None, 'integral_mean_value'),
}
integrals = {
'i1' : 2,
}
equations = {
'eq1' : """
dw_laplace.i1.Omega1(v1, u1) = 0
""",
'eq2' : """
dw_laplace.i1.Omega2(v2, u2) = 0
""",
}
solvers = {
'ls' : ('ls.scipy_direct', {}),
'newton' : ('nls.newton', {
'i_max' : 1,
'eps_a' : 1e-10,
}),
}
diffusion/laplace_fluid_2d.py
Description
A Laplace equation that models the flow of βdry waterβ around an obstacle shaped like a Citroen CX.
Description
β Β· βπ = βπ = 0
where π£ 0 is the 2D vector defining the far field velocity that generates the incompressible flow.
Since the value of the potential is defined up to a constant value, a Dirichlet boundary condition is set at a single vertex
to avoid having a singular matrix.
Usage examples
This example can be run with the simple.py script with the following:
source code
r"""
A Laplace equation that models the flow of "dry water" around an obstacle
shaped like a Citroen CX.
Description
-----------
.. math::
(continues on next page)
The weak formulation for this problem is to find :math:`\phi` such that:
.. math::
\int_{\Omega} \nabla \psi \cdot \nabla \phi
= \int_{\Gamma_{left}} \ul{v}_0 \cdot n \, \psi
+ \int_{\Gamma_{right}} \ul{v}_0 \cdot n \, \psi
+ \int_{\Gamma_{top}} \ul{v}_0 \cdot n \,\psi
+ \int_{\Gamma_{bottom}} \ul{v}_0 \cdot n \, \psi
\;, \quad \forall \psi \;,
where :math:`\ul{v}_0` is the 2D vector defining the far field velocity that
generates the incompressible flow.
Usage examples
--------------
This example can be run with the ``simple.py`` script with the following::
"""
import numpy as nm
from sfepy import data_dir
v0 = nm.array([1, 0.25])
materials = {
'm': ({'v0': v0.reshape(-1, 1)},),
}
regions = {
'Omega': 'all',
'Gamma_Left': ('vertices in (x < 0.1)', 'facet'),
'Gamma_Right': ('vertices in (x > 1919.9)', 'facet'),
'Gamma_Top': ('vertices in (y > 917.9)', 'facet'),
(continues on next page)
fields = {
'u': ('real', 1, 'Omega', 1),
}
variables = {
'phi': ('unknown field', 'u', 0),
'psi': ('test field', 'u', 'phi'),
}
# these EBCS prevent the matrix from being singular, see description
ebcs = {
'fix': ('Vertex', {'phi.0': 0.0}),
}
integrals = {
'i': 2,
}
equations = {
'Laplace equation':
"""dw_laplace.i.Omega( psi, phi )
= dw_surface_ndot.i.Gamma_Left( m.v0, psi )
+ dw_surface_ndot.i.Gamma_Right( m.v0, psi )
+ dw_surface_ndot.i.Gamma_Top( m.v0, psi )
+ dw_surface_ndot.i.Gamma_Bottom( m.v0, psi )"""
}
solvers = {
'ls': ('ls.scipy_direct', {}),
'newton': ('nls.newton', {
'i_max': 1,
'eps_a': 1e-10,
}),
}
diffusion/laplace_iga_interactive.py
Description
Laplace equation with Dirichlet boundary conditions solved in a single patch NURBS domain using the isogeometric
analysis (IGA) approach, using commands for interactive use.
This script allows the creation of a customisable NURBS surface using igakit built-in CAD routines, which is then
saved in custom HDF5-based files with .iga extension.
Notes
The create_patch function creates a NURBS-patch of the area between two coplanar nested circles using igakit
CAD built-in routines. The created patch is not connected in the orthoradial direction. This is a problem when the
disconnected boundary is not perpendicular to the line connecting the two centres of the circles, as the solution then
exhibits a discontinuity along this line. A workaround for this issue is to enforce perpendicularity by changing the start
angle in function igakit.cad.circle (see the code down below for the actual trick). The discontinuity disappears.
Usage Examples
Command line options for tweaking the geometry of the NURBS-patch & more:
$ python3 examples/diffusion/laplace_iga_interactive.py --R1=0.7 --C2=0.1,0.1 --viewpatch
source code
#!/usr/bin/env python
r"""
Laplace equation with Dirichlet boundary conditions solved in a single patch
NURBS domain using the isogeometric analysis (IGA) approach, using commands
for interactive use.
This script allows the creation of a customisable NURBS surface using igakit
built-in CAD routines, which is then saved in custom HDF5-based files with
.iga extension.
Notes
-----
Usage Examples
--------------
$ python3 sfepy/examples/diffusion/laplace_iga_interactive.py
import os
import sys
sys.path.append('.')
import numpy as nm
from sfepy import data_dir
from sfepy.base.ioutils import ensure_path
from sfepy.base.base import IndexedStruct
from sfepy.discrete import (FieldVariable, Integral, Equation,Equations,
Problem)
from sfepy.discrete.iga.domain import IGDomain
from sfepy.discrete.common.fields import Field
from sfepy.terms import Term
from sfepy.discrete.conditions import Conditions, EssentialBC
from sfepy.solvers.ls import ScipyDirect
from sfepy.solvers.nls import Newton
Parameters
----------
R1 : float
Radius of the inner circle.
R2 : float
Radius of the outer circle.
C1 : list of two floats
Coordinates of the center of the inner circle given as [x1, y1].
C2 : list of two floats
Coordinates of the center of the outer circle given as [x2, y2].
order : int, optional
Degree of the NURBS basis functions. The default is 2.
viewpatch : bool, optional
When set to True, display the NURBS patch. The default is False.
Returns
-------
None.
(continues on next page)
"""
# Geometry Creation
centers_direction = [C2[0]-C1[0], C2[1]-C1[1]]
if centers_direction[0]==0 and centers_direction[1]==0:
start_angle = 0.0
else:
start_angle = nm.arctan2(centers_direction[1], centers_direction[0])
c1 = circle(radius=R1, center=C1, angle=(start_angle, start_angle + 2*pi))
c2 = circle(radius=R2, center=C2, angle=(start_angle, start_angle + 2*pi))
srf = ruled(c1,c2).transpose() # make the radial direction first
# Refinement
insert_U = insert_uniformly(srf.knots[0], 6)
insert_V = insert_uniformly(srf.knots[1], 6)
srf.refine(0, insert_U).refine(1, insert_V)
if viewpatch:
try:
iplt.use('mayavi')
iplt.figure()
iplt.plot(srf)
iplt.show()
except ImportError:
iplt.use('matplotlib')
iplt.figure()
iplt.plot(srf)
(continues on next page)
Parameters
----------
U : numpy.ndarray
Original knot vector for a C^p-1 space.
n : int
Target number of uniformly-spaced knot spans.
Returns
-------
Knots to be inserted into U
"""
U0 = U
dU=(U.max()-U.min())/float(n) # target dU in knot vector
idone=0
while idone == 0:
# Add knots in middle of spans which are too large
Uadd=[]
for i in range(len(U)-1):
if U[i+1]-U[i] > dU:
Uadd.append(0.5*(U[i+1]+U[i]))
# Now we add these knots (once only, assumes C^(p-1))
if len(Uadd) > 0:
U = nm.sort(nm.concatenate([U,nm.asarray(Uadd)]))
else:
idone=1
# And now a little Laplacian smoothing
for num_iterations in range(5):
for i in range(len(U)-2):
if abs(U0[U0.searchsorted(U[i+1])]-U[i+1]) > 1.0e-14:
U[i+1] = 0.5*(U[i]+U[i+2])
return nm.setdiff1d(U,U0)
helps = {
'output_dir' :
'output directory',
'R1' :
'Inner circle radius [default: %(default)s]',
'R2' :
'Outer circle radius [default: %(default)s]',
'C1' :
'centre of the inner circle [default: %(default)s]',
'C2' :
(continues on next page)
def main():
parser = ArgumentParser(description=__doc__.rstrip(),
formatter_class=RawDescriptionHelpFormatter)
parser.add_argument('-o', '--output-dir', default='.',
help=helps['output_dir'])
parser.add_argument('--R1', metavar='R1',
action='store', dest='R1',
default='0.5', help=helps['R1'])
parser.add_argument('--R2', metavar='R2',
action='store', dest='R2',
default='1.0', help=helps['R2'])
parser.add_argument('--C1', metavar='C1',
action='store', dest='C1',
default='0.0,0.0', help=helps['C1'])
parser.add_argument('--C2', metavar='C2',
action='store', dest='C2',
default='0.0,0.0', help=helps['C2'])
parser.add_argument('--order', metavar='int', type=int,
action='store', dest='order',
default=2, help=helps['order'])
parser.add_argument('-v', '--viewpatch',
action='store_true', dest='viewpatch',
default=False, help=helps['viewpatch'])
options = parser.parse_args()
# Sub-domains
omega = domain.create_region('Omega', 'all')
Gamma_out = domain.create_region('Gamma_out', 'vertices of set xi01',
kind='facet')
Gamma_in = domain.create_region('Gamma_in', 'vertices of set xi00',
kind='facet')
# Variables
u = FieldVariable('u', 'unknown', field) # unknown function
v = FieldVariable('v', 'test', field, primary_var_name='u') # test function
# Integral
integral = Integral('i', order=2*field.approx_order)
# Term
t = Term.new('dw_laplace( v, u )', integral, omega, v=v, u=u)
# Equation
eq = Equation('laplace', t)
eqs = Equations([eq])
# Boundary Conditions
u_in = EssentialBC('u_in', Gamma_in, {'u.all' : 7.0})
u_out = EssentialBC('u_out', Gamma_out, {'u.all' : 3.0})
# solvers
ls = ScipyDirect({})
nls_status = IndexedStruct()
nls = Newton({}, lin_solver=ls, status=nls_status)
# problem instance
pb = Problem('potential', equations=eqs, active_only=True)
# solving
pb.set_solver(nls)
status = IndexedStruct()
state = pb.solve(status=status, save_results=True, verbose=True)
if __name__ == '__main__':
main()
diffusion/laplace_refine_interactive.py
Description
Example of solving Laplaceβs equation on a block domain refined with level 1 hanging nodes.
The domain is progressively refined towards the edge/face of the block, where Dirichlet boundary conditions are pre-
scribed by an oscillating function.
Find π’ such that:
β«οΈ
βπ£ Β· βπ’ = 0 , βπ .
Ξ©
Notes
The implementation of the mesh refinement with level 1 hanging nodes is a proof-of-concept code with many unresolved
issues. The main problem is the fact that a user needs to input the cells to refine at each level, while taking care of the
following constraints:
β’ the level 1 hanging nodes constraint: a cell that has a less-refined neighbour cannot be refined;
β’ the implementation constraint: a cell with a refined neighbour cannot be refined.
The hanging nodes are treated by a basis transformation/DOF substitution, which has to be applied explicitly by the
user:
β’ call field.substitute_dofs(subs) before assembling and solving;
β’ then call field.restore_dofs() before saving results.
Usage Examples
source code
#!/usr/bin/env python
r"""
Example of solving Laplace's equation on a block domain refined with level 1
hanging nodes.
The domain is progressively refined towards the edge/face of the block, where
Dirichlet boundary conditions are prescribed by an oscillating function.
.. math::
\int_{\Omega} \nabla v \cdot \nabla u = 0
\;, \quad \forall s \;.
Notes
-----
The implementation of the mesh refinement with level 1 hanging nodes is a
proof-of-concept code with many unresolved issues. The main problem is the fact
that a user needs to input the cells to refine at each level, while taking care
of the following constraints:
Usage Examples
--------------
import os
import sys
sys.path.append('.')
import numpy as nm
region = domain.create_region('aux',
'vertices in (%s %.10f )' % (axis, coor),
add_to_regions=False)
refine[region.cells] = 1
helps = {
'output_dir' :
'output directory',
'dims' :
'dimensions of the block [default: %(default)s]',
'shape' :
'shape (counts of nodes in x, y[, z]) of the block [default: %(default)s]',
(continues on next page)
def main():
parser = ArgumentParser(description=__doc__.rstrip(),
formatter_class=RawDescriptionHelpFormatter)
parser.add_argument('output_dir', help=helps['output_dir'])
parser.add_argument('--dims', metavar='dims',
action='store', dest='dims',
default='1.0,1.0,1.0', help=helps['dims'])
parser.add_argument('--shape', metavar='shape',
action='store', dest='shape',
default='7,7,7', help=helps['shape'])
parser.add_argument('--centre', metavar='centre',
action='store', dest='centre',
default='0.0,0.0,0.0', help=helps['centre'])
parser.add_argument('-3', '--3d',
action='store_true', dest='is_3d',
default=False, help=helps['3d'])
parser.add_argument('--order', metavar='int', type=int,
action='store', dest='order',
default=1, help=helps['order'])
options = parser.parse_args()
output('dimensions:', dims)
output('shape: ', shape)
output('centre: ', centre)
bbox = domain0.get_mesh_bounding_box()
min_x, max_x = bbox[:, 0]
eps = 1e-8 * (max_x - min_x)
cnt = (shape[0] - 1) // 2
g0 = 0.5 * dims[0]
grading = nm.array([g0 / 2**ii for ii in range(cnt)]) + eps + centre[0] - g0
gamma1 = domain.create_region('Gamma1',
'vertices in (x < %.10f )' % (min_x + eps),
'facet')
gamma2 = domain.create_region('Gamma2',
'vertices in (x > %.10f )' % (max_x - eps),
'facet')
t1 = Term.new('dw_laplace(v, u)',
integral, omega, v=v, u=u)
eq = Equation('eq', t1)
eqs = Equations([eq])
else:
min_y, max_y = bbox[:, 1]
min_z, max_z = bbox[:, 2]
y = (coors[:, 1] - min_y) / (max_y - min_y)
z = (coors[:, 2] - min_z) / (max_z - min_z)
return val
ls = ScipyDirect({})
pb = Problem('heat', equations=eqs)
pb.set_bcs(ebcs=Conditions([fix1, fix2]))
pb.set_solver(nls)
state = pb.solve(save_results=False)
pb.save_state(filename, state)
if options.order > 1:
pb.save_state(filename, state, linearization=Struct(kind='adaptive',
min_level=0,
max_level=8,
eps=1e-3))
if __name__ == '__main__':
main()
diffusion/laplace_shifted_periodic.py
Description
Laplace equation with shifted periodic BCs.
Display using:
#!/usr/bin/env python
"""
Laplace equation with shifted periodic BCs.
Display using::
t1 = Term.new('dw_laplace(v, u)',
integral, omega, v=v, u=u)
eq = Equation('eq', t1)
eqs = Equations([eq])
ls = ScipyDirect({})
nls = Newton({}, lin_solver=ls)
pb = Problem('laplace', equations=eqs)
pb.set_output_dir(output_dir)
pb.set_solver(nls)
state = pb.solve()
helps = {
'dims' :
'dimensions of the block [default: %(default)s]',
'centre' :
'centre of the block [default: %(default)s]',
'shape' :
'numbers of vertices along each axis [default: %(default)s]',
'show' : 'show the results figure',
}
def main():
parser = ArgumentParser(description=__doc__,
formatter_class=RawDescriptionHelpFormatter)
parser.add_argument('--version', action='version', version='%(prog)s')
parser.add_argument('-d', '--dims', metavar='dims',
action='store', dest='dims',
default='[1.0, 1.0]', help=helps['dims'])
parser.add_argument('-c', '--centre', metavar='centre',
action='store', dest='centre',
default='[0.0, 0.0]', help=helps['centre'])
parser.add_argument('-s', '--shape', metavar='shape',
action='store', dest='shape',
default='[11, 11]', help=helps['shape'])
parser.add_argument('--show',
action="store_true", dest='show',
default=False, help=helps['show'])
options = parser.parse_args()
output('dimensions:', dims)
output('centre: ', centre)
output('shape: ', shape)
if options.show:
from sfepy.postprocess.viewer import Viewer
from sfepy.postprocess.domain_specific import DomainSpecificPlot
view = Viewer('laplace_shifted_periodic.vtk')
view(rel_scaling=1,
domain_specific={'u' : DomainSpecificPlot('plot_warp_scalar',
['rel_scaling=1'])},
is_scalar_bar=True, is_wireframe=True,
opacity=0.3)
if __name__ == '__main__':
main()
diffusion/laplace_time_ebcs.py
Description
Example explaining how to change Dirichlet boundary conditions depending on time. It is shown on the stationary
Laplace equation for temperature, so there is no dynamics, only the conditions change with time.
Five time steps are solved on a cube domain, with the temperature fixed to zero on the bottom face, and set to other
values on the left, right and top faces in different time steps.
Find π‘ such that:
β«οΈ
πβπ Β· βπ‘ = 0 , βπ .
Ξ©
source code
r"""
Example explaining how to change Dirichlet boundary conditions depending
on time. It is shown on the stationary Laplace equation for temperature,
so there is no dynamics, only the conditions change with time.
Five time steps are solved on a cube domain, with the temperature fixed
to zero on the bottom face, and set to other values on the left, right
and top faces in different time steps.
.. math::
\int_{\Omega} c \nabla s \cdot \nabla t
= 0
\;, \quad \forall s \;.
"""
from __future__ import absolute_import
from sfepy import data_dir
'active_only' : False,
}
regions = {
'Omega' : 'all',
'Left' : ('vertices in (x < -0.499)', 'facet'),
'Right' : ('vertices in (x > 0.499)', 'facet'),
'Bottom' : ('vertices in (z < -0.499)', 'facet'),
'Top' : ('vertices in (z > 0.499)', 'facet'),
}
materials = {
'one' : ({'val' : 1.0},),
}
fields = {
'temperature' : ('real', 1, 'Omega', 1),
}
variables = {
't' : ('unknown field', 'temperature', 0),
's' : ('test field', 'temperature', 't'),
}
ebcs = {
'fixed' : ('Bottom', {'t.all' : 0}),
't_t02' : ('Left', [(-0.5, 0.5), (2.5, 3.5)], {'t.all' : 1.0}),
't_t1' : ('Right', [(0.5, 1.5)], {'t.all' : 2.0}),
't_t4' : ('Top', 'is_ebc', {'t.all' : 3.0}),
}
def is_ebc(ts):
if ts.step in (2, 4):
return True
else:
return False
functions = {
'is_ebc' : (is_ebc,),
}
equations = {
'eq' : """dw_laplace.2.Omega( one.val, s, t ) = 0""",
}
solvers = {
(continues on next page)
'quasistatic' : True,
'verbose' : 1,
}),
}
diffusion/poisson.py
Description
Laplace equation using the long syntax of keywords.
See the tutorial section Example Problem Description File for a detailed explanation. See diffu-
sion/poisson_short_syntax.py for the short syntax version.
Find π‘ such that:
β«οΈ
πβπ Β· βπ‘ = 0 , βπ .
Ξ©
source code
r"""
Laplace equation using the long syntax of keywords.
.. math::
\int_{\Omega} c \nabla s \cdot \nabla t
= 0
\;, \quad \forall s \;.
"""
from __future__ import absolute_import
from sfepy import data_dir
material_2 = {
'name' : 'coef',
(continues on next page)
region_1000 = {
'name' : 'Omega',
'select' : 'cells of group 6',
}
region_03 = {
'name' : 'Gamma_Left',
'select' : 'vertices in (x < 0.00001)',
'kind' : 'facet',
}
region_4 = {
'name' : 'Gamma_Right',
'select' : 'vertices in (x > 0.099999)',
'kind' : 'facet',
}
field_1 = {
'name' : 'temperature',
'dtype' : 'real',
'shape' : (1,),
'region' : 'Omega',
'approx_order' : 1,
}
variable_1 = {
'name' : 't',
'kind' : 'unknown field',
'field' : 'temperature',
'order' : 0, # order in the global vector of unknowns
}
variable_2 = {
'name' : 's',
'kind' : 'test field',
'field' : 'temperature',
'dual' : 't',
}
ebc_1 = {
'name' : 't1',
'region' : 'Gamma_Left',
'dofs' : {'t.0' : 2.0},
}
ebc_2 = {
'name' : 't2',
'region' : 'Gamma_Right',
'dofs' : {'t.0' : -2.0},
(continues on next page)
integral_1 = {
'name' : 'i',
'order' : 2,
}
equations = {
'Temperature' : """dw_laplace.i.Omega( coef.val, s, t ) = 0"""
}
solver_0 = {
'name' : 'ls',
'kind' : 'ls.scipy_direct',
'method' : 'auto',
}
solver_1 = {
'name' : 'newton',
'kind' : 'nls.newton',
'i_max' : 1,
'eps_a' : 1e-10,
'eps_r' : 1.0,
'macheps' : 1e-16,
'lin_red' : 1e-2, # Linear system error < (eps_a * lin_red).
'ls_red' : 0.1,
'ls_red_warp' : 0.001,
'ls_on' : 1.1,
'ls_min' : 1e-5,
'check' : 0,
'delta' : 1e-6,
}
options = {
'nls' : 'newton',
'ls' : 'ls',
}
diffusion/poisson_field_dependent_material.py
Description
Laplace equation with a field-dependent material parameter.
Find π (π‘) for π‘ β [0, π‘final ] such that:
β«οΈ
π(π )βπ Β· βπ = 0 , βπ .
Ξ©
where π(π ) is the π dependent diffusion coefficient. Each iteration calculates π and adjusts π(π ).
source code
r"""
Laplace equation with a field-dependent material parameter.
Find :math:`T(t)` for :math:`t \in [0, t_{\rm final}]` such that:
.. math::
\int_{\Omega} c(T) \nabla s \cdot \nabla T
= 0
\;, \quad \forall s \;.
t0 = 0.0
t1 = 0.1
(continues on next page)
materials = {
'coef' : 'get_conductivity',
}
fields = {
'temperature' : ('real', 1, 'Omega', 1),
}
variables = {
'T' : ('unknown field', 'temperature', 0),
's' : ('test field', 'temperature', 'T'),
}
regions = {
'Omega' : 'all',
'Gamma_Left' : ('vertices in (x < 0.00001)', 'facet'),
'Gamma_Right' : ('vertices in (x > 0.099999)', 'facet'),
}
ebcs = {
'T1' : ('Gamma_Left', {'T.0' : 2.0}),
'T2' : ('Gamma_Right', {'T.0' : -2.0}),
}
functions = {
'get_conductivity' : (get_conductivity,),
}
ics = {
'ic' : ('Omega', {'T.0' : 0.0}),
}
integrals = {
(continues on next page)
equations = {
'Temperature' : """dw_laplace.i.Omega( coef.val, s, T ) = 0"""
}
solvers = {
'ls' : ('ls.scipy_direct', {}),
'newton' : ('nls.newton', {
'i_max' : 1,
'eps_a' : 1e-10,
'eps_r' : 1.0,
}),
'ts' : ('ts.simple', {
't0' : t0,
't1' : t1,
'dt' : None,
'n_step' : n_step, # has precedence over dt!
'quasistatic' : True,
'verbose' : 1,
}),
}
options = {
'nls' : 'newton',
'ls' : 'ls',
'ts' : 'ts',
'save_times' : 'all',
}
diffusion/poisson_functions.py
Description
Poisson equation with source term.
Find π’ such that:
β«οΈ β«οΈ β«οΈ
πβπ£ Β· βπ’ = β ππ£ = β π π£π , βπ£ ,
Ξ© Ξ©πΏ Ξ©πΏ
where π(π₯) = π (π₯)π(π₯), π is a given FE field and π is a given general function of space.
This example demonstrates use of functions for defining material parameters, regions, parameter variables or boundary
conditions. Notably, it demonstrates the following:
1. How to define a material parameter by an arbitrary function - see the function get_pars() that evaluates π (π₯)
in quadrature points.
2. How to define a known function that belongs to a given FE space (field) - this function, π(π₯), is defined in a FE
sense by its nodal values only - see the function get_load_variable().
In order to define the load π(π₯) directly, the term dw_dot should be replaced by dw_integrate.
source code
r"""
Poisson equation with source term.
.. math::
\int_{\Omega} c \nabla v \cdot \nabla u
= - \int_{\Omega_L} b v = - \int_{\Omega_L} f v p
\;, \quad \forall v \;,
options = {
'nls' : 'newton',
'ls' : 'ls',
}
materials = {
'm' : ({'c' : 1.0},),
'load' : 'get_pars',
}
regions = {
'Omega' : 'all',
'Omega_L' : 'vertices by get_middle_ball',
'Gamma_Left' : ('vertices in (x < 0.00001)', 'facet'),
'Gamma_Right' : ('vertices in (x > 0.099999)', 'facet'),
}
fields = {
'temperature' : ('real', 1, 'Omega', 1),
'velocity' : ('real', 'vector', 'Omega', 1),
}
variables = {
'u' : ('unknown field', 'temperature', 0),
'v' : ('test field', 'temperature', 'u'),
'p' : ('parameter field', 'temperature',
{'setter' : 'get_load_variable'}),
'w' : ('parameter field', 'velocity',
{'setter' : 'get_convective_velocity'}),
}
ebcs = {
'u1' : ('Gamma_Left', {'u.0' : 'get_ebc'}),
'u2' : ('Gamma_Right', {'u.0' : -2.0}),
}
integrals = {
'i' : 1,
}
solvers = {
'ls' : ('ls.scipy_direct', {}),
'newton' : ('nls.newton', {
'i_max' : 1,
'eps_a' : 1e-10,
}),
}
val.shape = (coors.shape[0], 1, 1)
return {'f' : val}
return flag
val = 5e5 * y
return val
return val
functions = {
'get_pars' : (get_pars,),
'get_load_variable' : (get_load_variable,),
'get_convective_velocity' : (get_convective_velocity,),
'get_middle_ball' : (get_middle_ball,),
'get_ebc' : (lambda ts, coor, bc, problem, **kwargs: get_ebc(coor, 5.0),),
}
diffusion/poisson_iga.py
Description
Poisson equation solved in a single patch NURBS domain using the isogeometric analysis (IGA) approach.
Find π‘ such that:
β«οΈ β«οΈ
πβπ Β· βπ‘ = ππ , βπ .
Ξ© Ξ©0
Try setting the Dirichlet boundary condition (ebcs) on various sides of the domain ('Gamma1', . . . , 'Gamma4').
View the results using:
source code
r"""
Poisson equation solved in a single patch NURBS domain using the isogeometric
analysis (IGA) approach.
.. math::
\int_{\Omega} c \nabla s \cdot \nabla t
= \int_{\Omega_0} f s
\;, \quad \forall s \;.
Try setting the Dirichlet boundary condition (ebcs) on various sides of the
domain (``'Gamma1'``, ..., ``'Gamma4'``).
materials = {
'm' : ({'c' : 1.0, 'f' : -10.0},),
}
regions = {
'Omega' : 'all',
'Omega_0' : 'vertices in (x > 1.5)',
'Gamma1' : ('vertices of set xi00', 'facet'),
'Gamma2' : ('vertices of set xi01', 'facet'),
'Gamma3' : ('vertices of set xi10', 'facet'),
'Gamma4' : ('vertices of set xi11', 'facet'),
}
fields = {
'temperature' : ('real', 1, 'Omega', None, 'H1', 'iga'),
}
variables = {
't' : ('unknown field', 'temperature', 0),
's' : ('test field', 'temperature', 't'),
}
ebcs = {
't1' : ('Gamma3', {'t.0' : 2.0}),
't2' : ('Gamma4', {'t.0' : -2.0}),
}
integrals = {
'i' : 3,
}
equations = {
'Temperature' : """dw_laplace.i.Omega(m.c, s, t)
= dw_volume_lvf.i.Omega_0(m.f, s)"""
}
solvers = {
'ls' : ('ls.scipy_direct', {}),
'newton' : ('nls.newton', {
'i_max' : 1,
'eps_a' : 1e-10,
}),
}
options = {
'nls' : 'newton',
'ls' : 'ls',
}
diffusion/poisson_neumann.py
Description
The Poisson equation with Neumann boundary conditions on a part of the boundary.
Find π such that:
β«οΈ β«οΈ
πΎππ βπ π βπ ππ = π π , βπ ,
Ξ© Ξπ
where π is the given flux, π = π Β· πΎππ βπ πΒ―, and πΎππ = ππΏππ (an isotropic medium). See the tutorial section Strong form
of Poissonβs equation and its integration for a detailed explanation.
The diffusion velocity and fluxes through various parts of the boundary are computed in the post_process() function.
On βGamma_Nβ (the Neumann condition boundary part), the flux/length should correspond to the given value π = β50,
while on βGamma_N0β the flux should be zero. Use the βrefinement_levelβ option (see the usage examples below) to
check the convergence of the numerical solution to those values. The total flux and the flux through βGamma_Dβ (the
Dirichlet condition boundary part) are shown as well.
Usage Examples
source code
r"""
The Poisson equation with Neumann boundary conditions on a part of the
boundary.
.. math::
\int_{\Omega} K_{ij} \nabla_i s \nabla_j p T
= \int_{\Gamma_N} s g
\;, \quad \forall s \;,
where :math:`g` is the given flux, :math:`g = \ul{n} \cdot K_{ij} \nabla_j
\bar{T}`, and :math:`K_{ij} = c \delta_{ij}` (an isotropic medium). See the
tutorial section :ref:`poisson-weak-form-tutorial` for a detailed explanation.
The diffusion velocity and fluxes through various parts of the boundary are
computed in the :func:`post_process()` function. On 'Gamma_N' (the Neumann
condition boundary part), the flux/length should correspond to the given value
:math:`g = -50`, while on 'Gamma_N0' the flux should be zero. Use the
'refinement_level' option (see the usage examples below) to check the
convergence of the numerical solution to those values. The total flux and the
(continues on next page)
Usage Examples
--------------
totals = nm.zeros(3)
for gamma in ['Gamma_N', 'Gamma_N0', 'Gamma_D']:
return out
materials = {
'flux' : ({'val' : -50.0},),
(continues on next page)
regions = {
'Omega' : 'all',
'Gamma_D' : ('vertices in (x < -0.4999)', 'facet'),
'Gamma_N0' : ('vertices in (y > 0.4999)', 'facet'),
'Gamma_N' : ('vertices of surface -s (r.Gamma_D +v r.Gamma_N0)',
'facet'),
}
fields = {
'temperature' : ('real', 1, 'Omega', 1),
}
variables = {
't' : ('unknown field', 'temperature', 0),
's' : ('test field', 'temperature', 't'),
}
ebcs = {
't1' : ('Gamma_D', {'t.0' : 5.3}),
}
integrals = {
'i' : 2
}
equations = {
'Temperature' : """
dw_diffusion.i.Omega(m.K, s, t)
= dw_integrate.i.Gamma_N(flux.val, s)
"""
}
solvers = {
'ls' : ('ls.scipy_direct', {}),
'newton' : ('nls.newton', {
'i_max' : 1,
'eps_a' : 1e-10,
}),
}
options = {
'nls' : 'newton',
'ls' : 'ls',
'refinement_level' : 0,
'post_process_hook' : 'post_process',
}
diffusion/poisson_parallel_interactive.py
Description
Parallel assembling and solving of a Poissonβs equation, using commands for interactive use.
Find π’ such that:
β«οΈ β«οΈ
βπ£ Β· βπ’ = π£π , βπ .
Ξ© Ξ©
Important Notes
β’ This example requires petsc4py, mpi4py and (optionally) pymetis with their dependencies installed!
β’ This example generates a number of files - do not use an existing non-empty directory for the output_dir
argument.
β’ Use the --clear option with care!
Notes
β’ Each task is responsible for a subdomain consisting of a set of cells (a cell region).
β’ Each subdomain owns PETSc DOFs within a consecutive range.
β’ When both global and task-local variables exist, the task-local variables have _i suffix.
β’ This example does not use a nonlinear solver.
β’ This example can serve as a template for solving a linear single-field scalar problem - just replace the equations
in create_local_problem().
β’ The command line options are saved into <output_dir>/options.txt file.
Usage Examples
$ python examples/diffusion/poisson_parallel_interactive.py -h
Parallel runs:
source code
#!/usr/bin/env python
r"""
Parallel assembling and solving of a Poisson's equation, using commands for
interactive use.
.. math::
\int_{\Omega} \nabla v \cdot \nabla u
= \int_{\Omega} v f
\;, \quad \forall s \;.
Important Notes
---------------
- This example requires petsc4py, mpi4py and (optionally) pymetis with their
dependencies installed!
- This example generates a number of files - do not use an existing non-empty
directory for the ``output_dir`` argument.
- Use the ``--clear`` option with care!
Notes
-----
Usage Examples
--------------
$ python sfepy/examples/diffusion/poisson_parallel_interactive.py -h
Parallel runs::
import numpy as nm
import matplotlib.pyplot as plt
import sfepy.parallel.parallel as pl
import sfepy.parallel.plot_parallel_dofs as ppd
gamma1_i = domain_i.create_region('Gamma1',
'vertices in (x < %.10f )'
% (min_x + eps_x),
'facet', allow_empty=True)
gamma2_i = domain_i.create_region('Gamma2',
'vertices in (x > %.10f )'
% (max_x - eps_x),
'facet', allow_empty=True)
def _get_load(coors):
val = nm.ones_like(coors[:, 0])
for coor in coors.T:
val *= nm.sin(4 * nm.pi * coor)
return val
t2 = Term.new('dw_volume_lvf(load.val, v_i)',
integral, omega_i, load=load, v_i=v_i)
return pb
order = options.order
mesh = field.domain.mesh
filename = os.path.join(options.output_dir,
'para-domains-dofs.h5')
if field.is_higher_order():
out['aux'].mesh.write(filename, out=out)
else:
mesh.write(filename, out=out)
stats = Struct()
timer = Timer('solve_timer')
timer.start()
(continues on next page)
timer.start()
if rank == 0:
cell_tasks = pl.partition_mesh(mesh, size, use_metis=options.metis,
verbose=True)
else:
cell_tasks = None
stats.t_partition_mesh = timer.stop()
stats.t_create_global_fields = timer.stop()
output('...done in', timer.dt)
distribute = pl.distribute_fields_dofs
lfds, gfds = distribute([field], cell_tasks,
is_overlap=True,
save_inter_regions=options.save_inter_regions,
output_dir=options.output_dir,
comm=comm, verbose=True)
lfd = lfds[0]
stats.t_distribute_fields_dofs = timer.stop()
output('...done in', timer.dt)
if rank == 0:
dof_maps = gfds[0].dof_maps
id_map = gfds[0].id_map
if options.verify:
verify_save_dof_maps(field, cell_tasks,
dof_maps, id_map, options, verbose=True)
if options.plot:
ppd.plot_partitioning([None, None], field, cell_tasks, gfds[0],
options.output_dir, size)
pb = create_local_problem(omega_gi, order)
variables = pb.get_initial_state()
eqs = pb.equations
u_i = variables['u_i']
field_i = u_i.field
stats.t_create_local_problem = timer.stop()
output('...done in', timer.dt)
if options.plot:
ppd.plot_local_dofs([None, None], field, field_i, omega_gi,
options.output_dir, rank)
output('pdofs:', pdofs)
stats.t_allocate_global_system = timer.stop()
output('...done in', timer.dt)
variables.fill_state(0.0)
variables.apply_ebc()
rhs_i = eqs.eval_residuals(variables())
# This must be after pl.create_petsc_system() call!
mtx_i = eqs.eval_tangent_matrices(variables(), pb.mtx_a)
stats.t_evaluate_local_problem = timer.stop()
output('...done in', timer.dt)
apply_ebc_to_matrix(mtx_i, u_i.eq_map.eq_ebc)
pl.assemble_rhs_to_petsc(prhs, rhs_i, pdofs, drange, is_overlap=True,
comm=comm, verbose=True)
pl.assemble_mtx_to_petsc(pmtx, mtx_i, pdofs, drange, is_overlap=True,
comm=comm, verbose=True)
stats.t_assemble_global_system = timer.stop()
output('...done in', timer.dt)
output('creating solver...')
timer.start()
stats.t_create_solver = timer.stop()
output('...done in', timer.dt)
output('solving...')
timer.start()
psol_i = pl.create_local_petsc_vector(pdofs)
gather, scatter = pl.create_gather_scatter(pdofs, psol_i, psol, comm=comm)
scatter(psol_i, psol)
gather(psol, psol_i)
stats.t_solve = timer.stop()
output('...done in', timer.dt)
output('saving solution...')
timer.start()
variables.set_state(sol0_i)
out = u_i.create_output()
gather_to_zero = pl.create_gather_to_zero(psol)
psol_full = gather_to_zero(psol)
else:
out = u.create_output(sol, linearization=Struct(kind='adaptive',
min_level=0,
max_level=order,
eps=1e-3))
stats.t_save_solution = timer.stop()
output('...done in', timer.dt)
stats.t_total = timer.total
stats.n_dof = sizes[1]
stats.n_dof_local = sizes[0]
stats.n_cell = omega.shape.n_cell
stats.n_cell_local = omega_gi.shape.n_cell
if options.show:
plt.show()
return stats
out['size'] = comm.size
out['rank'] = rank
out['dim'] = pars.dim
out.update(shape_dict)
out['order'] = pars.order
helps = {
'output_dir' :
'output directory',
'dims' :
'dimensions of the block [default: %(default)s]',
'shape' :
'shape (counts of nodes in x, y, z) of the block [default: %(default)s]',
'centre' :
'centre of the block [default: %(default)s]',
'2d' :
'generate a 2D rectangle, the third components of the above'
' options are ignored',
'order' :
'field approximation order',
'linearization' :
'linearization used for storing the results with approximation order > 1'
' [default: %(default)s]',
'metis' :
'use metis for domain partitioning',
'verify' :
'verify domain partitioning, save cells and DOFs of tasks'
' for visualization',
'plot' :
'make partitioning plots',
'save_inter_regions' :
'save inter-task regions for debugging partitioning problems',
'show' :
'show partitioning plots (implies --plot)',
'stats_filename' :
'name of the stats file for storing elapsed time statistics',
'new_stats' :
'create a new stats file with a header line (overwrites existing!)',
'silent' : 'do not print messages to screen',
'clear' :
'clear old solution files from output directory'
' (DANGEROUS - use with care!)',
}
def main():
parser = ArgumentParser(description=__doc__.rstrip(),
formatter_class=RawDescriptionHelpFormatter)
parser.add_argument('output_dir', help=helps['output_dir'])
parser.add_argument('--dims', metavar='dims',
action='store', dest='dims',
default='1.0,1.0,1.0', help=helps['dims'])
parser.add_argument('--shape', metavar='shape',
action='store', dest='shape',
(continues on next page)
if options.show:
options.plot = True
comm = pl.PETSc.COMM_WORLD
output_dir = options.output_dir
if comm.rank == 0:
from sfepy.mesh.mesh_generators import gen_block_mesh
if options.clear:
remove_files_patterns(output_dir,
['*.h5', '*.mesh', '*.txt', '*.png'],
ignores=['output_log_%02d.txt' % ii
for ii in range(comm.size)],
verbose=True)
save_options(os.path.join(output_dir, 'options.txt'),
[('options', vars(options))])
comm.barrier()
if options.stats_filename:
if comm.rank == 0:
ensure_path(options.stats_filename)
comm.barrier()
if __name__ == '__main__':
main()
diffusion/poisson_parametric_study.py
Description
Poisson equation.
This example demonstrates parametric study capabilities of Application classes. In particular (written in the strong
form):
πβπ‘ = π in β¦,
π‘ = 2 on Ξ1 , π‘ = β2 on Ξ2 , π = 1 in β¦1 , π = 0 otherwise,
$ ./postproc.py output/r_omega1/circles_in_square*.vtk
Remark: this simple case could be achieved also by defining β¦1 by a time-dependent function and solve the static
problem as a time-dependent problem. However, the approach below is much more general.
Find π‘ such that:
β«οΈ
πβπ Β· βπ‘ = 0 , βπ .
Ξ©
source code
r"""
Poisson equation.
.. math::
c \Delta t = f \mbox{ in } \Omega,
Run::
$ ./postproc.py output/r_omega1/circles_in_square*.vtk
.. math::
\int_{\Omega} c \nabla s \cdot \nabla t
= 0
\;, \quad \forall s \;.
"""
from __future__ import absolute_import
import os
import numpy as nm
# Mesh.
filename_mesh = data_dir + '/meshes/2d/special/circles_in_square.vtk'
'parametric_hook' : 'vary_omega1_size',
'output_dir' : 'output/r_omega1',
}
equations = {
'Poisson' : """dw_laplace.i.Omega( coef.val, s, t )
= dw_volume_lvf.i.Omega_1( source.val, s )"""
}
# Solvers.
solver_0 = {
'name' : 'ls',
'kind' : 'ls.scipy_direct',
}
'i_max' : 1,
'eps_a' : 1e-10,
'eps_r' : 1.0,
'macheps' : 1e-16,
'lin_red' : 1e-2, # Linear system error < (eps_a * lin_red).
'ls_red' : 0.1,
'ls_red_warp' : 0.001,
'ls_on' : 1.1,
'ls_min' : 1e-5,
'check' : 0,
'delta' : 1e-6,
}
functions = {
'select_circ': (lambda coors, domain=None:
select_circ(coors[:,0], coors[:,1], 0, default_diameter),),
}
# Functions.
def select_circ( x, y, z, diameter ):
"""Select circular subdomain of a given diameter."""
r = nm.sqrt( x**2 + y**2 )
n = out.shape[0]
if n <= 3:
raise ValueError( 'too few vertices selected! (%d)' % n )
return out
Input:
problem: Problem instance
Return:
a generator object:
1. creates new (modified) problem
2. yields the new (modified) problem and output container
3. use the output container for some logging
4. yields None (to signal next iteration to Application)
"""
from sfepy.discrete import Problem
from sfepy.solvers.ts import get_print_info
output.prefix = 'vary_omega1_size:'
conf = problem.conf
cf = conf.get_raw( 'functions' )
n_digit, aux, d_format = get_print_info( len( diameters ) + 1 )
for ii, diameter in enumerate( diameters ):
output( 'iteration %d: diameter %3.2f ' % (ii, diameter) )
out = []
yield problem, out
yield None
diffusion/poisson_periodic_boundary_condition.py
Description
Transient Laplace equation with a localized power source and periodic boundary conditions.
This example is using a mesh generated by gmsh. Both the .geo script used by gmsh to generate the file and the .mesh
file can be found in meshes.
The mesh is suitable for periodic boundary conditions. It consists of a cylinder enclosed by a box in the x and y
directions.
The cylinder will act as a power source.
The transient Laplace equation will be solved in time interval π‘ β [0, π‘final ].
Find π (π‘) for π‘ β [0, π‘final ] such that:
β«οΈ β«οΈ β«οΈ
ππ
ππ + π2 βπ Β· βπ = π3 π , βπ .
Ξ© ππ‘ Ξ© Ξ©2
source code
r"""
Transient Laplace equation with a localized power source and
periodic boundary conditions.
(continues on next page)
Find :math:`T(t)` for :math:`t \in [0, t_{\rm final}]` such that:
.. math::
\int_{\Omega}c s \pdiff{T}{t}
+ \int_{\Omega} \sigma_2 \nabla s \cdot \nabla T
= \int_{\Omega_2} P_3 T
\;, \quad \forall s \;.
"""
t0 = 0.0
t1 = 1.
n_step = 11
power_per_volume =1.e2 # Heating power per volume of the cylinder
capacity_cylinder = 1. # Heat capacity of cylinder
capacity_fill = 1. # Heat capacity of filling material
conductivity_cylinder = 1. # Heat conductivity of cylinder
conductivity_fill = 1. # Heat conductivity of filling material
else:
(continues on next page)
materials = {
'cylinder' : 'cylinder_material_func',
'fill' : ({'capacity' : capacity_fill,
'conductivity' : conductivity_fill,},),
}
fields = {
'temperature' : ('real', 1, 'Omega', 1),
}
variables = {
'T' : ('unknown field', 'temperature', 1, 1),
's' : ('test field', 'temperature', 'T'),
}
regions = {
'Omega' : 'all',
'cylinder' : 'cells of group 444',
'fill' : 'cells of group 555',
'Gamma_Left' : ('vertices in (x < -2.4999)', 'facet'),
'y+' : ('vertices in (y >2.4999)', 'facet'),
'y-' : ('vertices in (y <-2.4999)', 'facet'),
'z+' : ('vertices in (z >0.4999)', 'facet'),
'z-' : ('vertices in (z <-0.4999)', 'facet'),
}
ebcs = {
'T1' : ('Gamma_Left', {'T.0' : 0.0}),
}
# The matching functions link the elements on each side with that on the
# opposing side.
functions = {
'cylinder_material_func' : (cylinder_material_func,),
"match_y_plane" : (per.match_y_plane,),
"match_z_plane" : (per.match_z_plane,),
}
epbcs = {
# In the y-direction
'periodic_y' : (['y+', 'y-'], {'T.0' : 'T.0'}, 'match_y_plane'),
# and in the z-direction. Due to the symmetry of the problem, this periodic
# boundary condition is actually not necessary, but we include it anyway.
'periodic_z' : (['z+', 'z-'], {'T.0' : 'T.0'}, 'match_z_plane'),
(continues on next page)
ics = {
'ic' : ('Omega', {'T.0' : 0.0}),
}
integrals = {
'i' : 1,
}
equations = {
'Temperature' :
"""dw_dot.i.cylinder( cylinder.capacity, s, dT/dt )
dw_dot.i.fill( fill.capacity, s, dT/dt )
dw_laplace.i.cylinder( cylinder.conductivity, s, T )
dw_laplace.i.fill( fill.conductivity, s, T )
= dw_integrate.i.cylinder( cylinder.power, s )"""
}
solvers = {
'ls' : ('ls.scipy_direct', {}),
'newton' : ('nls.newton', {
'i_max' : 1,
'eps_a' : 1e-10,
'eps_r' : 1.0,
}),
'ts' : ('ts.simple', {
't0' : t0,
't1' : t1,
'dt' : None,
'n_step' : n_step, # has precedence over dt!
'quasistatic' : False,
'verbose' : 1,
}),
}
options = {
'nls' : 'newton',
'ls' : 'ls',
'ts' : 'ts',
'output_dir' : 'output',
'save_times' : 'all',
'active_only' : False,
}
diffusion/poisson_short_syntax.py
Description
Laplace equation using the short syntax of keywords.
See diffusion/poisson.py for the long syntax version.
Find π‘ such that:
β«οΈ
πβπ Β· βπ‘ = 0 , βπ .
Ξ©
source code
r"""
Laplace equation using the short syntax of keywords.
.. math::
\int_{\Omega} c \nabla s \cdot \nabla t
= 0
(continues on next page)
materials = {
'coef' : ({'val' : 1.0},),
}
regions = {
'Omega' : 'all', # or 'cells of group 6'
'Gamma_Left' : ('vertices in (x < 0.00001)', 'facet'),
'Gamma_Right' : ('vertices in (x > 0.099999)', 'facet'),
}
fields = {
'temperature' : ('real', 1, 'Omega', 1),
}
variables = {
't' : ('unknown field', 'temperature', 0),
's' : ('test field', 'temperature', 't'),
}
ebcs = {
't1' : ('Gamma_Left', {'t.0' : 2.0}),
't2' : ('Gamma_Right', {'t.0' : -2.0}),
}
integrals = {
'i' : 2,
}
equations = {
'Temperature' : """dw_laplace.i.Omega( coef.val, s, t ) = 0"""
}
solvers = {
'ls' : ('ls.scipy_direct', {}),
'newton' : ('nls.newton',
{'i_max' : 1,
'eps_a' : 1e-10,
}),
}
options = {
'nls' : 'newton',
'ls' : 'ls',
}
diffusion/sinbc.py
Description
Laplace equation with Dirichlet boundary conditions given by a sine function and constants.
Find π‘ such that:
β«οΈ
πβπ Β· βπ‘ = 0 , βπ .
Ξ©
The sfepy.discrete.fem.meshio.UserMeshIO class is used to refine the original two-element mesh before the
actual solution.
The FE polynomial basis and the approximation order can be chosen on the command-line. By default, the fifth order
Lagrange polynomial space is used, see define() arguments.
This example demonstrates how to visualize higher order approximations of the continuous solution. The adaptive
linearization is applied in order to save viewable results, see both the options keyword and the post_process()
function that computes the solution gradient. The linearization parameters can also be specified on the command line.
The Lagrange or Bernstein polynomial bases support higher order DOFs in the Dirichlet boundary conditions, unlike
the hierarchical Lobatto basis implementation, compare the results of:
Use the following commands to view each of the results of the above commands (assuming default output directory
and names):
source code
r"""
Laplace equation with Dirichlet boundary conditions given by a sine function
and constants.
.. math::
\int_{\Omega} c \nabla s \cdot \nabla t
= 0
\;, \quad \forall s \;.
The FE polynomial basis and the approximation order can be chosen on the
command-line. By default, the fifth order Lagrange polynomial space is used,
see ``define()`` arguments.
Use the following commands to view each of the results of the above commands
(assuming default output directory and names)::
domain.mesh.name = '2_4_2_refined'
return domain.mesh
return out
filename_mesh = UserMeshIO(mesh_hook)
options = {
'nls' : 'newton',
'ls' : 'ls',
'post_process_hook' : 'post_process',
'linearization' : {
'kind' : 'adaptive',
'min_level' : min_level, # Min. refinement level applied everywhere.
'max_level' : max_level, # Max. refinement level.
'eps' : eps, # Relative error tolerance.
},
}
materials = {
'coef' : ({'val' : 1.0},),
}
regions = {
'Omega' : 'all',
}
regions.update(define_box_regions(dim, bbox[0], bbox[1], 1e-5))
fields = {
'temperature' : ('real', 1, 'Omega', order, 'H1', basis),
}
variables = {
't' : ('unknown field', 'temperature', 0),
's' : ('test field', 'temperature', 't'),
}
(continues on next page)
amplitude = 1.0
def ebc_sin(ts, coor, **kwargs):
x0 = 0.5 * (coor[:, 1].min() + coor[:, 1].max())
val = amplitude * nm.sin( (coor[:, 1] - x0) * 2. * nm.pi )
return val
ebcs = {
't1' : ('Left', {'t.0' : 'ebc_sin'}),
't2' : ('Right', {'t.0' : -0.5}),
't3' : ('Top', {'t.0' : 1.0}),
}
functions = {
'ebc_sin' : (ebc_sin,),
}
equations = {
'Temperature' : """dw_laplace.10.Omega(coef.val, s, t) = 0"""
}
solvers = {
'ls' : ('ls.scipy_direct', {}),
'newton' : ('nls.newton', {
'i_max' : 1,
'eps_a' : 1e-10,
}),
}
return locals()
diffusion/time_advection_diffusion.py
Description
The transient advection-diffusion equation with a given divergence-free advection velocity.
Find π’ such that:
β«οΈ β«οΈ β«οΈ
ππ’
π + π β Β· (π£π’) + π·βπ Β· βπ’ = 0 , βπ .
Ξ© ππ‘ Ξ© Ξ©
source code
r"""
The transient advection-diffusion equation with a given divergence-free
advection velocity.
.. math::
\int_{\Omega} s \pdiff{u}{t}
+ \int_{\Omega} s \nabla \cdot \left(\ul{v} u \right)
+ \int_{\Omega} D \nabla s \cdot \nabla u
= 0
\;, \quad \forall s \;.
regions = {
'Omega' : 'all', # or 'cells of group 6'
'Gamma_Left' : ('vertices in (x < -0.99999)', 'facet'),
'Gamma_Right' : ('vertices in (x > 0.99999)', 'facet'),
}
fields = {
'concentration' : ('real', 1, 'Omega', 1),
}
variables = {
'u' : ('unknown field', 'concentration', 0, 1),
's' : ('test field', 'concentration', 'u'),
}
ebcs = {
'u1' : ('Gamma_Left', {'u.0' : 2.0}),
'u2' : ('Gamma_Right', {'u.0' : 0.0}),
}
integrals = {
'i' : 2,
}
equations = {
'advection-diffusion' :
"""
dw_dot.i.Omega(s, du/dt)
+ dw_advect_div_free.i.Omega(m.v, s, u)
+ dw_laplace.i.Omega(m.D, s, u)
= 0
"""
}
solvers = {
'ts' : ('ts.simple', {
't0' : 0.0,
't1' : 10.0,
'dt' : None,
'n_step' : 11, # Has precedence over dt.
'verbose' : 1,
}),
'newton' : ('nls.newton', {
'i_max' : 1,
'eps_a' : 1e-10,
}),
(continues on next page)
options = {
'ts' : 'ts',
'nls' : 'newton',
'ls' : 'ls',
'save_times' : 'all',
}
diffusion/time_poisson.py
Description
Transient Laplace equation with non-constant initial conditions given by a function.
Find π (π‘) for π‘ β [0, π‘final ] such that:
β«οΈ β«οΈ
ππ
π + πβπ Β· βπ = 0 , βπ .
Ξ© ππ‘ Ξ©
source code
r"""
Transient Laplace equation with non-constant initial conditions given by a
function.
Find :math:`T(t)` for :math:`t \in [0, t_{\rm final}]` such that:
.. math::
\int_{\Omega} s \pdiff{T}{t}
+ \int_{\Omega} c \nabla s \cdot \nabla T
= 0
\;, \quad \forall s \;.
"""
from __future__ import absolute_import
from sfepy import data_dir
t0 = 0.0
t1 = 0.1
n_step = 11
material_2 = {
'name' : 'coef',
'values' : {'val' : 0.01},
'kind' : 'stationary', # 'stationary' or 'time-dependent'
}
field_1 = {
'name' : 'temperature',
'dtype' : 'real',
'shape' : (1,),
'region' : 'Omega',
'approx_order' : 1,
}
variable_1 = {
'name' : 'T',
'kind' : 'unknown field',
'field' : 'temperature',
'order' : 0,
'history' : 1,
}
variable_2 = {
'name' : 's',
'kind' : 'test field',
'field' : 'temperature',
'dual' : 'T',
}
regions = {
'Omega' : 'all',
'Gamma_Left' : ('vertices in (x < 0.00001)', 'facet'),
'Gamma_Right' : ('vertices in (x > 0.099999)', 'facet'),
(continues on next page)
ebcs = {
'T1': ('Gamma_Left', {'T.0' : 2.0}),
'T2': ('Gamma_Right', {'T.0' : -2.0}),
}
functions = {
'get_ic' : (get_ic,),
}
ics = {
'ic' : ('Omega', {'T.0' : 'get_ic'}),
}
integral_1 = {
'name' : 'i',
'order' : 1,
}
equations = {
'Temperature' :
"""dw_dot.i.Omega( s, dT/dt )
+ dw_laplace.i.Omega( coef.val, s, T ) = 0"""
}
solver_0 = {
'name' : 'ls',
'kind' : 'ls.scipy_direct',
'use_presolve' : True,
}
solver_1 = {
'name' : 'newton',
'kind' : 'nls.newton',
'i_max' : 1,
'eps_a' : 1e-10,
'eps_r' : 1.0,
'macheps' : 1e-16,
'lin_red' : 1e-2, # Linear system error < (eps_a * lin_red).
'ls_red' : 0.1,
'ls_red_warp' : 0.001,
'ls_on' : 1.1,
(continues on next page)
solver_2 = {
'name' : 'ts',
'kind' : 'ts.simple',
't0' : t0,
't1' : t1,
'dt' : None,
'n_step' : n_step, # has precedence over dt!
'verbose' : 1,
}
options = {
'nls' : 'newton',
'ls' : 'ls',
'ts' : 'ts',
'save_times' : 'all',
}
diffusion/time_poisson_explicit.py
Description
Transient Laplace equation.
The same example as time_poisson.py, but using the short syntax of keywords, and explicit time-stepping.
Find π (π‘) for π‘ β [0, π‘final ] such that:
β«οΈ β«οΈ
ππ
π + πβπ Β· βπ = 0 , βπ .
Ξ© ππ‘ Ξ©
source code
r"""
Transient Laplace equation.
The same example as time_poisson.py, but using the short syntax of keywords,
and explicit time-stepping.
Find :math:`T(t)` for :math:`t \in [0, t_{\rm final}]` such that:
.. math::
\int_{\Omega} s \pdiff{T}{t}
+ \int_{\Omega} c \nabla s \cdot \nabla T
= 0
\;, \quad \forall s \;.
"""
from __future__ import absolute_import
from sfepy import data_dir
regions = {
'Omega' : 'all',
'Gamma_Left' : ('vertices in (x < 0.00001)', 'facet'),
'Gamma_Right' : ('vertices in (x > 0.099999)', 'facet'),
}
fields = {
'temperature' : ('real', 1, 'Omega', 1),
}
variables = {
'T' : ('unknown field', 'temperature', 0, 1),
's' : ('test field', 'temperature', 'T'),
}
ebcs = {
't1' : ('Gamma_Left', {'T.0' : 2.0}),
't2' : ('Gamma_Right', {'T.0' : -2.0}),
}
ics = {
'ic' : ('Omega', {'T.0' : 'get_ic'}),
}
functions = {
'get_ic' : (get_ic,),
}
integrals = {
'i' : 1,
}
equations = {
'Temperature' :
"""dw_dot.i.Omega( s, dT/dt )
+ dw_laplace.i.Omega( coef.val, s, T[-1] ) = 0"""
}
solvers = {
'ls' : ('ls.scipy_direct', {}),
'newton' : ('nls.newton', {
'i_max' : 1,
'is_linear' : True,
}),
'ts' : ('ts.simple', {
't0' : 0.0,
't1' : 0.07,
'dt' : 0.00002,
(continues on next page)
options = {
'ls' : 'ls',
'ts' : 'ts',
'save_times' : 100,
'output_format' : 'h5',
}
diffusion/time_poisson_interactive.py
Description
Transient Laplace equation (heat equation) with non-constant initial conditions given by a function, using commands
for interactive use.
The script allows setting various simulation parameters, namely:
β’ the diffusivity coefficient
β’ the max. initial condition value
β’ temperature field approximation order
β’ uniform mesh refinement
The example shows also how to probe the results.
In the SfePy top-level directory the following command can be used to get usage information:
python examples/diffusion/time_poisson_interactive.py -h
source code
#!/usr/bin/env python
"""
Transient Laplace equation (heat equation) with non-constant initial conditions
given by a function, using commands for interactive use.
In the SfePy top-level directory the following command can be used to get usage
information::
python sfepy/examples/diffusion/time_poisson_interactive.py -h
(continues on next page)
import numpy as nm
import matplotlib.pyplot as plt
def gen_probes(problem):
"""
Define a line probe and a circle probe.
"""
# Use enough points for higher order approximations.
n_point = 1000
fig = plt.figure(1)
ax = plt.subplot(2, 2, 2 * ax_num + 1)
ax.cla()
pars, vals = results['T']
ax.plot(pars, vals, label=r'$T$', lw=1, ls='-', marker='+', ms=3)
dx = 0.05 * (pars[-1] - pars[0])
ax.set_xlim(pars[0] - dx, pars[-1] + dx)
ax.set_ylabel('temperature')
ax.set_xlabel('probe %s' % label, fontsize=8)
ax.legend(loc='best', fontsize=10)
ax = plt.subplot(2, 2, 2 * ax_num + 2)
ax.cla()
pars, vals = results['dvel']
for ic in range(vals.shape[1]):
ax.plot(pars, vals[:, ic], label=r'$w_{%d}$' % (ic + 1),
lw=1, ls='-', marker='+', ms=3)
dx = 0.05 * (pars[-1] - pars[0])
ax.set_xlim(pars[0] - dx, pars[-1] + dx)
ax.set_ylabel('diffusion velocity')
ax.set_xlabel('probe %s' % label, fontsize=8)
ax.legend(loc='best', fontsize=10)
helps = {
'diffusivity' : 'the diffusivity coefficient [default: %(default)s]',
'ic_max' : 'the max. initial condition value [default: %(default)s]',
'order' : 'temperature field approximation order [default: %(default)s]',
'refine' : 'uniform mesh refinement level [default: %(default)s]',
'probe' : 'probe the results',
'show' : 'show the probing results figure, if --probe is used',
}
def main():
from sfepy import data_dir
parser = ArgumentParser(description=__doc__,
formatter_class=RawDescriptionHelpFormatter)
parser.add_argument('--version', action='version', version='%(prog)s')
parser.add_argument('--diffusivity', metavar='float', type=float,
action='store', dest='diffusivity',
default=1e-5, help=helps['diffusivity'])
parser.add_argument('--ic-max', metavar='float', type=float,
(continues on next page)
output('using values:')
output(' diffusivity:', options.diffusivity)
output(' max. IC value:', options.ic_max)
output('uniform mesh refinement level:', options.refine)
if options.refine > 0:
for ii in range(options.refine):
output('refine %d...' % ii)
domain = domain.refine()
output('... %d nodes %d elements'
% (domain.shape.n_nod, domain.shape.n_el))
t1 = Term.new('dw_diffusion(m.diffusivity, s, T)',
integral, omega, m=m, s=s, T=T)
(continues on next page)
# Boundary conditions.
ebc1 = EssentialBC('T1', left, {'T.0' : 2.0})
ebc2 = EssentialBC('T2', right, {'T.0' : -2.0})
# Initial conditions.
def get_ic(coors, ic):
x, y, z = coors.T
return 2 - 40.0 * x + options.ic_max * nm.sin(4 * nm.pi * x / 0.1)
ic_fun = Function('ic_fun', get_ic)
ic = InitialCondition('ic', omega, {'T.0' : ic_fun})
pb = Problem('heat', equations=eqs)
pb.set_bcs(ebcs=Conditions([ebc1, ebc2]))
pb.set_ics(Conditions([ic]))
variables = pb.get_initial_state()
init_fun, prestep_fun, _poststep_fun = pb.get_tss_functions()
ls = ScipyDirect({})
nls_status = IndexedStruct()
nls = Newton({'is_linear' : True}, lin_solver=ls, status=nls_status)
tss = SimpleTimeSteppingSolver({'t0' : 0.0, 't1' : 100.0, 'n_step' : 11},
nls=nls, context=pb, verbose=True)
pb.set_solver(tss)
if options.probe:
# Prepare probe data.
probes, labels = gen_probes(pb)
ev = pb.evaluate
order = 2 * (options.order - 1)
suffix = tss.ts.suffix
def poststep_fun(ts, vec):
_poststep_fun(ts, vec)
all_results = []
for ii, probe in enumerate(probes):
fig, results = probe_results(ii, T, dvel, probe, labels[ii])
all_results.append(results)
plt.tight_layout()
fig.savefig('time_poisson_interactive_probe_%s.png'
% (suffix % ts.step), bbox_inches='tight')
else:
poststep_fun = _poststep_fun
pb.time_update(tss.ts)
variables.apply_ebc()
tss_status = IndexedStruct()
tss(variables.get_state(pb.active_only, force=True),
init_fun=init_fun, prestep_fun=prestep_fun, poststep_fun=poststep_fun,
status=tss_status)
output(tss_status)
if options.show:
plt.show()
if __name__ == '__main__':
main()
homogenization
homogenization/homogenization_opt.py
Description
missing description!
source code
# material function
def get_mat(coors, mode, pb):
if mode == 'qp':
cnf = pb.conf
# get material coefficients
if hasattr(cnf, 'opt_data'):
# from optim.
E_f, nu_f, E_m, nu_m = cnf.opt_data['mat_params']
else:
# given values
E_f, nu_f, E_m, nu_m = 160.e9, 0.28, 5.e9, 0.45
nqp = coors.shape[0]
nel = pb.domain.mesh.n_el
nqpe = nqp // nel
out = nm.zeros((nqp, 6, 6), dtype=nm.float64)
def optimization_hook(pb):
cnf = pb.conf
out = []
yield pb, out
if hasattr(cnf, 'opt_data'):
# store homogenized tensor
pb.conf.opt_data['D_homog'] = out[-1].D.copy()
yield None
def define(is_opt=False):
filename_mesh = data_dir + '/meshes/3d/matrix_fiber_rand.vtk'
mesh = Mesh.from_file(filename_mesh)
bbox = mesh.get_bounding_box()
regions = {
'Y' : 'all',
'Ym' : ('cells of group 7', 'cell'),
'Yf' : ('r.Y -c r.Ym', 'cell'),
}
functions = {
'get_mat': (lambda ts, coors, mode=None, problem=None, **kwargs:
get_mat(coors, mode, problem),),
'match_x_plane' : (per.match_x_plane,),
'match_y_plane' : (per.match_y_plane,),
'match_z_plane' : (per.match_z_plane,),
}
materials = {
'mat': 'get_mat',
}
fields = {
'corrector' : ('real', 3, 'Y', 1),
}
variables = {
'u': ('unknown field', 'corrector'),
'v': ('test field', 'corrector', 'u'),
'Pi': ('parameter field', 'corrector', 'u'),
'Pi1': ('parameter field', 'corrector', '(set-to-None)'),
'Pi2': ('parameter field', 'corrector', '(set-to-None)'),
}
epbcs = {
'periodic_x' : (['Left', 'Right'], {'u.all' : 'u.all'}, 'match_x_plane'),
'periodic_y' : (['Near', 'Far'], {'u.all' : 'u.all'}, 'match_y_plane'),
'periodic_z' : (['Top', 'Bottom'], {'u.all' : 'u.all'}, 'match_z_plane'),
}
options = {
'coefs': 'coefs',
'requirements': 'requirements',
'volume': { 'variables' : ['u'], 'expression' : 'ev_volume.5.Y( u )' },
'output_dir': 'output',
'coefs_filename': 'coefs_le',
}
equation_corrs = {
'balance_of_forces':
"""dw_lin_elastic.5.Y(mat.D, v, u)
= - dw_lin_elastic.5.Y(mat.D, v, Pi)"""
}
coefs = {
'D' : {
'requires' : ['pis', 'corrs_rs'],
'expression' : 'dw_lin_elastic.5.Y(mat.D, Pi1, Pi2 )',
'set_variables': [('Pi1', ('pis', 'corrs_rs'), 'u'),
('Pi2', ('pis', 'corrs_rs'), 'u')],
'class' : cb.CoefSymSym,
},
'vol': {
'regions': ['Ym', 'Yf'],
'expression': 'ev_volume.5.%s(u)',
'class': cb.VolumeFractions,
},
'filenames' : {},
}
requirements = {
'pis' : {
'variables' : ['u'],
'class' : cb.ShapeDimDim,
},
'corrs_rs' : {
'requires' : ['pis'],
'ebcs' : ['fixed_u'],
'epbcs' : all_periodic,
'equations' : equation_corrs,
(continues on next page)
solvers = {
'ls' : ('ls.scipy_direct', {}),
'newton' : ('nls.newton', {
'i_max' : 1,
'eps_a' : 1e-4,
'problem': 'linear',
})
}
if is_opt:
options.update({
'parametric_hook': 'optimization_hook',
'float_format': '%.16e',
})
return locals()
homogenization/linear_elastic_mM.py
Description
missing description!
source code
if isinstance(state, dict):
pass
else:
stress = pb.evaluate('ev_cauchy_stress.i.Omega(solid.D, u)',
mode='el_avg')
strain = pb.evaluate('ev_cauchy_strain.i.Omega(u)',
mode='el_avg')
out['cauchy_strain'] = Struct(name='output_data',
mode='cell', data=strain,
dofs=None)
(continues on next page)
if pb.conf.options.get('recover_micro', False):
rname = pb.conf.options.recovery_region
region = pb.domain.regions[rname]
filename = os.path.join(os.path.dirname(pb.get_output_name()),
'recovery_region.vtk')
save_recovery_region(pb, rname, filename=filename);
recover_micro_hook(pb.conf.options.micro_filename,
region, {'strain' : rstrain},
output_dir=pb.conf.options.output_dir)
return out
regenerate = True
return out
functions = {
'get_elements' : (get_elements,),
'get_homog' : (get_homog,),
}
regions = {
'Omega' : 'all',
'Left' : ('vertices in (x < 0.001)', 'facet'),
'Right' : ('vertices in (x > 0.099)', 'facet'),
'Recovery' : 'cells by get_elements',
}
materials = {
(continues on next page)
fields = {
'3_displacement' : ('real', 3, 'Omega', 1),
}
integrals = {
'i' : 1,
}
variables = {
'u' : ('unknown field', '3_displacement', 0),
'v' : ('test field', '3_displacement', 'u'),
}
ebcs = {
'Fixed' : ('Left', {'u.all' : 0.0}),
'PerturbedSurface' : ('Right', {'u.0' : 0.02, 'u.1' : 0.0, 'u.2' : 0.0}),
}
equations = {
'balance_of_forces' :
"""dw_lin_elastic.i.Omega(solid.D, v, u) = 0""",
}
solvers = {
'ls' : ('ls.scipy_direct', {}),
'newton' : ('nls.newton', {
'i_max' : 1,
'eps_a' : 1e-6,
}),
}
micro_filename = base_dir \
+ '/examples/homogenization/linear_homogenization_up.py'
options = {
'nls' : 'newton',
'ls' : 'ls',
'output_dir' : 'output',
'post_process_hook' : 'post_process',
'output_prefix' : 'macro:',
'recover_micro': True,
'recovery_region' : 'Recovery',
'micro_filename' : micro_filename,
}
homogenization/linear_elasticity_opt.py
Description
missing description!
source code
def optimization_hook(pb):
cnf = pb.conf
out = []
yield pb, out
state = out[-1][1].get_state_parts()
coors = pb.domain.cmesh.coors
displ = state['u'].reshape((coors.shape[0],3))
# elongation
mcoors = coors[cnf.mnodes, 2]
mdispl = displ[cnf.mnodes, 2]
dl = (mdispl[1] - mdispl[0]) / (mcoors[1] - mcoors[0])
if hasattr(cnf, 'opt_data'):
# compute slope of the force-elongation curve
cnf.opt_data['k'] = cnf.F / dl
yield None
nqp = coors.shape[0]
return {'D': nm.tile(D, (nqp, 1, 1))}
def define(is_opt=False):
filename_mesh = UserMeshIO(mesh_hook)
mnodes = (107, 113) # nodes for elongation eval.
regions = {
'Omega': 'all',
'Bottom': ('vertices in (z < 0.001)', 'facet'),
'Top': ('vertices in (z > 0.099)', 'facet'),
}
functions = {
'get_mat': (lambda ts, coors, mode=None, problem=None, **kwargs:
get_mat(coors, mode, problem),),
}
fields = {
'displacement': ('real', 'vector', 'Omega', 1),
}
variables = {
'u': ('unknown field', 'displacement', 0),
'v': ('test field', 'displacement', 'u'),
}
ebcs = {
'FixedBottom': ('Bottom', {'u.all': 0.0}),
'FixedTop': ('Top', {'u.0': 0.0, 'u.1': 0.0}),
}
equations = {
'balance_of_forces' :
"""dw_lin_elastic.5.Omega(solid.D, v, u)
= dw_surface_ltr.5.Top(load.val, v)""",
}
solvers = {
'ls': ('ls.scipy_direct', {}),
'newton': ('nls.newton', {'eps_a': 1e-6, 'eps_r': 1.e-6,
'check': 0, 'problem': 'nonlinear'}),
}
return locals()
homogenization/linear_homogenization.py
Description
Compute homogenized elastic coefficients for a given heterogeneous linear elastic microstructure, see [1] for details or
[2] and [3] for a quick explanation.
[1] D. Cioranescu, J.S.J. Paulin: Homogenization in open sets with holes. Journal of Mathematical Analysis and
Applications 71(2), 1979, pages 590-607. https://doi.org/10.1016/0022-247X(79)90211-7
[2] J. Pinho-da-Cruz, J.A. Oliveira, F. Teixeira-Dias: Asymptotic homogenisation in linear elasticity. Part I: Mathe-
matical formulation and finite element modelling. Computational Materials Science 45(4), 2009, pages 1073-1080.
http://dx.doi.org/10.1016/j.commatsci.2009.02.025
[3] J. Pinho-da-Cruz, J.A. Oliveira, F. Teixeira-Dias: Asymptotic homogenisation in linear elasticity. Part II: Finite
element procedures and multiscale applications. Computational Materials Science 45(4), 2009, pages 1081-1096.
http://dx.doi.org/10.1016/j.commatsci.2009.01.027
source code
r"""
Compute homogenized elastic coefficients for a given heterogeneous linear
elastic microstructure, see [1] for details or [2] and [3] for a quick
explanation.
out = {}
dim = corrs['corrs_le']['u_00'].shape[1]
mic_u = - compute_micro_u(corrs['corrs_le'], macro['strain'], 'u', dim)
out['u_mic'] = Struct(name='output_data',
mode='vertex', data=mic_u,
var_name='u', dofs=None)
stress_Y, strain_Y = \
compute_stress_strain_u(pb, 'i', 'Y', 'mat.D', 'u', mic_u)
stress_Y += \
compute_mac_stress_part(pb, 'i', 'Y', 'mat.D', 'u', macro['strain'])
out['cauchy_strain'] = Struct(name='output_data',
mode='cell', data=strain,
dofs=None)
out['cauchy_stress'] = Struct(name='output_data',
mode='cell', data=stress_Y,
dofs=None)
return out
regions = {
'Y': 'all',
'Ym': 'cells of group 1',
'Yc': 'cells of group 2',
}
regions.update(define_box_regions(dim, region_lbn, region_rtf))
materials = {
'mat': ({'D': {'Ym': stiffness_from_youngpoisson(dim, 7.0e9, 0.4),
'Yc': stiffness_from_youngpoisson(dim, 70.0e9, 0.2)}},),
}
fields = {
(continues on next page)
variables = {
'u': ('unknown field', 'corrector', 0),
'v': ('test field', 'corrector', 'u'),
'Pi': ('parameter field', 'corrector', 'u'),
'Pi1': ('parameter field', 'corrector', '(set-to-None)'),
'Pi2': ('parameter field', 'corrector', '(set-to-None)'),
}
functions = {
'match_x_plane': (per.match_x_plane,),
'match_y_plane': (per.match_y_plane,),
'match_z_plane': (per.match_z_plane,),
}
ebcs = {
'fixed_u': ('Corners', {'u.all': 0.0}),
}
if dim == 3:
epbcs = {
'periodic_x': (['Left', 'Right'], {'u.all': 'u.all'},
'match_x_plane'),
'periodic_y': (['Near', 'Far'], {'u.all': 'u.all'},
'match_y_plane'),
'periodic_z': (['Top', 'Bottom'], {'u.all': 'u.all'},
'match_z_plane'),
}
else:
epbcs = {
'periodic_x': (['Left', 'Right'], {'u.all': 'u.all'},
'match_x_plane'),
'periodic_y': (['Bottom', 'Top'], {'u.all': 'u.all'},
'match_y_plane'),
}
integrals = {
'i': 2,
}
options = {
'coefs': 'coefs',
'requirements': 'requirements',
'ls': 'ls', # linear solver to use
'volume': {'expression': 'ev_volume.i.Y(u)'},
'output_dir': 'output',
'coefs_filename': 'coefs_le',
'recovery_hook': 'recovery_le',
(continues on next page)
equation_corrs = {
'balance_of_forces':
"""dw_lin_elastic.i.Y(mat.D, v, u) =
- dw_lin_elastic.i.Y(mat.D, v, Pi)"""
}
coefs = {
'D': {
'requires': ['pis', 'corrs_rs'],
'expression': expr_coefs,
'set_variables': [('Pi1', ('pis', 'corrs_rs'), 'u'),
('Pi2', ('pis', 'corrs_rs'), 'u')],
'class': cb.CoefSymSym,
},
'filenames': {},
}
requirements = {
'pis': {
'variables': ['u'],
'class': cb.ShapeDimDim,
'save_name': 'corrs_pis',
},
'corrs_rs': {
'requires': ['pis'],
'ebcs': ['fixed_u'],
'epbcs': all_periodic,
'equations': equation_corrs,
'set_variables': [('Pi', 'pis', 'u')],
'class': cb.CorrDimDim,
'save_name': 'corrs_le',
'is_linear': True,
},
}
solvers = {
'ls': ('ls.scipy_direct', {}),
'newton': ('nls.newton', {
'i_max': 1,
'eps_a': 1e-4,
})
}
homogenization/linear_homogenization_postproc.py
Description
This example shows how to use the VTK postprocessing functions.
source code
"""
This example shows how to use the VTK postprocessing functions.
"""
options.update({
'post_process_hook' : 'post_process',
})
mesh = problem.domain.mesh
mesh_name = mesh.name[mesh.name.rfind(osp.sep) + 1:]
matrix_surf = get_vtk_surface(matrix)
matrix_surf_tri = tetrahedralize_vtk_mesh(matrix_surf)
write_vtk_to_file('%s_mat1_surface.vtk' % mesh_name, matrix_surf_tri)
matrix_edges = get_vtk_edges(matrix)
write_vtk_to_file('%s_mat1_edges.vtk' % mesh_name, matrix_edges)
return out
homogenization/linear_homogenization_up.py
Description
Compute homogenized elastic coefficients for a given heterogeneous linear elastic microstructure, see [1] for details or
[2] and [3] for a quick explanation. The mixed formulation, where displacements and pressures are as unknowns, is
used in this example.
[1] D. Cioranescu, J.S.J. Paulin: Homogenization in open sets with holes. Journal of Mathematical Analysis and
Applications 71(2), 1979, pages 590-607. https://doi.org/10.1016/0022-247X(79)90211-7
[2] J. Pinho-da-Cruz, J.A. Oliveira, F. Teixeira-Dias: Asymptotic homogenisation in linear elasticity. Part I: Mathe-
matical formulation and finite element modelling. Computational Materials Science 45(4), 2009, pages 1073-1080.
http://dx.doi.org/10.1016/j.commatsci.2009.02.025
[3] J. Pinho-da-Cruz, J.A. Oliveira, F. Teixeira-Dias: Asymptotic homogenisation in linear elasticity. Part II: Finite
element procedures and multiscale applications. Computational Materials Science 45(4), 2009, pages 1081-1096.
http://dx.doi.org/10.1016/j.commatsci.2009.01.027
source code
r"""
Compute homogenized elastic coefficients for a given heterogeneous linear
elastic microstructure, see [1] for details or [2] and [3] for a quick
explanation. The mixed formulation, where displacements and pressures are
as unknowns, is used in this example.
out['u_mic'] = Struct(name='output_data',
mode='vertex', data=mic_u,
var_name='u', dofs=None)
out['p_mic'] = Struct(name='output_data',
(continues on next page)
stress_Y, strain_Y = \
compute_stress_strain_u(pb, 'i', 'Y', 'mat.D', 'u', mic_u)
stress_Y += \
compute_mac_stress_part(pb, 'i', 'Y', 'mat.D', 'u', macro['strain'])
add_stress_p(stress_Y, pb, 'i', 'Y', 'p', mic_p)
out['cauchy_strain'] = Struct(name='output_data',
mode='cell', data=strain,
dofs=None)
out['cauchy_stress'] = Struct(name='output_data',
mode='cell', data=stress_Y,
dofs=None)
return out
dim = 3
filename_mesh = data_dir + '/meshes/3d/matrix_fiber.mesh'
region_lbn = (0, 0, 0)
region_rtf = (1, 1, 1)
regions = {
'Y': 'all',
'Ym': 'cells of group 1',
'Yc': 'cells of group 2',
}
regions.update(define_box_regions(dim, region_lbn, region_rtf))
materials = {
'mat': ({'D': {'Ym': stiffness_from_youngpoisson_mixed(dim, 7.0e9, 0.4),
'Yc': stiffness_from_youngpoisson_mixed(dim, 70.0e9, 0.2)},
'gamma': {'Ym': 1.0/bulk_from_youngpoisson(7.0e9, 0.4),
'Yc': 1.0/bulk_from_youngpoisson(70.0e9, 0.2)}},),
}
fields = {
'corrector_u': ('real', dim, 'Y', 1),
'corrector_p': ('real', 1, 'Y', 0),
}
variables = {
'u': ('unknown field', 'corrector_u'),
'v': ('test field', 'corrector_u', 'u'),
'p': ('unknown field', 'corrector_p'),
'q': ('test field', 'corrector_p', 'p'),
'Pi': ('parameter field', 'corrector_u', 'u'),
(continues on next page)
functions = {
'match_x_plane': (per.match_x_plane,),
'match_y_plane': (per.match_y_plane,),
'match_z_plane': (per.match_z_plane,),
}
ebcs = {
'fixed_u': ('Corners', {'u.all': 0.0}),
}
if dim == 3:
epbcs = {
'periodic_x': (['Left', 'Right'], {'u.all': 'u.all'},
'match_x_plane'),
'periodic_y': (['Near', 'Far'], {'u.all': 'u.all'},
'match_y_plane'),
'periodic_z': (['Top', 'Bottom'], {'u.all': 'u.all'},
'match_z_plane'),
}
else:
epbcs = {
'periodic_x': (['Left', 'Right'], {'u.all': 'u.all'},
'match_x_plane'),
'periodic_y': (['Bottom', 'Top'], {'u.all': 'u.all'},
'match_y_plane'),
}
integrals = {
'i': 2,
}
options = {
'coefs': 'coefs',
'requirements': 'requirements',
'ls': 'ls', # linear solver to use
'volume': {'value': get_box_volume(dim, region_lbn, region_rtf), },
'output_dir': 'output',
'coefs_filename': 'coefs_le_up',
'recovery_hook': 'recovery_le',
'multiprocessing': False,
}
equation_corrs = {
'balance_of_forces':
(continues on next page)
coefs = {
'elastic_u': {
'requires': ['pis', 'corrs_rs'],
'expression': 'dw_lin_elastic.i.Y(mat.D, Pi1u, Pi2u)',
'set_variables': [('Pi1u', ('pis', 'corrs_rs'), 'u'),
('Pi2u', ('pis', 'corrs_rs'), 'u')],
'class': cb.CoefSymSym,
},
'elastic_p': {
'requires': ['corrs_rs'],
'expression': 'dw_dot.i.Y(mat.gamma, Pi1p, Pi2p)',
'set_variables': [('Pi1p', 'corrs_rs', 'p'),
('Pi2p', 'corrs_rs', 'p')],
'class': cb.CoefSymSym,
},
'D': {
'requires': ['c.elastic_u', 'c.elastic_p'],
'class': cb.CoefSum,
},
'filenames': {},
}
requirements = {
'pis': {
'variables': ['u'],
'class': cb.ShapeDimDim,
},
'corrs_rs': {
'requires': ['pis'],
'ebcs': ['fixed_u'],
'epbcs': all_periodic,
'equations': equation_corrs,
'set_variables': [('Pi', 'pis', 'u')],
'class': cb.CorrDimDim,
'save_name': 'corrs_le',
'is_linear': True,
},
}
solvers = {
'ls': ('ls.auto_iterative', {}),
'newton': ('nls.newton', {
'i_max': 1,
(continues on next page)
homogenization/material_opt.py
Description
See the Material Identification tutorial for a comprehensive description of this example.
source code
#!/usr/bin/env python
"""
See the :ref:`sec-mat_optim` tutorial for a comprehensive description of this
example.
"""
from __future__ import print_function
from __future__ import absolute_import
import sys
sys.path.append('.')
import numpy as nm
from scipy.optimize import fmin_tnc
import sfepy
from sfepy.base.base import Struct
from sfepy.base.log import Log
class MaterialOptimizer(object):
@staticmethod
def create_app(filename, is_homog=False, **kwargs):
from sfepy.base.conf import ProblemConf, get_standard_keywords
from sfepy.homogenization.homogen_app import HomogenizationApp
from sfepy.applications import PDESolverApp
if is_homog:
(continues on next page)
else:
app = PDESolverApp(conf, options, 'material_opt_macro:')
app.conf.opt_data = {}
opts = conf.options
if hasattr(opts, 'parametric_hook'): # Parametric study.
parametric_hook = conf.get_function(opts.parametric_hook)
app.parametrize(parametric_hook)
return app
@staticmethod
def rotate_mat(D, angle):
s = nm.sin(angle)
c = nm.cos(angle)
s2 = s**2
c2 = c**2
sc = s * c
T = nm.array([[c2, 0, s2, 0, 2*sc,0],
[0, 1, 0, 0, 0, 0],
[s2, 0, c2, 0, -2*sc, 0],
[0, 0, 0, c, 0, -s],
[-sc, 0, sc, 0, c2 - s2, 0],
[0, 0, 0, s, 0, c]])
mic_od['coefs'] = {}
mic_od['mat_params'] = x
self.micro_app()
(continues on next page)
D = mic_od['D_homog']
val = 0.0
aux = []
for phi, exp_k in self.exp_data:
print('phi = %d' % phi)
comp_k = mac_od['k']
val += (1.0 - comp_k / exp_k)**2
aux.append((comp_k, exp_k))
val = nm.sqrt(val)
self.x.append(x)
self.eval_f.append(val)
return val
def material_optimize(self):
x0 = self.x0
bnds = zip(self.x_real2norm(self.x_L), self.x_real2norm(self.x_U))
feval = lambda x: self.matopt_eval(self.x_norm2real(x))
istep = lambda x: self.iter_step(self.x_norm2real(x))
self.iter_step(self.x_norm2real(x0), first_step=True)
self.log(finished=True)
return self.x_norm2real(xopt[0])
def main():
srcdir = sfepy.base_dir + '/examples/homogenization/'
micro_filename = srcdir + 'homogenization_opt.py'
(continues on next page)
optim_par = mo.material_optimize()
print('optimized parameters: ', optim_par)
if __name__ == '__main__':
main()
homogenization/nonlinear_homogenization.py
Description
missing description!
source code
# -*- coding: utf-8 -*-
import numpy as nm
from sfepy.homogenization.utils import define_box_regions
import sfepy.homogenization.coefs_base as cb
import sfepy.discrete.fem.periodic as per
from sfepy.base.base import Struct
from sfepy.terms.terms_hyperelastic_ul import\
HyperElasticULFamilyData, NeoHookeanULTerm, BulkPenaltyULTerm
from sfepy.terms.extmods.terms import sym2nonsym
from sfepy.discrete.functions import ConstantFunctionByRegion
from sfepy import data_dir
import sfepy.linalg as la
out['cauchy_stress'] = Struct(name='output_data',
mode='cell',
(continues on next page)
out['green_strain'] = Struct(name='output_data',
mode='cell',
data=strain,
dofs=None)
out['displacement'] = Struct(name='output_data',
mode='vertex',
data=ncoors[ii] - pb.get_mesh_coors(),
dofs=None)
micro_name = pb.get_output_name(extra='recovered_'
+ recovery_file_tag + suffix)
filename = op.join(output_dir, op.basename(micro_name))
fpv = pb.conf.options.get('file_per_var', False)
pb.save_state(filename, out=out, file_per_var=fpv)
update_var = pb.conf.options.mesh_update_variable
if pb.equations is None:
state_u = pb.create_variables([update_var])[update_var]
else:
state_u = pb.get_variables()[update_var]
if state_u.data[0] is None:
state_u.init_data()
state_u.set_data(
pb.domain.get_mesh_coors(actual=True) - pb.domain.get_mesh_coors())
state_u.field.clear_mappings()
family_data = pb.family_data(state_u, term.region,
term.integral, term.integration)
if len(state_u.field.mappings0) == 0:
state_u.field.save_mappings()
(continues on next page)
conf_mat = pb.conf.materials
solid_key = [key for key in conf_mat.keys() if 'solid' in key][0]
solid_mat = conf_mat[solid_key].values
mat = {}
for mat_key in ['mu', 'K']:
if isinstance(solid_mat[mat_key], dict):
mat_fun = ConstantFunctionByRegion({mat_key: solid_mat[mat_key]})
mat[mat_key] = mat_fun.function(ts=ts, coors=coors, mode='qp',
term=term, problem=pb)[mat_key].reshape((n_el, n_qp, 1, 1))
else:
mat[mat_key] = nm.ones((n_el, n_qp, 1, 1)) * solid_mat[mat_key]
shape = family_data.green_strain.shape[:2]
sym = family_data.green_strain.shape[-2]
dim2 = dim**2
fargs = [family_data.get(name)
for name in NeoHookeanULTerm.family_data_names]
stress = nm.empty(shape + (sym, 1), dtype=nm.float64)
tanmod = nm.empty(shape + (sym, sym), dtype=nm.float64)
NeoHookeanULTerm.stress_function(stress, mat['mu'], *fargs)
NeoHookeanULTerm.tan_mod_function(tanmod, mat['mu'], *fargs)
fargs = [family_data.get(name)
for name in BulkPenaltyULTerm.family_data_names]
stress_p = nm.empty(shape + (sym, 1), dtype=nm.float64)
tanmod_p = nm.empty(shape + (sym, sym), dtype=nm.float64)
BulkPenaltyULTerm.stress_function(stress_p, mat['K'], *fargs)
BulkPenaltyULTerm.tan_mod_function(tanmod_p, mat['K'], *fargs)
npts = nm.prod(shape)
J = family_data.det_f
mtx_f = family_data.mtx_f.reshape((npts, dim, dim))
out = {
'E': 0.5 * (la.dot_sequences(mtx_f, mtx_f, 'ATB') - nm.eye(dim)),
'A': ((tanmod_ns + stress_ns) / J).reshape((npts, dim2, dim2)),
'S': ((stress + stress_p) / J).reshape((npts, sym, 1)),
}
return out
options = {
'coefs': 'coefs',
'requirements': 'requirements',
'volume': {'expression': 'ev_volume.5.Y(u)'},
'output_dir': './output',
'coefs_filename': 'coefs_hyper_homog',
'multiprocessing': True,
'chunks_per_worker': 2,
'micro_update': {'coors': [('corrs_rs', 'u', 'mtx_e')]},
'mesh_update_variable': 'u',
'recovery_hook': 'recovery_hook',
'store_micro_idxs': [49, 81],
}
fields = {
'displacement': ('real', 'vector', 'Y', 1),
}
functions = {
'match_x_plane': (per.match_x_plane,),
'match_y_plane': (per.match_y_plane,),
'mat_fce': (lambda ts, coors, mode=None, term=None, problem=None, **kwargs:
def_mat(ts, mode, coors, term, problem),),
}
materials = {
'mat_he': 'mat_fce',
'solid': ({'K': 1000,
'mu': {'Ym': 100, 'Yc': 10},
},),
}
variables = {
'u': ('unknown field', 'displacement'),
'v': ('test field', 'displacement', 'u'),
'Pi': ('parameter field', 'displacement', 'u'),
'Pi1u': ('parameter field', 'displacement', '(set-to-None)'),
'Pi2u': ('parameter field', 'displacement', '(set-to-None)'),
}
regions = {
'Y': 'all',
'Ym': 'cells of group 1',
'Yc': 'cells of group 2',
}
epbcs = {
'periodic_ux': (['Left', 'Right'], {'u.all': 'u.all'}, 'match_x_plane'),
'periodic_uy': (['Bottom', 'Top'], {'u.all': 'u.all'}, 'match_y_plane'),
}
coefs = {
'A': {
'requires': ['pis', 'corrs_rs'],
'expression': 'dw_nonsym_elastic.3.Y(mat_he.A, Pi1u, Pi2u)',
'set_variables': [('Pi1u', ('pis', 'corrs_rs'), 'u'),
('Pi2u', ('pis', 'corrs_rs'), 'u')],
'class': cb.CoefNonSymNonSym,
},
'S': {
'expression': 'ev_integrate_mat.3.Y(mat_he.S, u)',
'class': cb.CoefOne,
}
}
requirements = {
'pis': {
'variables': ['u'],
'class': cb.ShapeDimDim,
},
'corrs_rs': {
'requires': ['pis'],
'ebcs': ['fixed_u'],
'epbcs': ['periodic_ux', 'periodic_uy'],
'equations': {
'balance_of_forces':
"""dw_nonsym_elastic.3.Y(mat_he.A, v, u)
= - dw_nonsym_elastic.3.Y(mat_he.A, v, Pi)"""
},
'set_variables': [('Pi', 'pis', 'u')],
'class': cb.CorrDimDim,
'save_name': 'corrs_hyper_homog',
},
}
solvers = {
'ls': ('ls.scipy_direct', {}),
'newton': ('nls.newton', {
'i_max': 1,
'eps_a': 1e-4,
'problem': 'nonlinear',
}),
}
homogenization/nonlinear_hyperelastic_mM.py
Description
missing description!
source code
import numpy as nm
import six
hyperelastic_data = {}
out['cauchy_stress'] = Struct(name='output_data',
mode='cell',
data=stress,
dofs=None)
out['green_strain'] = Struct(name='output_data',
mode='cell',
data=strain,
dofs=None)
pb.update_materials_flag = 0
if pb.conf.options.get('recover_micro', False):
happ = pb.homogen_app
if pb.ts.step == 0:
rname = pb.conf.options.recovery_region
rcells = pb.domain.regions[rname].get_cells()
sh = hyperelastic_data['homog_mat_shape']
return out
update_var = problem.conf.options.mesh_update_variables[0]
state_u = problem.equations.variables[update_var]
state_u.field.clear_mappings()
family_data = problem.family_data(state_u, term.region,
term.integral, term.integration)
mtx_f = family_data.mtx_f.reshape((coors.shape[0],)
+ family_data.mtx_f.shape[-2:])
if hasattr(problem, 'mtx_f_prev'):
rel_mtx_f = la.dot_sequences(mtx_f, nm.linalg.inv(problem.mtx_f_prev),
'AB')
else:
rel_mtx_f = mtx_f
problem.mtx_f_prev = mtx_f.copy()
hyperelastic_data['time'] = ts.step
hyperelastic_data['homog_mat_shape'] = family_data.det_f.shape[:2]
hyperelastic_data['homog_mat'] = \
{k: nm.array(v) for k, v in six.iteritems(out)}
return out
pb.iiter = it
pb.update_materials_flag = True
pb.update_materials()
pb.update_materials_flag = False
class MyEvaluator(Evaluator):
def eval_residual(self, vec, is_full=False):
if not is_full:
vec = self.problem.equations.make_full_vec(vec)
vec_r = self.problem.equations.eval_residuals(vec * 0)
return vec_r
def ulf_init(pb):
pb.family_data = HyperElasticULFamilyData()
pb_vars = pb.get_variables()
pb_vars['u'].init_data()
pb.update_materials_flag = True
pb.iiter = 0
options = {
'output_dir': 'output',
'mesh_update_variables': ['u'],
'nls_iter_hook': ulf_iteration_hook,
'pre_process_hook': ulf_init,
'micro_filename': 'examples/homogenization/nonlinear_homogenization.py',
'recover_micro': True,
'recovery_region': 'Recovery',
'post_process_hook': post_process,
'user_evaluator': MyEvaluator,
}
materials = {
'solid': 'get_homog',
}
fields = {
'displacement': ('real', 'vector', 'Omega', 1),
}
variables = {
'u': ('unknown field', 'displacement'),
'v': ('test field', 'displacement', 'u'),
}
regions = {
'Omega': 'all',
'Left': ('vertices in (x < 0.001)', 'facet'),
'Bottom': ('vertices in (y < 0.001 )', 'facet'),
'Recovery': ('cell 49, 81', 'cell'),
}
ebcs = {
'l': ('Left', {'u.all': 0.0}),
'b': ('Bottom', {'u.all': 'move_bottom'}),
}
return out
functions = {
'move_bottom': (move_bottom,),
'get_homog': (get_homog_mat,),
}
equations = {
'balance_of_forces':
"""dw_nonsym_elastic.1.Omega(solid.A, v, u)
= - dw_lin_prestress.1.Omega(solid.S, v)""",
}
solvers = {
'ls': ('ls.scipy_direct', {}),
'newton': ('nls.newton', {
'eps_a': 1e-3,
'eps_r': 1e-3,
'i_max': 20,
}),
'ts': ('ts.simple', {
't0': 0,
't1': 1,
'n_step': 3 + 1,
'verbose': 1,
})
}
homogenization/perfusion_micro.py
Description
Homogenization of the Darcy flow in a thin porous layer. The reference cell is composed of the matrix representing
the dual porosity and of two disconnected channels representing the primary porosity, see paper [1].
[1] E. Rohan, V. LukeΕ‘: Modeling Tissue Perfusion Using a Homogenized Model with Layer-wise Decomposition.
IFAC Proceedings Volumes 45(2), 2012, pages 1029-1034. https://doi.org/10.3182/20120215-3-AT-3016.00182
source code
return m1, m2
slev = ''
micro_nnod = pb.domain.mesh.n_nod
channels = {}
for k in six.iterkeys(macro):
if 'press' in k:
channels[k[-1]] = 1
channels = list(channels.keys())
varnames = ['pM']
for ch in channels:
nodes_Y[ch] = pb.domain.regions['Y' + ch].vertices
varnames.append('p' + ch)
press = {}
# matrix
press['M'] = \
corrs['corrs_%s_gamma_p' % pb_def['name']]['pM'] * macro['g_p'] + \
corrs['corrs_%s_gamma_m' % pb_def['name']]['pM'] * macro['g_m']
out = {}
# channels
for ch in channels:
press_mac = macro['press' + ch][0, 0]
press_mac_grad = macro['pressg' + ch]
nnod = corrs['corrs_%s_pi%s' % (pb_def['name'], ch)]\
['p%s_0' % ch].shape[0]
press_mic += \
compute_p_from_macro(press_mac_grad[nm.newaxis,nm.newaxis, :, :],
micro_coors[nodes_Y[ch]], 0,
centre=centre_Y, extdim=-1).reshape((nnod, 1))
pvars['p' + ch].set_data(press_mic)
dvel = pb.evaluate('ev_diffusion_velocity.iV.Y%s(mat1%s.k, p%s)'
% (ch, ch, ch),
var_dict={'p' + ch: pvars['p' + ch]},
mode='el_avg')
pvars['pM'].set_data(press['M'])
dvel = pb.evaluate('%e * ev_diffusion_velocity.iV.YM(mat1M.k, pM)' % eps0,
var_dict={'pM': pvars['pM']}, mode='el_avg')
return out
geoms = {
'2_4': ['2_4_Q1', '2', 5],
'3_8': ['3_8_Q1', '4', 5],
'3_4': ['3_4_P1', '3', 3],
}
pb_def = {
'name': '3d_2ch',
'mesh_filename': data_dir + '/meshes/3d/perfusion_micro3d.mesh',
'dim': 3,
'geom': geoms['3_4'],
'eps0': 1.0e-2,
'param_h': 1.0,
'param_kappa_m': 0.1,
'matrix_mat_el_grp': 3,
'channels': {
'A': {
'mat_el_grp': 1,
'fix_nd_grp': (4, 1),
'io_nd_grp': [1, 2, 3],
'param_kappa_ch': 1.0,
},
'B': {
'mat_el_grp': 2,
'fix_nd_grp': (14, 11),
'io_nd_grp': [11, 12, 13],
'param_kappa_ch': 2.0,
},
},
(continues on next page)
filename_mesh = pb_def['mesh_filename']
eps0 = pb_def['eps0']
param_h = pb_def['param_h']
# integrals
integrals = {
'iV': 2,
'iS': 2,
}
functions = {
'match_x_plane': (match_x_plane,),
'match_y_plane': (match_y_plane,),
}
aux = []
for ch, val in six.iteritems(pb_def['channels']):
aux.append('r.bYM' + ch)
# basic regions
regions = {
'Y': 'all',
'YM': 'cells of group %d' % pb_def['matrix_mat_el_grp'],
# periodic boundaries
'Pl': ('vertices in (x < 0.001)', 'facet'),
'Pr': ('vertices in (x > 0.999)', 'facet'),
'PlYM': ('r.Pl *v r.YM', 'facet'),
'PrYM': ('r.Pr *v r.YM', 'facet'),
'bYMp': ('r.bYp *v r.YM', 'facet', 'YM'),
'bYMm': ('r.bYm *v r.YM', 'facet', 'YM'),
'bYMpm': ('r.bYMp +v r.bYMm', 'facet', 'YM'),
}
# matrix/channel boundaries
regions.update({
'bYMchs': (' +v '.join(aux), 'facet', 'YM'),
'YMmchs': 'r.YM -v r.bYMchs',
})
if pb_def['dim'] == 2:
regions.update({
'bYm': ('vertices in (y < 0.001)', 'facet'),
'bYp': ('vertices in (y > 0.999)', 'facet'),
})
if pb_def['dim'] == 3:
regions.update({
'Pn': ('vertices in (y < 0.001)', 'facet'),
'Pf': ('vertices in (y > 0.999)', 'facet'),
'PnYM': ('r.Pn *v r.YM', 'facet'),
'PfYM': ('r.Pf *v r.YM', 'facet'),
'bYm': ('vertices in (z < 0.001)', 'facet'),
'bYp': ('vertices in (z > 0.999)', 'facet'),
})
# periodic boundary conditions - matrix, Y-direction
epbcs.update({
'periodic_yYM': (['PnYM', 'PfYM'], {'pM.0': 'pM.0'}, 'match_y_plane'),
})
reg_io = {}
ebcs_eta = {}
ebcs_gamma = {}
if 'fix_nd_grp' in val:
regions.update({
'fixedY' + ch: ('vertices of group %d' % val['fix_nd_grp'][0],
'vertex'),
})
ebcs_eta[ch] = []
for ch2, val2 in six.iteritems(pb_def['channels']):
aux = 'eta%s_bYM%s' % (ch, ch2)
if ch2 == ch:
ebcs.update({aux: ('bYM' + ch2, {'pM.0': 1.0})})
else:
ebcs.update({aux: ('bYM' + ch2, {'pM.0': 0.0})})
(continues on next page)
ebcs_eta[ch].append(aux)
# boundary conditions
# periodic boundary conditions - channels, X-direction
epbcs.update({
'periodic_xY' + ch: (['PlY' + ch, 'PrY' + ch],
{'p%s.0' % ch: 'p%s.0' % ch},
'match_x_plane'),
})
if pb_def['dim'] == 3:
regions.update({
'PnY' + ch: ('r.Pn *v r.Y' + ch, 'facet'),
'PfY' + ch: ('r.Pf *v r.Y' + ch, 'facet'),
})
# periodic boundary conditions - channels, Y-direction
epbcs.update({
'periodic_yY' + ch: (['PnY' + ch, 'PfY' + ch],
{'p%s.0' % ch: 'p%s.0' % ch},
'match_y_plane'),
})
reg_io[ch] = []
aux_bY = []
# channel: inputs/outputs
for i_io in range(len(val['io_nd_grp'])):
io = '%s_%d' % (ch, i_io+1)
# regions
aux = val['io_nd_grp'][i_io]
if 'fix_nd_grp' in val and val['fix_nd_grp'][1] == aux:
regions.update({
'bY%s' % io: ('vertices of group %d +v r.fixedY%s' % (aux, ch),
'facet', 'Y%s' % ch),
})
else:
regions.update({
'bY%s' % io: ('vertices of group %d' % aux,
'facet', 'Y%s' % ch),
})
aux_bY.append('r.bY%s' % io)
reg_io[ch].append('bY%s' % io)
regions.update({
'bY' + ch: (' +v '.join(aux_bY), 'facet', 'Y' + ch),
})
# channel: inputs/outputs
for i_io in range(len(val['io_nd_grp'])):
io = '%s_%d' % (ch, i_io + 1)
(continues on next page)
# boundary conditions
aux = 'fix_p%s_bY%s' % (ch, ion)
ebcs.update({
aux: ('bY%s' % ion, {'p%s.0' % ch: 0.0}),
})
lcbcs.update({
'imv' + ch: ('Y' + ch, {'ls%s.all' % ch: None}, None,
'integral_mean_value')
})
materials = {
'mat1M': ({'k': matk1},),
'mat2M': ({'k': matk2},),
}
fields = {
'corrector_M': ('real', 'scalar', 'YM', 1),
'vel_M': ('real', 'vector', 'YM', 1),
'vol_all': ('real', 'scalar', 'Y', 1),
}
variables = {
'pM': ('unknown field', 'corrector_M'),
'qM': ('test field', 'corrector_M', 'pM'),
'Pi_M': ('parameter field', 'corrector_M', '(set-to-None)'),
'corr_M': ('parameter field', 'corrector_M', '(set-to-None)'),
'corr1_M': ('parameter field', 'corrector_M', '(set-to-None)'),
'corr2_M': ('parameter field', 'corrector_M', '(set-to-None)'),
'wM': ('parameter field', 'vel_M', '(set-to-None)'),
'vol_all': ('parameter field', 'vol_all', '(set-to-None)'),
}
fields.update({
(continues on next page)
variables.update({
'p' + ch: ('unknown field', 'corrector_' + ch),
'q' + ch: ('test field', 'corrector_' + ch, 'p' + ch),
'Pi_' + ch: ('parameter field', 'corrector_' + ch, '(set-to-None)'),
'corr1_' + ch: ('parameter field', 'corrector_' + ch, '(set-to-None)'),
'corr2_' + ch: ('parameter field', 'corrector_' + ch, '(set-to-None)'),
'w' + ch: ('unknown field', 'vel_' + ch),
# lagrange mutltipliers - integral mean value
'ls' + ch: ('unknown field', 'corrector_' + ch),
'lv' + ch: ('test field', 'corrector_' + ch, 'ls' + ch),
})
options = {
'coefs': 'coefs',
'requirements': 'requirements',
'ls': 'ls', # linear solver to use
'volumes': {
'total': {
'variables': ['vol_all'],
'expression': """ev_volume.iV.Y(vol_all)""",
},
'one': {
'value': 1.0,
}
},
'output_dir': './output',
'file_per_var': True,
'coefs_filename': 'coefs_perf_' + pb_def['name'],
'coefs_info': {'eps0': eps0},
'recovery_hook': 'recovery_perf',
'multiprocessing': False,
}
for ch in six.iterkeys(reg_io):
for ireg in reg_io[ch]:
options['volumes'].update({
(continues on next page)
coefs = {
'vol_bYMpm': {
'regions': ['bYMp', 'bYMm'],
'expression': 'ev_volume.iS.%s(pM)',
'class': cb.VolumeFractions,
},
'filenames': {},
}
requirements = {
'corrs_one_YM': {
'variable': ['pM'],
'ebcs': ['gamma_pm_YMmchs', 'gamma_pm_bYMchs'],
'epbcs': [],
'save_name': 'corrs_one_YM',
'class': cb.CorrSetBCS,
},
}
return None
if mode == 'row':
val = pis.states[ir]['p' + ch] + corrs_pi.states[ir]['p' + ch]
variables['corr1_' + ch].set_data(val)
elif mode == 'col':
val = pis.states[ic]['p' + ch] + corrs_pi.states[ic]['p' + ch]
variables['corr2_' + ch].set_data(val)
pi = pis.states[ir]['p' + ch]
val = corrs_gamma.state['p' + ch]
variables['corr1_' + ch].set_data(pi)
variables['corr2_' + ch].set_data(val)
pi = pis.states[ir]['p' + ch]
pi = pi - nm.mean(pi)
val = pi + corrs_pi.states[ir]['p' + ch]
variables['corr1_' + ch].set_data(val)
requirements.update({
'pis_' + ch: {
'variables': ['p' + ch],
'class': cb.ShapeDim,
},
'corrs_one' + ch: {
'variable': ['pM'],
'ebcs': ebcs_eta[ch],
'epbcs': [],
'save_name': 'corrs_%s_one%s' % (pb_def['name'], ch),
'class': cb.CorrSetBCS,
},
'corrs_eta' + ch: {
'ebcs': ebcs_eta[ch],
'epbcs': all_periodicYM,
'equations': {
'eq_eta': 'dw_diffusion.iV.YM(mat2M.k, qM, pM) = 0',
},
'class': cb.CorrOne,
'save_name': 'corrs_%s_eta%s' % (pb_def['name'], ch),
},
'corrs_pi' + ch: {
'requires': ['pis_' + ch],
'set_variables': [('Pi_' + ch, 'pis_' + ch, 'p' + ch)],
'ebcs': [],
'epbcs': all_periodicY[ch],
'lcbcs': ['imv' + ch],
'equations': {
'eq_pi': """dw_diffusion.iV.Y%s(mat2%s.k, q%s, p%s)
+ dw_dot.iV.Y%s(q%s, ls%s)
= - dw_diffusion.iV.Y%s(mat2%s.k, q%s, Pi_%s)"""
% ((ch,) * 11),
'eq_imv': 'dw_dot.iV.Y%s(lv%s, p%s) = 0' % ((ch,) * 3),
},
'dim': pb_def['dim'] - 1,
'class': cb.CorrDim,
'save_name': 'corrs_%s_pi%s' % (pb_def['name'], ch),
},
})
(continues on next page)
coefs.update({
'S' + io: { # [Rohan1] (4.28), test+
'requires': ['corrs_gamma_' + io, 'pis_' + ch],
'set_variables': set_corr_S,
'expression': 'dw_diffusion.iV.Y%s(mat2%s.k,corr1_%s,corr2_%s)'
% ((ch,) * 4),
'dim': pb_def['dim'] - 1,
'class': cb.CoefDim,
},
'P' + io: { # test+
'requires': ['pis_' + ch, 'corrs_pi' + ch],
'set_variables': set_corr_cc,
'expression': 'ev_integrate.iS.bY%s(corr1_%s)'\
% (io, ch),
'set_volume': 'bYp',
'dim': pb_def['dim'] - 1,
'class': cb.CoefDim,
},
'S_test' + io: {
'requires': ['corrs_pi' + ch],
'set_variables': [('corr1_' + ch, 'corrs_pi' + ch, 'p' + ch)],
'expression': '%e * ev_integrate.iS.bY%s(corr1_%s)'\
% (1.0 / param_h, io, ch),
'dim': pb_def['dim'] - 1,
'class': cb.CoefDim,
},
})
solvers = {
'ls': ('ls.scipy_direct', {}),
'newton': ('nls.newton', {
'i_max': 1,
})
}
homogenization/rs_correctors.py
Description
Compute homogenized elastic coefficients for a given microstructure.
source code
#!/usr/bin/env python
"""
Compute homogenized elastic coefficients for a given microstructure.
"""
from __future__ import print_function
from __future__ import absolute_import
from argparse import ArgumentParser
import sys
import six
sys.path.append('.')
import numpy as nm
def define_regions(filename):
"""
Define various subdomains for a given mesh file.
"""
regions = {}
dim = 2
regions['Y'] = 'all'
out = {}
# in 1e+10 [Pa]
lam = 1.7
mu = 0.3
o = nm.array([1.] * dim + [0.] * (sym - dim), dtype = nm.float64)
oot = nm.outer(o, o)
out['D'] = lam * oot + mu * nm.diag(o + 1.0)
channels_cells = term.region.domain.regions['Yc'].cells
n_cell = term.region.get_n_cells()
val = out['D'].reshape((n_cell, -1, 3, 3))
val[channels_cells] *= 1e-1
return out
##
# Mesh file.
filename_mesh = data_dir + '/meshes/2d/special/osteonT1_11.mesh'
##
# Define regions (subdomains, boundaries) - $Y$, $Y_i$, ...
# depending on a mesh used.
dim, regions = define_regions(filename_mesh)
functions = {
'get_pars' : (lambda ts, coors, **kwargs:
get_pars(ts, coors, **kwargs),),
'match_x_plane' : (per.match_x_plane,),
'match_y_plane' : (per.match_y_plane,),
'match_z_plane' : (per.match_z_plane,),
'match_x_line' : (per.match_x_line,),
'match_y_line' : (per.match_y_line,),
}
##
# Define fields: 'displacement' in $Y$,
# 'pressure_m' in $Y_m$.
fields = {
'displacement' : ('real', dim, 'Y', 1),
}
##
# Define corrector variables: unknown displaements: uc, test: vc
# displacement-like variables: Pi, Pi1, Pi2
variables = {
'uc' : ('unknown field', 'displacement', 0),
(continues on next page)
##
# Periodic boundary conditions.
if dim == 3:
epbcs = {
'periodic_x' : (['Left', 'Right'], {'uc.all' : 'uc.all'},
'match_x_plane'),
'periodic_y' : (['Near', 'Far'], {'uc.all' : 'uc.all'},
'match_y_plane'),
'periodic_z' : (['Top', 'Bottom'], {'uc.all' : 'uc.all'},
'match_z_plane'),
}
else:
epbcs = {
'periodic_x' : (['Left', 'Right'], {'uc.all' : 'uc.all'},
'match_y_line'),
'periodic_y' : (['Bottom', 'Top'], {'uc.all' : 'uc.all'},
'match_x_line'),
}
##
# Dirichlet boundary conditions.
ebcs = {
'fixed_u' : ('Corners', {'uc.all' : 0.0}),
}
##
# Material defining constitutive parameters of the microproblem.
materials = {
'm' : 'get_pars',
}
##
# Numerical quadratures for volume (i3 - order 3) integral terms.
integrals = {
'i3' : 3,
}
##
# Homogenized coefficients to compute.
def set_elastic(variables, ir, ic, mode, pis, corrs_rs):
mode2var = {'row' : 'Pi1', 'col' : 'Pi2'}
variables[mode2var[mode]].set_data(val)
##
# Solvers.
solvers = {
'ls' : ('ls.scipy_direct', {}),
'newton' : ('nls.newton', {
'i_max' : 1,
'eps_a' : 1e-8,
'eps_r' : 1e-2,
})
}
############################################
# Mini-application below, computing the homogenized elastic coefficients.
helps = {
'no_pauses' : 'do not make pauses',
}
def main():
import os
from sfepy.base.base import spause, output
from sfepy.base.conf import ProblemConf, get_standard_keywords
from sfepy.discrete import Problem
import sfepy.homogenization.coefs_base as cb
if options.no_pauses:
def spause(*args):
output(*args)
nm.set_printoptions(precision=3)
spause(r""">>>
First, this file will be read in place of an input
(problem description) file.
Press 'q' to quit the example, press any other key to continue...""")
required, other = get_standard_keywords()
required.remove('equations')
# Use this file as the input file.
conf = ProblemConf.from_file(__file__, required, other)
print(list(conf.to_dict().keys()))
spause(r""">>>
...the read input as a dict (keys only for brevity).
['q'/other key to quit/continue...]""")
spause(r""">>>
Now the input will be used to create a Problem instance.
['q'/other key to quit/continue...]""")
problem = Problem.from_conf(conf, init_equations=False)
# The homogenization mini-apps need the output_dir.
output_dir = ''
problem.output_dir = output_dir
print(problem)
spause(r""">>>
...the Problem instance.
['q'/other key to quit/continue...]""")
spause(r""">>>
The homogenized elastic coefficient $E_{ijkl}$ is expressed
using $\Pi$ operators, computed now. In fact, those operators are permuted
coordinates of the mesh nodes.
['q'/other key to quit/continue...]""")
req = conf.requirements['pis']
mini_app = cb.ShapeDimDim('pis', problem, req)
mini_app.setup_output(save_formats=['vtk'],
file_per_var=False)
pis = mini_app()
print(pis)
spause(r""">>>
...the $\Pi$ operators.
['q'/other key to quit/continue...]""")
(continues on next page)
spause(r""">>>
Next, $E_{ijkl}$ needs so called steady state correctors $\bar{\omega}^{rs}$,
computed now.
['q'/other key to quit/continue...]""")
req = conf.requirements['corrs_rs']
spause(r""">>>
Then the volume of the domain is needed.
['q'/other key to quit/continue...]""")
volume = problem.evaluate('ev_volume.i3.Y(uc)')
print(volume)
spause(r""">>>
...the volume.
['q'/other key to quit/continue...]""")
spause(r""">>>
Finally, $E_{ijkl}$ can be computed.
['q'/other key to quit/continue...]""")
mini_app = cb.CoefSymSym('homogenized elastic tensor',
problem, conf.coefs['E'])
c_e = mini_app(volume, data={'pis': pis, 'corrs_rs' : corrs_rs})
print(r""">>>
The homogenized elastic coefficient $E_{ijkl}$, symmetric storage
with rows, columns in 11, 22, 12 ordering:""")
print(c_e)
if __name__ == '__main__':
main()
large_deformation
large_deformation/active_fibres.py
Description
Nearly incompressible hyperelastic material model with active fibres.
Large deformation is described using the total Lagrangian formulation. Models of this kind can be used in biomechanics
to model biological tissues, e.g. muscles.
Find π’ such that:
β«οΈ (οΈ )οΈ
π eff (π’) + πΎ(π½ β 1) π½πΆ β1 : πΏπΈ(π£) dπ = 0 , βπ£ ,
Ξ©(0)
where
The effective stress π eff (π’) incorporates also the effects of the active fibres in two preferential directions:
2
2 1 βοΈ
π eff (π’) = ππ½ β 3 (πΌ β tr(πΆ)πΆ β1 ) + π π ππ .
3
π=1
The first term is the neo-Hookean term and the sum add contributions of the two fibre systems. The tensors π π = ππ ππ
are defined by the fibre system direction vectors ππ (unit).
For the one-dimensional tensions π π holds simply (π omitted):
{οΈ }οΈ
π β πopt 2
π = π΄πmax exp β( ) ,π=πΈ :π.
π
source code
.. math::
\intl{\Omega\suz}{} \left( \ull{S}\eff(\ul{u})
+ K(J-1)\; J \ull{C}^{-1} \right) : \delta \ull{E}(\ul{v}) \difd{V}
= 0
\;, \quad \forall \ul{v} \;,
where
.. list-table::
:widths: 20 80
.. math::
\ull{S}\eff(\ul{u}) = \mu J^{-\frac{2}{3}}(\ull{I}
- \frac{1}{3}\tr(\ull{C}) \ull{C}^{-1})
+ \sum_{k=1}^2 \tau^k \ull{\omega}^k
\;.
The first term is the neo-Hookean term and the sum add contributions of
the two fibre systems. The tensors :math:`\ull{\omega}^k =
\ul{d}^k\ul{d}^k` are defined by the fibre system direction vectors
:math:`\ul{d}^k` (unit).
.. math::
\tau = A f_{\rm max} \exp{\left\{-(\frac{\epsilon - \varepsilon_{\rm
opt}}{s})^2\right\}} \mbox{ , } \epsilon = \ull{E} : \ull{\omega}
\;.
"""
from __future__ import print_function
from __future__ import absolute_import
import numpy as nm
vf_matrix = 0.5
vf_fibres1 = 0.2
vf_fibres2 = 0.3
options = {
'nls' : 'newton',
'ls' : 'ls',
'ts' : 'ts',
'save_times' : 'all',
'post_process_hook' : 'stress_strain',
(continues on next page)
fields = {
'displacement': (nm.float64, 3, 'Omega', 1),
}
materials = {
'solid' : ({
'K' : vf_matrix * 1e3, # bulk modulus
'mu' : vf_matrix * 20e0, # shear modulus of neoHookean term
},),
'f1' : 'get_pars_fibres1',
'f2' : 'get_pars_fibres2',
}
fmax = 10.0
eps_opt = 0.01
s = 1.0
if which == 0: # system 1
fdir = nm.array([1.0, 0.0, 0.0], dtype=nm.float64)
act = 0.5 * (1.0 + nm.sin(tt - (0.5 * nm.pi)))
else:
raise ValueError('unknown fibre system! (%d)' % which)
fdir.shape = (3, 1)
fdir /= nm.linalg.norm(fdir)
(continues on next page)
print(act)
shape = (coors.shape[0], 1, 1)
out = {
'fmax' : vf * nm.tile(fmax, shape),
'eps_opt' : nm.tile(eps_opt, shape),
's' : nm.tile(s, shape),
'fdir' : nm.tile(fdir, shape),
'act' : nm.tile(act, shape),
}
return out
functions = {
'get_pars_fibres1' : (lambda ts, coors, mode=None, **kwargs:
get_pars_fibres(ts, coors, mode=mode, which=0,
vf=vf_fibres1, **kwargs),),
'get_pars_fibres2' : (lambda ts, coors, mode=None, **kwargs:
get_pars_fibres(ts, coors, mode=mode, which=1,
vf=vf_fibres2, **kwargs),),
}
variables = {
'u' : ('unknown field', 'displacement', 0),
'v' : ('test field', 'displacement', 'u'),
}
regions = {
'Omega' : 'all',
'Left' : ('vertices in (x < 0.001)', 'facet'),
'Right' : ('vertices in (x > 0.099)', 'facet'),
}
##
# Dirichlet BC.
ebcs = {
'l' : ('Left', {'u.all' : 0.0}),
}
##
# Balance of forces.
integral_1 = {
'name' : 'i',
'order' : 1,
}
equations = {
'balance'
: """dw_tl_he_neohook.i.Omega( solid.mu, v, u )
+ dw_tl_bulk_penalty.i.Omega( solid.K, v, u )
+ dw_tl_fib_a.i.Omega( f1.fmax, f1.eps_opt, f1.s, f1.fdir, f1.act,
v, u )
(continues on next page)
ev = problem.evaluate
strain = ev('dw_tl_he_neohook.i.Omega( solid.mu, v, u )',
mode='el_avg', term_mode='strain')
out['green_strain'] = Struct(name='output_data',
mode='cell', data=strain, dofs=None)
return out
##
# Solvers etc.
solver_0 = {
'name' : 'ls',
'kind' : 'ls.scipy_direct',
}
solver_1 = {
'name' : 'newton',
'kind' : 'nls.newton',
'i_max' : 7,
'eps_a' : 1e-10,
'eps_r' : 1.0,
'macheps' : 1e-16,
'lin_red' : 1e-2, # Linear system error < (eps_a * lin_red).
'ls_red' : 0.1,
'ls_red_warp': 0.001,
'ls_on' : 1.1,
'ls_min' : 1e-5,
'check' : 0,
'delta' : 1e-6,
}
solver_2 = {
'name' : 'ts',
(continues on next page)
't0' : 0,
't1' : 1,
'dt' : None,
'n_step' : 21, # has precedence over dt!
'verbose' : 1,
}
large_deformation/balloon.py
Description
Inflation of a Mooney-Rivlin hyperelastic balloon.
This example serves as a verification of the membrane term (dw_tl_membrane, TLMembraneTerm) implementation.
Following Rivlin 1952 and Dumais, the analytical relation between a relative stretch πΏ = π/π0 of a thin (membrane)
sphere made of the Mooney-Rivlin material of the undeformed radius π0 , membrane thickness β0 and the inner pressure
π is
β0 1 1
π=4 ( β 7 )(π1 + π2 πΏ2 ) ,
π0 πΏ πΏ
where π1 , π2 are the Mooney-Rivlin material parameters.
In the equations below, only the surface of the domain is mechanically important - a stiff 2D membrane is embedded
in the 3D space and coincides with the balloon surface. The volume is very soft, to simulate a fluid-filled cavity. A
similar model could be used to model e.g. plant cells. The balloon surface is loaded by prescribing the inner volume
change π(π‘). The fluid pressure in the cavity is a single scalar value, enforced by the 'integral_mean_value' linear
combination condition.
Find π’(π) and a constant π such that:
β’ balance of forces:
β«οΈ (οΈ )οΈ β«οΈ
π eff (π’) β π π½πΆ β1 : πΏπΈ(π£; π£) dπ + π eff (Λ
π’)πΏπΈ(Λ
π’; π£Λ)β0 dπ = 0 , βπ£ β [π»01 (β¦)]3 ,
Ξ©(0) Ξ(0)
β’ volume conservation:
β«οΈ
[π(π‘) β π½(π’)] π ππ₯ = 0 βπ β πΏ2 (β¦) ,
Ξ©0
where
2 1 4 2
π eff (π’) = ππ½ β 3 (πΌ β tr(πΆ)πΆ β1 ) + π
π½ β 3 (tr(πΆπΌ β πΆ β ((tr πΆ)2 β tr (πΆ 2 ))πΆ β1 ) .
3 6
The π’
Λ and π£Λ variables correspond to π’, π£, respectively, transformed to the membrane coordinate frame.
Use the following command to show a comparison of the FEM solution with the above analytical relation (notice the
nonlinearity of the dependence):
The agreement should be very good, even though the mesh is coarse.
View the results using:
This example uses the adaptive time-stepping solver ('ts.adaptive') with the default adaptivity function
adapt_time_step(). Plot the used time steps by:
source code
r"""
Inflation of a Mooney-Rivlin hyperelastic balloon.
.. math::
- balance of forces:
.. math::
\intl{\Omega\suz}{} \left( \ull{S}\eff(\ul{u})
- p\; J \ull{C}^{-1} \right) : \delta \ull{E}(\ul{v}; \ul{v}) \difd{V}
+ \intl{\Gamma\suz}{} \ull{S}\eff(\tilde{\ul{u}}) \delta
\ull{E}(\tilde{\ul{u}}; \tilde{\ul{v}}) h_0 \difd{S}
= 0 \;, \quad \forall \ul{v} \in [H^1_0(\Omega)]^3 \;,
- volume conservation:
.. math::
\int\limits_{\Omega_0} \left[\omega(t)-J(u)\right] q\, dx = 0
\qquad \forall q \in L^2(\Omega) \;,
where
.. list-table::
:widths: 20 80
* - :math:`\ull{F}`
- deformation gradient :math:`F_{ij} = \pdiff{x_i}{X_j}`
* - :math:`J`
- :math:`\det(F)`
* - :math:`\ull{C}`
(continues on next page)
.. math::
\ull{S}\eff(\ul{u}) = \mu J^{-\frac{2}{3}}(\ull{I}
- \frac{1}{3}\tr(\ull{C}) \ull{C}^{-1})
+ \kappa J^{-\frac{4}{3}} (\tr(\ull{C}\ull{I} - \ull{C}
- \frac{2}{6}((\tr{\ull{C}})^2 - \tr{(\ull{C}^2)})\ull{C}^{-1})
\;.
Use the following command to show a comparison of the FEM solution with the
above analytical relation (notice the nonlinearity of the dependence)::
The agreement should be very good, even though the mesh is coarse.
output = Output('balloon:')
else:
raise ValueError('unknown mode %s!' % mode)
rv = get_rel_volume(rs)
output('relative stretch:', rs)
output('relative volume:', rv)
return out
def get_rel_volume(rel_stretch):
"""
Get relative volume V/V0 from relative stretch r/r0 of a ball.
"""
return nm.power(rel_stretch, 3.0)
def get_rel_stretch(rel_volume):
"""
Get relative stretch r/r0 from relative volume V/V0 of a ball.
"""
return nm.power(rel_volume, 1.0/3.0)
P = 4*h0/r0 * (1/L-1/L^7).*(C1+L^2*C2)
"""
L = rel_stretch
p = 4.0 * h0 / r0 * (1.0/L - 1.0/L**7) * (c1 + c2 * L**2)
return p
p = ths['p'][0]
L = 1.0 + ts.times[:p.shape[0]]
plt.rcParams['lines.linewidth'] = 3
plt.rcParams['font.size'] = 16
plt.legend(loc='best')
fig = plt.gcf()
fig.savefig('balloon_pressure_stretch.pdf')
plt.show()
def define(plot=False):
filename_mesh = data_dir + '/meshes/3d/unit_ball.mesh'
conf_dir = os.path.dirname(__file__)
io = MeshIO.any_from_filename(filename_mesh, prefix_dir=conf_dir)
bbox = io.read_bounding_box()
dd = bbox[1] - bbox[0]
radius = bbox[1, 0]
eps = 1e-8 * dd[0]
options = {
'nls' : 'newton',
'ls' : 'ls',
'ts' : 'ts',
'save_times' : 'all',
'output_dir' : '.',
'output_format' : 'h5',
}
if plot:
options['post_process_hook_final'] = plot_radius
fields = {
'displacement': (nm.float64, 3, 'Omega', 1),
'pressure': (nm.float64, 1, 'Omega', 0),
}
variables = {
'u' : ('unknown field', 'displacement', 0),
'v' : ('test field', 'displacement', 'u'),
'p' : ('unknown field', 'pressure', 1),
'q' : ('test field', 'pressure', 'p'),
'omega' : ('parameter field', 'pressure', {'setter' : 'get_volume'}),
}
regions = {
'Omega' : 'all',
'Ax1' : ('vertices by get_ax1', 'vertex'),
'Ax2' : ('vertices by get_ax2', 'vertex'),
'Equator' : ('vertices by get_equator', 'vertex'),
'Surface' : ('vertices of surface', 'facet'),
}
ebcs = {
'fix1' : ('Ax1', {'u.all' : 0.0}),
'fix2' : ('Ax2', {'u.[0, 1]' : 0.0}),
'fix3' : ('Equator', {'u.1' : 0.0}),
}
lcbcs = {
'pressure' : ('Omega', {'p.all' : None}, None, 'integral_mean_value'),
}
equations = {
'balance'
: """dw_tl_he_neohook.2.Omega(solid.mu, v, u)
+ dw_tl_he_mooney_rivlin.2.Omega(solid.kappa, v, u)
+ dw_tl_membrane.2.Surface(walls.mu, walls.kappa, walls.h0, v, u)
+ dw_tl_bulk_pressure.2.Omega(v, u, p)
= 0""",
'volume'
: """dw_tl_volume.2.Omega(q, u)
= dw_dot.2.Omega(q, omega)""",
}
solvers = {
'ls' : ('ls.scipy_direct', {}),
(continues on next page)
'dt_red_factor' : 0.8,
'dt_red_max' : 1e-3,
'dt_inc_factor' : 1.25,
'dt_inc_on_iter' : 4,
'dt_inc_wait' : 3,
'verbose' : 1,
'quasistatic' : True,
}),
}
functions = {
'get_ax1' : (lambda coors, domain:
get_nodes(coors, radius, eps, 'ax1'),),
'get_ax2' : (lambda coors, domain:
get_nodes(coors, radius, eps, 'ax2'),),
'get_equator' : (lambda coors, domain:
get_nodes(coors, radius, eps, 'equator'),),
'get_volume' : (get_volume,),
}
return locals()
large_deformation/compare_elastic_materials.py
Description
Compare various elastic materials w.r.t. uniaxial tension/compression test.
Requires Matplotlib.
source code
#!/usr/bin/env python
"""
Compare various elastic materials w.r.t. uniaxial tension/compression test.
Requires Matplotlib.
"""
from __future__ import absolute_import
from argparse import ArgumentParser, RawDescriptionHelpFormatter
import sys
import six
sys.path.append('.')
import numpy as nm
def define():
"""Define the problem to solve."""
from sfepy.discrete.fem.meshio import UserMeshIO
from sfepy.mesh.mesh_generators import gen_block_mesh
from sfepy.mechanics.matcoefs import stiffness_from_lame
filename_mesh = UserMeshIO(mesh_hook)
options = {
'nls' : 'newton',
'ls' : 'ls',
'ts' : 'ts',
'save_times' : 'all',
}
functions = {
'linear_tension' : (linear_tension,),
'linear_compression' : (linear_compression,),
(continues on next page)
fields = {
'displacement' : ('real', 3, 'Omega', 1),
}
# Coefficients are chosen so that the tangent stiffness is the same for all
# material for zero strains.
# Young modulus = 10 kPa, Poisson's ratio = 0.3
materials = {
'solid' : ({
'K' : 8.333, # bulk modulus
'mu_nh' : 3.846, # shear modulus of neoHookean term
'mu_mr' : 1.923, # shear modulus of Mooney-Rivlin term
'kappa' : 1.923, # second modulus of Mooney-Rivlin term
# elasticity for LE term
'D' : stiffness_from_lame(dim=3, lam=5.769, mu=3.846),
},),
'load' : 'empty',
}
variables = {
'u' : ('unknown field', 'displacement', 0),
'v' : ('test field', 'displacement', 'u'),
}
regions = {
'Omega' : 'all',
'Bottom' : ('vertices in (z < 0.1)', 'facet'),
'Top' : ('vertices in (z > 2.9)', 'facet'),
}
ebcs = {
'fixb' : ('Bottom', {'u.all' : 0.0}),
'fixt' : ('Top', {'u.[0,1]' : 0.0}),
}
integrals = {
'i' : 1,
'isurf' : 2,
}
equations = {
'linear' : """dw_lin_elastic.i.Omega(solid.D, v, u)
= dw_surface_ltr.isurf.Top(load.val, v)""",
'neo-Hookean' : """dw_tl_he_neohook.i.Omega(solid.mu_nh, v, u)
+ dw_tl_bulk_penalty.i.Omega(solid.K, v, u)
= dw_surface_ltr.isurf.Top(load.val, v)""",
'Mooney-Rivlin' : """dw_tl_he_neohook.i.Omega(solid.mu_mr, v, u)
+ dw_tl_he_mooney_rivlin.i.Omega(solid.kappa, v, u)
+ dw_tl_bulk_penalty.i.Omega(solid.K, v, u)
= dw_surface_ltr.isurf.Top(load.val, v)""",
(continues on next page)
solvers = {
'ls' : ('ls.scipy_direct', {}),
'newton' : ('nls.newton', {
'i_max' : 5,
'eps_a' : 1e-10,
'eps_r' : 1.0,
}),
'ts' : ('ts.simple', {
't0' : 0,
't1' : 1,
'dt' : None,
'n_step' : 101, # has precedence over dt!
'verbose' : 1,
}),
}
return locals()
##
# Pressure tractions.
def linear_tension(ts, coor, mode=None, **kwargs):
if mode == 'qp':
val = nm.tile(0.1 * ts.step, (coor.shape[0], 1, 1))
return {'val' : val}
def store_top_u(displacements):
"""Function _store() will be called at the end of each loading step. Top
displacements will be stored into `displacements`."""
def _store(problem, ts, state):
top = problem.domain.regions['Top']
top_u = problem.get_variables()['u'].get_state_in_region(top)
displacements.append(nm.mean(top_u[:,-1]))
return _store
load = problem.get_materials()['load']
load.set_function(branch_function)
return displacements
helps = {
'no_plot' : 'do not show plot window',
}
def main():
from sfepy.base.base import output
from sfepy.base.conf import ProblemConf, get_standard_keywords
from sfepy.discrete import Problem
from sfepy.base.plotutils import plt
parser = ArgumentParser(description=__doc__,
formatter_class=RawDescriptionHelpFormatter)
parser.add_argument('--version', action='version', version='%(prog)s')
parser.add_argument('-n', '--no-plot',
action="store_true", dest='no_plot',
default=False, help=helps['no_plot'])
options = parser.parse_args()
plt.legend(legend, loc = 2)
plt.xlabel('tension [kPa]')
plt.ylabel('displacement [mm]')
plt.grid(True)
plt.gcf().savefig('pressure_displacement.png')
if not options.no_plot:
plt.show()
if __name__ == '__main__':
main()
large_deformation/gen_yeoh_tl_up_interactive.py
Description
This example shows the use of the dw_tl_he_genyeoh hyperelastic term, whose contribution to the deformation energy
density per unit reference volume is given by
(οΈ )οΈπ
π = πΎ πΌ1 β 3
where πΌ 1 is the first main invariant of the deviatoric part of the right Cauchy-Green deformation tensor πΆ and K and p
are its parameters.
This term may be used to implement the generalized Yeoh hyperelastic material model [1] by adding three such terms:
(οΈ )οΈπ (οΈ )οΈπ (οΈ )οΈπ
π = πΎ1 πΌ 1 β 3 + πΎ2 πΌ 1 β 3 + πΎ3 πΌ 1 β 3
where the coefficients πΎ1 , πΎ2 , πΎ3 and exponents π, π, π are material parameters. Only a single term is used in this
example for the sake of simplicity.
Components of the second Piola-Kirchhoff stress are in the case of an incompressible material
ππ β1 βπ
πππ = 2 β π πΉππ πΉππ ,
ππΆππ
The following formula holds for the axial true (Cauchy) stress in the case of uniaxial stress:
(οΈ )οΈπβ1 (οΈ )οΈ
2 2 1
π(π) = π πΎ1 π2 + β3 πβ 2 ,
3 π π
where π = π/π0 is the prescribed stretch (π0 and π being the original and deformed specimen length respectively).
The boundary conditions are set so that a state of uniaxial stress is achieved, i.e. appropriate components of displace-
ment are fixed on the βLeftβ, βBottomβ, and βNearβ faces and a monotonously increasing displacement is prescribed
on the βRightβ face. This prescribed displacement is then used to calculate π and to convert the second Piola-Kirchhoff
stress to the true (Cauchy) stress.
The three-term generalized Yeoh model is meant to be used for modelling of filled rubbers. The following choice of
parameters is suggested [1] based on experimental data and stability considerations:
πΎ1 > 0,
πΎ2 < 0,
πΎ3 > 0,
0.7 < π < 1,
π < π < π.
Usage Examples
Default options:
$ python examples/large_deformation/gen_yeoh_tl_up_interactive.py
$ python examples/large_deformation/gen_yeoh_tl_up_interactive.py -p
$ python examples/large_deformation/gen_yeoh_tl_up_interactive.py \
--shape "5, 5, 5"
$ python examples/large_deformation/gen_yeoh_tl_up_interactive.py \
--dims "2, 1, 3"
$ python examples/large_deformation/gen_yeoh_tl_up_interactive.py \
-t 0,15,21
Use higher approximation order (the -t option to decrease the time step is required for convergence here):
$ python examples/large_deformation/gen_yeoh_tl_up_interactive.py \
--order 2 -t 0,2,21
[1] Travis W. Hohenberger, Richard J. Windslow, Nicola M. Pugno, James J. C. Busfield. Aconstitutive Model For
Both Lowand High Strain Nonlinearities In Highly Filled Elastomers And Implementation With User-Defined Material
Subroutines In Abaqus. Rubber Chemistry And Technology, Vol. 92, No. 4, Pp. 653-686 (2019)
source code
#!/usr/bin/env python
r"""
This example shows the use of the `dw_tl_he_genyeoh` hyperelastic term, whose
contribution to the deformation energy density per unit reference volume is
given by
.. math::
W = K \, \left( \overline I_1 - 3 \right)^{p}
where :math:`\overline I_1` is the first main invariant of the deviatoric part
of the right Cauchy-Green deformation tensor :math:`\ull{C}` and `K` and `p`
are its parameters.
This term may be used to implement the generalized Yeoh hyperelastic material
model [1] by adding three such terms:
.. math::
W =
K_1 \, \left( \overline I_1 - 3 \right)^{m}
+K_2 \, \left( \overline I_1 - 3 \right)^{p}
+K_3 \, \left( \overline I_1 - 3 \right)^{q}
where the coefficients :math:`K_1, K_2, K_3` and exponents :math:`m, p, q` are
material parameters. Only a single term is used in this example for the sake of
simplicity.
(continues on next page)
.. math::
S_{ij} = 2 \, \pdiff{W}{C_{ij}} - p \, F^{-1}_{ik} \, F^{-T}_{kj} \;,
.. math::
\intl{\Omega\suz}{} \ull{S}\eff(\ul{u}, p) : \ull{E}(\ul{v})
\difd{V} = 0
\;, \quad \forall \ul{v} \;,
The following formula holds for the axial true (Cauchy) stress in the case of
uniaxial stress:
.. math::
\sigma(\lambda) =
\frac{2}{3} \, m \, K_1 \,
\left( \lambda^2 + \frac{2}{\lambda} - 3 \right)^{m-1} \,
\left( \lambda - \frac{1}{\lambda^2} \right) \;,
The boundary conditions are set so that a state of uniaxial stress is achieved,
i.e. appropriate components of displacement are fixed on the "Left", "Bottom",
and "Near" faces and a monotonously increasing displacement is prescribed on
the "Right" face. This prescribed displacement is then used to calculate
:math:`\lambda` and to convert the second Piola-Kirchhoff stress to the true
(Cauchy) stress.
Usage Examples
--------------
Default options::
$ python sfepy/examples/large_deformation/gen_yeoh_tl_up_interactive.py
$ python sfepy/examples/large_deformation/gen_yeoh_tl_up_interactive.py -p
$ python sfepy/examples/large_deformation/gen_yeoh_tl_up_interactive.py \
--shape "5, 5, 5"
$ python sfepy/examples/large_deformation/gen_yeoh_tl_up_interactive.py \
--dims "2, 1, 3"
$ python sfepy/examples/large_deformation/gen_yeoh_tl_up_interactive.py \
-t 0,15,21
Use higher approximation order (the ``-t`` option to decrease the time step is
required for convergence here)::
$ python sfepy/examples/large_deformation/gen_yeoh_tl_up_interactive.py \
--order 2 -t 0,2,21
SFEPY_DIR = '.'
sys.path.append(SFEPY_DIR)
DIMENSION = 3
def plot_graphs(
material_parameters, global_stress, global_displacement,
undeformed_length):
"""
Plot a comparison of the nominal stress computed by the FEM and using the
(continues on next page)
Parameters
----------
material_parameters : list or tuple of float
The K_1 coefficient and exponent m.
global_displacement
The total displacement for each time step, from the FEM.
global_stress
The true (Cauchy) stress for each time step, from the FEM.
undeformed_length : float
The length of the undeformed specimen.
"""
coef, exp = material_parameters
ax_stress.legend(loc='best').set_draggable(True)
ax_stress.set_ylabel(r'nominal stress $\mathrm{[Pa]}$')
ax_stress.grid()
def stress_strain(
out, problem, _state, order=1, global_stress=None,
global_displacement=None, **_):
"""
Compute the stress and the strain and add them to the output.
Parameters
----------
out : dict
Holds the results of the finite element computation.
problem : sfepy.discrete.Problem
order : int
The approximation order of the displacement field.
(continues on next page)
Returns
-------
out : dict
"""
strain = problem.evaluate(
'dw_tl_he_genyeoh.%d.Omega(m1.par, v, u)' % (2*order),
mode='el_avg', term_mode='strain', copy_materials=False)
out['green_strain'] = Struct(
name='output_data', mode='cell', data=strain, dofs=None)
stress_1 = problem.evaluate(
'dw_tl_he_genyeoh.%d.Omega(m1.par, v, u)' % (2*order),
mode='el_avg', term_mode='stress', copy_materials=False)
stress_p = problem.evaluate(
'dw_tl_bulk_pressure.%d.Omega(v, u, p)' % (2*order),
mode='el_avg', term_mode='stress', copy_materials=False)
stress = stress_1 + stress_p
out['stress'] = Struct(
name='output_data', mode='cell', data=stress, dofs=None)
global_stress.append(stress[0, 0, 0, 0])
global_displacement.append(get_displacement(
problem.ts, np.array([[1., 0, 0]]))[0])
return out
def main(cli_args):
dims = parse_argument_list(cli_args.dims, float)
shape = parse_argument_list(cli_args.shape, int)
centre = parse_argument_list(cli_args.centre, float)
material_parameters = parse_argument_list(cli_args.material_parameters,
float)
order = cli_args.order
ts_vals = cli_args.ts.split(',')
ts = {
't0' : float(ts_vals[0]), 't1' : float(ts_vals[1]),
'n_step' : int(ts_vals[2])}
do_plot = cli_args.plot
term_1 = Term.new(
'dw_tl_he_genyeoh(m1.par, v, u)',
integral, omega, m1=m_1, v=v, u=u)
term_pressure = Term.new(
'dw_tl_bulk_pressure(v, u, p)',
integral, omega, v=v, u=u, p=p)
term_volume_change = Term.new(
'dw_tl_volume(q, u)',
integral, omega, q=q, u=u, term_mode='volume')
term_volume = Term.new(
'dw_integrate(q)',
integral, omega, q=q)
(continues on next page)
pb.solve(save_results=True, post_process_hook=stress_strain_fun)
if do_plot:
plot_graphs(
material_parameters, axial_stress, axial_displacement,
undeformed_length=dims[0])
Parameters
----------
cli_arg : str
type_fun : function
A function to be called on each substring of `cli_arg`; default: str.
value_separator : str
"""
if type_fun is None:
type_fun = str
out = [type_fun(value) for value in cli_arg.split(value_separator)]
return out
def parse_args():
(continues on next page)
if __name__ == '__main__':
args = parse_args()
main(args)
large_deformation/hyperelastic.py
Description
Nearly incompressible Mooney-Rivlin hyperelastic material model.
Large deformation is described using the total Lagrangian formulation. Models of this kind can be used to model e.g.
rubber or some biological materials.
Find π’ such that:
β«οΈ (οΈ )οΈ
π eff (π’) + πΎ(π½ β 1) π½πΆ β1 : πΏπΈ(π£) dπ = 0 , βπ£ ,
Ξ©(0)
where
2 1 4 2
π eff (π’) = ππ½ β 3 (πΌ β tr(πΆ)πΆ β1 ) + π
π½ β 3 (tr(πΆπΌ β πΆ β ((tr πΆ)2 β tr (πΆ 2 ))πΆ β1 ) .
3 6
source code