#!/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.
# ---------------------------------------------------------------------
"""
FieldOutputs store all kind of analysis results,
and are defined via the keyword ``*fieldOutput``.
All fieldOutputs are accessable to all outputmanagers at the end of each
increment, step and job.
Furthermore, they can be exported to ``*.csv`` files at the end of the analysis job.
ATTENTION:
If the results are exported to a .csv file with enabled "saveHistory",
the time History is automatically appended to the .csv file"
"""
from typing import Callable, Union
import numpy as np
from edelweissfe.fields.nodefield import NodeField
from edelweissfe.journal.journal import Journal
from edelweissfe.models.femodel import FEModel
from edelweissfe.sets.elementset import ElementSet
from edelweissfe.sets.orderedset import OrderedSet
from edelweissfe.utils.elementresultcollector import ElementResultCollector
documentation = {
"name": "name of the fieldOutput",
"nSet|elSet|node|element": "entity, for which the fieldOutput is defined",
"result": "e.g., U, P, stress, strain ...",
"quadraturePoint": "for element based fieldOutputs only, integers or slices",
"f(x)": "(optional), apply math (in each increment)",
"saveHistory": "(optional), save complete History or only last (increment) result. Default: True (node, element) and False (nSet, elSet)", # noqa: E501
"export": "(optional), export the fieldOutput to a file at the end of the job",
"f_export(x)": "(optional), apply math on the final result (table)",
}
[docs]class _FieldOutputBase:
"""
Entity of a fieldOutput request.
Carries the history or the latest result.
Parameters
----------
name
The name of this FieldOutput.
model
A dictionary containing the model tree.
journal
The journal object for logging.
saveHistory
Save the complete history or only the last result.
f_x
Apply a math function on the results.
export
Export the results to a file.
fExport_x
Apply a math function on the results before exporting.
"""
def __init__(
self,
name: str,
model: FEModel,
journal: Journal,
saveHistory: bool = False,
f_x: Callable = None,
export: str = None,
fExport_x: Callable = None,
):
self.timeTotal = 0.0
self.name = name
self.model = model
self.journal = journal
self.appendResults = saveHistory
self.result = [] if self.appendResults else None
self.f = f_x
self.f_export = fExport_x
self.timeHistory = []
self.export = export
[docs] def getLastResult(
self,
) -> np.ndarray:
"""Get the last result, no matter if the history is stored or not.
Returns
-------
np.ndarray
The result array.
"""
return self.result[-1] if self.appendResults else self.result
[docs] def getResultHistory(
self,
) -> np.ndarray:
"""Get the history.
Throws an exception if the history is not stored.
Returns
-------
np.ndarray
The result history.
"""
if not self.appendResults:
raise Exception(
"fieldOuput {:} does not save any history; please define it with saveHistory=True!".format(self.name)
)
return np.asarray(self.result)
[docs] def getTimeHistory(
self,
) -> np.ndarray:
"""Get the time history.
Returns
-------
np.ndarray
The time history.
"""
return np.asarray(self.timeHistory)
def _applyResultsPipleline(self, result):
"""Apply the pipeline of operations onto the results.
Called by inheriting classes.
Parameters
----------
model
The model tree.
"""
self.timeHistory.append(self.model.time)
if self.f:
result = self.f(result)
if self.appendResults:
self.result.append(result.copy())
else:
self.result = result
[docs] def writeLastResult(self):
"""Update file output.
Parameters
----------
model
The model tree.
"""
res = np.asarray(self.result)
if self.f_export:
res = self.f_export(res)
if res.ndim > 2:
self.journal.message("Reshaping fieldOutput result for export in .csv file", self.name)
res = res.reshape((res.shape[0], -1))
with open(f"{self.export}.csv", "a") as f:
np.savetxt(
f,
np.hstack(
(
self.timeHistory[-1],
res[-1,],
)
).reshape((1, -1)),
)
[docs] def initializeJob(self):
"""Initalize everything. Will also update the results
based on the proved start time and solution.
"""
self.updateResults(self.model)
if self.export:
f = open(f"{self.export}.csv", "w")
f.close()
def initializeStep(self, step):
if self.export:
self.writeLastResult()
[docs] def finalizeIncrement(
self,
):
"""Finalize an increment, i.e. store the current results."""
self.updateResults(self.model)
if self.export:
self.writeLastResult()
def finalizeStep(
self,
):
pass
def finalizeJob(
self,
):
pass
[docs] def setResults(self, values: np.ndarray):
"""Modifies a result at it's origin, if possible.
Throws an exception if not possible.
Parameters
----------
values
The values.
"""
raise Exception("setting field output currently not implemented for this type of output!")
def __eq__(self, other):
if type(other) is str:
return other == self.name
return self.getLastResult() == other
def __ne__(self, other):
return self.getLastResult() != other
def __lt__(self, other):
return self.getLastResult() < other
def __le__(self, other):
return self.getLastResult() <= other
def __gt__(self, other):
return self.getLastResult() > other
def __ge__(self, other):
return self.getLastResult() >= other
def __getitem__(self, index):
return self.getLastResult()[index]
def __add__(self, other):
return self.getLastResult() + other
def __sub__(self, other):
return self.getLastResult() - other
[docs]class NodeFieldOutput(_FieldOutputBase):
"""
This is a Node based FieldOutput.
It operates on NodeFields.
Parameters
----------
name
The name of this FieldOutput.
nodeField
The NodeField, on which this FieldOutput operates.
result
The name of the result entry in the NodeField.
model
The model tree instance.
journal
The journal object for logging.
saveHistory
Save the complete history or only the last result.
f_x
Apply a math function on the results.
export
Export the results to a file.
fExport_x
Apply a math function on the results before exporting.
"""
def __init__(
self,
name: str,
nodeField,
result: str,
model: FEModel,
journal: Journal,
saveHistory: bool = False,
f_x: Callable = None,
export: str = None,
fExport_x: Callable = None,
):
self.entry = result
self._nodeField = nodeField
self.associatedSet = nodeField.associatedSet
super().__init__(name, model, journal, saveHistory, f_x, export, fExport_x)
[docs] def updateResults(self, model: FEModel):
"""Update the field output.
Will use the current solution and reaction vector if result is a nodal result.
Parameters
----------
model
The model tree.
"""
result = self._nodeField[self.entry]
return super()._applyResultsPipleline(result)
[docs]class ElementFieldOutput(_FieldOutputBase):
"""
This is a Element based FieldOutput.
It operates on ElementSets.
Parameters
----------
name
The name of this FieldOutput.
elSet
The ElementSet on which this FieldOutput operates.
resultName
The name of the result entry in the :class:`ElementBase.
model
The model tree instance.
journal
The journal object for logging.
saveHistory
Save the complete history or only the last result.
f_x
Apply a math function on the results.
export
Export the results to a file.
fExport_x
Apply a math function on the results before exporting.
quadraturePoints
The list of quadrature points for which the results should be extracted.
"""
def __init__(
self,
name: str,
elSet: ElementSet,
resultName: str,
model: FEModel,
journal: Journal,
saveHistory: bool = False,
f_x: Callable = None,
export: str = None,
fExport_x: Callable = None,
quadraturePoints: Union[int, slice, list[int]] = 0,
):
self.associatedSet = elSet
self.resultName = resultName
self.quadraturePoints = quadraturePoints
self.elementResultCollector = ElementResultCollector(
list(self.associatedSet), self.quadraturePoints, self.resultName
)
super().__init__(name, model, journal, saveHistory, f_x, export, fExport_x)
[docs] def updateResults(self, model: FEModel):
"""Update the field output.
Will use the current solution and reaction vector if result is a nodal result.
Parameters
----------
model
The model tree.
"""
result = self.elementResultCollector.getCurrentResults()
super()._applyResultsPipleline(result)
[docs] def setResults(self, values: np.ndarray):
"""Modifies a result at it's origin, if possible.
Throws an exception if not possible.
Parameters
----------
values
The values.
"""
if self.f:
raise Exception("cannot set field output for modified results (f(x) != None) !")
for i, el in enumerate(self.associatedSet):
for j, g in enumerate(self.quadraturePoints):
theArray = el.getResultArray(self.resultName, g, True)
theArray[:] = values[i, j, :]
el.acceptLastState()
[docs]class ExpressionFieldOutput(_FieldOutputBase):
"""
This is a Node based FieldOutput.
It operates on NodeFields.
Parameters
----------
associatedSet
The associated set of nodes or elements.
theExpression
The expression to be evaluated.
name
The name of this FieldOutput.
nodeField
The NodeField, on which this FieldOutput operates.
result
The name of the result entry in the NodeField.
model
The model tree instance.
journal
The journal object for logging.
saveHistory
Save the complete history or only the last result.
f_x
Apply a math function on the results.
export
Export the results to a file.
fExport_x
Apply a math function on the results before exporting.
"""
def __init__(
self,
associatedSet: OrderedSet | None,
theExpression,
name: str,
model: FEModel,
journal: Journal,
saveHistory: bool = False,
f_x: Callable = None,
export: str = None,
fExport_x: Callable = None,
):
self.associatedSet = associatedSet
self.theExpression = theExpression
super().__init__(name, model, journal, saveHistory, f_x, export, fExport_x)
[docs] def updateResults(self, model: FEModel):
"""Update the field output.
Will use the current solution and reaction vector if result is a nodal result.
Parameters
----------
model
The model tree.
"""
if self.associatedSet:
result = np.reshape(np.asarray(self.theExpression()), (len(self.associatedSet), -1))
else:
result = np.reshape(np.asarray(self.theExpression()), (1, -1))
return super()._applyResultsPipleline(result)
[docs]class FieldOutputController:
"""
The central module for managing field outputs, which can be used by output managers.
"""
def __init__(self, model: FEModel, journal: Journal):
self.model = model
self.journal = journal
self.fieldOutputs = {}
[docs] def addExpressionFieldOutput(
self,
associatedSet: set,
theExpression: Callable,
name: str,
saveHistory=False,
f_x: Callable = None,
export: str = None,
fExport_x: Callable = None,
):
"""Add a new FieldOutput entry to be computed during the simulation
Parameters
----------
associatedSet
The associated set of nodes or elements.
theExpression
The expression to be evaluated.
name
The name of this FieldOutput.
saveHistory
Save the complete history or only the last result.
f_x
Apply a math function on the results.
export
Export the results to a file.
fExport_x
Apply a math function on the results before exporting.
"""
if name in self.fieldOutputs:
raise Exception("FieldOutput {:} already exists!".format(name))
self.fieldOutputs[name] = ExpressionFieldOutput(
associatedSet,
theExpression,
name,
self.model,
self.journal,
saveHistory,
f_x=f_x,
export=export,
fExport_x=fExport_x,
)
[docs] def addPerNodeFieldOutput(
self,
name: str,
nodeField: NodeField,
result: str = None,
saveHistory=False,
f_x: Callable = None,
export: str = None,
fExport_x: Callable = None,
):
"""Add a new FieldOutput entry to be computed during the simulation
Parameters
----------
name
The name of this FieldOutput.
nodeField
The :class:`NodeField, on which this FieldOutput should operate.
result
The name of the result entry in the :class:`NodeField
journal
The :class:`Journal instance for logging purposes.
saveHistory
Save the complete history or only the last result.
f_x
Apply a math function on the results.
export
Export the results to a file.
fExport_x
Apply a math function on the results before exporting.
"""
if not result:
result = name
if name in self.fieldOutputs:
raise Exception("FieldOutput {:} already exists!".format(name))
self.fieldOutputs[name] = NodeFieldOutput(
name, nodeField, result, self.model, self.journal, saveHistory, f_x, export, fExport_x
)
[docs] def addPerElementFieldOutput(
self,
name: str,
elSet: ElementSet,
result: str = None,
saveHistory=False,
f_x: Callable = None,
export: str = None,
fExport_x: Callable = None,
quadraturePoints=Union[int, slice, list[int]],
):
"""Add a new FieldOutput entry to be computed during the simulation
Parameters
----------
name
The name of this FieldOutput.
elSet
The :class:`ElementSet on which this FieldOutput should operate.
result
The name of the result, which is provided by the Elements in the :class:`ElementSet.
journal
The :class:`Journal instance for logging purposes.
saveHistory
Save the complete history or only the last result.
f_x
Apply a math function on the results.
export
Export the results to a file.
fExport_x
Apply a math function on the results before exporting.
quadraturePoints
The indices of quadrature points for which the results should be extracted.
"""
if not result:
result = name
if name in self.fieldOutputs:
raise Exception("FieldOutput {:} already exists!".format(name))
self.fieldOutputs[name] = ElementFieldOutput(
name, elSet, result, self.model, self.journal, saveHistory, f_x, export, fExport_x, quadraturePoints
)
def initializeJob(self):
for fieldOutput in self.fieldOutputs.values():
fieldOutput.initializeJob()
[docs] def finalizeIncrement(
self,
):
"""Finalize all field outputs at the end of an increment."""
for output in self.fieldOutputs.values():
output.finalizeIncrement()
[docs] def finalizeStep(
self,
):
"""Finalize all field outputs at the end of a step."""
for output in self.fieldOutputs.values():
output.finalizeStep()
[docs] def initializeStep(self, step):
"""Initalize an step.
Parameters
----------
step
The step information.
"""
for output in self.fieldOutputs.values():
output.initializeStep(step)
[docs] def finalizeJob(
self,
):
"""Finalize all field outputs at the end of a job."""
for output in self.fieldOutputs.values():
output.finalizeJob()