#!/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 Fri Jan 27 19:53:45 2017
# @author: Matthias Neuner
import textwrap
from operator import attrgetter
import h5py
import numpy as np
from edelweissfe.config.phenomena import getFieldSize, phenomena
from edelweissfe.fields.nodefield import NodeField
from edelweissfe.journal.journal import Journal
from edelweissfe.variables.fieldvariable import FieldVariable
from edelweissfe.variables.scalarvariable import ScalarVariable
[docs]class FEModel:
"""This is is a standard finite element model tree.
It takes care of the correct number of variables,
for nodes and scalar degrees of freedem, and it manages the fields.
Parameters
----------
dimension
The dimension of the model.
"""
identification = "FEModel"
def __init__(self, dimension: int):
self.time = 0.0 #: Current time of the model.
self.nodes = {} #: Nodes in the model.
self.elements = {} #: Elements in the model.
self.nodeSets = {} #: NodeSets in the model.
self.nodeFields = {} #: NodeFields in the model.
self.elementSets = {} #: ElementSets in the model.
self.sections = {} #: Sections in the model.
self.surfaces = {} #: Surface definitions in the model.
self.constraints = {} #: Constraints in the model.
self.materials = {} #: Materials in the model.
self.analyticalFields = {} #: AnalyticalFields in the model.
self.scalarVariables = {} #: ScalarVariables in the model.
self.additionalParameters = {} #: Additional information.
self.domainSize = dimension #: Spatial dimension of the model
def _populateNodeFieldVariablesFromElements(
self,
):
"""Creates FieldVariables on Nodes depending on the all
elements.
"""
for element in self.elements.values():
for node, elementNodeFields in zip(element.nodes, element.fields):
for field in elementNodeFields:
if field not in node.fields:
node.fields[field] = FieldVariable(node, field)
def _populateNodeFieldVariablesFromConstraints(
self,
):
"""Creates FieldVariables on Nodes depending on the all
constraints.
"""
for constraint in self.constraints.values():
for node, nodeFields in zip(constraint.nodes, constraint.fieldsOnNodes):
for field in nodeFields:
if field not in node.fields:
node.fields[field] = FieldVariable(node, field)
def _createNodeFieldsFromNodes(self, nodes: list, nodeSets: list) -> dict[str, NodeField]:
"""Bundle nodal FieldVariables together in contiguous NodeFields.
Parameters
----------
nodes
The list of Nodes from which the NodeFields should be created.
nodeSets
The list of NodeSets, which should be considered in the index map of the NodeFields.
Returns
-------
dict[str,NodeField]
The dictionary containing the NodeField instances for every active field."""
domainSize = self.domainSize
theNodeFields = dict()
for field in phenomena.keys():
theNodeField = NodeField(field, getFieldSize(field, domainSize), nodes)
if theNodeField.nodes:
theNodeFields[field] = theNodeField
return theNodeFields
def _linkFieldVariableObjects(self, nodes):
"""Link NodeFields to individual FieldVariable objects.
Parameters
----------
nodes
Nodes to be linked
"""
for node in nodes:
for field, fieldVariable in node.fields.items():
nodeField = self.nodeFields[field]
idx = nodeField._indicesOfNodesInArray[node]
fieldVariable.values = self.nodeFields[field]["U"][idx, :]
return
def _requestAdditionalScalarVariable(self, name: str):
"""Create a new scalar variables
Parameters
----------
name
The name of the variable.
Returns
-------
ScalarVariable
The instance of the variable.
"""
if name in self.scalarVariables:
raise Exception("ScalarVariable with name {:} already exists!".format(name))
self.scalarVariables[name] = ScalarVariable()
return self.scalarVariables[name]
def _createAndAssignScalarVariableForConstraints(self, journal: Journal):
"""Create ScalarVariables for constraints.
Parameters
----------
journal
The journal intance.
"""
# we may have additional scalar degrees of freedom, not associated with any node (e.g, lagrangian multipliers of constraints)
for constraintName, constraint in self.constraints.items():
nAdditionalScalarVariables = constraint.getNumberOfAdditionalNeededScalarVariables()
journal.message(
"Constraint {:} requests {:} additional ScalarVariables".format(
constraintName, nAdditionalScalarVariables
),
self.identification,
2,
)
scalarVariables = [
self._requestAdditionalScalarVariable("{:}_{:}".format(constraintName, i))
for i in range(nAdditionalScalarVariables)
]
constraint.assignAdditionalScalarVariables(scalarVariables)
def _prepareVariablesAndFields(self, journal):
"""Prepare all variables and fields for a simulation.
Parameters
----------
journal
The journal instance.
"""
journal.message(
"Activating fields on nodes from Elements and Constraints",
self.identification,
)
self._populateNodeFieldVariablesFromElements()
self._populateNodeFieldVariablesFromConstraints()
journal.message("Bundling fields on nodes to NodeFields", self.identification)
self.nodeFields = self._createNodeFieldsFromNodes(self.nodeSets["all"], self.nodeFields.values())
journal.message("Assembling ScalarVariables", self.identification)
self.scalarVariables = dict()
self._createAndAssignScalarVariableForConstraints(journal)
def _prepareElements(self, journal: Journal):
"""Prepare elements for a simulation.
In detail, sections are assigned.
Parameters
----------
journal
The journal instance.
"""
for section in self.sections.values():
section.assignSectionPropertiesToModel(self)
# check if all elements are assigned a material
materialAssigned = np.fromiter(map(attrgetter("hasMaterial"), self.elements.values()), dtype=bool)
if not materialAssigned.all():
elementIds = np.array([str(elId) for elId in self.elements.keys()])[np.logical_not(materialAssigned)]
raise Exception(f"No material was assigned to element(s) with id(s) {', '.join(elementIds)}.")
[docs] def prepareYourself(self, journal: Journal):
"""Prepare the model for a simulation.
Creates the variables, bundles the fields,
and initializes elements.
Parameters
----------
journal
The journal instance.
"""
self._prepareVariablesAndFields(journal)
self._prepareElements(journal)
[docs] def advanceToTime(self, time: float):
"""Accept the current state of the model and sub instances, and
set the new time.
Parameters
----------
time
The new time.
"""
self.time = time
for el in self.elements.values():
el.acceptLastState()
[docs] def writeRestart(self, restartFile: h5py.File):
"""Write the current state of the model to a restart file.
Parameters
----------
fileName
The name of the restart file.
"""
f = restartFile
f.attrs["time"] = self.time
# node fields
f.create_group("nodeFields")
for nf in self.nodeFields.values():
f["nodeFields"].create_group(nf.name)
for entryName, entryValues in nf._values.items(): # TODO
f["nodeFields"][nf.name].create_dataset(entryName, data=entryValues)
[docs] def readRestart(self, restartFile: h5py.File):
"""Read the state of the model from a restart file.
Parameters
----------
fileName
The name of the restart file.
"""
f = restartFile
self.time = f.attrs["time"]
# node fields
for nf in self.nodeFields.values():
for entryName, entryValues in nf._values.items():
entryValues = f["nodeFields"][nf.name][entryName]
nf[entryName][:] = entryValues
def printPrettyModelSummary(model: FEModel, journal: Journal):
identification = "PrettyModelSummary"
def wrapList(theList):
for line in textwrap.wrap("[" + ", ".join(theList) + "]"):
journal.message(
" {:<20} ".format(line),
identification,
0,
)
journal.message(
"Finite element model with spatial dimension {:} has".format(model.domainSize),
identification,
0,
)
journal.message(
" {:<20}{:<15} ".format("nodes:", len(model.nodes)),
identification,
0,
)
journal.message(
" {:<20}{:<15} ".format("node sets:", len(model.nodeSets)),
identification,
0,
)
wrapList(model.nodeSets.keys())
journal.message(
" {:<20}{:<15} ".format("node fields:", len(model.nodeFields)),
identification,
0,
)
wrapList(["{:} ({:})".format(k, len(v.nodes)) for k, v in model.nodeFields.items()])
journal.message(
" {:<20}{:<15}".format("elements: ", len(model.elements)),
identification,
0,
)
journal.message(
" {:<20}{:<15}".format("element sets: ", len(model.elementSets)),
identification,
0,
)
wrapList(model.elementSets.keys())
if model.constraints:
journal.message(
" {:<20}{:<15}".format("constraints: ", len(model.constraints)),
identification,
0,
)
if model.scalarVariables:
journal.message(
" {:<20}{:<15}".format("scalar variables: ", len(model.scalarVariables)),
identification,
0,
)
journal.message(
" {:<20}{:<15}".format("materials: ", len(model.materials)),
identification,
0,
)
wrapList(model.materials.keys())
if model.analyticalFields:
journal.message(
" {:<20}{:<15}".format("analytical fields: ", len(model.analyticalFields)),
identification,
0,
)
wrapList(model.analyticalFields.keys())