本文使用 OpenSees 来完成结构动力学中与单自由度体系(SDOF)有关的动力分析。

1
2
3
import openseespy.opensees as ops
import numpy as np
import matplotlib.pyplot as plt

SDOF 静力分析

在动力分析之前,先熟悉 SDOF 体系的建模和静力分析方法。使用弹簧-小车模型建模,分别进行力控制和位移控制的分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 单自由度体系静力分析 - 力控制

ops.wipe()
ops.model("basic", "-ndm", 1, "-ndf", 1)
ops.node(0, 0.)
ops.node(1, 0.)
ops.fix(0, 1)
ops.uniaxialMaterial("Elastic", 1, 7) # 使用弹性模型
ops.element("twoNodeLink", 1, 0, 1, "-mat", 1, "-dir", 1) # 使用twoNodeLink创建弹簧
ops.timeSeries("Linear", 1)
ops.pattern("Plain", 1, 1)
ops.load(1, 13) # 施加大小为13的外荷载
ops.recorder("Node", "-file", "static_load_control.txt", "-time", "-closeOnWrite", "-node", 1, "-dof", 1, "disp") # 记录节点力和位移
ops.system("BandGeneral")
ops.numberer("Plain")
ops.constraints("Plain")
ops.integrator("LoadControl", 0.1) # 每一步加载0.1倍的外荷载
ops.algorithm("Newton")
ops.analysis("Static")
ops.analyze(10) # 共加载10步
print(ops.nodeDisp(1)[0]) #输出1号节点第一自由度的位移
1.8571428571428568

以上代码定义了一个刚度 \(K=7\) 的弹簧,加了 \(F=13 \) 的外荷载,输出位移为 \(\Delta = F / K = 1.8571 \) ,与 OpenSees 的输出相同。

下面我们来进行位移控制的加载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 单自由度体系静力分析 - 位移控制

ops.wipe()
ops.model("basic", "-ndm", 1, "-ndf", 1)
ops.node(0, 0.)
ops.node(1, 0.)
ops.fix(0, 1)
ops.uniaxialMaterial("Elastic", 1, 7)
ops.element("twoNodeLink", 1, 0, 1, "-mat", 1, "-dir", 1)
ops.timeSeries("Linear", 1)
ops.pattern("Plain", 1, 1)
ops.load(1, 13)
ops.recorder("Node", "-file", "static_disp_control.txt", "-time", "-closeOnWrite", "-node", 1, "-dof", 1, "disp")

ops.system("BandGeneral")
ops.numberer("Plain")
ops.constraints("Plain")
ops.integrator("DisplacementControl", 1, 1, 0.1) # 1号节点在1自由度上加0.1的位移
ops.algorithm("Newton")
ops.analysis("Static")
ops.analyze(10) # 分析10步
print("Force1:", ops.eleForce(1)[0])
print("Force2:", ops.getTime() * 13)
Force1: -6.999999999999999
Force2: 6.999999999999998

以上使用位移控制加载,通过 10 步完成了目标位移为 1 的加载。最后在输出时,通过两种方式完成。第一是通过读取 twoNodeLink 单元的内力来完成。此时输出的是一个负值。表示这个单元的左侧节点对单元的作用力是向左的。第二是通过读取 time ,再乘以 load 中的数值 13 完成的。这是因为定义了一个 Linear 类型的 timeSeries ,且比例因子为默认的 1 。此时将 time 与 load 相乘,即为结构当前所受的外荷载。

SDOF 自由振动

下面我们来分析 SDOF 体系的自由振动。为使体系自由振动起来,需要给结构一个初始扰动。这里有两种方式,第一是先通过 pattern 加一个初始力,然后再移除初始力,进行动力分析。第二是直接在定义节点的时候,给一个初始的位移。我们用第一种方式来计算一个无阻尼自由振动,再用第二种方式计算一个有阻尼自由振动。

无阻尼自由振动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# SDOF自由振动 - 先施加力再卸掉

