「亀甲分割」とは?

建築構造解析における床荷重の分配方法は、「亀甲分割」「亀の甲割」と呼ばれる方法により考慮されます。 簡単にいうと、"床のある位置の微小領域にかかる荷重を最も近い梁(大梁または小梁)に荷重を載荷する"という考え方になります。
単純な長方形を例にとると非常に簡単に見えますが、これが任意の多角形を対象とすると、プログラムとして実装するのはそれほど簡単ではありません。
以前、当ブログ記事でも以下の記事で関連するトピックを扱っています。
上記の記事からもわかるように、この計算は "どうなるのが正解かは作図してみれば何となくわかるけれども、アルゴリズムとして実装するのはいろいろなアプローチがある" という類の問題です。 今回の記事は、上記とは異なるアプローチを試みてみたので、その備忘録的な意味合いとして執筆しています。 なお、現状のRESP-Dは異なるアプローチ(床を4cmの矩形メッシュ分割し、分割メッシュが最も近い梁に荷重が分配されるとみなす)をとっています。本アプローチと比較すると、あらゆる形状に対応できる代わりに、計算負荷が大きくなります。本記事のアプローチも今後実装を計画しています。

アルゴリズムの概要

アルゴリズムの概要は以下の通りです。
  1. 各頂点から2等分線のベクトルを抽出し、それぞれの2等分線同士の交点を求める。
  2. それぞれの頂点に対する2等分線同士の交点のうち、最近接交点が共通となるペアを抽出する。
  3. 残った線分同士の直線の交点から新頂点を生成する
  4. 負担領域が求まっていない線分が残り2本になれば終了。残っている場合、3で求めた交点を新しい頂点として、まだ最近接交点が見つかっていない頂点も含めて1に戻る。
フローチャートを見ると、非常にシンプルな再帰処理になっていることがわかります。 そのため、今回の実装においては、変数の再代入は一切行っていません。
flowchart TD node_1("2等分線の交点探索") --> node_2("最近接交点が共通となるペアの抽出") node_2 --> node_3("残りの線分同士の直線の交点から新頂点を生成") node_3 --> node_4{"線分が残り2本か"} node_4 -- yes --> node_1 node_4 -- no --> finish("終了")
今回のアプローチの肝は、3の手順です。個人的にはこれが一番の発見でした。順を追って説明していきます。

1. 2等分線ベクトルの交点算出

"床のある位置から最も近い梁に荷重を分配する" ということは、頂点に対する2等分線がその境界になることを意味します。頂点に対する2等分線から頂点を構成する線分への垂線の長さは両辺同じになるためです。そこで、まずは各頂点からの2等分線を計算し、交点を求めます。鈍角の頂点の場合には、ベクトルが逆方向にならないように注意が必要です(三角関数のライブラリは 0~180°で解を返すものが多いため)。

2. 最近接交点がペアとなる頂点を抽出

複数算出された交点のうち、それぞれの頂点からの距離が最短となる交点を抽出します。"最近接交点"が異なる頂点で同じ点となった場合、それをペアとみなします。その時点で、二つの頂点間の分割線が確定し、頂点において共通の線分となっている梁の支配面積が確定したことになります。

3. 残りの線分同士の直線の交点から新頂点を生成

ペアとなった交点の元端となる頂点に対し、頂点を構成する線分が隣接頂点と重複している線分(図中の赤線)はすでに荷重分配が決定しているものとして除外します。 残った線分(図中の青線)を直線として延長し、その交点を新しい仮想頂点として仮定します。残った線分が平行な場合には、線分の中間位置に位置する各線分と平行な直線を分割線とします。
4点しかない場合には、結果的には2で求まった内点同士を結べば分割線になりますが、これはあくまで上記のように、"新仮想頂点の2等分線が分割線になる" という前提があったうえで、両内点が新仮想頂点の2等分線上にある、という状況と理解することが重要です。 5点ある場合には以下のようになります。上記のように考えると、5点の場合でも自然と理解できます。 なお、下図の場合は、「仮想頂点2」 としている箇所は実際には頂点を持たず、2線分の中心を通る直線により分割線を作っています。

実装に必要な処理に分解する

ここまで理解すると、実装に必要な関数は以下となります。
  • ベクトルの角度を求める
  • ベクトルの2等分線を求める
  • ベクトルの交点を求める
  • 交点がポリゴンの内側か判定する
