独自のモデリング要素の作成

MotionSolve Pythonインターフェースでは、高度なモデリング要素を作成し、使用できます。以下の例で詳しく説明します。以下の例は、MotionSolve Pythonインターフェースを使用しなくても行えますが、その場合、作成も使用も難しくなります。

MotionSolveでは、汎用のモデリング要素と現象固有のモデリング要素の豊富なライブラリが提供されています。これらを組み合わせて、新しい挙動を示す新しいモデリング要素を定義できます。このプロセスは、集約と呼ばれます。MotionSolveでは、このようなオブジェクトを表現するための特別なCompositeクラスが提供されています。

Compositeクラス

Compositeは、パート、マーカー、フォース、微分方程式、代数方程式、その他のMotionSolveプリミティブなどのエンティティを集約することを可能にする特別なクラスです。Compositeクラスオブジェクトは他のソルバーオブジェクトと同じように動作しますが、扱いやすいようにまとめてパッケージ化されたエンティティの集合という点が異なります。これらのオブジェクトはアトミックエンティティとして動作するため、複数回インスタンス化できます。作成する複合クラスは、組み込みのCompositeクラスから継承されます。次の例では、独自の複合クラスを作成する方法を示します。

例えば、ローパスフィルタButterworthFilterという複合クラスを作成するとします。リンク先ではButterworthフィルタについて詳しく説明していますが、ここでは簡単に説明します。ローパスフィルタを使用すると、低周波信号を通過させることができます。このフィルタは高周波信号を減衰させます。カットオフ周波数によって、減衰が始まる周波数が定義されます。周波数が高くなるほど、減衰の度合が高まります。Butterworthフィルタには次数があります。次数が大きいほど、カットオフが急激になります。

ButterworthFilterクラスは、次に示すようにCompositeクラスから継承されます。
from msolve import * 

class ButterworthFilter (Composite):
このクラス内で、必要なプロパティを割り当てることができます。このインスタンス内で、1 ≤ order ≤ 4という範囲のorder(デフォルト値は3)を持つフィルタを作成します。当然ながら、処理対象となる入力信号と結果を戻すための出力信号も必要です。このため、プロパティを持つこのクラスは次のようになります:
from msolve import * 

class ButterworthFilter (Composite):
inputSignal = Function()
outputSignal = Function ()
order = Int (3)

2つの特別なメソッドcreateChildrenおよびupdateChildrenを記述して、このクラスの挙動を定義する必要があります。オプションとして検証メソッドvalidateSelfを記述することもできます。

  • createChildren: その複合クラスで必要なすべてのMotionSolveエンティティを作成するために使用されます。複合クラスオブジェクトが作成されると、createChildrenメソッドが呼び出されます。この呼び出しは、複合オブジェクトの作成時に一度だけ行われます。createChildren内で、さまざまなMotionSolveオブジェクトをインスタンス化し、これらのオブジェクトの不変のプロパティを定義します。
  • updateChildren: このクラスのすべての変更可能なプロパティを定義するために使用されます。例えば、Butterworthフィルタの係数はそのフィルタの次数に依存します。同様に、処理対象の入力信号を変更できます。updateChildrenメソッドは、フィルタの次数と入力信号を定義します。これらをcreateChildrenメソッドで定義することもできますが、これはオプションです。
  • validateSelf: 入力をチェックして、ButterworthFilterオブジェクトに渡されたデータが物理的に意味があることを確認します。
Butterworthフィルタの例では、これら3つのメソッドは次のようになります:
from msolve import * 

class ButterworthFilter (Composite):
 """The Butterworth filter is a type of signal processing filter designed to have 
 as flat a frequency response as possible in the passband. It is also referred 
 to as a maximally flat magnitude filter and it is generally used to eliminate 
 high frequency content from an input signal.
 """
inputSignal = Function ()
outputSignal = Function ()
order = Int (3)

 #- topology ----------------------------------------------------------------

def createChildren (self):
 """This method is called once when the object is created. It is used to
 define all the 'immutable' properties of the class. The Butterworth
 filter consists of the following:
 * A string containing the function expression defining the input signal
 * A VARIABLE that defines the input signal
 * A TFSISO that captures the input to output relationship
 * An ARRAY of type 'X' to hold the internal states of the TFSISO
 * An ARRAY of type 'Y' to hold the output from TFSISO
 * An ARRAY of type 'U' to hold the input signal to the TFSISO
 * A string that defines the function expression to access the output
 """