ops.wipe()
ops.model("basic", "-ndm", 1, "-ndf", 1)
ops.node(0, 0.)
ops.node(1, 0.)
ops.fix(0, 1)
ops.mass(1, 13.)
ops.uniaxialMaterial("Elastic", 1, 7)
ops.element("twoNodeLink", 1, 0, 1, "-mat", 1, "-dir", 1)
ops.timeSeries("Linear", 1)
ops.pattern("Plain", 1, 1) # 1号工况
ops.load(1, 17.) # 施加一个17的外力
ops.recorder("Node", "-file", "undamped_free_vib1.txt", "-time", "-closeOnWrite", "-node", 1, "-dof", 1, "disp")
# 进行静力分析,把外力加上去
ops.system("BandGeneral")
ops.numberer("Plain")
ops.constraints("Plain")
ops.algorithm("Newton")
ops.integrator("LoadControl", 1)
ops.analysis("Static")
ops.analyze(1)
# 移除初始力,进行动力分析,此处自由振动无需再额外定义工况
ops.remove("loadPattern", 1)
ops.analysis("Transient")
ops.integrator("Newmark", 0.5, 0.25)
ops.analyze(3000, 0.01) # 每0.01秒为一步,分析3000步,即30秒
# 画图
data1 = np.loadtxt("undamped_free_vib1.txt")
plt.figure()
plt.plot(data1[:, 0] - 1, data1[:, 1]) # 静力工况时间为1,需要减去
plt.xlim([0, 30])
plt.xlabel("Time (s)")
plt.ylabel("Displacement (mm)")
plt.show()
WARNING analysis Transient - no Integrator specified, 
 TransientIntegrator default will be used

png

有阻尼自由振动

下面我们使用直接加初始位移的方式来施加初始扰动。然后使用 OpenSees 中的 Rayleigh 命令来施加阻尼。

对于单自由度体系,Rayleigh 阻尼中的质量系数和刚度系数任取其一即可。这里我们使用质量系数。则有

$$ c = 2 m \zeta \omega = \alpha m $$

$$ \alpha = 2 \zeta \omega $$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# SDOF有阻尼自由振动 - 直接加初始位移

ops.wipe()
ops.model("basic", "-ndm", 1, "-ndf", 1)
ops.node(0, 0.)
ops.node(1, 0., "-disp", 17./7.)
ops.fix(0, 1)
ops.mass(1, 13.)
ops.uniaxialMaterial("Elastic", 1, 7)
ops.element("twoNodeLink", 1, 0, 1, "-mat", 1, "-dir", 1)
ops.recorder("Node", "-file", "damped_free_vib1.txt", "-time", "-closeOnWrite", "-node", 1, "-dof", 1, "disp")

# 输出特征值
lambda_ = ops.eigen("-fullGenLapack", 1)
omega = lambda_[0] ** 0.5
print("Omega: ", omega)

# Rayleigh阻尼
damping = 0.05
alpha = 2 * damping * omega
ops.rayleigh(alpha, 0., 0., 0.)

# 进行静力分析,把外力加上去
ops.system("BandGeneral")
ops.numberer("Plain")
ops.constraints("Plain")
ops.algorithm("Newton")
ops.integrator("Newmark", 0.5, 0.25)
ops.analysis("Transient")
ops.analyze(3000, 0.01)

# 画图
data2 = np.loadtxt("damped_free_vib1.txt")
plt.figure()
plt.plot(data2[:, 0], data2[:, 1])
plt.xlim([0, 30])
plt.xlabel("Time (s)")
plt.ylabel("Displacement (mm)")
plt.show()
WARNING - the 'fullGenLapack' eigen solver is VERY SLOW. Consider using the default eigen solver.

Omega:  0.7337993857053428

png

根据响应计算自振特性

下面我们将有、无阻尼的响应画在一起,并画出有阻尼结构振动的包络线。包络线的表达式为

$$ u = \rho e^{ - \zeta \omega_{n} t} $$

其中

$$ \rho = \sqrt{u_0^2 + \left(\frac{\dot{u_0} + \zeta \omega_n u_0}{\omega_D}\right)^2} $$

代入数据

