進化計算ライブラリDEAPでレイリー減衰パラメータ探査

前回の「遺伝的アルゴリズムによるレイリー減衰のパラメータ探査」で試した、一定減衰に相当するレイリー減衰のパラメータ探査を、今回はPythonの進化計算ライブラリのDEAPを使ってやってみます。

プログラムは、データサイエンス教育総合研究所のサイトを参考にさせていただきました。参考にしたというより、目的関数のところを一定減衰Shakeとレイリー減衰応答解析の差分2乗和とする関数に置き換えただけです。

前回は、遺伝的アルゴリズムをプログラミングしてパラメータを探しましたが、DEAPを使うとアルゴリズムの詳細をプログラミングしなくてもパラメータ探査できます。

実行結果は以下のとおり。

最も良い個体は [2.572249424150136, 6.2771604118935755]で、そのときの目的関数の値は (7.337222985431525,)

上記の個体の数値がレイリー減衰パラメータである二つの振動数になります。

このレイリー減衰のパラメータで応答計算し、評価位置で応答スペクトルを比較したものが下図です。

DEAPによるレイリー減衰パラメータ探査

一定減衰とレイリー減衰の応答性状がよく合致しています。

参考までに、全く的外れなレーリー減衰のパラメータとして、0.02Hzと50Hzを指定した場合の比較は下図になります。

不適切なレイリー減衰パラメータの応答スペクトル比較

以下のプログラムでは、この0.02Hzから50Hzを遺伝子の範囲としています。

#個体の各遺伝子を決めるために使用
import random
import numpy as np
import pandas as pd

#DEAPの中にある必要なモジュールをインポート
from deap import base
from deap import creator
from deap import tools
from deap import algorithms

from FreeField import FreeField

#最小化問題として設定(-1.0で最小化、1.0で最大化問題)
#面食らうかもしれませんが、目的が最小化の場合は以下のように設定します。
creator.create("FitnessMin", base.Fitness, weights=(-1.0,))

#個体の定義(list型と指定しただけで、中身の遺伝子は後で入れる)
#これも面食らうかもしれませんが、個体を準備したと思ってください。
creator.create("Individual", list, fitness=creator.FitnessMin)

#目的関数の定義。必ずreturnの後に,をつける
#レイリー減衰の応答解析とターゲットとなる一定減衰Shakeの差分2乗和を計算する
def resp(individual):
    #一定減衰Shakeのターゲットスペクトルの読み込み
    df_target_spec = pd.read_csv("shake.csv")
    #レイリー減衰応答解析のスペクトル計算
    FreeField_spec = np.array([FreeField(individual).resp()])
    object = np.sum((df_target_spec["shake"] - FreeField_spec[0]) ** 2)
    return object,

#各種関数の設定を行います
#交叉、選択、突然変異などには、DEAPのToolbox内にある関数を利用
toolbox = base.Toolbox()
#random.uniformの別名をattribute関数として設定。各個体の遺伝子の中身を決める関数(各遺伝子は0.02Hz~50Hzのランダムな値)
toolbox.register("attribute", random.uniform, 0.02,50)
#individualという関数を設定。それぞれの個体に含まれる2個の遺伝子をattributeにより決めるよ、ということ。
toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attribute, 2)
#集団の個体数を設定するための関数を準備
toolbox.register("population", tools.initRepeat, list, toolbox.individual)
#トーナメント方式で次世代に子を残す親を選択(tornsizeは各トーナメントに参加する個体の数)
toolbox.register("select", tools.selTournament, tournsize=5)
#交叉関数の設定。ブレンド交叉という手法を採用
toolbox.register("mate", tools.cxBlend,alpha=0.2)
#突然変異関数の設定。indpbは各遺伝子が突然変異を起こす確率。muとsigmaは変異の平均と標準偏差
toolbox.register("mutate", tools.mutGaussian, mu=[0.0, 0.0], sigma=[20.0, 20.0], indpb=0.2)
#評価したい関数の設定(目的関数のこと)
toolbox.register("evaluate", resp)

#以下でパラメータの設定
#今回は最も単純な遺伝的アルゴリズムの手法を採用

