Plate under static loadΒΆ
Note
The script for this example can be found under the examples/plate/ directory.
This problem will show how to use some of pytacs more advanced load setting procedures. The nominal case is a 1m x 1m flat plate. The perimeter of the plate is fixed in all 6 degrees of freedom. The plate comprises 900 CQUAD4 elements. We consider a static case where a 10 kN point force is applied at the plate center.
First, import required libraries:
import numpy as np
from tacs import functions, constitutive, elements, pyTACS
Next, we must create the pyTACS
class.
pyTACS
acts as an assembler for all of the TACS submodules.
It's purpose is to read in mesh files, setup TACS element objects, and create TACS problems for analysis.
To create a pyTACS
class, at minimum, a NASTRAN bdf defining nodes, elements, and boundary conditions is required.
bdfFile = './plate.bdf'
FEAAssembler = pyTACS(bdfFile)
Next step, we must initialize pyTACS.
pyTACS must always be initialized before an analysis can be conducted.
During this step, TACS element objects and design variables are setup and assigned for use in analysis.
There are two ways to call the pyTACS.initialize
method.
The first, involves defining a elemCallBack()
function that tells pyTACS which TACS element to setup
for each NASTRAN element card in the BDF and passing this function handle to the pyTACS.initialize
.
The second method, allows pyTACS to automatically initialize itself based on information from the BDF file,
as long as property cards exist for every element. This is done by calling the
pyTACS.initialize
method with no arguments.
For more information on the pyTACS initialization procedure, see here.
For this example, we will take the first approach. Our elemCallBack()
for this model is defined below.
def elemCallBack(dvNum, compID, compDescript, elemDescripts, specialDVs, **kwargs):
# Material properties
rho = 2500.0 # density kg/m^3
E = 70e9 # Young's modulus (Pa)
nu = 0.3 # Poisson's ratio
ys = 464.0e6 # yield stress
# Plate geometry
tplate = 0.005 # 5 mm
# Set up material properties
prop = constitutive.MaterialProperties(rho=rho, E=E, nu=nu, ys=ys)
# Set up constitutive model
con = constitutive.IsoShellConstitutive(prop, t=tplate, tNum=dvNum)
# Set the transform used to define shell stresses, None defaults to NaturalShellTransform
transform = None
# Set up tacs element for every entry in elemDescripts
# According to the bdf file, elemDescripts should always be ["CQUAD4"]
elemList = []
for descript in elemDescripts:
if descript == 'CQUAD4':
elem = elements.Quad4Shell(transform, con)
else: # Add a catch for any unexpected element types
raise ValueError(f"Unexpected element of type {descript}.")
return elemList
The callback function for this example is pretty simple.
First, we define the MaterialProperties
for aluminum.
We then use those properties and the plate thickness to setup a IsoShellConstitutive
for modeling the shell stiffness. We set the element transform type to None
. Finally, for every element card in
elemDescripts
, we pass back an appropriate initialized TACS element class. In this case, the only element type
in the BDF are CQUAD4, so we'll always pass back an elemList
with one entry, a Quad4Shell
.
Now that the callback function has been defined, we can pass it to pyTACS.initialize
.
FEAAssembler.initialize(elemCallBack)
The pyTACS
has been initialized, we can now use it to create a StaticProblem
.
TACS problem classes are generally responsible for setting loads, solving analyses, evaluating
functions of interests, and computing gradients.
To create our StaticProblem
we can use the
pyTACS.createStaticProblem
method.
This method requires at minimum a name for our problem.
staticProb = FEAAssembler.createStaticProblem('point_force')
Next, we'll add some functions of interest to our problem that we can evaluate after we've solved it.
This can be accomplished using StaticProblem.addFunction
method.
This method takes a user-defined name and any uninitialized TACS functions
class as an input. Additional arguments necessary to
setup the function class (minus the Assembler
) can be passed as keyword arguments to StaticProblem.addFunction
.
For now let's add a function to evaluate the mass of the plate using StructuralMass
and a function to evaluate the maximum vonMises-based failure criteria using KSFailure
.
staticProb.addFunction('mass', functions.StructuralMass)
staticProb.addFunction('ks_vmfailure', functions.KSFailure, ksWeight=100.0)
Now let's add our point load to the problem. We can do this by using the
StaticProblem.addLoadToNodes
method and
selecting node ID 481 (the node at the center of the plate).
F = np.array([0.0, 0.0, 1e4, 0.0, 0.0, 0.0])
staticProb.addLoadToNodes(481, F, nastranOrdering=True)
Now that our problem has been setup with loads and functions we can solve it and evaluate its functions using the
StaticProblem.solve
and
StaticProblem.evalFunctions
methods, respectively.
funcs = {}
problem.solve()
problem.evalFunctions(funcs)
To get the function sensitivity with respect to the design variables and node locations using the
StaticProblem.evalFunctionsSens
method.
funcsSens = {}
problem.evalFunctionsSens(funcsSens)
Finally, we can write out our solution to an f5 file format for further post-processing and visualization by using the
StaticProblem.writeSolution
method.
problem.writeSolution()
This produces a file called point_force_000.f5
in our runscript directory. This file can be converted into a .vtk
file
(using f5tovtk
) for visualization in Paraview or a .plt
(using f5totec
) for visualization in TecPlot using:
$ f5tovtk point_force_000.f5
or
$ f5totec point_force_000.f5