$$ \omega_n = \sqrt{\frac{k}{m}} = \frac{7}{13} = 0.7338 $$

$$ \omega_D = \omega_n \sqrt{1-\zeta^2} = 0.7338 * \sqrt{1-0.05^2} = 0.7329 $$

$$ u_0 = 17 / 7 = 2.4286 $$

$$ \dot{u_0} = 0 $$

$$ \rho = \sqrt{ 2.4286^2 + \left(\frac{0.05 \times 0.7329 \times 2.4286 }{0.7338}\right)^2} = 2.4316 $$

根据上述计算可以输出包络线,代码如下

1
2
3
4
5
6
7
8
9
plt.figure()
plt.plot(data1[:, 0] - 1, data1[:, 1], label="Undamped")
plt.plot(data2[:, 0], data2[:, 1], label="Damped")
ts = np.arange(0, 30, 0.02)
ys = 2.4316 * np.exp(-0.05 * 0.7338 * ts)
plt.plot(ts, ys, "g--", label="Envelope")
plt.plot(ts, -ys, "g--")
plt.xlim([0, 30])
plt.legend(fontsize="small")
<matplotlib.legend.Legend at 0x235043f4400>

png

下面我们来根据响应计算结构的周期和阻尼比。首先找到输出曲线的极值点,并取第一个和第三个进行计算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from scipy import signal

peak_args = signal.find_peaks(data1[:, 1])
ud_t1 = data1[peak_args[0][0], 0]
ud_t3 = data1[peak_args[0][2], 0]
ud_period = (ud_t3 - ud_t1) / 2

peak_args = signal.find_peaks(data2[:, 1])
d_t1 = data2[peak_args[0][0], 0]
d_t3 = data2[peak_args[0][2], 0]
d_period = (d_t3 - d_t1) / 2

d_peak1 = data2[peak_args[0][0], 1]
d_peak3 = data2[peak_args[0][2], 1]
delta = np.log(d_peak1 / d_peak3) / 2
zeta = delta / 2 / 3.1416

print("Undamped period: ", ud_period)
print("Damped period: ", d_period)
print("Damping ratio: ", zeta)
Undamped period:  8.56
Damped period:  8.57
Damping ratio:  0.05006255873633257

再根据结构定义进行计算

$$ T_n = \frac{2\pi}{\omega_n} = 8.5625 \mathrm{s} $$

$$ T_d = T_n / \sqrt{1-\zeta^2} = 8.5732 \mathrm{s} $$

可见结果基本吻合。

SDOF 受迫振动-简谐荷载

无阻尼

下面进行 SDOF 体系的受迫振动分析。首先对静止的无阻尼的结构施加简谐荷载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 无阻尼SDOF受迫振动 - 简谐荷载

ops.wipe()
ops.model("basic", "-ndm", 1, "-ndf", 1)
ops.node(0, 0.)
ops.node(1, 0.)
ops.fix(0, 1)
ops.mass(1, 13.)
ops.uniaxialMaterial("Elastic", 1, 7)
ops.element("twoNodeLink", 1, 0, 1, "-mat", 1, "-dir", 1)
ops.timeSeries("Trig", 1, 0., 60., 3.0) # 创建简谐波,长60秒,周期为3.0
ops.pattern("Plain", 1, 1)
ops.load(1, 17) # 简谐波的振幅为17
ops.recorder("Node", "-file", "undamped_trig.txt", "-time", "-closeOnWrite", "-node", 1, "-dof", 1, "disp")

# 输出特征值
lambda_ = ops.eigen("-fullGenLapack", 1)
omega = lambda_[0] ** 0.5
print("Omega: ", omega)

ops.system("BandGeneral")
ops.numberer("Plain")
ops.constraints("Plain")
ops.algorithm("Newton")
ops.analysis("Transient")
ops.integrator("Newmark", 0.5, 0.25)
ops.analyze(6000, 0.01)

# 画图
data4 = np.loadtxt("undamped_trig.txt")
plt.figure()
plt.plot(data4[:, 0], data4[:, 1])
plt.xlabel("Time (s)")
plt.ylabel("Displacement (mm)")
plt.xlim([0, 60])
plt.show()
Omega:  0.7337993857053428