#乱数を固定
random.seed(64)
#何世代まで行うか
NGEN = 30
#集団の個体数
POP = 10
#交叉確率
CXPB = 0.9
#個体が突然変異を起こす確率
MUTPB = 0.1

#集団は80個体という情報の設定
pop = toolbox.population(n=POP)
#集団内の個体それぞれの適応度(目的関数の値)を計算
for individual in pop:
    individual.fitness.values = toolbox.evaluate(individual)
#パレート曲線上の個体(つまり、良い結果の個体)をhofという変数に格納
hof = tools.ParetoFront()

#今回は最も単純なSimple GAという進化戦略を採用
algorithms.eaSimple(pop, toolbox, cxpb=CXPB, mutpb=MUTPB, ngen=NGEN, halloffame=hof)

#最終的な集団(pop)からベストな個体を1体選出する関数
best_ind = tools.selBest(pop, 1)[0]
#結果表示
print("最も良い個体は %sで、そのときの目的関数の値は %s" % (best_ind, best_ind.fitness.values))

以下、関連リンク
DEAPの本家サイト

教師なし機械学習で地震波をクラスタリングする

Pythonの機械学習ライブラリ、scikit-learnの教師なし機械学習で、地震波をクラスタリングしてみました。

地震波は、K-NETから2011年3月11日の地震波をダウンロードして、予めランダムに選択した地震波の応答スペクトルを計算しておき、これをデータにして5つのクラスタに分けます。

以下がプログラムです。

import glob
import numpy as np
import pandas as pd
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
import geopandas as gpd


if __name__ == '__main__':

    # 予め計算しておいた各記録波の応答スペクトルを読込む
    files = glob.glob('*.spec.csv')
    feature = np.array([])
    for file in files:
        df = pd.read_csv(file, index_col=None)
        feature = np.append(feature, df['acc'])
        # feature = np.append(feature, df['acc']/df['acc'][0])

    # 1次元numpy配列を2次元に変換する(応答スペクトルの計算ポイント数300)
    feature = feature.reshape(-1, 300)  

    # 5種類のグループにクラスタリングする
    model = KMeans(n_clusters=5, init='random').fit(feature)

    # 学習結果のラベルを取得する
    labels = model.labels_
    
    # 応答スペクトルをクラスタ毎に色分けしてプロットする
    colors = ["red", "green", "blue", "magenta", "cyan"]
    plt.grid()
    plt.xscale('log')
    plt.xlabel('Period(sec)')
    plt.ylabel('Response Acceleration Spectrum')
    for label, v, file in zip(labels, feature, files):
        plt.plot(df['period(sec)'], v, color=colors[label])
    plt.show()


    # 各観測点の情報を読込む
    geo = pd.read_csv('geo.csv', index_col='name')

    # クラスタ毎に色分けして地図上にプロットする
    jpnShp = gpd.read_file('./gm-jpn-all_u_2_2/coastl_jpn.shp')
    ax = jpnShp.plot(figsize=(10, 20), color='black', linewidth=.5)
    for label, file in zip(labels, files):
        plt.scatter(geo.at[file[:6],'Long'], geo.at[file[:6],'Lat'], marker='o', s=10, color=colors[label], alpha=1.0, linewidths = 1)
    plt.show()

データを準備したり、結果を描画するための処理が多いのですが、機械学習自体は以下のたった1行で完結します。

model = KMeans(n_clusters=5, init='random').fit(feature)

このプログラムでグループ分けされた応答スペクトルを描画すると以下のようになります。

応答スペクトルのクラスタリング(1)

少し分かりづらいですが、greenのグループが応答スペクトルが大きいグループ、blueのグループが応答スペクトルが小さいグループで、blue < cyan < red < magenta < green の順で大きくなっています。

これらの地震波を観測点ごとに、上図と同じ色分けで日本地図に描画すると以下のようになります。
(地図データは国土地理院のデータを使用しています。)

観測点プロット(1)

容易に想像できますが、震源に近い観測点で大きな応答スペクトルになっていて、遠いところで小さな応答スペクトルになっています。

