#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ---------------------------------------------------------------------
#
# _____ _ _ _ _____ _____
# | ____|__| | ___| |_ _____(_)___ ___| ___| ____|
# | _| / _` |/ _ \ \ \ /\ / / _ \ / __/ __| |_ | _|
# | |__| (_| | __/ |\ V V / __/ \__ \__ \ _| | |___
# |_____\__,_|\___|_| \_/\_/ \___|_|___/___/_| |_____|
#
#
# Unit of Strength of Materials and Structural Analysis
# University of Innsbruck,
# 2017 - today
#
# Matthias Neuner matthias.neuner@uibk.ac.at
#
# This file is part of EdelweissFE.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# The full text of the license can be found in the file LICENSE.md at
# the top level directory of EdelweissFE.
# ---------------------------------------------------------------------
# Created on Tue Jan 10 19:10:42 2017
# @author: Matthias Neuner
"""
This module contains the central definition of
the known physics,
i.e.,
* the available physical fields
* their tensorial order
* their default residual tolerances
If a new physical field should be added to EdelweissFE,
then this should happen (only) here.
"""
# field field type
phenomena = {
"displacement": "vector",
"rotation": "rotation vector",
"micro rotation": "rotation vector",
"thermal": "scalar",
"nonlocal damage": "scalar",
"nonlocal damage 2": "scalar",
"concentration": "scalar",
"chemical potential": "scalar",
"strain symmetric": "symmetric tensor second order",
"plastic multiplier": "scalar",
"pressure": "scalar",
"jacobi": "scalar",
}
# field kind of inertia
#
# The coefficient of a field's SECOND time derivative is not always a mass. Reporting a momentum or an
# energy needs to know which inertia entries may be added to which:
#
# * ``"mass"`` -- m*v is a linear momentum, 0.5*m*v^2 an energy.
# * ``"rotational inertia"`` -- 0.5*I*w^2 is an energy, but I*w is an ANGULAR momentum and must
# not be added to a linear one (in 3d both have three components).
# * ``"non-mechanical"`` -- neither, e.g. the non-local micro-inertia (a time squared).
# Also used for fields without any inertia.
inertiaKind = {
"displacement": "mass",
"rotation": "rotational inertia",
"micro rotation": "rotational inertia",
"thermal": "non-mechanical",
"nonlocal damage": "non-mechanical",
"nonlocal damage 2": "non-mechanical",
"concentration": "non-mechanical",
"chemical potential": "non-mechanical",
"strain symmetric": "non-mechanical",
"plastic multiplier": "non-mechanical",
"pressure": "non-mechanical",
"jacobi": "non-mechanical",
}
# The tolerances of the legacy convergence criterion per field; see edelweissfe.solvers.base.convergencecriteria.
# The entry "scalar variables" applies to the scalar variables (e.g., Lagrange multipliers) of the model.
# field absolute correction tolerance
fieldCorrectionTolerance = {
"displacement": 1e-7,
"rotation": 1e-3,
"micro rotation": 1e-8,
"nonlocal damage": 1e-8,
"nonlocal damage 2": 1e-8,
"concentration": 1e-1,
"chemical potential": 1e-1,
"strain symmetric": 1e-7,
"scalar variables": 1e-3,
"plastic multiplier": 1e-8,
"pressure": 1e-8,
"jacobi": 1e-8,
}
# field correction tolerance relative to the largest increment of the field; 0: none
relativeFieldCorrectionTolerance = {
"displacement": 0.0,
"rotation": 0.0,
"micro rotation": 0.0,
"nonlocal damage": 0.0,
"nonlocal damage 2": 0.0,
"concentration": 0.0,
"chemical potential": 0.0,
"strain symmetric": 0.0,
"scalar variables": 0.0,
"plastic multiplier": 0.0,
"pressure": 0.0,
"jacobi": 0.0,
}
# field flux residual tolerance relative to the spatial average flux
fluxResidualTolerance = {
"displacement": 1e-8,
"rotation": 1e-4,
"micro rotation": 1e-8,
"nonlocal damage": 1e-8,
"nonlocal damage 2": 1e-8,
"concentration": 1e-1,
"chemical potential": 1e-2,
"strain symmetric": 1e-8,
"scalar variables": 1e-8,
"plastic multiplier": 1e-8,
"pressure": 1e-8,
"jacobi": 1e-8,
}
# field relative flux residual tolerance from iteration iterationsForAlternativeTolerance on
fluxResidualToleranceAlternative = {
"displacement": 5e-3,
"rotation": 5e-3,
"micro rotation": 5e-3,
"nonlocal damage": 5e-3,
"nonlocal damage 2": 5e-3,
"concentration": 5e-2,
"chemical potential": 5e-2,
"strain symmetric": 5e-3,
"scalar variables": 1e-8,
"plastic multiplier": 5e-3,
"pressure": 5e-3,
"jacobi": 5e-3,
}
# field absolute flux residual tolerance; the scalar variables' flux residual tolerances above are
# absolute already
absoluteFluxResidualTolerance = {
"displacement": 1e-7,
"rotation": 1e-7,
"micro rotation": 1e-7,
"nonlocal damage": 1e-7,
"nonlocal damage 2": 1e-7,
"concentration": 1e-7,
"chemical potential": 1e-7,
"strain symmetric": 1e-7,
"scalar variables": 0.0,
"plastic multiplier": 1e-7,
"pressure": 1e-7,
"jacobi": 1e-7,
}
# domain dimensions
domainMapping = {
"1d": 1,
"2d": 2,
"3d": 3,
"axisymmetric": 2,
}
[docs]
def getInertiaKind(field: str) -> str:
"""The kind of inertia a field carries; see :data:`inertiaKind`.
Parameters
----------
field
The name of the physical field.
Returns
-------
str
One of ``"mass"``, ``"rotational inertia"``, ``"non-mechanical"``.
Raises
------
NotImplementedError
If the field is not registered here.
"""
try:
return inertiaKind[field]
except KeyError:
raise NotImplementedError(
"Physical field {:} has no registered inertia kind. Add it to inertiaKind in "
"edelweissfe/config/phenomena.py, alongside its entry in phenomena.".format(field)
)
[docs]
def carriesLinearMomentum(field: str) -> bool:
"""Whether a field's inertia times its rate is a linear momentum, i.e. whether the field may
enter a linear-momentum balance and be summed with the other fields in it.
Parameters
----------
field
The name of the physical field.
Returns
-------
bool
True only for a field whose inertia is a mass.
"""
return getInertiaKind(field) == "mass"
[docs]
def carriesKineticEnergy(field: str) -> bool:
"""Whether a field's ``0.5 * inertia * rate**2`` is a mechanical energy, i.e. whether the field
may enter the energy balance and be summed with the other fields in it.
Work done at a prescribed degree of freedom of such a field -- a force through a displacement,
a moment through a rotation -- is an energy by the same token, which is why the external work
is accumulated over exactly these fields as well.
Parameters
----------
field
The name of the physical field.
Returns
-------
bool
True for a field whose inertia is a mass or a rotational inertia.
"""
return getInertiaKind(field) in ("mass", "rotational inertia")
def getFieldSize(field, domainSize):
fType = phenomena[field]
if fType == "scalar":
return 1
if fType == "vector":
return domainSize
if fType == "rotation vector":
# Called for every declared phenomenon, used or not. 2D has a single out-of-plane rotation axis;
# for 1D, 1 is a placeholder that keeps every 1D model buildable, not a physical claim.
if domainSize in (1, 2):
return 1
elif domainSize == 3:
return 3
if fType == "symmetric tensor second order":
# if domainSize == 2:
# return 3
# elif domainSize == 3:
return 6
raise NotImplementedError("Invalid physical field {:} requested".format(field))