はじめに
前回から本格的に4角形1次要素の平面応力状態における静弾性問題の有限要素解析の実装を行い始めました。今回はその続きで有限要素法の肝となる全体剛性マトリクスの導出まで行いたいと思います。
実装の流れ
前回のおさらいで実装の流れは下記のとおりです。
- Dマトリクスの算出
- Bマトリクスの算出
- 要素剛性マトリクスの算出
- 全体剛性マトリクスの算出
- 連立方程式を解き、全節点の変位を求める
このうち、Bマトリクスの導出まで前回までで完了しました。
今回はその次の要素剛性マトリクスの導出からやっていきましょう
実装
以前の記事で作成したコードと、クラス要件をベースに実装を進めてます。
基本的な計算はすべてSolverクラスが実行するようなことが書かれてたので今回もそっちに実行するようにします。
まずは過去記事のおさらいからです。全コード載せときます。
import numpy as np import matplotlib.pyplot as plt from matplotlib import patches class Node: """ Nodeのインデックス、座標および境界条件をまとめておくクラス(2次元用) Attributes: id (int): Nodeのインデックス番号(重複不可) x, y (float): Nodeのx, y座標 """ def __init__(self, idx, x, y): self.id = idx self.x = x self.y = y def __str__(self): return 'Node {}: x {}, y {}'.format(self.id, self.x, self.y) class Element: """ 4角形1次要素Elementのインデックス、要素内のNode情報および要素の厚みをまとめておくクラス Attributes: id (int): Elementのインデックス番号(重複不可) node ([int, int, int, int]): 要素内のNodeのインデックスのリスト(左下から反時計回りで指定) thickness (float): Elementの厚み xy (ndarray(size: 2, 4)): Element内のNodeのxy座標まとめ """ def __init__(self, idx, node_idxs, thickness): self.id = idx self.node = node_idxs self.thickness = thickness self.xy = None def __str__(self): return 'Element {}: Nodes {}, Thickness: {}'.format(self.id, self.node, self.thickness) def get_coordination(self, nodes_dict): """ 要素の各節点のx,y座標を求めてself.xyに代入。 Args: nodes_dict ({Node.id: Node}): Nodeの辞書 """ res = [] for node_idx in self.node: res.append([nodes_dict[node_idx].x, nodes_dict[node_idx].y]) self.xy = np.array(res) class Mesh: """ NodeオブジェクトとElementオブジェクトをまとめておくクラス Attributes: id (int): メッシュのID(重複不可) nodes ({Node.id: Node}): Nodeの辞書 elements (list): Elementのリスト """ def __init__(self, idx, nodes_dict, elements): self.id = idx self.nodes = nodes_dict self.elements = elements self.get_element_coord() def get_element_coord(self): for elm in self.elements: elm.get_coordination(self.nodes) class Material: """ 材料特性をまとめておくクラス Attributes: E (float): ヤング率 nu (float): ポアソン比 """ def __init__(self, E, nu): self.E = E self.nu = nu class Solver: """ 有限要素法の計算を行うクラス Attributes: mesh (Mesh): Meshクラス material (Material): Materialクラス D (array 3x3): Dマトリクス """ def __init__(self, mesh, material): self.mesh = mesh self.material = material self.D = self.calc_D() def calc_D(self): """ Dマトリクスを算出。 Returns: D (array 3x3): Dマトリクス """ D = np.array([[1, self.material.nu, 0], [self.material.nu, 1, 0], [0, 0, (1 - self.material.nu) / 2]]) \ * self.material.E / (1 - self.material.nu ** 2) return D def calc_B(self, elem, xi, eta): """ Bマトリクス及びヤコビアンを算出。 Args: elem (Element): 対象のElementクラス xi, eta (float): Bマトリクス算出用の座標 Returns: B (array 3x8): Bマトリクス J (array 2x2): ヤコビアン """ dndxi = np.array([- 1 + eta, 1 - eta, 1 + eta, - 1 - eta]) / 4 dndeta = np.array([- 1 + xi, - 1 - xi, 1 + xi, 1 - xi]) / 4 x = elem.xy[:, 0] y = elem.xy[:, 1] dxdxi = np.dot(dndxi, x) dydxi = np.dot(dndxi, y) dxdeta = np.dot(dndeta, x) dydeta = np.dot(dndeta, y) J = np.array([[dxdxi, dydxi], [dxdeta, dydeta]]) B = np.zeros((3, 8)) for i in range(4): Bi = np.dot(np.linalg.inv(J), np.array([dndxi[i], dndeta[i]]).T) B[0, 2 * i] = Bi[0] B[2, 2 * i + 1] = Bi[0] B[2, 2 * i] = Bi[1] B[1, 2 * i + 1] = Bi[1] return B, J MESH_SIZE = 5. LENGTH = 20. HEIGHT = 10. THICKNESS = 1. E = 208000 # ヤング率 NU = 0.27 # ポアソン比 nodes_dict, elems = create_mesh(LENGTH, HEIGHT, MESH_SIZE, THICKNESS) material = Material(E, NU) mesh = Mesh(0, nodes_dict, elems, material) solver = Solver(mesh, material) B, J = solver.calc_B(solver.mesh.elements[0], -1, -1) # xi, etaはとりあえず-1で算出 print(B) print(J)
要素剛性マトリクスの算出
要素剛性マトリクスの導出はそこまで難しくなくて、以下の式を要素ごとに計算するだけです。
そのうえでポイントとなるのはや算出におけるやをどう決めるか?というところになると思います。
上の式はガウスルジャンドル積分と呼ばれるもので積分を近似的に解く手法になります。
簡単に言うと、要素全域で積分をしたいけど計算が大変なので、ガウス点と呼ばれる要素内の数点における値を計算して、それを重みづけして足し合わせることで要素全域の積分近似ができるという計算方法です。
基本的にどの点をガウス点とするか?やその重みは決められています。
Wikiによると以下の通りです
4角形1次のアイソパラメトリック要素の場合は以下のような点になります。
今回はよく使われるn=2のガウス点で実装しましょう。この時の重みはすべて1です。
ガウス点は以下の4点であり、その時のをの式に代入すればOKです。
class Solver: """ 有限要素法の計算を行うクラス Attributes: mesh (Mesh): Meshクラス material (Material): Materialクラス D (array 3x3): Dマトリクス """ def __init__(self, mesh, material): self.mesh = mesh self.material = material self.D = self.calc_D() def calc_D(self): """ Dマトリクスを算出。 Returns: D (array 3x3): Dマトリクス """ D = np.array([[1, self.material.nu, 0], [self.material.nu, 1, 0], [0, 0, (1 - self.material.nu) / 2]]) \ * self.material.E / (1 - self.material.nu ** 2) return D def calc_B(self, elem, xi, eta): """ Bマトリクス及びヤコビアンを算出。 Args: elem (Element): 対象のElementクラス xi, eta (float): Bマトリクス算出用の座標 Returns: B (array 3x8): Bマトリクス J (array 2x2): ヤコビアン """ dndxi = np.array([- 1 + eta, 1 - eta, 1 + eta, - 1 - eta]) / 4 dndeta = np.array([- 1 + xi, - 1 - xi, 1 + xi, 1 - xi]) / 4 x = elem.xy[:, 0] y = elem.xy[:, 1] dxdxi = np.dot(dndxi, x) dydxi = np.dot(dndxi, y) dxdeta = np.dot(dndeta, x) dydeta = np.dot(dndeta, y) J = np.array([[dxdxi, dydxi], [dxdeta, dydeta]]) B = np.zeros((3, 8)) for i in range(4): Bi = np.dot(np.linalg.inv(J), np.array([dndxi[i], dndeta[i]]).T) B[0, 2 * i] = Bi[0] B[2, 2 * i + 1] = Bi[0] B[2, 2 * i] = Bi[1] B[1, 2 * i + 1] = Bi[1] return B, J def calc_Ke(self, elem): """ Keマトリクスを算出。 Args: elem (Element): 対象のElementクラス Returns: Ke (array 8x8): 要素剛性マトリクス """ gps = ((-1 / np.sqrt(3), -1 / np.sqrt(3), 1, 1), (1 / np.sqrt(3), -1 / np.sqrt(3), 1, 1), (1 / np.sqrt(3), 1 / np.sqrt(3), 1, 1), (-1 / np.sqrt(3), 1 / np.sqrt(3), 1, 1), ) Ke = np.zeros((8, 8)) for xi, eta, wi, wj in gps: B, J = self.calc_B(elem, xi, eta) Ke += wi * wj * np.dot(B.T, np.dot(self.D, B)) * np.linalg.det(J) * elem.thickness return Ke
結果は8x8の行列になります。
全体剛性マトリクスの算出
要素剛性マトリクスを求められたので次は全体剛性マトリクスを算出します。最終的に求めたい式は下記の形です。
ここでは全節点の荷重をまとめたベクトル、は全体剛性マトリクス、は全節点の変位をまとめたベクトルです。
以下に示す要素ごとのつり合いの式を全要素分まとめて連立させた形になっております。
簡単のために以下のようなメッシュを考えます。
ノードの変位をとすると、要素1のつり合いの式は下記のとおりです。
要素2のつり合いの式は下記のとおり。
この2つの式を連立すると以下の形になり、全体剛性マトリクスは次のように計算できます。
実際にソルバークラスの中にcalc_Kという関数を作って全体剛性マトリクスを求める機能を実装してみます。
class Solver: """ 有限要素法の計算を行うクラス Attributes: mesh (Mesh): Meshクラス material (Material): Materialクラス D (array 3x3): Dマトリクス """ def __init__(self, mesh, material): self.mesh = mesh self.material = material self.D = self.calc_D() def calc_D(self): """ Dマトリクスを算出。 Returns: D (array 3x3): Dマトリクス """ D = np.array([[1, self.material.nu, 0], [self.material.nu, 1, 0], [0, 0, (1 - self.material.nu) / 2]]) \ * self.material.E / (1 - self.material.nu ** 2) return D def calc_B(self, elem, xi, eta): """ Bマトリクス及びヤコビアンを算出。 Args: elem (Element): 対象のElementクラス xi, eta (float): Bマトリクス算出用の座標 Returns: B (array 3x8): Bマトリクス J (array 2x2): ヤコビアン """ dndxi = np.array([- 1 + eta, 1 - eta, 1 + eta, - 1 - eta]) / 4 dndeta = np.array([- 1 + xi, - 1 - xi, 1 + xi, 1 - xi]) / 4 x = elem.xy[:, 0] y = elem.xy[:, 1] dxdxi = np.dot(dndxi, x) dydxi = np.dot(dndxi, y) dxdeta = np.dot(dndeta, x) dydeta = np.dot(dndeta, y) J = np.array([[dxdxi, dydxi], [dxdeta, dydeta]]) B = np.zeros((3, 8)) for i in range(4): Bi = np.dot(np.linalg.inv(J), np.array([dndxi[i], dndeta[i]]).T) B[0, 2 * i] = Bi[0] B[2, 2 * i + 1] = Bi[0] B[2, 2 * i] = Bi[1] B[1, 2 * i + 1] = Bi[1] return B, J def calc_Ke(self, elem): """ Keマトリクスを算出。 Args: elem (Element): 対象のElementクラス Returns: Ke (array 8x8): 要素剛性マトリクス """ gps = ((-1 / np.sqrt(3), -1 / np.sqrt(3), 1, 1), (1 / np.sqrt(3), -1 / np.sqrt(3), 1, 1), (1 / np.sqrt(3), 1 / np.sqrt(3), 1, 1), (-1 / np.sqrt(3), 1 / np.sqrt(3), 1, 1), ) Ke = np.zeros((8, 8)) for xi, eta, wi, wj in gps: B, J = self.calc_B(elem, xi, eta) Ke += wi * wj * np.dot(B.T, np.dot(self.D, B)) * np.linalg.det(J) * elem.thickness return Ke def calc_K(self): """ Kマトリクスを算出。 Returns: K (array 2node_numx2node_num): 全体剛性マトリクス """ K = np.zeros((len(self.mesh.nodes) * 2, len(self.mesh.nodes) * 2)) for elem in self.mesh.elements: Ke = self.calc_Ke(elem) for i in range(4): for j in range(4): K[2 * elem.node[i]:2 * elem.node[i]+2, 2 * elem.node[j]:2 * elem.node[j]+2] += Ke[2 * i:2 * i+2, 2 * j:2 * j+2] return K
とりあえずエラーは吐いてないので大丈夫そうです。
終わりに
次回はついに最後のステップである連立方程式を解いて変形を求めるところまでやっていきましょう。