さて、地震波はその特徴として周期特性がありますが、この周期特性を元にしてクラスタリングする為に、応答スペクトルを地震波の最大値で基準化してグループ分けしてみます。

地震波の時間刻みが0.01秒であれば、解像度は50Hzで、応答スペクトルの最短周期の0.02秒が地震波の最大値になるので、この値で応答スペクトル全体を徐算します。

プログラムは以下の1行のコメントを外して入れ替えます。

# feature = np.append(feature, df['acc'])
feature = np.append(feature, df['acc']/df['acc'][0])

この結果を描画したものが以下です。

応答スペクトルのクラスタリング(2)

redが長周期成分が卓越しているグループ、greenが短周期成分が卓越しているグループで、green < cyan < magenta < blue < red の順で卓越している周期成分が長周期寄りになります。

最初のグループ分けと同様にこれらを日本地図に描画したものが以下です。

観測点プロット(2)

最初のケースと比較すると、各グループの際立った地域特性は発見できません。これは、観測波の周期特性はその点の地盤の特性が大きく影響するためだと思います。

北海道や関西に長周期寄りの観測波が多いのは何か理由があるのかもしれません。

scikit-learnの公式サイトには豊富なサンプルがあるので、直ぐに機械学習を試すことができます。

※2021/10/30追記
長周期成分が卓越している記録波が遠方に多いのは、長周期成分は遠方まで到達しやすいからではないかとのコメントをいただきました。

遺伝的アルゴリズムによるレイリー減衰のパラメータ探査

遺伝的アルゴリズム(genetic algorithm、略称:GA)は、古くからある近似解探査法の一つで、パラメータを生物の遺伝子に見立て、良い遺伝子を選択して交叉と突然変異を行いながら、世代交代を繰り返して最適なパラメータを探しあてる手法です。

有名なところでは、新幹線のN700系のフロントノーズの設計に使われました。

以下は一定減衰(一次元波動論)の応答スペクトルをターゲットにして、レイリー減衰(質点系)の振動数パラメータを遺伝子としてGAで最適な解を探査したものです。

図はGAで計算した各世代の最優良パラメータにおける応答スペクトルの進化をアニメgifで表しています。

各世代の応答スペクトルの進化

モデルの概要は以下です。

解析モデル概要

このアプローチでは遺伝子が二つで、あっという間に優良解が見つかってしまいます。

しかし、各部別レイリー減衰で物性ごとにレイリー減衰を設定する場合は遺伝子の数を増やすことになるので、こういった探査法が有益だと思います。

また、水平動だけでなく、上下動も同時に評価して最適解を求めたいときは、優良遺伝子の判定を工夫することで同様に探査が可能です。

ところで、質点系レイリー減衰のモデルは、外部減衰の接続先が半無限性を模擬するダンパー端部の固定点です。この固定点と、質点系モデルとの相対速度は工学的には無意味なので、減衰が適切に評価されませんが、このアプローチは、そうしたモデルの矛盾も吸収してレイリー減衰のパラメータを決定していることになります。

以下、関連リンク

# coding:utf-8

import numpy as np
from numpy import random as rand
import pandas as pd
from FreeField import FreeField
import matplotlib.pyplot as plt

from PIL import Image
import glob

