Methodology

Base Class for Joint Constitutive Models

The object-oriented programming approach described in the Introduction is exploited in 3DEC’s support for user-written constitutive models.

A base class provides a framework for actual joint constitutive models, which are classes derived from the base class. The base class, called JointModel, is an “abstract” class. It declares a number of “pure virtual” member functions (signified by the =0 syntax appended to the function prototypes). This means that no object of this base class can be created, and any derived-class object must supply real member functions to replace every one of the pure virtual functions of JointModel.

Full documentation of JointModel is provided in i Programmer’s Interface. See the jmodels namespace.

Member Functions

Any derived constitutive-model class must provide actual functions to replace the virtual member-functions in JointModel.

The model class definition should also contain a constructor that must invoke the base constructor. In all cases, the derived-class constructor should be called with no parameters, as in the clone member function. Initialization of data members may be performed by the constructor, as illustrated here. In this example, the symbols kn_, ks_, etc. are the data members for the derived model.

Typical Model Constructor

  JModelExample::JModelExample() :     
    kn_(0),
    ks_(0),
    cohesion_(0),
    friction_(0),
    dilation_(0),
    tension_(0),
    zero_dilation_(0),
    res_cohesion_(0),
    res_friction_(0),
    res_tension_(0),
    tan_friction_(0),
    tan_dilation_(0),
    tan_res_friction_(0)
  {
  }

Registration of Models

Each user-written constitutive model is compiled into a DLL that must be instantiated in the 3DEC process. By convention, there are four exported functions in a DLL that is used as a plugin to 3DEC: getName(), getMajorVersion(), getMinorVersion() and createInstance(). A stub function called DllMain() is also required; it is called when the library is loaded and unloaded from the system. For example, this is how these functions appear for the Example model.

int __stdcall DllMain(void *,unsigned, void *)
{
  return 1;
}

extern "C" __declspec(dllexport) const char *getName() 
{
#ifdef JMODELDEBUG
  return "jmodelexampled";
#else
  return "jmodelexample";
#endif
}

extern "C" __declspec(dllexport) unsigned getMajorVersion()
{
  return MAJOR_VERSION;
}

extern "C" __declspec(dllexport) unsigned getMinorVersion()
{
  return UPDATE_VERSION;
}

extern "C" __declspec(dllexport) void *createInstance() 
{
  jmodels::JModelExample *m = new jmodels::JModelExample();
  return (void *)m;
}

The extern "C" __declspec(dllexport) code indicates that these functions should be exported from the DLL.

The DllMain() function is always the same.

The exported DLL function getName() should always return a string that begins with the word “jmodel”. This indicates that the DLL is a joint constitutive model plugin. In the above example, the string “jmodelexample” is returned by the exported DLL function getName(). This convention of prefixing the exported name with “jmodel” should be followed. The JointModel’s getName() function must return a unique string (this is the method used to distinguish constitutive models from each other).

The getMajorVersion() function should not be altered. The major version is determined by the base constitutive model DLL, and indicates binary compatibility. By convention, this number will also be indicated in the file name of the DLL that is produced.

The getMinorVersion() function indicates the minor version update of the custom constitutive model. If new properties are added to a constitutive model, for example, it might not be save file-compatible with an older version. In this case, the minor version number (defined in the file “version.txt”) would be incremented by 1.

The createInstance() function actually creates and returns an instance of the derived class. This is stored in a registry and used (via the clone() function) to create all other instances.

Information Passed between Model and Program during Cycling

The most important link between 3DEC and a user-written model is the member-function run(UByte dim, State *s), which computes the mechanical response of the model during cycling. A structure, State (defined in “state.h”), is used to transfer information to and from the model.

Full documentation of State is also provided in the jmodels namespace of the Programmer’s Interface documentation.

The main task of member-function run() is to compute new forces from displacement increments. In a nonlinear model, it is also useful to communicate the internal state of the model, so that the state may be plotted and printed. For example, the supplied models indicate whether they are currently yielding or have yielded in the past. Each subcontact may set the variable state_, which records the state of a model as a series of bits that can be on or off (1 or 0). Each bit can be associated with a message that is displayed on the screen. The string returned by member function States contains sub-strings corresponding to bit positions that the model may set in state_. The first sub-string refers to bit 1, the second to bit 2, and so on. Several bits may be set simultaneously. For example, both shear and tensile yield may occur together. The bit assignment is described below. The operation of the state logic may be appreciated by consulting any of the nonlinear model files (e.g., “jmodelexample.cpp”).

The arrays working_ and iworking_ allow data to be safely stored in the State structure . Note that the user should not assume that these arrays are initialized to zero on the first call to the first tetra, and detect and initialize this storage themselves.

State Indicators of Subcontacts

Each subcontact State has a state_ member that can be used to store the failure state. The member variable has 32 bits that can be used to represent a maximum of 16 distinct states (assuming “now” and “in past” are tracked separately).

The state indicator bits are used by built-in constitutive models to denote plastic failure of subcontacts. See Table 1 for bit assignment and the corresponding failure state for built-in constitutive models. Note that this is a convention and is not actually required for all constitutive models.

Table 1: Joint failure states and bit assignments
    Hex Decimal Binary
slip_now = 0x0001 1 0000 0000 0000 0001
tension_now = 0x0002 2 0000 0000 0000 0010
slip_past = 0x0004 4 0000 0000 0000 0100
tension_past = 0x0008 8 0000 0000 0000 1000
unused = 0x0010 16 0000 0000 0001 0000
unused = 0x0020 32 0000 0000 0010 0000
.        
.        
.        
unused = 0x8000 32768 1000 0000 0000 0000

For user-defined constitutive models, the user may create a named state and assign any particular bit for that state, and subsequently update the subcontact state indicator variable. The named states in Table 1 are used by built-in constitutive models to update the failure states of subcontacts and show use case for state indicator variable.

3DEC calls the constitutive model function run() for each subcontact to update its force values. Typically, the state indicator is also updated in this process by the constitutive model. For built-in models, the state indicator denotes the failure state of the subcontact. This is updated by the constitutive model using the logical “or” (|) operation with the subcontact state indicator variable and the current failure state calculated by the constitutive model. The user should take care to appropriately set or un-set all previous states updated prior to the current state calculated by the constitutive model. The state of a subcontact can then be checked using the logical “and” (&) operator with the state variable and desired user-defined state.

Suppose a subcontact is undergoing failure in tension and shear. This failure state is stored in the state indicator of the subcontact. The built-in constitutive model updates the state variable:

  1. Initially, check the current state and set/un-set state indicator, appropriately. For example,

        if (s->state_ & slip_now) s->state_ |= slip_past;
        s->state_ &= ~slip_now;
        if (s->state_ & tension_now) s->state_ |= tension_past;
        s->state_ &= ~tension_now;
    
  2. Calculate the current state of the subcontact.

  3. Update the state if necessary. For example,

          s->state_ |= tension_now;
    

During initialization (step 1.), slip_past and tension_past are turned on, shear_now and tension_now are turned off. Step (3.) sets the second bit (tension_now) for that particular subcontact. Assuming no failure in the past, the FISH function block.subcontact.state returns a value of 2, the decimal equivalent of the second bit being set.