MV-7006: Python UserSub for MotionSolve

In this tutorial, you will learn how to use Python to make a user subroutine for MotionSolve and convert a model with six SFOSUBs, written in C, into a Python usersub.

Note: It is assumed that you are familiar with Python and MotionSolve XML syntax.

Using scripting language such as Python gives you power with reduced complexity. These scripts are interpreted and do not require compiling. Therefore, you do not need a building tool to build subroutines. Furthermore, scripts are easier to read and understand, and can be used for faster prototyping.

If you do not have much programming experience, writing scripts for user-subroutines is simpler than writing C code. For a C user, the usage is even simpler. Besides following the language syntax of Python, you only need to follow the rules to convert the C code into Python scripts.

For your reference, a sample set of Python user subroutines is available in the hwsolvers\motionsolve\usersub\py_src folder.

Rules for Written Python User Subroutines

It is easy to understand the usage for py_* utility functions from the usage of their c_* counterparts, with the help of the following rules:
  1. The output arguments should be moved to the left-hand-side.
    c_datout(&istat); 

    becomes

    istat = py_datout()

    In C utility functions, the input and output arguments are combined in an argument list. In Python, the arguments of the py_* utility functions are strictly the input arguments. All output arguments should be moved to the left-side as return values of the function call.

  2. In C utility functions, any input or output array argument is generally followed by an integer argument for the array size. In Python utility functions, the integer argument for the array size is removed because it is not necessary.
    ipar[0] = (int)par[0];
    ipar[1] = (int)par[1];
    c_sysfnc("DM", ipar, 2, &dm, &errflg);
    simply becomes
    [dm, errflg] = py_sysfnc("DM", [par[0],par[1]])
    and
    ipar[0] = (int)par[1];
    ipar[1] = (int)par[0];
    c_sysary("TDISP", ipar, 2, u1, &nstates, &errflg);
    becomes
    [u1, errflg] = py_sysary("TDISP", [par[1],par[0]])
  3. Change the function name from c_* to py_*.
    c_rcnvrt(sys1, coord1, sys2, coord2, &istate);
    becomes
    [coord2, istate] = py_rcnvrt(sys1, coord1, sys2)

