20191007

断面二次モーメントを、座標点の配列から計算する(2)断面二次モーメント編

断面二次モーメント

やろうとしている事

断面二次モーメントを数値計算的に算出しようとしている。(先々月の続き。)

計算の仕方は二つ

  1. 長さ寸法と、公式に当てはめて計算をする。

     公式の公式集。書籍よりDVD版の方が明らかにお得だが高い。

    • メリット
      • 正確な値
    • ディメリット
      • 形状によっては式が用意されていない。
  2. 座標値を使い、数値計算的に求める

  3. メリット
    • 断面形状にかかわらず、座標値さえあれば計算可能。
  4. ディメリット
    • 点の間隔、カーブ、数値積分の手法などで誤差が生じる。
    • 座標値を準備しなければならない。

1の方法が普通のやり方。できるなら1をやるべき。

断面二次モーメントの公式

$$\int _{\rm A}y^2\rm\ dA$$

上は、断面二次モーメントの式。

下は、8×8の正方形の断面二次モーメントを計算する際の積分のイメージ。

20190918130448_2.png

数値計算で断面二次モーメントを求めるための、計算手順

断面二次モーメントまでの道のり
  1. 座標値を手に入れる

  2.  - 断面一次モーメントを計算する。

    • 断面一次モーメントから図心を計算する。
    • 図心が原点となるように座標値を整える。(省略)
  3. 断面二次モーメントを計算する。

1. 座標値を手に入れる 四角形のx,y座標を得る関数
def coorsquare(r=4, center=[0,0], n=10, closed=True):
rx1=-r/2.0+center[0]
rx2=r/2.0+center[0]
ry1=-r/2.0+center[1]
ry2=r/2.0+center[1]

x = [rx1 + i*(rx2-rx1)/float(n) for i in range(n)]
y = [ry2 for i in range(n)]
#1
x = x + [rx2 for i in range(n)]
y = y + [ry2 - i*(ry2-ry1)/float(n) for i in range(n)]
#2
x = x + [rx2 - i*(rx2-rx1)/float(n) for i in range(n)]
y = y + [ry1 for i in range(n)]
#3
x = x + [rx1 for i in range(n)]
y = y + [ry1 + i*(ry2-ry1)/float(n) for i in range(n)]
#4
if closed:
x = x + [rx1]
y = y + [ry2]

return x, y


x,y = coorsquare(r=5, center=[0,0], n=8, closed=True)
上の関数を使って作ったx,yを作図。
import matplotlib.pyplot as plt

fig,ax1 = plt.subplots(1)
ax1.plot(x,y)
ax1.scatter(x,y)

for i in range(len(x)):
ax1.text(x[i],y[i],"%s"%(i))

ax1.set_xlim(-4,4)
ax1.set_ylim(-4,4)
ax1.set_aspect("equal")

plt.show()

shikaku.png

2. 面積を計算→断面一次モーメント→図心を計算。
def area(x, y):
A = sum([(y[i]+y[i+1])/2.0*(x[i+1]-x[i]) for i in range(len(x)-1)])
return A

x,y = coorsquare(r=5, center=[0,0], n=20, closed=True)
print(area(x,y))

def FirstInrt(x, y):
FIy = sum([(y[i]+y[i+1])**2/8.0*(x[i+1]-x[i]) for i in range(len(x)-1)])
FIx = sum([(x[i]+x[i+1])**2/8.0*(y[i]-y[i+1]) for i in range(len(x)-1)])
return FIx, FIy

print(FirstInrt(x, y))


def CntCS(x, y):
A = area(x, y)
FIx, FIy = FirstInrt(x, y)
Cntx, Cnty = FIx/A, FIy/A
return Cntx, Cnty

print("center(%s,%s)"%(CntCS(x, y)))
3. 断面二次モーメントの計算(シンプソン則)

二次の式なので、台形則でなくシンプソン則で計算した。この関数が、この文章の一番重要な所

def simpson(x,y):#sekibun_1/3simpson(3point)
Cntx, Cnty = CntCS(x, y)
x = np.array(x)-Cntx
y = np.array(y)-Cnty
SecondInrty,SecondInrtx = 0,0

x1 = np.array(x)**3/3.0
y1 = np.array(y)**3/3.0

