Many publications describe software design patterns for reusing object-oriented software. The most widely read book on design patterns is so influential that it has its own Wikipedia page. In this book, the “Gang of Four” offers two guiding principles for software design patterns.
The first principle is to program to an interface, not an implementation, i.e., seek general solutions, not specific solutions that work for only a few cases. The second principle is to favor object composition over class inheritance as a means of code reuse, i.e., treat objects you want to reuse as unchangeable units.
Although numerous software design patterns appear at all levels of the OpenSees framework, this post will highlight the Decorator pattern and its application to UniaxialMaterial and NDMaterial objects.
The Decorator pattern creates a new object that modifies the functionality of an object of the same type. Not only does the Decorator pattern allow for extensive code reuse, it can lead to rapid development of new models and quick changes to existing models without having to add new code.
Writing this post, I hope to mitigate unnecessary code duplication in OpenSees. So, before you code up a new material model, think to yourself “there might be a wrapper for that”, then re-read this post.
There are several UniaxialMaterial wrapper classes. After briefly describing the functionality of each wrapper, I will show how the wrapper affects a canonical wrapped material through one load cycle. The wrapped material is a HardeningMaterial object with elastic modulus, , yield stress, =1, and kinematic hardening modulus, .
In a case of addition by subtraction, the PathIndependentMaterial class does not call commitState() on its wrapped UniaxialMaterial object, making the stress-strain response path-independent.
An undocumented wrapper, the TensionOnlyMaterial class returns zero stress and tangent for any UniaxialMaterial object that returns negative stress. The wrapper also doesn’t call commitState() when there is negative stress in the wrapped material.
The MinMaxMaterial forwards strain to its wrapped UniaxialMaterial object and returns its stress and tangent, until the material strain is greater than a specified maximum strain or less than a specified minimum. After this condition is met in commitState(), the wrapper does nothing, like the wrapped material has failed. Below is a demonstration with the minimum strain set to -0.8.
Another undocumented wrapper, the SimpleFractureMaterial imposes tensile fracture on its wrapped UniaxialMaterial object. After reaching the specified tensile strain, the wrapper will provide compressive stress based on an estimate of the elastic strain. Below is a demonstration with the maximum tensile strain set to 0.8.
The InitStrainMaterial wrapper imposes an initial strain on its wrapped UniaxialMaterial object. After the constructor is called, nothing special is done by the wrapper. Below is a demonstration with the initial strain equal to -0.1.
Similarly, the InitStressMaterial imposes an initial stress on its wrapped UniaxialMaterial object. Remember that it’s always a good idea to do a single analysis step with when using the initial strain and initial stress wrappers in your model. Below is a demonstration with the initial stress equal to 0.3.
The FatigueMaterial class wraps any UniaxialMaterial object and accumulates damage according to Miner’s Rule using a modified rainflow cycle counting algorithm. After the fatigue life is exhausted, the wrapper returns zero stress and tangent. Please see the wiki documentation for this wrapper to find more details.
An undocumented wrapper, the DamperMaterial class uses the stress-strain response of any UniaxialMaterial object as a stress-strain rate relationship for damping. This conversion is done by a simple transfer of strain rate to strain and tangent to damping tangent in the material state determination methods.
You might wonder why I omitted the ParallelMaterial and SeriesMaterial classes from this list. The reason is they are examples of the Composite pattern.
There are also wrappers for NDMaterial objects. The PlaneStrainMaterial sets the appropriate strain components to zero before calling its wrapped three-dimensional NDMaterial object. We should also have one of these for the axisymmetric strain condition.
The PlaneStressMaterial, PlateFiberMaterial, and BeamFiberMaterial (versions for 2D and 3D beams), classes use static condensation in order to impose the appropriate stress conditions on their wrapped three-dimensional NDMaterial objects.
The ContinuumUniaxial class performs static condensation to get from a three-dimensional NDMaterial object to a uniaxial state of stress. Because it goes between the NDMaterial and UniaxialMaterial interfaces, the ContinuumUniaxial class is technically an Adapter, not a Decorator.
The PlateFromPlaneStressMaterial wrapper adds linear-elastic shear behavior to a plane stress material in order to get the plate fiber stress condition. This wrapper does not need to perform static condensation. The PlateRebarMaterial wrapper (also technically an Adapter) uses a UniaxialMaterial object and an orientation angle to represent reinforcement in a shell layer.
The InitStressNDMaterial is the NDMaterial version of the InitStressMaterial wrapper shown above. It only works for plane strain and three-dimensional material models, but there’s no reason it can’t be adapted for initial stresses in beam and plate materials.
The InitStressNDMaterial, BeamFiberMaterial, and ContinuumUniaxial wrappers are undocumented. This is my doing.
Well, that should wrap up this post. Information on other design patterns used in the material and element models of OpenSees can be found here while information on the design patterns used in the high level model building and solution strategies can be found here.