Source code for edelweissfe.models.femodel

#!/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())