for i in range(len(x))[::2][:-1]:
a1 = float(x[i+1]-x[i])
# if は、0割対策。
if a1 !=0.0:
a1 = y1[i] * (x[i+2]-x[i])*(-x[i+2]-2.0*x[i]+3.0*x[i+1])/a1/6.0
a2 = float((x[i+1]-x[i])*(x[i+2]-x[i+1]))
if a2 !=0.0:
a2 = y1[i+1] * (x[i+2]-x[i])**3/a2/6.0
a3 = float(x[i+2]-x[i+1])
if a3 !=0.0:
a3 = y1[i+2] * (x[i+2]-x[i])*(2.0*x[i+2]+x[i]-3.0*x[i+1])/a3/6.0

SecondInrty = SecondInrty + a1 + a2 + a3

b1 = y[i+1]-y[i]
if b1 !=0:
b1 = x1[i]*(y[i+2]-y[i])*(-y[i+2]-2*y[i]+3*y[i+1])/b1/6.0
b2 = (y[i+1]-y[i])*(y[i+2]-y[i+1])
if b2 !=0:
b2 = x1[i+1]*(y[i+2]-y[i])**3/6.0/b2
b3 = y[i+2]-y[i+1]
if b3 !=0:
b3 = x1[i+2]*(y[i+2]-y[i])*(2*y[i+2]+y[i]-3*y[i+1])/b3/6.0

SecondInrtx = SecondInrtx - (b1 + b2 + b3)

return SecondInrtx, SecondInrty


# 断面が中心にない場合でも、図心を原点に持って行った時の値になるようにした。
x,y = coorsquare(r=5, center=[7,7], n=8, closed=True)
print("1/3simpson(3point)%s,%s"(simpson(x,y)))

# nは、偶数にしないと、正しく計算されない。
#うまくいかない。
x,y = coorsquare(r=5, center=[0,0], n=7, closed=True)
print("1/3simpson(3point)%s,%s"%(simpson(x,y)))

# 全体で偶数になっていても、一辺が奇数ではだめ。
#うまくいかない
x,y = coorsquare(r=5, center=[0,0], n=8, closed=True)
x.insert(-9,(-2.5-1.875)/2.0)
y.insert(-9,-2.5)
y.insert(-1,(2.5+1.875)/2.0)
x.insert(-1,-2.5)
print("1/3simpson(3point)%s,%s"%(simpson(x,y)))

# nが偶数なら、不等間隔でも計算される。
x,y = coorsquare(r=5, center=[0,0], n=8, closed=True)
x.insert(-9,(-2.5-1.875)/2.0)
y.insert(-9,-2.5)
x.insert(-9,-2.4)
y.insert(-9,-2.5)
print("1/3simpson(3point)%s,%s"%(simpson(x,y)))

for ループは、2個飛ばしで行う。

3点使って三個飛ばしだと、間が空く。

3点使って二個飛ばしだと、間が埋まる。

ごめん、うまく書けない。こういう事。

figure_1.png

ってなっちゃうから、3個飛ばしにすればいいわけじゃない。

3個使うけど、最後の一個は次のループでも使うから、二個飛ばしが正解。
(ちなみに、pythonで二個飛ばしの書き方は、[::2]。)
こういうindexingの所で、いつも試行錯誤してる。頭だけで出来ないとだめだろうか?

figure_1-2.png

前のループでi+2だったところを、次のループではiとして扱っている。

積分は不等間隔のシンプソン則で模擬。不等間隔のシンプソン則は、ココを参考にして作った。

hutou_kankaku.png

一辺のサンプルが奇数ならば正しく計算できる。

ひし形(45度回転。一部不等間隔。)
x,y = coorsquare(r=5, center=[0,0], n=8, closed=True)
x.insert(-9,(-2.5-1.875)/2.0)
y.insert(-9,-2.5)x.insert(-9,-2.4)
y.insert(-9,-2.5)
xy= np.dot(np.array([x,y]).T, [[np.cos(1/4.0*np.pi), np.sin(1/4.0*np.pi)],[-np.sin(1/4.0*np.pi), np.cos(1/4.0*np.pi)]]).T
x=xy[0]
y=xy[1]

print("1/3simpson(3point)%s,%s"%(simpson(x,y)))

hishi_gata.png

前回は、これが正しく計算できなかったが、今回は不等間隔シンプソン則のおかげで計算できた。
(サンプル数の偶奇に気を付けなければならないが)