png

可见无阻尼 SDOF 在简谐波的作用下,响应为自由振动与外力引起的振动叠加。

共振现象

当外荷载的周期接近结构的自振周期时,会出现共振现象。让我们来尝试一下。将外加简谐荷载的周期调整为 8.56 s:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 无阻尼SDOF受迫振动 - 简谐荷载 - 共振

ops.wipe()
ops.model("basic", "-ndm", 1, "-ndf", 1)
ops.node(0, 0.)
ops.node(1, 0.)
ops.fix(0, 1)
ops.mass(1, 13.)
ops.uniaxialMaterial("Elastic", 1, 7)
ops.element("twoNodeLink", 1, 0, 1, "-mat", 1, "-dir", 1)
ops.timeSeries("Trig", 1, 0., 60., 8.56) # 创建简谐波,长30秒,周期为8.56s
ops.pattern("Plain", 1, 1)
ops.load(1, 17) # 简谐波的振幅为17
ops.recorder("Node", "-file", "undamped_reso.txt", "-time", "-closeOnWrite", "-node", 1, "-dof", 1, "disp")

# 输出特征值
lambda_ = ops.eigen("-fullGenLapack", 1)
omega = lambda_[0] ** 0.5
print("Omega: ", omega)

ops.system("BandGeneral")
ops.numberer("Plain")
ops.constraints("Plain")
ops.algorithm("Newton")
ops.analysis("Transient")
ops.integrator("Newmark", 0.5, 0.25)
ops.analyze(6000, 0.01)

# 画图
data5 = np.loadtxt("undamped_reso.txt")
plt.figure()
plt.plot(data5[:, 0], data5[:, 1], label="$T_{ex}$=8.56 s")
plt.plot(data4[:, 0], data4[:, 1], label="$T_{ex}$=3.00 s")
plt.xlabel("Time (s)")
plt.ylabel("Displacement (mm)")
plt.xlim([0, 60])
plt.legend()
plt.show()
Omega:  0.7337993857053428

png

可见发生共振时,结构的振动幅值不断增大。

有阻尼

下面给结构加上阻尼,再来观察结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# 有阻尼SDOF受迫振动 - 简谐荷载

ops.wipe()
ops.model("basic", "-ndm", 1, "-ndf", 1)
ops.node(0, 0.)
ops.node(1, 0.)
ops.fix(0, 1)
ops.mass(1, 13.)
ops.uniaxialMaterial("Elastic", 1, 7)
ops.element("twoNodeLink", 1, 0, 1, "-mat", 1, "-dir", 1)
ops.timeSeries("Trig", 1, 0., 60., 3.0) # 创建简谐波,长30秒,周期为3.0
ops.pattern("Plain", 1, 1)
ops.load(1, 17) # 简谐波的振幅为17
ops.recorder("Node", "-file", "damped_trig.txt", "-time", "-closeOnWrite", "-node", 1, "-dof", 1, "disp")

# 输出特征值
lambda_ = ops.eigen("-fullGenLapack", 1)
omega = lambda_[0] ** 0.5
print("Omega: ", omega)

# Rayleigh阻尼
damping = 0.05
alpha = 2 * damping * omega
ops.rayleigh(alpha, 0., 0., 0.)

ops.system("BandGeneral")
ops.numberer("Plain")
ops.constraints("Plain")
ops.algorithm("Newton")
ops.analysis("Transient")
ops.integrator("Newmark", 0.5, 0.25)
ops.analyze(6000, 0.01)

# 有阻尼SDOF受迫振动 - 简谐荷载 - 共振

ops.wipe()
ops.model("basic", "-ndm", 1, "-ndf", 1)
ops.node(0, 0.)
ops.node(1, 0.)
ops.fix(0, 1)
ops.mass(1, 13.)
ops.uniaxialMaterial("Elastic", 1, 7)
ops.element("twoNodeLink", 1, 0, 1, "-mat", 1, "-dir", 1)
ops.timeSeries("Trig", 1, 0., 60., 8.56) # 创建简谐波,长30秒,周期为8.56
ops.pattern("Plain", 1, 1)
ops.load(1, 17) # 简谐波的振幅为17
ops.recorder("Node", "-file", "damped_reso.txt", "-time", "-closeOnWrite", "-node", 1, "-dof", 1, "disp")