class GeneticAlgorithm(object):
    """遺伝的アルゴリズム"""

    def __init__(self):
        """初期化"""

        """定数"""
        # 染色体数
        self.N = 2
        # 個体数
        self.M = 10
        # エリート選択個数
        self.n_elite = 3
        self.p_mutation = 0.10

        """変数"""
        # 第g世代評価降順インデックス
        self.val_sorted_index = []
        # 選択された遺伝子のインデックス
        self.selected_index = []
        # 次世代遺伝子
        self.next_generation = []
        # 各世代の最良応答スペクトル
        self.best_spec = []

        """初期値"""
        # 第g世代遺伝子群
        self.X_g = np.array([[rand.uniform(0.2,50.0),       # f1
                             rand.uniform(0.2,50.0)]        # f2
                             for i in range(self.M)])

        """ターゲットスペクトル"""
        self.df_target_spec = pd.read_csv("shake.csv")


    def assess(self):
        """評価"""
        # ターゲットスペクトルとの差分2乗和を評価値とする
        FreeField_spec = np.array([FreeField(x).resp() for x in self.X_g])
        R = []
        for spec in FreeField_spec:
            R.append(np.sum((self.df_target_spec["shake"] - spec) ** 2))
        R = np.array(R)
        self.val_sorted_index = np.argsort(R)[::1]
        self.best_spec.append(FreeField_spec[self.val_sorted_index[0]])


    def select(self):
        """選択"""
        # エリート選択で N 個
        elite_index = self.val_sorted_index[0:self.n_elite]
        self.selected_index = np.r_[elite_index]
        print("selected_index",self.selected_index)


    def crossover1p(self):
        """交叉"""
        ng = []
        for i in range(self.M - self.n_elite):
            p_index = rand.choice(self.selected_index, 2, replace=False)
            new_g = np.r_[self.X_g[p_index[0]][0], self.X_g[p_index[1]][1]]
            ng.append(new_g)

        ng = np.array(ng)
        self.next_generation = np.r_[ng, self.X_g[self.selected_index[0:self.n_elite]]]


    def mutation(self):
        """突然変異"""
        # 確率的に選ばれた染色体をランダム値に変更
        # 突然変異を起こす染色体の数
        n_mutation = rand.binomial(n=self.M * self.N, p=self.p_mutation)

        # 突然変異
        for i in range(n_mutation):
            m = rand.randint(self.M)
            n = rand.randint(self.N)
            self.next_generation[m][n] = rand.uniform(0.2,50.0)


    def alternation(self):
        """世代交代"""
        self.X_g = self.next_generation


if __name__ == '__main__':

    """設定"""
    # 計算世代
    G = 50

    """計算"""
    ga = GeneticAlgorithm()
    for g in range(G):
        # 評価
        ga.assess()
        # 選択
        ga.select()
        # 交叉
        ga.crossover1p()
        # 突然変異
        ga.mutation()
        # 世代交代
        ga.alternation()

    for i, spec in enumerate(ga.best_spec):
        # グラフ描画
        plt.plot(ga.df_target_spec["t"], ga.df_target_spec["shake"], label="Target")
        plt.plot(ga.df_target_spec["t"], spec, label="GA")
        plt.title("GA = "+str(i))
        plt.xlabel("Period(sec)")
        plt.ylabel("Acceleration Response Spectrum $(m/s^{2})$")
        plt.xscale("log")
        plt.ylim(0.0, 20.0)
        plt.legend()
        plt.grid()
        png = "spec_{:0>2d}.png".format(i)
        plt.savefig(png)
        plt.clf()

    # GIF 作成
    files = sorted(glob.glob('*.png'))  
    images = list(map(lambda file : Image.open(file) , files))
    images[0].save('GA.gif' , save_all = True , append_images = images[1:] , duration = 1000)
# coding:utf-8
import numpy as np
from numpy.lib.function_base import append
import pandas as pd
import math