self.var = Variable (function=self.inputSignal)
x = Array (type="X")
y = Array (type="Y")
u = Array (type="U", size=1, variables=[self.var])
num = [1]
den = [1, 2, 2, 1]
self.tf = Tfsiso (label="Butterworth Filter", x=x, y=y, u=u, numerator=num,
denominator=den)
self.outputSignal = "ARYVAL({id},1)".format(id=y.id)
#- Update Children -----------------------------------------------------------
def updateChildren (self):
 """This is called when the property value changes to propagate the change
 to the child objects
 """
self.var.function = self.inputSignal
if self.order == 1:
self.tf.denominator = [1, 1]
elif self.order == 2:
self.tf.denominator = [1, math.sqrt(2), 1]
elif self.order == 3:
self.tf.denominator = [1, 2, 2, 1]
elif self.order == 4:
self.tf.denominator = [1, 2.6131, 3.4142, 2.6131, 1]
 #- validate ----------------------------------------------------------------
def validateSelf (self, validator):
validator.checkGe ("order", 1)
validator.checkLe ("order", 4)

ButterWorth複合クラスを作成したら、これを任意のモデルで“アトミック”オブジェクトとして使用できます。以下の例は、全体座標系のy方向に振動する非線形マススプリングダンパです。重力は負のy方向に作用しています。T=1秒においてy方向に100Nの衝撃力が加えられます。この衝撃は11秒に終了します。

ここでの目標は、質量のCMの速度をButterworthフィルタに渡し、フィルタリングされた信号を出力として取得することです。order=1とorder=2におけるButterworthフィルタの応答が比較されます。
###############################################################################
## The Model ################################################################
###############################################################################
def butterworthFilter ():

m = Model ()
Units (mass="KILOGRAM",length="MILLIMETER", time="SECOND", force="NEWTON")
Accgrav (jgrav=-9800)
Integrator (error=1e-7)
ground = Part (ground=True)
                            
 # Mass
block = Part (mass=1.4702,ip=[44144.717,44144.717,73.5132,0,0,0])
block.cm = Marker (part=block, qp=[0,10,0], zp=[0,11,0])
 # Translational joint
jjm = Marker (part=ground, qp=[0,0,0], zp=[0,1,0])
jnt = Joint (type="TRANSLATIONAL", i=block.cm, j=jjm)
 # Nonlinear Spring-damper
fexp = "-10*(DY({i},{j})-10)**3  -1e-3*VR({i},{j})".format(i=block.cm.id, j=jjm.id)
nlspdp = Sforce (type="TRANSLATION", i=block.cm, j=jjm, function=fexp)

 # Impulse force
iforce = "STEP(TIME, 1, 0, 1.04, 10) *STEP(TIME, 1.05, 10, 1.1, 0)"
sfim = Marker (part=block, qp=[0,10,0], zp=[0,11,0])
impulse = Sforce (type="TRANSLATION", actiononly=True, i=sfim, j=jjm, function=iforce)

 # Filter
inputSignal = "VY({i}, {j})".format(i=block.cm.id, j=jjm.id)
m.filt = ButterworthFilter (inputSignal=inputSignal, order=1)

 # Requests
m.r1 = Request (type="DISPLACEMENT", i=block.cm, j=jjm)
m.r2 = Request (type="VELOCITY", i=block.cm, j=jjm)
m.r3 = Request (type="ACCELERATION", i=block.cm, j=jjm)
m.r4 = Request (type="FORCE", i=block.cm, j=jjm)
m.r5 = Request (f2=m.filt.inputSignal, f3=m.filt.outputSignal)
m.r6 = Request (type="FORCE", i=sfim, j=jjm)
return m
###############################################################################
## Entry Point ################################################################
###############################################################################
if __name__ == "__main__":
m = butterworthFilter ("butterworth-1")
m.simulate (type="DYNAMICS", end=5, dtout=0.005)
m.reset()
m = butterworthFilter ("butterworth-2")
m.filt.order = 2
m.simulate (type="DYNAMICS", end=5, dtout=.005)
m.reset()
重力の作用のために、質量は下降します。システム内ではわずかに減衰するため、システムは全体座標系のy方向に振動し始めて、振動幅は徐々に小さくなります。Time=1秒に、衝撃力によってシステムに急激な振動が生じます。システムはこの振動に応答して、再び落ち着き始めます。次の図は、このモデルの挙動を示しています。


