from __future__ import absolute_import
import numpy as np
from desicos.logger import warn
from .imperfection import Imperfection
[docs]class FFI(Imperfection):
r"""Fiber Fraction Imperfection
Thickness variations are generally caused by a varying amount of matrix,
while the amount of fibers remains constant. Thus, the actual fiber volume
fraction is higher in thinner sections of the material. This imperfection
aims to include that effect in the model, by adjusting the material
properties.
===================== ==================================================
Attributes Description
===================== ==================================================
nominal_vf ``float``, nominal fiber volume fraction
E_matrix ``float``, Young's modulus of the matrix material
nu_matrix ``float``, Poisson's ratio of the matrix material
use_ti ``bool``, if ``True``, create varying material
properties according to the thickness imperfection
data (if present).
global_sf ``float`` or ``None``, global scaling factor to
apply to the material thickness. Set to ``None``
to disable. The global scaling may be overridden
by a thickness imperfection, if ``use_ti`` (see
above) is ``True``.
created ``bool``, ``True`` after the imperfection has been
created.
===================== ==================================================
"""
def __init__(self, nominal_vf, E_matrix, nu_matrix, use_ti, global_sf=None):
super(FFI, self).__init__()
self.nominal_vf = nominal_vf
self.E_matrix = E_matrix
self.nu_matrix = nu_matrix
self.use_ti = use_ti
self.global_sf = global_sf
self.name = 'FFI'
self.xaxis = 'scaling_factor'
self.xaxis_label = 'Global thickness scaling factor, -'
self.created = False
@property
def scaling_factor(self):
return 1.0 if self.global_sf is None else self.global_sf
def rebuild(self):
if self.global_sf is not None:
self.name = 'FFI_SF_%05d' % (int(round(100*self.global_sf)))
[docs] def calc_amplitude(self):
"""Calculates the imperfection amplitude
Amplitude measured as the biggest thickness difference between
the actual and nominal layup thickness of the Cone/Cylinder,
considering only the layups that have this imperfection applied.
.. note:: Must be called from Abaqus.
Returns
-------
max_amp : float
Maximum absolute imperfection amplitude.
"""
cc = self.impconf.conecyl
max_amp = 0
cc_total_t = sum(cc.plyts)
if self.global_sf is not None:
max_amp = abs(self.global_sf - 1.0) * cc_total_t
if self.use_ti:
from abaqus import mdb
part = mdb.models[cc.model_name].parts[cc.part_name_shell]
for layup in part.compositeLayups.values():
if not layup.suppressed:
layup_t = sum(p.thickness for p in layup.plies.values())
max_amp = max(max_amp, abs(layup_t - cc_total_t))
self.amplitude = max_amp
return self.amplitude
[docs] def calc_scaled_laminaprop(self, laminaprop, scaling_factor):
"""Calculate material properties of a lamina with a scaled thickness.
Calculates the new lamina properties, if a given material (lamina)
is scaled in thickness by a given factor. The new material properties
are calculated from the properties of the fiber and the matrix, using
the composition rule (for E11 and nu12) and the corrected composition
rule (for E22, G12, G13 and G23).
The matrix properties are to be supplied by the user. The fiber
properties are calculated based on the original (nominal) lamina
properties, using the inverse of the respective composition rule.
Parameters
----------
laminaprop : tuple
Material properties (E11, E12, nu12, G12, G13, G23) of the lamina
at the nominal thickness
scaling_factor : float
Scaling factor that is to be applied to the ply thickness. The
total amount of fibers is assumed to remain constant, thus the
actual fiber volume fraction is inversely proportional to this
scaling factor.
Returns
-------
new_laminaprop : tuple
Lamina properties of the lamina with a scaled thickness
"""
assert len(laminaprop) == 6
E_m = float(self.E_matrix)
nu_m = float(self.nu_matrix)
G_m = E_m / (2.*(1.+nu_m))
vf_nom = float(self.nominal_vf)
vf = vf_nom / scaling_factor
if not (0. < vf < 1.):
raise ValueError(('Invalid scaling factor {0:.2f} for Fiber' +
'Fraction Imperfection, resulting fiber volume fraction' +
' ({1:.2f}) is out of range 0..1)').format(scaling_factor, vf))
# Composition rule to obtain material properties,
# based on fiber and matrix data
# There are two different rules, one for longitudinal properties
# (E11, nu12) and one for transversal props (E22, G12, G13, G23)
def compL(X_f, X_m):
return vf*X_f + (1 - vf)*X_m
def compT(X_f, X_m):
return X_m / (1 - np.sqrt(vf)*(1 - X_m/X_f))
comp_rule = (compL, compT, compL, compT, compT, compT)
# Inverse composition rule to obtain fiber properties,
# based on nominal laminate properties and matrix data
def invL(X_nom, X_m):
return (X_nom - (1 - vf_nom)*X_m) / vf_nom
def invT(X_nom, X_m):
return X_m*np.sqrt(vf_nom) / (X_m/X_nom - (1 - np.sqrt(vf_nom)))
inv_comp_rule = (invL, invT, invL, invT, invT, invT)
matrix_prop = (E_m, E_m, nu_m, G_m, G_m, G_m)
fiber_prop = tuple(f(X_nom, X_m) for f, X_nom, X_m in
zip(inv_comp_rule, laminaprop, matrix_prop))
new_laminaprop = tuple(f(X_f, X_m) for f, X_f, X_m in
zip(comp_rule, fiber_prop, matrix_prop))
DEBUG_STRING = 'E11={0:.2f}, E22={1:.2f}, nu12 = {2:.2f},' +\
' G12={3:.2f}, G13={4:.2f}, G23={5:.2f}'
to_check = [(laminaprop, 'nominal laminate'),
(matrix_prop, 'matrix'),
(fiber_prop, 'calculated fiber'),
(new_laminaprop, 'imperfect laminate')]
for prop, name in to_check:
if not (all(x > 0 for x in prop) and prop[2] < 0.5):
mat_str = DEBUG_STRING.format(*prop)
raise ValueError(('One or more invalid value(s) for {0}' +
' properties:\n{1}').format(name, mat_str))
return new_laminaprop
def _update_material(self, suffix, scaling_factor):
from abaqus import mdb
cc = self.impconf.conecyl
mod = mdb.models[cc.model_name]
warned = set()
for name, laminaprop in zip(cc.laminapropKeys, cc.laminaprops):
if len(laminaprop) == 6:
new_laminaprop = self.calc_scaled_laminaprop(laminaprop, scaling_factor)
else:
if name not in warned:
warn(('Invalid number of lamina properties for material' +
'{0}, or material is isotropic. Fiber Fraction' +
' Imperfection is not applied.').format(name))
warned.add(name)
new_laminaprop = laminaprop
new_mat = mod.Material(name=(name + suffix),
objectToCopy=mod.materials[name])
new_mat.elastic.setValues(table=(new_laminaprop,))
[docs] def create(self):
"""Actually create the imperfection
.. note:: Must be called from Abaqus.
"""
from abaqus import mdb
from desicos.abaqus.abaqus_functions import modify_composite_layup
cc = self.impconf.conecyl
part = mdb.models[cc.model_name].parts[cc.part_name_shell]
if self.global_sf is not None:
MAT_SUFFIX = '_scaled'
self._update_material(MAT_SUFFIX, self.global_sf)
def modify_mat_thick(index, kwargs):
kwargs['thickness'] = cc.plyts[index] * self.global_sf
kwargs['material'] = cc.laminapropKeys[index] + MAT_SUFFIX
return kwargs
modify_composite_layup(part, 'CompositePlate', modify_mat_thick)
self.update_after_tis()
self.created = True
[docs] def update_after_tis(self):
"""Call this function after the thickness imperfection(s) are applied,
to modify the material properties as well, if needed.
"""
if not self.use_ti:
return
from abaqus import mdb
from desicos.abaqus.abaqus_functions import modify_composite_layup
cc = self.impconf.conecyl
part = mdb.models[cc.model_name].parts[cc.part_name_shell]
for layup_name in part.compositeLayups.keys():
if layup_name.startswith('CLayup_'):
layup = part.compositeLayups[layup_name]
thickness = sum(p.thickness for p in layup.plies.values())
scaling_factor = thickness / sum(cc.plyts)
suffix = layup_name[-6:]
self._update_material(suffix, scaling_factor)
def modify_material(index, kwargs):
kwargs['material'] = cc.laminapropKeys[index] + suffix
return kwargs
modify_composite_layup(part, layup_name, modify_material)