class FreeField(object):

    def __init__(self, x):
        self.f1 = x[0]
        self.f2 = x[1]


    # 時刻歴応答解析
    def resp(self):
        f1 = self.f1
        f2 = self.f2

        model = pd.read_csv("model.csv")
        h1 = model["h"][0]
        h2 = model["h"][0]

        size = len(model["M"])
        # 質量マトリクス
        m = np.diag(list(map(lambda x: x / 9.80665, model["M"])))
        # 剛性マトリクス
        k = np.zeros((size, size))
        kdata = size -1
        for i in range(kdata):
            i1 = i
            i2 = i + 1
            k[i1][i1] = k[i1][i1] + model["K"][i]
            k[i2][i2] = k[i2][i2] + model["K"][i]
            k[i1][i2] = k[i1][i2] - model["K"][i]
            k[i2][i1] = k[i2][i1] - model["K"][i]
        # 減衰マトリクス(レイリー減衰)
        w1 = 2.0 * np.pi * f1
        w2 = 2.0 * np.pi * f2
        alpha = (2.0 * w1 * w2 * (h2 * w1 - h1 * w2)) / (w1 ** 2 - w2 ** 2)
        beta = (2.0 * (h1 * w1 - h2 * w2)) / (w1 ** 2 - w2 ** 2)
        c = np.array(alpha * m + beta * k)

        # 底面ダンパー(2E入力用)
        c[kdata][kdata] = c[kdata][kdata] + model["C"][0]

        # 入力波読み込み
        df = pd.read_csv(model["accfile"][0])
        acc0 = df["acc"]
        dt = 0.01
        beta = 0.25
        unit_vector = np.ones(size)
        pre_acc0 = 0.0
        time = 0.0
        dis = np.zeros(size)
        vel = np.zeros(size)
        acc = np.zeros(size)
        ddis = np.zeros(size)
        dvel = np.zeros(size)
        dacc = np.zeros(size)
        acc_history = {}
        for i in range(0, size):
            acc_history[i] = []
        time_history = []
        # Newmarkβ法による数値解析(増分変位による表現)
        for i in range(0, len(acc0)):
            kbar = k + (1.0/(2.0*beta*dt)) * c + (1.0/(beta*dt**2.0)) * m
            dp1 = -1.0 * m.dot(unit_vector) * (acc0[i] - pre_acc0)
            dp2 = m.dot((1.0/(beta*dt))*vel + (1.0/(2.0*beta))*acc)
            dp3 = c.dot((1.0/(2.0*beta))*vel + (1.0/(4.0*beta)-1.0)*acc*dt)
            dp = dp1 + dp2 + dp3
            ddis = np.linalg.inv(kbar).dot(dp)
            dvel = (1.0/(2.0*beta*dt))*ddis - (1.0/(2.0*beta))*vel - ((1.0/(4.0*beta)-1.0))*acc*dt
            dacc = (1.0/(beta*dt**2.0))*ddis - (1.0/(beta*dt))*vel - (1.0/(2.0*beta))*acc
            dis += ddis
            vel += dvel
            acc += dacc
            acc_abs = acc + [acc0[i] for n in range(0,size)]
            [acc_history[i].append(x) for i, x in enumerate(acc_abs)]
            time_history.append(time)
            time += dt
            pre_acc0 = acc0[i]

        # 評価対象として最上部の応答スペクトルを計算する
        spec = self.duhm_spec(acc_history[0])
        return(spec)


    def duhm_spec(self, acc):
        spec = np.empty(0)
        dlta = (math.log(5.0) - math.log(0.02)) / float(300 - 1)
        for i in range(1, 300 + 1):
            tmp = math.log(0.02) + dlta * float(i - 1)
            period = math.exp(tmp)
            zmax = self.duhm(0.01, 0.05, period, acc)
            spec = np.append(spec, zmax)
        return(spec)


    def duhm(self, dt, h, T, a):
        """duhamel integral"""
        w = 2.0 * math.pi / T
        w2 = w * w
        hw = h * w
        wd = w * math.sqrt( 1.0 - h * h )
        wdt = wd * dt
        e = math.exp( -hw * dt )
        cwdt = math.cos( wdt )
        swdt = math.sin( wdt )
        e11 = e * ( cwdt - hw * swdt / wd )
        e12 = -e * ( wd * wd + hw * hw ) * swdt / wd
        e21 = e * swdt / wd
        e22 = e * ( cwdt + hw * swdt / wd )
        ss = - hw * swdt - wd * cwdt
        cc = - hw * cwdt + wd * swdt
        s1 = ( e * ss + wd ) / w2
        c1 = ( e * cc + hw ) / w2
        s2 = ( e * dt * ss + hw * s1 + wd * c1 ) / w2
        c2 = ( e * dt * cc + hw * c1 - wd * s1 ) /w2
        s3 = dt * s1 - s2
        c3 = dt * c1 - c2
        g11 = ( -hw * s3 + wd * c3 ) / wdt
        g12 = ( -hw * s2 + wd * c2 ) / wdt
        g21 = s3 / wdt
        g22 = s2 / wdt
        dx = 0.0
        x = 0.0
        accmax = 0.0
        for m in range(1, len(a)):
            dxf = dx
            xf = x
            ddym = a[ m ]
            ddyf = a[ m - 1 ]
            dx = e11 * dxf + e12 * xf + g11 * ddym + g12 * ddyf
            x = e21 * dxf + e22 * xf + g21 * ddym + g22 * ddyf
            ddx = 2.0 * hw * dx + w2 * x
            if abs(ddx) > abs(accmax):
                accmax = abs(ddx)

        return(accmax)