図 1.
左側のプロットは、時間の関数としてのブロック速度を示しています。これは伝達関数への入力です。右側のプロットは、次数1と次数2のButterworthフィルタの応答を示しています。2次フィルタによって高周波成分が除去されている様子に注目してください。


図 2.

DocString

Pythonに組み込まれている強力なヘルプ機能では、モジュール、クラス、および関数にドキュメントを関連付けることができます。オブジェクト定義の先頭ステートメントとして文字列定数を追加することにより、このようなオブジェクトをドキュメントできます。docstringでは、その関数やクラスの役割を記述します。そうすることで、ユーザーやプログラマーは、クラス、関数、またはオブジェクトのインスタンスに対するヘルプ機能を呼び出すことによって、実行時にオブジェクトのクラスやメソッドについて調べることができます。

例えば、上記の例で定義されたButterworthクラスについて調べるには、次のように入力します:
help (ButterworthFilter)
モジュールが現在のPython名前空間に正常にインポートされている場合は、上記のコマンドを実行すると、ButterworthFilterクラスに関連付けられたdocstringの内容がPythonインタープリタによって出力されます。一般に、Python内のすべてのオブジェクト、メソッド、およびモジュールのdocstringを追加することが推奨されます。次のスニペットは、モジュール、クラス、メソッド、および関数のdocstringを定義する方法を示しています。
"""
Assuming this is file mymodule.py, then this string, being the
first statement in the file, will become the "mymodule" module's
docstring when the file is imported.
"""
class MyClass(object):
   """The class's docstring"""
   def my_method(self):
       """The method's docstring"""
def my_function():
   """The function's docstring"""

摩擦のモデリング例

摩擦は、多くの工学システムで発生する自然現象です。摩擦モデルには、非連続性、ヒステリシス、内部力学、クーロン摩擦などのさまざまな非線形特性が含まれています。これらの特性により、摩擦モデルは数値的に硬くなり、複雑な計算が必要となります。LuGre (Lundt-Grenoble)摩擦モデルは、文献でよく知られています。このモデルは、いくつかの異なる効果を表すことができます:
  • 動摩擦
  • 静摩擦またはクーロン摩擦
  • 速度依存の摩擦現象(変化する最大静止摩擦力や摩擦遅延など)
このモデルは、以下の入力パラメータについて定義されます:
  • VS= 静摩擦から動摩擦への遷移速度
  • MUS = 静摩擦係数
  • MUD = 動摩擦係数
  • K0 = ブリッスル剛性
  • K1 = ブリッスル減衰
  • K2 = 粘性係数
この摩擦モデルは次のように定式化されます:
  • V = VZ (I, J, J, J)
## ジョイント内の速度
  • P = -(v/VS)**2
## ストライベック係数
  • N = sqrt (FX(I, J, J)**2 + FY(I,J,J)**2)
## 垂直抗力
  • Fc = Mud*N
## クーロン摩擦
  • Fs = Mus*N
## 静摩擦
  • G = (Fc + (Fs-Fc)*eP)/K0
 
  • z ˙ = v - |v|*z / G
## ブリッスルたわみzを定義するODE
  • F = -(K0*z + K1*zdot + K2*v)
## 摩擦力
LuGre摩擦力は次の2つのアトミック要素で構成されています:
  • A DIFF defining the bristle deflection
  • A FORCE defining the friction element
この複合クラスは次のように定義できます。
from msolve import * 
################################################################################
## LuGre Friction Element######################################################
################################################################################
class LuGre (Composite):
"""
 Create a friction force on a translational joint using the LuGre friction model.
                            
 The LuGre friction force consists of 4 atomic elements:
 1. A DIFF defining the bristle deflection
 2. A MARKER defining the point of action of the friction force
 3. A FORCE defining the friction element
 4. A REQUEST capturing the friction force on the block
 """
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)
 #- topology----------------------------------------------------------------
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")
 #- Update Children -----------------------------------------------------------
def updateChildren (self):
 """This is called when the property value changes to propagate the change
 to the child objects
"""
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)
 #- validate ----------------------------------------------------------------
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 = tr("Mu dynamic({0}) must be <= Mu static ({1})",
         self.mud, self.mus)
         validator.sendError(msg)
 #- Lugre Diff ------------------------------------------------------------
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
if iflag or math.fabs(g) <1e-8:
return v
else:
return v - math.fabs(v) * z / g
 #- friction_force -----------------------------------------------------------
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
return -F