# 输出特征值
lambda_ = ops.eigen("-fullGenLapack", 1)
omega = lambda_[0] ** 0.5
print("Omega: ", omega)

# Rayleigh阻尼
damping = 0.05
alpha = 2 * damping * omega
ops.rayleigh(alpha, 0., 0., 0.)

ops.system("BandGeneral")
ops.numberer("Plain")
ops.constraints("Plain")
ops.algorithm("Newton")
ops.analysis("Transient")
ops.integrator("Newmark", 0.5, 0.25)
ops.analyze(6000, 0.01)

# 画图
data6 = np.loadtxt("damped_trig.txt")
data7 = np.loadtxt("damped_reso.txt")
plt.figure()
plt.plot(data6[:, 0], data6[:, 1], label="Damped")
plt.plot(data4[:, 0], data4[:, 1], label="Undamped")
plt.xlabel("Time (s)")
plt.ylabel("Displacement (mm)")
plt.xlim([0, 60])
plt.legend()
plt.show()

plt.figure()
plt.plot(data7[:, 0], data7[:, 1], label="Damped")
plt.plot(data5[:, 0], data5[:, 1], label="Undamped")
plt.xlabel("Time (s)")
plt.ylabel("Displacement (mm)")
plt.xlim([0, 60])
plt.legend()
plt.show()
Omega:  0.7337993857053428
Omega:  0.7337993857053428

png

png

以上两张图,从上面一张可以看出,对于有阻尼结构,其自由振动会逐渐耗散,振动的频率渐渐地与外荷载的频率相同。当外荷载的频率与自振频率接近时,结构发生共振,但是有阻尼结构的振幅不会发散,而是稳定在一个常数。该常数约等于 \( 1/2 \zeta u_{st} \)。

值得注意的是,在使用 eigen 命令输出特征值时,无论结构是否定义了阻尼,输出的都是无阻尼的特征值,即 \(\omega_n\) 。对于有阻尼体系,需要有阻尼特征值 \(\omega_D\) 时,需要自行计算。

SDOF 地震作用

SDOF 在地震作用下的响应

在地震工程研究中,常常需要分析 SDOF 在地震作用下的响应。这里可以使用 OpenSees 的 UniformExcitation 来完成。下面我们来分析一个有阻尼 SDOF 在 El Centro 波下的响应。 El Centro 波的时程可以在 这里下载。需要把解压得到的 accel.txt 文件放在工作目录中。注意这个文件中共3000个点,采样时间间隔为 0.01s,加速度的单位为 g 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# 有阻尼 SDOF 在地震作用下的响应

ops.wipe()
ops.model("basic", "-ndm", 1, "-ndf", 1)
ops.node(0, 0.)
ops.node(1, 0.)
ops.fix(0, 1)
ops.mass(1, 1.)
ops.uniaxialMaterial("Elastic", 1, 5)
ops.element("twoNodeLink", 1, 0, 1, "-mat", 1, "-dir", 1)
# 使用 N mm s 单位系统时,g 要乘9800
ops.timeSeries("Path", 1, "-dt", 0.01, "-filePath", "accel.txt", "-factor", 9800)
ops.pattern("UniformExcitation", 1, 1, "-accel", 1)
ops.recorder("Node", "-file", "excitation_disp.txt", "-time", "-closeOnWrite", "-node", 1, "-dof", 1, "disp")
ops.recorder("Node", "-file", "excitation_vel.txt", "-time", "-closeOnWrite", "-node", 1, "-dof", 1, "vel")
# 记录加速度时,需要使用 -timeSeries 来计算绝对加速度。其他量取相对值。
ops.recorder("Node", "-file", "excitation_accel.txt", "-timeSeries", 1, "-time", "-closeOnWrite", "-node", 1, "-dof", 1, "accel")
ops.recorder("Element", "-file", "excitation_ele.txt", "-time", "-closeOnWrite", "-ele", 1, "material", 1, "stressStrain")

