Writing Custom Models¶
Writing custom theoretical models is a powerful, extensible option of the LamAna package.
Authoring Custom Models¶
Custom models are simple .py
files that can be locally placed by the
user into the models directory. The API finds these selected files from
the apply(model='<name>')
method in the distributions.Case
class. In order for these processes to work smoothly, the following
essentials are needed to “handshake” with theories
module.
- Implement a
_use_model_()
hook that returns (at minimum) an updated DataFrame. - If using the class-style models, implement
_use_model_()
hook within a class that inherits fromtheories.BaseModel
.
Exceptions for specific models are maintained by the models author.
Which style do I implement?¶
- For beginners, function-style models are the best way to start making custom models.
- We recommend class-style models in general, which use object-oriented principles such as inheritance. This is best suited for intermediate Pythonistas, which we encourage everyone to consider acheiving. :)
The following cell shows an excerpt of the class-style model.
Examples of function-style and class-style models are found in the “examples” folder of the repository.
#------------------------------------------------------------------------------
# Class-style model
# ...
class Model(BaseModel):
'''A custom CLT model.
A modified laminate theory for circular biaxial flexure disks,
loaded with a flat piston punch on 3-ball support having two distinct
materials (polymer and ceramic).
'''
def __init__(self):
self.Laminate = None
self.FeatureInput = None
self.LaminateModel = None
def _use_model_(self, Laminate, adjusted_z=False):
'''Return updated DataFrame and FeatureInput.
...
Returns
-------
tuple
The updated calculations and parameters stored in a tuple
`(LaminateModel, FeatureInput)``.
df : DataFrame
LaminateModel with IDs and Dimensional Variables.
FeatureInut : dict
Geometry, laminate parameters and more. Updates Globals dict for
parameters in the dashboard output.
'''
self.Laminate = Laminate
df = Laminate.LFrame.copy()
FeatureInput = Laminate.FeatureInput
# Author-defined Exception Handling
if (FeatureInput['Parameters']['r'] == 0):
raise ZeroDivisionError('r=0 is invalid for the log term in the moment eqn.')
elif (FeatureInput['Parameters']['a'] == 0):
raise ZeroDivisionError('a=0 is invalid for the log term in the moment eqn.')
# ...
# Calling functions to calculate Qs and Ds
df.loc[:, 'Q_11'] = self.calc_stiffness(df, FeatureInput['Properties']).q_11
df.loc[:, 'Q_12'] = self.calc_stiffness(df, FeatureInput['Properties']).q_12
df.loc[:, 'D_11'] = self.calc_bending(df, adj_z=adjusted_z).d_11
df.loc[:, 'D_12'] = self.calc_bending(df, adj_z=adjusted_z).d_12
# Global Variable Update
if (FeatureInput['Parameters']['p'] == 1) & (Laminate.nplies%2 == 0):
D_11T = sum(df['D_11'])
D_12T = sum(df['D_12'])
else:
D_11T = sum(df.loc[df['label'] == 'interface', 'D_11']) # total D11
D_12T = sum(df.loc[df['label'] == 'interface', 'D_12'])
#print(FeatureInput['Geometric']['p'])
D_11p = (1./((D_11T**2 - D_12T**2)) * D_11T) #
D_12n = -(1./((D_11T**2 - D_12T**2)) *D_12T) #
v_eq = D_12T/D_11T # equiv. Poisson's ratio
M_r = self.calc_moment(df, FeatureInput['Parameters'], v_eq).m_r
M_t = self.calc_moment(df, FeatureInput['Parameters'], v_eq).m_t
K_r = (D_11p*M_r) + (D_12n*M_t) # curvatures
K_t = (D_12n*M_r) + (D_11p*M_t)
# Update FeatureInput
global_params = {
'D_11T': D_11T,
'D_12T': D_12T,
'D_11p': D_11p,
'D_12n': D_12n,
'v_eq ': v_eq,
'M_r': M_r,
'M_t': M_t,
'K_r': K_r,
'K_t:': K_t,
}
FeatureInput['Globals'] = global_params
self.FeatureInput = FeatureInput # update with Globals
#print(FeatureInput)
# Calculate Strains and Stresses and Update DataFrame
df.loc[:,'strain_r'] = K_r * df.loc[:, 'Z(m)']
df.loc[:,'strain_t'] = K_t * df.loc[:, 'Z(m)']
df.loc[:, 'stress_r (Pa/N)'] = (df.loc[:, 'strain_r'] * df.loc[:, 'Q_11']
) + (df.loc[:, 'strain_t'] * df.loc[:, 'Q_12'])
df.loc[:,'stress_t (Pa/N)'] = (df.loc[:, 'strain_t'] * df.loc[:, 'Q_11']
) + (df.loc[:, 'strain_r'] * df.loc[:, 'Q_12'])
df.loc[:,'stress_f (MPa/N)'] = df.loc[:, 'stress_t (Pa/N)']/1e6
del df['Modulus']
del df['Poissons']
self.LaminateModel = df
return (df, FeatureInput)
# Add Defaults here
Note
DEV: If testing with both function- and class-styles, keep in mind any changes to the model should be reflected in both styles.
What are Defaults
?¶
Recall there are a set of geometric, loading and material parameters that are required to run LT calculations. For testing purposes, these parameters can become tedious to set up each time you wish to run a simple plot or test parallel case. Therefore, you can prepare variables that store default parameters with specific values. Calling these variables can reduce the redundancy of typing them over again.
Simply inherit from BaseDefaults
. The BaseDefaults
class stores
a number of common geometry strings, Geometry objects, arbitrary
loading parameters and material properties. These values are
intended to get you started, but can be altered easily to fit your
model. In addition, this class has methods for easily building accepted,
formatted FeatureInput objects.
class Defaults(BaseDefaults):
'''Return parameters for building distributions cases. Useful for consistent
testing.
Dimensional defaults are inherited from utils.BaseDefaults().
Material-specific parameters are defined here by he user.
- Default geometric parameters
- Default material properties
- Default FeatureInput
Examples
========
>>> dft = Defaults()
>>> dft.load_params
{'R' : 12e-3, 'a' : 7.5e-3, 'p' : 1, 'P_a' : 1, 'r' : 2e-4,}
>>> dft.mat_props
{'Modulus': {'HA': 5.2e10, 'PSu': 2.7e9},
'Poissons': {'HA': 0.25, 'PSu': 0.33}}
>>> dft.FeatureInput
{'Geometry' : '400-[200]-800',
'Geometric' : {'R' : 12e-3, 'a' : 7.5e-3, 'p' : 1, 'P_a' : 1, 'r' : 2e-4,},
'Materials' : {'HA' : [5.2e10, 0.25], 'PSu' : [2.7e9, 0.33],},
'Custom' : None,
'Model' : Wilson_LT}
'''
def __init__(self):
BaseDefaults.__init__(self)
'''DEV: Add defaults first. Then adjust attributes.'''
# DEFAULTS ------------------------------------------------------------
# Build dicts of geometric and material parameters
self.load_params = {
'R': 12e-3, # specimen radius
'a': 7.5e-3, # support ring radius
'p': 5, # points/layer
'P_a': 1, # applied load
'r': 2e-4, # radial distance from center loading
}
self.mat_props = {
'Modulus': {'HA': 5.2e10, 'PSu': 2.7e9},
'Poissons': {'HA': 0.25, 'PSu': 0.33}
}
# ATTRIBUTES ----------------------------------------------------------
# FeatureInput
self.FeatureInput = self.get_FeatureInput(
self.Geo_objects['standard'][0],
load_params=self.load_params,
mat_props=self.mat_props,
model='Wilson_LT',
global_vars=None
)
Handling Model Exceptions (0.4.3c6)¶
Since users can create their own models and use them in LamAna
, it
becomes important to handle erroroneous code. The oneous of exception
handling is maintained by the model’s author. However, basic handling is
incorporated within Laminate._update_calculations
to prevent
erroroneous code from halting LamAna. In other words, provided the
variables for Laminate construction are valid, a Laminate will be stored
and accessed via Laminate.LFrame
. This again is the a primitive
DataFrame with IDs and Dimensional data prior to updating. When
_update_cacluations()
is called and any exception is raised, they
are caught and LFrame
is set to LMFrame
, allowing other
dependency code to work. A traceback will still print even though the
exception was caught, allowing the author to improve their code and
prevent breakage. LMFrame
will not update unless the author model
code lacks exceptions.
Again, primary exception handling of models is the author’s responsibility.
Modified Classical Laminate Theory - Wilson_LT
¶
Here is a model that comes with LamAna. It applies Classical Laminate Theory (CLT) to circular-disk laminates with alternating ceramic-polymer materials. CLT was modified for disks loaded in biaxial flexure.
Stiffness Matrix: \(E\) is elastic modulus, \(\nu\) is Poisson’s ratio.
Bending : \(k\) is essentially the enumerated interface where \(k=0\) is tensile surface. \(h\) is the layer thickness relative to the neutral axis where \(t_{middle} = h_{middle}/2\). \(z\) (lower case) is the relative distance betweeen the neuatral axis and a lamina centroid.
Equivalent Poisson’s Ratio
Moments: radial and tangential bending moments. The tangential stress is used for the failure stress.
Curvature
Strain: \(Z\) (caplital) is the distance betwen the neutral axis and the lamina interface.
Stress