穴あき(正方形)
x1,y1 = coorsquare(r=5, center=[0,0], n=8, closed=True)
x2,y2 = coorsquare(r=3, center=[0,0], n=4, closed=True)
x2=x2[::-1]
y2=y2[::-1]
Ix1,Iy1 = simpson(x1,y1)
Ix2,Iy2 = simpson(x2,y2)
a = Ix1+Ix2
b = Iy1+Iy2
print("1/3simpson(3point)%s,%s"%(a,b))
import matplotlib.pyplot as plt

fig,ax1 = plt.subplots(1)
ax1.plot(x1,y1)
ax1.plot(x2,y2)
ax1.scatter(x1,y1)
ax1.scatter(x2,y2)
for i in range(len(x1)):
ax1.text(x1[i],y1[i],"%s"%(i))
if i<len(x2):
ax1.text(x2[i],y2[i],"%s"%(i))

ax1.set_xlim(-3,3)
ax1.set_ylim(-3,3)
ax1.set_aspect("equal")

plt.show()

ana_aki.png

緑色の部分が反時計回りになっている所が重要。反時計回りはdx(差分)がマイナスになるので、負の面積というか負の断面二次モーメントが出てくる。

円形
N=21
x = np.cos(np.linspace(0, 2.0*np.pi, N))
y = np.sin(np.linspace(0, 2.0*np.pi, N))
print("1/3simpson(3point)%s,%s"%(simpson(x,y)))

maru.png

円は、理論値からの誤差がまだある。どのくらいサンプル間隔を細かくすればいいのか考えるために、下で誤差などを確認した。

円の理論値

$$\frac{d^{4}\pi}{64} \\ 半径r=1なので直径d=2\\ \frac{16\pi}{64}=0.25\pi \\ =‭0.78539816339744830961566084581988 \\ (電卓でπ=3.1415926535897932384626433832795‬を使い計算)‬$$

円周率にはnp.pi 3.141592653589793(16桁)を使うのでそれが原因の誤差が出るが、確認しない。

va = 0.25 * np.pi
Ix = []
Iy = []
Acx = []
Acy = []
Erx = []
Ery = []

for N in np.array(np.logspace(1,4,10),"int"):
#for N in np.array(np.logspace(1,4,10),"int")*4+1:
x = np.cos(np.linspace(0, 2.0*np.pi, N))
y = -np.sin(np.linspace(0, 2.0*np.pi, N))#Clockwise

I = simpson(x,y)
Ix.append(I[0])
Iy.append(I[1])

Acx.append(Ix[-1]/va)
Acy.append(Iy[-1]/va)

Erx.append(1 - Acx[-1])
Ery.append(1 - Acy[-1])
print("%s-points,\tIx:%s,\tIy:%s,\tAcx:%s,\tAcy:%s,\tErx:%s,\tEry:%s,\t"%(N,Ix[-1],Iy[-1],Acx[-1],Acy[-1],Erx[-1],Ery[-1]))
import matplotlib.pyplot as plt

x = np.array(np.logspace(1,4,10),"int")#*4+1
fig,(ax1,ax2) = plt.subplots(2,1)
ax1.plot(x,[va for i in range(len(x))])
ax1.scatter(x, Acy)

ax2.plot(x,[1 for i in range(len(x))])
ax2.scatter(x, Ery)

ax1.set_ylim(0.99,1)
ax2.set_ylim(0.00000000000001, 0.1)

ax1.set_xscale("log")
ax2.set_xscale("log")
ax1.set_yscale("log")
ax2.set_yscale("log")

plt.show()

figure_1 (3).png

上が精度で1に近づけばいい値。理論値で割って計算した。y軸はなんか知らんがすっ飛んだ。0.99~1.0間。

下が誤差で、0に近づけばいい値。1-精度で計算。両方とも対数グラフ。

サンプル数によっては、うまく計算できないのがある。実行して、printの結果を確認すればわかるけど、10の時に断面二次モーメントに異常な値が出る。dxの±が入れ替わる所か、yの±が入れ替わる所で起こっているのではないかと思っているが、確認は次回以降にする。とにかく、サンプル数に気を遣わずにシンプソン則を実行できなければならない。あと、欲を言えば、excelで出来れば言うことない。有料ソフトなのに、どこに行っても必ずあるし。このままでは、ベジェ曲線で自由に描いた線の積分ができない。円形で起こった現象は、ひし形でも出てきそう。正方形で出てこなかったのは、dxがぴったし0だから。

実用上は、40個位の分割でも円の断面二次モーメントを10-4の誤差で計算できる事が知れてよかった。

