Composite Class#

class Composite(**kwds)#

Composite is a special class that allows you to aggregate modeling entities, such as Parts, Markers, Forces and other MotionSolve primitives, into a higher-level modeling entity (HLE). A HLE derived from the Composite Base class has the following characteristics:

  • Behaves as an atomic entity.

  • Can be instantiated multiple times.

  • The constructor takes any/all properties.

  • The properties are set/modified via Python’s dot notation.

  • Can have its own validation method.

To create a higher-level entity:

  1. Derive a class from the Composite Base class.

  2. Define the user-defined object properties.

  3. Implement a createChildren method to initiate all the lower-level entities that compose the new object.

  4. Implement an updateChildren method to populate the entities created in createChildren with the user input properties.

  5. Optionally implement a validateSelf method with customized validations.

Note

Upon instantiation of a higher-level entity, the initializer of the corresponding class gets called, which in sequence, initializes the Composite’s properties, ensures all required properties have been specified, calls the createChildren method, then calls the updateChildren method.

Note

When defining an object property, setting its topology attribute to True will indicate that the property has dependencies on other solver objects. In this case, when the property is changed, instead of calling updateChildren to set the new value, createChildren is called first, after deleting the property, and then updateChildren is called. This calling sequence assures that the property has been updated correctly.

Warning

Make sure setting the object’s properties takes place in createChildren and updating them in updateChildren. Avoid setting a property in updateChildren.

createChildren()#

Used to create all the MotionSolve objects that are needed by the Composite class. When the Composite class object is created, the createChildren method is invoked. This happens only once, at the creation of the Composite object. In createChildren you will instantiate various MotionSolve objects and define their immutable properties. Note that only MotionSolve objects should be instantiated here; other types like Point, Vector, and Matrix44 are not relevant for this method.

updateChildren()#

Used to define all the mutable properties of the Composite class. In updateChildren you will populate the MotionSolve objects created in createChildren.

validateSelf(validator)#

Checks the Composite class input to ensure that the data provided complies with the physical restrictions of the new object. This method can be optionally implemented when creating a Composite class object. The Object Validator Module provides various validation functions that can be implemented in the validateSelf method.

Example

Create a higher-level entity to model the LuGre friction force.#
from msolve import *

# Create a LuGre Composite Class
class LuGre (Composite):

  joint  = Reference (Joint)
  vs     = Double (1.E-3)
  mus    = Double (0.3)
  mud    = Double (0.2)
  k0     = Double (1e5)
  k1     = Double (math.sqrt(1e5))
  k2     = Double (0.4)

  def createChildren (self):
    """This is called when the object is created so the children objects
    """

    # The DIFF defining bristle deflection
    self.diff =  Diff    (routine=self.lugre_diff, ic=[0,0])

    # The MARKER on which the friction force acts
    self.im   =  Marker  ()

    # The FORCE defining the friction force
    self.friction =  Sforce  (
     type       = "TRANSLATION",
     actiononly = True,
     routine    = self.lugre_force,
    )

    # The REQUEST capturing the friction force
    self.request = Request (type="FORCE", comment="Friction force")

  def updateChildren (self):

    # Set topology

    self.friction.i = self.joint.i
    self.friction.j = self.joint.j

    self.im.setValues (body=self.joint.i.body, qp=self.joint.i.qp, zp=self.joint.i.zp)

    self.request.setValues (i=self.im, j=self.joint.j, rm=self.joint.j)

  def validateSelf (self, validator):
    validator.checkGe0 ("vs")
    validator.checkGe0 ("mus")
    validator.checkGe0 ("mud")
    validator.checkGe0 ("k0")
    validator.checkGe0 ("k1")
    validator.checkGe0 ("k2")
    if self.mud > self.mus:
      msg = f"Mu dynamic '{self.mud}' needs to be smaller than Mu static '{self.mus}'"
      validator.sendError(msg)

  # Now that the class has been defined what we need to do is to fill the
  # implementation details. The LuGre friction uses a single component
  # force between two bodies, and a first-order differential equation
  # that models the average deflection of the bristles as a function of
  # slip velocity, coefficients of friction, and normal force.

  # First let's work on the Single Component Force. The goal of the
  # Sforce function (normally called SfoSub) is to compute and return
  # the scalar value of the friction force. The three component depicted
  # here are computed separately in the force function.

  def lugre_diff (self, id, time, par, npar, dflag, iflag):
    "Diff user function"
    i   = self.joint.i
    j   = self.joint.j
    vs  = self.vs
    mus = self.mus
    mud = self.mud
    k0  = self.k0

    z   = DIF(self)
    v   = VZ(i,j,j,j)

    N   = math.sqrt (FX(i,j,j)**2 + FY(i,j,j)**2)
    fs  = mus*N
    fc  = mud*N

    p   = -(v/vs)**2
    g   = (fc + (fs - fc) * math.exp(p))/k0

    def smooth_fabs(x):
      return math.fabs(x) # temp solution

    if iflag or math.fabs(g) <1e-8:
      return v
    else:
      return v - smooth_fabs(v) * z / g

  def lugre_force (self, id, time, par, npar, dflag, iflag):
    "Friction Force user function"
    i    = self.joint.i
    j    = self.joint.j
    diff = self.diff
    k0   = self.k0
    k1   = self.k1
    k2   = self.k2

    v    = VZ(i,j,j,j)
    z    = DIF(diff)
    zdot = DIF1(diff)

    F    = k0*z + k1*zdot + k2*v
    #print "Time=", time, "  V=", v, "  Z=", z, "  ZDOT=", zdot, "  F=", F
    #print "k0*z=", k0*z, "  k1*zdot=", k1*zdot, "  k2*v=", k2*v
    return -F

