From 5ecadcddd87f5a09bff7a80317db16cb7f63c22c Mon Sep 17 00:00:00 2001 From: sean-engelstad Date: Fri, 20 Oct 2023 12:30:38 -0400 Subject: [PATCH 01/10] add new titanium alloy --- tacs/caps2tacs/materials.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tacs/caps2tacs/materials.py b/tacs/caps2tacs/materials.py index 9241fbabd..d8c599cdf 100644 --- a/tacs/caps2tacs/materials.py +++ b/tacs/caps2tacs/materials.py @@ -195,6 +195,21 @@ def titanium(cls): kappa=11.4, ) + @classmethod + def titanium_alloy(cls): + # Ti 6AL-4V (grade 5) + return cls( + name="titanium-alloy", + E=114e9, + nu=0.361, + rho=4.43e3, + T1=880e6, + C1=970e6, + alpha=9.2e-6, + cp=526.3, + kappa=6.7, + ) + @classmethod def steel(cls): return cls( From b87424a1c2859c23e63f619d251a4f6500e932f1 Mon Sep 17 00:00:00 2001 From: seanfireball1 Date: Sun, 29 Oct 2023 18:08:48 -0400 Subject: [PATCH 02/10] add smeared stringer model and PCOMP cards into caps2tacs --- examples/caps_wing/6_stringer_test.py | 109 ++++++++++++++++++++++++ examples/caps_wing/simple_naca_wing.csm | 5 ++ tacs/caps2tacs/materials.py | 62 ++++++++++++++ tacs/caps2tacs/property.py | 70 +++++++++++++-- tacs/caps2tacs/tacs_aim.py | 10 ++- tacs/caps2tacs/variables.py | 32 +++++-- 6 files changed, 270 insertions(+), 18 deletions(-) create mode 100644 examples/caps_wing/6_stringer_test.py diff --git a/examples/caps_wing/6_stringer_test.py b/examples/caps_wing/6_stringer_test.py new file mode 100644 index 000000000..30bd37558 --- /dev/null +++ b/examples/caps_wing/6_stringer_test.py @@ -0,0 +1,109 @@ +""" +Sean Engelstad, Febuary 2023 +GT SMDO Lab, Dr. Graeme Kennedy +Caps to TACS example +""" + +from tacs import caps2tacs +from mpi4py import MPI + +# run a steady elastic structural analysis in TACS using the tacsAIM wrapper caps2tacs submodule +# ------------------------------------------------------------------------------------------------- +# 1: build the tacs aim, egads aim wrapper classes + +comm = MPI.COMM_WORLD +tacs_model = caps2tacs.TacsModel.build(csm_file="simple_naca_wing.csm", comm=comm) +tacs_model.mesh_aim.set_mesh( + edge_pt_min=15, + edge_pt_max=20, + global_mesh_size=0.25, + max_surf_offset=0.01, + max_dihedral_angle=15, +).register_to(tacs_model) + +aluminum = caps2tacs.Isotropic.aluminum() +aluminum_w_stringer = caps2tacs.Orthotropic.smeared_stringer(aluminum, area_ratio=0.5).register_to(tacs_model) + +# setup the thickness design variables + automatic shell properties +nribs = int(tacs_model.get_config_parameter("nribs")) +nspars = int(tacs_model.get_config_parameter("nspars")) +for irib in range(1, nribs + 1): + caps2tacs.ThicknessVariable( + caps_group=f"rib{irib}", value=0.05, material=aluminum_w_stringer, ply_angle=0, + ).register_to(tacs_model) +for ispar in range(1, nspars + 1): + caps2tacs.ThicknessVariable( + caps_group=f"spar{ispar}", value=0.05, material=aluminum_w_stringer, ply_angle=0, + ).register_to(tacs_model) +caps2tacs.ThicknessVariable( + caps_group="OML", value=0.03, material=aluminum_w_stringer, ply_angle=0, +).register_to(tacs_model) + +# add constraints and loads +caps2tacs.PinConstraint("root").register_to(tacs_model) +caps2tacs.GridForce("OML", direction=[0, 0, 1.0], magnitude=100).register_to(tacs_model) + +# add analysis functions to the model +caps2tacs.AnalysisFunction.ksfailure(ksWeight=50.0, safetyFactor=1.5).register_to( + tacs_model +) +caps2tacs.AnalysisFunction.mass().register_to(tacs_model) + +# run the pre analysis to build tacs input files +# alternative is to call tacs_aim.setup_aim().pre_analysis() with tacs_aim = tacs_model.tacs_aim +tacs_model.setup(include_aim=True) + +# ---------------------------------------------------------------------------------- +# 2. Run the TACS steady elastic structural analysis, forward + adjoint + +# choose method 1 or 2 to demonstrate the analysis : 1 - fewer lines, 2 - more lines +method = 2 + +# show both ways of performing the structural analysis in more or less detail +if method == 1: # less detail version + tacs_model.pre_analysis() + tacs_model.run_analysis() + tacs_model.post_analysis() + + print("Tacs model static analysis outputs...\n") + for func in tacs_model.analysis_functions: + print(f"func {func.name} = {func.value}") + + for var in tacs_model.variables: + derivative = func.get_derivative(var) + print(f"\td{func.name}/d{var.name} = {derivative}") + print("\n") + +elif method == 2: # longer way that directly uses pyTACS & static problem routines + SPs = tacs_model.createTACSProbs(addFunctions=True) + + # solve each structural analysis problem (in this case 1) + tacs_funcs = {} + tacs_sens = {} + function_names = tacs_model.function_names + for caseID in SPs: + SPs[caseID].solve() + SPs[caseID].evalFunctions(tacs_funcs, evalFuncs=function_names) + SPs[caseID].evalFunctionsSens(tacs_sens, evalFuncs=function_names) + SPs[caseID].writeSolution( + baseName="tacs_output", outputDir=tacs_model.analysis_dir + ) + + # print the output analysis functions and sensitivities + print("\nTacs Analysis Outputs...") + for func_name in function_names: + # find the associated tacs key (tacs key = (loadset) + (func_name)) + for tacs_key in tacs_funcs: + if func_name in tacs_key: + break + + func_value = tacs_funcs[tacs_key].real + print(f"\tfunctional {func_name} = {func_value}") + + struct_sens = tacs_sens[tacs_key]["struct"] + print(f"\t{func_name} structDV gradient = {struct_sens}") + + xpts_sens = tacs_sens[tacs_key]["Xpts"] + print(f"\t{func_name} coordinate derivatives = {xpts_sens}") + + print("\n") diff --git a/examples/caps_wing/simple_naca_wing.csm b/examples/caps_wing/simple_naca_wing.csm index 01c3c4c1d..5f1b194a8 100644 --- a/examples/caps_wing/simple_naca_wing.csm +++ b/examples/caps_wing/simple_naca_wing.csm @@ -34,6 +34,10 @@ despmtr rib_a1 1.0 despmtr rib_a2 0.0 set rib_a3 1.0-rib_a1-rib_a2 +# composite material coordinate system +dimension compositeCoord 9 1 0 +set compositeCoord "0;0;0;0.0;1.0;0.0;1.0;0.0;0.0;" + # Depedendent parameters set sspan span/2.0 @@ -181,6 +185,7 @@ attribute capsMesh $OML # add AIM attribute to specify the analyses to use select body +csystem wing compositeCoord ATTRIBUTE AFLR4_Cmp_ID 1 attribute capsMeshLength 1.0 attribute capsAIM $aflr4AIM;tacsAIM;egadsTessAIM diff --git a/tacs/caps2tacs/materials.py b/tacs/caps2tacs/materials.py index d8c599cdf..bc0227ef8 100644 --- a/tacs/caps2tacs/materials.py +++ b/tacs/caps2tacs/materials.py @@ -209,6 +209,21 @@ def titanium_alloy(cls): cp=526.3, kappa=6.7, ) + + @classmethod + def aluminum_alloy(cls): + # Aluminum alloy Al-MS89 + return cls( + name="aluminum-alloy", + E=90e9, + rho=2.92e3, + nu=0.3, + T1=420e6, + C1=420e6, + alpha=19.0e-6, + kappa=115.0, + cp=903, # guessed the cp (not provided in data sheet) + ) @classmethod def steel(cls): @@ -302,3 +317,50 @@ def carbon_fiber(cls): kappa3=4.8, #W/m-K cp=1130.0 # J / kg-K ) + + @classmethod + def smeared_stringer(cls, isotropic:Isotropic, area_ratio:float): + """ + By: Sean Engelstad + Adapted from paper "Smeared Stiffeners in Panel for Mesh Simplification at + Conceptual Design Phase" by Denis Walch1, Simon Tetreault2, and Franck Dervault3 + + Here area_ratio = Astiff / (Askin+Astiff) in the cross-section which is "assumed + to be in the range 0.25 to 0.66 for most aircraft structures" + """ + # get the isotropic properties + E = isotropic._E1 + nu = isotropic._nu12 + rho = isotropic._rho + T1 = isotropic._T1 + C1 = isotropic._C1 + alpha = isotropic._alpha1 + kappa = isotropic._kappa1 + cp = isotropic._cp + assert (0.0 < area_ratio) and (area_ratio < 1.0) + # get area_ratio2 = Astiff / Askin + area_ratio2 = area_ratio / (1 - area_ratio) + # new moduli (stiffened in spanwise direction) + Ex = E + Ey = E / (1 - nu**2 * area_ratio) + nu_xy = nu + nu_yx = nu / (1 + area_ratio2 * (1 - nu**2)) + Gxy = Ex * nu_yx / (1 - nu_xy * nu_yx) + return cls( + name=f"smeared-stringer-{isotropic.name}", + E1=Ex, + E2=Ey, + G12=Gxy, + nu12=nu_xy, + T1=T1, + C1=C1, + T2=T1, + C2=C1, + rho=rho*(1+area_ratio2), + alpha1 = alpha, + alpha2=alpha, + kappa1= kappa, + kappa2=kappa, + kappa3=kappa, + cp=cp, + ) diff --git a/tacs/caps2tacs/property.py b/tacs/caps2tacs/property.py index 8f03d24e5..a60910899 100644 --- a/tacs/caps2tacs/property.py +++ b/tacs/caps2tacs/property.py @@ -1,10 +1,10 @@ -__all__ = ["Property", "ShellProperty"] +__all__ = ["BaseProperty", "ShellProperty", "CompositeProperty"] from .materials import Material from typing import TYPE_CHECKING -class Property: +class BaseProperty: def __init__(self, caps_group: str, material: Material, property_type: str): self._caps_group = caps_group self._material = material @@ -32,7 +32,7 @@ def register_to(self, tacs_aim): return self -class ShellProperty(Property): +class ShellProperty(BaseProperty): """ Example ShellProperty Dictionary shell = {"propertyType" : "Shell", @@ -49,7 +49,7 @@ def __init__( caps_group: str, material: Material, membrane_thickness: float, - bending_inertia: float = 20.0, + bending_inertia: float = 1.0, shear_membrane_ratio: float = 5.0 / 6.0, ): super(ShellProperty, self).__init__( @@ -59,8 +59,6 @@ def __init__( self._bending_inertia = bending_inertia self._shear_membrane_ratio = shear_membrane_ratio - # bending inertia supposed to be 1.0 but boosted to 20.0 here - @property def membrane_thickness(self) -> float: return self._membrane_thickness @@ -88,3 +86,63 @@ def register_to(self, tacs_aim): """ tacs_aim.register(self) return self + +class CompositeProperty(BaseProperty): + """ + Define a composite material property from ESP/CAPS + Tip: you need to use the csystem command and a 1x9 list to define + "Ox,Oy,Oz, d1x, d1y, d1z, d2x, d2y, d2z" + which is the origin, 1-direction, and 2-direction used for shells + """ + def __init__( + self, + caps_group: str, + ply_materials:list, + ply_thicknesses:list, + ply_angles:list, + sym_laminate:bool=True, + composite_failure_theory:str="STRN", + shear_bond_allowable:float=1.0e9, # high in case just stringer-one ply + bending_inertia: float = 1.0, + shear_membrane_ratio: float = 0.0, + ): + self._caps_group = caps_group + self._ply_materials = ply_materials + self._ply_thicknesses = ply_thicknesses + self._ply_angles = ply_angles # in degrees + self._sym_laminate = sym_laminate + self._composite_failure_theory = composite_failure_theory + self._shear_bond_allowable = shear_bond_allowable + self._bending_inertia = bending_inertia + self._shear_membrane_ratio = shear_membrane_ratio + + @property + def ply_materials(self) -> list: + if isinstance(self._ply_materials[0], str): + return self._ply_materials + elif isinstance(self._ply_materials[0], Material): + return [_.name for _ in self._ply_materials] + else: + raise AssertionError("caps2tacs error: Ply material objects need to be list of material name strings or material objects..") + + @property + def dictionary(self) -> dict: + """ + return property dictionary to pass into tacsAim + """ + return {"propertyType" : "Composite", + "shearBondAllowable" : self._shear_bond_allowable, + "bendingInertiaRatio" : self._bending_inertia, + "shearMembraneRatio" : self._shear_membrane_ratio, + "compositeMaterial" : self.ply_materials, + "compositeThickness" : self._ply_thicknesses, + "compositeOrientation" : self._ply_angles, # in degrees + "symmetricLaminate" : self._sym_laminate, + "compositeFailureTheory" : self._composite_failure_theory } + + def register_to(self, tacs_aim): + """ + cascaded method to register this ShellProperty to TacsAim + """ + tacs_aim.register(self) + return self diff --git a/tacs/caps2tacs/tacs_aim.py b/tacs/caps2tacs/tacs_aim.py index c13a7d3cf..092c07714 100644 --- a/tacs/caps2tacs/tacs_aim.py +++ b/tacs/caps2tacs/tacs_aim.py @@ -5,7 +5,7 @@ from .proc_decorator import root_proc, root_broadcast from .materials import Material from .constraints import Constraint -from .property import ShellProperty, Property +from .property import BaseProperty from .loads import Load from .variables import ShapeVariable, ThicknessVariable from .egads_aim import EgadsAim @@ -68,10 +68,10 @@ def register(self, obj): elif isinstance(obj, ThicknessVariable): self._design_variables.append(obj) if obj.can_make_shell: - self._properties.append(obj.shell_property) + self._properties.append(obj.auto_property) elif isinstance(obj, ShapeVariable): self._design_variables.append(obj) - elif isinstance(obj, Property): + elif isinstance(obj, BaseProperty): self._properties.append(obj) elif isinstance(obj, Constraint): self._constraints.append(obj) @@ -124,6 +124,8 @@ def setup_aim( } # add properties to tacsAim + for prop in self._properties: + print(f"caps group {prop.caps_group}, dict = {prop.dictionary}") self.aim.input.Property = { prop.caps_group: prop.dictionary for prop in self._properties } @@ -274,7 +276,7 @@ def update_properties(self): # update property thicknesses by the modified thickness variables for property in self._properties: for dv in self._design_variables: - if isinstance(property, ShellProperty) and isinstance( + if isinstance(property, Property) and isinstance( dv, ThicknessVariable ): if property.caps_group == dv.caps_group: diff --git a/tacs/caps2tacs/variables.py b/tacs/caps2tacs/variables.py index 687ff9c62..4379acbb1 100644 --- a/tacs/caps2tacs/variables.py +++ b/tacs/caps2tacs/variables.py @@ -1,7 +1,7 @@ __all__ = ["ShapeVariable", "ThicknessVariable"] -from .materials import Material -from .property import ShellProperty +from .materials import * +from .property import * class ShapeVariable: @@ -52,6 +52,7 @@ def __init__( upper_bound: float = None, max_delta: float = None, material: Material = None, + ply_angle:float=None, ): """ ESP/CAPS Thickness variable sets the thickness over a portion of the geometry in the CSM file @@ -71,6 +72,7 @@ def __init__( self.lower_bound = lower_bound self.upper_bound = upper_bound self.max_delta = max_delta + self.ply_angle = ply_angle # private variables used to create shell property self._material = material @@ -135,13 +137,27 @@ def DVR_dictionary(self) -> dict: } @property - def shell_property(self) -> ShellProperty: + def auto_property(self) -> BaseProperty: + # automatic property is made if a material is provided assert self._material is not None - return ShellProperty( - caps_group=self.caps_group, - material=self._material, - membrane_thickness=self.value, - ) + if isinstance(self._material, Isotropic): + return ShellProperty( + caps_group=self.caps_group, + material=self._material, + membrane_thickness=self.value, + ) + elif isinstance(self._material, Orthotropic) and ("stringer" in self._material.name): + # auto-making a stringer based CompositeProperty (for ease of use in large cases with metal + smeared stringer models) + return CompositeProperty( + caps_group=self.caps_group, + ply_materials=[self._material], + ply_thicknesses=[self.value], + ply_angles=[self.ply_angle], + ) + else: + raise AssertionError(f"Can't directly make a thickness variable {self.name} from a composite material."+\ + "Don't provide a material => just give the caps_group for the thickness variable"+\ + " and separately make the composite property (registering each to the tacs model or tacs aim).") def register_to(self, tacs_aim): """ From 276379d6fdea40e644feadc0c06c5a2159a2154d Mon Sep 17 00:00:00 2001 From: sean-engelstad Date: Tue, 31 Oct 2023 17:21:16 -0400 Subject: [PATCH 03/10] update sizing optimization to allow switching to stringers --- examples/caps_wing/3_sizing_optimization.py | 12 +++++++++--- examples/caps_wing/large_naca_wing.csm | 5 +++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/examples/caps_wing/3_sizing_optimization.py b/examples/caps_wing/3_sizing_optimization.py index d8deda64b..b11b8063d 100644 --- a/examples/caps_wing/3_sizing_optimization.py +++ b/examples/caps_wing/3_sizing_optimization.py @@ -25,7 +25,13 @@ tacs_model ) +use_stringers = False aluminum = caps2tacs.Isotropic.aluminum().register_to(tacs_model) +aluminum_w_stringer = caps2tacs.Orthotropic.smeared_stringer(aluminum, area_ratio=0.5).register_to(tacs_model) +if use_stringers: + material = aluminum_w_stringer +else: + material = aluminum # setup the thickness design variables + automatic shell properties nribs = int(tacs_model.get_config_parameter("nribs")) @@ -33,15 +39,15 @@ nOML = nribs - 1 for irib in range(1, nribs + 1): caps2tacs.ThicknessVariable( - caps_group=f"rib{irib}", value=0.03, material=aluminum + caps_group=f"rib{irib}", value=0.03, material=material ).register_to(tacs_model) for ispar in range(1, nspars + 1): caps2tacs.ThicknessVariable( - caps_group=f"spar{ispar}", value=0.03, material=aluminum + caps_group=f"spar{ispar}", value=0.03, material=material ).register_to(tacs_model) for iOML in range(1, nOML + 1): caps2tacs.ThicknessVariable( - caps_group=f"OML{iOML}", value=0.1, material=aluminum + caps_group=f"OML{iOML}", value=0.1, material=material ).register_to(tacs_model) # add constraints and loads diff --git a/examples/caps_wing/large_naca_wing.csm b/examples/caps_wing/large_naca_wing.csm index d8214083d..430b01a20 100644 --- a/examples/caps_wing/large_naca_wing.csm +++ b/examples/caps_wing/large_naca_wing.csm @@ -42,6 +42,10 @@ set margin1 5.0 set margin2 3.0 set margin3 10.0 +# composite material coordinate system +dimension compositeCoord 9 1 0 +set compositeCoord "0;0;0;0.0;1.0;0.0;1.0;0.0;0.0;" + ### 1. Wing Solid Body ### @@ -176,6 +180,7 @@ udprim editAttr filename << # add AIM attribute to specify the analyses to use select body +csystem wing compositeCoord attribute capsAIM $egadsTessAIM;tacsAIM end From 5f946b9360d8c9c35ace15b65dd137e7aca6e395 Mon Sep 17 00:00:00 2001 From: seanfireball1 Date: Tue, 31 Oct 2023 21:56:33 -0400 Subject: [PATCH 04/10] add missing BaseProperty class check --- tacs/caps2tacs/tacs_aim.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tacs/caps2tacs/tacs_aim.py b/tacs/caps2tacs/tacs_aim.py index 3d79477a3..5f8fbdbf7 100644 --- a/tacs/caps2tacs/tacs_aim.py +++ b/tacs/caps2tacs/tacs_aim.py @@ -124,8 +124,6 @@ def setup_aim( } # add properties to tacsAim - for prop in self._properties: - print(f"caps group {prop.caps_group}, dict = {prop.dictionary}") self.aim.input.Property = { prop.caps_group: prop.dictionary for prop in self._properties } @@ -276,7 +274,7 @@ def update_properties(self): # update property thicknesses by the modified thickness variables for property in self._properties: for dv in self._design_variables: - if isinstance(property, Property) and isinstance( + if isinstance(property, BaseProperty) and isinstance( dv, ThicknessVariable ): if property.caps_group == dv.caps_group: From a6de76ab08c7a8ee7c891dd345d1e4a5c7997cca Mon Sep 17 00:00:00 2001 From: seanfireball1 Date: Wed, 1 Nov 2023 11:19:43 -0400 Subject: [PATCH 05/10] working smeared stringer model --- tacs/caps2tacs/materials.py | 2 +- tacs/caps2tacs/property.py | 24 ++++++++++++++++++++++++ tacs/caps2tacs/tacs_model.py | 4 ++-- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/tacs/caps2tacs/materials.py b/tacs/caps2tacs/materials.py index 91d945477..bfa884279 100644 --- a/tacs/caps2tacs/materials.py +++ b/tacs/caps2tacs/materials.py @@ -363,7 +363,7 @@ def smeared_stringer(cls, isotropic:Isotropic, area_ratio:float): T2=T1, C2=C1, S1=S1, - rho=rho*(1+area_ratio2), + rho=rho*0.5*(1+area_ratio2), # get double mass from CompositeShellConstitutive so correction for that alpha1 = alpha, alpha2=alpha, kappa1= kappa, diff --git a/tacs/caps2tacs/property.py b/tacs/caps2tacs/property.py index a60910899..83555b7af 100644 --- a/tacs/caps2tacs/property.py +++ b/tacs/caps2tacs/property.py @@ -116,6 +116,30 @@ def __init__( self._bending_inertia = bending_inertia self._shear_membrane_ratio = shear_membrane_ratio + @classmethod + def one_ply( + cls, + caps_group:str, + material:Material, + thickness:float, + ply_angle:float, + sym_laminate:bool=True, + composite_failure_theory:str="STRN", + shear_bond_allowable:float=1.0e9, + bending_inertia: float = 1.0, + shear_membrane_ratio: float = 0.0, + ): + return cls(caps_group=caps_group, + ply_materials=[material], + ply_thicknesses=[thickness], + ply_angles=[ply_angle], + sym_laminate=sym_laminate, + composite_failure_theory=composite_failure_theory, + shear_bond_allowable=shear_bond_allowable, + bending_inertia=bending_inertia, + shear_membrane_ratio=shear_membrane_ratio, + ) + @property def ply_materials(self) -> list: if isinstance(self._ply_materials[0], str): diff --git a/tacs/caps2tacs/tacs_model.py b/tacs/caps2tacs/tacs_model.py index fb3821028..7fa0b2f27 100644 --- a/tacs/caps2tacs/tacs_model.py +++ b/tacs/caps2tacs/tacs_model.py @@ -6,7 +6,7 @@ from .analysis_function import AnalysisFunction, Derivative from .materials import Material from .constraints import Constraint -from .property import ShellProperty +from .property import BaseProperty from .loads import Load from .variables import ShapeVariable, ThicknessVariable from .egads_aim import EgadsAim @@ -104,7 +104,7 @@ def register(self, obj): Material, ThicknessVariable, ShapeVariable, - ShellProperty, + BaseProperty, Constraint, Load, EgadsAim, From 2ee89ef4442a371de21b8a7f79dfe549628a3d7e Mon Sep 17 00:00:00 2001 From: seanfireball1 Date: Sat, 4 Nov 2023 13:56:21 -0400 Subject: [PATCH 06/10] correct the smeared stringer modulus factor --- tacs/caps2tacs/materials.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tacs/caps2tacs/materials.py b/tacs/caps2tacs/materials.py index bfa884279..2e054a4b9 100644 --- a/tacs/caps2tacs/materials.py +++ b/tacs/caps2tacs/materials.py @@ -346,8 +346,12 @@ def smeared_stringer(cls, isotropic:Isotropic, area_ratio:float): # get area_ratio2 = Astiff / Askin area_ratio2 = area_ratio / (1 - area_ratio) # new moduli (stiffened in spanwise direction) - Ex = E - Ey = E / (1 - nu**2 * area_ratio) + # it makes more sense to me that the stress should be lower in the 1 or x-direction + # as there is more area with an actual load path in that direction (and the same overall x-load) + # whereas the stringers have no loadpath in the 2 or y-direction + # the out-of-plane modulus does not matter. + Ex = E / (1 - nu**2 * area_ratio) + Ey = E nu_xy = nu nu_yx = nu / (1 + area_ratio2 * (1 - nu**2)) Gxy = Ex * nu_yx / (1 - nu_xy * nu_yx) From 0df42fc9865f879a5bc17d9aaad3a9178a9fb705 Mon Sep 17 00:00:00 2001 From: seanfireball1 Date: Sat, 4 Nov 2023 15:04:12 -0400 Subject: [PATCH 07/10] test smeared stringer on simple wing.. --- examples/caps_wing/3_sizing_optimization.py | 4 +- examples/caps_wing/6_stringer_test.py | 105 ++++++++------------ tacs/caps2tacs/gif_writer.py | 4 +- tacs/caps2tacs/materials.py | 39 ++++---- tacs/caps2tacs/property.py | 63 ++++++------ tacs/caps2tacs/tacs_aim.py | 4 +- tacs/caps2tacs/tacs_model.py | 24 +++-- tacs/caps2tacs/variables.py | 18 ++-- 8 files changed, 133 insertions(+), 128 deletions(-) diff --git a/examples/caps_wing/3_sizing_optimization.py b/examples/caps_wing/3_sizing_optimization.py index b11b8063d..b4bc6f81c 100644 --- a/examples/caps_wing/3_sizing_optimization.py +++ b/examples/caps_wing/3_sizing_optimization.py @@ -27,7 +27,9 @@ use_stringers = False aluminum = caps2tacs.Isotropic.aluminum().register_to(tacs_model) -aluminum_w_stringer = caps2tacs.Orthotropic.smeared_stringer(aluminum, area_ratio=0.5).register_to(tacs_model) +aluminum_w_stringer = caps2tacs.Orthotropic.smeared_stringer( + aluminum, area_ratio=0.5 +).register_to(tacs_model) if use_stringers: material = aluminum_w_stringer else: diff --git a/examples/caps_wing/6_stringer_test.py b/examples/caps_wing/6_stringer_test.py index a28dabe47..8f483bba7 100644 --- a/examples/caps_wing/6_stringer_test.py +++ b/examples/caps_wing/6_stringer_test.py @@ -24,21 +24,29 @@ ).register_to(tacs_model) aluminum = caps2tacs.Isotropic.aluminum() -aluminum_w_stringer = caps2tacs.Orthotropic.smeared_stringer(aluminum, area_ratio=0.5).register_to(tacs_model) +aluminum_w_stringer = caps2tacs.Orthotropic.smeared_stringer( + aluminum, area_ratio=0.5 +).register_to(tacs_model) # setup the thickness design variables + automatic shell properties nribs = int(tacs_model.get_config_parameter("nribs")) nspars = int(tacs_model.get_config_parameter("nspars")) for irib in range(1, nribs + 1): - caps2tacs.ThicknessVariable( - caps_group=f"rib{irib}", value=0.05, material=aluminum_w_stringer, ply_angle=0, + caps2tacs.CompositeProperty.one_ply( + caps_group=f"rib{irib}", + material=aluminum_w_stringer, + ply_angle=0, + thickness=0.05, # * 0.5 ).register_to(tacs_model) for ispar in range(1, nspars + 1): - caps2tacs.ThicknessVariable( - caps_group=f"spar{ispar}", value=0.05, material=aluminum_w_stringer, ply_angle=0, + caps2tacs.CompositeProperty.one_ply( + caps_group=f"spar{ispar}", + material=aluminum_w_stringer, + ply_angle=0, + thickness=0.05, # * 0.5 ).register_to(tacs_model) -caps2tacs.ThicknessVariable( - caps_group="OML", value=0.03, material=aluminum_w_stringer, ply_angle=0, +caps2tacs.CompositeProperty.one_ply( + caps_group="OML", material=aluminum_w_stringer, ply_angle=0, thickness=0.03 # * 0.5 ).register_to(tacs_model) # add constraints and loads @@ -51,6 +59,8 @@ ) caps2tacs.AnalysisFunction.mass().register_to(tacs_model) +caps2tacs.ShapeVariable("rib_a1", value=1.0).register_to(tacs_model) + # run the pre analysis to build tacs input files # alternative is to call tacs_aim.setup_aim().pre_analysis() with tacs_aim = tacs_model.tacs_aim tacs_model.setup(include_aim=True) @@ -59,61 +69,30 @@ # 2. Run the TACS steady elastic structural analysis, forward + adjoint # choose method 1 or 2 to demonstrate the analysis : 1 - fewer lines, 2 - more lines -method = 2 +method = 1 # show both ways of performing the structural analysis in more or less detail -if method == 1: # less detail version - tacs_model.pre_analysis() - tacs_model.run_analysis() - - # assembler = - # sx = np.array([0 for _ in range(8)]) - # sx[0] = 1.0 - # sy = np.array([0 for _ in range(8)]) - # sy[1] = 1.0 - - - tacs_model.post_analysis() - - print("Tacs model static analysis outputs...\n") - for func in tacs_model.analysis_functions: - print(f"func {func.name} = {func.value}") - - for var in tacs_model.variables: - derivative = func.get_derivative(var) - print(f"\td{func.name}/d{var.name} = {derivative}") - print("\n") - -elif method == 2: # longer way that directly uses pyTACS & static problem routines - SPs = tacs_model.createTACSProbs(addFunctions=True) - - # solve each structural analysis problem (in this case 1) - tacs_funcs = {} - tacs_sens = {} - function_names = tacs_model.function_names - for caseID in SPs: - SPs[caseID].solve() - SPs[caseID].evalFunctions(tacs_funcs, evalFuncs=function_names) - SPs[caseID].evalFunctionsSens(tacs_sens, evalFuncs=function_names) - SPs[caseID].writeSolution( - baseName="tacs_output", outputDir=tacs_model.analysis_dir - ) - - # print the output analysis functions and sensitivities - print("\nTacs Analysis Outputs...") - for func_name in function_names: - # find the associated tacs key (tacs key = (loadset) + (func_name)) - for tacs_key in tacs_funcs: - if func_name in tacs_key: - break - - func_value = tacs_funcs[tacs_key].real - print(f"\tfunctional {func_name} = {func_value}") - - struct_sens = tacs_sens[tacs_key]["struct"] - print(f"\t{func_name} structDV gradient = {struct_sens}") - - xpts_sens = tacs_sens[tacs_key]["Xpts"] - print(f"\t{func_name} coordinate derivatives = {xpts_sens}") - - print("\n") +tacs_model.pre_analysis() +tacs_model.run_analysis() + +# view failure profile +# assembler = +# sx = np.array([0 for _ in range(8)]) +# sx[0] = 1.0 +# sy = np.array([0 for _ in range(8)]) +# sy[1] = 1.0 + +# it builds the wrong Composite constitutive model namely CompositeShellConstitutive +# so that we can't easily do derivatives with the default tacs callback +# instead in funtofem we can make a custom callback there. +# thickness variables for SmearedCompositeShellConstitutive don't work +tacs_model.post_analysis() + +print("Tacs model static analysis outputs...\n") +for func in tacs_model.analysis_functions: + print(f"func {func.name} = {func.value}") + + for var in tacs_model.variables: + derivative = func.get_derivative(var) + print(f"\td{func.name}/d{var.name} = {derivative}") + print("\n") diff --git a/tacs/caps2tacs/gif_writer.py b/tacs/caps2tacs/gif_writer.py index 86757b6d5..87c5b6123 100644 --- a/tacs/caps2tacs/gif_writer.py +++ b/tacs/caps2tacs/gif_writer.py @@ -16,7 +16,9 @@ def __call__(self, gif_filename: str, path: str): call on current path to create gif of given filename """ gif_filepath = os.path.join(path, gif_filename) - with imageio.get_writer(gif_filepath, mode="I", duration=self._duration) as writer: + with imageio.get_writer( + gif_filepath, mode="I", duration=self._duration + ) as writer: path_dir = os.listdir(path) path_dir = sorted(path_dir) for image_file in path_dir: diff --git a/tacs/caps2tacs/materials.py b/tacs/caps2tacs/materials.py index 2e054a4b9..e9c6e3501 100644 --- a/tacs/caps2tacs/materials.py +++ b/tacs/caps2tacs/materials.py @@ -191,7 +191,7 @@ def titanium(cls): rho=4.51e3, T1=2.4e7, C1=2.4e7, - S1=0.6 * 2.4e7, # estimated shear strength + S1=0.6 * 2.4e7, # estimated shear strength alpha=8.41e-6, cp=522.3, kappa=11.4, @@ -207,12 +207,12 @@ def titanium_alloy(cls): rho=4.43e3, T1=880e6, C1=970e6, - S1=0.6*880e6, #estimated shear strength + S1=0.6 * 880e6, # estimated shear strength alpha=9.2e-6, cp=526.3, kappa=6.7, ) - + @classmethod def aluminum_alloy(cls): # Aluminum alloy Al-MS89 @@ -223,10 +223,10 @@ def aluminum_alloy(cls): nu=0.3, T1=420e6, C1=420e6, - S1=0.6*420e6, # estimated + S1=0.6 * 420e6, # estimated alpha=19.0e-6, kappa=115.0, - cp=903, # guessed the cp (not provided in data sheet) + cp=903, # guessed the cp (not provided in data sheet) ) @classmethod @@ -238,7 +238,7 @@ def steel(cls): rho=7.8e3, T1=1.0e9, C1=1.7e9, - S1=0.6*1.0e9,#estimated + S1=0.6 * 1.0e9, # estimated alpha=11.5e-6, kappa=45, cp=420, @@ -322,15 +322,15 @@ def carbon_fiber(cls): kappa3=4.8, # W/m-K cp=1130.0, # J / kg-K ) - + @classmethod - def smeared_stringer(cls, isotropic:Isotropic, area_ratio:float): + def smeared_stringer(cls, isotropic: Isotropic, area_ratio: float): """ By: Sean Engelstad Adapted from paper "Smeared Stiffeners in Panel for Mesh Simplification at Conceptual Design Phase" by Denis Walch1, Simon Tetreault2, and Franck Dervault3 - Here area_ratio = Astiff / (Askin+Astiff) in the cross-section which is "assumed + Here area_ratio = Astiff / (Askin+Astiff) in the cross-section which is "assumed to be in the range 0.25 to 0.66 for most aircraft structures" """ # get the isotropic properties @@ -346,16 +346,19 @@ def smeared_stringer(cls, isotropic:Isotropic, area_ratio:float): # get area_ratio2 = Astiff / Askin area_ratio2 = area_ratio / (1 - area_ratio) # new moduli (stiffened in spanwise direction) - # it makes more sense to me that the stress should be lower in the 1 or x-direction - # as there is more area with an actual load path in that direction (and the same overall x-load) - # whereas the stringers have no loadpath in the 2 or y-direction - # the out-of-plane modulus does not matter. - Ex = E / (1 - nu**2 * area_ratio) - Ey = E + # added longitudinal boost to modulus in longitudinal direction not included in paper + # out-of-plane modulus E3 or Ez not modified + Ex = E / area_ratio + Ey = E / (1 - nu**2 * area_ratio) nu_xy = nu nu_yx = nu / (1 + area_ratio2 * (1 - nu**2)) Gxy = Ex * nu_yx / (1 - nu_xy * nu_yx) S1 = isotropic._S1 + print(f"rho = {rho}", flush=True) + print( + f"new rho = {rho*0.5*(1+area_ratio2)}, area_ratio2 = {area_ratio2}", + flush=True, + ) return cls( name=f"smeared-stringer-{isotropic.name}", E1=Ex, @@ -367,10 +370,10 @@ def smeared_stringer(cls, isotropic:Isotropic, area_ratio:float): T2=T1, C2=C1, S1=S1, - rho=rho*0.5*(1+area_ratio2), # get double mass from CompositeShellConstitutive so correction for that - alpha1 = alpha, + rho=rho * (1 + area_ratio2), + alpha1=alpha, alpha2=alpha, - kappa1= kappa, + kappa1=kappa, kappa2=kappa, kappa3=kappa, cp=cp, diff --git a/tacs/caps2tacs/property.py b/tacs/caps2tacs/property.py index 83555b7af..0058e81c9 100644 --- a/tacs/caps2tacs/property.py +++ b/tacs/caps2tacs/property.py @@ -86,7 +86,8 @@ def register_to(self, tacs_aim): """ tacs_aim.register(self) return self - + + class CompositeProperty(BaseProperty): """ Define a composite material property from ESP/CAPS @@ -94,22 +95,23 @@ class CompositeProperty(BaseProperty): "Ox,Oy,Oz, d1x, d1y, d1z, d2x, d2y, d2z" which is the origin, 1-direction, and 2-direction used for shells """ + def __init__( self, caps_group: str, - ply_materials:list, - ply_thicknesses:list, - ply_angles:list, - sym_laminate:bool=True, - composite_failure_theory:str="STRN", - shear_bond_allowable:float=1.0e9, # high in case just stringer-one ply + ply_materials: list, + ply_thicknesses: list, + ply_angles: list, + sym_laminate: bool = True, + composite_failure_theory: str = "STRN", + shear_bond_allowable: float = 1.0e9, # high in case just stringer-one ply bending_inertia: float = 1.0, shear_membrane_ratio: float = 0.0, ): self._caps_group = caps_group self._ply_materials = ply_materials self._ply_thicknesses = ply_thicknesses - self._ply_angles = ply_angles # in degrees + self._ply_angles = ply_angles # in degrees self._sym_laminate = sym_laminate self._composite_failure_theory = composite_failure_theory self._shear_bond_allowable = shear_bond_allowable @@ -118,18 +120,19 @@ def __init__( @classmethod def one_ply( - cls, - caps_group:str, - material:Material, - thickness:float, - ply_angle:float, - sym_laminate:bool=True, - composite_failure_theory:str="STRN", - shear_bond_allowable:float=1.0e9, + cls, + caps_group: str, + material: Material, + thickness: float, + ply_angle: float, + sym_laminate: bool = True, + composite_failure_theory: str = "STRN", + shear_bond_allowable: float = 1.0e9, bending_inertia: float = 1.0, shear_membrane_ratio: float = 0.0, - ): - return cls(caps_group=caps_group, + ) -> BaseProperty: + return cls( + caps_group=caps_group, ply_materials=[material], ply_thicknesses=[thickness], ply_angles=[ply_angle], @@ -147,22 +150,26 @@ def ply_materials(self) -> list: elif isinstance(self._ply_materials[0], Material): return [_.name for _ in self._ply_materials] else: - raise AssertionError("caps2tacs error: Ply material objects need to be list of material name strings or material objects..") + raise AssertionError( + "caps2tacs error: Ply material objects need to be list of material name strings or material objects.." + ) @property def dictionary(self) -> dict: """ return property dictionary to pass into tacsAim """ - return {"propertyType" : "Composite", - "shearBondAllowable" : self._shear_bond_allowable, - "bendingInertiaRatio" : self._bending_inertia, - "shearMembraneRatio" : self._shear_membrane_ratio, - "compositeMaterial" : self.ply_materials, - "compositeThickness" : self._ply_thicknesses, - "compositeOrientation" : self._ply_angles, # in degrees - "symmetricLaminate" : self._sym_laminate, - "compositeFailureTheory" : self._composite_failure_theory } + return { + "propertyType": "Composite", + "shearBondAllowable": self._shear_bond_allowable, + "bendingInertiaRatio": self._bending_inertia, + "shearMembraneRatio": self._shear_membrane_ratio, + "compositeMaterial": self.ply_materials, + "compositeThickness": self._ply_thicknesses, + "compositeOrientation": self._ply_angles, # in degrees + "symmetricLaminate": self._sym_laminate, + "compositeFailureTheory": self._composite_failure_theory, + } def register_to(self, tacs_aim): """ diff --git a/tacs/caps2tacs/tacs_aim.py b/tacs/caps2tacs/tacs_aim.py index 5f8fbdbf7..37e8b74b9 100644 --- a/tacs/caps2tacs/tacs_aim.py +++ b/tacs/caps2tacs/tacs_aim.py @@ -151,7 +151,9 @@ def setup_aim( } if len(self.variables) > 0: self.aim.input.Design_Variable = { - dv.name: dv.DV_dictionary for dv in self._design_variables if dv._active + dv.name: dv.DV_dictionary + for dv in self._design_variables + if dv._active } if self._dict_options is not None: diff --git a/tacs/caps2tacs/tacs_model.py b/tacs/caps2tacs/tacs_model.py index 7fa0b2f27..a60e1f941 100644 --- a/tacs/caps2tacs/tacs_model.py +++ b/tacs/caps2tacs/tacs_model.py @@ -17,8 +17,10 @@ # optional funtofem dependency for shape variable animations # will still work without funtofem import importlib + f2f_loader = importlib.util.find_spec("funtofem") + class TacsModel: MESH_AIMS = ["egads", "aflr"] @@ -277,7 +279,7 @@ def createTACSProbs(self, addFunctions: bool = True): ) return self.SPs - def _convert_shape_vars_dict(self, shape_vars_dict:dict): + def _convert_shape_vars_dict(self, shape_vars_dict: dict): """convert the shape variable dict keys from str or funtofem variables to Tacs Shape Variables if necessary""" # convert shape variable dict to caps2tacs ShapeVariable keys if necessary shape_vars_dict2 = {} @@ -290,8 +292,8 @@ def _convert_shape_vars_dict(self, shape_vars_dict:dict): for var in self.tacs_aim.shape_variables: if var.name == key: shape_vars_dict2[var] = shape_vars_dict[key] - break # go to next key - elif f2f_loader is not None: # optional funtofem dependency + break # go to next key + elif f2f_loader is not None: # optional funtofem dependency # import has to go here to prevent circular import from funtofem import Variable @@ -300,16 +302,19 @@ def _convert_shape_vars_dict(self, shape_vars_dict:dict): for f2f_var in shape_vars_dict: for tacs_var in self.tacs_aim.shape_variables: if f2f_var.name == tacs_var.name: - # copy the value list to the new dictionary shape_vars_dict2[tacs_var] = shape_vars_dict[f2f_var] - break # go to next variable + break # go to next variable else: - raise AssertionError(f"The datatype {type(first_key)} is not allowed in caps2tacs shape variable dictionaries.") + raise AssertionError( + f"The datatype {type(first_key)} is not allowed in caps2tacs shape variable dictionaries." + ) else: - raise AssertionError(f"The datatype {type(first_key)} is not allowed in caps2tacs shape variable dictionaries.") - + raise AssertionError( + f"The datatype {type(first_key)} is not allowed in caps2tacs shape variable dictionaries." + ) + return shape_vars_dict2 def animate_shape_vars(self, shape_vars_dict: dict): @@ -372,7 +377,8 @@ def animate_shape_vars(self, shape_vars_dict: dict): number=i, ) self.SPs[caseID].writeSensFile( - evalFuncs=None, tacsAim=self.tacs_aim, + evalFuncs=None, + tacsAim=self.tacs_aim, ) del self.SPs diff --git a/tacs/caps2tacs/variables.py b/tacs/caps2tacs/variables.py index 4534e5453..6a01febbb 100644 --- a/tacs/caps2tacs/variables.py +++ b/tacs/caps2tacs/variables.py @@ -9,7 +9,7 @@ class ShapeVariable: shape variables in ESP/CAPS are design parameters that affect the structural geometry """ - def __init__(self, name: str, value=None, active:bool=True): + def __init__(self, name: str, value=None, active: bool = True): """ ESP/CAPS shape variable controls a design parameter in the CSM file name: corresponds to the design parameter in the CSM file @@ -53,8 +53,8 @@ def __init__( upper_bound: float = None, max_delta: float = None, material: Material = None, - ply_angle:float=None, - active:bool=True, + ply_angle: float = None, + active: bool = True, ): """ ESP/CAPS Thickness variable sets the thickness over a portion of the geometry in the CSM file @@ -149,7 +149,9 @@ def auto_property(self) -> BaseProperty: material=self._material, membrane_thickness=self.value, ) - elif isinstance(self._material, Orthotropic) and ("stringer" in self._material.name): + elif isinstance(self._material, Orthotropic) and ( + "stringer" in self._material.name + ): # auto-making a stringer based CompositeProperty (for ease of use in large cases with metal + smeared stringer models) return CompositeProperty( caps_group=self.caps_group, @@ -158,9 +160,11 @@ def auto_property(self) -> BaseProperty: ply_angles=[self.ply_angle], ) else: - raise AssertionError(f"Can't directly make a thickness variable {self.name} from a composite material."+\ - "Don't provide a material => just give the caps_group for the thickness variable"+\ - " and separately make the composite property (registering each to the tacs model or tacs aim).") + raise AssertionError( + f"Can't directly make a thickness variable {self.name} from a composite material." + + "Don't provide a material => just give the caps_group for the thickness variable" + + " and separately make the composite property (registering each to the tacs model or tacs aim)." + ) def register_to(self, tacs_aim): """ From 5610fb824df289257cacb83350539f77e7d97288 Mon Sep 17 00:00:00 2001 From: sean-engelstad Date: Mon, 11 Mar 2024 19:31:14 -0400 Subject: [PATCH 08/10] working composite materials with caps2tacs --- examples/caps_wing/3_sizing_optimization.py | 4 +- ...ringer_test.py => 6_composite_mat_test.py} | 47 ++++- examples/caps_wing/simple_naca_wing.csm | 26 ++- tacs/caps2tacs/materials.py | 192 +++++++++++++----- tacs/caps2tacs/property.py | 42 ++-- tacs/caps2tacs/tacs_aim.py | 8 +- tacs/caps2tacs/tacs_model.py | 13 +- tacs/caps2tacs/variables.py | 36 ++-- 8 files changed, 252 insertions(+), 116 deletions(-) rename examples/caps_wing/{6_stringer_test.py => 6_composite_mat_test.py} (76%) diff --git a/examples/caps_wing/3_sizing_optimization.py b/examples/caps_wing/3_sizing_optimization.py index b11b8063d..b4bc6f81c 100644 --- a/examples/caps_wing/3_sizing_optimization.py +++ b/examples/caps_wing/3_sizing_optimization.py @@ -27,7 +27,9 @@ use_stringers = False aluminum = caps2tacs.Isotropic.aluminum().register_to(tacs_model) -aluminum_w_stringer = caps2tacs.Orthotropic.smeared_stringer(aluminum, area_ratio=0.5).register_to(tacs_model) +aluminum_w_stringer = caps2tacs.Orthotropic.smeared_stringer( + aluminum, area_ratio=0.5 +).register_to(tacs_model) if use_stringers: material = aluminum_w_stringer else: diff --git a/examples/caps_wing/6_stringer_test.py b/examples/caps_wing/6_composite_mat_test.py similarity index 76% rename from examples/caps_wing/6_stringer_test.py rename to examples/caps_wing/6_composite_mat_test.py index a28dabe47..a8a14056b 100644 --- a/examples/caps_wing/6_stringer_test.py +++ b/examples/caps_wing/6_composite_mat_test.py @@ -23,23 +23,51 @@ max_dihedral_angle=15, ).register_to(tacs_model) -aluminum = caps2tacs.Isotropic.aluminum() -aluminum_w_stringer = caps2tacs.Orthotropic.smeared_stringer(aluminum, area_ratio=0.5).register_to(tacs_model) +carbon_fiber = caps2tacs.Orthotropic.carbon_fiber().register_to(tacs_model) # setup the thickness design variables + automatic shell properties nribs = int(tacs_model.get_config_parameter("nribs")) nspars = int(tacs_model.get_config_parameter("nspars")) +# makes unidirectional laminate composite properties automatically for irib in range(1, nribs + 1): - caps2tacs.ThicknessVariable( - caps_group=f"rib{irib}", value=0.05, material=aluminum_w_stringer, ply_angle=0, + caps_group = f"rib{irib}" + thick = 0.05 + caps2tacs.CompositeProperty( + caps_group=caps_group, + ply_materials=[carbon_fiber] * 4, + ply_thicknesses=[thick / 4] * 4, + ply_angles=[0, -45, 45, 90], ).register_to(tacs_model) + # caps2tacs.ThicknessVariable( + # caps_group=f"rib{irib}", + # value=thick, + # ).register_to(tacs_model) for ispar in range(1, nspars + 1): - caps2tacs.ThicknessVariable( - caps_group=f"spar{ispar}", value=0.05, material=aluminum_w_stringer, ply_angle=0, + caps_group = f"spar{ispar}" + thick = 0.04 + caps2tacs.CompositeProperty( + caps_group=caps_group, + ply_materials=[carbon_fiber] * 4, + ply_thicknesses=[thick / 4] * 4, + ply_angles=[0, -45, 45, 90], ).register_to(tacs_model) -caps2tacs.ThicknessVariable( - caps_group="OML", value=0.03, material=aluminum_w_stringer, ply_angle=0, + # caps2tacs.ThicknessVariable( + # caps_group=caps_group, + # value=thick, + # ).register_to(tacs_model) + +caps_group = "OML" +thick = 0.03 +caps2tacs.CompositeProperty( + caps_group=caps_group, + ply_materials=[carbon_fiber] * 4, + ply_thicknesses=[thick / 4] * 4, + ply_angles=[0, -45, 45, 90], ).register_to(tacs_model) +# caps2tacs.ThicknessVariable( +# caps_group=caps_group, +# value=thick, +# ).register_to(tacs_model) # add constraints and loads caps2tacs.PinConstraint("root").register_to(tacs_model) @@ -66,13 +94,12 @@ tacs_model.pre_analysis() tacs_model.run_analysis() - # assembler = + # assembler = # sx = np.array([0 for _ in range(8)]) # sx[0] = 1.0 # sy = np.array([0 for _ in range(8)]) # sy[1] = 1.0 - tacs_model.post_analysis() print("Tacs model static analysis outputs...\n") diff --git a/examples/caps_wing/simple_naca_wing.csm b/examples/caps_wing/simple_naca_wing.csm index 5f1b194a8..d82c27db3 100644 --- a/examples/caps_wing/simple_naca_wing.csm +++ b/examples/caps_wing/simple_naca_wing.csm @@ -35,8 +35,11 @@ despmtr rib_a2 0.0 set rib_a3 1.0-rib_a1-rib_a2 # composite material coordinate system -dimension compositeCoord 9 1 0 -set compositeCoord "0;0;0;0.0;1.0;0.0;1.0;0.0;0.0;" +dimension YZcsys 9 1 0 +set YZcsys "0;0;0;0.0;1.0;0.0;1.0;0.0;0.0;" + +dimension XZcsys 9 1 0 +set XZcsys "0;0;0;0.0;1.0;0.0;1.0;0.0;0.0;" # Depedendent parameters @@ -66,6 +69,9 @@ translate xf -sspan zf loft 0 # attribute the solid wing +#select face +attribute capsGroup $OML +csystem OML YZcsys select face attribute capsGroup $OML attribute _color $blue @@ -97,16 +103,16 @@ patbeg ispar nspars # make the panel to intersect box xroot margin2 -margin1 0.0 -sspan-2*margin2 2*margin1 - # rotate it to match the xtip location - translate -xroot 0 0 - rotatez angle 0 0 - translate xroot 0 0 - # add caps attributes - select face attribute capsGroup !$spar+ispar + csystem !$spar+ispar YZcsys ATTRIBUTE AFLR_GBC $TRANSP_UG3_GBC attribute _color $green + + # rotate it to match the xtip location + translate -xroot 0 0 + rotatez angle 0 0 + translate xroot 0 0 patend # make the ribs except for end cap ribs @@ -123,8 +129,8 @@ patbeg index ninnerRibs box -margin3 ypos -margin2 2*margin3 0 2*margin2 # add caps attributes - select face attribute capsGroup !$rib+irib + csystem !$rib+irib XZcsys ATTRIBUTE AFLR_GBC $TRANSP_UG3_GBC attribute _color $green @@ -173,6 +179,7 @@ udprim editAttr filename << # add load attribute to OML select face $capsGroup $OML +# set the composite coordinate system on the OML #ATTRIBUTE AFLR_GBC $TRANSP_UG3_GBC attribute capsLoad $OML @@ -185,7 +192,6 @@ attribute capsMesh $OML # add AIM attribute to specify the analyses to use select body -csystem wing compositeCoord ATTRIBUTE AFLR4_Cmp_ID 1 attribute capsMeshLength 1.0 attribute capsAIM $aflr4AIM;tacsAIM;egadsTessAIM diff --git a/tacs/caps2tacs/materials.py b/tacs/caps2tacs/materials.py index 91d945477..3e91c7071 100644 --- a/tacs/caps2tacs/materials.py +++ b/tacs/caps2tacs/materials.py @@ -191,7 +191,7 @@ def titanium(cls): rho=4.51e3, T1=2.4e7, C1=2.4e7, - S1=0.6 * 2.4e7, # estimated shear strength + S1=0.6 * 2.4e7, # estimated shear strength alpha=8.41e-6, cp=522.3, kappa=11.4, @@ -207,12 +207,12 @@ def titanium_alloy(cls): rho=4.43e3, T1=880e6, C1=970e6, - S1=0.6*880e6, #estimated shear strength + S1=0.6 * 880e6, # estimated shear strength alpha=9.2e-6, cp=526.3, kappa=6.7, ) - + @classmethod def aluminum_alloy(cls): # Aluminum alloy Al-MS89 @@ -223,10 +223,10 @@ def aluminum_alloy(cls): nu=0.3, T1=420e6, C1=420e6, - S1=0.6*420e6, # estimated + S1=0.6 * 420e6, # estimated alpha=19.0e-6, kappa=115.0, - cp=903, # guessed the cp (not provided in data sheet) + cp=903, # guessed the cp (not provided in data sheet) ) @classmethod @@ -238,7 +238,7 @@ def steel(cls): rho=7.8e3, T1=1.0e9, C1=1.7e9, - S1=0.6*1.0e9,#estimated + S1=0.6 * 1.0e9, # estimated alpha=11.5e-6, kappa=45, cp=420, @@ -302,7 +302,6 @@ def __init__( @classmethod def carbon_fiber(cls): # STD CF UD (carbon-fiber fiber/epoxy resin) - # TODO : add more kinds here return cls( name="carbon_fiber_UD", E1=135e9, @@ -322,52 +321,145 @@ def carbon_fiber(cls): kappa3=4.8, # W/m-K cp=1130.0, # J / kg-K ) - + @classmethod - def smeared_stringer(cls, isotropic:Isotropic, area_ratio:float): + def solvay5320(cls, comm, bdf_file, a, b, h, ply_angle=0.0): + """ + NIAR dataset - Solvay 5320-1 material (thermoset) + Fiber: T650 unitape, Resin: Cycom 5320-1 + Room Temperature Dry (RTD) mean properties shown below + units in Pa, ND """ - By: Sean Engelstad - Adapted from paper "Smeared Stiffeners in Panel for Mesh Simplification at - Conceptual Design Phase" by Denis Walch1, Simon Tetreault2, and Franck Dervault3 + return cls( + name="solvay5320", + E1=138.461e9, + E2=9.177e9, + G12=4.957e9, + nu12=0.326, + # TBD add these rem properties + T1=1.5e9, + C1=1.2e9, + T2=50e6, + C2=250e6, + S1=70e9, + alpha1=-0.3e-6, + alpha2=28e-6, + rho=1.6e3, + kappa1=14.5, # W/m-K + kappa2=4.8, # W/m-K + kappa3=4.8, # W/m-K + cp=1130.0, # J / kg-K + ) - Here area_ratio = Astiff / (Askin+Astiff) in the cross-section which is "assumed - to be in the range 0.25 to 0.66 for most aircraft structures" + @classmethod + def solvayMTM45(cls, comm, bdf_file, a, b, h, ply_angle=0.0): + """ + NIAR dataset - Solvay MTM45 material (thermoset) + Style: 12K AS4 Unidirectional + Room Temperature Dry (RTD) mean properties shown below + units in Pa, ND """ - # get the isotropic properties - E = isotropic._E1 - nu = isotropic._nu12 - rho = isotropic._rho - T1 = isotropic._T1 - C1 = isotropic._C1 - alpha = isotropic._alpha1 - kappa = isotropic._kappa1 - cp = isotropic._cp - assert (0.0 < area_ratio) and (area_ratio < 1.0) - # get area_ratio2 = Astiff / Askin - area_ratio2 = area_ratio / (1 - area_ratio) - # new moduli (stiffened in spanwise direction) - Ex = E - Ey = E / (1 - nu**2 * area_ratio) - nu_xy = nu - nu_yx = nu / (1 + area_ratio2 * (1 - nu**2)) - Gxy = Ex * nu_yx / (1 - nu_xy * nu_yx) - S1 = isotropic._S1 return cls( - name=f"smeared-stringer-{isotropic.name}", - E1=Ex, - E2=Ey, - G12=Gxy, - nu12=nu_xy, - T1=T1, - C1=C1, - T2=T1, - C2=C1, - S1=S1, - rho=rho*(1+area_ratio2), - alpha1 = alpha, - alpha2=alpha, - kappa1= kappa, - kappa2=kappa, - kappa3=kappa, - cp=cp, + name="solvayMTM45", + E1=129.5e9, + E2=7.936e9, + G12=4.764e9, + nu12=0.313, + # TBD add these rem properties + T1=1.5e9, + C1=1.2e9, + T2=50e6, + C2=250e6, + S1=70e9, + alpha1=-0.3e-6, + alpha2=28e-6, + rho=1.6e3, + kappa1=14.5, # W/m-K + kappa2=4.8, # W/m-K + kappa3=4.8, # W/m-K + cp=1130.0, # J / kg-K + ) + + @classmethod + def torayBT250E(cls, comm, bdf_file, a, b, h, ply_angle=0.0): + """ + NIAR dataset - Toray (formerly Tencate) BT250E-6 S2 Unitape Gr 284 material (thermoset) + Room Temperature Dry (RTD) mean properties shown below + units in Pa, ND + """ + return cls( + name="torayBT250E", + E1=44.74e9, + E2=11.36e9, + G12=3.77e9, + nu12=0.278, + # TBD add these rem properties + T1=1.5e9, + C1=1.2e9, + T2=50e6, + C2=250e6, + S1=70e9, + alpha1=-0.3e-6, + alpha2=28e-6, + rho=1.6e3, + kappa1=14.5, # W/m-K + kappa2=4.8, # W/m-K + kappa3=4.8, # W/m-K + cp=1130.0, # J / kg-K + ) + + @classmethod + def victrexAE(cls, comm, bdf_file, a, b, h, ply_angle=0.0): + """ + NIAR dataset - Victrex AE 250 LMPAEK (thermoplastic) + Room Temperature Dry (RTD) mean properties shown below + units in Pa, ND + """ + return cls( + name="victrexAE", + E1=131.69e9, + E2=9.694e9, + G12=4.524e9, + nu12=0.3192, + # TBD add these rem properties + T1=1.5e9, + C1=1.2e9, + T2=50e6, + C2=250e6, + S1=70e9, + alpha1=-0.3e-6, + alpha2=28e-6, + rho=1.6e3, + kappa1=14.5, # W/m-K + kappa2=4.8, # W/m-K + kappa3=4.8, # W/m-K + cp=1130.0, # J / kg-K + ) + + @classmethod + def hexcelIM7(cls, comm, bdf_file, a, b, h, ply_angle=0.0): + """ + NIAR dataset - Hexcel 8552 IM7 Unidirectional Prepreg (thermoset) + Room Temperature Dry (RTD) mean properties shown below + units in Pa, ND + """ + return cls( + name="hexcelIM7", + E1=158.51e9, + E2=8.96e9, + G12=4.688e9, + nu12=0.316, + # TBD add these rem properties + T1=1.5e9, + C1=1.2e9, + T2=50e6, + C2=250e6, + S1=70e9, + alpha1=-0.3e-6, + alpha2=28e-6, + rho=1.6e3, + kappa1=14.5, # W/m-K + kappa2=4.8, # W/m-K + kappa3=4.8, # W/m-K + cp=1130.0, # J / kg-K ) diff --git a/tacs/caps2tacs/property.py b/tacs/caps2tacs/property.py index a60910899..e2d7f1cc5 100644 --- a/tacs/caps2tacs/property.py +++ b/tacs/caps2tacs/property.py @@ -86,7 +86,8 @@ def register_to(self, tacs_aim): """ tacs_aim.register(self) return self - + + class CompositeProperty(BaseProperty): """ Define a composite material property from ESP/CAPS @@ -94,22 +95,23 @@ class CompositeProperty(BaseProperty): "Ox,Oy,Oz, d1x, d1y, d1z, d2x, d2y, d2z" which is the origin, 1-direction, and 2-direction used for shells """ + def __init__( self, caps_group: str, - ply_materials:list, - ply_thicknesses:list, - ply_angles:list, - sym_laminate:bool=True, - composite_failure_theory:str="STRN", - shear_bond_allowable:float=1.0e9, # high in case just stringer-one ply + ply_materials: list, + ply_thicknesses: list, + ply_angles: list, + sym_laminate: bool = True, + composite_failure_theory: str = "STRN", + shear_bond_allowable: float = 1.0e9, # high in case just stringer-one ply bending_inertia: float = 1.0, shear_membrane_ratio: float = 0.0, ): self._caps_group = caps_group self._ply_materials = ply_materials self._ply_thicknesses = ply_thicknesses - self._ply_angles = ply_angles # in degrees + self._ply_angles = ply_angles # in degrees self._sym_laminate = sym_laminate self._composite_failure_theory = composite_failure_theory self._shear_bond_allowable = shear_bond_allowable @@ -123,22 +125,26 @@ def ply_materials(self) -> list: elif isinstance(self._ply_materials[0], Material): return [_.name for _ in self._ply_materials] else: - raise AssertionError("caps2tacs error: Ply material objects need to be list of material name strings or material objects..") + raise AssertionError( + "caps2tacs error: Ply material objects need to be list of material name strings or material objects.." + ) @property def dictionary(self) -> dict: """ return property dictionary to pass into tacsAim """ - return {"propertyType" : "Composite", - "shearBondAllowable" : self._shear_bond_allowable, - "bendingInertiaRatio" : self._bending_inertia, - "shearMembraneRatio" : self._shear_membrane_ratio, - "compositeMaterial" : self.ply_materials, - "compositeThickness" : self._ply_thicknesses, - "compositeOrientation" : self._ply_angles, # in degrees - "symmetricLaminate" : self._sym_laminate, - "compositeFailureTheory" : self._composite_failure_theory } + return { + "propertyType": "Composite", + "shearBondAllowable": self._shear_bond_allowable, + "bendingInertiaRatio": self._bending_inertia, + "shearMembraneRatio": self._shear_membrane_ratio, + "compositeMaterial": self.ply_materials, + "compositeThickness": self._ply_thicknesses, + "compositeOrientation": self._ply_angles, # in degrees + "symmetricLaminate": self._sym_laminate, + "compositeFailureTheory": self._composite_failure_theory, + } def register_to(self, tacs_aim): """ diff --git a/tacs/caps2tacs/tacs_aim.py b/tacs/caps2tacs/tacs_aim.py index 3d79477a3..287dc4fa7 100644 --- a/tacs/caps2tacs/tacs_aim.py +++ b/tacs/caps2tacs/tacs_aim.py @@ -153,7 +153,9 @@ def setup_aim( } if len(self.variables) > 0: self.aim.input.Design_Variable = { - dv.name: dv.DV_dictionary for dv in self._design_variables if dv._active + dv.name: dv.DV_dictionary + for dv in self._design_variables + if dv._active } if self._dict_options is not None: @@ -276,9 +278,7 @@ def update_properties(self): # update property thicknesses by the modified thickness variables for property in self._properties: for dv in self._design_variables: - if isinstance(property, Property) and isinstance( - dv, ThicknessVariable - ): + if isinstance(property, Property) and isinstance(dv, ThicknessVariable): if property.caps_group == dv.caps_group: property.membrane_thickness == dv.value break diff --git a/tacs/caps2tacs/tacs_model.py b/tacs/caps2tacs/tacs_model.py index f7a24dd2b..358d4020a 100644 --- a/tacs/caps2tacs/tacs_model.py +++ b/tacs/caps2tacs/tacs_model.py @@ -6,7 +6,7 @@ from .analysis_function import AnalysisFunction, Derivative from .materials import Material from .constraints import Constraint -from .property import ShellProperty +from .property import BaseProperty from .loads import Load from .variables import ShapeVariable, ThicknessVariable from .egads_aim import EgadsAim @@ -100,7 +100,7 @@ def register(self, obj): Material, ThicknessVariable, ShapeVariable, - ShellProperty, + BaseProperty, Constraint, Load, EgadsAim, @@ -217,9 +217,9 @@ def update_design(self, input_dict: dict = None): if self.geometry.despmtr[shape_var.name].value != shape_var.value: changed_design = True if shape_var.value is not None: - self.geometry.despmtr[ - shape_var.name - ].value = shape_var.value + self.geometry.despmtr[shape_var.name].value = ( + shape_var.value + ) else: shape_var.value = self.geometry.despmtr[ shape_var.name @@ -326,7 +326,8 @@ def animate_shape_vars(self, shape_vars_dict: dict): number=i, ) self.SPs[caseID].writeSensFile( - evalFuncs=None, tacsAim=self.tacs_aim, + evalFuncs=None, + tacsAim=self.tacs_aim, ) self.tacs_aim.post_analysis() diff --git a/tacs/caps2tacs/variables.py b/tacs/caps2tacs/variables.py index 4534e5453..f5b705782 100644 --- a/tacs/caps2tacs/variables.py +++ b/tacs/caps2tacs/variables.py @@ -9,7 +9,7 @@ class ShapeVariable: shape variables in ESP/CAPS are design parameters that affect the structural geometry """ - def __init__(self, name: str, value=None, active:bool=True): + def __init__(self, name: str, value=None, active: bool = True): """ ESP/CAPS shape variable controls a design parameter in the CSM file name: corresponds to the design parameter in the CSM file @@ -53,8 +53,8 @@ def __init__( upper_bound: float = None, max_delta: float = None, material: Material = None, - ply_angle:float=None, - active:bool=True, + ply_angle: float = None, + active: bool = True, ): """ ESP/CAPS Thickness variable sets the thickness over a portion of the geometry in the CSM file @@ -116,15 +116,15 @@ def DV_dictionary(self) -> dict: return { "groupName": self.caps_group, "initialValue": self.value, - "lowerBound": self.lower_bound - if self.lower_bound is not None - else self.value * 0.5, - "upperBound": self.upper_bound - if self.upper_bound is not None - else self.value * 2.0, - "maxDelta": self.max_delta - if self.max_delta is not None - else self.value * 0.1, + "lowerBound": ( + self.lower_bound if self.lower_bound is not None else self.value * 0.5 + ), + "upperBound": ( + self.upper_bound if self.upper_bound is not None else self.value * 2.0 + ), + "maxDelta": ( + self.max_delta if self.max_delta is not None else self.value * 0.1 + ), } @property @@ -149,8 +149,8 @@ def auto_property(self) -> BaseProperty: material=self._material, membrane_thickness=self.value, ) - elif isinstance(self._material, Orthotropic) and ("stringer" in self._material.name): - # auto-making a stringer based CompositeProperty (for ease of use in large cases with metal + smeared stringer models) + elif isinstance(self._material, Orthotropic): + # auto-making a unidirectional laminate CompositeProperty (for ease of use in large cases with metal + smeared stringer models) return CompositeProperty( caps_group=self.caps_group, ply_materials=[self._material], @@ -158,9 +158,11 @@ def auto_property(self) -> BaseProperty: ply_angles=[self.ply_angle], ) else: - raise AssertionError(f"Can't directly make a thickness variable {self.name} from a composite material."+\ - "Don't provide a material => just give the caps_group for the thickness variable"+\ - " and separately make the composite property (registering each to the tacs model or tacs aim).") + raise AssertionError( + f"Can't directly make a thickness variable {self.name} from a composite material." + + "Don't provide a material => just give the caps_group for the thickness variable" + + " and separately make the composite property (registering each to the tacs model or tacs aim)." + ) def register_to(self, tacs_aim): """ From 3ed16cf46d301e39cb389a0d83ecf1453a746e2f Mon Sep 17 00:00:00 2001 From: sean-engelstad Date: Mon, 11 Mar 2024 20:02:14 -0400 Subject: [PATCH 09/10] working composite material case --- examples/caps_wing/simple_naca_wing.csm | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/caps_wing/simple_naca_wing.csm b/examples/caps_wing/simple_naca_wing.csm index d82c27db3..93affc7b8 100644 --- a/examples/caps_wing/simple_naca_wing.csm +++ b/examples/caps_wing/simple_naca_wing.csm @@ -71,6 +71,7 @@ loft 0 # attribute the solid wing #select face attribute capsGroup $OML +attribute capsBound $OML csystem OML YZcsys select face attribute capsGroup $OML @@ -105,7 +106,8 @@ patbeg ispar nspars # add caps attributes attribute capsGroup !$spar+ispar - csystem !$spar+ispar YZcsys + attribute capsBound $spar + csystem spar YZcsys ATTRIBUTE AFLR_GBC $TRANSP_UG3_GBC attribute _color $green @@ -130,7 +132,8 @@ patbeg index ninnerRibs # add caps attributes attribute capsGroup !$rib+irib - csystem !$rib+irib XZcsys + attribute capsBound $rib + csystem rib XZcsys ATTRIBUTE AFLR_GBC $TRANSP_UG3_GBC attribute _color $green From eb7e914b41d40aa447e86c57cfd17b07b55d736b Mon Sep 17 00:00:00 2001 From: seanfireball1 Date: Mon, 11 Mar 2024 22:09:44 -0400 Subject: [PATCH 10/10] rename composite material test to #9 --- .../{6_composite_mat_test.py => 9_composite_mat_test.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/caps_wing/{6_composite_mat_test.py => 9_composite_mat_test.py} (100%) diff --git a/examples/caps_wing/6_composite_mat_test.py b/examples/caps_wing/9_composite_mat_test.py similarity index 100% rename from examples/caps_wing/6_composite_mat_test.py rename to examples/caps_wing/9_composite_mat_test.py