ちなみに、データ点数を4n+1(nは整数)にすれば、異常な値は出てこない。

現在の課題

  • ひし形、丸形などで、誤差が大きくなる。
  • 任意形状(ひらがなの「あ」とか)でやってない。

次回以降の目標

- 【断面二次】 シンプソン則のサンプル数を気にせず実行。

  • 【断面二次】Excelでやる。

  • 一覧表を作成し、誤差等をチェック
  • 【three.js】 pdbを表示する

絶対やるというわけではない。

posted by yuchan at 08:00 | Comment(51) | python

20191014

three.jsのpdb_loaderを使って、カーボンナノチューブを表示した。

やったこと

three.jsを使って、pdbファイルのカーボンナノチューブを表示した。
カーボンナノチューブのpdbファイルは、vmdというソフトを使って書き出した。

参考図書に、「はじめてのThree.js」を使ったけれど、pdb_loaderがうまく動かなかった。
githubのサンプル通りに、書いたのに(もちろん一部改変しなければいThree.jsのアドレスとかの所は改変した)表示されなかった。

どうも、この部分、https://github.com/josdirksen/learning-threejs/blob/85f85aeb137f0b01ba96c8b1bfd7019134e596ef/chapter-08/12-load-pdb.html#L88-L111
が動いてくれていない。
コードの中で、geometry.vertices.forEachとか書いてあるけど、「geometry以下にverticesがない」様な事を示唆するエラーが出てくる。(undefinedがどうとか)

ググったら
(https://threejs.org/docs/#examples/en/loaders/PDBLoader)
が関係ありそうだったので見た。

公式refereceではコールバック関数が一つ。
対する「はじめてのThree.js」では二つ。

書籍だから、たぶん情報が古い。昔はこれで動いたんだろう。

コールバック関数を一つにして再度チャレンジ。
この関数から、

  • コールバック関数名.geometryAtoms
  • コールバック関数名.geometryBonds
  • コールバック関数名.json

という感じの3つのデータが取り出せる。
しかし、jsonに原子と結合手のデータが全部入っていたので、そっちを使った。


変更前

  • 原子を元素名ごとに色分けしてsphereで表示してた
  • 結合手をsylinderで表示してた

変更後

  • 原子はすべて単色、
  • 結合手は、lineで書く。

L88-L111を、下記のように書き換えたら大体うまく動いた。
動くという意味では改善だが、原子の大きさや色などを反映しなくなった点で改悪。
var loader = new THREE.PDBLoader();
var group = new THREE.Object3D();
var json=loader.load("http://adoresu-wo-ireru.sakura.ne.jp/sblo_files/anatano-no-saito-no-namae/tekitouna_foruda_demo_tukure/nanotube.pdb", function (geometry) {
var json = geometry.json;
for (var j = 0; j < json.atoms.length; j += 1) {
var sphere = new THREE.SphereGeometry(0.1,8,8);
var material = new THREE.MeshBasicMaterial( {color: 0xffff00} );
var mesh = new THREE.Mesh(sphere, material);
mesh.position.set(json.atoms[j][0], json.atoms[j][1], json.atoms[j][2]);
group.add(mesh);
};
for (var j = 0; j < json.bonds.length; j += 1) {
var material2 = new THREE.MeshBasicMaterial( {color: 0x0000ff} );
eval("var ln" + j + "= new THREE.Geometry();");
// console.log(json.bonds.length)
ii=0;
json.bonds[j].forEach(function(element) {
if (ii != 2) {
var aaa = element
eval("ln" + j + ".vertices.push(new THREE.Vector3(json.atoms[aaa][0], json.atoms[aaa][1], json.atoms[aaa][2]));");
// console.log(aaa);
ii+=1;
};
var material4 = new THREE.LineBasicMaterial({color: 0x0000ff});
eval("var line" + j + "= new THREE.Line(ln" + j + ", material4 );");
eval("group.add( line" + j + " );");
});
};
scene.add(group);
});

結果はこちら

pdb_loader_test.png

上の画面の様なCNTがクルクル回るのが、自分のインターネットブラウザの環境からは見える。

ネット上で3Dが動かせている。その内インタラクティブにもできるようになるだろう。

最終目標は、あくまでも「web、地震シミュ、VR」インタラクティブは、視点の移動位でいいかも。

posted by yuchan at 08:00 | Comment(1) | javascript