使用したデータも含め、プログラムはこちらにも置いています。

AEiスクリプトで埋込SRモデルの解析を連続実行する

動的サブストラクチャ解析の一つに、建屋の一部(または全部)が表層地盤に埋め込まれた構造を、地盤の応答解析の結果を引き継ぎ、建屋の応答解析を実施するという方法があります。

この場合、建屋側面地盤の非線形性を等価線形解析で求め、この物性で求めた建屋側面のインピーダンスから地盤剛性と地盤減衰を算出して、建屋に接続して解析を行うことが一般的です。

以下は、その一連の解析を、入力地震動を変更しながら、また地盤物性と建屋減衰を変更してAEiスクリプトで連続実行するデモです。

入力地震動はエルセントロNSと日本建築センター模擬波(基盤波)、八戸NSの3種、地盤物性は標準、+σ、-σの3種、建屋減衰は5%と3%の2種で合計18ケースの解析を実施します。

なお、地盤の応答解析以外は自前のプログラムを使用しております。

地盤の応答解析は吉田望氏が作成されたDYNEQ336を使用させていただきました。
たいへん使いやすく、ドキュメントもしっかりいて素晴らしいものだと思います。

デモ動画のOSはMacOSですが、AEiスクリプトはWindowsでも、Linuxでも動作します。

参考として以下にaeiスクリプトを記載します。

print " aei スクリプトで、入力地震動と地盤物性を変化させ、埋込SRを連続解析して各ケースの解析結果のレポートを作成する"
pause

print "解析定義のExcelから解析ケースのシートを抽出する"
cmd{
	excelcsv -proc ex2csv -excel analysis.xlsx -sheet case -csv case.csv
	cat case.csv
	}
	pause