Convert C Sub into Python Sub

  1. In this example, the model uses six SFOSUBs written in C code as shown below:
    DLLFUNC void STDCALL SFOSUB (int *id, double *time, double
     *par, int *npar, int *dflag, int *iflag, double *result)
    {
    // --- Add your local definitions here ---------------------
         double vector[3],dm,vm;
         int ipar[2], iord;
         int errflg;
    // --- Add your executable code here -----------------------
         int itype  = (int)par[0];
         iord = 0;
         if (itype==50)
         {
             ipar[0] = (int)par[1];
             ipar[1] = (int)par[2];
             c_sysfnc("DM", ipar, 2, &dm, &errflg);
             ipar[0] = (int)par[3];
             ipar[1] = (int)par[4];
             c_sysfnc("VM", ipar, 2, &vm, &errflg);
             c_impact(dm, vm, par[5], par[6], par[7], par[8], par[9], iord,
               vector, &errflg);
             *result = vector[0];
         }
    }
  2. Following the rules specified in last section, the corresponding Python script is shown as:
    def SFOSUB (id, time, par, npar, dflag, iflag):
    
    [dm, errflg] = py_sysfnc("DM", [par[0],par[1]])
    [vm, errflg] = py_sysfnc("VM", [par[2],par[3]])
    [vector, errflg] = py_impact(dm, vm, par[4], par[5], 
                       par[6], par[7],par[8],0
    )return vector[0]
  3. Besides the Python scripts, you also need to specify in the XML model the Python scripts that are used in the user subroutine. MotionSolve provides this definition through two attributes in the corresponding elements that uysrsub can be defined.
    1. interpreter = " Python"
    2. script_name = " script_name.py

    This combination replaces the attribute "usrsub_dll_name" in that element.

  4. The following image shows the difference between using C user sub and Python user sub in this example:
    Figure 1.


  5. In the original model, there are six Force_Scalar_TwoBody elements that use SFOSUB written in C:
    <Force_Scalar_TwoBody
         id                  = "30701"
         type                = "Force"
         i_marker_id         = "30701010"
         j_marker_id         = "30701011"
         usrsub_param_string = 
    "USER(50,30301010,30401010,30301010,30401010,10,10,2.0,0.001,0.01)"
         usrsub_dll_name     = "NULL"
         usrsub_fnc_name     = "SFOSUB"
       />
      <Force_Scalar_TwoBody
         id                  = "30801"
         type                = "Force"
         i_marker_id         = "30801010"
         j_marker_id         = "30801011"
         usrsub_param_string = "
    USER
    (50,30301010,30501010,30301010,30501010,10,10,2.0,0.001,0.01)"
         usrsub_dll_name     = "NULL"
         usrsub_fnc_name     = "SFOSUB"
       />
      <Force_Scalar_TwoBody
         id                  = "30901"
         type                = "Force"
         i_marker_id         = "30901010"
         j_marker_id         = "30901011"
         usrsub_param_string = 
    "USER
    (50,30301010,30601010,30301010,30601010,10,10,2.0,0.001,0.01)"
         usrsub_dll_name     = "NULL"
         usrsub_fnc_name     = "SFOSUB"
       />
      <Force_Scalar_TwoBody
         id                  = "31001"
         type                = "Force"
         i_marker_id         = "31001010"
         j_marker_id         = "31001011"
         usrsub_param_string = 
    "USER(50,30401010,30501010,30401010,30501010,10,10,2.0,0.001,0.01)"
         usrsub_dll_name     = "NULL"
         usrsub_fnc_name     = "SFOSUB"
       />
      <Force_Scalar_TwoBody
         id                  = "31101"
         type                = "Force"
         i_marker_id         = "31101010"
         j_marker_id         = "31101011"
         usrsub_param_string = 
    "USER(50,30401010,30601010,30401010,30601010,10,10,2.0,0.001,0.01)"
         usrsub_dll_name     = "NULL"
         usrsub_fnc_name     = "SFOSUB"
       />
      <Force_Scalar_TwoBody
         id                  = "31201"
         type                = "Force"
         i_marker_id         = "31201010"
         j_marker_id         = "31201011"
         usrsub_param_string = 
    "USER(50,30501010,30601010,30501010,30601010,10,10,2.0,0.001,0.01)"
         usrsub_dll_name     = "NULL"
         usrsub_fnc_name     = "SFOSUB"
       />
    
  6. After changing C SFOSUB into Python SFOSUB, the XML content above is replaced with the following:
    <Force_Scalar_TwoBody
         id                  = "30701"
         type                = "Force"
         i_marker_id         = "30701010"
         j_marker_id         = "30701011"
         usrsub_param_string = "USER(30301010,30401010,30301010,30401010,10,10,2.0,0.001,0.01)"
         interpreter         = "Python"
         script_name         = "script/sfosub.py"
         usrsub_fnc_name     = "SFOSUB"
      />
      <Force_Scalar_TwoBody
         id                  = "30801"
         type                = "Force"
         i_marker_id         = "30801010"
         j_marker_id         = "30801011"
         usrsub_param_string = "USER
    (30301010,30501010,30301010,30501010,10,10,2.0,0.001,0.01)"
         interpreter         = "Python"
         script_name         = "script/sfosub.py"
         usrsub_fnc_name     = "SFOSUB"     
      />
      <Force_Scalar_TwoBody
         id                  = "30901"
         type                = "Force"
         i_marker_id         = "30901010"
         j_marker_id         = "30901011"
         usrsub_param_string = "USER(30301010,30601010,30301010,30601010,10,10,2.0,0.001,0.01)"
         interpreter         = "Python"
         script_name         = "script/sfosub.py"
         usrsub_fnc_name     = "SFOSUB"     
      />
      <Force_Scalar_TwoBody
         id                  = "31001"
         type                = "Force"
         i_marker_id         = "31001010"
         j_marker_id         = "31001011"
         usrsub_param_string = "USER(30401010,30501010,30401010,30501010,10,10,2.0,0.001,0.01)"
         interpreter         = "Python"
         script_name         = "script/sfosub.py"
         usrsub_fnc_name     = "SFOSUB"     
      />
      <Force_Scalar_TwoBody
         id                  = "31101"
         type                = "Force"
         i_marker_id         = "31101010"
         j_marker_id         = "31101011"
         usrsub_param_string = "USER(30401010,30601010,30401010,30601010,10,10,2.0,0.001,0.01)"
         interpreter         = "Python"
         script_name         = "script/sfosub.py"
         usrsub_fnc_name     = "SFOSUB"     
      />
      <Force_Scalar_TwoBody
         id                  = "31201"
         type                = "Force"
         i_marker_id         = "31201010"
         j_marker_id         = "31201011"
         usrsub_param_string = "USER(30501010,30601010,30501010,30601010,10,10,2.0,0.001,0.01)"
         interpreter         = "Python"
         script_name         = "script/sfosub.py"
         usrsub_fnc_name     = "SFOSUB"     
      />
  7. With these changes (C code into Python code and XML model change), the model with Python user subroutines are ready to run with MotionSolve.