# 输出特征值
lambda_ = ops.eigen("-fullGenLapack", 1)
omega = lambda_[0] ** 0.5
print("Omega: ", omega)
print("Period: ", 2 * 3.14 / omega)

# Rayleigh阻尼
damping = 0.05
alpha = 2 * damping * omega
ops.rayleigh(alpha, 0., 0., 0.)

ops.system("BandGeneral")
ops.numberer("Plain")
ops.constraints("Plain")
ops.algorithm("Newton")
ops.analysis("Transient")
ops.integrator("Newmark", 0.5, 0.25)
ops.analyze(3000, 0.01)

# 画图
disp = np.loadtxt("excitation_disp.txt")
vel = np.loadtxt("excitation_vel.txt")
accel = np.loadtxt("excitation_accel.txt")
force = np.loadtxt("excitation_ele.txt")
fig, ax = plt.subplots(5, 1, figsize=(8, 8), sharex=True)
ground_accel = np.loadtxt("accel.txt") * 9800
ground_time = np.arange(0, 30.01, 0.01)
ax[0].plot(ground_time, ground_accel)
ax[0].set_ylabel("Ground motion\n (mm/s${}^2$)")
ax[1].plot(accel[:, 0], accel[:, 1])
ax[1].set_ylabel("Acceleration\n (mm/s${}^2$)")
ax[2].plot(vel[:, 0], vel[:, 1])
ax[2].set_ylabel("Velocity (mm/s)")
ax[3].plot(disp[:, 0], disp[:, 1])
ax[3].set_ylabel("Displacement (mm)")
ax[4].plot(force[:, 0], force[:, 1])
ax[4].set_ylabel("Force (N)")
ax[4].set_xlabel("Time (s)")
for axis in ax:
axis.set_xlim([0, 30])
plt.show()
Omega:  2.23606797749979
Period:  2.808501379739736

png

从图中可以看出,结构的加速度、位移、内力时程的形状基本是一样的,只差一个系数。内力和加速度的相反数之比为结构质量,内力和位移之比为结构刚度。注意惯性力并不完全与内力相等,它们之间相差一个阻尼力。因为阻尼力很小,可近似认为它们相等。我们把惯性力和内力画在一起做个比较:

1
2
3
4
5
6
7
8
plt.figure()
plt.plot(accel[:, 0], accel[:, 1] * 1.0, label="Inertia force") # 加速度乘以质量为惯性力
plt.plot(force[:, 0], -force[:, 1], label="Restoring force")
plt.xlabel("Time (s)")
plt.ylabel("Force (N)")
plt.xlim([0, 30])
plt.legend()
plt.show()

png

基于 SDOF 画反应谱

下面我们借助 OpenSees ,批量建立 SDOF 结构并求其响应,从而绘制反应谱。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def peakResponse(period, damping=0.05, accel_file="accel.txt"):
motion = np.loadtxt(accel_file) * 9800
if period == 0: # 当周期为0时,结构完全刚性,在这里特殊处理
return max(abs(motion)), 0., 0.
ops.wipe()
ops.model("basic", "-ndm", 1, "-ndf", 1)
ops.node(0, 0.)
ops.node(1, 0.)
ops.fix(0, 1)
ops.mass(1, 1.)
# 计算 period 对应的刚度
stiffness = (2 * np.pi / period) ** 2
ops.uniaxialMaterial("Elastic", 1, stiffness)
ops.element("twoNodeLink", 1, 0, 1, "-mat", 1, "-dir", 1)
ops.timeSeries("Path", 1, "-dt", 0.01, "-filePath", accel_file, "-factor", 9800)
ops.pattern("UniformExcitation", 1, 1, "-accel", 1)
# Rayleigh阻尼
omega = 2 * np.pi / period
alpha = 2 * damping * omega
ops.rayleigh(alpha, 0., 0., 0.)
ops.system("BandGeneral")
ops.numberer("Plain")
ops.constraints("Plain")
ops.algorithm("Newton")
ops.integrator("Newmark", 0.5, 0.25)
ops.analysis("Transient")
max_accel, max_vel, max_disp = 0, 0, 0
for i in range(3000):
ops.analyze(1, 0.01)
max_accel = max(max_accel, abs(ops.nodeAccel(1, 1) + motion[i]))
max_vel = max(max_vel, abs(ops.nodeVel(1, 1)))
max_disp = max(max_disp, abs(ops.nodeDisp(1, 1)))
return max_accel, max_vel, max_disp

