OpenSees 후처리: 변형형상은 왜 틀릴 수 없고, 어떤 질의는 틀릴 수 있는가
변위 0.0059 mm 와 58.7 mm 가 똑같은 그림을 만든다. opsvis 가 최대 변위를 언제나 모델 최대 치수의 10% 로 그리기 때문이고, 그래서 변형형상은 예상한 방향으로 움직였는지에만 답하고 예상한 만큼 움직였는지에는 결코 답하지 않는다. 고유벡터는 질량정규화되어 돌아오므로 모든 모드의 최
왼쪽 그림은 변위 0.0059 mm이고 오른쪽 그림은 58.7 mm인데, 같은 그림입니다. 비슷한 게 아니라, 그려진 변형량이 소수점 여섯 자리까지 동일합니다. opsvis는 배율을 0.1 × dlmax / max|u|로 고르므로, 어떤 변형형상 그림에서든 최대 변위는 모델의 최대 치수의 정확히 10%로 그려집니다. 실제 값이 무엇이든 상관없이. 변형형상은 움직임의 패턴을 말해줄 뿐 그 크기에 대해서는 아무것도 말하지 않습니다.
이 글의 주제가 그것입니다. 후처리는 틀린 모델이 검토를 가장 자주 통과해 나가는 지점입니다. 그림은 설득력이 있는데, 그림이 눈에 보이게 틀리는 경우는 거의 없기 때문입니다. 이어지는 내용은 눈에 보이게 틀릴 수 있는 질의들 — 기준값이 있는 것들 — 을 OpenSeesPy 3.8.0에서 닫힌해와 대조해 측정한 것입니다. 이 글은 12부입니다. 1–11부에서 객체 모델, 탄성 골조, 파이버 단면, 동적해석, 소스 읽기, 철근콘크리트, 지진기록, 3차원 건물, 수렴 실패, Tcl, 연속체 요소를 다뤘습니다.
1. 변형형상은 크기를 담지 않는다
같은 포털 프레임을 1 kN, 100 kN, 10,000 kN으로 세 번 돌린 결과입니다.
하중실제 변위자동 배율그려진 변형량
1 kN5.874 × 10⁻⁶ m85,121.730.500000 m
100 kN5.874 × 10⁻⁴ m851.220.500000 m
10,000 kN5.874 × 10⁻² m8.510.500000 m
실제 변형은 10,000배 차이입니다. 그려진 변형은 1.000000배 차이입니다. opsvis의 결함이 아닙니다. 자동 배율이 있어야 변형형상이 보이기라도 합니다. 다만 이 그림은 "예상한 방향으로 움직였는가"에 답하지, "예상한 만큼 움직였는가"에는 결코 답하지 않습니다. 숫자를 그림 옆에 찍으십시오.
sfac = opsv.defo_scale(17, ops.getNodeTags()) # 그림이 쓸 배율 opsv.plot_defo(sfac=sfac) plt.title(f'max drift {max(abs(ops.nodeDisp(n,1)) for n in ops.getNodeTags())*1000:.2f} mm' f' (drawn at x{sfac:.0f})')
2. 모드형상은 질량정규화되어 돌아온다
층당 100 t인 3층 전단건물. 고유벡터를 요청하면 모든 모드가 같은 최대 진폭 0.073698을 돌려줍니다. OpenSees가 각 모드를 φᵀMφ = 1이 되도록 정규화하기 때문입니다.
모드주기max |φ|φᵀ M φ
10.44646 s0.0736981.000000
20.15934 s0.0736981.000000
30.11027 s0.0736981.000000
결과는 둘입니다. 원본 고유벡터를 한 축에 겹쳐 그리는 것은 진폭 비교로서 의미가 없습니다. 그 진폭은 물리가 아니라 정규화가 배정한 것이기 때문입니다. 그리기 전에 각자의 최대값으로 나누십시오. 그리고 벡터가 실제로 질량정규화되어 있으므로 모드 참여계수는 분모 없이 Γ = φᵀ M r입니다. 4부의 modalProperties가 그렇게 동작하는 이유입니다.
반나절을 잡아먹는 곁가지 하나. 모델의 자유도 수만큼 모드를 요청하면 기본 솔버로는 실패합니다.
ops.eigen(3) # 자유도 3개 -> ArpackSolver::Error with _saupd info = -3 ops.eigen('-fullGenLapack', 3) # 동작한다
ARPACK은 요청한 고유값보다 많은 Lanczos 벡터가 필요하므로 전체 스펙트럼을 돌려줄 수 없습니다. 작은 모델에서는 -fullGenLapack을 쓰고, 큰 모델에서는 자유도 수보다 적게 요청하십시오.
3. recorder의 열은 스스로를 설명하지 않는다
같은 해석에서 같은 포털 프레임에 건 recorder 둘입니다.
recorder Node -file d.txt -time -node 3 4 -dof 1 2 disp -> 5개 열 recorder Element -file f.txt -ele 1 3 force -> 12개 열
절점 파일은 시간, n3 ux, n3 uy, n4 ux, n4 uy입니다. 절점 우선, 그다음 자유도, 정확히 나열한 순서입니다. 요소 파일에는 시간 열이 아예 없습니다. -time을 요청하지 않았기 때문이고, 12개 열은 요소 2개 × 값 6개입니다. 두 파일을 "0번 열은 시간"이라는 같은 가정으로 읽으면 모든 값이 한 칸씩 밀립니다.
이 부류의 오류를 통째로 없애는 규칙 셋. -time은 모든 recorder에 붙이거나 아무 데도 붙이지 마십시오. 나중에 해독해야 할 넓은 파일 대신 파일 하나에 한 가지 양만 쓰십시오. 그리고 첫 스텝에서 직접 질의와 대조하십시오. 위 해석에서 ops.nodeDisp(3, 1)이 절점 파일의 1번 열과 모든 자리까지 일치했고, 그것이 열 지도가 맞는지 확인하는 가장 싼 방법입니다.
4. force는 전역, basicForce는 기본계, 그리고 평형이 어느 쪽인지 알려준다
(0, 0)에서 (0, 3)까지 가는 기둥에 대해:
eleResponse(1, 'force') -> -50.3114 +26.5609 +92.0817 +50.3114 -26.5609 +58.8526 eleResponse(1, 'basicForce') -> -26.5609 +92.0817 +58.8526
값이 여섯 개 또는 세 개입니다. 여섯 개는 전역 좌표계의 단부력 — 각 단부의 Fx, Fy, Mz — 이고, 그것을 가정이 아니라 증명으로 확인하는 방법은 전역 좌표에서 아래 절점에 대해 모멘트를 취하는 것입니다.
Mi + Mj − 3·Fxj = 92.081682 + 58.852632 − 3 × 50.311438 = 2.842e-14
세 개는 기본력 [N, Mi, Mj]입니다. 축력과 두 단부 모멘트를 요소 자신의 좌표계로 준 것이고, 2부에서 단면에 쓴 바로 그 양입니다. 이 연직 부재에서는 기본 축력이 전역 연직력과 정확히 같은데, 같은 검사를 반대편에서 한 것입니다.
그리고 모델 전체를 덮는 두 줄짜리 검사입니다.
ops.reactions() sum(ops.nodeReaction(n, 1) for n in supports) # 재하 +100.0 에 대해 -100.0000000000 sum(ops.nodeReaction(n, 2) for n in supports) # 재하 -100.0 에 대해 +100.0000000000
전역 평형은 정식화 선택으로도, 메시로도, 그리기 배율로도 속일 수 없는 유일한 후처리 검사입니다. 이것이 닫히지 않으면 그 뒤의 어떤 것도 읽을 값어치가 없습니다.
5. 직사각형 단면에 파이버가 몇 개 필요한가, 정확히
sectionStiffness(eleTag, ip)는 지정한 적분점에서 OpenSees가 실제로 조립한 단면 강성을 돌려줍니다. 2D 보 단면이면 네 숫자, 즉 2 × 2 행렬을 펼친 것이고, sectionResponseType이 순서가 ['P', 'MZ']임을 확인해주므로 0번이 EA, 3번이 EI입니다.
EA와 EI를 아는 탄성 직사각형 파이버 단면을 만들고, 깊이 방향 파이버 수만 바꾸며 되읽습니다.
파이버 수EI / 정확한 EI1 − 1/n²EI 부족분
20.7500000000.75000000025.00%
40.9375000000.9375000006.25%
100.9900000000.9900000001.00%
200.9975000000.9975000000.25%
400.9993750000.9993750000.06%
800.9998437500.9998437500.02%
EI / 정확한 EI = 1 − 1/n², 정확히 그렇습니다. 축강성 EA는 영향을 받지 않습니다. 파이버 면적의 합은 개수와 무관하게 정확합니다. 하지만 2차 모멘트는 스트립에 대한 A·y²의 이산합이고, 적분값보다 정확히 1/n²만큼 모자랍니다.
덕분에 추측이 산수가 됩니다. EI를 오차 e 이내로 맞추려면 n ≥ 1/√e입니다. 1%면 10개, 0.25%면 20개, 0.1%면 32개. 흔히 보이는 "깊이 방향 10개"는 재료 비선형이 개입하기도 전에 탄성 영역에서 EI가 1% 무른 상태이고, 대개 받아들일 만하지만 이것은 무작위 오차가 아니라 계통적 편향이며, 그것을 스스로 선택했다는 사실은 알고 있을 값어치가 있습니다.
6. 단면이 실제로 어디 있는가
2부와 3부는 모두 적분점이 이론이 말하는 자리에 있다는 데 기대고 있었습니다. sectionLocation과 sectionWeight는 여러분이 요청했다고 생각하는 규칙이 아니라 실제로 쓰이고 있는 규칙을 보고합니다.
3점 x/L = 0.000000 0.500000 1.000000 w/L = 0.166667 0.666667 0.166667 합 1.0000000000 5점 x/L = 0.000000 0.172673 0.500000 0.827327 1.000000 w/L = 0.050000 0.272222 0.355556 0.272222 0.050000 합 1.0000000000
5점의 내부 위치 0.172673은 (1 − √(3/7))/2와 소수점 여섯 자리까지 같고, 가중치 합은 열 자리까지 1입니다. 실무적 용도는 둘입니다. 단면 응답을 "밑단"에서 기록할 때 section 1이 x/L = 0인 점인 것은 Lobatto일 때뿐입니다. Gauss–Legendre면 첫 점이 요소 안쪽에 있고 여러분의 "밑단 모멘트"는 밑단 모멘트가 아닙니다. 그리고 가중치는 기록한 각 단면이 요소의 얼마를 대표하는지 알려주는데, 소성힌지 길이를 손으로 적분할 때 필요한 값이 바로 그것입니다.
7. 그림은 모델을 검사하지 못한다
11부에서 파이버 단면을 둘러싼 중괄호가 단면을 닫지 않으므로 블록 뒤의 흘러나온 patch가 단면에 더해져 두 배가 된다는 것을 보였습니다. 그림이 여기서 여러분을 구하지 못하는 이유입니다.
EAEI
의도한 대로7.200000 × 10⁶2.154600 × 10⁵
흘러나온 patch 하나1.440000 × 10⁷4.309200 × 10⁵
비2.0000002.000000
opsvis.plot_fiber_section은 patch와 layer 명령의 파이썬 리스트를 받아 그 리스트를 그립니다. 단면을 도메인에서 되읽는 일은 결코 없으므로, 도메인에는 있고 여러분 리스트에는 없는 파이버를 보여줄 수 없습니다. sectionStiffness는 OpenSees가 만든 것을 읽고, 그것이 이 오류를 한 줄로 잡았습니다.
이것이 한 사례인 일반 규칙은 이렇습니다. 검사는 양쪽이 서로 다른 곳에서 올 때만 검사입니다. 내 입력으로 만든 그림을 내 입력과 대조하는 것은 아무것도 증명하지 않습니다. 도메인에서 읽은 강성을 손계산과 대조하는 것은 무언가를 증명합니다.
8. 각 출력이 말해줄 수 있는 것과 없는 것
출력말해준다말해주지 못한다
변형형상움직임의 패턴크기는 전혀
모드형상 그림어느 층이 함께 움직이는지질량이 얼마나 참여하는지
파이버 단면 그림당신이 쓴 리스트OpenSees가 만든 것
응력 컨투어최대값이 어디인지메시가 수렴했는지
sectionStiffness만들어진 그대로의 EA, EI—
반력 대 재하하중전역 평형—
닫힌해 스칼라한 점에서 전체 사슬—
컨투어 그림은 한 문장을 따로 받을 자격이 있습니다. opsvis.plot_stress의 von Mises 분포는 적분점 값을 절점으로 외삽한 뒤 요소들 사이에서 평균합니다. 그래서 성긴 메시가 매끄럽고 그럴듯하며 틀린 그림을 만듭니다. 평균화가 메시가 너무 성기다고 알려줬을 요소 간 불연속을 정확히 가려버리기 때문입니다. 11부의 조언이 그대로 적용됩니다. 한 번 더 조밀하게 하고 그림이 아니라 최대값을 비교하십시오.
9. 고리를 닫는 후처리 절차
매 해석마다 ops.reactions()로 반력 합을 재하하중과 대조한다.
직접 질의로 스칼라 하나를 되읽어 recorder 파일 첫 줄과 대조한다.
파이버 단면이 있으면 sectionStiffness를 읽어 EA, EI를 손계산과 대조한다.
요소의 "끝"에서 무언가를 기록한다면 sectionLocation을 확인한다.
모드형상은 각자의 최대값으로 나눠 그리고, 옆에 유효질량비를 적는다.
모든 변형형상 옆에 변위 숫자와 쓰인 배율을 찍는다.
-time은 모든 recorder에 붙이거나 아무 데도 붙이지 않으며, 한 해석에서 섞지 않는다.
어떤 컨투어든 믿기 전에 메시를 한 번 더 조밀하게 하고 최대값을 비교한다.
10. 같은 출력을 다섯 단계 깊이로
입문자는 변형형상을 그리고 방향이 맞다고 확인합니다. 주니어는 그 옆에 변위 숫자를 찍습니다. 실무 해석자는 반력을 재하하중과 대조하고 recorder 열 하나를 직접 질의와 맞춰봅니다. 시니어는 sectionStiffness와 sectionLocation을 읽어 도메인 안의 모델이 자기가 기술한 모델인지 확인합니다. 전문가는 자기 검사들 중 어느 것이 같은 출처를 공유해서 아무것도 증명하지 못하는지를 알고, 모든 검사의 양쪽이 서로 다른 곳에서 오도록 배치합니다.
다음 글은 다시 해석으로 돌아갑니다. 좌굴과 한계점 — 고유치 좌굴, 분기점과 한계점의 차이, 그리고 변위제어가 못 하는 일을 호장법이 하는 방식.
전체 스크립트
파이버 개수 법칙을, 가정하지 않고 sectionStiffness 로 측정합니다.
# Part 12 - how many fibres a rectangle needs: EI / EI_exact = 1 - 1/n^2 # units: kN, m import openseespy.opensees as ops B, H, E = 0.40, 0.60, 3.0e7 EA_X, EI_X = E * B * H, E * B * H**3 / 12.0 def section_stiffness(nfib): ops.wipe(); ops.model('basic', '-ndm', 2, '-ndf', 3) ops.uniaxialMaterial('Elastic', 1, E) ops.section('Fiber', 1) ops.patch('rect', 1, nfib, 2, -H/2, -B/2, H/2, B/2) ops.node(1, 0., 0.); ops.node(2, 0., 1.); ops.fix(1, 1, 1, 1) ops.geomTransf('Linear', 1); ops.beamIntegration('Lobatto', 1, 1, 3) ops.element('forceBeamColumn', 1, 1, 2, 1, 1) ops.timeSeries('Linear', 1); ops.pattern('Plain', 1, 1); ops.load(2, 0., -1., 0.) ops.constraints('Plain'); ops.numberer('Plain'); ops.system('BandGeneral') ops.test('NormDispIncr', 1e-12, 10); ops.algorithm('Linear') ops.integrator('LoadControl', 1.0); ops.analysis('Static'); ops.analyze(1) k = ops.sectionStiffness(1, 1) # [EA, ., ., EI] for a 2D beam section return k[0], k[3] print(f'exact EA {EA_X:.6e} EI {EI_X:.6e}\n') print(f"{'fibres':>7s}{'EA error':>12s}{'EI / EI_exact':>16s}{'1 - 1/n^2':>13s}{'difference':>13s}") worst = 0.0 for n in (2, 4, 10, 20, 40, 80): ea, ei = section_stiffness(n) ratio, law = ei / EI_X, 1.0 - 1.0 / n**2 worst = max(worst, abs(ratio - law)) print(f'{n:7d}{abs(ea-EA_X)/EA_X:12.1e}{ratio:16.9f}{law:13.9f}{abs(ratio-law):13.1e}') assert worst < 1e-12, f'the law and the measurement differ by {worst:.1e}' print(f'\nOK the law holds to {worst:.1e}. For EI within e, use n >= 1/sqrt(e).')
실행하면 이렇게 나옵니다.
exact EA 7.200000e+06 EI 2.160000e+05 fibres EA error EI / EI_exact 1 - 1/n^2 difference 2 0.0e+00 0.750000000 0.750000000 1.1e-16 4 0.0e+00 0.937500000 0.937500000 1.1e-16 10 0.0e+00 0.990000000 0.990000000 2.2e-16 20 1.3e-16 0.997500000 0.997500000 1.1e-16 40 3.9e-16 0.999375000 0.999375000 1.1e-16 80 2.6e-16 0.999843750 0.999843750 0.0e+00 OK the law holds to 2.2e-16. For EI within e, use n >= 1/sqrt(e).
OpenSees 시리즈 전체
각 편은 닫힌해·독립적인 풀이·OpenSees 소스 중 하나와 대조해 검증했다. 순서대로 읽도록 썼지만 각 편이 자기 전제를 스스로 밝힌다.
1부 — 도메인 모델과 해석 조립
2부 — 단면·기하변환·분포하중
3부 — 파이버 단면·모멘트–곡률·푸시오버
4부 — 질량·감쇠·시간적분
5부 — 라이브러리 읽기와 재현 가능한 워크플로
6부 — 철근콘크리트 파이버 단면과 구속
7부 — 실제 지진기록·층간변위·증분동적해석
8부 — 3차원: 강막·비틀림·leaning column
9부 — 수렴하지 않을 때: 실패·알고리즘·한계점
10부 — Tcl 읽기와 공개 스크립트 이식
11부 — 쉘·솔리드·지반: 락킹·메시·부지응답
12부 — 후처리: recorder·질의, 그리고 그림이 증명하는 것 — 지금 보는 글
13부 — 좌굴과 한계점: 분기점·스냅백·호장법
14부 — Windows: 설치·파일 형식·조용한 실패
15부 — 검증된 정답이 딸린 연습문제와 한영 용어집
16부 — 어떻게 동작하는가: 시행상태·확정상태·해석 루프
17부 — 커뮤니티가 말하는 것, 측정해보면
18부 — 절점 변위에서 파이버 응력까지
19부 — 면진과 감쇠: 속도 의존 요소와 베어링
참고 자료
opsvis — 여기서 쓴 그리기 라이브러리. 1절의 배율 공식은 defo.py에 있다.
OpenSeesPy, recorder, eleResponse.
OpenSeesPy, sectionStiffness, sectionLocation, sectionWeight.
OpenSeesPy, nodeEigenvector, eigen, nodeReaction.
Chopra, A. K., Dynamics of Structures — 질량정규화와 참여계수.
Scott, M. H., OpenSees Digital — 단면 응답과 recorder에 관한 풍부한 실무 자료.
출처 확인 2026-08-31. 모든 결과는 OpenSeesPy 3.8.0과 opsvis 1.3.7에서 산출했으며 본문의 스크립트로 재현할 수 있습니다. 배율 공식은 opsvis 소스에서 읽은 뒤 측정으로 확인했습니다. 결과는 프레임워크의 거동을 보여주는 것으로, 프로젝트별 해석이나 설계기준 검토를 대체하지 않습니다.