これらを組み合わせることで、亀甲分割を完成させることができます。最初の3つは高校までの数学で対応できる範囲かと思います。 最後の"交点がポリゴンの内側か判定する"は、最近接となる交点がポリゴンの外側にくることがあるため除外するために必要となる処理です。 この判定処理は、Crossing Number Algorithm と呼ばれる方法などがあります。 以下の記事を参考にさせていただきました。 https://www.nttpc.co.jp/technology/number_algorithm.html ただし、実際にはこの判定が必要になるのは以下に示すようなかなり極端なケースであり、多くの場合はこの判定がなくても問題なく機能するといえるかもしれません。ただ、実務システムをつくろうとすると、当初は極端だと思っていても不意に "開発時には極端だと思っていたシナリオ" に直面することも少なくありません。そのあたりが、あくまで自分が使うように作るプログラムと広く人に使っていただくプログラムの違いであり、難しさ・醍醐味でもあります。

複雑な形状で実践

複雑な形状の床に対して分配を行ってみます。 以下のような複雑な六角形(形状としては五角形ですが、梁が分割されている)でも分割が行えました。
順を追ってプロセスを可視化すると、以下のようになります。
プログラム実装上の苦労話も少ししておきます。 内部の分割線が途中で折れるパターンの時に、どうやってその情報を記録するかが悩みどころでした。上記のような複雑な形状だと、床内部の分割線が複数回折れ曲がるので、荷重分配を考えるときに求まった交点をたどりなおさなくてはいけなくなります。今回は、"仮想頂点を新たに作る際に、一つ前の仮想頂点を履歴として保持しておく" という考えで実装してみました。 以下の図で例を示します。例では、右側から仮想頂点による2等分線で分割線が進み、途中から仮想頂点2に対する分割線に移行している様子を示しています。このときに仮想頂点1の情報を受け継ぐことで、後からさかのぼってたどりなおすことができ、最終的な支配面積を取得できるようにしました。
これは実装上の仕様として、 "再帰的に分割線を求める (ステップごとに独立して計算を行い、状態の更新を行わない)" という仕様にしたことにより必要になった処理です。オブジェクト指向的に、線分に対して求まった交点を追加していく、という思想であればこの点は苦労しなかったと思います。一方で、この妥協をしなかったおかげで純粋関数型としてプログラムを実装できたので、テストは非常にしやすくなりました。副作用がない、状態がないということは、数学的な処理に近いといえるので、検証もスムーズになります。 なお、複雑な形状だとテストが難しいのですが、上記のように可視化しつつ、"梁ごとに分割された支配面積の合計" と "多角形の面積の合計" が一致することを確認することで、ある程度様々な形状の分割の妥当性を検証することができました。 余談ですが、多角形の面積は "座標法" による計算を用いると簡単に計算することができます。不勉強で恥ずかしながら初めて知りました。 https://ja.wikipedia.org/wiki/%E5%BA%A7%E6%A8%99%E6%B3%95

凹多角形の場合

冒頭に挙げた過去記事でのアプローチでは、凹多角形の場合の処理で曲線的な分割線が表現されていました。 今回は純粋に二等分線を扱う処理としたため、凹多角形でも直線的に分割されます。 凹多角形の場合は、「2辺に対して床のある点の位置が同じ距離になる境界線」が多角形の外にしか存在しないため、厳密に考えると前述の記事のように曲線的になります。これは、床を細かくメッシュ分割して、それぞれの分割メッシュを最も近い位置の線分または頂点に振り分けた場合と同様の挙動と言えます。 ただ、曲線に分割すると梁荷重にする過程が煩雑になるので、実務上の今回のような2等分線を用いて分割するロジックを採用しているアプリケーションは多いようです。

実装に際して

このように書くと簡単にやったように見えますが、実際はかなり苦労しました。 簡単な四角形でうまくいっても、台形にするとうまくいかない。台形がうまくいくようになっても、五角形だとうまくいかない。五角形がうまくいっても、少し座標を動かすとなぜかうまくいかない、、 結果的には約40ものユニットテストを記述して確認を行っています。段階的に改善を続ける中では、このユニットテストがあるおかげで、うっかりうまくいっていた処理を壊してしまったときにすぐに気付くことができ、非常に有用でした。 また、値の再代入がない関数型で実装したことも、問題の原因特定においては非常に有効でした。再代入がなければ、値がどう変化していくかを頭で記憶しておく必要はなく、純粋なロジックのミスだけに集中して考えることができます。

まとめ

亀甲分割の実装を行ってみた記録を書いてみました。 亀甲分割は、自分にとっては概念はわかっていても実際に実装しようとするとアイディアがない、というものでした。 今回実装するにあたって、悩みに悩みましたが、値の再代入がない形で実装できたことは自分の中では成長を感じる成果でした。

返信を残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です