periods = np.arange(0, 4, 0.02)
response = [peakResponse(p) for p in periods]
accels, vels, disps = zip(*response)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
plt.figure(figsize=(4, 3))
plt.plot(periods,np.array(accels) / 9800)
plt.xlabel("Period (s)")
plt.ylabel("Acceleration (g)")
plt.xlim([0, 4])
plt.ylim([0, None])

plt.figure(figsize=(4, 3))
plt.plot(periods,np.array(vels) / 1000)
plt.xlabel("Period (s)")
plt.ylabel("Velocity (m/s)")
plt.xlim([0, 4])
plt.ylim([0, None])

plt.figure(figsize=(4, 3))
plt.plot(periods,np.array(disps) / 1000)
plt.xlabel("Period (s)")
plt.ylabel("Displacement (m)")
plt.xlim([0, 4])
plt.ylim([0, None])
(0.0, 0.2628783530740493)

png

png

png

实际上,可以通过将位移反应谱乘以 \(\omega\) 得到伪速度反应谱,再乘以 \(\omega\) 得到伪位移反应谱。我们来比较一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
plt.figure(figsize=(4, 3))
plt.plot(periods, np.array(vels) / 1000, label="Actural")
plt.plot(periods, np.array(disps) * (2 * np.pi / periods) / 1000, label="Pseudo")
plt.xlabel("Period (s)")
plt.ylabel("Velocity (m/s)")
plt.xlim([0, 4])
plt.ylim([0, None])
plt.legend()

plt.figure(figsize=(4, 3))
plt.plot(periods, np.array(accels) / 9800, label="Actural")
plt.plot(periods, np.array(disps) * (2 * np.pi / periods) ** 2 / 9800, label="Pseudo")
plt.xlabel("Period (s)")
plt.ylabel("Acceleration (m/s)")
plt.xlim([0, 4])
plt.ylim([0, None])
plt.legend()

C:\Users\hanlin\AppData\Local\Temp\ipykernel_15968\2916255756.py:3: RuntimeWarning: divide by zero encountered in true_divide
  plt.plot(periods, np.array(disps) * (2 * np.pi / periods) / 1000, label="Pseudo")
C:\Users\hanlin\AppData\Local\Temp\ipykernel_15968\2916255756.py:3: RuntimeWarning: invalid value encountered in multiply
  plt.plot(periods, np.array(disps) * (2 * np.pi / periods) / 1000, label="Pseudo")
C:\Users\hanlin\AppData\Local\Temp\ipykernel_15968\2916255756.py:12: RuntimeWarning: divide by zero encountered in true_divide
  plt.plot(periods, np.array(disps) * (2 * np.pi / periods) ** 2 / 9800, label="Pseudo")
C:\Users\hanlin\AppData\Local\Temp\ipykernel_15968\2916255756.py:12: RuntimeWarning: invalid value encountered in multiply
  plt.plot(periods, np.array(disps) * (2 * np.pi / periods) ** 2 / 9800, label="Pseudo")





<matplotlib.legend.Legend at 0x23506c7ff40>

png

png

白噪声激励下结构响应

下面我们生成一段白噪声,并作为结构的基底加速度输入,来分析结构的响应。再通过响应的结果来判断 SDOF 的自振特性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# 白噪声激励下结构响应