# 連続解析のループ
for i = 1 to 18
	L = i + 1

	print "解析ケースのCSVから入力波と地盤の情報を変数に代入する"
	csvfile = case.csv
	$NUM  = csv_r[L]_c[1]
	$SOIL = csv_r[L]_c[2]
	$WAVE = csv_r[L]_c[3]
	$WNUM = csv_r[L]_c[4]
	$WSTP = csv_r[L]_c[5]
	$BLDH = csv_r[L]_c[6]
	print "ケースNo   = ",$NUM
	print "地盤条件   = ",$SOIL
	print "入力地震動 = ",$WAVE,"( ",$WSTP,")"
	print "建屋減衰   = ",$BLDH
	pause

	print "各解析データのファイル名を定義する"
	$CASE = CASE<$NUM>
	$INPUT_WAV = <$WAVE>.wav
	$JBNWAV    = <$SOIL>_<$WAVE>
	$SHAKE_DAT = Shake_<$JBNWAV>.dat
	$SHAKE_OUT = Shake_<$JBNWAV>.out
	$SHAKE_WAV = Shake_<$JBNWAV>.wav
	$NOVAK_DAT = Novak_<$JBNWAV>.dat
	$NOVAK_OUT = Novak_<$JBNWAV>.out
	$ADMIT_DAT = Admit_<$SOIL>.dat
	$ADMIT_OUT = Admit_<$SOIL>.out
	$NAMAZ_DAT = <$CASE>_Namaz_<$JBNWAV>_h<$BLDH>.dat
	$NAMAZ_OUT = <$CASE>_Namaz_<$JBNWAV>_h<$BLDH>.out
	$NAMAZ_WAV = <$CASE>_Namaz_<$JBNWAV>_h<$BLDH>.wav
	print "Shake DATA  = ",$SHAKE_DAT
	print "Novak DATA  = ",$NOVAK_DAT
	print "Admit DATA  = ",$ADMIT_DAT
	print "Namaz DATA  = ",$NAMAZ_DAT
	pause

	print "解析定義のExcelから地盤情報を抽出して地盤物性を変数に代入する"
	cmd{
		excelcsv -proc ex2csv -excel analysis.xlsx -sheet <$SOIL> -csv JIBAN.csv
		cat JIBAN.csv
		}
	csvfile = JIBAN.csv
	for j = 1 to 7
		M = j + 1
		$G[j]  = csv_r[M]_c[2]
		$VS[j] = csv_r[M]_c[3]
		$RO[j] = csv_r[M]_c[4]
		$PO[j] = csv_r[M]_c[5]
		$T[j]  = csv_r[M]_c[6]
		$SH[j] = csv_r[M]_c[7]
	next j
	pause

	print "解析定義のExcelから入力波のCSVを抽出してShakeが読み込める書式に変換する"
	cmd{
		excelcsv -proc ex2csv -excel analysis.xlsx -sheet WAVE -csv WAVE.csv
		csv2wav.sh -c WAVE.csv -w <$INPUT_WAV> -r <$WNUM> -s <$WSTP>
		}
	pause

	print "Shakeデータを作成して解析を実行する"
	makedata shake.template $SHAKE_DAT
	print "Shakeのプログラムは 吉田望 氏 が作成された DYNEQ336 を使わせていただいています。"
	cmd{ sub.sh DYNEQ336 $SHAKE_DAT $SHAKE_OUT }
	pause

	print "アドミッタンスばねデータを作成して解析を実行する"
	makedata admit.template $ADMIT_DAT
	cmd{ sub.sh admit $ADMIT_DAT $ADMIT_OUT }
	pause
	
	print "Shake解析結果からNovakばね計算に必要な情報を取得する"
	cmd{
		shake2csv.sh -L $SHAKE_OUT -c SHAKE.csv
		cat SHAKE.csv
		}
	csvfile = SHAKE.csv
	for j = 1 to 7
		$SKG[j]  = csv_r[j]_c[1]
		$SKVS[j] = csv_r[j]_c[2]
	next j
	pause

	print "novakばねデータを作成して解析を実行する"
	makedata novak.template $NOVAK_DAT
	cmd{ sub.sh novak $NOVAK_DAT $NOVAK_OUT }
	pause
	
	print "アドミッタンスばねのKとCを取り出す(Cの計算は暫定的に1.0Hz)"
	cmd{
		admit2csv.sh -L $ADMIT_OUT -c ADMIT.csv -f 1.0
		cat ADMIT.csv
		}
	pause

	print "novakばねのKとCを取り出す(Cの計算は暫定的に1.0Hz)"
	cmd{
		novak2csv.sh -L $NOVAK_OUT -c NOVAK.csv -f 1.0
		cat NOVAK.csv
		}
	pause

	print "アドミッタンスばねとnovakばねの値を変数に代入する(Cの計算は暫定的に1.0Hz)"
	csvfile = ADMIT.csv
	$ADMK[1] = csv_r[1]_c[1]
	$ADMK[2] = csv_r[2]_c[1]
	$ADMC[1] = csv_r[1]_c[3]
	$ADMC[2] = csv_r[2]_c[3]
	print "SWAY K = ",$ADMK[1]," ROCK K = ",$ADMK[2]
	print "SWAY C = ",$ADMC[1]," ROCK C = ",$ADMC[2]
	csvfile = NOVAK.csv
	for j = 1 to 6
		$NVKK[j] = csv_r[j]_c[1]
		$NVKC[j] = csv_r[j]_c[3]
		print "NOVAK K= ",$NVKK[j]," NOVAK C= ",$NVKC[j]
	next j
	pause

	print "固有値解析用データ作成と実行(一度、1.0Hzの減衰で応答解析を実施する)"
	makedata namazu.template $NAMAZ_DAT
	cmd{ sub.sh namazu $NAMAZ_DAT $NAMAZ_OUT }
	pause

	print "解析モデルの地盤-建屋連成1次の振動数を取得する"
	cmd{
		namazu2csv.sh -L $NAMAZ_OUT -c NAMAZU.csv
		cat NAMAZU.csv
		}
	pause

	print "連成1次の減衰を取得する"
	csvfile = NAMAZU.csv
	$FREQ = csv_r[1]_c[1]
	cmd{
		admit2csv.sh -L $ADMIT_OUT -c ADMIT.csv -f $FREQ
		novak2csv.sh -L $NOVAK_OUT -c NOVAK.csv -f $FREQ
		}
	pause

	print "アドミッタンスばねとnovakばねの値を変数に代入する"
	csvfile = ADMIT.csv
	$ADMK[1] = csv_r[1]_c[1]
	$ADMK[2] = csv_r[2]_c[1]
	$ADMC[1] = csv_r[1]_c[3]
	$ADMC[2] = csv_r[2]_c[3]
	print "SWAY K = ",$ADMK[1]," ROCK K = ",$ADMK[2]
	print "SWAY C = ",$ADMC[1]," ROCK C = ",$ADMC[2]
	csvfile = NOVAK.csv
	for j = 1 to 6
		$NVKK[j] = csv_r[j]_c[1]
		$NVKC[j] = csv_r[j]_c[3]
		print "NOVAK K= ",$NVKK[j]," NOVAK C= ",$NVKC[j]
	next j
	pause

	print "応答解析を実施する"
	makedata namazu.template $NAMAZ_DAT
	cmd{ sub.sh namazu $NAMAZ_DAT $NAMAZ_OUT }
	pause
	
	print "応答スペクトル計算"
	pause
	cmd{
		wav2csv -in $INPUT_WAV -csv <$INPUT_WAV>.csv -fmt '(8f10.0)' -step $WSTP -dt 0.01
		wav2csv -in $NAMAZ_WAV -csv <$NAMAZ_WAV>.csv -fmt '(8f10.0)' -step $WSTP -dt 0.01
		duhamel -c <$INPUT_WAV>.csv -d 0.05 -s 0.02 -e 5.0 -r 1 -b 300
		duhamel -c <$NAMAZ_WAV>.csv -d 0.05 -s 0.02 -e 5.0 -r 1 -b 300
		duhamel -c <$NAMAZ_WAV>.csv -d 0.05 -s 0.02 -e 5.0 -r 2 -b 300
		duhamel -c <$NAMAZ_WAV>.csv -d 0.05 -s 0.02 -e 5.0 -r 3 -b 300
		duhamel -c <$NAMAZ_WAV>.csv -d 0.05 -s 0.02 -e 5.0 -r 4 -b 300
		duhamel -c <$NAMAZ_WAV>.csv -d 0.05 -s 0.02 -e 5.0 -r 5 -b 300
		}
	pause

	print "一括後処理(関連するCSVファイルを全てTemplateのExcelに貼り付ける)"
	cmd{
		sr_post.sh <$CASE> <$WAVE> <$SOIL> <$BLDH> <$INPUT_WAV> <$CASE>_<$WAVE>_<$SOIL>_<$BLDH>.xlsx
		open <$CASE>_<$WAVE>_<$SOIL>_<$BLDH>.pdf
		}
	pause

next i

応答スペクトルの減衰値の違いで、加速度応答の大小関係が逆転する現象

加速度応答スペクトル図

良く見かける基盤波の加速度応答スペクトルの図です。詳しく見ると、周期0.15秒の加速度応答が、減衰1%と3%で値の大小関係が逆転しています。

一般に入力が同じなら構造物の減衰が1%と3%とでは、1%の方が応答は大きくなります。

しかし、上記の様なスペクトルの特徴を持つ基盤波を入力とした場合、構造物の卓越周期が0.15秒ならば、減衰が3%の方が1%の場合より応答が大きくなることがあります。

構造物の応答は全てのモードの重ね合せであるため、周期0.15秒のモードだけで応答が決まることはありませんが、鉄塔の様に単純な構造物の上下動解析では、1次モードだけが励起されることもあり、この周期が上記の入力の0.15秒に合致してしまった場合は減衰3%の方が応答が大きくなるという不思議な現象が起こります。

長く振動解析をしていますが、過去にこの様な現象に一度だけ出くわしたことがあります。

※上記の地震波は以下のサイトからダウンロードしたものを少々編集したものです。
https://www.bcj.or.jp/download/wave/