################################################################################
# Model definition                                                             #
################################################################################
def sliding_block ():

  """Test case for the LuGre friction model

     Slide a block across a surface.

     The block is attached to ground with a translational joint that has
     LuGre friction
  """

  model = Model (output="lugre_friction")

  Units      (mass="KILOGRAM", length="METER", time="SECOND", force="NEWTON")
  Accgrav    (jgrav=-9.800)
  Integrator (error=1e-5)
  Output     (reqsave=True)

  # Location for Part cm and markers used for the joint markers
  qp = Point (10,0,0)
  xp = Point (10,1,0)
  zp = Point (11,0,0)

  ground    = Part   (ground=True)
  ground.jm = Marker (part=ground, qp=qp, zp=zp, xp=xp, label="Joint Marker")

  block    = Part   (mass=1, ip=[1e3,1e3,1e3], label="Block")
  block.cm = Marker (part=block, qp=qp, zp=zp, xp=xp, label="Block CM")

  # Attach the block to ground
  joint = Joint (
    type = "TRANSLATIONAL",
    i    = block.cm,
    j    = ground.jm,
  )

  # Apply the LuGre friction to the joint
  model.lugre = LuGre (joint=joint)

  # Push the block by applying a simple harmonic function +- 3 N of amplitude
  Sforce (
    type = "TRANSLATION",
    actiononly = True,
    i          = block.cm,
    j          = ground.jm,
    function   = "3*sin(2*pi*time)",
    label      = "Actuation Force",
  )


  # Request the DISPLACEMENT, VELOCITY and FORCE of the Joint
  model.r1 = Request (
    type    = "DISPLACEMENT",
    i       = block.cm,
    j       = ground.jm,
    rm      = ground.jm,
    comment ="Joint displacement",
  )
  model.r2 = Request (
    type    = "VELOCITY",
    i       = block.cm,
    j       = ground.jm,
    rm      = ground.jm,
    comment ="Joint velocity",
  )
  model.r3 = Request (
    type    = "FORCE",
    i       = block.cm,
    j       = ground.jm,
    rm      = ground.jm,
    comment ="Joint force",
  )

  return model

# first let's create a model by calling the function
model = sliding_block ()

# run a 4 second simulation
# store the results of the run in an appropriate container
run = model.simulate (type="DYNAMICS", returnResults=True, end=4, dtout=.01)

# define a plot function
import matplotlib.pyplot as plt

def plot(model):

  # get the channels from the model
  disp = run.getObject (model.r1)
  velo = run.getObject (model.r3)
  force = run.getObject (model.r2)

  plt.subplot(3, 1, 1)
  plt.plot(force.times, force.getComponent (3))
  plt.title('Friction force')
  plt.ylabel('force [N]')

  plt.subplot(3, 1, 2)
  plt.plot(velo.times, velo.getComponent (3), 'r')
  plt.title('Block Velocity')
  plt.ylabel('velocity [m/s]')

  plt.subplot(3, 1, 3)
  plt.plot(disp.times, disp.getComponent (3), 'g')
  plt.title('Block Displacement')
  plt.xlabel('time (s)')
  plt.ylabel('travel [m]')

  plt.grid(True)
  plt.tight_layout()

  # Show the plot
  plt.show()


# plot the results
plot(model)

# change the static friction coefficient, simulate and plot the new results
model.lugre.mus= 0.4
run = model.simulate (type="DYNAMICS", returnResults=True, duration=4, dtout=.01)
# replot the quantities and observe the changes to the Friction Force
plot(model)