ops.wipe()
ops.model("basic", "-ndm", 1, "-ndf", 1)
ops.node(0, 0.)
ops.node(1, 0.)
ops.fix(0, 1)
ops.mass(1, 1.)
ops.uniaxialMaterial("Elastic", 1, 5)
ops.element("twoNodeLink", 1, 0, 1, "-mat", 1, "-dir", 1)
np.random.seed(10) # 固定随机种子,代码复现用
noise = np.random.normal(loc=0, scale=0.01, size=6000) * 9800 # 生成一段3000个点的白噪声
ops.timeSeries("Path", 1, "-dt", 0.01, "-values", *noise) # 把生成的点输入 timeSeries 中
ops.pattern("UniformExcitation", 1, 1, "-accel", 1)
ops.recorder("Node", "-file", "whitenoise_disp.txt", "-time", "-closeOnWrite", "-node", 1, "-dof", 1, "disp")
ops.recorder("Node", "-file", "whitenoise_vel.txt", "-time", "-closeOnWrite", "-node", 1, "-dof", 1, "vel")
ops.recorder("Node", "-file", "whitenoise_accel.txt", "-timeSeries", 1, "-time", "-closeOnWrite", "-node", 1, "-dof", 1, "accel")
ops.recorder("Element", "-file", "whitenoise_ele.txt", "-time", "-closeOnWrite", "-ele", 1, "material", 1, "stressStrain")

# 输出特征值
lambda_ = ops.eigen("-fullGenLapack", 1)
omega = lambda_[0] ** 0.5
print("Omega: ", omega)

# Rayleigh阻尼
alpha = 2 * damping * omega
ops.rayleigh(alpha, 0., 0., 0.)

ops.system("BandGeneral")
ops.numberer("Plain")
ops.constraints("Plain")
ops.algorithm("Newton")
ops.analysis("Transient")
ops.integrator("Newmark", 0.5, 0.25)
ops.analyze(6000, 0.01)

# 画图
disp = np.loadtxt("whitenoise_disp.txt")
vel = np.loadtxt("whitenoise_vel.txt")
accel = np.loadtxt("whitenoise_accel.txt")
force = np.loadtxt("whitenoise_ele.txt")
fig, ax = plt.subplots(5, 1, figsize=(8, 8), sharex=True)
ground_accel = np.hstack([[0], noise])
ground_time = np.arange(0, 60.01, 0.01)
ax[0].plot(ground_time, ground_accel)
ax[0].set_ylabel("Ground motion\n (mm/s${}^2$)")
ax[1].plot(accel[:, 0], accel[:, 1])
ax[1].set_ylabel("Acceleration\n (mm/s${}^2$)")
ax[2].plot(vel[:, 0], vel[:, 1])
ax[2].set_ylabel("Velocity (mm/s)")
ax[3].plot(disp[:, 0], disp[:, 1])
ax[3].set_ylabel("Displacement (mm)")
ax[4].plot(force[:, 0], force[:, 1])
ax[4].set_ylabel("Force (N)")
ax[4].set_xlabel("Time (s)")
for axis in ax:
axis.set_xlim([0, 60])
plt.show()
Omega:  2.23606797749979

png

下面对结构的位移做傅里叶变换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 傅里叶变换

from scipy.fft import fft, fftfreq

# Number of samples in normalized_tone
SAMPLE_RATE = 100
DURATION = 60
N = SAMPLE_RATE * DURATION

yf = fft(disp[:, 1])
yfabs = np.abs(yf)
xf = fftfreq(N, 1 / SAMPLE_RATE)

plt.plot(xf, yfabs)
plt.xlim([0, 1])
plt.ylim([0, None])
arg = np.argmax(yfabs)
plt.plot(xf[arg], yfabs[arg], "o")
plt.annotate(f"Freq={xf[arg]:.4f}", xy=(xf[arg], yfabs[arg]), xytext=(0.41, 17700))
plt.show()

png

根据结构性质来计算结构的频率:

$$ f_n = \frac{\omega_n}{2\pi} = \frac{1}{2 \pi} \sqrt{\frac{k}{m}} = 0.3561 $$

$$ f_D = f_n / \sqrt{(1 - \zeta^2)} = 0.3565 $$

可见测得的频率与结构的自振频率接近,误差是采样频率导致的。