diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index b57d4e17f..1d24cd0b3 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -21,7 +21,7 @@ jobs: unit_test_and_docs: # Run on Ubuntu runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 40 # Necessary to prevent mpi tests failing due to lack of slots env: OMPI_MCA_btl: self,tcp diff --git a/docs/source/pytacs/buckling.rst b/docs/source/pytacs/buckling.rst index a99fe0a83..45fc595ab 100644 --- a/docs/source/pytacs/buckling.rst +++ b/docs/source/pytacs/buckling.rst @@ -1,5 +1,5 @@ BucklingProblem ------------- +--------------- .. automodule:: tacs.problems.buckling Options @@ -16,4 +16,4 @@ API Reference ^^^^^^^^^^^^^ .. autoclass:: tacs.problems.BucklingProblem :members: - :inherited-members: \ No newline at end of file + :inherited-members: diff --git a/src/constitutive/TACSBladeStiffenedShellConstitutive.cpp b/src/constitutive/TACSBladeStiffenedShellConstitutive.cpp index 39feb0c33..97653bff0 100644 --- a/src/constitutive/TACSBladeStiffenedShellConstitutive.cpp +++ b/src/constitutive/TACSBladeStiffenedShellConstitutive.cpp @@ -106,8 +106,8 @@ TACSBladeStiffenedShellConstitutive::TACSBladeStiffenedShellConstitutive( this->numDesignVars++; this->numPanelDV++; } - this->panelPlyFracLowerBounds[ii] = 0.1; - this->panelPlyFracUpperBounds[ii] = 0.9; + this->panelPlyFracLowerBounds[ii] = 0.0; + this->panelPlyFracUpperBounds[ii] = 1.0; } // --- Stiffener DVs --- @@ -154,8 +154,8 @@ TACSBladeStiffenedShellConstitutive::TACSBladeStiffenedShellConstitutive( this->numDesignVars++; this->numStiffenerDV++; } - this->stiffenerPlyFracLowerBounds[ii] = 0.1; - this->stiffenerPlyFracUpperBounds[ii] = 0.9; + this->stiffenerPlyFracLowerBounds[ii] = 0.0; + this->stiffenerPlyFracUpperBounds[ii] = 1.0; } // --- Stiffener flange fraction --- diff --git a/src/constitutive/TACSBladeStiffenedShellConstitutive.h b/src/constitutive/TACSBladeStiffenedShellConstitutive.h index c75d2b597..0e1d93148 100644 --- a/src/constitutive/TACSBladeStiffenedShellConstitutive.h +++ b/src/constitutive/TACSBladeStiffenedShellConstitutive.h @@ -238,6 +238,16 @@ class TACSBladeStiffenedShellConstitutive : public TACSShellConstitutive { int getDesignVarRange(int elemIndex, int dvLen, TacsScalar lb[], TacsScalar ub[]); + /** + * @brief Get the number of panel plies + */ + int getNumPanelPlies() { return this->numPanelPlies; } + + /** + * @brief Get the number of stiffener plies + */ + int getNumStiffenerPlies() { return this->numStiffenerPlies; } + // ============================================================================== // Evaluate mass properties // ============================================================================== diff --git a/tacs/constitutive.pxd b/tacs/constitutive.pxd index 58ba7c32b..a1888dfed 100644 --- a/tacs/constitutive.pxd +++ b/tacs/constitutive.pxd @@ -156,6 +156,8 @@ cdef extern from "TACSBladeStiffenedShellConstitutive.h": int[], # stiffenerPlyFracNums TacsScalar # flangeFraction ) + int getNumPanelPlies() + int getNumStiffenerPlies() void setKSWeight(double ksWeight) void setStiffenerPitchBounds(TacsScalar lowerBound, TacsScalar upperBound) void setStiffenerHeightBounds(TacsScalar lowerBound, TacsScalar upperBound) diff --git a/tacs/constitutive.pyx b/tacs/constitutive.pyx index f2bfc8a89..42ad8e4c5 100644 --- a/tacs/constitutive.pyx +++ b/tacs/constitutive.pyx @@ -764,10 +764,89 @@ cdef class CompositeShellConstitutive(ShellConstitutive): self.props = [prop.getMaterialProperties() for prop in ply_list] + def generateBDFCard(self): + """ + Generate pyNASTRAN card class based on current design variable values. + + Returns: + card (pyNastran.bdf.cards.properties.shell.PCOMP): pyNastran card holding property information + """ + num_plies = len(self.props) + cdef TACSCompositeShellConstitutive* comp_ptr = self.cptr + cdef np.ndarray ply_thicknesses = np.zeros(num_plies, dtype) + cdef np.ndarray ply_angles = np.zeros(num_plies, dtype) + + comp_ptr.getPlyThicknesses(ply_thicknesses.data) + comp_ptr.getPlyAngles(ply_angles.data) + + mat_ids = [] + for i in range(num_plies): + ply_id = self.props[i].getNastranID() + mat_ids.append(ply_id) + + prop = nastran_cards.properties.shell.PCOMP(self.nastranID, mat_ids, + ply_thicknesses.astype(float), + np.rad2deg(ply_angles, dtype=float)) + return prop + cdef class BladeStiffenedShellConstitutive(ShellConstitutive): """This constitutive class models a shell stiffened with T-shaped stiffeners. The stiffeners are not explicitly modelled. Instead, their stiffness is "smeared" across the shell. + + Parameters + ---------- + panelPly : tacs.constitutive.OrthotropicPly + Ply model to use for the panel + stiffenerPly : tacs.constitutive.OrthotropicPly + Ply model to use for the stiffener + kcorr : float or complex + Shear correction factor, usually 5.0/6.0 + panelLength : float or complex + Panel length DV value + panelLengthNum : int + Panel lenth DV number, passing a negative value tells TACS not to treat this as a DV + stiffenerPitch : float or complex + Stiffener pitch DV value + stiffenerPitchNum : int + DV number, passing a negative value tells TACS not to treat this as a DV + panelThick : float or complex + Panel thickness DV value + panelThickNum : int + DV number, passing a negative value tells TACS not to treat this as a DV + numPanelPlies : int + Number of distinct ply angles in the panel + panelPlyAngles : numpy.ndarray[float or complex] + Array of ply angles in the panel + panelPlyFracs : numpy.ndarray[float or complex] + Array of ply fractions in the panel + panelPlyFracNums : numpy.ndarray[np.intc] + Array of ply fraction DV numbers in the panel, passing negative values tells TACS not to treat that ply fraction as a DV + stiffenerHeight : float or complex + Stiffener height DV value + stiffenerHeightNum : int + DV number, passing a negative value tells TACS not to treat this as a DV + stiffenerThick : float or complex + Stiffener thickness DV value + stiffenerThickNum : int + DV number, passing a negative value tells TACS not to treat this as a DV + numStiffenerPlies : int + Number of distinct ply angles in the stiffener + stiffenerPlyAngles : numpy.ndarray[float or complex] + Array of ply angles for the stiffener + stiffenerPlyFracs : numpy.ndarray[float or complex] + Array of ply fractions for the stiffener + stiffenerPlyFracNums : numpy.ndarray[numpy.intc] + Array of ply fraction DV numbers for the stiffener, passing negative values tells TACS not to treat that ply fraction as a DV + flangeFraction : float, optional + Ratio of the stiffener base width to the stiffener height, by default 1.0 + + Raises + ------ + ValueError + Raises error if panelPlyAngles, panelPlyFracs, or panelPlyFracNums do not have numPanelPlies entries + ValueError + Raises error if stiffenerPlyAngles, stiffenerPlyFracs, or stiffenerPlyFracNums do not have numStiffenerPlies entries """ def __cinit__( self, @@ -797,19 +876,22 @@ cdef class BladeStiffenedShellConstitutive(ShellConstitutive): if len(panelPlyAngles) != numPanelPlies: raise ValueError('panelPlyAngles must have length numPanelPlies') - if len(panelPlyAngles) != numPanelPlies: - raise ValueError('panelPlyNums must have length numPanelPlies') + if len(panelPlyFracs) != numPanelPlies: + raise ValueError('panelPlyFracs must have length numPanelPlies') + if len(panelPlyFracNums) != numPanelPlies: + raise ValueError('panelPlyFracNums must have length numPanelPlies') if len(stiffenerPlyAngles) != numStiffenerPlies: raise ValueError('stiffenerPlyAngles must have length numStiffenerPlies') - if len(stiffenerPlyAngles) != numStiffenerPlies: - raise ValueError('stiffenerPlyNums must have length numStiffenerPlies') + if len(stiffenerPlyFracs) != numStiffenerPlies: + raise ValueError('stiffenerPlyFracs must have length numStiffenerPlies') + if len(stiffenerPlyFracNums) != numStiffenerPlies: + raise ValueError('stiffenerPlyFracNums must have length numStiffenerPlies') # Numpy's default int type is int64, but this is interpreted by Cython as a long. if panelPlyFracNums.dtype != np.intc: panelPlyFracNums = panelPlyFracNums.astype(np.intc) if stiffenerPlyFracNums.dtype != np.intc: stiffenerPlyFracNums = stiffenerPlyFracNums.astype(np.intc) - # raise ValueError('panelPlyFracNums must be of type int32') self.blade_ptr = new TACSBladeStiffenedShellConstitutive( panelPly.ptr, @@ -842,27 +924,73 @@ cdef class BladeStiffenedShellConstitutive(ShellConstitutive): """ Update the ks weight used for aggregating the different failure modes - Args: - ksWeight (float): KS weight + Parameters + ---------- + ksWeight : float + KS aggregation weight """ if self.blade_ptr: self.blade_ptr.setKSWeight(ksWeight) def setStiffenerPitchBounds(self, TacsScalar lowerBound, TacsScalar upperBound): + """Set the lower and upper bounds for the stiffener pitch design variable + + The default bounds are 1e-3 and 1e20 + + Parameters + ---------- + lowerBound : float or complex + Lower bound + upperBound : float or complex + Upper bound + """ if self.blade_ptr: self.blade_ptr.setStiffenerPitchBounds(lowerBound, upperBound) def setStiffenerHeightBounds(self, TacsScalar lowerBound, TacsScalar upperBound): + """Set the lower and upper bounds for the stiffener height design variable + + The default bounds are 1e-3 and 1e20 + + Parameters + ---------- + lowerBound : float or complex + Lower bound + upperBound : float or complex + Upper bound + """ if self.blade_ptr: self.blade_ptr.setStiffenerHeightBounds(lowerBound, upperBound) def setStiffenerThicknessBounds(self, TacsScalar lowerBound, TacsScalar upperBound): + """Set the lower and upper bounds for the stiffener thickness design variable + + The default bounds are 1e-4 and 1e20 + + Parameters + ---------- + lowerBound : float or complex + Lower bound + upperBound : float or complex + Upper bound + """ if self.blade_ptr: self.blade_ptr.setStiffenerThicknessBounds(lowerBound, upperBound) def setPanelThicknessBounds(self, TacsScalar lowerBound, TacsScalar upperBound): + """Set the lower and upper bounds for the panel thickness design variable + + The default bounds are 1e-4 and 1e20 + + Parameters + ---------- + lowerBound : float or complex + Lower bound + upperBound : float or complex + Upper bound + """ if self.blade_ptr: self.blade_ptr.setPanelThicknessBounds(lowerBound, upperBound) @@ -872,8 +1000,28 @@ cdef class BladeStiffenedShellConstitutive(ShellConstitutive): np.ndarray[TacsScalar, ndim=1, mode='c'] lowerBound, np.ndarray[TacsScalar, ndim=1, mode='c'] upperBound ): + """Set the lower and upper bounds for the stiffener ply fraction design variables + + The default bounds are 0 and 1 + + Parameters + ---------- + lowerBound : numpy.ndarray[float or complex] + Lower bound + upperBound : numpy.ndarray[float or complex] + Upper bounds + + Raises + ------ + ValueError + Raises error if the length of lowerBound or upperBound is not equal to the number of stiffener plies + """ if self.blade_ptr: + if len(lowerBound) != self.blade_ptr.getNumStiffenerPlies(): + raise ValueError('lowerBound must have length numStiffenerPlies') + if len(upperBound) != self.blade_ptr.getNumStiffenerPlies(): + raise ValueError('upperBound must have length numStiffenerPlies') self.blade_ptr.setStiffenerPlyFractionBounds(lowerBound.data, upperBound.data) def setPanelPlyFractionBounds( @@ -881,34 +1029,29 @@ cdef class BladeStiffenedShellConstitutive(ShellConstitutive): np.ndarray[TacsScalar, ndim=1, mode='c'] lowerBound, np.ndarray[TacsScalar, ndim=1, mode='c'] upperBound ): + """Set the lower and upper bounds for the panel ply fraction design variables - if self.blade_ptr: - self.blade_ptr.setPanelPlyFractionBounds(lowerBound.data, upperBound.data) + The default bounds are 0 and 1 - def generateBDFCard(self): - """ - Generate pyNASTRAN card class based on current design variable values. + Parameters + ---------- + lowerBound : numpy.ndarray[float or complex] + Lower bound + upperBound : numpy.ndarray[float or complex] + Upper bounds - Returns: - card (pyNastran.bdf.cards.properties.shell.PCOMP): pyNastran card holding property information + Raises + ------ + ValueError + Raises error if the length of lowerBound or upperBound is not equal to the number of panel plies """ - num_plies = len(self.props) - cdef TACSCompositeShellConstitutive* comp_ptr = self.cptr - cdef np.ndarray ply_thicknesses = np.zeros(num_plies, dtype) - cdef np.ndarray ply_angles = np.zeros(num_plies, dtype) - comp_ptr.getPlyThicknesses(ply_thicknesses.data) - comp_ptr.getPlyAngles(ply_angles.data) - - mat_ids = [] - for i in range(num_plies): - ply_id = self.props[i].getNastranID() - mat_ids.append(ply_id) - - prop = nastran_cards.properties.shell.PCOMP(self.nastranID, mat_ids, - ply_thicknesses.astype(float), - np.rad2deg(ply_angles, dtype=float)) - return prop + if self.blade_ptr: + if len(lowerBound) != self.blade_ptr.getNumPanelPlies(): + raise ValueError('lowerBound must have length numPanelPlies') + if len(upperBound) != self.blade_ptr.getNumPanelPlies(): + raise ValueError('upperBound must have length numPanelPlies') + self.blade_ptr.setPanelPlyFractionBounds(lowerBound.data, upperBound.data) cdef class LamParamShellConstitutive(ShellConstitutive): def __cinit__(self, OrthotropicPly ply, **kwargs): diff --git a/tacs/pytacs.py b/tacs/pytacs.py index b2d2078c7..d37e713bb 100755 --- a/tacs/pytacs.py +++ b/tacs/pytacs.py @@ -263,10 +263,12 @@ def __init__(self, fileName, comm=None, dvNum=0, scaleList=None, options=None): @preinitialize_method def addGlobalDV(self, descript, value, lower=None, upper=None, scale=1.0): """ + Add a global design variable that can affect multiple components. + This function allows adding design variables that are not cleanly associated with a particular constitutive object. One example is the pitch of the stiffeners for blade-stiffened - panels; It is often the same for many different constitutive + panels. It is often the same for many different constitutive objects. By calling this function, the internal dvNum counter is incremented, and the user doesn't have to worry about it. @@ -276,7 +278,6 @@ def addGlobalDV(self, descript, value, lower=None, upper=None, scale=1.0): descript : str A user-supplied string that can be used to retrieve the variable number and value elemCallBackFunction. - value : float Initial value for variable. lower : float @@ -453,12 +454,12 @@ def selectCompIDs( Methods of selection: 1. include, integer, string, list of integers and/or strings: The - simplest and most direct way of selecting a component. - The - user supplies the index of the componentID, a name or partial - name, or a list containing a combination of both. + simplest and most direct way of selecting a component. + The + user supplies the index of the componentID, a name or partial + name, or a list containing a combination of both. - For example:: + For example:: # Select the 11th component selectCompIDs(include=10) @@ -478,69 +479,65 @@ def selectCompIDs( selectCompIDs(include=['rib.00', 10, 'spar']) 2. Exclude, operates similarly to 'include'. - The behaviour - of exclude is identical to include above, except that - component ID's that are found using 'exclude' are - 'subtracted' from those found using include. - A special - case is treated if 'include' is NOT given: if only an - exclude list is given, this implies the selection of all - compID's EXCEPT the those in exclude. + The behaviour of exclude is identical to include above, except that + component ID's that are found using 'exclude' are + 'subtracted' from those found using include. + A special case is treated if 'include' is NOT given: if only an + exclude list is given, this implies the selection of all + compID's EXCEPT the those in exclude. - For example:: + For example:: - # This will return will [0, 1, 2, 3, 5, ..., nComp-1] - selectCompIDs(exclude = 4) + # This will return will [0, 1, 2, 3, 5, ..., nComp-1] + selectCompIDs(exclude = 4) - # This will return [0, 1, 4, 5, ..., nComp-1] - selectCompIDs(exclude = [2, 3]) will return + # This will return [0, 1, 4, 5, ..., nComp-1] + selectCompIDs(exclude = [2, 3]) will return - # This will return components that have 'ribs' in the - # component ID, but not those that have 'le_ribs' in the - # component id. - selectCompIDs(include='ribs', exclude='le_ribs') + # This will return components that have 'ribs' in the + # component ID, but not those that have 'le_ribs' in the + # component id. + selectCompIDs(include='ribs', exclude='le_ribs') 3. includeBounds, list of components defining a region inside - which 'include' components will be selected. - This - functionality uses a geometric approach to select the compIDs. - All components within the project 2D convex hull are included. - Therefore, it is essential to split up concave include regions - into smaller convex regions. - Use multiple calls to selectCompIDs to - accumulate multiple regions. - - For example:: - - # This will select upper skin components between the - # leading and trailing edge spars and between ribs 1 and 4. - selectCompIDs(include='U_SKIN', includeBound= - ['LE_SPAR', 'TE_SPAR', 'RIB.01', 'RIB.04']) + which 'include' components will be selected. + This functionality uses a geometric approach to select the compIDs. + All components within the project 2D convex hull are included. + Therefore, it is essential to split up concave include regions + into smaller convex regions. + Use multiple calls to selectCompIDs to accumulate multiple regions. + + For example:: + + # This will select upper skin components between the + # leading and trailing edge spars and between ribs 1 and 4. + selectCompIDs(include='U_SKIN', includeBound= + ['LE_SPAR', 'TE_SPAR', 'RIB.01', 'RIB.04']) 4. nGroup: The number of groups to divide the found components - into. - Generally this will be 1. However, in certain cases, it - is convenient to create multiple groups in one pass. + into. + Generally this will be 1. However, in certain cases, it + is convenient to create multiple groups in one pass. - For example:: + For example:: - # This will 'evenly' create 10 groups on all components - # containing LE_SPAR. - Note that once the components are - # selected, they are sorted **alphabetically** and assigned - # sequentially. - selectCompIDs(include='LE_SPAR', nGroup=10) + # This will 'evenly' create 10 groups on all components + # containing LE_SPAR. + Note that once the components are + # selected, they are sorted **alphabetically** and assigned + # sequentially. + selectCompIDs(include='LE_SPAR', nGroup=10) - nGroup can also be negative. - If it is negative, then a single - design variable group is added to each of the found - components. + nGroup can also be negative. + If it is negative, then a single + design variable group is added to each of the found + components. - For example:: + For example:: - # will select all components and assign a design variable - # group to each one. - selectCompIDs(nGroup=-1) + # will select all components and assign a design variable + # group to each one. + selectCompIDs(nGroup=-1) includeOp, str: 'and' or 'or'. Selects the logical operation diff --git a/tests/constitutive_tests/test_blade_sitffened_shell_constitutive.py b/tests/constitutive_tests/test_blade_sitffened_shell_constitutive.py index 918ecf110..082564f0d 100644 --- a/tests/constitutive_tests/test_blade_sitffened_shell_constitutive.py +++ b/tests/constitutive_tests/test_blade_sitffened_shell_constitutive.py @@ -60,7 +60,9 @@ def setUp(self): self.stiffenerPlyFracs ) # Make sure ply Fracs sum to 1 self.stiffenerPlyFracNums = np.arange( - 5 + self.numPanelPlies, 5 + self.numStiffenerPlies, dtype=np.intc + 5 + self.numPanelPlies, + 5 + self.numPanelPlies + self.numStiffenerPlies, + dtype=np.intc, ) self.dvs = (